#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "alloc")]
extern crate alloc;
mod test_lifetime;
pub(crate) mod pkcs11_uri;
pub use pkcs11_uri::{merge_object_label, pct_encode_path, Pkcs11Uri, Pkcs11UriAttributes};
#[cfg(feature = "pkcs11-mgmt")]
pub mod pkcs11_mgmt;
#[cfg(feature = "pkcs11-mgmt")]
pub use crypto::token_manager::{Pkcs11KeyInfo, SlotInfo, TokenManager};
#[cfg(feature = "pkcs11-mgmt")]
pub fn pkcs11_manager() -> Result<pkcs11_mgmt::Pkcs11Manager, crypto::PrivateKeyError> {
pkcs11_mgmt::Pkcs11Manager::from_env()
}
#[cfg(feature = "pkcs11-mgmt")]
pub fn list_pkcs11_slots() -> Result<Vec<SlotInfo>, crypto::PrivateKeyError> {
pkcs11_manager()?.list_slots()
}
pub mod name;
pub use name::{decode_string_value, format_dn, format_dn_slash, parse_name_attrs, NameBuilder};
pub mod pubkey;
pub use pubkey::{decode_public_key_info, PublicKeyInfo};
pub mod oids;
pub mod names;
pub mod pem;
pub use pem::{decode_base64, der_to_pem, encode_base64, pem_blocks, pem_to_der};
pub mod time_utils;
pub use time_utils::{parse_generalized_time, parse_time};
include!(concat!(env!("OUT_DIR"), "/x509_borrowed.rs"));
pub mod owned {
include!(concat!(env!("OUT_DIR"), "/x509_owned.rs"));
}
pub mod csr {
include!(concat!(env!("OUT_DIR"), "/csr_borrowed.rs"));
}
pub mod crl {
include!(concat!(env!("OUT_DIR"), "/crl_borrowed.rs"));
}
pub mod ocsp {
include!(concat!(env!("OUT_DIR"), "/ocsp_borrowed.rs"));
}
pub mod pkcs7_types {
include!(concat!(env!("OUT_DIR"), "/pkcs7_generated.rs"));
}
pub mod pkcs12_types {
include!(concat!(env!("OUT_DIR"), "/pkcs12_generated.rs"));
}
pub mod pkcs9_types {
include!(concat!(env!("OUT_DIR"), "/pkcs9_generated.rs"));
}
pub mod pkcs1_types {
include!(concat!(env!("OUT_DIR"), "/pkcs1_generated.rs"));
}
pub use pkcs1_types::RsaPublicKey;
pub use mldsa_types::{
MlDsa44PrivateKey, MlDsa44PrivateKeyBoth, MlDsa44PublicKey, MlDsa65PrivateKey,
MlDsa65PrivateKeyBoth, MlDsa65PublicKey, MlDsa87PrivateKey, MlDsa87PrivateKeyBoth,
MlDsa87PublicKey,
};
pub mod mldsa_types {
include!(concat!(env!("OUT_DIR"), "/mldsa_generated.rs"));
}
pub mod pkixalgs_types {
include!(concat!(env!("OUT_DIR"), "/pkixalgs_generated.rs"));
}
#[allow(clippy::large_enum_variant)]
pub mod attribute_cert_types {
include!(concat!(env!("OUT_DIR"), "/attribute_cert_generated.rs"));
}
#[allow(clippy::large_enum_variant)]
pub mod crmf_types {
include!(concat!(env!("OUT_DIR"), "/crmf_generated.rs"));
}
pub mod crmf_builder;
pub use crmf_builder::{
CertReqMessagesBuilder, CertReqMsgBuilder, PUB_METHOD_DONT_CARE, PUB_METHOD_LDAP,
PUB_METHOD_WEB, PUB_METHOD_X500,
};
pub mod cmp_types {
include!(concat!(env!("OUT_DIR"), "/cmp_generated.rs"));
}
pub mod cmp_builder;
pub use cmp_builder::CMPMessageBuilder;
pub mod rfc9925_types {
include!(concat!(env!("OUT_DIR"), "/rfc9925_generated.rs"));
}
pub mod ace88_types {
include!(concat!(env!("OUT_DIR"), "/ace88_generated.rs"));
}
pub mod cbor_content_types {
include!(concat!(env!("OUT_DIR"), "/cbor_content_types_generated.rs"));
}
pub mod acme_types {
include!(concat!(env!("OUT_DIR"), "/acme_rfc8737_generated.rs"));
}
pub mod rpki_manifest_types {
include!(concat!(env!("OUT_DIR"), "/rpki_manifest_generated.rs"));
}
pub mod smime_v3dot1_types {
include!(concat!(env!("OUT_DIR"), "/smime_v3dot1_generated.rs"));
}
pub mod pkcs12_pbmac1_2023_types {
include!(concat!(env!("OUT_DIR"), "/pkcs12_pbmac1_2023_generated.rs"));
}
pub mod cms_cek_hkdf_sha256_2023_types {
include!(concat!(
env!("OUT_DIR"),
"/cms_cek_hkdf_sha256_2023_generated.rs"
));
}
pub mod cms_ori_for_psk_2019_types {
include!(concat!(
env!("OUT_DIR"),
"/cms_ori_for_psk_2019_generated.rs"
));
}
pub mod cms_gmac_algorithms_types {
include!(concat!(
env!("OUT_DIR"),
"/cms_gmac_algorithms_generated.rs"
));
}
pub mod rpc_with_tls_2021_types {
include!(concat!(env!("OUT_DIR"), "/rpc_with_tls_2021_generated.rs"));
}
pub mod hkdf_oid_2019_types {
include!(concat!(env!("OUT_DIR"), "/hkdf_oid_2019_generated.rs"));
}
pub mod pkcs8_types {
include!(concat!(env!("OUT_DIR"), "/pkcs8_generated.rs"));
}
pub mod pkinit_types {
include!(concat!(env!("OUT_DIR"), "/pkinit_generated.rs"));
}
pub mod ms_pki_types {
include!(concat!(env!("OUT_DIR"), "/ms_pki_generated.rs"));
}
pub mod pkcs5_types {
include!(concat!(env!("OUT_DIR"), "/pkcs5_generated.rs"));
}
pub mod pkix_common_types {
include!(concat!(env!("OUT_DIR"), "/pkix_common_types_generated.rs"));
}
pub mod alg_info_types {
include!(concat!(env!("OUT_DIR"), "/alg_info_generated.rs"));
}
pub mod pkixalgs_2009_types {
include!(concat!(env!("OUT_DIR"), "/pkixalgs_2009_generated.rs"));
}
pub mod pkix1_explicit_types {
include!(concat!(env!("OUT_DIR"), "/pkix1_explicit_generated.rs"));
}
pub mod pkix1_implicit_types {
include!(concat!(env!("OUT_DIR"), "/pkix1_implicit_generated.rs"));
}
pub mod cms_2010_types {
include!(concat!(env!("OUT_DIR"), "/cms_2010_generated.rs"));
}
pub mod kem_alg_info_types {
include!(concat!(env!("OUT_DIR"), "/kem_alg_info_generated.rs"));
}
pub mod pkix_test_cert_policies_types {
include!(concat!(
env!("OUT_DIR"),
"/pkix_test_cert_policies_generated.rs"
));
}
pub mod cms_kem_types {
include!(concat!(env!("OUT_DIR"), "/cms_kem_generated.rs"));
}
pub mod cms_rfc5652_types {
include!(concat!(env!("OUT_DIR"), "/cms_rfc5652_generated.rs"));
}
pub mod cert_image_module_types {
include!(concat!(env!("OUT_DIR"), "/cert_image_module_generated.rs"));
}
pub mod logotype_cert_extn_types {
include!(concat!(env!("OUT_DIR"), "/logotype_cert_extn_generated.rs"));
}
pub mod ess_types {
include!(concat!(env!("OUT_DIR"), "/ess_generated.rs"));
}
pub mod tsp_types {
include!(concat!(env!("OUT_DIR"), "/pkixtsp_generated.rs"));
}
pub mod cms_2009_types {
include!(concat!(env!("OUT_DIR"), "/cms_2009_generated.rs"));
}
pub mod pkix1_pss_oaep_alg_2009_types {
include!(concat!(
env!("OUT_DIR"),
"/pkix1_pss_oaep_alg_2009_generated.rs"
));
}
#[allow(unused_imports, dead_code)]
pub mod ocsp_2024_88_types {
include!(concat!(env!("OUT_DIR"), "/ocsp_2024_88_generated.rs"));
}
#[allow(unused_imports, dead_code)]
pub mod ocsp_2024_08_types {
include!(concat!(env!("OUT_DIR"), "/ocsp_2024_08_generated.rs"));
}
#[allow(unused_imports, dead_code)]
pub mod no_rev_avail_extn_types {
include!(concat!(env!("OUT_DIR"), "/no_rev_avail_extn_generated.rs"));
}
#[allow(unused_imports, dead_code)]
pub mod delegated_cred_extn_types {
include!(concat!(
env!("OUT_DIR"),
"/delegated_cred_extn_generated.rs"
));
}
#[allow(unused_imports, dead_code)]
pub mod nf_type_cert_extn_types {
include!(concat!(env!("OUT_DIR"), "/nf_type_cert_extn_generated.rs"));
}
#[allow(unused_imports, dead_code)]
pub mod pk_validation_attr_types {
include!(concat!(env!("OUT_DIR"), "/pk_validation_attr_generated.rs"));
}
#[allow(unused_imports, dead_code)]
pub mod tls_feature_module_types {
include!(concat!(env!("OUT_DIR"), "/tls_feature_module_generated.rs"));
}
pub mod slh_dsa_module_2024_types {
include!(concat!(
env!("OUT_DIR"),
"/slh_dsa_module_2024_generated.rs"
));
}
pub mod x509_ml_dsa_2025_types {
include!(concat!(env!("OUT_DIR"), "/x509_ml_dsa_2025_generated.rs"));
}
pub mod x509_ml_kem_2025_types {
include!(concat!(env!("OUT_DIR"), "/x509_ml_kem_2025_generated.rs"));
}
pub mod crypto;
pub use crypto::{
constant_time_eq, default_key_id_hasher, default_signature_verifier, BackendPrivateKey,
BackendPublicKey, BlockCipherProvider, CertificateSigner, CmsDecryptor, CmsEncryptor,
DataHasher, Encryptor, EnvelopedDataDecryptor, ErasedCertificateSigner, ErasedDataHasher,
ErasedHmacProvider, ErasedKeyIdHasher, ErasedSignatureVerifier, ErasedStreamingHasher,
ErasedStreamingHmacProvider, HashState, HmacProvider, HmacState, KeyDecryptor, KeyEncryptor,
KeyIdHasher, KeyIdMethod, KeySpec, KeyWrapAlgorithm, NoCmsDecryptor, NoCrypto, NoCryptoError,
NoEncryptor, NoEncryptorError, NoEnvelopedDataDecryptor, NoEnvelopedDataDecryptorError,
NoKeyIdHasher, NoKeyIdHasherError, NoPkcs12Encryptor, NoSignatureVerifier,
NoSignatureVerifierError, NoSigner, NoSignerError, NoSymmetricCrypto, Pbkdf2Provider,
Pkcs12Decryptor, Pkcs12Encryptor, PrivateKey, PrivateKeyBuilder, PrivateKeyError,
RsaPrivateComponents, SecureRandom, SignatureVerifier, StreamingHasher, StreamingHmacProvider,
UnsignedCertificateSigner,
};
#[cfg(any(feature = "openssl", feature = "nss"))]
pub use crypto::{
default_create_enveloped_data, default_prepare_enveloped_data, DefaultCrypto,
DefaultCryptoError, DefaultEnvelopedDataDecryptor,
};
pub use crypto::{hkdf_expand, hkdf_extract, hmac_output_len};
pub fn default_data_hasher() -> Box<dyn crypto::ErasedDataHasher> {
#[cfg(all(feature = "nss", not(feature = "openssl")))]
{
crate::nss_backend::nss_data_hasher()
}
#[cfg(feature = "openssl")]
{
crate::openssl_backend::openssl_data_hasher()
}
#[cfg(not(any(feature = "openssl", feature = "nss")))]
{
Box::new(crypto::NoSymmetricCrypto)
}
}
pub fn default_hmac_provider() -> Box<dyn crypto::ErasedHmacProvider> {
#[cfg(all(feature = "nss", not(feature = "openssl")))]
{
crate::nss_backend::nss_hmac_provider()
}
#[cfg(feature = "openssl")]
{
crate::openssl_backend::openssl_hmac_provider()
}
#[cfg(not(any(feature = "openssl", feature = "nss")))]
{
Box::new(crypto::NoSymmetricCrypto)
}
}
pub fn default_streaming_hasher() -> Box<dyn crypto::ErasedStreamingHasher> {
#[cfg(all(feature = "nss", not(feature = "openssl")))]
{
crate::nss_backend::nss_streaming_hasher()
}
#[cfg(feature = "openssl")]
{
crate::openssl_backend::openssl_streaming_hasher()
}
#[cfg(not(any(feature = "openssl", feature = "nss")))]
{
Box::new(crypto::NoSymmetricCrypto)
}
}
pub fn default_streaming_hmac_provider() -> Box<dyn crypto::ErasedStreamingHmacProvider> {
#[cfg(all(feature = "nss", not(feature = "openssl")))]
{
crate::nss_backend::nss_streaming_hmac_provider()
}
#[cfg(feature = "openssl")]
{
crate::openssl_backend::openssl_streaming_hmac_provider()
}
#[cfg(not(any(feature = "openssl", feature = "nss")))]
{
Box::new(crypto::NoSymmetricCrypto)
}
}
pub fn default_pbkdf2_provider() -> impl crypto::Pbkdf2Provider {
#[cfg(all(feature = "nss", not(feature = "openssl")))]
{
crate::nss_backend::nss_pbkdf2_provider()
}
#[cfg(feature = "openssl")]
{
crate::openssl_backend::openssl_symmetric_crypto()
}
#[cfg(not(any(feature = "openssl", feature = "nss")))]
{
crypto::NoSymmetricCrypto
}
}
pub fn default_block_cipher_provider() -> impl crypto::BlockCipherProvider {
#[cfg(all(feature = "nss", not(feature = "openssl")))]
{
crate::nss_backend::nss_block_cipher_provider()
}
#[cfg(feature = "openssl")]
{
crate::openssl_backend::openssl_symmetric_crypto()
}
#[cfg(not(any(feature = "openssl", feature = "nss")))]
{
crypto::NoSymmetricCrypto
}
}
pub fn default_secure_random() -> impl crypto::SecureRandom {
#[cfg(all(feature = "nss", not(feature = "openssl")))]
{
crate::nss_backend::nss_secure_random()
}
#[cfg(feature = "openssl")]
{
crate::openssl_backend::openssl_symmetric_crypto()
}
#[cfg(not(any(feature = "openssl", feature = "nss")))]
{
crypto::NoSymmetricCrypto
}
}
pub mod pkcs7;
pub use pkcs7::{certs_from_pkcs7, Pkcs7Error};
pub mod pkcs12;
pub use pkcs12::{certs_from_pkcs12, keys_from_pkcs12, pki_from_pkcs12, Pkcs12Error, Pkcs12Pki};
pub mod pkcs12_builder;
pub use pkcs12_builder::{Pkcs12Builder, Pkcs12BuilderError};
pub mod enveloped_data_builder;
pub use enveloped_data_builder::{EnvelopedDataBuilder, EnvelopedDataBuilderError};
#[cfg(all(feature = "nss", not(feature = "openssl")))]
pub mod nss_backend;
#[cfg(all(feature = "nss", not(feature = "openssl")))]
pub use nss_backend::{NssKeyIdHasher, NssSignatureVerifier, NssVerifierError};
#[cfg(feature = "openssl")]
pub mod openssl_backend;
#[cfg(feature = "openssl")]
pub use openssl_backend::{
create_enveloped_data, prepare_enveloped_data, OpensslCertificateSigner,
OpensslCertificateSignerError, OpensslDecryptor, OpensslDecryptorError, OpensslEncryptor,
OpensslEncryptorError, OpensslEnvelopedDataDecryptor, OpensslKeyError, OpensslKeyIdHasher,
OpensslKeyIdHasherError, OpensslPkcs12Encryptor, OpensslPrivateKey, OpensslRsaOaepDecryptor,
OpensslRsaOaepEncryptor, OpensslRsaPkcs1Decryptor, OpensslRsaPkcs1Encryptor,
OpensslSignatureVerifier, OpensslSymmetricCrypto, OpensslSymmetricError, OpensslVerifierError,
Pkcs12Cipher, Pkcs12Config, Pkcs12HmacAlgorithm,
};
pub mod ext_builder;
pub use ext_builder::{
encode_authority_key_identifier, encode_basic_constraints, encode_key_usage,
encode_subject_key_identifier, AuthorityInformationAccessBuilder, CRLDistributionPointsBuilder,
CertificatePoliciesBuilder, ExtendedKeyUsageBuilder, IssuerAlternativeNameBuilder,
IssuingDistributionPointBuilder, NameConstraintsBuilder, SubjectAlternativeNameBuilder,
};
pub mod builder;
pub use builder::{BuilderError, CertificateBuilder};
pub mod csr_builder;
pub use csr_builder::{CsrBuilder, CsrBuilderError};
pub mod ac_builder;
pub use ac_builder::AttributeCertificateBuilder;
pub mod crl_builder;
pub use crl_builder::CertificateListBuilder;
pub mod ocsp_builder;
pub use ocsp_builder::{OCSPResponseBuilder, SingleResponseSpec};
pub mod ocsp_request_builder;
pub use ocsp_request_builder::{CertIDSpec, OCSPRequestBuilder};
pub mod tsp_builder;
pub use tsp_builder::TimeStampReqBuilder;
pub mod ess_builder;
pub use ess_builder::{ESSSecurityLabelBuilder, ReceiptRequestBuilder, SigningCertificateBuilder};
pub mod pkcs5_builder;
pub use pkcs5_builder::{Pbes2ParamsBuilder, Pbkdf2ParamsBuilder};
pub mod logotype_builder;
pub use logotype_builder::{LogotypeDetailsSpec, LogotypeExtnBuilder};
pub mod ace88_builder;
pub use ace88_builder::AuthenticationContextsBuilder;
pub mod reader;
pub use reader::{read_pki_blocks, PkiDecryptor, ReadAnyError};
pub fn identify_signature_algorithm(oid: &ObjectIdentifier) -> &'static str {
let c = oid.components();
const ED0: u32 = oids::ED25519[0]; const ED1: u32 = oids::ED25519[1]; const ED2: u32 = oids::ED25519[2]; const ED25519_V: u32 = oids::ED25519[3]; const ED448_V: u32 = oids::ED448[3];
const PQC0: u32 = oids::ML_DSA_44[0]; const PQC1: u32 = oids::ML_DSA_44[1]; const PQC2: u32 = oids::ML_DSA_44[2]; const PQC3: u32 = oids::ML_DSA_44[3]; const PQC4: u32 = oids::ML_DSA_44[4]; const PQC5: u32 = oids::ML_DSA_44[5]; const PQC6: u32 = oids::ML_DSA_44[6]; const MLDSA_D: u32 = oids::ML_DSA_44[7]; const MLDSA44_V: u32 = oids::ML_DSA_44[8]; const MLDSA65_V: u32 = oids::ML_DSA_65[8]; const MLDSA87_V: u32 = oids::ML_DSA_87[8];
const RSA0: u32 = oids::RSA[0]; const RSA1: u32 = oids::RSA[1]; const RSA2: u32 = oids::RSA[2]; const RSA3: u32 = oids::RSA[3]; const RSA4: u32 = oids::RSA[4]; const RSA5: u32 = oids::RSA[5]; const MD5_RSA_V: u32 = oids::MD5_WITH_RSA[6]; const SHA1_RSA_V: u32 = oids::SHA1_WITH_RSA[6]; const SHA256_RSA_V: u32 = oids::SHA256_WITH_RSA[6]; const SHA384_RSA_V: u32 = oids::SHA384_WITH_RSA[6]; const SHA512_RSA_V: u32 = oids::SHA512_WITH_RSA[6];
const EC0: u32 = oids::ECDSA_SIG[0]; const EC1: u32 = oids::ECDSA_SIG[1]; const EC2: u32 = oids::ECDSA_SIG[2]; const EC3: u32 = oids::ECDSA_SIG[3]; const EC4: u32 = oids::ECDSA_SIG[4]; const ECDSA_SHA1_V: u32 = oids::ECDSA_WITH_SHA1[5]; const ECDSA_SUB: u32 = oids::ECDSA_WITH_SHA256[5]; const ECDSA_SHA256_V: u32 = oids::ECDSA_WITH_SHA256[6]; const ECDSA_SHA384_V: u32 = oids::ECDSA_WITH_SHA384[6]; const ECDSA_SHA512_V: u32 = oids::ECDSA_WITH_SHA512[6];
if let &[ED0, ED1, ED2, sub] = c {
return match sub {
ED25519_V => names::ED25519,
ED448_V => names::ED448,
_ => names::OTHER,
};
}
if let &[PQC0, PQC1, PQC2, PQC3, PQC4, PQC5, PQC6, MLDSA_D, sub] = c {
match sub {
MLDSA44_V => return names::ML_DSA_44,
MLDSA65_V => return names::ML_DSA_65,
MLDSA87_V => return names::ML_DSA_87,
_ => {} }
}
if let &[RSA0, RSA1, RSA2, RSA3, RSA4, RSA5, sub] = c {
return match sub {
MD5_RSA_V => names::MD5_WITH_RSA,
SHA1_RSA_V => names::SHA1_WITH_RSA,
SHA256_RSA_V => names::SHA256_WITH_RSA,
SHA384_RSA_V => names::SHA384_WITH_RSA,
SHA512_RSA_V => names::SHA512_WITH_RSA,
_ => names::RSA, };
}
if let &[EC0, EC1, EC2, EC3, EC4, ECDSA_SHA1_V] = c {
return names::ECDSA_WITH_SHA1;
}
if let &[EC0, EC1, EC2, EC3, EC4, ECDSA_SUB, sub] = c {
return match sub {
ECDSA_SHA256_V => names::ECDSA_WITH_SHA256,
ECDSA_SHA384_V => names::ECDSA_WITH_SHA384,
ECDSA_SHA512_V => names::ECDSA_WITH_SHA512,
_ => names::ECDSA, };
}
const UNSIGNED_OID: &[u32] = oids::ALG_UNSIGNED;
if c == UNSIGNED_OID {
return names::UNSIGNED;
}
const CA0: u32 = oids::COMPOSITE_MLDSA_ARC[0]; const CA1: u32 = oids::COMPOSITE_MLDSA_ARC[1]; const CA2: u32 = oids::COMPOSITE_MLDSA_ARC[2]; const CA3: u32 = oids::COMPOSITE_MLDSA_ARC[3]; const CA4: u32 = oids::COMPOSITE_MLDSA_ARC[4]; const CA5: u32 = oids::COMPOSITE_MLDSA_ARC[5]; const CA6: u32 = oids::COMPOSITE_MLDSA_ARC[6]; const CA7: u32 = oids::COMPOSITE_MLDSA_ARC[7]; if let &[CA0, CA1, CA2, CA3, CA4, CA5, CA6, CA7, sub] = c {
match sub {
37 => return names::MLDSA44_RSA2048_PSS_SHA256,
38 => return names::MLDSA44_RSA2048_PKCS15_SHA256,
39 => return names::MLDSA44_ED25519_SHA512,
40 => return names::MLDSA44_ECDSA_P256_SHA256,
41 => return names::MLDSA65_RSA3072_PSS_SHA512,
42 => return names::MLDSA65_RSA3072_PKCS15_SHA512,
43 => return names::MLDSA65_RSA4096_PSS_SHA512,
44 => return names::MLDSA65_RSA4096_PKCS15_SHA512,
45 => return names::MLDSA65_ECDSA_P256_SHA512,
46 => return names::MLDSA65_ECDSA_P384_SHA512,
47 => return names::MLDSA65_ECDSA_BRAINPOOL_P256R1_SHA512,
48 => return names::MLDSA65_ED25519_SHA512,
49 => return names::MLDSA87_ECDSA_P384_SHA512,
50 => return names::MLDSA87_ECDSA_BRAINPOOL_P384R1_SHA512,
51 => return names::MLDSA87_ED448_SHAKE256,
52 => return names::MLDSA87_RSA3072_PSS_SHA512,
53 => return names::MLDSA87_RSA4096_PSS_SHA512,
54 => return names::MLDSA87_ECDSA_P521_SHA512,
_ => {} }
}
if c.starts_with(oids::RSA) {
names::RSA
} else if c.starts_with(oids::ECDSA_SIG) {
names::ECDSA
} else if c.starts_with(oids::DSA) {
names::DSA
} else {
names::OTHER
}
}
pub fn identify_public_key_algorithm(oid: &ObjectIdentifier) -> Option<&'static str> {
let c = oid.components();
const ED0: u32 = oids::ED25519[0]; const ED1: u32 = oids::ED25519[1]; const ED2: u32 = oids::ED25519[2]; const ED25519_V: u32 = oids::ED25519[3]; const ED448_V: u32 = oids::ED448[3];
const PQC0: u32 = oids::ML_DSA_44[0]; const PQC1: u32 = oids::ML_DSA_44[1]; const PQC2: u32 = oids::ML_DSA_44[2]; const PQC3: u32 = oids::ML_DSA_44[3]; const PQC4: u32 = oids::ML_DSA_44[4]; const PQC5: u32 = oids::ML_DSA_44[5]; const PQC6: u32 = oids::ML_DSA_44[6];
const MLDSA_D: u32 = oids::ML_DSA_44[7]; const MLDSA44_V: u32 = oids::ML_DSA_44[8]; const MLDSA65_V: u32 = oids::ML_DSA_65[8]; const MLDSA87_V: u32 = oids::ML_DSA_87[8];
const MLKEM_D: u32 = oids::ML_KEM_512[7]; const MLKEM512_V: u32 = oids::ML_KEM_512[8]; const MLKEM768_V: u32 = oids::ML_KEM_768[8]; const MLKEM1024_V: u32 = oids::ML_KEM_1024[8];
if let &[ED0, ED1, ED2, sub] = c {
return match sub {
ED25519_V => Some(names::ED25519),
ED448_V => Some(names::ED448),
_ => None,
};
}
if let &[PQC0, PQC1, PQC2, PQC3, PQC4, PQC5, PQC6, d, sub] = c {
match d {
MLDSA_D => {
return match sub {
MLDSA44_V => Some(names::ML_DSA_44),
MLDSA65_V => Some(names::ML_DSA_65),
MLDSA87_V => Some(names::ML_DSA_87),
_ => None,
}
}
MLKEM_D => {
return match sub {
MLKEM512_V => Some(names::ML_KEM_512),
MLKEM768_V => Some(names::ML_KEM_768),
MLKEM1024_V => Some(names::ML_KEM_1024),
_ => None,
}
}
_ => {} }
}
const CA0: u32 = oids::COMPOSITE_MLDSA_ARC[0]; const CA1: u32 = oids::COMPOSITE_MLDSA_ARC[1]; const CA2: u32 = oids::COMPOSITE_MLDSA_ARC[2]; const CA3: u32 = oids::COMPOSITE_MLDSA_ARC[3]; const CA4: u32 = oids::COMPOSITE_MLDSA_ARC[4]; const CA5: u32 = oids::COMPOSITE_MLDSA_ARC[5]; const CA6: u32 = oids::COMPOSITE_MLDSA_ARC[6]; const CA7: u32 = oids::COMPOSITE_MLDSA_ARC[7]; if let &[CA0, CA1, CA2, CA3, CA4, CA5, CA6, CA7, sub] = c {
let name = match sub {
37 => names::MLDSA44_RSA2048_PSS_SHA256,
38 => names::MLDSA44_RSA2048_PKCS15_SHA256,
39 => names::MLDSA44_ED25519_SHA512,
40 => names::MLDSA44_ECDSA_P256_SHA256,
41 => names::MLDSA65_RSA3072_PSS_SHA512,
42 => names::MLDSA65_RSA3072_PKCS15_SHA512,
43 => names::MLDSA65_RSA4096_PSS_SHA512,
44 => names::MLDSA65_RSA4096_PKCS15_SHA512,
45 => names::MLDSA65_ECDSA_P256_SHA512,
46 => names::MLDSA65_ECDSA_P384_SHA512,
47 => names::MLDSA65_ECDSA_BRAINPOOL_P256R1_SHA512,
48 => names::MLDSA65_ED25519_SHA512,
49 => names::MLDSA87_ECDSA_P384_SHA512,
50 => names::MLDSA87_ECDSA_BRAINPOOL_P384R1_SHA512,
51 => names::MLDSA87_ED448_SHAKE256,
52 => names::MLDSA87_RSA3072_PSS_SHA512,
53 => names::MLDSA87_RSA4096_PSS_SHA512,
54 => names::MLDSA87_ECDSA_P521_SHA512,
_ => return None, };
return Some(name);
}
if c.starts_with(oids::RSA) {
Some(names::RSA)
} else if c.starts_with(oids::ECDSA_KEY) {
Some(names::ECDSA)
} else if c.starts_with(oids::DSA) {
Some(names::DSA)
} else {
None
}
}
pub fn signing_algorithm_der(key_oid: &ObjectIdentifier, hash_algo: &str) -> Option<Vec<u8>> {
use synta::{Element, Null};
let (sig_oid_comps, null_params): (&[u32], bool) = {
let c = key_oid.components();
if c == oids::EC_PUBLIC_KEY {
let sig = match hash_algo {
"sha256" => oids::ECDSA_WITH_SHA256,
"sha384" => oids::ECDSA_WITH_SHA384,
"sha512" => oids::ECDSA_WITH_SHA512,
_ => return None,
};
(sig, false)
} else if c == oids::RSA_ENCRYPTION {
let sig = match hash_algo {
"sha1" => oids::SHA1_WITH_RSA,
"sha256" => oids::SHA256_WITH_RSA,
"sha384" => oids::SHA384_WITH_RSA,
"sha512" => oids::SHA512_WITH_RSA,
_ => return None,
};
(sig, true)
} else if c == oids::ED25519 {
(oids::ED25519, false)
} else if c == oids::ED448 {
(oids::ED448, false)
} else if c == oids::ML_DSA_44 {
(oids::ML_DSA_44, false)
} else if c == oids::ML_DSA_65 {
(oids::ML_DSA_65, false)
} else if c == oids::ML_DSA_87 {
(oids::ML_DSA_87, false)
} else {
return None;
}
};
let sig_oid = ObjectIdentifier::new(sig_oid_comps).ok()?;
let sig_alg = AlgorithmIdentifier {
algorithm: sig_oid,
parameters: if null_params {
Some(Element::Null(Null))
} else {
None
},
};
sig_alg.to_der().ok()
}
pub fn digest_alg_id(hash_algo: &str) -> Option<AlgorithmIdentifier<'static>> {
use synta::{Element, Null};
let oid_comps: &[u32] = match hash_algo {
"sha1" => oids::ID_SHA1,
"sha256" => oids::ID_SHA256,
"sha384" => oids::ID_SHA384,
"sha512" => oids::ID_SHA512,
_ => return None,
};
let oid = ObjectIdentifier::new(oid_comps).ok()?;
Some(AlgorithmIdentifier {
algorithm: oid,
parameters: Some(Element::Null(Null)),
})
}
pub fn ec_curve_key_bits(c: &[u32]) -> Option<usize> {
match c {
_ if c == oids::EC_CURVE_P256 => Some(256),
_ if c == oids::EC_CURVE_P384 => Some(384),
_ if c == oids::EC_CURVE_P521 => Some(521),
_ if c == oids::EC_CURVE_SECP256K1 => Some(256),
_ => None,
}
}
pub fn ec_curve_short_name(c: &[u32]) -> Option<&'static str> {
match c {
_ if c == oids::EC_CURVE_P256 => Some("prime256v1"),
_ if c == oids::EC_CURVE_P384 => Some("secp384r1"),
_ if c == oids::EC_CURVE_P521 => Some("secp521r1"),
_ if c == oids::EC_CURVE_SECP256K1 => Some("secp256k1"),
_ => None,
}
}
pub fn ec_curve_nist_name(c: &[u32]) -> Option<&'static str> {
match c {
_ if c == oids::EC_CURVE_P256 => Some("P-256"),
_ if c == oids::EC_CURVE_P384 => Some("P-384"),
_ if c == oids::EC_CURVE_P521 => Some("P-521"),
_ => None,
}
}
pub fn extension_oid_name(oid: &synta::ObjectIdentifier) -> String {
match oid.components() {
c if c == oids::SUBJECT_KEY_IDENTIFIER => "X509v3 Subject Key Identifier".into(),
c if c == oids::KEY_USAGE => "X509v3 Key Usage".into(),
c if c == oids::SUBJECT_ALT_NAME => "X509v3 Subject Alternative Name".into(),
c if c == oids::ISSUER_ALT_NAME => "X509v3 Issuer Alternative Name".into(),
c if c == oids::BASIC_CONSTRAINTS => "X509v3 Basic Constraints".into(),
c if c == oids::CRL_DISTRIBUTION_POINTS => "X509v3 CRL Distribution Points".into(),
c if c == oids::CERTIFICATE_POLICIES => "X509v3 Certificate Policies".into(),
c if c == oids::AUTHORITY_KEY_IDENTIFIER => "X509v3 Authority Key Identifier".into(),
c if c == oids::EXTENDED_KEY_USAGE => "X509v3 Extended Key Usage".into(),
c if c == oids::AUTHORITY_INFO_ACCESS => "Authority Information Access".into(),
c if c == oids::CT_PRECERT_SCTS => "CT Precertificate SCTs".into(),
_ => oid.to_string(),
}
}
pub fn decode_extensions<'a>(raw: &'a [u8]) -> Vec<Extension<'a>> {
use synta::Decode;
let mut decoder = synta::Decoder::new(raw, synta::Encoding::Der);
Vec::<Extension<'a>>::decode(&mut decoder).unwrap_or_default()
}
pub fn validate_envelope(data: &[u8]) -> synta::Result<core::ops::Range<usize>> {
use synta::{Decoder, Encoding};
let mut d = Decoder::new(data, Encoding::Der);
d.read_tag()?;
d.read_length()?.definite()?;
let tbs_start = d.position();
d.read_tag()?;
let tbs_content_len = d.read_length()?.definite()?;
Ok(tbs_start..(d.position() + tbs_content_len))
}
pub fn key_usage_bit(ku: &KeyUsage, n: usize) -> bool {
let bytes = ku.as_bytes();
bytes
.get(n / 8)
.is_some_and(|&b| (b >> (7 - (n % 8))) & 1 != 0)
}
pub struct CertByteRanges {
pub tbs: core::ops::Range<usize>,
pub signature_algorithm: core::ops::Range<usize>,
pub subject_public_key_info: core::ops::Range<usize>,
}
pub fn cert_byte_ranges(cert_der: &[u8]) -> Option<CertByteRanges> {
use synta::{Decoder, Encoding, TagClass};
let mut d = Decoder::new(cert_der, Encoding::Der);
d.read_tag().ok()?;
d.read_length().ok()?.definite().ok()?;
let tbs_start = d.position();
d.read_tag().ok()?;
let tbs_content_len = d.read_length().ok()?.definite().ok()?;
let tbs_content_start = d.position();
let tbs_end = tbs_content_start + tbs_content_len;
d.read_bytes(tbs_content_len).ok()?;
let sig_alg_start = d.position();
d.read_tag().ok()?;
let sig_alg_content_len = d.read_length().ok()?.definite().ok()?;
let sig_alg_end = d.position() + sig_alg_content_len;
let tbs_content = cert_der.get(tbs_content_start..tbs_end)?;
let mut t = Decoder::new(tbs_content, Encoding::Der);
let first_tag = t.peek_tag().ok()?;
if first_tag.class() == TagClass::ContextSpecific && first_tag.number() == 0 {
t.read_tag().ok()?;
let n = t.read_length().ok()?.definite().ok()?;
t.read_bytes(n).ok()?;
}
for _ in 0..5 {
t.read_tag().ok()?;
let n = t.read_length().ok()?.definite().ok()?;
t.read_bytes(n).ok()?;
}
let spki_start = tbs_content_start + t.position();
t.read_tag().ok()?;
let spki_content_len = t.read_length().ok()?.definite().ok()?;
let spki_end = tbs_content_start + t.position() + spki_content_len;
Some(CertByteRanges {
tbs: tbs_start..tbs_end,
signature_algorithm: sig_alg_start..sig_alg_end,
subject_public_key_info: spki_start..spki_end,
})
}
pub fn format_extension_value<'a>(ext: &Extension<'a>) -> Option<String> {
let c = ext.extn_id.components();
let bytes = ext.extn_value.as_bytes();
const CE0: u32 = oids::KEY_USAGE[0]; const CE1: u32 = oids::KEY_USAGE[1]; const CE2: u32 = oids::KEY_USAGE[2]; const CE_SKI_V: u32 = oids::SUBJECT_KEY_IDENTIFIER[3]; const CE_KU_V: u32 = oids::KEY_USAGE[3]; const CE_SAN_V: u32 = oids::SUBJECT_ALT_NAME[3]; const CE_IAN_V: u32 = oids::ISSUER_ALT_NAME[3]; const CE_BC_V: u32 = oids::BASIC_CONSTRAINTS[3]; const CE_CDP_V: u32 = oids::CRL_DISTRIBUTION_POINTS[3]; const CE_CP_V: u32 = oids::CERTIFICATE_POLICIES[3]; const CE_AKI_V: u32 = oids::AUTHORITY_KEY_IDENTIFIER[3]; const CE_EKU_V: u32 = oids::EXTENDED_KEY_USAGE[3];
if let &[CE0, CE1, CE2, sub] = c {
return match sub {
CE_SKI_V => format_ski_ext(bytes),
CE_KU_V => format_key_usage_ext(bytes),
CE_SAN_V | CE_IAN_V => format_general_names_ext(bytes),
CE_BC_V => format_basic_constraints_ext(bytes),
CE_CDP_V => format_crl_dp_ext(bytes),
CE_CP_V => format_cert_policies_ext(bytes),
CE_AKI_V => format_aki_ext(bytes),
CE_EKU_V => format_eku_ext(bytes),
_ => None,
};
}
if c == oids::AUTHORITY_INFO_ACCESS {
return format_aia_ext(bytes);
}
None
}
fn format_key_usage_ext(bytes: &[u8]) -> Option<String> {
let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
let ku: KeyUsage = decoder.decode().ok()?;
let ku_bytes = ku.as_bytes();
let bit_set = |n: usize| -> bool {
ku_bytes
.get(n / 8)
.is_some_and(|&b| (b >> (7 - (n % 8))) & 1 != 0)
};
let mut parts = Vec::new();
if bit_set(KEY_USAGE_DIGITAL_SIGNATURE) {
parts.push("Digital Signature");
}
if bit_set(KEY_USAGE_NON_REPUDIATION) {
parts.push("Non Repudiation");
}
if bit_set(KEY_USAGE_KEY_ENCIPHERMENT) {
parts.push("Key Encipherment");
}
if bit_set(KEY_USAGE_DATA_ENCIPHERMENT) {
parts.push("Data Encipherment");
}
if bit_set(KEY_USAGE_KEY_AGREEMENT) {
parts.push("Key Agreement");
}
if bit_set(KEY_USAGE_KEY_CERT_SIGN) {
parts.push("Certificate Sign");
}
if bit_set(KEY_USAGE_C_RLSIGN) {
parts.push("CRL Sign");
}
if bit_set(KEY_USAGE_ENCIPHER_ONLY) {
parts.push("Encipher Only");
}
if bit_set(KEY_USAGE_DECIPHER_ONLY) {
parts.push("Decipher Only");
}
Some(parts.join(", "))
}
fn format_eku_ext(bytes: &[u8]) -> Option<String> {
let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
let eku: Vec<ObjectIdentifier> = decoder.decode().ok()?;
const KP0: u32 = ID_KP_SERVER_AUTH[0]; const KP1: u32 = ID_KP_SERVER_AUTH[1]; const KP2: u32 = ID_KP_SERVER_AUTH[2]; const KP3: u32 = ID_KP_SERVER_AUTH[3]; const KP4: u32 = ID_KP_SERVER_AUTH[4]; const KP5: u32 = ID_KP_SERVER_AUTH[5]; const KP6: u32 = ID_KP_SERVER_AUTH[6]; const KP7: u32 = ID_KP_SERVER_AUTH[7]; const KP_SERVER_V: u32 = ID_KP_SERVER_AUTH[8]; const KP_CLIENT_V: u32 = ID_KP_CLIENT_AUTH[8]; const KP_CODESIGN_V: u32 = ID_KP_CODE_SIGNING[8]; const KP_EMAIL_V: u32 = ID_KP_EMAIL_PROTECTION[8]; const KP_TIME_V: u32 = ID_KP_TIME_STAMPING[8]; const KP_OCSP_V: u32 = ID_KP_OCSPSIGNING[8];
let names: Vec<&str> = eku
.iter()
.map(|oid| {
if let &[KP0, KP1, KP2, KP3, KP4, KP5, KP6, KP7, sub] = oid.components() {
match sub {
KP_SERVER_V => "TLS Web Server Authentication",
KP_CLIENT_V => "TLS Web Client Authentication",
KP_CODESIGN_V => "Code Signing",
KP_EMAIL_V => "E-mail Protection",
KP_TIME_V => "Time Stamping",
KP_OCSP_V => "OCSP Signing",
_ => "Unknown Purpose",
}
} else {
"Unknown Purpose"
}
})
.collect();
Some(names.join(", "))
}
pub mod general_name {
pub const OTHER_NAME: u32 = 0;
pub const RFC822_NAME: u32 = 1;
pub const DNS_NAME: u32 = 2;
pub const X400_ADDRESS: u32 = 3;
pub const DIRECTORY_NAME: u32 = 4;
pub const EDI_PARTY_NAME: u32 = 5;
pub const URI: u32 = 6;
pub const IP_ADDRESS: u32 = 7;
pub const REGISTERED_ID: u32 = 8;
#[derive(Clone)]
pub enum GeneralNameSpec {
Rfc822(String),
Dns(String),
Uri(String),
DirectoryName(Vec<u8>),
IpAddress(Vec<u8>),
RegisteredId(synta::ObjectIdentifier),
}
impl GeneralNameSpec {
pub fn rfc822(email: &str) -> Self {
Self::Rfc822(email.to_string())
}
pub fn dns(host: &str) -> Self {
Self::Dns(host.to_string())
}
pub fn uri(uri: &str) -> Self {
Self::Uri(uri.to_string())
}
pub fn directory_name(name_der: &[u8]) -> Self {
Self::DirectoryName(name_der.to_vec())
}
pub fn ip_address(addr: &[u8]) -> Self {
Self::IpAddress(addr.to_vec())
}
pub fn registered_id(oid: synta::ObjectIdentifier) -> Self {
Self::RegisteredId(oid)
}
pub fn to_general_name(&self) -> synta::Result<crate::GeneralName<'_>> {
match self {
Self::Rfc822(s) => {
let ia5 = synta::IA5String::new(s.clone())?;
Ok(crate::GeneralName::Rfc822Name(ia5))
}
Self::Dns(s) => {
let ia5 = synta::IA5String::new(s.clone())?;
Ok(crate::GeneralName::DNSName(ia5))
}
Self::Uri(s) => {
let ia5 = synta::IA5String::new(s.clone())?;
Ok(crate::GeneralName::UniformResourceIdentifier(ia5))
}
Self::DirectoryName(bytes) => {
let name = synta::Decoder::new(bytes, synta::Encoding::Der)
.decode::<crate::Name<'_>>()?;
Ok(crate::GeneralName::DirectoryName(name))
}
Self::IpAddress(bytes) => Ok(crate::GeneralName::IPAddress(
synta::OctetString::new(bytes.clone()),
)),
Self::RegisteredId(oid) => Ok(crate::GeneralName::RegisteredID(oid.clone())),
}
}
}
}
pub use general_name::GeneralNameSpec;
pub fn parse_general_names(raw: &[u8]) -> Vec<(u32, Vec<u8>)> {
parse_general_names_inner(raw).unwrap_or_default()
}
pub fn encode_general_names(entries: &[(u32, &[u8])]) -> Option<Vec<u8>> {
use synta::traits::Decode;
fn push_der_len(out: &mut Vec<u8>, len: usize) {
if len < 0x80 {
out.push(len as u8);
} else if len <= 0xFF {
out.extend_from_slice(&[0x81, len as u8]);
} else {
out.extend_from_slice(&[0x82, (len >> 8) as u8, len as u8]);
}
}
fn context_constructed_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
let mut tlv = Vec::with_capacity(3 + value.len());
tlv.push(0xA0 | tag); push_der_len(&mut tlv, value.len());
tlv.extend_from_slice(value);
tlv
}
fn encode_one(tag_num: u32, content: &[u8]) -> Option<Vec<u8>> {
use general_name as gn;
match tag_num {
gn::OTHER_NAME | gn::X400_ADDRESS | gn::EDI_PARTY_NAME => {
Some(context_constructed_tlv(tag_num as u8, content))
}
gn::RFC822_NAME => {
let s = std::str::from_utf8(content).ok()?;
GeneralName::Rfc822Name(synta::IA5String::new(s.to_string()).ok()?)
.to_der()
.ok()
}
gn::DNS_NAME => {
let s = std::str::from_utf8(content).ok()?;
GeneralName::DNSName(synta::IA5String::new(s.to_string()).ok()?)
.to_der()
.ok()
}
gn::DIRECTORY_NAME => {
let mut dec = synta::Decoder::new(content, synta::Encoding::Der);
GeneralName::DirectoryName(Name::decode(&mut dec).ok()?)
.to_der()
.ok()
}
gn::URI => {
let s = std::str::from_utf8(content).ok()?;
GeneralName::UniformResourceIdentifier(synta::IA5String::new(s.to_string()).ok()?)
.to_der()
.ok()
}
gn::IP_ADDRESS => {
GeneralName::IPAddress(synta::OctetString::new(content.to_vec()))
.to_der()
.ok()
}
gn::REGISTERED_ID => {
let oid = synta::ObjectIdentifier::from_content_bytes(content).ok()?;
GeneralName::RegisteredID(oid).to_der().ok()
}
_ => None,
}
}
let mut body: Vec<u8> = Vec::new();
for &(tag_num, content) in entries {
body.extend_from_slice(&encode_one(tag_num, content)?);
}
let mut out = Vec::with_capacity(4 + body.len());
out.push(0x30); push_der_len(&mut out, body.len());
out.extend_from_slice(&body);
Some(out)
}
impl<'a> crl::CertificateList<'a> {
pub fn crl_number(&self) -> Option<synta::Integer> {
let exts = self.tbs_cert_list.crl_extensions.as_ref()?;
for ext in exts {
if ext.extn_id.components() == oids::CRL_NUMBER {
use synta::Decode;
let mut dec = synta::Decoder::new(ext.extn_value.as_bytes(), synta::Encoding::Der);
return synta::Integer::decode(&mut dec).ok();
}
}
None
}
}
impl<'a> Certificate<'a> {
pub fn subject_alt_names(&self) -> Vec<(u32, Vec<u8>)> {
let raw = match self.tbs_certificate.extensions.as_ref() {
Some(r) => r,
None => return Vec::new(),
};
find_extension_value(raw.as_bytes(), oids::SUBJECT_ALT_NAME)
.map(parse_general_names)
.unwrap_or_default()
}
}
pub fn find_extension_value<'a>(raw: &'a [u8], oid: &[u32]) -> Option<&'a [u8]> {
use synta::tag::TAG_SEQUENCE;
use synta::Tag;
let mut dec = synta::Decoder::new(raw, synta::Encoding::Der);
let seq_tag = Tag::universal_constructed(TAG_SEQUENCE);
let mut outer = dec.enter_constructed(seq_tag).ok()?;
while !outer.is_empty() {
let ext: Extension<'_> = outer.decode().ok()?;
if ext.extn_id.components() == oid {
return Some(ext.extn_value.as_bytes());
}
}
None
}
fn parse_general_names_inner(raw: &[u8]) -> Option<Vec<(u32, Vec<u8>)>> {
use synta::tag::TAG_SEQUENCE;
use synta::{Tag, TagClass};
let mut decoder = synta::Decoder::new(raw, synta::Encoding::Der);
let seq_tag = Tag::universal_constructed(TAG_SEQUENCE);
let mut inner = decoder.enter_constructed(seq_tag).ok()?;
let mut result = Vec::new();
while !inner.is_empty() {
let tag = inner.read_tag().ok()?;
let len = inner.read_length().ok()?.definite().ok()?;
let content = inner.read_bytes(len).ok()?.to_vec();
if tag.class() != TagClass::ContextSpecific {
continue;
}
result.push((tag.number(), content));
}
Some(result)
}
fn format_general_names_ext(bytes: &[u8]) -> Option<String> {
use synta::tag::TAG_SEQUENCE;
use synta::TagClass;
let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
let seq_tag = synta::Tag::universal_constructed(TAG_SEQUENCE);
let mut inner = decoder.enter_constructed(seq_tag).ok()?;
let mut parts = Vec::new();
while !inner.is_empty() {
let tag = inner.read_tag().ok()?;
let len = inner.read_length().ok()?.definite().ok()?;
let content = inner.read_bytes(len).ok()?;
if tag.class() != TagClass::ContextSpecific {
continue;
}
let s = match tag.number() {
1 => format!("email:{}", core::str::from_utf8(content).unwrap_or("?")),
2 => format!("DNS:{}", core::str::from_utf8(content).unwrap_or("?")),
6 => format!("URI:{}", core::str::from_utf8(content).unwrap_or("?")),
7 => format_ip_address(content),
_ => continue,
};
parts.push(s);
}
if parts.is_empty() {
None
} else {
Some(parts.join(", "))
}
}
fn format_ip_address(bytes: &[u8]) -> String {
match bytes.len() {
4 => format!(
"IP Address:{}.{}.{}.{}",
bytes[0], bytes[1], bytes[2], bytes[3]
),
16 => {
let parts: Vec<String> = bytes
.chunks(2)
.map(|c| format!("{:02X}{:02X}", c[0], c[1]))
.collect();
format!("IP Address:{}", parts.join(":"))
}
_ => format!(
"IP Address:{}",
bytes
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join(":")
),
}
}
fn format_basic_constraints_ext(bytes: &[u8]) -> Option<String> {
let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
let bc: BasicConstraints = decoder.decode().ok()?;
let mut parts = Vec::new();
let is_ca = bc.c_a.map(|b| b.0).unwrap_or(false);
parts.push(if is_ca { "CA:TRUE" } else { "CA:FALSE" }.to_string());
if let Some(path_len) = &bc.path_len_constraint {
parts.push(format!("pathlen:{}", path_len.as_i64().unwrap_or(0)));
}
Some(parts.join(", "))
}
fn format_ski_ext<'a>(bytes: &'a [u8]) -> Option<String> {
use synta::OctetStringRef;
let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
let ski: OctetStringRef<'a> = decoder.decode().ok()?;
let hex: String = ski
.as_bytes()
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(":");
Some(hex)
}
fn format_crl_dp_ext(bytes: &[u8]) -> Option<String> {
use synta::tag::TAG_SEQUENCE;
use synta::{Tag, TagClass};
let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
let seq_tag = Tag::universal_constructed(TAG_SEQUENCE);
let mut outer = decoder.enter_constructed(seq_tag).ok()?;
let mut parts = Vec::new();
while !outer.is_empty() {
let mut dp = outer.enter_constructed(seq_tag).ok()?;
while !dp.is_empty() {
let dp_tag = dp.read_tag().ok()?;
let dp_len = dp.read_length().ok()?.definite().ok()?;
let dp_content = dp.read_bytes(dp_len).ok()?;
if dp_tag.class() == TagClass::ContextSpecific && dp_tag.number() == 0 {
let mut gn_dec = synta::Decoder::new(dp_content, synta::Encoding::Der);
while !gn_dec.is_empty() {
let gn_tag = gn_dec.read_tag().ok()?;
let gn_len = gn_dec.read_length().ok()?.definite().ok()?;
let gn_content = gn_dec.read_bytes(gn_len).ok()?;
if gn_tag.class() == TagClass::ContextSpecific && gn_tag.number() == 6 {
let uri = core::str::from_utf8(gn_content).unwrap_or("?");
parts.push(format!("URI:{}", uri));
}
}
}
}
}
if parts.is_empty() {
None
} else {
Some(parts.join(", "))
}
}
fn format_cert_policies_ext(bytes: &[u8]) -> Option<String> {
let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
let infos: Vec<PolicyInformation<'_>> = decoder.decode().ok()?;
if infos.is_empty() {
return None;
}
Some(
infos
.iter()
.map(|pi| format!("Policy: {}", pi.policy_identifier))
.collect::<Vec<_>>()
.join(", "),
)
}
fn format_aia_ext(bytes: &[u8]) -> Option<String> {
use synta::tag::TAG_SEQUENCE;
use synta::{Tag, TagClass};
let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
let seq_tag = Tag::universal_constructed(TAG_SEQUENCE);
let mut outer = decoder.enter_constructed(seq_tag).ok()?;
const AD0: u32 = ID_AD[0];
const AD1: u32 = ID_AD[1];
const AD2: u32 = ID_AD[2];
const AD3: u32 = ID_AD[3];
const AD4: u32 = ID_AD[4];
const AD5: u32 = ID_AD[5];
const AD6: u32 = ID_AD[6];
const AD7: u32 = ID_AD[7];
const OCSP_V: u32 = oids::AD_OCSP[oids::AD_OCSP.len() - 1];
const CA_ISSUERS_V: u32 = oids::AD_CA_ISSUERS[oids::AD_CA_ISSUERS.len() - 1];
let mut parts = Vec::new();
while !outer.is_empty() {
let mut ad = outer.enter_constructed(seq_tag).ok()?;
let oid = synta::ObjectIdentifier::decode(&mut ad).ok()?;
let gn_tag = ad.read_tag().ok()?;
let gn_len = ad.read_length().ok()?.definite().ok()?;
let gn_content = ad.read_bytes(gn_len).ok()?;
let location_str = if gn_tag.class() == TagClass::ContextSpecific && gn_tag.number() == 6 {
match core::str::from_utf8(gn_content) {
Ok(uri) => format!("URI:{}", uri),
Err(_) => format!("URI:<invalid UTF-8: {} bytes>", gn_content.len()),
}
} else {
format!("[tag {}]", gn_tag.number())
};
let label = if let &[AD0, AD1, AD2, AD3, AD4, AD5, AD6, AD7, sub] = oid.components() {
match sub {
OCSP_V => "OCSP",
CA_ISSUERS_V => "CA Issuers",
_ => "Unknown",
}
} else {
"Unknown"
};
parts.push(format!("{} - {}", label, location_str));
}
if parts.is_empty() {
None
} else {
Some(parts.join("\n"))
}
}
fn format_aki_ext(bytes: &[u8]) -> Option<String> {
use synta::tag::TAG_SEQUENCE;
use synta::TagClass;
let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
let seq_tag = synta::Tag::universal_constructed(TAG_SEQUENCE);
let mut inner = decoder.enter_constructed(seq_tag).ok()?;
let mut parts = Vec::new();
while !inner.is_empty() {
let tag = inner.read_tag().ok()?;
let len = inner.read_length().ok()?.definite().ok()?;
let content = inner.read_bytes(len).ok()?;
if tag.class() != TagClass::ContextSpecific {
continue;
}
match tag.number() {
0 => {
let hex = content
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(":");
parts.push(format!("keyid:{}", hex));
}
2 => {
let hex = content
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(":");
parts.push(format!("serial:{}", hex));
}
1 => {
let mut gn_dec = synta::Decoder::new(content, synta::Encoding::Der);
while !gn_dec.is_empty() {
let gn_tag = gn_dec.read_tag().ok()?;
let gn_len = gn_dec.read_length().ok()?.definite().ok()?;
let gn_content = gn_dec.read_bytes(gn_len).ok()?;
if gn_tag.class() == synta::TagClass::ContextSpecific && gn_tag.number() == 4 {
parts.push(format!("DirName:{}", format_dn_slash(gn_content)));
}
}
}
_ => {} }
}
if parts.is_empty() {
None
} else {
Some(parts.join("\n "))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_general_names_skips_universal_class_tags() {
let gn_der: &[u8] = &[
0x30, 0x1b, 0x82, 0x05, b'a', b'.', b'c', b'o', b'm', 0x0c, 0x04, b's', b'k', b'i',
b'p', 0x86, 0x0c, b'h', b't', b't', b'p', b':', b'/', b'/', b'b', b'.', b'c', b'o',
b'm',
];
let result = parse_general_names(gn_der);
assert_eq!(
result.len(),
2,
"Universal tag must be skipped; got {result:?}"
);
assert_eq!(result[0], (2, b"a.com".to_vec()));
assert_eq!(result[1], (6, b"http://b.com".to_vec()));
}
#[test]
fn encode_general_names_roundtrip_othername() {
#[rustfmt::skip]
let gn_seq: &[u8] = &[
0x30, 0x10, 0xA0, 0x0E, 0x06, 0x02, 0x2A, 0x03, 0xA0, 0x08, 0x0C, 0x06, b'f', b'o', b'o', b'b', b'a', b'r',
];
let parsed = parse_general_names(gn_seq);
assert_eq!(parsed.len(), 1);
assert_eq!(parsed[0].0, general_name::OTHER_NAME);
let entries: Vec<(u32, &[u8])> = parsed.iter().map(|(t, c)| (*t, c.as_slice())).collect();
let re_encoded = encode_general_names(&entries).expect("encode_general_names failed");
assert_eq!(
re_encoded, gn_seq,
"OtherName roundtrip mismatch:\n got: {re_encoded:02X?}\n expected: {gn_seq:02X?}"
);
}
#[test]
fn encode_general_names_roundtrip_edipartyname() {
#[rustfmt::skip]
let gn_seq: &[u8] = &[
0x30, 0x0C, 0xA5, 0x0A, 0x81, 0x08, b't', b'e', b's', b't', b'n', b'a', b'm', b'e',
];
let parsed = parse_general_names(gn_seq);
assert_eq!(parsed.len(), 1);
assert_eq!(parsed[0].0, general_name::EDI_PARTY_NAME);
let entries: Vec<(u32, &[u8])> = parsed.iter().map(|(t, c)| (*t, c.as_slice())).collect();
let re_encoded = encode_general_names(&entries).expect("encode_general_names failed");
assert_eq!(
re_encoded, gn_seq,
"EDIPartyName roundtrip mismatch:\n got: {re_encoded:02X?}\n expected: {gn_seq:02X?}"
);
}
#[test]
fn encode_general_names_roundtrips_simple_types() {
let gn_der: &[u8] = &[
0x30, 0x0b, 0x82, 0x03, b'x', b'.', b'y', 0x87, 0x04, 1, 2, 3, 4, ];
let tuples = parse_general_names(gn_der);
assert_eq!(tuples.len(), 2);
let encoded = encode_general_names(
&tuples
.iter()
.map(|(t, v)| (*t, v.as_slice()))
.collect::<Vec<_>>(),
);
assert_eq!(encoded.as_deref(), Some(gn_der));
}
#[cfg(feature = "derive")]
fn make_extensions_der(oid_content: &[u8], ext_value_der: &[u8]) -> Vec<u8> {
assert!(oid_content.len() <= 127);
assert!(ext_value_der.len() <= 127);
let oid_tlv: Vec<u8> = {
let mut v = vec![0x06u8, oid_content.len() as u8];
v.extend_from_slice(oid_content);
v
};
let octet_tlv: Vec<u8> = {
let mut v = vec![0x04u8, ext_value_der.len() as u8];
v.extend_from_slice(ext_value_der);
v
};
let ext_body: Vec<u8> = [oid_tlv.as_slice(), octet_tlv.as_slice()].concat();
assert!(ext_body.len() <= 127);
let ext_tlv: Vec<u8> = {
let mut v = vec![0x30u8, ext_body.len() as u8];
v.extend_from_slice(&ext_body);
v
};
assert!(ext_tlv.len() <= 127);
let mut out = vec![0x30u8, ext_tlv.len() as u8];
out.extend_from_slice(&ext_tlv);
out
}
#[cfg(feature = "derive")]
#[test]
fn format_extension_value_crl_distribution_points() {
let uri = b"http://crl.test/crl"; let ext_val: Vec<u8> = {
let mut v = vec![
0x30,
0x19, 0x30,
0x17, 0xA0,
0x15, 0x86,
uri.len() as u8,
];
v.extend_from_slice(uri);
v
};
let raw = make_extensions_der(&[0x55, 0x1D, 0x1F], &ext_val);
let exts = decode_extensions(&raw);
assert_eq!(exts.len(), 1);
let result = format_extension_value(&exts[0]);
assert_eq!(result.as_deref(), Some("URI:http://crl.test/crl"));
}
#[cfg(feature = "derive")]
#[test]
fn format_extension_value_certificate_policies() {
let ext_val: &[u8] = &[
0x30, 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1D, 0x20, 0x00, ];
let raw = make_extensions_der(&[0x55, 0x1D, 0x20], ext_val);
let exts = decode_extensions(&raw);
assert_eq!(exts.len(), 1);
let result = format_extension_value(&exts[0]);
assert_eq!(result.as_deref(), Some("Policy: 2.5.29.32.0"));
}
#[cfg(feature = "derive")]
#[test]
fn format_extension_value_authority_info_access() {
let uri = b"http://ocsp.test"; let ext_val: Vec<u8> = {
let mut v = vec![
0x30,
0x1E, 0x30,
0x1C, 0x06,
0x08,
0x2B,
0x06,
0x01,
0x05,
0x05,
0x07,
0x30,
0x01,
0x86,
uri.len() as u8,
];
v.extend_from_slice(uri);
v
};
let raw = make_extensions_der(&[0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01], &ext_val);
let exts = decode_extensions(&raw);
assert_eq!(exts.len(), 1);
let result = format_extension_value(&exts[0]);
assert_eq!(result.as_deref(), Some("OCSP - URI:http://ocsp.test"));
}
}