diff --git a/Cargo.lock b/Cargo.lock index f99a7bde..4de07039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,9 +185,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "const-oid" -version = "0.10.0-rc.0" +version = "0.10.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9adcf94f05e094fca3005698822ec791cb4433ced416afda1c5ca3b8dfc05a2f" +checksum = "610947c93f422f713d1ed0570e7c76307aa3bbfd8db723214fed2045a2e32815" [[package]] name = "cpufeatures" @@ -1095,6 +1095,7 @@ version = "0.2.0-pre" dependencies = [ "aes", "cipher", + "const-oid", "criterion", "ctr", "digest", @@ -1104,6 +1105,7 @@ dependencies = [ "hybrid-array", "num-bigint", "paste", + "pkcs8", "proptest", "quickcheck", "quickcheck_macros", diff --git a/slh-dsa/Cargo.toml b/slh-dsa/Cargo.toml index e3ab2c72..7e369163 100644 --- a/slh-dsa/Cargo.toml +++ b/slh-dsa/Cargo.toml @@ -24,6 +24,8 @@ signature = { version = "2.3.0-pre.4", features = ["rand_core"] } hmac = "=0.13.0-pre.4" sha2 = { version = "=0.11.0-pre.4", default-features = false } digest = "=0.11.0-pre.9" +pkcs8 = { version = "=0.11.0-rc.1", default-features = false } +const-oid = { version = "0.10.0-rc.0", features = ["db"] } [dev-dependencies] hex-literal = "0.4.1" @@ -41,6 +43,7 @@ paste = "1.0.15" rand = "0.8.5" serde_json = "1.0.124" serde = { version = "1.0.207", features = ["derive"] } +pkcs8 = { version = "=0.11.0-rc.1", features = ["pem"] } [lib] bench = false diff --git a/slh-dsa/src/hashes/sha2.rs b/slh-dsa/src/hashes/sha2.rs index 2fa8e19d..5f7ef989 100644 --- a/slh-dsa/src/hashes/sha2.rs +++ b/slh-dsa/src/hashes/sha2.rs @@ -9,6 +9,7 @@ use crate::{ xmss::XmssParams, ParameterSet, }; use crate::{PkSeed, SkPrf, SkSeed}; +use const_oid::db::fips205; use digest::{Digest, KeyInit, Mac}; use hmac::Hmac; use hybrid_array::{Array, ArraySize}; @@ -168,6 +169,7 @@ impl ForsParams for Sha2_128s { } impl ParameterSet for Sha2_128s { const NAME: &'static str = "SLH-DSA-SHA2-128s"; + const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips205::ID_SLH_DSA_SHA_2_128_S; } /// SHA2 at L1 security with fast signatures @@ -190,6 +192,7 @@ impl ForsParams for Sha2_128f { } impl ParameterSet for Sha2_128f { const NAME: &'static str = "SLH-DSA-SHA2-128f"; + const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips205::ID_SLH_DSA_SHA_2_128_F; } /// Implementation of the component hash functions using SHA2 at Security Category 3 and 5 @@ -328,6 +331,7 @@ impl ForsParams for Sha2_192s { } impl ParameterSet for Sha2_192s { const NAME: &'static str = "SLH-DSA-SHA2-192s"; + const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips205::ID_SLH_DSA_SHA_2_192_S; } /// SHA2 at L3 security with fast signatures @@ -350,6 +354,7 @@ impl ForsParams for Sha2_192f { } impl ParameterSet for Sha2_192f { const NAME: &'static str = "SLH-DSA-SHA2-192f"; + const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips205::ID_SLH_DSA_SHA_2_192_F; } /// SHA2 at L5 security with small signatures @@ -372,6 +377,7 @@ impl ForsParams for Sha2_256s { } impl ParameterSet for Sha2_256s { const NAME: &'static str = "SLH-DSA-SHA2-256s"; + const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips205::ID_SLH_DSA_SHA_2_256_S; } /// SHA2 at L5 security with fast signatures @@ -394,4 +400,5 @@ impl ForsParams for Sha2_256f { } impl ParameterSet for Sha2_256f { const NAME: &'static str = "SLH-DSA-SHA2-256f"; + const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips205::ID_SLH_DSA_SHA_2_256_F; } diff --git a/slh-dsa/src/hashes/shake.rs b/slh-dsa/src/hashes/shake.rs index aa267be6..3d21730d 100644 --- a/slh-dsa/src/hashes/shake.rs +++ b/slh-dsa/src/hashes/shake.rs @@ -7,6 +7,7 @@ use crate::hypertree::HypertreeParams; use crate::wots::WotsParams; use crate::xmss::XmssParams; use crate::{ParameterSet, PkSeed, SkPrf, SkSeed}; +use const_oid::db::fips205; use digest::{ExtendableOutput, Update}; use hybrid_array::typenum::consts::{U16, U30, U32}; use hybrid_array::typenum::{U24, U34, U39, U42, U47, U49}; @@ -144,6 +145,7 @@ impl ForsParams for Shake128s { } impl ParameterSet for Shake128s { const NAME: &'static str = "SLH-DSA-SHAKE-128s"; + const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips205::ID_SLH_DSA_SHAKE_128_S; } /// SHAKE256 at L1 security with fast signatures @@ -166,6 +168,7 @@ impl ForsParams for Shake128f { } impl ParameterSet for Shake128f { const NAME: &'static str = "SLH-DSA-SHAKE-128f"; + const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips205::ID_SLH_DSA_SHAKE_128_F; } /// SHAKE256 at L3 security with small signatures @@ -188,6 +191,7 @@ impl ForsParams for Shake192s { } impl ParameterSet for Shake192s { const NAME: &'static str = "SLH-DSA-SHAKE-192s"; + const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips205::ID_SLH_DSA_SHAKE_192_S; } /// SHAKE256 at L3 security with fast signatures @@ -210,6 +214,7 @@ impl ForsParams for Shake192f { } impl ParameterSet for Shake192f { const NAME: &'static str = "SLH-DSA-SHAKE-192f"; + const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips205::ID_SLH_DSA_SHAKE_192_F; } /// SHAKE256 at L5 security with small signatures @@ -232,6 +237,7 @@ impl ForsParams for Shake256s { } impl ParameterSet for Shake256s { const NAME: &'static str = "SLH-DSA-SHAKE-256s"; + const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips205::ID_SLH_DSA_SHAKE_256_S; } /// SHAKE256 at L5 security with fast signatures @@ -254,6 +260,7 @@ impl ForsParams for Shake256f { } impl ParameterSet for Shake256f { const NAME: &'static str = "SLH-DSA-SHAKE-256f"; + const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips205::ID_SLH_DSA_SHAKE_256_F; } #[cfg(test)] diff --git a/slh-dsa/src/lib.rs b/slh-dsa/src/lib.rs index 0887bb3b..869c169a 100644 --- a/slh-dsa/src/lib.rs +++ b/slh-dsa/src/lib.rs @@ -73,6 +73,9 @@ pub trait ParameterSet: { /// Human-readable name for parameter set, matching the FIPS-205 designations const NAME: &'static str; + + /// Associated OID with the Parameter + const ALGORITHM_OID: pkcs8::ObjectIdentifier; } #[cfg(test)] diff --git a/slh-dsa/src/signature_encoding.rs b/slh-dsa/src/signature_encoding.rs index 3d84f3c1..002f7715 100644 --- a/slh-dsa/src/signature_encoding.rs +++ b/slh-dsa/src/signature_encoding.rs @@ -8,8 +8,15 @@ use crate::{fors::ForsSignature, Shake128s}; use ::signature::{Error, SignatureEncoding}; use hybrid_array::sizes::{U16224, U17088, U29792, U35664, U49856, U7856}; use hybrid_array::{Array, ArraySize}; +use pkcs8::{der::AnyRef, spki::AssociatedAlgorithmIdentifier, AlgorithmIdentifierRef}; use typenum::Unsigned; +#[cfg(feature = "alloc")] +use pkcs8::{ + der::{self, asn1::BitString}, + spki::SignatureBitStringEncoding, +}; + #[derive(Debug, Clone, PartialEq, Eq)] /// A parsed SLH-DSA signature for a given parameter set /// @@ -95,6 +102,22 @@ impl SignatureEncoding for Signature

{ } } +#[cfg(feature = "alloc")] +impl SignatureBitStringEncoding for Signature

{ + fn to_bitstring(&self) -> der::Result { + BitString::new(0, self.to_vec()) + } +} + +impl AssociatedAlgorithmIdentifier for Signature

{ + type Params = AnyRef<'static>; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef { + oid: P::ALGORITHM_OID, + parameters: None, + }; +} + impl From> for Array { fn from(sig: Signature

) -> Array { sig.to_bytes() diff --git a/slh-dsa/src/signing_key.rs b/slh-dsa/src/signing_key.rs index 8f8aa0e8..788e2414 100644 --- a/slh-dsa/src/signing_key.rs +++ b/slh-dsa/src/signing_key.rs @@ -5,6 +5,11 @@ use crate::verifying_key::VerifyingKey; use crate::{ParameterSet, PkSeed, Sha2L1, Sha2L35, Shake, VerifyingKeyLen}; use ::signature::{Error, KeypairRef, RandomizedSigner, Signer}; use hybrid_array::{Array, ArraySize}; +use pkcs8::{ + der::{self, asn1::OctetStringRef, AnyRef}, + spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier, SignatureAlgorithmIdentifier}, + EncodePrivateKey, +}; use typenum::{Unsigned, U, U16, U24, U32}; // NewTypes for ensuring hash argument order correctness @@ -216,6 +221,46 @@ impl KeypairRef for SigningKey

{ type VerifyingKey = VerifyingKey

; } +impl

TryFrom> for SigningKey

+where + P: ParameterSet, +{ + type Error = pkcs8::Error; + + fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result { + private_key_info + .algorithm + .assert_algorithm_oid(P::ALGORITHM_OID)?; + + Self::try_from(private_key_info.private_key.as_bytes()) + .map_err(|_| pkcs8::Error::KeyMalformed) + } +} + +impl

EncodePrivateKey for SigningKey

+where + P: ParameterSet, +{ + fn to_pkcs8_der(&self) -> pkcs8::Result { + let algorithm_identifier = pkcs8::AlgorithmIdentifierRef { + oid: P::ALGORITHM_OID, + parameters: None, + }; + + let private_key = self.to_bytes(); + let pkcs8_key = + pkcs8::PrivateKeyInfoRef::new(algorithm_identifier, OctetStringRef::new(&private_key)?); + Ok(der::SecretDocument::encode_msg(&pkcs8_key)?) + } +} + +impl SignatureAlgorithmIdentifier for SigningKey

{ + type Params = AnyRef<'static>; + + const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier = + Signature::

::ALGORITHM_IDENTIFIER; +} + impl SigningKeyLen for Sha2L1 { type SkLen = U<{ 4 * 16 }>; } diff --git a/slh-dsa/src/verifying_key.rs b/slh-dsa/src/verifying_key.rs index f67737fb..e8da3eef 100644 --- a/slh-dsa/src/verifying_key.rs +++ b/slh-dsa/src/verifying_key.rs @@ -7,6 +7,7 @@ use crate::Sha2L35; use crate::Shake; use ::signature::{Error, Verifier}; use hybrid_array::{Array, ArraySize}; +use pkcs8::{der, EncodePublicKey}; use typenum::{Unsigned, U, U16, U24, U32}; /// A trait specifying the length of a serialized verifying key for a given parameter set @@ -147,6 +148,26 @@ impl Verifier> for VerifyingKey

{ } } +impl

