use synta::tag::TAG_SEQUENCE;
use synta::traits::Decode;
use synta::{Encoder, Encoding, Tag, ToDer};
use crate::crypto::{KeyIdHasher, KeyIdMethod};
use crate::{
AccessDescription, AuthorityKeyIdentifier, BasicConstraints, DistributionPoint,
DistributionPointName, GeneralNameSpec, GeneralSubtree, IssuingDistributionPoint,
PolicyInformation, PolicyQualifierInfo, SubjectPublicKeyInfo, KEY_USAGE_DECIPHER_ONLY,
};
pub fn encode_basic_constraints(ca: bool, path_length: Option<u64>) -> Option<Vec<u8>> {
use synta::{Boolean, Integer};
let bc = BasicConstraints {
c_a: if ca { Some(Boolean(true)) } else { None },
path_len_constraint: path_length.map(|n| Integer::from(n as i64)),
};
bc.to_der().ok()
}
pub fn encode_key_usage(bits: u16) -> Option<Vec<u8>> {
let b0 = (bits as u8).reverse_bits();
let has_decipher_only = (bits >> KEY_USAGE_DECIPHER_ONLY) & 1 != 0;
let (bytes, unused_bits) = if has_decipher_only {
(vec![b0, 0x80u8], 7u8)
} else if b0 == 0 {
(vec![], 0u8)
} else {
let trailing = b0.trailing_zeros() as u8;
(vec![b0], trailing)
};
let bs = synta::BitString::new(bytes, unused_bits).ok()?;
bs.to_der().ok()
}
pub fn encode_subject_key_identifier<H: KeyIdHasher>(
spki_der: &[u8],
method: KeyIdMethod,
hasher: &H,
) -> Option<Vec<u8>> {
let mut dec = synta::Decoder::new(spki_der, synta::Encoding::Der);
let spki: SubjectPublicKeyInfo<'_> = dec.decode().ok()?;
let hash_input: &[u8] = if method.uses_full_spki_der() {
spki_der
} else {
spki.subject_public_key.as_bytes()
};
let raw_hash = hasher.hash(method.algorithm_oid(), hash_input).ok()?;
let key_id = method.apply_output_length(raw_hash);
let os = synta::OctetString::new(key_id);
os.to_der().ok()
}
pub fn encode_authority_key_identifier<H: KeyIdHasher>(
issuer_spki_der: &[u8],
method: KeyIdMethod,
hasher: &H,
) -> Option<Vec<u8>> {
use synta::OctetStringRef;
let mut dec = synta::Decoder::new(issuer_spki_der, synta::Encoding::Der);
let spki: SubjectPublicKeyInfo<'_> = dec.decode().ok()?;
let hash_input: &[u8] = if method.uses_full_spki_der() {
issuer_spki_der
} else {
spki.subject_public_key.as_bytes()
};
let raw_hash = hasher.hash(method.algorithm_oid(), hash_input).ok()?;
let key_id = method.apply_output_length(raw_hash);
let aki = AuthorityKeyIdentifier {
key_identifier: Some(OctetStringRef::new(&key_id)),
authority_cert_issuer: None,
authority_cert_serial_number: None,
};
aki.to_der().ok()
}
pub(crate) fn encode_sequence(content: Vec<u8>) -> Vec<u8> {
let mut enc = Encoder::new(Encoding::Der);
let _ = enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE));
enc.write_bytes(&content);
let _ = enc.end_constructed();
enc.finish().unwrap_or_default()
}
#[derive(Debug, Default)]
pub struct SubjectAlternativeNameBuilder {
encoded: Vec<u8>,
error: Option<String>,
}
impl SubjectAlternativeNameBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, spec: GeneralNameSpec) {
if self.error.is_some() {
return;
}
let gn = match spec.to_general_name() {
Ok(gn) => gn,
Err(e) => {
self.error = Some(format!("GeneralName error: {e}"));
return;
}
};
match gn.to_der() {
Ok(bytes) => self.encoded.extend_from_slice(&bytes),
Err(e) => self.error = Some(format!("GeneralName DER encoding failed: {e}")),
}
}
pub fn dns_name(mut self, name: &str) -> Self {
self.push(GeneralNameSpec::dns(name));
self
}
pub fn rfc822_name(mut self, email: &str) -> Self {
self.push(GeneralNameSpec::rfc822(email));
self
}
pub fn uri(mut self, uri: &str) -> Self {
self.push(GeneralNameSpec::uri(uri));
self
}
pub fn ip_address(mut self, addr: &[u8]) -> Self {
self.push(GeneralNameSpec::ip_address(addr));
self
}
pub fn directory_name(mut self, name_der: &[u8]) -> Self {
self.push(GeneralNameSpec::directory_name(name_der));
self
}
pub fn registered_id(mut self, oid: synta::ObjectIdentifier) -> Self {
self.push(GeneralNameSpec::registered_id(oid));
self
}
pub fn other_name(mut self, other_name_der: &[u8]) -> Self {
if self.error.is_some() {
return self;
}
let mut dec = synta::Decoder::new(other_name_der, synta::Encoding::Der);
match crate::OtherName::decode(&mut dec) {
Ok(on) => {
let gn = crate::GeneralName::OtherName(on);
match gn.to_der() {
Ok(bytes) => self.encoded.extend_from_slice(&bytes),
Err(e) => self.error = Some(format!("OtherName DER re-encoding failed: {e}")),
}
}
Err(e) => {
self.error = Some(format!("OtherName DER decode failed: {e}"));
}
}
self
}
pub fn build(self) -> Result<Vec<u8>, String> {
if let Some(e) = self.error {
return Err(e);
}
Ok(encode_sequence(self.encoded))
}
}
#[derive(Debug, Default)]
pub struct AuthorityInformationAccessBuilder {
encoded: Vec<u8>,
error: Option<String>,
}
impl AuthorityInformationAccessBuilder {
pub fn new() -> Self {
Self::default()
}
fn push(&mut self, desc: AccessDescription<'_>) {
if self.error.is_some() {
return;
}
match desc.to_der() {
Ok(bytes) => self.encoded.extend_from_slice(&bytes),
Err(e) => self.error = Some(format!("AccessDescription DER encoding failed: {e}")),
}
}
fn push_uri(&mut self, method_oid: &[u32], uri: &str) {
if self.error.is_some() {
return;
}
let access_method = match synta::ObjectIdentifier::new(method_oid) {
Ok(oid) => oid,
Err(e) => {
self.error = Some(format!("invalid access method OID: {e}"));
return;
}
};
let spec = GeneralNameSpec::uri(uri);
let access_location = match spec.to_general_name() {
Ok(gn) => gn,
Err(e) => {
self.error = Some(format!("URI GeneralName error: {e}"));
return;
}
};
self.push(AccessDescription {
access_method,
access_location,
});
}
pub fn ocsp(mut self, uri: &str) -> Self {
self.push_uri(crate::ID_AD_OCSP, uri);
self
}
pub fn ca_issuers(mut self, uri: &str) -> Self {
self.push_uri(crate::ID_AD_CA_ISSUERS, uri);
self
}
pub fn build(self) -> Result<Vec<u8>, String> {
if let Some(e) = self.error {
return Err(e);
}
Ok(encode_sequence(self.encoded))
}
}
#[derive(Debug, Default)]
pub struct ExtendedKeyUsageBuilder {
encoded: Vec<u8>,
error: Option<String>,
}
impl ExtendedKeyUsageBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add_oid(mut self, comps: &[u32]) -> Self {
if self.error.is_some() {
return self;
}
let oid = match synta::ObjectIdentifier::new(comps) {
Ok(oid) => oid,
Err(e) => {
self.error = Some(format!("invalid EKU OID: {e}"));
return self;
}
};
match oid.to_der() {
Ok(bytes) => self.encoded.extend_from_slice(&bytes),
Err(e) => self.error = Some(format!("OID DER encoding failed: {e}")),
}
self
}
pub fn server_auth(self) -> Self {
self.add_oid(crate::ID_KP_SERVER_AUTH)
}
pub fn client_auth(self) -> Self {
self.add_oid(crate::ID_KP_CLIENT_AUTH)
}
pub fn code_signing(self) -> Self {
self.add_oid(crate::ID_KP_CODE_SIGNING)
}
pub fn email_protection(self) -> Self {
self.add_oid(crate::ID_KP_EMAIL_PROTECTION)
}
pub fn time_stamping(self) -> Self {
self.add_oid(crate::ID_KP_TIME_STAMPING)
}
pub fn ocsp_signing(self) -> Self {
self.add_oid(crate::ID_KP_OCSPSIGNING)
}
pub fn build(self) -> Result<Vec<u8>, String> {
if let Some(e) = self.error {
return Err(e);
}
Ok(encode_sequence(self.encoded))
}
}
#[derive(Debug, Default)]
pub struct IssuerAlternativeNameBuilder {
encoded: Vec<u8>,
error: Option<String>,
}
impl IssuerAlternativeNameBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, spec: GeneralNameSpec) {
if self.error.is_some() {
return;
}
let gn = match spec.to_general_name() {
Ok(gn) => gn,
Err(e) => {
self.error = Some(format!("GeneralName error: {e}"));
return;
}
};
match gn.to_der() {
Ok(bytes) => self.encoded.extend_from_slice(&bytes),
Err(e) => {
self.error = Some(format!("GeneralName DER encoding failed: {e}"));
}
}
}
pub fn dns_name(mut self, name: &str) -> Self {
self.push(GeneralNameSpec::dns(name));
self
}
pub fn rfc822_name(mut self, email: &str) -> Self {
self.push(GeneralNameSpec::rfc822(email));
self
}
pub fn uri(mut self, uri: &str) -> Self {
self.push(GeneralNameSpec::uri(uri));
self
}
pub fn ip_address(mut self, addr: &[u8]) -> Self {
self.push(GeneralNameSpec::ip_address(addr));
self
}
pub fn directory_name(mut self, name_der: &[u8]) -> Self {
self.push(GeneralNameSpec::directory_name(name_der));
self
}
pub fn registered_id(mut self, oid: synta::ObjectIdentifier) -> Self {
self.push(GeneralNameSpec::registered_id(oid));
self
}
pub fn build(self) -> Result<Vec<u8>, String> {
if let Some(e) = self.error {
return Err(e);
}
Ok(encode_sequence(self.encoded))
}
}
#[derive(Debug, Default)]
pub struct NameConstraintsBuilder {
permitted_bytes: Vec<u8>,
excluded_bytes: Vec<u8>,
error: Option<String>,
}
impl NameConstraintsBuilder {
pub fn new() -> Self {
Self::default()
}
fn push_subtree(&mut self, spec: GeneralNameSpec, buf: &mut Vec<u8>) {
if self.error.is_some() {
return;
}
let gn = match spec.to_general_name() {
Ok(gn) => gn,
Err(e) => {
self.error = Some(format!("GeneralName error: {e}"));
return;
}
};
let subtree = GeneralSubtree {
base: gn,
minimum: None,
maximum: None,
};
match subtree.to_der() {
Ok(bytes) => buf.extend_from_slice(&bytes),
Err(e) => {
self.error = Some(format!("GeneralSubtree DER encoding failed: {e}"));
}
}
}
pub fn permit_dns(mut self, dns: &str) -> Self {
let mut buf = std::mem::take(&mut self.permitted_bytes);
self.push_subtree(GeneralNameSpec::dns(dns), &mut buf);
self.permitted_bytes = buf;
self
}
pub fn permit_rfc822(mut self, email: &str) -> Self {
let mut buf = std::mem::take(&mut self.permitted_bytes);
self.push_subtree(GeneralNameSpec::rfc822(email), &mut buf);
self.permitted_bytes = buf;
self
}
pub fn permit_uri(mut self, uri: &str) -> Self {
let mut buf = std::mem::take(&mut self.permitted_bytes);
self.push_subtree(GeneralNameSpec::uri(uri), &mut buf);
self.permitted_bytes = buf;
self
}
pub fn permit_ip(mut self, addr_and_mask: &[u8]) -> Self {
let mut buf = std::mem::take(&mut self.permitted_bytes);
self.push_subtree(GeneralNameSpec::ip_address(addr_and_mask), &mut buf);
self.permitted_bytes = buf;
self
}
pub fn permit_directory_name(mut self, name_der: &[u8]) -> Self {
let mut buf = std::mem::take(&mut self.permitted_bytes);
self.push_subtree(GeneralNameSpec::directory_name(name_der), &mut buf);
self.permitted_bytes = buf;
self
}
pub fn exclude_dns(mut self, dns: &str) -> Self {
let mut buf = std::mem::take(&mut self.excluded_bytes);
self.push_subtree(GeneralNameSpec::dns(dns), &mut buf);
self.excluded_bytes = buf;
self
}
pub fn exclude_rfc822(mut self, email: &str) -> Self {
let mut buf = std::mem::take(&mut self.excluded_bytes);
self.push_subtree(GeneralNameSpec::rfc822(email), &mut buf);
self.excluded_bytes = buf;
self
}
pub fn exclude_uri(mut self, uri: &str) -> Self {
let mut buf = std::mem::take(&mut self.excluded_bytes);
self.push_subtree(GeneralNameSpec::uri(uri), &mut buf);
self.excluded_bytes = buf;
self
}
pub fn exclude_ip(mut self, addr_and_mask: &[u8]) -> Self {
let mut buf = std::mem::take(&mut self.excluded_bytes);
self.push_subtree(GeneralNameSpec::ip_address(addr_and_mask), &mut buf);
self.excluded_bytes = buf;
self
}
pub fn exclude_directory_name(mut self, name_der: &[u8]) -> Self {
let mut buf = std::mem::take(&mut self.excluded_bytes);
self.push_subtree(GeneralNameSpec::directory_name(name_der), &mut buf);
self.excluded_bytes = buf;
self
}
pub fn build(self) -> Result<Vec<u8>, String> {
if let Some(e) = self.error {
return Err(e);
}
let wrap_implicit = |tag_byte: u8, content: Vec<u8>| -> Vec<u8> {
let mut out = Vec::with_capacity(content.len() + 4);
out.push(tag_byte);
let len = content.len();
if len < 128 {
out.push(len as u8);
} else if len < 256 {
out.push(0x81);
out.push(len as u8);
} else if len < 65536 {
out.push(0x82);
out.push((len >> 8) as u8);
out.push((len & 0xFF) as u8);
} else {
out.push(0x83);
out.push((len >> 16) as u8);
out.push((len >> 8) as u8);
out.push((len & 0xFF) as u8);
}
out.extend_from_slice(&content);
out
};
let mut content = Vec::new();
if !self.permitted_bytes.is_empty() {
content.extend_from_slice(&wrap_implicit(0xA0, self.permitted_bytes));
}
if !self.excluded_bytes.is_empty() {
content.extend_from_slice(&wrap_implicit(0xA1, self.excluded_bytes));
}
Ok(encode_sequence(content))
}
}
#[derive(Debug, Default)]
pub struct CRLDistributionPointsBuilder {
encoded: Vec<u8>,
error: Option<String>,
}
impl CRLDistributionPointsBuilder {
pub fn new() -> Self {
Self::default()
}
fn push_full_name(&mut self, spec: GeneralNameSpec) {
if self.error.is_some() {
return;
}
let gn = match spec.to_general_name() {
Ok(gn) => gn,
Err(e) => {
self.error = Some(format!("GeneralName error: {e}"));
return;
}
};
let dp = DistributionPoint {
distribution_point: Some(DistributionPointName::FullName(vec![gn])),
reasons: None,
c_rlissuer: None,
};
match dp.to_der() {
Ok(bytes) => self.encoded.extend_from_slice(&bytes),
Err(e) => {
self.error = Some(format!("DistributionPoint DER encoding failed: {e}"));
}
}
}
pub fn full_name_uri(mut self, uri: &str) -> Self {
self.push_full_name(GeneralNameSpec::uri(uri));
self
}
pub fn full_name_dns(mut self, dns: &str) -> Self {
self.push_full_name(GeneralNameSpec::dns(dns));
self
}
pub fn full_name_directory(mut self, name_der: &[u8]) -> Self {
self.push_full_name(GeneralNameSpec::directory_name(name_der));
self
}
pub fn build(self) -> Result<Vec<u8>, String> {
if let Some(e) = self.error {
return Err(e);
}
Ok(encode_sequence(self.encoded))
}
}
#[derive(Debug, Default)]
pub struct IssuingDistributionPointBuilder {
distribution_point: Option<Vec<u8>>,
only_contains_user_certs: bool,
only_contains_cacerts: bool,
indirect_crl: bool,
only_contains_attribute_certs: bool,
error: Option<String>,
}
impl IssuingDistributionPointBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn full_name_uri(mut self, uri: &str) -> Self {
if self.error.is_some() {
return self;
}
let spec = GeneralNameSpec::uri(uri);
let gn = match spec.to_general_name() {
Ok(gn) => gn,
Err(e) => {
self.error = Some(format!("GeneralName error: {e}"));
return self;
}
};
let dpn = DistributionPointName::FullName(vec![gn]);
match dpn.to_der() {
Ok(bytes) => self.distribution_point = Some(bytes),
Err(e) => {
self.error = Some(format!("DistributionPointName DER encoding failed: {e}"));
}
}
self
}
pub fn full_name_dns(mut self, dns: &str) -> Self {
if self.error.is_some() {
return self;
}
let spec = GeneralNameSpec::dns(dns);
let gn = match spec.to_general_name() {
Ok(gn) => gn,
Err(e) => {
self.error = Some(format!("GeneralName error: {e}"));
return self;
}
};
let dpn = DistributionPointName::FullName(vec![gn]);
match dpn.to_der() {
Ok(bytes) => self.distribution_point = Some(bytes),
Err(e) => {
self.error = Some(format!("DistributionPointName DER encoding failed: {e}"));
}
}
self
}
pub fn only_contains_user_certs(mut self, val: bool) -> Self {
self.only_contains_user_certs = val;
self
}
pub fn only_contains_cacerts(mut self, val: bool) -> Self {
self.only_contains_cacerts = val;
self
}
pub fn indirect_crl(mut self, val: bool) -> Self {
self.indirect_crl = val;
self
}
pub fn only_contains_attribute_certs(mut self, val: bool) -> Self {
self.only_contains_attribute_certs = val;
self
}
pub fn build(self) -> Result<Vec<u8>, String> {
if let Some(e) = self.error {
return Err(e);
}
let dp_bytes = self.distribution_point;
let distribution_point = if let Some(ref bytes) = dp_bytes {
let dp_name = synta::Decoder::new(bytes, synta::Encoding::Der)
.decode::<DistributionPointName<'_>>()
.map_err(|e| format!("DistributionPointName re-decode failed: {e}"))?;
Some(dp_name)
} else {
None
};
let idp = IssuingDistributionPoint {
distribution_point,
only_contains_user_certs: self
.only_contains_user_certs
.then_some(synta::Boolean(true)),
only_contains_cacerts: self.only_contains_cacerts.then_some(synta::Boolean(true)),
only_some_reasons: None,
indirect_crl: self.indirect_crl.then_some(synta::Boolean(true)),
only_contains_attribute_certs: self
.only_contains_attribute_certs
.then_some(synta::Boolean(true)),
};
idp.to_der()
.map_err(|e| format!("IssuingDistributionPoint DER encoding failed: {e}"))
}
}
#[derive(Debug, Default)]
pub struct CertificatePoliciesBuilder {
encoded: Vec<u8>,
error: Option<String>,
}
impl CertificatePoliciesBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add_policy(mut self, policy_oid: &[u32]) -> Self {
if self.error.is_some() {
return self;
}
let policy_identifier = match synta::ObjectIdentifier::new(policy_oid) {
Ok(oid) => oid,
Err(e) => {
self.error = Some(format!("invalid policy OID: {e}"));
return self;
}
};
let pi = PolicyInformation {
policy_identifier,
policy_qualifiers: None,
};
match pi.to_der() {
Ok(bytes) => self.encoded.extend_from_slice(&bytes),
Err(e) => self.error = Some(format!("PolicyInformation DER encoding failed: {e}")),
}
self
}
pub fn add_policy_cps(mut self, policy_oid: &[u32], cps_uri: &str) -> Self {
if self.error.is_some() {
return self;
}
let policy_identifier = match synta::ObjectIdentifier::new(policy_oid) {
Ok(oid) => oid,
Err(e) => {
self.error = Some(format!("invalid policy OID: {e}"));
return self;
}
};
let cps_oid = synta::ObjectIdentifier::new(&[1, 3, 6, 1, 5, 5, 7, 2, 1])
.expect("id-qt-cps is a valid OID");
let uri_ref = match synta::IA5StringRef::new(cps_uri) {
Ok(r) => r,
Err(e) => {
self.error = Some(format!("CPS URI contains non-ASCII characters: {e}"));
return self;
}
};
let pqi = PolicyQualifierInfo {
policy_qualifier_id: cps_oid,
qualifier: synta::Element::IA5String(uri_ref),
};
let pi = PolicyInformation {
policy_identifier,
policy_qualifiers: Some(vec![pqi]),
};
match pi.to_der() {
Ok(bytes) => self.encoded.extend_from_slice(&bytes),
Err(e) => self.error = Some(format!("PolicyInformation DER encoding failed: {e}")),
}
self
}
pub fn build(self) -> Result<Vec<u8>, String> {
if let Some(e) = self.error {
return Err(e);
}
Ok(encode_sequence(self.encoded))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cdp_full_name_uri_correct_implicit_tagging() {
let uri = "http://crl.example.com/ca.crl";
let cdp = CRLDistributionPointsBuilder::new()
.full_name_uri(uri)
.build()
.expect("CDP build must succeed");
assert_eq!(cdp[0], 0x30, "outer SEQUENCE tag");
assert_eq!(cdp[2], 0x30, "DistributionPoint SEQUENCE tag");
assert_eq!(cdp[4], 0xA0, "distributionPoint [0] must be A0 (EXPLICIT)");
assert_eq!(
cdp[6], 0xA0,
"fullName [0] IMPLICIT must be A0 (not 0x30 SEQUENCE), got 0x{:02x}",
cdp[6]
);
assert_eq!(cdp[8], 0x86, "[6] uniformResourceIdentifier tag");
let uri_len = cdp[9] as usize;
assert_eq!(&cdp[10..10 + uri_len], uri.as_bytes());
}
#[cfg(feature = "derive")]
#[test]
fn cdp_full_name_uri_roundtrip() {
use crate::{DistributionPoint, DistributionPointName, GeneralName};
let uri = "http://crl.example.com/ca.crl";
let cdp_der = CRLDistributionPointsBuilder::new()
.full_name_uri(uri)
.build()
.expect("CDP build must succeed");
let inner = &cdp_der[2..];
let mut dec = synta::Decoder::new(inner, synta::Encoding::Der);
let dp: DistributionPoint<'_> = dec.decode().expect("DistributionPoint must decode");
let names = match dp
.distribution_point
.expect("distributionPoint must be present")
{
DistributionPointName::FullName(names) => names,
other => panic!("expected FullName, got {:?}", other),
};
assert_eq!(names.len(), 1);
match &names[0] {
GeneralName::UniformResourceIdentifier(s) => {
assert_eq!(s.as_str(), uri);
}
other => panic!("expected URI GeneralName, got {:?}", other),
}
}
}