use alloc::string::String;
use alloc::vec::Vec;
use crate::error::{Error, Result};
use crate::format::{Reader, read_mpint};
use purecrypto::hash::{Digest, Sha1, Sha256};
const KRL_MAGIC: u64 = 0x5353_484b_524c_0a00;
const KRL_FORMAT_VERSION: u32 = 1;
const SECTION_CERTIFICATES: u8 = 1;
const SECTION_EXPLICIT_KEY: u8 = 2;
const SECTION_FINGERPRINT_SHA1: u8 = 3;
const SECTION_SIGNATURE: u8 = 4;
const SECTION_FINGERPRINT_SHA256: u8 = 5;
const SECTION_EXTENSION: u8 = 255;
const CERT_SERIAL_LIST: u8 = 0x20;
const CERT_SERIAL_RANGE: u8 = 0x21;
const CERT_SERIAL_BITMAP: u8 = 0x22;
const CERT_KEY_ID: u8 = 0x23;
const CERT_EXTENSION: u8 = 0x39;
const MAX_BITMAP_BYTES: usize = 1 << 20;
struct CaRevocation {
ca_key_blob: Vec<u8>,
serials: Vec<u64>,
serial_ranges: Vec<(u64, u64)>,
serial_bitmaps: Vec<(u64, Vec<u8>)>,
key_ids: Vec<String>,
}
impl CaRevocation {
fn covers(&self, serial: u64, key_id: &str) -> bool {
if self.serials.contains(&serial) {
return true;
}
for &(lo, hi) in &self.serial_ranges {
if serial >= lo && serial <= hi {
return true;
}
}
for (offset, bits) in &self.serial_bitmaps {
if serial >= *offset {
let idx = serial - *offset;
if bit_is_set(bits, idx) {
return true;
}
}
}
if !key_id.is_empty() && self.key_ids.iter().any(|k| k == key_id) {
return true;
}
false
}
}
pub struct Krl {
cert_revocations: Vec<CaRevocation>,
explicit_keys: Vec<Vec<u8>>,
fp_sha1: Vec<[u8; 20]>,
fp_sha256: Vec<[u8; 32]>,
}
fn bit_is_set(magnitude: &[u8], idx: u64) -> bool {
let byte_from_end = (idx / 8) as usize;
if byte_from_end >= magnitude.len() {
return false;
}
let byte = magnitude[magnitude.len() - 1 - byte_from_end];
let bit = (idx % 8) as u8;
(byte >> bit) & 1 == 1
}
impl Krl {
pub fn parse(blob: &[u8]) -> Result<Self> {
let mut r = Reader::new(blob);
if r.read_u64()? != KRL_MAGIC {
return Err(Error::Format("krl: bad magic"));
}
if r.read_u32()? != KRL_FORMAT_VERSION {
return Err(Error::Format("krl: unsupported format version"));
}
let _krl_version = r.read_u64()?;
let _generated_date = r.read_u64()?;
let _flags = r.read_u64()?;
let _reserved = r.read_string()?;
let _comment = r.read_string()?;
let mut krl = Krl {
cert_revocations: Vec::new(),
explicit_keys: Vec::new(),
fp_sha1: Vec::new(),
fp_sha256: Vec::new(),
};
while !r.is_empty() {
let section_type = r.read_u8()?;
let body = r.read_string()?;
match section_type {
SECTION_CERTIFICATES => krl.parse_certificates(body)?,
SECTION_EXPLICIT_KEY => parse_blob_list(body, &mut krl.explicit_keys)?,
SECTION_FINGERPRINT_SHA1 => parse_hash_list(body, &mut krl.fp_sha1)?,
SECTION_FINGERPRINT_SHA256 => parse_hash_list(body, &mut krl.fp_sha256)?,
SECTION_SIGNATURE => {}
SECTION_EXTENSION => {}
_ => {}
}
}
Ok(krl)
}
fn parse_certificates(&mut self, body: &[u8]) -> Result<()> {
let mut r = Reader::new(body);
let ca_key_blob = r.read_string()?.to_vec();
let _reserved = r.read_string()?;
let mut rev = CaRevocation {
ca_key_blob,
serials: Vec::new(),
serial_ranges: Vec::new(),
serial_bitmaps: Vec::new(),
key_ids: Vec::new(),
};
while !r.is_empty() {
let sub_type = r.read_u8()?;
let sub = r.read_string()?;
match sub_type {
CERT_SERIAL_LIST => {
let mut sr = Reader::new(sub);
while !sr.is_empty() {
rev.serials.push(sr.read_u64()?);
}
}
CERT_SERIAL_RANGE => {
let mut sr = Reader::new(sub);
let lo = sr.read_u64()?;
let hi = sr.read_u64()?;
if !sr.is_empty() {
return Err(Error::Format("krl: trailing serial-range data"));
}
rev.serial_ranges.push((lo, hi));
}
CERT_SERIAL_BITMAP => {
let mut sr = Reader::new(sub);
let offset = sr.read_u64()?;
let bits = read_mpint(&mut sr)?;
if bits.len() > MAX_BITMAP_BYTES {
return Err(Error::Format("krl: serial bitmap too large"));
}
if !sr.is_empty() {
return Err(Error::Format("krl: trailing serial-bitmap data"));
}
rev.serial_bitmaps.push((offset, bits.to_vec()));
}
CERT_KEY_ID => {
let mut sr = Reader::new(sub);
while !sr.is_empty() {
let id = sr.read_string()?;
let s = core::str::from_utf8(id)
.map_err(|_| Error::Format("krl: non-utf8 key-id"))?;
rev.key_ids.push(s.into());
}
}
CERT_EXTENSION => {}
_ => {}
}
}
self.cert_revocations.push(rev);
Ok(())
}
pub fn is_revoked_cert(&self, ca_key_blob: &[u8], serial: u64, key_id: &str) -> bool {
self.cert_revocations.iter().any(|rev| {
(rev.ca_key_blob.is_empty() || rev.ca_key_blob == ca_key_blob)
&& rev.covers(serial, key_id)
})
}
pub fn is_revoked_key(&self, pubkey_blob: &[u8]) -> bool {
if self.explicit_keys.iter().any(|k| k == pubkey_blob) {
return true;
}
if !self.fp_sha1.is_empty() {
let h = Sha1::digest(pubkey_blob);
if self.fp_sha1.iter().any(|fp| fp == &h) {
return true;
}
}
if !self.fp_sha256.is_empty() {
let h = Sha256::digest(pubkey_blob);
if self.fp_sha256.iter().any(|fp| fp == &h) {
return true;
}
}
false
}
pub fn is_empty(&self) -> bool {
self.cert_revocations.is_empty()
&& self.explicit_keys.is_empty()
&& self.fp_sha1.is_empty()
&& self.fp_sha256.is_empty()
}
}
fn parse_blob_list(body: &[u8], out: &mut Vec<Vec<u8>>) -> Result<()> {
let mut r = Reader::new(body);
while !r.is_empty() {
out.push(r.read_string()?.to_vec());
}
Ok(())
}
fn parse_hash_list<const N: usize>(body: &[u8], out: &mut Vec<[u8; N]>) -> Result<()> {
let mut r = Reader::new(body);
while !r.is_empty() {
let h = r.read_string()?;
let arr: [u8; N] = h
.try_into()
.map_err(|_| Error::Format("krl: bad fingerprint length"))?;
out.push(arr);
}
Ok(())
}
#[cfg(test)]
mod tests;