Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

slh-dsa: adds pkcs8 support #867

Merged
merged 1 commit into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion slh-dsa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -51,4 +54,4 @@ harness = false

[features]
alloc = []
default = ["alloc"]
default = ["alloc", "pkcs8/alloc"]
7 changes: 7 additions & 0 deletions slh-dsa/src/hashes/sha2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 changes: 7 additions & 0 deletions slh-dsa/src/hashes/shake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)]
Expand Down
3 changes: 3 additions & 0 deletions slh-dsa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
23 changes: 23 additions & 0 deletions slh-dsa/src/signature_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand Down Expand Up @@ -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()
Expand Down
51 changes: 51 additions & 0 deletions slh-dsa/src/signing_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>);
Expand Down Expand Up @@ -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 }>;
}
Expand Down
38 changes: 38 additions & 0 deletions slh-dsa/src/verifying_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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>;
}
Expand Down
44 changes: 44 additions & 0 deletions slh-dsa/tests/pkcs8.rs
Original file line number Diff line number Diff line change
@@ -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-----
"#
);
}
Loading