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.
This commit is contained in:
zz-sol 2026-06-10 06:44:35 -04:00 committed by GitHub
parent 34a01d5b75
commit c41adab68f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 76 additions and 10 deletions

View file

@ -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() {

View file

@ -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<SubjectPublicKeyInfoRef<'a>> for VerificationKeyBytes {
type Error = Error;
fn try_from(spki: SubjectPublicKeyInfoRef<'a>) -> Result<VerificationKeyBytes, Error> {
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<VerificationKeyBytes, SpkiError> {
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<Document> {
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<Self, pkcs8::spki::Error> {
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)
}
}