use synta::{Integer, ObjectIdentifier};
use synta_certificate::{
cert_byte_ranges, decode_extensions, ec_curve_key_bits, ec_curve_nist_name,
ec_curve_short_name, encode_basic_constraints, encode_general_names, encode_key_usage,
extension_oid_name, format_extension_value, general_name, identify_public_key_algorithm,
identify_signature_algorithm, key_usage_bit, oids, parse_time, signing_algorithm_der,
validate_envelope, CertificateBuilder, ExtendedKeyUsageBuilder, SubjectAlternativeNameBuilder,
UnsignedCertificateSigner, KEY_USAGE_C_RLSIGN, KEY_USAGE_DATA_ENCIPHERMENT,
KEY_USAGE_DECIPHER_ONLY, KEY_USAGE_DIGITAL_SIGNATURE, KEY_USAGE_ENCIPHER_ONLY,
KEY_USAGE_KEY_AGREEMENT, KEY_USAGE_KEY_CERT_SIGN, KEY_USAGE_KEY_ENCIPHERMENT,
KEY_USAGE_NON_REPUDIATION,
};
const EMPTY_NAME: &[u8] = &[0x30, 0x00];
const DUMMY_SPKI: &[u8] = &[0x30, 0x03, 0x02, 0x01, 0x01];
fn build_test_cert() -> Vec<u8> {
CertificateBuilder::new()
.serial_number(Integer::from_i64(1))
.not_valid_before(parse_time("20240101120000Z").unwrap())
.not_valid_after(parse_time("20250101120000Z").unwrap())
.subject_name(EMPTY_NAME)
.issuer_name(EMPTY_NAME)
.public_key_der(DUMMY_SPKI)
.sign(&UnsignedCertificateSigner)
.unwrap()
}
fn build_extension_der(oid_comps: &[u32], value: &[u8]) -> Vec<u8> {
use synta::{Encoder, Encoding};
let oid = ObjectIdentifier::new(oid_comps).unwrap();
let mut oid_enc = Encoder::new(Encoding::Der);
oid_enc.encode(&oid).unwrap();
let oid_der = oid_enc.finish().unwrap();
assert!(
value.len() <= 0x7F,
"helper: value too long for short-form length"
);
let mut octet: Vec<u8> = vec![0x04, value.len() as u8];
octet.extend_from_slice(value);
let content_len = oid_der.len() + octet.len();
assert!(
content_len <= 0x7F,
"helper: content too long for short-form length"
);
let mut ext = vec![0x30u8, content_len as u8];
ext.extend_from_slice(&oid_der);
ext.extend_from_slice(&octet);
ext
}
#[test]
fn ec_curve_key_bits_p256() {
assert_eq!(ec_curve_key_bits(oids::EC_CURVE_P256), Some(256));
}
#[test]
fn ec_curve_key_bits_p384() {
assert_eq!(ec_curve_key_bits(oids::EC_CURVE_P384), Some(384));
}
#[test]
fn ec_curve_key_bits_p521() {
assert_eq!(ec_curve_key_bits(oids::EC_CURVE_P521), Some(521));
}
#[test]
fn ec_curve_key_bits_secp256k1() {
assert_eq!(ec_curve_key_bits(oids::EC_CURVE_SECP256K1), Some(256));
}
#[test]
fn ec_curve_key_bits_unknown() {
assert_eq!(ec_curve_key_bits(&[1, 2, 3]), None);
}
#[test]
fn ec_curve_short_name_p256() {
assert_eq!(ec_curve_short_name(oids::EC_CURVE_P256), Some("prime256v1"));
}
#[test]
fn ec_curve_short_name_p384() {
assert_eq!(ec_curve_short_name(oids::EC_CURVE_P384), Some("secp384r1"));
}
#[test]
fn ec_curve_short_name_p521() {
assert_eq!(ec_curve_short_name(oids::EC_CURVE_P521), Some("secp521r1"));
}
#[test]
fn ec_curve_short_name_secp256k1() {
assert_eq!(
ec_curve_short_name(oids::EC_CURVE_SECP256K1),
Some("secp256k1")
);
}
#[test]
fn ec_curve_short_name_unknown() {
assert_eq!(ec_curve_short_name(&[1, 2, 3]), None);
}
#[test]
fn ec_curve_nist_name_p256() {
assert_eq!(ec_curve_nist_name(oids::EC_CURVE_P256), Some("P-256"));
}
#[test]
fn ec_curve_nist_name_p384() {
assert_eq!(ec_curve_nist_name(oids::EC_CURVE_P384), Some("P-384"));
}
#[test]
fn ec_curve_nist_name_p521() {
assert_eq!(ec_curve_nist_name(oids::EC_CURVE_P521), Some("P-521"));
}
#[test]
fn ec_curve_nist_name_secp256k1_has_no_nist_name() {
assert_eq!(ec_curve_nist_name(oids::EC_CURVE_SECP256K1), None);
}
#[test]
fn ec_curve_nist_name_unknown() {
assert_eq!(ec_curve_nist_name(&[1, 2, 3]), None);
}
#[test]
fn identify_sig_alg_rsa_sha256() {
let oid = ObjectIdentifier::new(oids::SHA256_WITH_RSA).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::SHA256_WITH_RSA
);
}
#[test]
fn identify_sig_alg_rsa_sha384() {
let oid = ObjectIdentifier::new(oids::SHA384_WITH_RSA).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::SHA384_WITH_RSA
);
}
#[test]
fn identify_sig_alg_rsa_sha512() {
let oid = ObjectIdentifier::new(oids::SHA512_WITH_RSA).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::SHA512_WITH_RSA
);
}
#[test]
fn identify_sig_alg_rsa_sha1() {
let oid = ObjectIdentifier::new(oids::SHA1_WITH_RSA).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::SHA1_WITH_RSA
);
}
#[test]
fn identify_sig_alg_rsa_md5() {
let oid = ObjectIdentifier::new(oids::MD5_WITH_RSA).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::MD5_WITH_RSA
);
}
#[test]
fn identify_sig_alg_ecdsa_sha256() {
let oid = ObjectIdentifier::new(oids::ECDSA_WITH_SHA256).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::ECDSA_WITH_SHA256
);
}
#[test]
fn identify_sig_alg_ecdsa_sha384() {
let oid = ObjectIdentifier::new(oids::ECDSA_WITH_SHA384).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::ECDSA_WITH_SHA384
);
}
#[test]
fn identify_sig_alg_ecdsa_sha512() {
let oid = ObjectIdentifier::new(oids::ECDSA_WITH_SHA512).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::ECDSA_WITH_SHA512
);
}
#[test]
fn identify_sig_alg_ed25519() {
let oid = ObjectIdentifier::new(oids::ED25519).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::ED25519
);
}
#[test]
fn identify_sig_alg_ed448() {
let oid = ObjectIdentifier::new(oids::ED448).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::ED448
);
}
#[test]
fn identify_sig_alg_ml_dsa_44() {
let oid = ObjectIdentifier::new(oids::ML_DSA_44).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::ML_DSA_44
);
}
#[test]
fn identify_sig_alg_ml_dsa_65() {
let oid = ObjectIdentifier::new(oids::ML_DSA_65).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::ML_DSA_65
);
}
#[test]
fn identify_sig_alg_ml_dsa_87() {
let oid = ObjectIdentifier::new(oids::ML_DSA_87).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::ML_DSA_87
);
}
#[test]
fn identify_sig_alg_unknown_returns_other() {
let oid = ObjectIdentifier::new(&[1, 2, 3, 99]).unwrap();
assert_eq!(
identify_signature_algorithm(&oid),
synta_certificate::names::OTHER
);
}
#[test]
fn identify_pubkey_alg_rsa() {
let oid = ObjectIdentifier::new(oids::RSA_ENCRYPTION).unwrap();
assert!(identify_public_key_algorithm(&oid).is_some());
}
#[test]
fn identify_pubkey_alg_ec() {
let oid = ObjectIdentifier::new(oids::EC_PUBLIC_KEY).unwrap();
assert!(identify_public_key_algorithm(&oid).is_some());
}
#[test]
fn identify_pubkey_alg_ed25519() {
let oid = ObjectIdentifier::new(oids::ED25519).unwrap();
assert_eq!(
identify_public_key_algorithm(&oid),
Some(synta_certificate::names::ED25519)
);
}
#[test]
fn identify_pubkey_alg_ed448() {
let oid = ObjectIdentifier::new(oids::ED448).unwrap();
assert_eq!(
identify_public_key_algorithm(&oid),
Some(synta_certificate::names::ED448)
);
}
#[test]
fn identify_pubkey_alg_ml_dsa_44() {
let oid = ObjectIdentifier::new(oids::ML_DSA_44).unwrap();
assert_eq!(
identify_public_key_algorithm(&oid),
Some(synta_certificate::names::ML_DSA_44)
);
}
#[test]
fn identify_pubkey_alg_ml_dsa_65() {
let oid = ObjectIdentifier::new(oids::ML_DSA_65).unwrap();
assert_eq!(
identify_public_key_algorithm(&oid),
Some(synta_certificate::names::ML_DSA_65)
);
}
#[test]
fn identify_pubkey_alg_ml_dsa_87() {
let oid = ObjectIdentifier::new(oids::ML_DSA_87).unwrap();
assert_eq!(
identify_public_key_algorithm(&oid),
Some(synta_certificate::names::ML_DSA_87)
);
}
#[test]
fn identify_pubkey_alg_ml_kem_512() {
let oid = ObjectIdentifier::new(oids::ML_KEM_512).unwrap();
assert_eq!(
identify_public_key_algorithm(&oid),
Some(synta_certificate::names::ML_KEM_512)
);
}
#[test]
fn identify_pubkey_alg_ml_kem_768() {
let oid = ObjectIdentifier::new(oids::ML_KEM_768).unwrap();
assert_eq!(
identify_public_key_algorithm(&oid),
Some(synta_certificate::names::ML_KEM_768)
);
}
#[test]
fn identify_pubkey_alg_ml_kem_1024() {
let oid = ObjectIdentifier::new(oids::ML_KEM_1024).unwrap();
assert_eq!(
identify_public_key_algorithm(&oid),
Some(synta_certificate::names::ML_KEM_1024)
);
}
#[test]
fn identify_pubkey_alg_unknown() {
let oid = ObjectIdentifier::new(&[1, 2, 3, 99]).unwrap();
assert!(identify_public_key_algorithm(&oid).is_none());
}
#[test]
fn signing_alg_der_ec_sha256() {
let key_oid = ObjectIdentifier::new(oids::EC_PUBLIC_KEY).unwrap();
let der = signing_algorithm_der(&key_oid, "sha256");
assert!(der.is_some());
assert_eq!(der.unwrap()[0], 0x30);
}
#[test]
fn signing_alg_der_ec_sha384() {
let key_oid = ObjectIdentifier::new(oids::EC_PUBLIC_KEY).unwrap();
let der = signing_algorithm_der(&key_oid, "sha384");
assert!(der.is_some());
}
#[test]
fn signing_alg_der_ec_sha512() {
let key_oid = ObjectIdentifier::new(oids::EC_PUBLIC_KEY).unwrap();
let der = signing_algorithm_der(&key_oid, "sha512");
assert!(der.is_some());
}
#[test]
fn signing_alg_der_ec_invalid_hash_returns_none() {
let key_oid = ObjectIdentifier::new(oids::EC_PUBLIC_KEY).unwrap();
assert!(signing_algorithm_der(&key_oid, "sha1").is_none());
}
#[test]
fn signing_alg_der_rsa_sha256() {
let key_oid = ObjectIdentifier::new(oids::RSA_ENCRYPTION).unwrap();
let der = signing_algorithm_der(&key_oid, "sha256");
assert!(der.is_some());
assert_eq!(der.unwrap()[0], 0x30);
}
#[test]
fn signing_alg_der_rsa_sha1() {
let key_oid = ObjectIdentifier::new(oids::RSA_ENCRYPTION).unwrap();
assert!(signing_algorithm_der(&key_oid, "sha1").is_some());
}
#[test]
fn signing_alg_der_rsa_sha384() {
let key_oid = ObjectIdentifier::new(oids::RSA_ENCRYPTION).unwrap();
assert!(signing_algorithm_der(&key_oid, "sha384").is_some());
}
#[test]
fn signing_alg_der_rsa_sha512() {
let key_oid = ObjectIdentifier::new(oids::RSA_ENCRYPTION).unwrap();
assert!(signing_algorithm_der(&key_oid, "sha512").is_some());
}
#[test]
fn signing_alg_der_rsa_invalid_hash_returns_none() {
let key_oid = ObjectIdentifier::new(oids::RSA_ENCRYPTION).unwrap();
assert!(signing_algorithm_der(&key_oid, "md5").is_none());
}
#[test]
fn signing_alg_der_ed25519_ignores_hash() {
let key_oid = ObjectIdentifier::new(oids::ED25519).unwrap();
let der = signing_algorithm_der(&key_oid, "");
assert!(der.is_some());
}
#[test]
fn signing_alg_der_ed448_ignores_hash() {
let key_oid = ObjectIdentifier::new(oids::ED448).unwrap();
assert!(signing_algorithm_der(&key_oid, "").is_some());
}
#[test]
fn signing_alg_der_ml_dsa_44_ignores_hash() {
let key_oid = ObjectIdentifier::new(oids::ML_DSA_44).unwrap();
assert!(signing_algorithm_der(&key_oid, "").is_some());
}
#[test]
fn signing_alg_der_ml_dsa_65_ignores_hash() {
let key_oid = ObjectIdentifier::new(oids::ML_DSA_65).unwrap();
assert!(signing_algorithm_der(&key_oid, "").is_some());
}
#[test]
fn signing_alg_der_ml_dsa_87_ignores_hash() {
let key_oid = ObjectIdentifier::new(oids::ML_DSA_87).unwrap();
assert!(signing_algorithm_der(&key_oid, "").is_some());
}
#[test]
fn signing_alg_der_unknown_key_oid_returns_none() {
let key_oid = ObjectIdentifier::new(&[1, 2, 3, 99]).unwrap();
assert!(signing_algorithm_der(&key_oid, "sha256").is_none());
}
#[test]
fn extension_oid_name_key_usage() {
let oid = ObjectIdentifier::new(oids::KEY_USAGE).unwrap();
assert_eq!(extension_oid_name(&oid), "X509v3 Key Usage");
}
#[test]
fn extension_oid_name_basic_constraints() {
let oid = ObjectIdentifier::new(oids::BASIC_CONSTRAINTS).unwrap();
assert_eq!(extension_oid_name(&oid), "X509v3 Basic Constraints");
}
#[test]
fn extension_oid_name_subject_alt_name() {
let oid = ObjectIdentifier::new(oids::SUBJECT_ALT_NAME).unwrap();
assert_eq!(extension_oid_name(&oid), "X509v3 Subject Alternative Name");
}
#[test]
fn extension_oid_name_issuer_alt_name() {
let oid = ObjectIdentifier::new(oids::ISSUER_ALT_NAME).unwrap();
assert_eq!(extension_oid_name(&oid), "X509v3 Issuer Alternative Name");
}
#[test]
fn extension_oid_name_ski() {
let oid = ObjectIdentifier::new(oids::SUBJECT_KEY_IDENTIFIER).unwrap();
assert_eq!(extension_oid_name(&oid), "X509v3 Subject Key Identifier");
}
#[test]
fn extension_oid_name_aki() {
let oid = ObjectIdentifier::new(oids::AUTHORITY_KEY_IDENTIFIER).unwrap();
assert_eq!(extension_oid_name(&oid), "X509v3 Authority Key Identifier");
}
#[test]
fn extension_oid_name_eku() {
let oid = ObjectIdentifier::new(oids::EXTENDED_KEY_USAGE).unwrap();
assert_eq!(extension_oid_name(&oid), "X509v3 Extended Key Usage");
}
#[test]
fn extension_oid_name_cdp() {
let oid = ObjectIdentifier::new(oids::CRL_DISTRIBUTION_POINTS).unwrap();
assert_eq!(extension_oid_name(&oid), "X509v3 CRL Distribution Points");
}
#[test]
fn extension_oid_name_cert_policies() {
let oid = ObjectIdentifier::new(oids::CERTIFICATE_POLICIES).unwrap();
assert_eq!(extension_oid_name(&oid), "X509v3 Certificate Policies");
}
#[test]
fn extension_oid_name_aia() {
let oid = ObjectIdentifier::new(oids::AUTHORITY_INFO_ACCESS).unwrap();
assert_eq!(extension_oid_name(&oid), "Authority Information Access");
}
#[test]
fn extension_oid_name_unknown_returns_dotted_notation() {
let oid = ObjectIdentifier::new(&[1, 2, 3, 99]).unwrap();
let name = extension_oid_name(&oid);
assert!(name.contains("1.2.3.99"));
}
#[test]
fn validate_envelope_minimal_cert() {
let der: &[u8] = &[
0x30, 0x0a, 0x30, 0x00, 0x30, 0x00, 0x03, 0x04, 0x00, 0xde, 0xad, 0xbe, ];
let range = validate_envelope(der).unwrap();
assert_eq!(range, 2..4);
}
#[test]
fn validate_envelope_truncated_returns_error() {
let der: &[u8] = &[0x30, 0xFF]; assert!(validate_envelope(der).is_err());
}
#[test]
fn validate_envelope_wrong_outer_tag_returns_error() {
let der: &[u8] = &[0x02, 0x01, 0x00]; assert!(validate_envelope(der).is_err());
}
#[test]
fn validate_envelope_empty_returns_error() {
assert!(validate_envelope(&[]).is_err());
}
#[test]
fn validate_envelope_cert_from_builder() {
let cert_der = build_test_cert();
let range = validate_envelope(&cert_der).unwrap();
assert!(range.start < range.end);
assert!(range.end <= cert_der.len());
}
fn decode_key_usage_for_flags(flags: u16) -> synta_certificate::KeyUsage {
let ku_der = encode_key_usage(flags).unwrap();
let mut decoder = synta::Decoder::new(&ku_der, synta::Encoding::Der);
decoder.decode::<synta_certificate::KeyUsage>().unwrap()
}
#[test]
fn key_usage_bit_digital_signature_set() {
let ku = decode_key_usage_for_flags(1 << KEY_USAGE_DIGITAL_SIGNATURE);
assert!(key_usage_bit(&ku, KEY_USAGE_DIGITAL_SIGNATURE));
assert!(!key_usage_bit(&ku, KEY_USAGE_KEY_ENCIPHERMENT));
}
#[test]
fn key_usage_bit_key_cert_sign_set() {
let ku = decode_key_usage_for_flags(1 << KEY_USAGE_KEY_CERT_SIGN);
assert!(key_usage_bit(&ku, KEY_USAGE_KEY_CERT_SIGN));
assert!(!key_usage_bit(&ku, KEY_USAGE_DIGITAL_SIGNATURE));
}
#[test]
fn key_usage_bit_crl_sign_set() {
let ku = decode_key_usage_for_flags(1 << KEY_USAGE_C_RLSIGN);
assert!(key_usage_bit(&ku, KEY_USAGE_C_RLSIGN));
}
#[test]
fn key_usage_bit_key_encipherment_set() {
let ku = decode_key_usage_for_flags(1 << KEY_USAGE_KEY_ENCIPHERMENT);
assert!(key_usage_bit(&ku, KEY_USAGE_KEY_ENCIPHERMENT));
}
#[test]
fn key_usage_bit_out_of_range_returns_false() {
let ku = decode_key_usage_for_flags(1 << KEY_USAGE_DIGITAL_SIGNATURE);
assert!(!key_usage_bit(&ku, 100));
}
#[test]
fn key_usage_bit_multiple_bits_set() {
let flags = (1 << KEY_USAGE_DIGITAL_SIGNATURE)
| (1 << KEY_USAGE_KEY_CERT_SIGN)
| (1 << KEY_USAGE_C_RLSIGN);
let ku = decode_key_usage_for_flags(flags);
assert!(key_usage_bit(&ku, KEY_USAGE_DIGITAL_SIGNATURE));
assert!(key_usage_bit(&ku, KEY_USAGE_KEY_CERT_SIGN));
assert!(key_usage_bit(&ku, KEY_USAGE_C_RLSIGN));
assert!(!key_usage_bit(&ku, KEY_USAGE_KEY_ENCIPHERMENT));
}
#[test]
fn cert_byte_ranges_real_cert() {
let cert_der = build_test_cert();
let ranges = cert_byte_ranges(&cert_der).unwrap();
assert!(ranges.tbs.start < ranges.tbs.end);
assert!(ranges.signature_algorithm.start < ranges.signature_algorithm.end);
assert!(ranges.subject_public_key_info.start < ranges.subject_public_key_info.end);
assert!(ranges.tbs.end <= cert_der.len());
assert!(ranges.signature_algorithm.end <= cert_der.len());
assert!(ranges.subject_public_key_info.end <= cert_der.len());
assert!(ranges.tbs.start < ranges.signature_algorithm.start);
}
#[test]
fn cert_byte_ranges_malformed_returns_none() {
assert!(cert_byte_ranges(&[0x02, 0x01, 0x00]).is_none());
}
#[test]
fn cert_byte_ranges_empty_returns_none() {
assert!(cert_byte_ranges(&[]).is_none());
}
#[test]
fn decode_extensions_empty_sequence() {
let der: &[u8] = &[0x30, 0x00]; let exts = decode_extensions(der);
assert!(exts.is_empty());
}
#[test]
fn decode_extensions_one_extension() {
let ku_value = encode_key_usage(1 << KEY_USAGE_DIGITAL_SIGNATURE).unwrap();
let ext_der = build_extension_der(oids::KEY_USAGE, &ku_value);
assert!(
ext_der.len() <= 0x7F,
"ext_der too long for short-form length"
);
let mut seq_der = vec![0x30u8, ext_der.len() as u8];
seq_der.extend_from_slice(&ext_der);
let exts = decode_extensions(&seq_der);
assert_eq!(exts.len(), 1);
}
#[test]
fn decode_extensions_malformed_returns_empty() {
let exts = decode_extensions(&[0xFF, 0xFF]);
assert!(exts.is_empty());
}
fn decode_extension_from(der: &[u8]) -> synta_certificate::Extension<'_> {
let mut decoder = synta::Decoder::new(der, synta::Encoding::Der);
decoder
.decode::<synta_certificate::Extension<'_>>()
.unwrap()
}
#[test]
fn format_extension_value_key_usage_digital_signature() {
let ku_value = encode_key_usage(1 << KEY_USAGE_DIGITAL_SIGNATURE).unwrap();
let ext_der = build_extension_der(oids::KEY_USAGE, &ku_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
assert!(result.unwrap().contains("Digital Signature"));
}
#[test]
fn format_extension_value_key_usage_ca_bits() {
let ku_value =
encode_key_usage((1 << KEY_USAGE_KEY_CERT_SIGN) | (1 << KEY_USAGE_C_RLSIGN)).unwrap();
let ext_der = build_extension_der(oids::KEY_USAGE, &ku_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
let s = result.unwrap();
assert!(s.contains("Certificate Sign") || s.contains("CRL Sign"));
}
#[test]
fn format_extension_value_basic_constraints_ca() {
let bc_value = encode_basic_constraints(true, None).unwrap();
let ext_der = build_extension_der(oids::BASIC_CONSTRAINTS, &bc_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
}
#[test]
fn format_extension_value_basic_constraints_not_ca() {
let bc_value = encode_basic_constraints(false, None).unwrap();
let ext_der = build_extension_der(oids::BASIC_CONSTRAINTS, &bc_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
}
#[test]
fn format_extension_value_subject_alt_name() {
use synta_certificate::SubjectAlternativeNameBuilder;
let san_value = SubjectAlternativeNameBuilder::new()
.dns_name("example.com")
.build()
.unwrap();
let ext_der = build_extension_der(oids::SUBJECT_ALT_NAME, &san_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
assert!(result.unwrap().contains("example.com"));
}
#[test]
fn format_extension_value_unknown_oid_returns_none() {
let ext_der = build_extension_der(&[1, 2, 3, 99], &[0x01, 0x02, 0x03]);
let ext = decode_extension_from(&ext_der);
assert!(format_extension_value(&ext).is_none());
}
#[test]
fn format_extension_value_ski() {
let mut ski_value: Vec<u8> = vec![0x04, 0x14]; ski_value.extend_from_slice(&[0xABu8; 20]);
let ext_der = build_extension_der(oids::SUBJECT_KEY_IDENTIFIER, &ski_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some(), "SKI extension must produce a string");
let s = result.unwrap();
assert!(s.contains("AB"), "SKI hex must contain AB bytes");
assert!(s.contains(':'), "SKI hex must use colon separator");
}
#[test]
fn format_extension_value_aki_keyid() {
let key_id: &[u8] = &[0xCDu8; 20];
let mut aki_inner: Vec<u8> = vec![0x80u8, 0x14]; aki_inner.extend_from_slice(key_id);
let mut aki_value: Vec<u8> = vec![0x30u8, aki_inner.len() as u8]; aki_value.extend_from_slice(&aki_inner);
let ext_der = build_extension_der(oids::AUTHORITY_KEY_IDENTIFIER, &aki_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some(), "AKI extension must produce a string");
let s = result.unwrap();
assert!(s.contains("keyid:"), "AKI output must contain 'keyid:'");
assert!(s.contains("CD"), "AKI keyid must include the key bytes");
}
#[test]
fn format_extension_value_eku_server_auth() {
let eku_value = ExtendedKeyUsageBuilder::new()
.server_auth()
.build()
.expect("EKU with serverAuth must build");
let ext_der = build_extension_der(oids::EXTENDED_KEY_USAGE, &eku_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some(), "EKU extension must produce a string");
assert!(
result.unwrap().contains("TLS Web Server"),
"EKU must mention server auth"
);
}
#[test]
fn format_extension_value_eku_multiple_purposes() {
let eku_value = ExtendedKeyUsageBuilder::new()
.client_auth()
.email_protection()
.time_stamping()
.ocsp_signing()
.code_signing()
.build()
.expect("EKU with multiple purposes must build");
let ext_der = build_extension_der(oids::EXTENDED_KEY_USAGE, &eku_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some(), "EKU extension must produce a string");
let s = result.unwrap();
assert!(s.contains("TLS Web Client"), "must include client auth");
assert!(s.contains("E-mail"), "must include email protection");
assert!(s.contains("Time"), "must include time stamping");
assert!(s.contains("OCSP"), "must include OCSP signing");
assert!(s.contains("Code"), "must include code signing");
}
#[test]
fn format_extension_value_eku_unknown_purpose() {
let eku_value = ExtendedKeyUsageBuilder::new()
.add_oid(&[1, 2, 3, 99]) .build()
.expect("EKU with unknown OID must build");
let ext_der = build_extension_der(oids::EXTENDED_KEY_USAGE, &eku_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
assert!(
result.unwrap().contains("Unknown"),
"unknown OID must map to 'Unknown Purpose'"
);
}
#[test]
fn format_extension_value_san_email() {
let san_value = SubjectAlternativeNameBuilder::new()
.rfc822_name("user@example.com")
.build()
.expect("SAN with email must build");
let ext_der = build_extension_der(oids::SUBJECT_ALT_NAME, &san_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
assert!(
result.unwrap().contains("email:"),
"SAN with email must produce 'email:...' entry"
);
}
#[test]
fn format_extension_value_san_uri() {
let san_value = SubjectAlternativeNameBuilder::new()
.uri("http://example.com/")
.build()
.expect("SAN with URI must build");
let ext_der = build_extension_der(oids::SUBJECT_ALT_NAME, &san_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
assert!(
result.unwrap().contains("URI:"),
"SAN with URI must produce 'URI:...' entry"
);
}
#[test]
fn format_extension_value_san_ip_v4() {
let san_value = SubjectAlternativeNameBuilder::new()
.ip_address(&[192u8, 168, 1, 1])
.build()
.expect("SAN with IPv4 must build");
let ext_der = build_extension_der(oids::SUBJECT_ALT_NAME, &san_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
assert!(
result.unwrap().contains("192"),
"SAN with IPv4 must contain IP bytes"
);
}
#[test]
fn format_extension_value_ian_dns() {
let ian_value = SubjectAlternativeNameBuilder::new()
.dns_name("ca.example.com")
.build()
.expect("IAN with DNS must build");
let ext_der = build_extension_der(oids::ISSUER_ALT_NAME, &ian_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
assert!(result.unwrap().contains("ca.example.com"));
}
#[test]
fn format_extension_value_key_usage_non_repudiation() {
let ku_value = encode_key_usage(1 << KEY_USAGE_NON_REPUDIATION).unwrap();
let ext_der = build_extension_der(oids::KEY_USAGE, &ku_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
assert!(result.unwrap().contains("Non Repudiation"));
}
#[test]
fn format_extension_value_key_usage_data_encipherment() {
let ku_value = encode_key_usage(1 << KEY_USAGE_DATA_ENCIPHERMENT).unwrap();
let ext_der = build_extension_der(oids::KEY_USAGE, &ku_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
assert!(result.unwrap().contains("Data Encipherment"));
}
#[test]
fn format_extension_value_key_usage_key_agreement() {
let ku_value = encode_key_usage(1 << KEY_USAGE_KEY_AGREEMENT).unwrap();
let ext_der = build_extension_der(oids::KEY_USAGE, &ku_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
assert!(result.unwrap().contains("Key Agreement"));
}
#[test]
fn format_extension_value_key_usage_encipher_only() {
let ku_value =
encode_key_usage((1 << KEY_USAGE_KEY_AGREEMENT) | (1 << KEY_USAGE_ENCIPHER_ONLY)).unwrap();
let ext_der = build_extension_der(oids::KEY_USAGE, &ku_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
let s = result.unwrap();
assert!(s.contains("Encipher Only"), "must include Encipher Only");
}
#[test]
fn format_extension_value_key_usage_decipher_only() {
let ku_value =
encode_key_usage((1 << KEY_USAGE_KEY_AGREEMENT) | (1 << KEY_USAGE_DECIPHER_ONLY)).unwrap();
let ext_der = build_extension_der(oids::KEY_USAGE, &ku_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
let s = result.unwrap();
assert!(s.contains("Decipher Only"), "must include Decipher Only");
}
#[test]
fn crl_number_returns_integer_when_present() {
use synta::{Element, Encoder, Encoding, Null, ObjectIdentifier};
use synta_certificate::{crl, AlgorithmIdentifier, CertificateListBuilder, NameBuilder};
let issuer = NameBuilder::new().common_name("Test CA").build().unwrap();
let sig_oid = ObjectIdentifier::new(oids::SHA256_WITH_RSA).unwrap();
let alg = AlgorithmIdentifier {
algorithm: sig_oid,
parameters: Some(Element::Null(Null)),
};
let mut enc = Encoder::new(Encoding::Der);
enc.encode(&alg).unwrap();
let sig_alg_der = enc.finish().unwrap();
let crl_number_value = &[0x02u8, 0x01, 0x2A];
let tbs = CertificateListBuilder::new()
.issuer(&issuer)
.this_update("20240101120000Z")
.signature_algorithm(&sig_alg_der)
.add_crl_extension(oids::CRL_NUMBER, false, crl_number_value)
.build()
.expect("CRL with cRLNumber must build");
let full_crl = CertificateListBuilder::assemble(&tbs, &sig_alg_der, &[0u8; 4])
.expect("CRL assemble must succeed");
let mut dec = synta::Decoder::new(&full_crl, synta::Encoding::Der);
let cert_list: crl::CertificateList<'_> =
dec.decode().expect("CertificateList decode must succeed");
let num = cert_list.crl_number();
assert!(
num.is_some(),
"crl_number() must return Some when extension is present"
);
assert_eq!(num.unwrap().as_i64().ok(), Some(42));
}
#[test]
fn crl_number_returns_none_when_absent() {
use synta::{Element, Encoder, Encoding, Null, ObjectIdentifier};
use synta_certificate::{crl, AlgorithmIdentifier, CertificateListBuilder, NameBuilder};
let issuer = NameBuilder::new().common_name("No Ext CA").build().unwrap();
let sig_oid = ObjectIdentifier::new(oids::SHA256_WITH_RSA).unwrap();
let alg = AlgorithmIdentifier {
algorithm: sig_oid,
parameters: Some(Element::Null(Null)),
};
let mut enc = Encoder::new(Encoding::Der);
enc.encode(&alg).unwrap();
let sig_alg_der = enc.finish().unwrap();
let tbs = CertificateListBuilder::new()
.issuer(&issuer)
.this_update("20240101120000Z")
.signature_algorithm(&sig_alg_der)
.build()
.expect("minimal CRL must build");
let full_crl = CertificateListBuilder::assemble(&tbs, &sig_alg_der, &[0u8; 4])
.expect("CRL assemble must succeed");
let mut dec = synta::Decoder::new(&full_crl, synta::Encoding::Der);
let cert_list: crl::CertificateList<'_> =
dec.decode().expect("CertificateList decode must succeed");
assert!(
cert_list.crl_number().is_none(),
"crl_number() must return None when no cRLNumber extension"
);
}
#[test]
fn format_extension_value_key_usage_key_encipherment() {
let ku_value = encode_key_usage(1 << KEY_USAGE_KEY_ENCIPHERMENT).unwrap();
let ext_der = build_extension_der(oids::KEY_USAGE, &ku_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
assert!(result.unwrap().contains("Key Encipherment"));
}
#[test]
fn format_extension_value_eku_unknown_kp_sub() {
let eku_value = ExtendedKeyUsageBuilder::new()
.add_oid(&[1, 3, 6, 1, 5, 5, 7, 3, 99])
.build()
.expect("EKU with 9-arc id-kp unknown sub must build");
let ext_der = build_extension_der(oids::EXTENDED_KEY_USAGE, &eku_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
assert!(
result.unwrap().contains("Unknown"),
"9-arc id-kp OID with unknown sub must produce 'Unknown Purpose'"
);
}
#[test]
fn format_extension_value_san_ipv6() {
let san_value = SubjectAlternativeNameBuilder::new()
.ip_address(&[0u8; 16])
.build()
.expect("SAN with IPv6 must build");
let ext_der = build_extension_der(oids::SUBJECT_ALT_NAME, &san_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
let s = result.unwrap();
assert!(
s.contains("IP Address:"),
"IPv6 SAN must produce 'IP Address:' prefix: {s}"
);
assert!(
s.contains("0000"),
"IPv6 SAN must contain hex zero pairs: {s}"
);
}
#[test]
fn format_extension_value_basic_constraints_with_pathlen() {
let bc_value = encode_basic_constraints(true, Some(3)).unwrap();
let ext_der = build_extension_der(oids::BASIC_CONSTRAINTS, &bc_value);
let ext = decode_extension_from(&ext_der);
let result = format_extension_value(&ext);
assert!(result.is_some());
let s = result.unwrap();
assert!(
s.contains("CA:TRUE"),
"BasicConstraints must include CA:TRUE"
);
assert!(
s.contains("pathlen:3"),
"BasicConstraints must include pathlen:3"
);
}
#[test]
fn encode_general_names_rfc822_name() {
let email = b"user@example.com";
let result = encode_general_names(&[(general_name::RFC822_NAME, email.as_slice())]);
assert!(result.is_some(), "RFC822_NAME must encode");
let der = result.unwrap();
assert_eq!(der[0], 0x30, "must be a SEQUENCE");
assert!(
der.windows(email.len()).any(|w| w == email.as_slice()),
"email bytes must appear in encoded output"
);
}
#[test]
fn encode_general_names_directory_name() {
let name_der = synta_certificate::NameBuilder::new()
.common_name("A")
.build()
.unwrap();
let result = encode_general_names(&[(general_name::DIRECTORY_NAME, name_der.as_slice())]);
assert!(result.is_some(), "DIRECTORY_NAME must encode");
let der = result.unwrap();
assert_eq!(der[0], 0x30, "must be a SEQUENCE");
}
#[test]
fn encode_general_names_uri() {
let uri = b"http://example.com/";
let result = encode_general_names(&[(general_name::URI, uri.as_slice())]);
assert!(result.is_some(), "URI must encode");
let der = result.unwrap();
assert_eq!(der[0], 0x30, "must be a SEQUENCE");
assert!(
der.windows(uri.len()).any(|w| w == uri.as_slice()),
"URI bytes must appear in encoded output"
);
}
#[test]
fn encode_general_names_registered_id() {
let oid_content: &[u8] = &[0x2A, 0x03]; let result = encode_general_names(&[(general_name::REGISTERED_ID, oid_content)]);
assert!(result.is_some(), "REGISTERED_ID must encode");
let der = result.unwrap();
assert_eq!(der[0], 0x30, "must be a SEQUENCE");
}
const VALID_SPKI: &[u8] = &[
0x30, 0x09, 0x30, 0x04, 0x06, 0x02, 0x2A, 0x03, 0x03, 0x01, 0x00, ];
fn build_decodable_cert(
extra_oid: Option<ObjectIdentifier>,
extra_value: Option<&[u8]>,
) -> Vec<u8> {
let mut builder = CertificateBuilder::new()
.serial_number(Integer::from_i64(1))
.not_valid_before(parse_time("20240101120000Z").unwrap())
.not_valid_after(parse_time("20250101120000Z").unwrap())
.subject_name(EMPTY_NAME)
.issuer_name(EMPTY_NAME)
.public_key_der(VALID_SPKI);
if let (Some(oid), Some(val)) = (extra_oid, extra_value) {
builder = builder.add_extension(oid, false, val);
}
builder.sign(&UnsignedCertificateSigner).unwrap()
}
#[test]
fn certificate_subject_alt_names_no_extensions() {
use synta_certificate::Certificate;
let cert_der = build_decodable_cert(None, None);
let mut dec = synta::Decoder::new(&cert_der, synta::Encoding::Der);
let cert: Certificate<'_> = dec.decode().expect("must decode");
let sans = cert.subject_alt_names();
assert!(
sans.is_empty(),
"cert with no extensions must return empty SAN list"
);
}
#[test]
fn certificate_subject_alt_names_with_dns() {
use synta_certificate::Certificate;
let san_value = SubjectAlternativeNameBuilder::new()
.dns_name("test.example.com")
.build()
.unwrap();
let san_oid = ObjectIdentifier::new(oids::SUBJECT_ALT_NAME).unwrap();
let cert_der = build_decodable_cert(Some(san_oid), Some(&san_value));
let mut dec = synta::Decoder::new(&cert_der, synta::Encoding::Der);
let cert: Certificate<'_> = dec.decode().expect("must decode");
let sans = cert.subject_alt_names();
assert_eq!(sans.len(), 1, "cert with one DNS SAN must return one entry");
assert_eq!(
sans[0].0,
general_name::DNS_NAME,
"tag must be DNS_NAME (2)"
);
assert_eq!(
sans[0].1, b"test.example.com",
"DNS name content must match"
);
}