use pkcs8::der::asn1::{AnyRef, OctetStringRef};
use pkcs8::{
AlgorithmIdentifierRef, ObjectIdentifier, PrivateKeyInfoRef, SubjectPublicKeyInfoRef,
der::{Decode, Encode},
};
use crate::error::Error;
use crate::key::ed25519::{ED25519_PUBLIC_KEY_LEN, Ed25519PublicKey, Ed25519SigningKey};
use crate::key::rsa::{RsaPublicKey, RsaSigningKey};
pub(crate) const PEM_LABEL_PRIVATE_KEY: &str = "PRIVATE KEY";
pub(crate) const PEM_LABEL_RSA_PRIVATE_KEY: &str = "RSA PRIVATE KEY";
pub(crate) const PEM_LABEL_PUBLIC_KEY: &str = "PUBLIC KEY";
const OID_ED25519: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112");
const OID_RSA_ENCRYPTION: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1");
pub(crate) fn ed25519_signing_key_from_pem(pem_text: &str) -> Result<Ed25519SigningKey, Error> {
let block = parse_single_pem(pem_text)?;
if block.tag() != PEM_LABEL_PRIVATE_KEY {
return Err(Error::UnexpectedPemLabel(
block.tag().to_owned(),
PEM_LABEL_PRIVATE_KEY,
));
}
let der = block.contents();
let oid = parse_pkcs8_algorithm_oid(der)?;
if oid != OID_ED25519 {
return Err(Error::UnsupportedAlgorithm(format!(
"PKCS#8 body identifies algorithm {oid}, expected id-Ed25519 (1.3.101.112)"
)));
}
Ed25519SigningKey::from_pkcs8_der(der)
}
#[must_use]
pub(crate) fn ed25519_signing_key_to_pem(key: &Ed25519SigningKey) -> String {
encode_pem(PEM_LABEL_PRIVATE_KEY, key.to_pkcs8_der())
}
pub(crate) fn ed25519_public_key_from_pem(pem_text: &str) -> Result<Ed25519PublicKey, Error> {
let block = parse_single_pem(pem_text)?;
if block.tag() != PEM_LABEL_PUBLIC_KEY {
return Err(Error::UnexpectedPemLabel(
block.tag().to_owned(),
PEM_LABEL_PUBLIC_KEY,
));
}
let (oid, bits) = parse_spki(block.contents())?;
if oid != OID_ED25519 {
return Err(Error::UnsupportedAlgorithm(format!(
"SPKI body identifies algorithm {oid}, expected id-Ed25519 (1.3.101.112)"
)));
}
if bits.len() != ED25519_PUBLIC_KEY_LEN {
return Err(Error::InvalidMultikeyLength {
expected: ED25519_PUBLIC_KEY_LEN,
actual: bits.len(),
});
}
Ed25519PublicKey::from_bytes(bits)
}
#[must_use]
pub(crate) fn ed25519_public_key_to_pem(key: &Ed25519PublicKey) -> String {
let spki = ed25519_spki_der(key.as_bytes());
encode_pem(PEM_LABEL_PUBLIC_KEY, &spki)
}
pub(crate) fn rsa_signing_key_from_pem(pem_text: &str) -> Result<RsaSigningKey, Error> {
let block = parse_single_pem(pem_text)?;
match block.tag() {
PEM_LABEL_PRIVATE_KEY => {
let der = block.contents();
let oid = parse_pkcs8_algorithm_oid(der)?;
if oid != OID_RSA_ENCRYPTION {
return Err(Error::UnsupportedAlgorithm(format!(
"PKCS#8 body identifies algorithm {oid}, expected rsaEncryption \
(1.2.840.113549.1.1.1)"
)));
}
RsaSigningKey::from_pkcs8_der(der)
}
PEM_LABEL_RSA_PRIVATE_KEY => {
let pkcs8 = wrap_pkcs1_rsa_as_pkcs8(block.contents())?;
RsaSigningKey::from_pkcs8_der(&pkcs8)
}
other => Err(Error::UnexpectedPemLabel(
other.to_owned(),
PEM_LABEL_PRIVATE_KEY,
)),
}
}
#[must_use]
pub(crate) fn rsa_signing_key_to_pem(key: &RsaSigningKey) -> String {
encode_pem(PEM_LABEL_PRIVATE_KEY, key.to_pkcs8_der())
}
pub(crate) fn rsa_public_key_from_pem(pem_text: &str) -> Result<RsaPublicKey, Error> {
let block = parse_single_pem(pem_text)?;
if block.tag() != PEM_LABEL_PUBLIC_KEY {
return Err(Error::UnexpectedPemLabel(
block.tag().to_owned(),
PEM_LABEL_PUBLIC_KEY,
));
}
let (oid, _bits) = parse_spki(block.contents())?;
if oid != OID_RSA_ENCRYPTION {
return Err(Error::UnsupportedAlgorithm(format!(
"SPKI body identifies algorithm {oid}, expected rsaEncryption \
(1.2.840.113549.1.1.1)"
)));
}
RsaPublicKey::from_spki_der(block.contents())
}
#[must_use]
pub(crate) fn rsa_public_key_to_pem(key: &RsaPublicKey) -> String {
encode_pem(PEM_LABEL_PUBLIC_KEY, key.as_spki_der())
}
fn parse_single_pem(pem_text: &str) -> Result<pem::Pem, Error> {
pem::parse(pem_text.as_bytes()).map_err(|e| Error::InvalidPem(e.to_string()))
}
fn encode_pem(label: &'static str, der: &[u8]) -> String {
let block = pem::Pem::new(label, der.to_vec());
pem::encode(&block)
}
fn parse_pkcs8_algorithm_oid(der: &[u8]) -> Result<ObjectIdentifier, Error> {
let info = PrivateKeyInfoRef::from_der(der).map_err(|e| Error::InvalidPkcs8(e.to_string()))?;
Ok(info.algorithm.oid)
}
fn parse_spki(der: &[u8]) -> Result<(ObjectIdentifier, &[u8]), Error> {
let spki =
SubjectPublicKeyInfoRef::from_der(der).map_err(|e| Error::InvalidPem(e.to_string()))?;
let bits = spki.subject_public_key.as_bytes().ok_or_else(|| {
Error::InvalidPem(
"SubjectPublicKeyInfo.subjectPublicKey is not a well-formed BIT STRING".into(),
)
})?;
Ok((spki.algorithm.oid, bits))
}
fn wrap_pkcs1_rsa_as_pkcs8(pkcs1_der: &[u8]) -> Result<Vec<u8>, Error> {
let private_key =
OctetStringRef::new(pkcs1_der).map_err(|e| Error::InvalidPkcs8(e.to_string()))?;
let info = PrivateKeyInfoRef::new(
AlgorithmIdentifierRef {
oid: OID_RSA_ENCRYPTION,
parameters: Some(AnyRef::NULL),
},
private_key,
);
info.to_der().map_err(|e| Error::InvalidPem(e.to_string()))
}
fn ed25519_spki_der(key_bytes: &[u8; ED25519_PUBLIC_KEY_LEN]) -> Vec<u8> {
let mut out = Vec::with_capacity(44);
out.extend_from_slice(&[
0x30, 0x2A, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x70, 0x03, 0x21, 0x00,
]);
out.extend_from_slice(key_bytes);
out
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::key::rsa::RsaBits;
#[test]
fn ed25519_signing_key_pem_roundtrip() {
let key = Ed25519SigningKey::generate().expect("rng");
let pem = ed25519_signing_key_to_pem(&key);
assert!(pem.starts_with("-----BEGIN PRIVATE KEY-----"));
let reloaded = ed25519_signing_key_from_pem(&pem).expect("parse");
assert_eq!(reloaded.public_key(), key.public_key());
}
#[test]
fn ed25519_public_key_pem_roundtrip() {
let key = Ed25519SigningKey::generate().expect("rng");
let public = key.public_key();
let pem = ed25519_public_key_to_pem(&public);
assert!(pem.starts_with("-----BEGIN PUBLIC KEY-----"));
let reloaded = ed25519_public_key_from_pem(&pem).expect("parse");
assert_eq!(reloaded, public);
}
#[test]
fn rsa_signing_key_pem_roundtrip() {
let key = RsaSigningKey::generate(RsaBits::Rsa2048).expect("rng");
let pem = rsa_signing_key_to_pem(&key);
assert!(pem.starts_with("-----BEGIN PRIVATE KEY-----"));
let reloaded = rsa_signing_key_from_pem(&pem).expect("parse");
assert_eq!(reloaded.bits(), key.bits());
}
#[test]
fn rsa_public_key_pem_roundtrip() {
let key = RsaSigningKey::generate(RsaBits::Rsa2048).expect("rng");
let public = key.public_key();
let pem = rsa_public_key_to_pem(&public);
assert!(pem.starts_with("-----BEGIN PUBLIC KEY-----"));
let reloaded = rsa_public_key_from_pem(&pem).expect("parse");
assert_eq!(reloaded, public);
}
#[test]
fn wrong_label_returns_unexpected_label_error() {
let key = Ed25519SigningKey::generate().expect("rng");
let pem = ed25519_signing_key_to_pem(&key);
let err = ed25519_public_key_from_pem(&pem).expect_err("wrong label");
assert!(matches!(
err,
Error::UnexpectedPemLabel(_, PEM_LABEL_PUBLIC_KEY)
));
}
#[test]
fn malformed_rsa_private_key_pem_is_rejected() {
let pem = "-----BEGIN RSA PRIVATE KEY-----\n\
AAAA\n\
-----END RSA PRIVATE KEY-----\n";
rsa_signing_key_from_pem(pem).expect_err("malformed PKCS#1 body must fail");
}
#[test]
fn legacy_pkcs1_rsa_private_key_pem_is_accepted() {
let key = RsaSigningKey::generate(RsaBits::Rsa2048).expect("rng");
let pkcs8_pem = rsa_signing_key_to_pem(&key);
let pkcs8_der = {
let block = parse_single_pem(&pkcs8_pem).expect("re-parse");
block.contents().to_vec()
};
let pkcs1_body = {
let info = PrivateKeyInfoRef::from_der(&pkcs8_der).expect("pkcs8 decode");
info.private_key.as_bytes().to_vec()
};
let pkcs1_pem = encode_pem(PEM_LABEL_RSA_PRIVATE_KEY, &pkcs1_body);
let reloaded = rsa_signing_key_from_pem(&pkcs1_pem).expect("accept PKCS#1");
assert_eq!(reloaded.bits(), key.bits());
}
#[test]
fn pkcs8_with_non_rsa_oid_reports_algorithm_mismatch() {
let ed = Ed25519SigningKey::generate().expect("rng");
let pem = ed25519_signing_key_to_pem(&ed);
let err = rsa_signing_key_from_pem(&pem).expect_err("Ed25519 key must not load as RSA");
assert!(matches!(err, Error::UnsupportedAlgorithm(msg) if msg.contains("rsaEncryption")));
}
}