slh-dsa: adds pkcs8 support (#867)

This commit is contained in:
Arthur Gautier
2025-01-19 13:11:43 -08:00
committed by GitHub
parent c9eabf70e4
commit e658b9686b
9 changed files with 179 additions and 1 deletions
Generated
+2
View File
@@ -1162,6 +1162,7 @@ version = "0.2.0-pre"
dependencies = [
"aes",
"cipher",
"const-oid",
"criterion",
"ctr",
"digest 0.11.0-pre.9",
@@ -1171,6 +1172,7 @@ dependencies = [
"hybrid-array",
"num-bigint",
"paste",
"pkcs8",
"proptest",
"quickcheck",
"quickcheck_macros",
+4 -1
View File
@@ -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.1", 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
@@ -51,4 +54,4 @@ harness = false
[features]
alloc = []
default = ["alloc"]
default = ["alloc", "pkcs8/alloc"]
+7
View File
@@ -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};
@@ -169,6 +170,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
@@ -191,6 +193,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
@@ -330,6 +333,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
@@ -352,6 +356,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
@@ -374,6 +379,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
@@ -396,4 +402,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;
}
+7
View File
@@ -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};
@@ -146,6 +147,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
@@ -168,6 +170,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
@@ -190,6 +193,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
@@ -212,6 +216,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
@@ -234,6 +239,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
@@ -256,6 +262,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)]
+3
View File
@@ -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)]
+23
View File
@@ -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<P: ParameterSet> SignatureEncoding for Signature<P> {
}
}
#[cfg(feature = "alloc")]
impl<P: ParameterSet> SignatureBitStringEncoding for Signature<P> {
fn to_bitstring(&self) -> der::Result<BitString> {
BitString::new(0, self.to_vec())
}
}
impl<P: ParameterSet> AssociatedAlgorithmIdentifier for Signature<P> {
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef {
oid: P::ALGORITHM_OID,
parameters: None,
};
}
impl<P: ParameterSet> From<Signature<P>> for Array<u8, P::SigLen> {
fn from(sig: Signature<P>) -> Array<u8, P::SigLen> {
sig.to_bytes()
+51
View File
@@ -5,8 +5,18 @@ 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::AnyRef,
spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier, SignatureAlgorithmIdentifier},
};
use typenum::{Unsigned, U, U16, U24, U32};
#[cfg(feature = "alloc")]
use pkcs8::{
der::{self, asn1::OctetStringRef},
EncodePrivateKey,
};
// NewTypes for ensuring hash argument order correctness
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct SkSeed<N: ArraySize>(pub(crate) Array<u8, N>);
@@ -215,6 +225,47 @@ impl<P: ParameterSet> KeypairRef for SigningKey<P> {
type VerifyingKey = VerifyingKey<P>;
}
impl<P> TryFrom<pkcs8::PrivateKeyInfoRef<'_>> for SigningKey<P>
where
P: ParameterSet,
{
type Error = pkcs8::Error;
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
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)
}
}
#[cfg(feature = "alloc")]
impl<P> EncodePrivateKey for SigningKey<P>
where
P: ParameterSet,
{
fn to_pkcs8_der(&self) -> pkcs8::Result<der::SecretDocument> {
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<P: ParameterSet> SignatureAlgorithmIdentifier for SigningKey<P> {
type Params = AnyRef<'static>;
const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
Signature::<P>::ALGORITHM_IDENTIFIER;
}
impl<M> SigningKeyLen for Sha2L1<U16, M> {
type SkLen = U<{ 4 * 16 }>;
}
+38
View File
@@ -7,8 +7,12 @@ use crate::Sha2L35;
use crate::Shake;
use ::signature::{Error, Verifier};
use hybrid_array::{Array, ArraySize};
use pkcs8::{der, spki};
use typenum::{Unsigned, U, U16, U24, U32};
#[cfg(feature = "alloc")]
use pkcs8::EncodePublicKey;
/// A trait specifying the length of a serialized verifying key for a given parameter set
pub trait VerifyingKeyLen {
/// The length of the serialized verifying key in bytes
@@ -150,6 +154,40 @@ impl<P: ParameterSet> Verifier<Signature<P>> for VerifyingKey<P> {
}
}
#[cfg(feature = "alloc")]
impl<P: ParameterSet> EncodePublicKey for VerifyingKey<P> {
fn to_public_key_der(&self) -> pkcs8::spki::Result<der::Document> {
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<P: ParameterSet> TryFrom<pkcs8::SubjectPublicKeyInfoRef<'_>> for VerifyingKey<P> {
type Error = spki::Error;
fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
spki.algorithm.assert_algorithm_oid(P::ALGORITHM_OID)?;
Ok(Self::try_from(
spki.subject_public_key
.as_bytes()
.ok_or_else(|| der::Tag::BitString.value_error())?,
)
.map_err(|_| pkcs8::Error::KeyMalformed)?)
}
}
impl<M> VerifyingKeyLen for Sha2L1<U16, M> {
type VkLen = U<32>;
}
+44
View File
@@ -0,0 +1,44 @@
#![cfg(feature = "alloc")]
use hex_literal::hex;
use pkcs8::{DecodePrivateKey, EncodePrivateKey, EncodePublicKey, LineEnding};
use slh_dsa::{Sha2_128s, SigningKey, VerifyingKey};
use std::ops::Deref;
// Serialization of the SLH-DSA keys is still a draft
// The vectors used here are taken from the draft-ietf-lamps-x509-slhdsa
// https://github.com/lamps-wg/x509-slhdsa/commit/128c68b6b141e109e3e0ec8f3f47c832a4baaa30
#[test]
fn pkcs8_output() {
let signing = SigningKey::<Sha2_128s>::try_from(&hex!("A2263BCA45860836523160049523D621677FAD90D51EB6067A327E0D1E64A5012B8109EC777CAA4E1F024CCFCF9497D99180509280F4256AF2B07AF80289B494")[..]).unwrap();
let out = signing.to_pkcs8_pem(LineEnding::LF).unwrap();
// https://github.com/lamps-wg/x509-slhdsa/blob/main/id-slh-dsa-sha2-128s.priv
assert_eq!(
out.deref(),
r#"-----BEGIN PRIVATE KEY-----
MFICAQAwCwYJYIZIAWUDBAMUBECiJjvKRYYINlIxYASVI9YhZ3+tkNUetgZ6Mn4N
HmSlASuBCex3fKpOHwJMz8+Ul9mRgFCSgPQlavKwevgCibSU
-----END PRIVATE KEY-----
"#
);
let parsed = SigningKey::<Sha2_128s>::from_pkcs8_pem(out.deref()).unwrap();
assert_eq!(parsed, signing);
let public: VerifyingKey<Sha2_128s> = parsed.as_ref().clone();
let out = public.to_public_key_pem(LineEnding::LF).unwrap();
// https://github.com/lamps-wg/x509-slhdsa/blob/main/id-slh-dsa-sha2-128s.pub
assert_eq!(
out.deref(),
r#"-----BEGIN PUBLIC KEY-----
MDAwCwYJYIZIAWUDBAMUAyEAK4EJ7Hd8qk4fAkzPz5SX2ZGAUJKA9CVq8rB6+AKJ
tJQ=
-----END PUBLIC KEY-----
"#
);
}