diff --git a/src/error.rs b/src/error.rs index 7b29f5f..00c454f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -75,8 +75,10 @@ pub enum Error { Pkcs8(pkcs8::Error), // rustc mysteriously complains when we use `#[source]` here #[error("could not parse file in PKCS#8 format (RSA)")] Pkcs8Rsa(#[source] rsa::pkcs8::Error), - #[error("could not parse file in PKCS#8 format (spki): {0}")] + #[error("could not parse file in PKCS#8 format (SPKI): {0}")] Pkcs8Spki(pkcs8::spki::Error), // rustc mysteriously complains when we use `#[source]` here + #[error("could not parse file in PKCS#8 format (RSA/SPKI)")] + Pkcs8RsaSpki(#[source] rsa::pkcs8::spki::Error), #[error("could not parse file in PKCS#8 format (Ed25519)")] Pkcs8Ed25519(#[source] ed25519_dalek::SignatureError), #[error("unknown algorithm OID {0:?} in PKCS#8 file")] diff --git a/src/keys/mod.rs b/src/keys/mod.rs index eed7005..1c8b385 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -12,6 +12,7 @@ pub use self::pkcs1::{ }; pub use self::pkcs8::{ decode_pkcs8_pem_privkey, decode_pkcs8_der_privkey, decode_pkcs8_encrypted_der_privkey, + decode_pkcs8_pem_pubkey, decode_pkcs8_der_pubkey, }; mod openssh; diff --git a/src/keys/pkcs8.rs b/src/keys/pkcs8.rs index 915c034..3aa8c6b 100644 --- a/src/keys/pkcs8.rs +++ b/src/keys/pkcs8.rs @@ -2,7 +2,7 @@ use ecdsa::elliptic_curve; use ed25519_dalek::ed25519; use pkcs8::AssociatedOid as _; use crate::error::{Result, Error}; -use crate::pubkey::Privkey; +use crate::pubkey::{Privkey, Pubkey}; /// Decode a private key from PKCS#8 PEM format. /// @@ -67,3 +67,43 @@ pub fn decode_pkcs8_encrypted_der_privkey(der_data: &[u8], passphrase: &[u8]) -> let document = info.decrypt(passphrase).map_err(Error::Pkcs8)?; decode_pkcs8_der_privkey(document.as_bytes()) } + +/// Decode a public key from PKCS#8 PEM format. +/// +/// Files in this format start with `-----BEGIN PUBLIC KEY-----`. +pub fn decode_pkcs8_pem_pubkey(pem_data: &[u8]) -> Result { + let data = super::decode_pem(pem_data, "PUBLIC KEY")?; + decode_pkcs8_der_pubkey(&data) +} + +/// Decode a public key from PKCS#8 DER (binary) format. +pub fn decode_pkcs8_der_pubkey(der_data: &[u8]) -> Result { + let info = pkcs8::SubjectPublicKeyInfo::try_from(der_data).map_err(Error::Pkcs8Spki)?; + let algo_oid = info.algorithm.oid; + if algo_oid == RSA_OID { + // unfortunately, `rsa` uses an older version of `pkcs8`, so we must re-parse the DER + let info = rsa::pkcs8::SubjectPublicKeyInfo::try_from(der_data).map_err(Error::Pkcs8RsaSpki)?; + let pubkey = rsa::RsaPublicKey::try_from(info).map_err(Error::Pkcs8RsaSpki)?; + Ok(Pubkey::Rsa(pubkey.into())) + } else if algo_oid == EC_OID { + let curve_oid = info.algorithm.parameters_oid().map_err(Error::Pkcs8Spki)?; + if curve_oid == p256::NistP256::OID { + let pubkey = elliptic_curve::PublicKey::::try_from(info) + .map_err(Error::Pkcs8Spki)?; + Ok(Pubkey::EcdsaP256(pubkey.into())) + } else if curve_oid == p384::NistP384::OID { + let pubkey = elliptic_curve::PublicKey::::try_from(info) + .map_err(Error::Pkcs8Spki)?; + Ok(Pubkey::EcdsaP384(pubkey.into())) + } else { + Err(Error::Pkcs8BadCurveOid(curve_oid.to_string())) + } + } else if algo_oid == ED25519_OID { + let public_bytes = ed25519::pkcs8::PublicKeyBytes::try_from(info).map_err(Error::Pkcs8Spki)?; + let pubkey = ed25519_dalek::PublicKey::from_bytes(&public_bytes.to_bytes()) + .map_err(Error::Pkcs8Ed25519)?; + Ok(Pubkey::Ed25519(pubkey.into())) + } else { + Err(Error::Pkcs8BadAlgorithmOid(algo_oid.to_string())) + } +} diff --git a/tests/keys/main.rs b/tests/keys/main.rs index 67a7ebe..03fec52 100644 --- a/tests/keys/main.rs +++ b/tests/keys/main.rs @@ -131,20 +131,30 @@ fn check_pkcs8_privkey(expected_privkey: makiko::Privkey, pem_data: &str, passwo assert_privkeys_eq!(&decoded_privkey, &expected_privkey); } +fn check_pkcs8_pubkey(expected_privkey: makiko::Privkey, pem_data: &str) { + let decoded_pubkey = makiko::keys::decode_pkcs8_pem_pubkey(pem_data.as_bytes()) + .expect("could not decode pubkey"); + assert_eq!(decoded_pubkey, expected_privkey.pubkey()); +} + #[test] fn test_decode_pkcs8_rsa() { check_pkcs8_privkey(keys::pkcs8_rsa(), keys::PKCS8_RSA_PRIVKEY_FILE, ""); + check_pkcs8_pubkey(keys::pkcs8_rsa(), keys::PKCS8_RSA_PUBKEY_FILE); } #[test] fn test_decode_pkcs8_ecdsa_p256() { check_pkcs8_privkey(keys::pkcs8_ecdsa_p256(), keys::PKCS8_ECDSA_P256_PRIVKEY_FILE, ""); + check_pkcs8_pubkey(keys::pkcs8_ecdsa_p256(), keys::PKCS8_ECDSA_P256_PUBKEY_FILE); } #[test] fn test_decode_pkcs8_ecdsa_p384() { check_pkcs8_privkey(keys::pkcs8_ecdsa_p384(), keys::PKCS8_ECDSA_P384_PRIVKEY_FILE, ""); + check_pkcs8_pubkey(keys::pkcs8_ecdsa_p384(), keys::PKCS8_ECDSA_P384_PUBKEY_FILE); } #[test] fn test_decode_pkcs8_ed25519() { check_pkcs8_privkey(keys::pkcs8_ed25519(), keys::PKCS8_ED25519_PRIVKEY_FILE, ""); + check_pkcs8_pubkey(keys::pkcs8_ed25519(), keys::PKCS8_ED25519_PUBKEY_FILE); } #[test] fn test_decode_pkcs8v2_ed25519() {