EncodePublicKey for VerifyingKey

+where + P: ParameterSet, +{ + fn to_public_key_der(&self) -> pkcs8::spki::Result { + let algorithm_identifier = pkcs8::AlgorithmIdentifierRef { + oid: P::ALGORITHM_OID, + parameters: None, + }; + + let public_key = self.to_bytes(); + let subject_public_key = der::asn1::BitStringRef::new(0, &public_key)?; + + pkcs8::SubjectPublicKeyInfo { + algorithm: algorithm_identifier, + subject_public_key, + } + .try_into() + } +} impl VerifyingKeyLen for Sha2L1 { type VkLen = U<32>; } diff --git a/slh-dsa/tests/pkcs8.rs b/slh-dsa/tests/pkcs8.rs new file mode 100644 index 00000000..8ccf9573 --- /dev/null +++ b/slh-dsa/tests/pkcs8.rs @@ -0,0 +1,37 @@ +use hex_literal::hex; +use pkcs8::{DecodePrivateKey, EncodePrivateKey, EncodePublicKey, LineEnding}; +use slh_dsa::{Sha2_128s, SigningKey, VerifyingKey}; +use std::ops::Deref; + +#[test] +fn pkcs8_output() { + let signing = SigningKey::::try_from(&hex!("A0FC7756572F3008F544399C25C9E087C28287AB54ADB1601FCACF85C2995A54404F690CD9A145512F61F2E4DE9292DA71371E754B3C2A79F2471E14608A2E34")[..]).unwrap(); + + let out = signing.to_pkcs8_pem(LineEnding::LF).unwrap(); + + assert_eq!( + out.deref(), + r#"-----BEGIN PRIVATE KEY----- +MFICAQAwCwYJYIZIAWUDBAMUBECg/HdWVy8wCPVEOZwlyeCHwoKHq1StsWAfys+F +wplaVEBPaQzZoUVRL2Hy5N6SktpxNx51SzwqefJHHhRgii40 +-----END PRIVATE KEY----- +"# + ); + + let parsed = SigningKey::::from_pkcs8_pem(out.deref()).unwrap(); + + assert_eq!(parsed, signing); + + let public: VerifyingKey = parsed.as_ref().clone(); + + let out = public.to_public_key_pem(LineEnding::LF).unwrap(); + + assert_eq!( + out.deref(), + r#"-----BEGIN PUBLIC KEY----- +MDAwCwYJYIZIAWUDBAMUAyEAQE9pDNmhRVEvYfLk3pKS2nE3HnVLPCp58kceFGCK +LjQ= +-----END PUBLIC KEY----- +"# + ); +}