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";
const PKCS1_PRIVATE: &str = "pkcs1 export writes the public key; call to_public first";
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 | Label::RsaPrivate => Some(KeyFormat::Pkcs1),
Label::EncryptedPrivateKey | 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)?;
match label {
Label::RsaPublic => super::rsa::from_pkcs1_public(&der),
Label::RsaPrivate => super::rsa::from_pkcs1_private(&der),
_ => 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 => super::rsa::from_pkcs8(parts),
}
}
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::Rsa(_), KeyFormat::Pkcs1) if !self.is_public() => {
Err(Error::InvalidKey(PKCS1_PRIVATE))
}
(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(key), KeyFormat::Pkcs8) => super::rsa::to_pkcs8(key),
}
}
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));
}
assert_eq!(label_format(Label::RsaPrivate), Some(KeyFormat::Pkcs1));
for label in [Label::EncryptedPrivateKey, Label::Other] {
assert_eq!(label_format(label), None, "{label:?}");
}
}
#[test]
fn a_pkcs1_rsa_private_key_imports_only_as_pkcs1() {
let der = super::super::der::fixtures::load("openssl", "reject-rsa2048-private-pkcs1.der");
let imported = Jwk::from_der(&der, KeyFormat::Pkcs1).unwrap();
assert!(!imported.is_public() && imported.alg().is_none());
let pem = pem::encode(Label::RsaPrivate, &der).unwrap();
assert_eq!(*Jwk::from_pem(&pem).unwrap().to_json(), *imported.to_json());
for format in [KeyFormat::Pkcs8, KeyFormat::Sec1, KeyFormat::Spki] {
assert!(
matches!(Jwk::from_der(&der, format), Err(Error::InvalidKey(_))),
"{format:?}"
);
}
for label in [Label::PrivateKey, Label::EcPrivateKey, Label::RsaPublic] {
let text = pem::encode(label, &der).unwrap();
assert!(
matches!(Jwk::from_pem(&text), Err(Error::InvalidKey(_))),
"{label:?}"
);
}
let public = super::super::der::fixtures::load("openssl", "rsa2048-pkcs1-pub.der");
let text = pem::encode(Label::RsaPrivate, &public).unwrap();
assert!(matches!(Jwk::from_pem(&text), Err(Error::InvalidKey(_))));
let pkcs8 = imported.to_der(KeyFormat::Pkcs8).unwrap();
let back = Jwk::from_der(&pkcs8, KeyFormat::Pkcs8).unwrap();
assert_eq!(*back.to_json(), *imported.to_json());
let public = imported.to_public().unwrap();
assert_eq!(
public.to_der(KeyFormat::Pkcs8).err(),
Some(Error::InvalidKey(PRIVATE_KEY_REQUIRED))
);
}
#[test]
fn pkcs1_export_writes_a_public_key_and_refuses_a_private_one() {
let der = super::super::der::fixtures::load("openssl", "reject-rsa2048-private-pkcs1.der");
let private = Jwk::from_der(&der, KeyFormat::Pkcs1).unwrap();
assert!(!private.is_public());
let refused = Some(Error::InvalidKey(PKCS1_PRIVATE));
assert_eq!(private.to_der(KeyFormat::Pkcs1).err(), refused);
assert_eq!(private.to_pem(KeyFormat::Pkcs1).err(), refused);
assert_eq!(
PKCS1_PRIVATE,
"pkcs1 export writes the public key; call to_public first"
);
let public = private.to_public().unwrap();
let pkcs1 = public.to_der(KeyFormat::Pkcs1).unwrap();
assert_eq!(
&pkcs1[..],
&super::super::der::fixtures::load("openssl", "rsa2048-pkcs1-pub.der")[..]
);
assert!(
public
.to_pem(KeyFormat::Pkcs1)
.unwrap()
.starts_with("-----BEGIN RSA PUBLIC KEY-----\n")
);
let ec = Jwk::from_json(
br#"{"kty":"EC","crv":"P-256","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM","d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"}"#,
)
.unwrap();
assert_eq!(
ec.to_der(KeyFormat::Pkcs1).err(),
Some(Error::UnsupportedAlgorithm)
);
}
}