use core::fmt;
use crate::jwa::{ContentEncAlg, KeyMgmtAlg, SigningAlg};
use crate::jwk::{EcCurve, OkpCurve};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum KeyFormat {
Pkcs8,
Sec1,
Spki,
Pkcs1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum KeySpec {
Oct {
bytes: usize,
},
Ec(EcCurve),
Okp(OkpCurve),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum JoseAlg {
Signing(SigningAlg),
KeyMgmt(KeyMgmtAlg),
ContentEnc(ContentEncAlg),
}
impl From<SigningAlg> for JoseAlg {
fn from(alg: SigningAlg) -> Self {
JoseAlg::Signing(alg)
}
}
impl From<KeyMgmtAlg> for JoseAlg {
fn from(alg: KeyMgmtAlg) -> Self {
JoseAlg::KeyMgmt(alg)
}
}
impl From<ContentEncAlg> for JoseAlg {
fn from(enc: ContentEncAlg) -> Self {
JoseAlg::ContentEnc(enc)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum AlgId {
Ec(EcCurve),
Ed25519,
X25519,
RsaEncryption,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum Label {
PrivateKey,
PublicKey,
EcPrivateKey,
RsaPublic,
RsaPrivate,
EncryptedPrivateKey,
Other,
}
impl Label {
const KNOWN: [(Label, &'static str); 6] = [
(Label::PrivateKey, "PRIVATE KEY"),
(Label::PublicKey, "PUBLIC KEY"),
(Label::EcPrivateKey, "EC PRIVATE KEY"),
(Label::RsaPublic, "RSA PUBLIC KEY"),
(Label::RsaPrivate, "RSA PRIVATE KEY"),
(Label::EncryptedPrivateKey, "ENCRYPTED PRIVATE KEY"),
];
pub(crate) fn as_str(self) -> Option<&'static str> {
Self::KNOWN
.iter()
.find(|(label, _)| *label == self)
.map(|(_, text)| *text)
}
pub(crate) fn from_text(text: &str) -> Label {
Self::KNOWN
.iter()
.find(|(_, known)| *known == text)
.map_or(Label::Other, |(label, _)| *label)
}
}
#[derive(Clone, Copy)]
pub(crate) struct Pkcs8Parts<'a> {
pub(crate) version: u8,
pub(crate) alg: AlgId,
pub(crate) private_key: &'a [u8],
pub(crate) public_key: Option<&'a [u8]>,
}
impl fmt::Debug for Pkcs8Parts<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Pkcs8Parts")
.field("version", &self.version)
.field("alg", &self.alg)
.field("private_key", &"<redacted>")
.field("public_key", &self.public_key.map(Octets))
.finish()
}
}
#[derive(Clone, Copy)]
pub(crate) struct SpkiParts<'a> {
pub(crate) alg: AlgId,
pub(crate) subject_public_key: &'a [u8],
}
impl fmt::Debug for SpkiParts<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SpkiParts")
.field("alg", &self.alg)
.field("subject_public_key", &Octets(self.subject_public_key))
.finish()
}
}
struct Octets<'a>(&'a [u8]);
impl fmt::Debug for Octets<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"<{} octets>\"", self.0.len())
}
}
#[cfg(test)]
mod tests {
use super::*;
const MARKER: [u8; 32] = [0xA5; 32];
#[test]
fn pkcs8_parts_debug_never_shows_key_bytes() {
let parts = Pkcs8Parts {
version: 1,
alg: AlgId::Ed25519,
private_key: &MARKER,
public_key: Some(&MARKER),
};
for debug in [format!("{parts:?}"), format!("{parts:#?}")] {
assert!(!debug.contains("165"), "{debug}");
assert!(!debug.to_ascii_lowercase().contains("a5"), "{debug}");
assert!(debug.contains("<redacted>"), "{debug}");
}
assert_eq!(
format!("{parts:?}"),
r#"Pkcs8Parts { version: 1, alg: Ed25519, private_key: "<redacted>", public_key: Some("<32 octets>") }"#
);
}
#[test]
fn parts_borrow_the_decoded_der() {
let parts = Pkcs8Parts {
version: 0,
alg: AlgId::X25519,
private_key: &MARKER[..16],
public_key: None,
};
assert_eq!(parts.private_key, &MARKER[..16]);
assert_eq!(parts.public_key, None);
let spki = SpkiParts {
alg: AlgId::RsaEncryption,
subject_public_key: &MARKER[..3],
};
assert_eq!(spki.subject_public_key.len(), 3);
assert_eq!(
format!(
"{:?}",
[
spki.alg,
parts.alg,
AlgId::Ed25519,
AlgId::Ec(EcCurve::Secp256k1)
]
),
"[RsaEncryption, X25519, Ed25519, Ec(Secp256k1)]"
);
}
#[test]
fn spki_parts_debug_shows_only_the_length() {
let parts = SpkiParts {
alg: AlgId::Ec(EcCurve::P256),
subject_public_key: &MARKER,
};
let debug = format!("{parts:?}");
assert!(!debug.contains("165"), "{debug}");
assert_eq!(
debug,
r#"SpkiParts { alg: Ec(P256), subject_public_key: "<32 octets>" }"#
);
}
#[test]
fn labels_are_the_exact_rfc_7468_and_openssl_texts() {
let table = [
(Label::PrivateKey, "PRIVATE KEY"),
(Label::PublicKey, "PUBLIC KEY"),
(Label::EcPrivateKey, "EC PRIVATE KEY"),
(Label::RsaPublic, "RSA PUBLIC KEY"),
(Label::RsaPrivate, "RSA PRIVATE KEY"),
(Label::EncryptedPrivateKey, "ENCRYPTED PRIVATE KEY"),
];
for (label, text) in table {
assert_eq!(label.as_str(), Some(text));
assert_eq!(Label::from_text(text), label);
}
assert_eq!(Label::Other.as_str(), None);
for other in [
"CERTIFICATE",
"private key",
"PRIVATE KEY",
" PRIVATE KEY",
"DSA PRIVATE KEY",
"",
] {
assert_eq!(Label::from_text(other), Label::Other, "{other:?}");
}
}
#[test]
fn jose_alg_wraps_each_algorithm_enum() {
assert_eq!(
JoseAlg::from(SigningAlg::EdDSA),
JoseAlg::Signing(SigningAlg::EdDSA)
);
assert_eq!(
JoseAlg::from(KeyMgmtAlg::A128KW),
JoseAlg::KeyMgmt(KeyMgmtAlg::A128KW)
);
assert_eq!(
JoseAlg::from(ContentEncAlg::A128CbcHs256),
JoseAlg::ContentEnc(ContentEncAlg::A128CbcHs256)
);
let unknown: JoseAlg = SigningAlg::from("Ed448").into();
assert_eq!(
unknown,
JoseAlg::Signing(SigningAlg::Unknown("Ed448".into()))
);
}
#[test]
fn the_same_name_in_two_registries_stays_distinct() {
assert_ne!(
JoseAlg::from(SigningAlg::from("A128GCM")),
JoseAlg::from(ContentEncAlg::A128GCM)
);
}
}