use std::time::{Duration, SystemTime};
use rama_core::error::{BoxError, ErrorContext};
use yasna::{
Tag,
models::{GeneralizedTime, ObjectIdentifier},
};
fn oid_sha1() -> ObjectIdentifier {
ObjectIdentifier::from_slice(&[1, 3, 14, 3, 2, 26])
}
fn oid_ecdsa_sha256() -> ObjectIdentifier {
ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 4, 3, 2])
}
fn oid_rsa_sha256() -> ObjectIdentifier {
ObjectIdentifier::from_slice(&[1, 2, 840, 113549, 1, 1, 11])
}
fn oid_ocsp_basic() -> ObjectIdentifier {
ObjectIdentifier::from_slice(&[1, 3, 6, 1, 5, 5, 7, 48, 1, 1])
}
fn oid_ocsp_nonce() -> ObjectIdentifier {
ObjectIdentifier::from_slice(&[1, 3, 6, 1, 5, 5, 7, 48, 1, 2])
}
fn oid_ad_ocsp() -> ObjectIdentifier {
ObjectIdentifier::from_slice(&[1, 3, 6, 1, 5, 5, 7, 48, 1])
}
#[must_use]
pub fn authority_info_access_ocsp_der(uri: &str) -> Vec<u8> {
yasna::construct_der(|w| {
w.write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_oid(&oid_ad_ocsp());
w.next().write_tagged_implicit(Tag::context(6), |w| {
w.write_bytes(uri.as_bytes());
});
});
});
})
}
#[must_use]
pub fn sha1_hash_algorithm_der() -> Vec<u8> {
yasna::construct_der(|w| {
w.write_sequence(|w| {
w.next().write_oid(&oid_sha1());
w.next().write_null();
});
})
}
#[derive(Debug, Clone, Copy)]
pub enum OcspCertStatus {
Good,
Revoked {
revocation_time: SystemTime,
},
}
#[derive(Debug, Clone, Copy)]
pub enum OcspSignatureAlgorithm {
EcdsaSha256,
RsaSha256,
}
#[derive(Debug, Clone, Copy)]
pub struct OcspCertId<'a> {
pub issuer_name_der: &'a [u8],
pub hash_algorithm_der: &'a [u8],
pub issuer_name_hash: &'a [u8],
pub issuer_key_hash: &'a [u8],
pub serial: &'a [u8],
}
pub fn build_ocsp_response(
cert: &OcspCertId<'_>,
status: OcspCertStatus,
produced_at: SystemTime,
validity: Duration,
nonce: Option<&[u8]>,
sign_tbs: impl FnOnce(&[u8]) -> Result<(OcspSignatureAlgorithm, Vec<u8>), BoxError>,
) -> Result<Vec<u8>, BoxError> {
let revoked_at = match status {
OcspCertStatus::Good => None,
OcspCertStatus::Revoked { revocation_time } => Some(generalized_time(revocation_time)?),
};
let produced = generalized_time(produced_at)?;
let next_at = produced_at
.checked_add(validity)
.ok_or_else(|| BoxError::from("ocsp: nextUpdate overflow"))?;
let next_update = generalized_time(next_at)?;
let tbs_der = yasna::construct_der(|w| {
w.write_sequence(|w| {
w.next()
.write_tagged(Tag::context(1), |w| w.write_der(cert.issuer_name_der));
w.next().write_generalized_time(&produced);
w.next().write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_der(cert.hash_algorithm_der);
w.next().write_bytes(cert.issuer_name_hash);
w.next().write_bytes(cert.issuer_key_hash);
w.next().write_bigint_bytes(cert.serial, true);
});
match &revoked_at {
None => {
w.next()
.write_tagged_implicit(Tag::context(0), |w| w.write_null());
}
Some(revoked_at) => {
w.next().write_tagged_implicit(Tag::context(1), |w| {
w.write_sequence(|w| {
w.next().write_generalized_time(revoked_at);
});
});
}
}
w.next().write_generalized_time(&produced);
w.next()
.write_tagged(Tag::context(0), |w| w.write_generalized_time(&next_update));
});
});
if let Some(nonce) = nonce {
w.next().write_tagged(Tag::context(1), |w| {
w.write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_oid(&oid_ocsp_nonce());
w.next().write_bytes(nonce);
});
});
});
}
});
});
let (alg, signature) = sign_tbs(&tbs_der)?;
let basic_der = yasna::construct_der(|w| {
w.write_sequence(|w| {
w.next().write_der(&tbs_der);
w.next().write_sequence(|w| match alg {
OcspSignatureAlgorithm::EcdsaSha256 => {
w.next().write_oid(&oid_ecdsa_sha256());
}
OcspSignatureAlgorithm::RsaSha256 => {
w.next().write_oid(&oid_rsa_sha256());
w.next().write_null();
}
});
w.next().write_bitvec_bytes(&signature, signature.len() * 8);
});
});
let resp_der = yasna::construct_der(|w| {
w.write_sequence(|w| {
w.next().write_enum(0);
w.next().write_tagged(Tag::context(0), |w| {
w.write_sequence(|w| {
w.next().write_oid(&oid_ocsp_basic());
w.next().write_bytes(&basic_der);
});
});
});
});
Ok(resp_der)
}
#[derive(Debug, Clone)]
pub struct OcspRequestCertId {
pub hash_algorithm_der: Vec<u8>,
pub issuer_name_hash: Vec<u8>,
pub issuer_key_hash: Vec<u8>,
pub serial: Vec<u8>,
}
#[derive(Debug, Clone, Default)]
pub struct OcspRequestInfo {
pub certs: Vec<OcspRequestCertId>,
pub nonce: Option<Vec<u8>>,
}
pub fn parse_ocsp_request(der: &[u8]) -> Result<OcspRequestInfo, BoxError> {
yasna::parse_der(der, |r| {
r.read_sequence(|r| {
let info = r.next().read_sequence(|r| {
r.read_optional(|r| r.read_tagged(Tag::context(0), |r| r.read_i64()))?;
r.read_optional(|r| r.read_tagged(Tag::context(1), |r| r.read_der()))?;
let mut certs = Vec::new();
r.next().read_sequence_of(|r| {
r.read_sequence(|r| {
let cert = r.next().read_sequence(|r| {
let hash_algorithm_der = r.next().read_der()?;
let issuer_name_hash = r.next().read_bytes()?;
let issuer_key_hash = r.next().read_bytes()?;
let (serial, _positive) = r.next().read_bigint_bytes()?;
let serial = match serial.split_first() {
Some((0x00, rest)) if !rest.is_empty() => rest.to_vec(),
_ => serial,
};
Ok(OcspRequestCertId {
hash_algorithm_der,
issuer_name_hash,
issuer_key_hash,
serial,
})
})?;
r.read_optional(|r| r.read_tagged(Tag::context(0), |r| r.read_der()))?;
certs.push(cert);
Ok(())
})
})?;
let nonce = r
.read_optional(|r| {
r.read_tagged(Tag::context(2), |r| {
let mut nonce = None;
r.read_sequence_of(|r| {
r.read_sequence(|r| {
let oid = r.next().read_oid()?;
r.read_optional(|r| r.read_bool())?;
let value = r.next().read_bytes()?;
if oid == oid_ocsp_nonce() {
nonce = Some(value);
}
Ok(())
})
})?;
Ok(nonce)
})
})?
.flatten();
Ok(OcspRequestInfo { certs, nonce })
})?;
r.read_optional(|r| r.read_tagged(Tag::context(0), |r| r.read_der()))?;
Ok(info)
})
})
.map_err(|e| BoxError::from(format!("ocsp: parse request: {e}")))
}
fn generalized_time(t: SystemTime) -> Result<GeneralizedTime, BoxError> {
let secs = t
.duration_since(SystemTime::UNIX_EPOCH)
.context("ocsp: timestamp before unix epoch")?
.as_secs();
let odt = time::OffsetDateTime::from_unix_timestamp(secs as i64)
.map_err(|e| BoxError::from(format!("ocsp: invalid timestamp: {e}")))?;
Ok(GeneralizedTime::from_datetime(odt))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builds_wellformed_ocsp_response() {
let cert = OcspCertId {
issuer_name_der: &yasna::construct_der(|w| {
w.write_sequence(|_| {});
}),
hash_algorithm_der: &sha1_hash_algorithm_der(),
issuer_name_hash: &[0xAA; 20],
issuer_key_hash: &[0xBB; 20],
serial: &[0x12, 0x34, 0x56],
};
let mut signed_tbs: Vec<u8> = Vec::new();
let der = build_ocsp_response(
&cert,
OcspCertStatus::Good,
SystemTime::UNIX_EPOCH + Duration::from_secs(1_800_000_000),
Duration::from_hours(24 * 7),
None,
|tbs| {
signed_tbs = tbs.to_vec();
Ok((
OcspSignatureAlgorithm::EcdsaSha256,
vec![0xDE, 0xAD, 0xBE, 0xEF],
))
},
)
.expect("build ocsp response");
assert!(
!signed_tbs.is_empty(),
"tbsResponseData was handed to the signer"
);
let basic_der = yasna::parse_der(&der, |r| {
r.read_sequence(|r| {
let status = r.next().read_enum()?;
assert_eq!(status, 0, "responseStatus successful");
r.next().read_tagged(Tag::context(0), |r| {
r.read_sequence(|r| {
let oid = r.next().read_oid()?;
assert_eq!(oid, oid_ocsp_basic(), "responseType id-pkix-ocsp-basic");
r.next().read_bytes()
})
})
})
})
.expect("parse OCSPResponse");
yasna::parse_der(&basic_der, |r| {
r.read_sequence(|r| {
let tbs = r.next().read_der()?;
assert_eq!(tbs, signed_tbs, "embedded tbs == signed tbs");
r.next().read_sequence(|r| {
let oid = r.next().read_oid()?;
assert_eq!(oid, oid_ecdsa_sha256());
Ok(())
})?;
let (sig, _bits) = r.next().read_bitvec_bytes()?;
assert_eq!(sig, vec![0xDE, 0xAD, 0xBE, 0xEF]);
Ok(())
})
})
.expect("parse BasicOCSPResponse");
}
fn sha256_hash_algorithm_der() -> Vec<u8> {
yasna::construct_der(|w| {
w.write_sequence(|w| {
w.next().write_oid(&ObjectIdentifier::from_slice(&[
2, 16, 840, 1, 101, 3, 4, 2, 1,
]));
});
})
}
fn build_request(version: bool, nonce: Option<&[u8]>, algid: &[u8]) -> Vec<u8> {
yasna::construct_der(|w| {
w.write_sequence(|w| {
w.next().write_sequence(|w| {
if version {
w.next().write_tagged(Tag::context(0), |w| w.write_i64(0));
}
w.next().write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_der(algid);
w.next().write_bytes(&[0xAA; 20]);
w.next().write_bytes(&[0xBB; 20]);
w.next().write_bigint_bytes(&[0x12, 0x34, 0x56], true);
});
});
});
if let Some(nonce) = nonce {
w.next().write_tagged(Tag::context(2), |w| {
w.write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_oid(&oid_ocsp_nonce());
w.next().write_bytes(nonce);
});
});
});
}
});
});
})
}
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
haystack.windows(needle.len()).any(|w| w == needle)
}
#[test]
fn parses_minimal_request() {
let info = parse_ocsp_request(&build_request(false, None, &sha1_hash_algorithm_der()))
.expect("parse");
assert_eq!(info.certs.len(), 1);
let c = &info.certs[0];
assert_eq!(c.hash_algorithm_der, sha1_hash_algorithm_der());
assert_eq!(c.issuer_name_hash, vec![0xAA; 20]);
assert_eq!(c.issuer_key_hash, vec![0xBB; 20]);
assert_eq!(c.serial, vec![0x12, 0x34, 0x56]);
assert!(info.nonce.is_none());
}
#[test]
fn parses_request_with_version_and_nonce() {
let nonce_value = yasna::construct_der(|w| w.write_bytes(&[1, 2, 3, 4, 5, 6, 7, 8]));
let info = parse_ocsp_request(&build_request(
true,
Some(&nonce_value),
&sha1_hash_algorithm_der(),
))
.expect("parse");
assert_eq!(info.certs.len(), 1);
assert_eq!(info.nonce.as_deref(), Some(nonce_value.as_slice()));
}
#[test]
fn parses_msb_set_serial_as_unsigned_magnitude() {
let algid = sha1_hash_algorithm_der();
let req = yasna::construct_der(|w| {
w.write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_der(&algid);
w.next().write_bytes(&[0xAA; 20]);
w.next().write_bytes(&[0xBB; 20]);
w.next().write_bigint_bytes(&[0xDE, 0xAD, 0xBE, 0xEF], true);
});
});
});
});
});
});
let info = parse_ocsp_request(&req).expect("parse");
assert_eq!(
info.certs[0].serial,
vec![0xDE, 0xAD, 0xBE, 0xEF],
"leading 0x00 sign byte stripped"
);
}
#[test]
fn response_echoes_request_certid_and_nonce() {
let nonce_value = yasna::construct_der(|w| w.write_bytes(&[9, 9, 9, 9]));
let info = parse_ocsp_request(&build_request(
true,
Some(&nonce_value),
&sha1_hash_algorithm_der(),
))
.expect("parse");
let c = &info.certs[0];
let issuer = yasna::construct_der(|w| w.write_sequence(|_| {}));
let cert = OcspCertId {
issuer_name_der: &issuer,
hash_algorithm_der: &c.hash_algorithm_der,
issuer_name_hash: &c.issuer_name_hash,
issuer_key_hash: &c.issuer_key_hash,
serial: &c.serial,
};
let der = build_ocsp_response(
&cert,
OcspCertStatus::Good,
SystemTime::UNIX_EPOCH + Duration::from_secs(1_800_000_000),
Duration::from_hours(24),
info.nonce.as_deref(),
|_| Ok((OcspSignatureAlgorithm::EcdsaSha256, vec![0x00])),
)
.expect("build ocsp response");
assert!(contains(&der, &[0xAA; 20]), "issuerNameHash echoed");
assert!(contains(&der, &[0xBB; 20]), "issuerKeyHash echoed");
assert!(contains(&der, &[0x12, 0x34, 0x56]), "serial echoed");
assert!(contains(&der, &nonce_value), "nonce echoed");
}
fn cert_status_first_byte(response_der: &[u8]) -> u8 {
let basic = yasna::parse_der(response_der, |r| {
r.read_sequence(|r| {
let _status = r.next().read_enum()?;
r.next().read_tagged(Tag::context(0), |r| {
r.read_sequence(|r| {
let _oid = r.next().read_oid()?;
r.next().read_bytes()
})
})
})
})
.expect("parse OCSPResponse");
yasna::parse_der(&basic, |r| {
r.read_sequence(|r| {
let tbs = r.next().read_der()?;
let _alg = r.next().read_der()?;
let _sig = r.next().read_bitvec_bytes()?;
yasna::parse_der(&tbs, |r| {
r.read_sequence(|r| {
let _responder = r.next().read_der()?;
let _produced = r.next().read_der()?;
r.next().read_sequence(|r| {
r.next().read_sequence(|r| {
let _cert_id = r.next().read_der()?;
let cert_status = r.next().read_der()?;
let _this = r.next().read_der()?;
let _next = r.next().read_der()?;
Ok(cert_status[0])
})
})
})
})
})
})
.expect("parse BasicOCSPResponse")
}
fn good_or_revoked(status: OcspCertStatus) -> u8 {
let cert = OcspCertId {
issuer_name_der: &yasna::construct_der(|w| w.write_sequence(|_| {})),
hash_algorithm_der: &sha1_hash_algorithm_der(),
issuer_name_hash: &[0xAA; 20],
issuer_key_hash: &[0xBB; 20],
serial: &[0x12, 0x34, 0x56],
};
let der = build_ocsp_response(
&cert,
status,
SystemTime::UNIX_EPOCH + Duration::from_secs(1_800_000_000),
Duration::from_hours(24),
None,
|_| Ok((OcspSignatureAlgorithm::EcdsaSha256, vec![0x00])),
)
.expect("build ocsp response");
cert_status_first_byte(&der)
}
#[test]
fn revoked_status_encodes_as_context_1() {
assert_eq!(good_or_revoked(OcspCertStatus::Good), 0x80, "good is [0]");
assert_eq!(
good_or_revoked(OcspCertStatus::Revoked {
revocation_time: SystemTime::UNIX_EPOCH + Duration::from_secs(1_700_000_000),
}),
0xA1,
"revoked is [1] IMPLICIT RevokedInfo"
);
}
#[test]
fn echoes_sha256_cert_id_hash_algorithm() {
let sha256 = sha256_hash_algorithm_der();
let info = parse_ocsp_request(&build_request(false, None, &sha256)).expect("parse");
let c = &info.certs[0];
assert_eq!(c.hash_algorithm_der, sha256, "parsed verbatim");
let issuer = yasna::construct_der(|w| w.write_sequence(|_| {}));
let cert = OcspCertId {
issuer_name_der: &issuer,
hash_algorithm_der: &c.hash_algorithm_der,
issuer_name_hash: &c.issuer_name_hash,
issuer_key_hash: &c.issuer_key_hash,
serial: &c.serial,
};
let der = build_ocsp_response(
&cert,
OcspCertStatus::Good,
SystemTime::UNIX_EPOCH + Duration::from_secs(1_800_000_000),
Duration::from_hours(24),
None,
|_| Ok((OcspSignatureAlgorithm::EcdsaSha256, vec![0x00])),
)
.expect("build ocsp response");
assert!(contains(&der, &sha256), "sha256 hashAlgorithm echoed");
}
#[test]
fn aia_ocsp_carries_the_responder_uri() {
let uri = "http://127.0.0.1:9999/ocsp/abc";
let der = authority_info_access_ocsp_der(uri);
let oid = yasna::parse_der(&der, |r| {
r.read_sequence(|r| {
r.next().read_sequence(|r| {
let oid = r.next().read_oid()?;
let _loc = r.next().read_der()?;
Ok(oid)
})
})
})
.expect("AIA structure");
assert_eq!(oid, oid_ad_ocsp());
assert!(contains(&der, uri.as_bytes()), "responder URI present");
}
}