use base64::prelude::*;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::fs::OpenOptions;
use std::io::{Error, ErrorKind::InvalidInput, Result, Seek, SeekFrom, Write};
use std::path::PathBuf;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use x509_parser::{nom::AsBytes, prelude::*};
pub struct EntryArgs<'a> {
scheme: &'a [u8],
ascii_host: &'a [u8],
port: Option<String>,
base_domain: &'a [u8],
cert: X509Certificate<'a>,
}
impl<'a> EntryArgs<'a> {
pub fn new(
scheme: &'a str,
ascii_host: &'a str,
port: u32,
base_domain: &'a str,
der_cert: &'a [u8],
) -> Result<Self> {
let res = X509Certificate::from_der(der_cert);
match res {
Ok((_rem, cert)) => Ok(EntryArgs {
scheme: scheme.as_bytes(),
ascii_host: ascii_host.as_bytes(),
port: match port {
80 | 443 => None,
p => Some(p.to_string()),
},
base_domain: base_domain.as_bytes(),
cert,
}),
_ => Err(Error::new(
InvalidInput,
format!("x509 parsing failed: {:?}", res),
)),
}
}
}
pub fn write_entry(entry_args: EntryArgs, backing_path: PathBuf) -> Result<()> {
write_entries(vec![entry_args], backing_path)
}
pub fn write_entries(entry_inputs: Vec<EntryArgs>, backing_path: PathBuf) -> Result<()> {
const KEY_LENGTH: usize = 256;
const SLOT_LENGTH: usize = 1286;
let mut backing_file = OpenOptions::new()
.write(true)
.truncate(true)
.open(backing_path)?;
let necessary_len = (entry_inputs.len() * SLOT_LENGTH) as u64;
if backing_file.metadata()?.len() < necessary_len {
backing_file.set_len(necessary_len)?;
}
let mut buf = vec![0u8; SLOT_LENGTH];
for (slot_index, entry_input) in entry_inputs.iter().enumerate() {
let mut buf_writer = buf.as_mut_slice();
buf_writer.write_u16::<BigEndian>(0)?; let mut checksum: u16 = 1; buf_writer.write_u16::<BigEndian>(1)?; let last_accessed = now_in_days();
checksum ^= last_accessed;
buf_writer.write_u16::<BigEndian>(last_accessed)?;
let entry_key = get_entry_key(entry_input).unwrap();
for mut chunk in entry_key.chunks(2) {
if chunk.len() == 1 {
checksum ^= (chunk[0] as u16) << 8;
} else {
checksum ^= chunk.read_u16::<BigEndian>()?;
}
}
buf_writer.write_all(&entry_key)?;
let (key_remainder, mut buf_writer) = buf_writer.split_at_mut(KEY_LENGTH - entry_key.len());
key_remainder.fill(0);
let db_key = get_dbkey(entry_input).unwrap();
for mut chunk in db_key.chunks(2) {
if chunk.len() == 1 {
checksum ^= (chunk[0] as u16) << 8;
} else {
checksum ^= chunk.read_u16::<BigEndian>()?;
}
}
buf_writer.write_all(&db_key)?;
buf_writer.fill(0);
backing_file.seek(SeekFrom::Start((slot_index * SLOT_LENGTH) as u64))?;
backing_file.write_all(&buf)?;
backing_file.flush()?;
backing_file.seek(SeekFrom::Start((slot_index * SLOT_LENGTH) as u64))?;
backing_file.write_u16::<BigEndian>(checksum)?;
}
Ok(())
}
fn now_in_days() -> u16 {
const SECONDS_PER_DAY: u64 = 60 * 60 * 24;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO);
(now.as_secs() / SECONDS_PER_DAY)
.try_into()
.unwrap_or(u16::MAX)
}
fn get_entry_key(entry_input: &EntryArgs) -> Result<Vec<u8>> {
const COMMA_COMMA_CARET: &[u8] = b",,^";
const PARTITION_KEY_EQUALS: &[u8] = b"partitionKey=";
const PERCENT_ENCODED_LEFT_PAREN: &[u8] = b"%28";
const PERCENT_ENCODED_COMMA: &[u8] = b"%2C";
const PERCENT_ENCODED_RIGHT_PAREN: &[u8] = b"%29";
let buf_length = entry_input.ascii_host.len()
+ COMMA_COMMA_CARET.len()
+ PARTITION_KEY_EQUALS.len()
+ PERCENT_ENCODED_LEFT_PAREN.len()
+ entry_input.scheme.len()
+ PERCENT_ENCODED_COMMA.len()
+ entry_input.base_domain.len()
+ match &entry_input.port {
Some(p) => PERCENT_ENCODED_COMMA.len() + p.as_bytes().len(),
None => 0,
}
+ PERCENT_ENCODED_RIGHT_PAREN.len();
let mut buf = vec![0u8; buf_length];
let mut buf_writer = buf.as_mut_slice();
buf_writer.write_all(entry_input.ascii_host.as_bytes())?;
buf_writer.write_all(COMMA_COMMA_CARET)?;
buf_writer.write_all(PARTITION_KEY_EQUALS)?;
buf_writer.write_all(PERCENT_ENCODED_LEFT_PAREN)?;
buf_writer.write_all(entry_input.scheme.as_bytes())?;
buf_writer.write_all(PERCENT_ENCODED_COMMA)?;
buf_writer.write_all(entry_input.base_domain.as_bytes())?;
if entry_input.port.is_some() {
buf_writer.write_all(PERCENT_ENCODED_COMMA)?;
buf_writer.write_all(entry_input.port.as_ref().unwrap().as_bytes())?;
}
buf_writer.write_all(PERCENT_ENCODED_RIGHT_PAREN)?;
Ok(buf)
}
fn get_dbkey(entry_input: &EntryArgs) -> Result<Vec<u8>> {
let serial_bytes = entry_input.cert.raw_serial();
let serial_bytes_len = serial_bytes.len();
let issuer_raw = entry_input.cert.issuer.as_raw();
let issuer_raw_len = issuer_raw.len();
let buf_length = 4 + 4 + 4 + 4 + serial_bytes_len + issuer_raw_len;
let mut buf = vec![0u8; buf_length];
let mut buf_writer = buf.as_mut_slice();
buf_writer.write_u32::<BigEndian>(0)?; buf_writer.write_u32::<BigEndian>(0)?; buf_writer.write_u32::<BigEndian>(serial_bytes_len as u32)?; buf_writer.write_u32::<BigEndian>(issuer_raw_len as u32)?; buf_writer.write_all(serial_bytes)?; buf_writer.write_all(issuer_raw)?;
Ok(BASE64_STANDARD.encode(buf).into_bytes())
}