use zeroize::Zeroizing;
use super::types::{AlgId, KeyFormat, Label};
use super::{ec, okp, pem, pkcs8, spki};
use crate::error::{Error, Result};
use crate::jwk::{Jwk, KeyMaterial};
const PRIVATE_KEY_REQUIRED: &str = "private key required";
fn label_format(label: Label) -> Option<KeyFormat> {
match label {
Label::PrivateKey => Some(KeyFormat::Pkcs8),
Label::PublicKey => Some(KeyFormat::Spki),
Label::EcPrivateKey => Some(KeyFormat::Sec1),
Label::RsaPublic => Some(KeyFormat::Pkcs1),
Label::EncryptedPrivateKey | Label::RsaPrivate | Label::Other => None,
}
}
fn format_label(format: KeyFormat) -> Label {
match format {
KeyFormat::Pkcs8 => Label::PrivateKey,
KeyFormat::Spki => Label::PublicKey,
KeyFormat::Sec1 => Label::EcPrivateKey,
KeyFormat::Pkcs1 => Label::RsaPublic,
}
}
impl Jwk {
pub fn from_pem(pem: &str) -> Result<Jwk> {
let (label, der) = pem::decode(pem)?;
let format = label_format(label).ok_or(Error::UnsupportedAlgorithm)?;
Jwk::from_der(&der, format)
}
pub fn from_der(der: &[u8], format: KeyFormat) -> Result<Jwk> {
match format {
KeyFormat::Pkcs8 => {
let parts = pkcs8::decode(der)?;
match parts.alg {
AlgId::Ec(_) => ec::from_pkcs8(parts),
AlgId::Ed25519 | AlgId::X25519 => okp::from_pkcs8(parts),
AlgId::RsaEncryption => Err(Error::UnsupportedAlgorithm),
}
}
KeyFormat::Spki => {
let parts = spki::decode(der)?;
match parts.alg {
AlgId::Ec(_) => ec::from_spki(parts),
AlgId::Ed25519 | AlgId::X25519 => okp::from_spki(parts),
AlgId::RsaEncryption => super::rsa::from_spki(parts),
}
}
KeyFormat::Sec1 => ec::from_sec1(der),
KeyFormat::Pkcs1 => super::rsa::from_pkcs1(der),
}
}
pub fn to_der(&self, format: KeyFormat) -> Result<Zeroizing<Vec<u8>>> {
let private_format = matches!(format, KeyFormat::Pkcs8 | KeyFormat::Sec1);
match (self.material(), format) {
(KeyMaterial::Oct(_), _) => Err(Error::UnsupportedAlgorithm),
(KeyMaterial::Rsa(_) | KeyMaterial::Okp(_), KeyFormat::Sec1)
| (KeyMaterial::Ec(_) | KeyMaterial::Okp(_), KeyFormat::Pkcs1) => {
Err(Error::UnsupportedAlgorithm)
}
_ if private_format && self.is_public() => Err(Error::InvalidKey(PRIVATE_KEY_REQUIRED)),
(KeyMaterial::Ec(key), KeyFormat::Pkcs8) => ec::to_pkcs8(key),
(KeyMaterial::Ec(key), KeyFormat::Sec1) => ec::to_sec1(key),
(KeyMaterial::Ec(key), KeyFormat::Spki) => ec::to_spki(key),
(KeyMaterial::Okp(key), KeyFormat::Pkcs8) => okp::to_pkcs8(key),
(KeyMaterial::Okp(key), KeyFormat::Spki) => okp::to_spki(key),
(KeyMaterial::Rsa(key), KeyFormat::Spki) => super::rsa::to_spki(key),
(KeyMaterial::Rsa(key), KeyFormat::Pkcs1) => super::rsa::to_pkcs1(key),
(KeyMaterial::Rsa(_), KeyFormat::Pkcs8) => Err(Error::InvalidKey(PRIVATE_KEY_REQUIRED)),
}
}
pub fn to_pem(&self, format: KeyFormat) -> Result<Zeroizing<String>> {
let der = self.to_der(format)?;
pem::encode(format_label(format), &der)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_import_label_maps_to_its_format_and_back() {
for format in [
KeyFormat::Pkcs8,
KeyFormat::Spki,
KeyFormat::Sec1,
KeyFormat::Pkcs1,
] {
assert_eq!(label_format(format_label(format)), Some(format));
}
for label in [Label::EncryptedPrivateKey, Label::RsaPrivate, Label::Other] {
assert_eq!(label_format(label), None, "{label:?}");
}
}
#[test]
fn a_pkcs1_rsa_private_key_is_unsupported_under_every_label_and_format() {
let der = super::super::der::fixtures::load("openssl", "reject-rsa2048-private-pkcs1.der");
for format in [KeyFormat::Pkcs8, KeyFormat::Sec1, KeyFormat::Pkcs1] {
assert_eq!(
Jwk::from_der(&der, format).err(),
Some(Error::UnsupportedAlgorithm),
"{format:?}"
);
}
for label in [Label::PrivateKey, Label::EcPrivateKey] {
let text = pem::encode(label, &der).unwrap();
assert_eq!(
Jwk::from_pem(&text).err(),
Some(Error::UnsupportedAlgorithm),
"{label:?}"
);
}
assert!(matches!(
Jwk::from_der(&der, KeyFormat::Spki),
Err(Error::InvalidKey(_))
));
}
}