use super::types::AlgId;
use crate::jwk::EcCurve;
pub(crate) const OID_EC_PUBLIC_KEY: &[u8] = &[0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01];
pub(crate) const OID_P256: &[u8] = &[0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07];
pub(crate) const OID_P384: &[u8] = &[0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22];
pub(crate) const OID_P521: &[u8] = &[0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23];
pub(crate) const OID_SECP256K1: &[u8] = &[0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x0A];
pub(crate) const OID_X25519: &[u8] = &[0x06, 0x03, 0x2B, 0x65, 0x6E];
pub(crate) const OID_ED25519: &[u8] = &[0x06, 0x03, 0x2B, 0x65, 0x70];
pub(crate) const OID_RSA_ENCRYPTION: &[u8] = &[
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
];
pub(crate) const OID_RSASSA_PSS: &[u8] = &[
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A,
];
pub(crate) const OID_X448: &[u8] = &[0x06, 0x03, 0x2B, 0x65, 0x6F];
pub(crate) const OID_ED448: &[u8] = &[0x06, 0x03, 0x2B, 0x65, 0x71];
pub(crate) const OID_BRAINPOOL_P256R1: &[u8] = &[
0x06, 0x09, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07,
];
pub(crate) const ALG_EC_P256: &[u8] = &[
0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48,
0xCE, 0x3D, 0x03, 0x01, 0x07,
];
pub(crate) const ALG_EC_P384: &[u8] = &[
0x30, 0x10, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x05, 0x2B, 0x81, 0x04,
0x00, 0x22,
];
pub(crate) const ALG_EC_P521: &[u8] = &[
0x30, 0x10, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x05, 0x2B, 0x81, 0x04,
0x00, 0x23,
];
pub(crate) const ALG_EC_SECP256K1: &[u8] = &[
0x30, 0x10, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x05, 0x2B, 0x81, 0x04,
0x00, 0x0A,
];
pub(crate) const ALG_X25519: &[u8] = &[0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x6E];
pub(crate) const ALG_ED25519: &[u8] = &[0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x70];
pub(crate) const ALG_RSA_ENCRYPTION: &[u8] = &[
0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00,
];
pub(crate) const ALGORITHM_IDENTIFIERS: [(AlgId, &[u8]); 7] = [
(AlgId::Ec(EcCurve::P256), ALG_EC_P256),
(AlgId::Ec(EcCurve::P384), ALG_EC_P384),
(AlgId::Ec(EcCurve::P521), ALG_EC_P521),
(AlgId::Ec(EcCurve::Secp256k1), ALG_EC_SECP256K1),
(AlgId::X25519, ALG_X25519),
(AlgId::Ed25519, ALG_ED25519),
(AlgId::RsaEncryption, ALG_RSA_ENCRYPTION),
];
pub(crate) fn algorithm_identifier(alg: AlgId) -> &'static [u8] {
match alg {
AlgId::Ec(EcCurve::P256) => ALG_EC_P256,
AlgId::Ec(EcCurve::P384) => ALG_EC_P384,
AlgId::Ec(EcCurve::P521) => ALG_EC_P521,
AlgId::Ec(EcCurve::Secp256k1) => ALG_EC_SECP256K1,
AlgId::X25519 => ALG_X25519,
AlgId::Ed25519 => ALG_ED25519,
AlgId::RsaEncryption => ALG_RSA_ENCRYPTION,
}
}
pub(crate) fn named_curve(crv: EcCurve) -> &'static [u8] {
match crv {
EcCurve::P256 => OID_P256,
EcCurve::P384 => OID_P384,
EcCurve::P521 => OID_P521,
EcCurve::Secp256k1 => OID_SECP256K1,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
fn fixture(dir: &str, name: &str) -> Vec<u8> {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/data/keys")
.join(dir)
.join(name);
let bytes = std::fs::read(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
if name.ends_with(".pem") {
let text = String::from_utf8(bytes).expect("PEM is ASCII");
pem_rfc7468::decode_vec(text.as_bytes())
.expect("fixture PEM decodes")
.1
} else {
bytes
}
}
fn outer_contents(der: &[u8]) -> &[u8] {
assert_eq!(der[0], 0x30, "outer SEQUENCE");
let (header, len) = match der[1] {
n @ 0..=0x7F => (2, usize::from(n)),
0x81 => (3, usize::from(der[2])),
0x82 => (4, usize::from(der[2]) << 8 | usize::from(der[3])),
other => panic!("unexpected length octet {other:#x}"),
};
assert_eq!(der.len(), header + len, "one element, no trailing data");
&der[header..]
}
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
haystack.windows(needle.len()).any(|w| w == needle)
}
const FIXTURES: [(AlgId, &str, Option<&str>); 7] = [
(
AlgId::Ec(EcCurve::P256),
"ec-p256-spki.der",
Some("ec-p256-pkcs8.der"),
),
(
AlgId::Ec(EcCurve::P384),
"ec-p384-spki.der",
Some("ec-p384-pkcs8.der"),
),
(
AlgId::Ec(EcCurve::P521),
"ec-p521-spki.der",
Some("ec-p521-pkcs8.der"),
),
(
AlgId::Ec(EcCurve::Secp256k1),
"ec-secp256k1-spki.der",
Some("ec-secp256k1-pkcs8.der"),
),
(AlgId::X25519, "x25519-spki.der", Some("x25519-pkcs8.der")),
(
AlgId::Ed25519,
"ed25519-spki.der",
Some("ed25519-pkcs8.der"),
),
(AlgId::RsaEncryption, "rsa2048-spki.der", None),
];
#[test]
fn each_template_is_the_algorithm_identifier_of_its_openssl_fixtures() {
for (alg, spki, pkcs8) in FIXTURES {
let template = algorithm_identifier(alg);
let der = fixture("openssl", spki);
assert!(outer_contents(&der).starts_with(template), "{spki}");
if let Some(pkcs8) = pkcs8 {
let der = fixture("openssl", pkcs8);
let contents = outer_contents(&der);
assert_eq!(&contents[..3], [0x02, 0x01, 0x00], "{pkcs8}: version v1");
assert!(contents[3..].starts_with(template), "{pkcs8}");
}
}
}
#[test]
fn templates_are_their_oids_and_parameters() {
let sequence = |parts: &[&[u8]]| {
let body = parts.concat();
let mut der = vec![0x30, u8::try_from(body.len()).expect("short form")];
der.extend_from_slice(&body);
der
};
for crv in [
EcCurve::P256,
EcCurve::P384,
EcCurve::P521,
EcCurve::Secp256k1,
] {
assert_eq!(
algorithm_identifier(AlgId::Ec(crv)),
sequence(&[OID_EC_PUBLIC_KEY, named_curve(crv)]),
"{crv}"
);
}
assert_eq!(ALG_X25519, sequence(&[OID_X25519]));
assert_eq!(ALG_ED25519, sequence(&[OID_ED25519]));
assert_eq!(
ALG_RSA_ENCRYPTION,
sequence(&[OID_RSA_ENCRYPTION, &[0x05, 0x00]])
);
}
#[test]
fn the_table_and_the_lookup_agree() {
for (alg, template) in ALGORITHM_IDENTIFIERS {
assert_eq!(algorithm_identifier(alg), template);
}
for (i, (_, a)) in ALGORITHM_IDENTIFIERS.iter().enumerate() {
for (_, b) in &ALGORITHM_IDENTIFIERS[i + 1..] {
assert!(!a.starts_with(b) && !b.starts_with(a));
}
}
}
#[test]
fn named_curve_oids_appear_in_the_sec1_fixtures() {
for (crv, file) in [
(EcCurve::P256, "ec-p256-sec1.der"),
(EcCurve::P384, "ec-p384-sec1.der"),
(EcCurve::P521, "ec-p521-sec1.der"),
(EcCurve::Secp256k1, "ec-secp256k1-sec1.der"),
] {
let oid = named_curve(crv);
let mut tagged = vec![0xA0, u8::try_from(oid.len()).expect("short")];
tagged.extend_from_slice(oid);
assert!(contains(&fixture("openssl", file), &tagged), "{file}");
}
}
#[test]
fn classification_oids_appear_in_the_reject_fixtures() {
for (oid, file) in [
(OID_RSASSA_PSS, "reject-rsapss2048-spki.pem"),
(OID_ED448, "reject-ed448-spki.pem"),
(OID_ED448, "reject-ed448-pkcs8.pem"),
(OID_X448, "reject-x448-pkcs8.pem"),
(OID_BRAINPOOL_P256R1, "reject-ec-brainpoolp256r1-spki.pem"),
(OID_BRAINPOOL_P256R1, "reject-ec-brainpoolp256r1-pkcs8.pem"),
] {
let der = fixture("openssl", file);
assert!(contains(&der, oid), "{file}");
for (_, template) in ALGORITHM_IDENTIFIERS {
assert!(!contains(&der, template), "{file}");
}
}
let brainpool = fixture("openssl", "reject-ec-brainpoolp256r1-spki.pem");
assert!(outer_contents(&brainpool)[2..].starts_with(OID_EC_PUBLIC_KEY));
}
#[test]
fn the_rsa_template_is_also_what_rsa_private_keys_carry() {
let der = fixture("openssl", "reject-rsa2048-private-pkcs8.der");
assert!(outer_contents(&der)[3..].starts_with(ALG_RSA_ENCRYPTION));
}
}