From d304bb8237ebde6fd34cd2649a6312dd83662091 Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Tue, 1 Oct 2024 23:31:23 -0700 Subject: [PATCH] slh-dsa: adds pkcs8 support --- Cargo.lock | 5 +++-- Cargo.toml | 4 ++++ slh-dsa/Cargo.toml | 2 ++ slh-dsa/src/hashes/sha2.rs | 7 +++++++ slh-dsa/src/hashes/shake.rs | 7 +++++++ slh-dsa/src/lib.rs | 3 +++ slh-dsa/src/signing_key.rs | 37 ++++++++++++++++++++++++++++++++++++ slh-dsa/src/verifying_key.rs | 21 ++++++++++++++++++++ slh-dsa/tests/pkcs8.rs | 37 ++++++++++++++++++++++++++++++++++++ 9 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 slh-dsa/tests/pkcs8.rs diff --git a/Cargo.lock b/Cargo.lock index f99a7bde..88f1a5cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,8 +186,7 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "const-oid" version = "0.10.0-rc.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9adcf94f05e094fca3005698822ec791cb4433ced416afda1c5ca3b8dfc05a2f" +source = "git+https://github.com/baloo/formats.git?branch=baloo/const-oid/fips203-204-205#e9e824e04ffb1fb1948168b7b289c2fc1a298589" [[package]] name = "cpufeatures" @@ -1095,6 +1094,7 @@ version = "0.2.0-pre" dependencies = [ "aes", "cipher", + "const-oid", "criterion", "ctr", "digest", @@ -1104,6 +1104,7 @@ dependencies = [ "hybrid-array", "num-bigint", "paste", + "pkcs8", "proptest", "quickcheck", "quickcheck_macros", diff --git a/Cargo.toml b/Cargo.toml index d9709ecd..b2060d76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,7 @@ members = [ [profile.dev] opt-level = 2 + +[patch.crates-io] +# https://github.com/RustCrypto/formats/pull/1541 +const-oid = { git = "https://github.com/baloo/formats.git", branch = "baloo/const-oid/fips203-204-205" } diff --git a/slh-dsa/Cargo.toml b/slh-dsa/Cargo.toml index e3ab2c72..d513b051 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, features = ["pem"] } +const-oid = { version = "0.10.0-rc.0", features = ["db"] } [dev-dependencies] hex-literal = "0.4.1" 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/signing_key.rs b/slh-dsa/src/signing_key.rs index 8f8aa0e8..a5ba7588 100644 --- a/slh-dsa/src/signing_key.rs +++ b/slh-dsa/src/signing_key.rs @@ -5,6 +5,10 @@ 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}, + EncodePrivateKey, +}; use typenum::{Unsigned, U, U16, U24, U32}; // NewTypes for ensuring hash argument order correctness @@ -216,6 +220,39 @@ 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 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----- +"# + ); +}