From c41adab68f9a4dbd5487e3ecdafcdb077f3559f5 Mon Sep 17 00:00:00 2001 From: zz-sol Date: Wed, 10 Jun 2026 06:44:35 -0400 Subject: [PATCH] Validate SPKI OID/bytes and add pkcs8 tests (#43) Add strict SPKI validation and tests for PKCS#8 public keys. Introduce OID and ALGORITHM_ID constants and refactor SPKI parsing into verification_key_bytes_from_spki which verifies the algorithm OID, parameters, and key byte length/format, returning appropriate pkcs8::spki::Error values. Update TryFrom/EncodePublicKey/DecodePublicKey implementations to use the new helper and to propagate/mapping errors correctly. Add two tests (behind the pkcs8 feature) to assert rejection of SPKI docs with the wrong algorithm OID and with malformed key bytes. --- .../src/ed_sigs/tests/decoding.rs | 39 +++++++++++++++ .../src/ed_sigs/verification_key.rs | 47 +++++++++++++++---- 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/curve25519/solana-ed25519/src/ed_sigs/tests/decoding.rs b/curve25519/solana-ed25519/src/ed_sigs/tests/decoding.rs index fe149a2..4816790 100644 --- a/curve25519/solana-ed25519/src/ed_sigs/tests/decoding.rs +++ b/curve25519/solana-ed25519/src/ed_sigs/tests/decoding.rs @@ -78,6 +78,45 @@ fn decode_der_to_verification_key() { assert_eq!(hex::decode(vk_bytes_string).unwrap(), vk.as_ref()); } +#[test] +#[cfg(feature = "pkcs8")] +fn reject_public_key_der_with_wrong_algorithm_oid() { + let vk = VerificationKey::from_public_key_der(PUBLIC_KEY_DER).unwrap(); + let oid = pkcs8::ObjectIdentifier::new_unwrap("1.3.101.110"); // X25519 + let spki = pkcs8::spki::SubjectPublicKeyInfoRef { + algorithm: pkcs8::spki::AlgorithmIdentifierRef { + oid, + parameters: None, + }, + subject_public_key: pkcs8::der::asn1::BitStringRef::from_bytes(vk.as_ref()).unwrap(), + }; + let doc = pkcs8::Document::try_from(spki).unwrap(); + + assert_eq!( + VerificationKey::from_public_key_der(doc.as_bytes()).unwrap_err(), + pkcs8::spki::Error::OidUnknown { oid } + ); +} + +#[test] +#[cfg(feature = "pkcs8")] +fn reject_public_key_der_with_malformed_key_bytes() { + let oid = pkcs8::ObjectIdentifier::new_unwrap("1.3.101.112"); // Ed25519 + let spki = pkcs8::spki::SubjectPublicKeyInfoRef { + algorithm: pkcs8::spki::AlgorithmIdentifierRef { + oid, + parameters: None, + }, + subject_public_key: pkcs8::der::asn1::BitStringRef::from_bytes(&[0u8; 31]).unwrap(), + }; + let doc = pkcs8::Document::try_from(spki).unwrap(); + + assert_eq!( + VerificationKey::from_public_key_der(doc.as_bytes()).unwrap_err(), + pkcs8::spki::Error::KeyMalformed + ); +} + #[test] #[cfg(feature = "pem")] fn decode_doc_to_verification_key() { diff --git a/curve25519/solana-ed25519/src/ed_sigs/verification_key.rs b/curve25519/solana-ed25519/src/ed_sigs/verification_key.rs index 06e4561..a059f76 100644 --- a/curve25519/solana-ed25519/src/ed_sigs/verification_key.rs +++ b/curve25519/solana-ed25519/src/ed_sigs/verification_key.rs @@ -28,7 +28,8 @@ use ed25519::{Signature, signature::Verifier}; use pkcs8::der::asn1::BitStringRef; #[cfg(feature = "pkcs8")] use pkcs8::spki::{ - AlgorithmIdentifierRef, DecodePublicKey, EncodePublicKey, SubjectPublicKeyInfoRef, + AlgorithmIdentifierRef, DecodePublicKey, EncodePublicKey, Error as SpkiError, + SubjectPublicKeyInfoRef, }; #[cfg(feature = "pkcs8")] use pkcs8::{Document, ObjectIdentifier}; @@ -38,6 +39,14 @@ use super::{Error, scalar_from_sha512}; /// The length of an ed25519 `VerificationKey`, in bytes. pub const VERIFICATION_KEY_LENGTH: usize = 32; +#[cfg(feature = "pkcs8")] +const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112"); // RFC 8410 +#[cfg(feature = "pkcs8")] +const ALGORITHM_ID: AlgorithmIdentifierRef<'_> = AlgorithmIdentifierRef { + oid: OID, + parameters: None, +}; + const LEGACY_EXCLUDED_R_ENCODINGS: [[u8; 32]; 11] = [ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -163,10 +172,32 @@ impl<'a> TryFrom> for VerificationKeyBytes { type Error = Error; fn try_from(spki: SubjectPublicKeyInfoRef<'a>) -> Result { - Ok(VerificationKeyBytes::try_from(spki.subject_public_key.as_bytes().unwrap()).unwrap()) + verification_key_bytes_from_spki(spki).map_err(|_| Error::MalformedPublicKey) } } +#[cfg(feature = "pkcs8")] +fn verification_key_bytes_from_spki( + spki: SubjectPublicKeyInfoRef<'_>, +) -> Result { + if spki.algorithm.oid != OID { + return Err(SpkiError::OidUnknown { + oid: spki.algorithm.oid, + }); + } + + if spki.algorithm != ALGORITHM_ID { + return Err(SpkiError::KeyMalformed); + } + + let bytes = spki + .subject_public_key + .as_bytes() + .ok_or(SpkiError::KeyMalformed)?; + + VerificationKeyBytes::try_from(bytes).map_err(|_| SpkiError::KeyMalformed) +} + /// A valid Ed25519 verification key. /// /// This is also called a public key by other implementations. @@ -262,12 +293,8 @@ impl TryFrom<[u8; 32]> for VerificationKey { impl EncodePublicKey for VerificationKey { /// Serialize [`VerificationKey`] to an ASN.1 DER-encoded document. fn to_public_key_der(&self) -> pkcs8::spki::Result { - let alg_info = AlgorithmIdentifierRef { - oid: ObjectIdentifier::new_unwrap("1.3.101.112"), // RFC 8410 - parameters: None, - }; SubjectPublicKeyInfoRef { - algorithm: alg_info, + algorithm: ALGORITHM_ID, subject_public_key: BitStringRef::from_bytes(&self.A_bytes.0[..])?, } .try_into() @@ -278,9 +305,9 @@ impl EncodePublicKey for VerificationKey { impl DecodePublicKey for VerificationKey { /// Deserialize [`VerificationKey`] from ASN.1 DER bytes (32 bytes). fn from_public_key_der(bytes: &[u8]) -> Result { - let spki = SubjectPublicKeyInfoRef::try_from(bytes).unwrap(); - let pk_bytes = spki.subject_public_key.as_bytes().unwrap(); - Ok(Self::try_from(pk_bytes).unwrap()) + let spki = SubjectPublicKeyInfoRef::try_from(bytes)?; + let pk_bytes = verification_key_bytes_from_spki(spki)?; + Self::try_from(pk_bytes).map_err(|_| SpkiError::KeyMalformed) } }