use super::{BasicResponse, Digest, ResponderMatch};
use crate::error::CallbackError;
use crate::path::{Verified, Verify, WorkingKey};
use crate::{Certificate, Error};
use core::fmt;
use structured_asn1::memory::{MemoryError, Work};
#[derive(Debug)]
pub enum SignatureError<V, D> {
Processing(Error),
Verification(V),
Digest(D),
}
impl<V, D> From<Error> for SignatureError<V, D> {
fn from(error: Error) -> Self {
Self::Processing(error)
}
}
impl<V: fmt::Display, D: fmt::Display> fmt::Display for SignatureError<V, D> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Processing(error) => error.fmt(formatter),
Self::Verification(error) => write!(formatter, "signature verifier: {error}"),
Self::Digest(error) => write!(formatter, "digest provider: {error}"),
}
}
}
#[cfg(feature = "std")]
impl<V: std::error::Error + 'static, D: std::error::Error + 'static> std::error::Error
for SignatureError<V, D>
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Processing(error) => Some(error),
Self::Verification(error) => Some(error),
Self::Digest(error) => Some(error),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignatureFailure {
KeyBinding,
ResponderIdentity,
DigestWidth,
Signature,
}
#[derive(Debug, Clone, Copy)]
pub struct SignedResponse<'r, 'a> {
response: &'r BasicResponse<'a>,
signer: &'r Certificate<'a>,
key: WorkingKey<'a>,
}
impl<'r, 'a> SignedResponse<'r, 'a> {
#[must_use]
pub fn response(self) -> &'r BasicResponse<'a> {
self.response
}
#[must_use]
pub fn signer(self) -> &'r Certificate<'a> {
self.signer
}
#[must_use]
pub fn key(self) -> WorkingKey<'a> {
self.key
}
}
#[derive(Debug)]
pub enum SignatureAuthentication<'r, 'a> {
Signed(SignedResponse<'r, 'a>),
Rejected(SignatureFailure),
UnsupportedDigest,
UnsupportedSignature,
}
impl<'a> BasicResponse<'a> {
pub fn authenticate_signature<'r, V: Verify<E>, D: Digest<E>, E: Work>(
&'r self,
signer: &'r Certificate<'a>,
key: WorkingKey<'a>,
verifier: &V,
digest: &D,
execution: &E,
) -> core::result::Result<SignatureAuthentication<'r, 'a>, SignatureError<V::Error, D::Error>>
{
const FIELD: &str = "ocspResponse.signature";
let charge = |work| {
execution
.charge_work(work)
.map_err(|reason| Error::resource(reason, FIELD, self.span.start))
};
let encoded = key.key().as_bytes();
let actual = signer.tbs().public_key().as_bytes();
let work = u64::try_from(encoded.len().max(actual.len()))
.ok()
.and_then(|n| n.checked_mul(2))
.and_then(|n| n.checked_add(1))
.ok_or_else(|| Error::resource(MemoryError::WorkExceeded, FIELD, self.span.start))?;
charge(work)?;
if encoded != actual {
return Ok(SignatureAuthentication::Rejected(
SignatureFailure::KeyBinding,
));
}
let identity = self
.responder
.matches_certificate(signer, digest, execution, FIELD, self.span.start)
.map_err(|cause| match cause {
CallbackError::Processing(cause) => SignatureError::Processing(cause),
CallbackError::Callback(cause) => SignatureError::Digest(cause),
})?;
match identity {
ResponderMatch::Matches => {}
ResponderMatch::Differs => {
return Ok(SignatureAuthentication::Rejected(
SignatureFailure::ResponderIdentity,
));
}
ResponderMatch::MalformedHashWidth => {
return Ok(SignatureAuthentication::Rejected(
SignatureFailure::DigestWidth,
));
}
ResponderMatch::UnsupportedHash => {
return Ok(SignatureAuthentication::UnsupportedDigest);
}
}
charge(1)?;
match verifier
.verify(&self.algorithm, key, self.signed, self.signature, execution)
.map_err(SignatureError::Verification)?
{
Verified::Yes => Ok(SignatureAuthentication::Signed(SignedResponse {
response: self,
signer,
key,
})),
Verified::No => Ok(SignatureAuthentication::Rejected(
SignatureFailure::Signature,
)),
Verified::Unsupported => Ok(SignatureAuthentication::UnsupportedSignature),
}
}
}
#[cfg(test)]
mod tests {
use super::super::DigestAlgorithm;
use super::*;
use crate::fixtures::{CertificateParts, bits, context, extension, octets, seq, tlv};
use core::{cell::Cell, num::NonZeroUsize};
use structured_asn1::memory::Memory;
use structured_asn1::{BitString, Parser};
struct Provider<'a> {
key: &'a [u8],
signed: &'a [u8],
hashes: Cell<usize>,
signatures: Cell<usize>,
width: usize,
digest_answer: Option<bool>,
signature_answer: Verified,
failure: bool,
}
impl Digest for Provider<'_> {
type Handle = ();
type Error = &'static str;
fn resolve(
&self,
algorithm: DigestAlgorithm<'_>,
_: &Memory,
) -> Result<Option<super::super::ResolvedDigest<()>>, Self::Error> {
assert!(matches!(algorithm, DigestAlgorithm::Sha1));
if self.failure {
return Err("digest refusal");
}
Ok(self.digest_answer.map(|_| super::super::ResolvedDigest {
handle: (),
width: NonZeroUsize::new(self.width).unwrap(),
}))
}
fn matches(
&self,
(): (),
input: &[u8],
expected: &[u8],
_: &Memory,
) -> Result<bool, Self::Error> {
self.hashes.set(self.hashes.get() + 1);
assert_eq!(input.as_ptr(), self.key.as_ptr());
assert_eq!(input, self.key);
assert_eq!(expected, &[0x33; 20]);
Ok(self.digest_answer.unwrap())
}
}
impl Verify for Provider<'_> {
type Error = &'static str;
fn verify(
&self,
_: &crate::AlgorithmIdentifier<'_>,
_: WorkingKey<'_>,
signed: &[u8],
_: BitString<'_>,
_: &Memory,
) -> Result<Verified, Self::Error> {
self.signatures.set(self.signatures.get() + 1);
assert_eq!(signed.as_ptr(), self.signed.as_ptr());
assert_eq!(signed, self.signed);
Ok(self.signature_answer)
}
}
#[test]
fn responder_identity_query_reuses_original_key_bits_without_signature_or_output_copy() {
use crate::error::CallbackError;
use crate::ocsp::{ResponderId, ResponderMatch};
let bytes = CertificateParts::default().build();
let memory = crate::fixtures::memory();
let parser = Parser::der(memory.clone());
let certificate = Certificate::read(&parser, &bytes, 100).unwrap();
let mut provider = Provider {
key: certificate.tbs().public_key().key().as_bytes(),
signed: &[],
hashes: Cell::new(0),
signatures: Cell::new(0),
width: 20,
digest_answer: Some(true),
signature_answer: Verified::Yes,
failure: false,
};
let selector = ResponderId::KeyHash(&[0x33; 20]);
let held = memory.used();
assert_eq!(
selector
.matches_certificate(&certificate, &provider, &memory, "original.byKey", 900)
.unwrap(),
ResponderMatch::Matches
);
assert_eq!((provider.hashes.get(), provider.signatures.get()), (1, 0));
assert_eq!(memory.used(), held);
provider.failure = true;
assert_eq!(
ResponderId::KeyHash(&[0x33; 19])
.matches_certificate(&certificate, &provider, &memory, "original.byKey", 900)
.unwrap(),
ResponderMatch::MalformedHashWidth
);
assert!(matches!(
selector.matches_certificate(&certificate, &provider, &memory, "original.byKey", 900),
Err(CallbackError::Callback("digest refusal"))
));
let stopped = Memory::new(alloc::sync::Arc::new(
structured_asn1::memory::MemoryAccount::new(0, 0),
));
assert!(
matches!(selector.matches_certificate(&certificate,&provider,&stopped,"original.byKey",900),Err(CallbackError::Processing(error)) if error.is_resource() && error.offset()==900)
);
assert_eq!((provider.hashes.get(), provider.signatures.get()), (1, 0));
assert_eq!(memory.used(), held);
}
#[test]
fn issuer_binding_enforces_object_signing_key_usage() {
for (usage, permitted) in [
(None, true),
(Some([7, 0x80]), true),
(Some([6, 0x40]), true),
(Some([1, 0x06]), false),
(Some([5, 0x20]), false),
] {
let certificate_bytes = CertificateParts {
extensions: usage
.map(|value| seq(&[extension(crate::oid::KEY_USAGE, true, &tlv(3, &value))])),
..CertificateParts::default()
}
.build();
let execution = crate::fixtures::memory();
let parser = Parser::der(execution.clone());
let issuer = Certificate::read(&parser, &certificate_bytes, 100).unwrap();
let algorithm = issuer.signature_algorithm().as_bytes();
let timestamp = tlv(24, b"20261012120000Z");
let id = seq(&[
algorithm.to_vec(),
octets(&[0x11; 20]),
octets(&[0x22; 20]),
tlv(2, &[1]),
]);
let single = seq(&[id, tlv(0x80, &[]), timestamp.clone()]);
let data = seq(&[
context(2, &[octets(&[0x33; 20])]),
timestamp,
seq(&[single]),
]);
let bytes = seq(&[data, algorithm.to_vec(), bits(&[0xab])]);
let mut reader = parser.read(&bytes, 700).unwrap();
let root = reader.next_element().unwrap().unwrap();
let response = super::super::read_basic(&reader, &root).unwrap();
let key = WorkingKey::standing(issuer.tbs().public_key());
let provider = Provider {
key: issuer.tbs().public_key().key().as_bytes(),
signed: response.signed_bytes(),
hashes: Cell::new(0),
signatures: Cell::new(0),
width: 20,
digest_answer: Some(true),
signature_answer: Verified::Yes,
failure: false,
};
let SignatureAuthentication::Signed(signed) = response
.authenticate_signature(&issuer, key, &provider, &provider, &execution)
.unwrap()
else {
panic!("signature fixture rejected");
};
let result = signed.bind_issuer(&issuer, key, &execution).unwrap();
assert_eq!(
matches!(result, super::super::AuthorityBinding::Bound(_)),
permitted
);
if !permitted {
assert!(matches!(
result,
super::super::AuthorityBinding::Rejected(
super::super::AuthorityFailure::KeyUsage
)
));
}
assert_eq!(provider.signatures.get(), 1);
}
}
#[test]
fn actual_signer_precedes_hash_and_original_response_data_verification() {
let certificate_bytes = CertificateParts::default().build();
let execution = crate::fixtures::memory();
let parser = Parser::der(execution.clone());
let signer = Certificate::read(&parser, &certificate_bytes, 100).unwrap();
let algorithm = signer.signature_algorithm().as_bytes();
let timestamp = tlv(24, b"20261012120000Z");
let id = seq(&[
algorithm.to_vec(),
octets(&[0x11; 20]),
octets(&[0x22; 20]),
tlv(2, &[1]),
]);
let single = seq(&[id, tlv(0x80, &[]), timestamp.clone()]);
let data = seq(&[
context(2, &[octets(&[0x33; 20])]),
timestamp,
seq(&[single]),
]);
let bytes = seq(&[data, algorithm.to_vec(), bits(&[0xab])]);
let mut reader = parser.read(&bytes, 700).unwrap();
let element = reader.next_element().unwrap().unwrap();
let response = super::super::read_basic(&reader, &element).unwrap();
let key = WorkingKey::standing(signer.tbs().public_key());
let mut provider = Provider {
key: signer.tbs().public_key().key().as_bytes(),
signed: response.signed_bytes(),
hashes: Cell::new(0),
signatures: Cell::new(0),
width: 20,
digest_answer: Some(true),
signature_answer: Verified::Yes,
failure: false,
};
let retained = execution.used();
let SignatureAuthentication::Signed(proof) = response
.authenticate_signature(&signer, key, &provider, &provider, &execution)
.unwrap()
else {
panic!("signature not authenticated");
};
assert!(core::ptr::eq(proof.response(), &raw const response));
assert!(core::ptr::eq(proof.signer(), &raw const signer));
assert_eq!(provider.signatures.get(), 1);
assert_eq!(execution.used(), retained);
provider.signatures.set(0);
provider.width = 19;
assert!(matches!(
response
.authenticate_signature(&signer, key, &provider, &provider, &execution)
.unwrap(),
SignatureAuthentication::Rejected(SignatureFailure::DigestWidth)
));
assert_eq!(provider.signatures.get(), 0);
provider.width = 20;
provider.digest_answer = Some(false);
assert!(matches!(
response
.authenticate_signature(&signer, key, &provider, &provider, &execution)
.unwrap(),
SignatureAuthentication::Rejected(SignatureFailure::ResponderIdentity)
));
assert_eq!(provider.signatures.get(), 0);
provider.digest_answer = None;
assert!(matches!(
response
.authenticate_signature(&signer, key, &provider, &provider, &execution)
.unwrap(),
SignatureAuthentication::UnsupportedDigest
));
provider.failure = true;
assert!(matches!(
response.authenticate_signature(&signer, key, &provider, &provider, &execution),
Err(SignatureError::Digest("digest refusal"))
));
let unfunded = Memory::new(alloc::sync::Arc::new(
structured_asn1::memory::MemoryAccount::new(0, 0),
));
assert!(
matches!(response.authenticate_signature(&signer, key, &provider, &provider, &unfunded), Err(SignatureError::Processing(error)) if error.is_resource() && error.offset() == 700)
);
}
}