use crate::algorithm::Nested;
use crate::error::{Error, ErrorCode, Located, Result};
use crate::path::WorkingKey;
use structured_asn1::memory::{Execution, MemoryError, Work};
use structured_asn1::{Encoding, Span};
const FIELD: &str = "subjectPublicKeyInfo.effectiveDER";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Part {
Framing,
Algorithm,
Parameters,
PublicKey,
}
#[derive(Debug)]
pub enum VisitError<E> {
Processing {
part: Part,
context: Span,
source: Error,
},
Callback {
part: Part,
context: Span,
source: E,
},
}
#[derive(Debug)]
struct Header {
bytes: [u8; 11],
len: usize,
}
impl Header {
fn new(tag: u8, length: u64) -> Self {
let mut bytes = [0; 11];
bytes[0] = tag;
let encoded = structured_asn1::length::DerLength::new(length);
let encoded = encoded.as_bytes();
bytes[1..=encoded.len()].copy_from_slice(encoded);
Self {
bytes,
len: 1 + encoded.len(),
}
}
fn bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
}
#[derive(Debug)]
pub struct EffectiveSpki<'a> {
key: WorkingKey<'a>,
parameters: Option<Nested<'a>>,
sequence: Header,
algorithm: Header,
oid: Header,
bits: Header,
length: u64,
}
fn overflow(at: u64) -> Error {
Error::resource(MemoryError::WorkExceeded, FIELD, at)
}
fn add(left: u64, right: u64, at: u64) -> Result<u64> {
left.checked_add(right).ok_or_else(|| overflow(at))
}
impl<'a> EffectiveSpki<'a> {
pub fn read<W: Work>(key: WorkingKey<'a>, work: &W) -> Result<Self> {
let own = key.key().algorithm();
let execution = own.query_in(work);
execution
.charge_work(64)
.map_err(|e| Error::resource(e, FIELD, own.offset()))?;
let bytes = key.key().whole_octets(FIELD)?;
let parameters = key.parameter_source().or_else(|| own.parameters_nested());
if let Some(source) = parameters.filter(|source| !source.encoding().is_der()) {
let (reader, element) = source.open(work, FIELD)?;
if !reader.encoding().is_der() {
let mut strict = reader.reread(&element, Encoding::Der).field(FIELD)?;
let original = strict
.next_element()
.field(FIELD)?
.ok_or_else(|| Error::new(ErrorCode::MissingField, FIELD, source.offset()))?;
crate::der::conforms(&strict, &original, FIELD)?;
}
}
let at = own.offset();
let oid = Header::new(6, own.algorithm().as_bytes().len() as u64);
let algorithm_len = add(oid.len as u64, own.algorithm().as_bytes().len() as u64, at)?;
let algorithm_len = add(
algorithm_len,
parameters.map_or(0, |p| p.as_bytes().len() as u64),
at,
)?;
let algorithm = Header::new(48, algorithm_len);
let mut bits = Header::new(3, add(bytes.len() as u64, 1, at)?);
bits.bytes[bits.len] = 0;
bits.len += 1;
let content = add(
add(algorithm.len as u64, algorithm_len, at)?,
add(bits.len as u64, bytes.len() as u64, at)?,
at,
)?;
let sequence = Header::new(48, content);
let length = add(sequence.len as u64, content, at)?;
Ok(Self {
key,
parameters,
sequence,
algorithm,
oid,
bits,
length,
})
}
#[must_use]
pub fn encoded_len(&self) -> u64 {
self.length
}
#[must_use]
pub fn key(&self) -> WorkingKey<'a> {
self.key
}
pub fn execution_in<'q, W: Work>(&'q self, work: &'q W) -> Execution<'q> {
self.key.key().algorithm().query_in(work)
}
pub fn visit<W: Work, E>(
&self,
work: &W,
mut visitor: impl FnMut(Part, &[u8], &Execution<'_>) -> core::result::Result<(), E>,
) -> core::result::Result<(), VisitError<E>> {
let own = self.key.key().algorithm();
let execution = own.query_in(work);
let context = Span {
start: own.offset(),
length: own.as_bytes().len() as u64,
};
for bytes in [
self.sequence.bytes(),
self.algorithm.bytes(),
self.oid.bytes(),
] {
emit(Part::Framing, bytes, &execution, context, &mut visitor)?;
}
emit(
Part::Algorithm,
own.algorithm().as_bytes(),
&execution,
context,
&mut visitor,
)?;
if let Some(source) = self.parameters {
emit(
Part::Parameters,
source.as_bytes(),
&source.execution_in(work),
Span {
start: source.offset(),
length: source.as_bytes().len() as u64,
},
&mut visitor,
)?;
}
emit(
Part::Framing,
self.bits.bytes(),
&execution,
context,
&mut visitor,
)?;
emit(
Part::PublicKey,
self.key.key().key().as_bytes(),
&execution,
Span {
start: self.key.key().key_offset(),
length: self.key.key().key().as_bytes().len() as u64,
},
&mut visitor,
)
}
}
fn emit<E>(
part: Part,
bytes: &[u8],
work: &Execution<'_>,
context: Span,
visitor: &mut impl FnMut(Part, &[u8], &Execution<'_>) -> core::result::Result<(), E>,
) -> core::result::Result<(), VisitError<E>> {
work.charge_work(1).map_err(|e| VisitError::Processing {
part,
context,
source: Error::resource(e, FIELD, context.start),
})?;
visitor(part, bytes, work).map_err(|source| VisitError::Callback {
part,
context,
source,
})
}