From 3cbf979cbd98b7e7857112ec74f8dd2ff9c5514b Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Fri, 11 Oct 2024 18:33:41 -0400 Subject: [PATCH 01/30] cms: ecc-kari support - add kari and utils modules --- cms/src/builder.rs | 4 ++++ cms/src/builder/kari.rs | 35 +++++++++++++++++++++++++++++++++++ cms/src/builder/utils.rs | 0 3 files changed, 39 insertions(+) create mode 100644 cms/src/builder/kari.rs create mode 100644 cms/src/builder/utils.rs diff --git a/cms/src/builder.rs b/cms/src/builder.rs index 89bbcdc63..7aceba2fc 100644 --- a/cms/src/builder.rs +++ b/cms/src/builder.rs @@ -47,6 +47,10 @@ use x509_cert::attr::{Attribute, AttributeValue, Attributes}; use x509_cert::builder::{self, AsyncBuilder, Builder}; use zeroize::Zeroize; +// Modules +mod kari; +mod utils; + /// Error type #[derive(Debug)] #[non_exhaustive] diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs new file mode 100644 index 000000000..82524c6c5 --- /dev/null +++ b/cms/src/builder/kari.rs @@ -0,0 +1,35 @@ +//! Key Agreement Recipient Info (Kari) Builder +//! +//! This module contains the building logic for Key Agreement Recipient Info. +//! It partially implements [RFC 5753]. +//! +//! [RFC 5753]: https://datatracker.ietf.org/doc/html/rfc5753 +//! +//! + +/// The `EccCmsSharedInfo` type is defined in [RFC 5753 Section 7.2]. +/// +/// ```text +/// EccCmsSharedInfo ::= SEQUENCE { +/// keyInfo AlgorithmIdentifier, +/// entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, +/// suppPubInfo [2] EXPLICIT OCTET STRING } +/// ``` +/// +/// [RFC 5753 Section 7.2]: https://www.rfc-editor.org/rfc/rfc5753#section-7.2 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct EccCmsSharedInfo { + /// Object identifier of the key-encryption algorithm + pub key_info: AlgorithmIdentifierOwned, + /// Additional keying material - optional + #[asn1( + context_specific = "0", + tag_mode = "EXPLICIT", + constructed = "true", + optional = "true" + )] + pub entity_u_info: Option, + /// Length of the generated KEK, in bits, represented as a 32-bit number + #[asn1(context_specific = "2", tag_mode = "EXPLICIT", constructed = "true")] + pub supp_pub_info: OctetString, +} diff --git a/cms/src/builder/utils.rs b/cms/src/builder/utils.rs new file mode 100644 index 000000000..e69de29bb From 55b259e8b38f4e4249424423a1a507e508488c0e Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Fri, 11 Oct 2024 18:39:01 -0400 Subject: [PATCH 02/30] cms: ecc-kari support - add EccCmsSharedInfo --- cms/src/builder/kari.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs index 82524c6c5..85f5a6fa6 100644 --- a/cms/src/builder/kari.rs +++ b/cms/src/builder/kari.rs @@ -7,6 +7,11 @@ //! //! +use super::AlgorithmIdentifierOwned; +use super::UserKeyingMaterial; + +use der::{asn1::OctetString, Sequence}; + /// The `EccCmsSharedInfo` type is defined in [RFC 5753 Section 7.2]. /// /// ```text From 1663f410ead9960f2546a6dd9c5a9ab8cfcf34b7 Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sat, 12 Oct 2024 10:20:02 -0400 Subject: [PATCH 03/30] cms: ecc-kari support - add KeyAgreementAlgorithm --- cms/src/builder/kari.rs | 45 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs index 85f5a6fa6..b15a6eb2e 100644 --- a/cms/src/builder/kari.rs +++ b/cms/src/builder/kari.rs @@ -7,9 +7,9 @@ //! //! -use super::AlgorithmIdentifierOwned; -use super::UserKeyingMaterial; +use super::{AlgorithmIdentifierOwned, UserKeyingMaterial}; +use const_oid::ObjectIdentifier; use der::{asn1::OctetString, Sequence}; /// The `EccCmsSharedInfo` type is defined in [RFC 5753 Section 7.2]. @@ -38,3 +38,44 @@ pub struct EccCmsSharedInfo { #[asn1(context_specific = "2", tag_mode = "EXPLICIT", constructed = "true")] pub supp_pub_info: OctetString, } + +/// Represents supported key agreement algorithm for ECC - as defined in [RFC 5753 Section 7.1.4]. +/// +/// As per [RFC 5753 Section 8], the following are supported: +/// - dhSinglePass-stdDH-sha224kdf-scheme +/// - dhSinglePass-stdDH-sha256kdf-scheme +/// - dhSinglePass-stdDH-sha384kdf-scheme +/// - dhSinglePass-stdDH-sha512kdf-scheme +/// +/// [RFC 5753 Section 7.1.4]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.1.4 +/// [RFC 5753 Section 8]: https://datatracker.ietf.org/doc/html/rfc5753#section-8 + +pub enum KeyAgreementAlgorithm { + /// dhSinglePass-stdDH-sha224kdf-scheme + SinglePassStdDhSha224Kdf, + /// dhSinglePass-stdDH-sha256kdf-scheme + SinglePassStdDhSha256Kdf, + /// dhSinglePass-stdDH-sha384kdf-scheme + SinglePassStdDhSha384Kdf, + /// dhSinglePass-stdDH-sh512df-scheme + SinglePassStdDhSha512Kdf, +} +impl KeyAgreementAlgorithm { + /// Return the OID of the algorithm. + pub fn oid(&self) -> ObjectIdentifier { + match self { + Self::SinglePassStdDhSha224Kdf => { + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_224_KDF_SCHEME + } + Self::SinglePassStdDhSha256Kdf => { + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_256_KDF_SCHEME + } + Self::SinglePassStdDhSha384Kdf => { + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_384_KDF_SCHEME + } + Self::SinglePassStdDhSha512Kdf => { + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_512_KDF_SCHEME + } + } + } +} From 73fb73108a98acd51e143a82649e49adc08d8a8b Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sat, 12 Oct 2024 10:36:51 -0400 Subject: [PATCH 04/30] cms: ecc-kari support - add RFC details for KeyAgreementAlgorithm --- cms/src/builder/kari.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs index b15a6eb2e..b9599dbfe 100644 --- a/cms/src/builder/kari.rs +++ b/cms/src/builder/kari.rs @@ -41,7 +41,24 @@ pub struct EccCmsSharedInfo { /// Represents supported key agreement algorithm for ECC - as defined in [RFC 5753 Section 7.1.4]. /// -/// As per [RFC 5753 Section 8], the following are supported: +/// As per [RFC 5753 Section 8]: +/// ```text +/// Implementations that support EnvelopedData with the ephemeral-static +/// ECDH standard primitive: +/// +/// - MUST support the dhSinglePass-stdDH-sha256kdf-scheme key +/// agreement algorithm, the id-aes128-wrap key wrap algorithm, and +/// the id-aes128-cbc content encryption algorithm; and +/// - MAY support the dhSinglePass-stdDH-sha1kdf-scheme, dhSinglePass- +/// stdDH-sha224kdf-scheme, dhSinglePass-stdDH-sha384kdf-scheme, and +/// dhSinglePass-stdDH-sha512kdf-scheme key agreement algorithms; +/// the id-alg-CMS3DESwrap, id-aes192-wrap, and id-aes256-wrap key +/// wrap algorithms; and the des-ede3-cbc, id-aes192-cbc, and id- +/// aes256-cbc content encryption algorithms; other algorithms MAY +/// also be supported. +/// ``` +/// +/// As such the following are currently supported: /// - dhSinglePass-stdDH-sha224kdf-scheme /// - dhSinglePass-stdDH-sha256kdf-scheme /// - dhSinglePass-stdDH-sha384kdf-scheme @@ -49,7 +66,6 @@ pub struct EccCmsSharedInfo { /// /// [RFC 5753 Section 7.1.4]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.1.4 /// [RFC 5753 Section 8]: https://datatracker.ietf.org/doc/html/rfc5753#section-8 - pub enum KeyAgreementAlgorithm { /// dhSinglePass-stdDH-sha224kdf-scheme SinglePassStdDhSha224Kdf, From 479deb5b2e36d8549f91db395ca1ef7bf3787901 Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sat, 12 Oct 2024 14:41:21 -0400 Subject: [PATCH 05/30] cms: ecc-kari support - add KeyWrapAlgorithm --- cms/src/builder/kari.rs | 117 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs index b9599dbfe..09a18d2eb 100644 --- a/cms/src/builder/kari.rs +++ b/cms/src/builder/kari.rs @@ -10,7 +10,7 @@ use super::{AlgorithmIdentifierOwned, UserKeyingMaterial}; use const_oid::ObjectIdentifier; -use der::{asn1::OctetString, Sequence}; +use der::{asn1::OctetString, Any, Sequence}; /// The `EccCmsSharedInfo` type is defined in [RFC 5753 Section 7.2]. /// @@ -95,3 +95,118 @@ impl KeyAgreementAlgorithm { } } } + +/// Represents supported key wrap algorithm for ECC - as defined in [RFC 5753 Section 7.1.5]. +/// +/// As per [RFC 5753 Section 8]: +/// ```text +/// Implementations that support EnvelopedData with the ephemeral-static +/// ECDH standard primitive: +/// +/// - MUST support the dhSinglePass-stdDH-sha256kdf-scheme key +/// agreement algorithm, the id-aes128-wrap key wrap algorithm, and +/// the id-aes128-cbc content encryption algorithm; and +/// - MAY support the dhSinglePass-stdDH-sha1kdf-scheme, dhSinglePass- +/// stdDH-sha224kdf-scheme, dhSinglePass-stdDH-sha384kdf-scheme, and +/// dhSinglePass-stdDH-sha512kdf-scheme key agreement algorithms; +/// the id-alg-CMS3DESwrap, id-aes192-wrap, and id-aes256-wrap key +/// wrap algorithms; and the des-ede3-cbc, id-aes192-cbc, and id- +/// aes256-cbc content encryption algorithms; other algorithms MAY +/// also be supported. +/// ``` +/// +/// As such the following algorithm are currently supported +/// - id-aes128-wrap +/// - id-aes192-wrap - (OPTIONAL) +/// - id-aes256-wrap - (OPTIONAL) +/// +/// [RFC 5753 Section 8]: https://datatracker.ietf.org/doc/html/rfc5753#section-8 +/// [RFC 5753 Section 7.1.5]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.1.5 +#[derive(Copy, Clone)] +pub enum KeyWrapAlgorithm { + /// id-aes128-wrap + Aes128, + /// id-aes192-wrap + Aes192, + /// id-aes256-wrap + Aes256, +} +impl KeyWrapAlgorithm { + /// Return the Object Identifier (OID) of the algorithm. + /// + /// OID are defined in [RFC 3565 Section 2.3.2] + /// + /// [RFC 3565 Section 2.3.2]: + /// ```text + /// NIST has assigned the following OIDs to define the AES key wrap + /// algorithm. + /// + /// id-aes128-wrap OBJECT IDENTIFIER ::= { aes 5 } + /// id-aes192-wrap OBJECT IDENTIFIER ::= { aes 25 } + /// id-aes256-wrap OBJECT IDENTIFIER ::= { aes 45 } + /// + /// In all cases the parameters field MUST be absent. + /// ``` + /// + /// [RFC 3565 Section 2.3.2]: https://datatracker.ietf.org/doc/html/rfc3565#section-2.3.2 + fn oid(&self) -> ObjectIdentifier { + match self { + Self::Aes128 => const_oid::db::rfc5911::ID_AES_128_WRAP, + Self::Aes192 => const_oid::db::rfc5911::ID_AES_192_WRAP, + Self::Aes256 => const_oid::db::rfc5911::ID_AES_256_WRAP, + } + } + + /// Return parameters of the algorithm to be used in the context of `AlgorithmIdentifierOwned`. + /// + /// It should be absent as defined in [RFC 3565 Section 2.3.2] and per usage in [RFC 5753 Section 7.2]. + /// + /// [RFC 3565 Section 2.3.2]: https://datatracker.ietf.org/doc/html/rfc3565#section-2.3.2 + /// [RFC 5753 Section 7.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.2 + fn parameters(&self) -> Option { + match self { + Self::Aes128 => None, + Self::Aes192 => None, + Self::Aes256 => None, + } + } + + /// Return key size of the algorithm in number of bits + pub fn key_size_in_bits(&self) -> u32 { + match self { + Self::Aes128 => 128, + Self::Aes192 => 192, + Self::Aes256 => 256, + } + } +} +impl From for AlgorithmIdentifierOwned { + /// Convert a `KeyWrapAlgorithm` to the corresponding `AlgorithmIdentifierOwned`. + /// + /// Conversion is done as defined in [RFC 3565 Section 2.3.2] and according to [RFC 5753 Section 7.2]: + /// + /// [RFC 3565 Section 2.3.2] + /// ```text + /// keyInfo contains the object identifier of the key-encryption + /// algorithm (used to wrap the CEK) and associated parameters. In + /// this specification, 3DES wrap has NULL parameters while the AES + /// wraps have absent parameters. + /// ``` + /// + /// [RFC 5753 Section 73.2] + /// ```text + /// keyInfo contains the object identifier of the key-encryption + /// algorithm (used to wrap the CEK) and associated parameters. In + /// this specification, 3DES wrap has NULL parameters while the AES + /// wraps have absent parameters. + /// ``` + /// + /// [RFC 3565 Section 2.3.2]: https://datatracker.ietf.org/doc/html/rfc3565#section-2.3.2 + /// [RFC 5753 Section 7.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.2 + fn from(kw_algo: KeyWrapAlgorithm) -> Self { + Self { + oid: kw_algo.oid(), + parameters: kw_algo.parameters(), + } + } +} From e94d0e4533deec5f393b6969c80d0c9f824d3a53 Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sat, 12 Oct 2024 14:42:34 -0400 Subject: [PATCH 06/30] cms: ecc-kari support - add elliptic-curve dependency --- cms/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cms/Cargo.toml b/cms/Cargo.toml index d1ea5ac5e..a0ebf1303 100644 --- a/cms/Cargo.toml +++ b/cms/Cargo.toml @@ -25,6 +25,7 @@ aes = { version = "=0.9.0-pre.2", optional = true } async-signature = { version = "=0.6.0-pre.4", features = ["digest", "rand_core"], optional = true } cbc = { version = "=0.2.0-pre.2", optional = true } cipher = { version = "=0.5.0-pre.7", features = ["alloc", "block-padding", "rand_core"], optional = true } +elliptic-curve = { version = "=0.14.0-rc.1", optional = true } rsa = { version = "=0.10.0-pre.3", optional = true } sha1 = { version = "=0.11.0-pre.4", optional = true } sha2 = { version = "=0.11.0-pre.4", optional = true } @@ -47,12 +48,14 @@ tokio = { version = "1.43.0", features = ["macros", "rt"] } x509-cert = { version = "=0.3.0-pre.0", features = ["pem"] } [features] +default = ["builder"] std = ["der/std", "spki/std"] builder = [ "dep:aes", "dep:async-signature", "dep:cbc", "dep:cipher", + "elliptic-curve/ecdh", "dep:rsa", "dep:sha1", "dep:sha2", From 147146ea2ad0870767378c5082c3382cb4fcf855 Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 13 Oct 2024 09:27:41 -0400 Subject: [PATCH 07/30] cms: ecc-kari support - add aes-kw dependency --- cms/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cms/Cargo.toml b/cms/Cargo.toml index a0ebf1303..125372872 100644 --- a/cms/Cargo.toml +++ b/cms/Cargo.toml @@ -22,6 +22,7 @@ x509-cert = { version = "=0.3.0-pre.0", default-features = false } # optional dependencies aes = { version = "=0.9.0-pre.2", optional = true } +aes-kw = { version ="0.2.1", optional = true } async-signature = { version = "=0.6.0-pre.4", features = ["digest", "rand_core"], optional = true } cbc = { version = "=0.2.0-pre.2", optional = true } cipher = { version = "=0.5.0-pre.7", features = ["alloc", "block-padding", "rand_core"], optional = true } @@ -52,6 +53,7 @@ default = ["builder"] std = ["der/std", "spki/std"] builder = [ "dep:aes", + "dep:aes-kw", "dep:async-signature", "dep:cbc", "dep:cipher", From 89c49b835630fb6379c3cb18fe8c5de172ce5b4e Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:07:46 -0400 Subject: [PATCH 08/30] cms: ecc-kari support - move KeyWrapAlgorithm to kw module --- cms/src/builder/kari.rs | 145 ++++++---------------------------------- 1 file changed, 19 insertions(+), 126 deletions(-) diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs index 09a18d2eb..2639a4d61 100644 --- a/cms/src/builder/kari.rs +++ b/cms/src/builder/kari.rs @@ -5,12 +5,10 @@ //! //! [RFC 5753]: https://datatracker.ietf.org/doc/html/rfc5753 //! -//! - -use super::{AlgorithmIdentifierOwned, UserKeyingMaterial}; +use super::{utils::HashDigest, AlgorithmIdentifierOwned, UserKeyingMaterial}; use const_oid::ObjectIdentifier; -use der::{asn1::OctetString, Any, Sequence}; +use der::{asn1::OctetString, Sequence}; /// The `EccCmsSharedInfo` type is defined in [RFC 5753 Section 7.2]. /// @@ -43,19 +41,19 @@ pub struct EccCmsSharedInfo { /// /// As per [RFC 5753 Section 8]: /// ```text -/// Implementations that support EnvelopedData with the ephemeral-static -/// ECDH standard primitive: +/// Implementations that support EnvelopedData with the ephemeral-static +/// ECDH standard primitive: /// -/// - MUST support the dhSinglePass-stdDH-sha256kdf-scheme key +/// - MUST support the dhSinglePass-stdDH-sha256kdf-scheme key /// agreement algorithm, the id-aes128-wrap key wrap algorithm, and /// the id-aes128-cbc content encryption algorithm; and /// - MAY support the dhSinglePass-stdDH-sha1kdf-scheme, dhSinglePass- -/// stdDH-sha224kdf-scheme, dhSinglePass-stdDH-sha384kdf-scheme, and -/// dhSinglePass-stdDH-sha512kdf-scheme key agreement algorithms; -/// the id-alg-CMS3DESwrap, id-aes192-wrap, and id-aes256-wrap key -/// wrap algorithms; and the des-ede3-cbc, id-aes192-cbc, and id- -/// aes256-cbc content encryption algorithms; other algorithms MAY -/// also be supported. +/// stdDH-sha224kdf-scheme, dhSinglePass-stdDH-sha384kdf-scheme, and +/// dhSinglePass-stdDH-sha512kdf-scheme key agreement algorithms; +/// the id-alg-CMS3DESwrap, id-aes192-wrap, and id-aes256-wrap key +/// wrap algorithms; and the des-ede3-cbc, id-aes192-cbc, and id- +/// aes256-cbc content encryption algorithms; other algorithms MAY +/// also be supported. /// ``` /// /// As such the following are currently supported: @@ -78,7 +76,7 @@ pub enum KeyAgreementAlgorithm { } impl KeyAgreementAlgorithm { /// Return the OID of the algorithm. - pub fn oid(&self) -> ObjectIdentifier { + fn oid(&self) -> ObjectIdentifier { match self { Self::SinglePassStdDhSha224Kdf => { const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_224_KDF_SCHEME @@ -95,118 +93,13 @@ impl KeyAgreementAlgorithm { } } } - -/// Represents supported key wrap algorithm for ECC - as defined in [RFC 5753 Section 7.1.5]. -/// -/// As per [RFC 5753 Section 8]: -/// ```text -/// Implementations that support EnvelopedData with the ephemeral-static -/// ECDH standard primitive: -/// -/// - MUST support the dhSinglePass-stdDH-sha256kdf-scheme key -/// agreement algorithm, the id-aes128-wrap key wrap algorithm, and -/// the id-aes128-cbc content encryption algorithm; and -/// - MAY support the dhSinglePass-stdDH-sha1kdf-scheme, dhSinglePass- -/// stdDH-sha224kdf-scheme, dhSinglePass-stdDH-sha384kdf-scheme, and -/// dhSinglePass-stdDH-sha512kdf-scheme key agreement algorithms; -/// the id-alg-CMS3DESwrap, id-aes192-wrap, and id-aes256-wrap key -/// wrap algorithms; and the des-ede3-cbc, id-aes192-cbc, and id- -/// aes256-cbc content encryption algorithms; other algorithms MAY -/// also be supported. -/// ``` -/// -/// As such the following algorithm are currently supported -/// - id-aes128-wrap -/// - id-aes192-wrap - (OPTIONAL) -/// - id-aes256-wrap - (OPTIONAL) -/// -/// [RFC 5753 Section 8]: https://datatracker.ietf.org/doc/html/rfc5753#section-8 -/// [RFC 5753 Section 7.1.5]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.1.5 -#[derive(Copy, Clone)] -pub enum KeyWrapAlgorithm { - /// id-aes128-wrap - Aes128, - /// id-aes192-wrap - Aes192, - /// id-aes256-wrap - Aes256, -} -impl KeyWrapAlgorithm { - /// Return the Object Identifier (OID) of the algorithm. - /// - /// OID are defined in [RFC 3565 Section 2.3.2] - /// - /// [RFC 3565 Section 2.3.2]: - /// ```text - /// NIST has assigned the following OIDs to define the AES key wrap - /// algorithm. - /// - /// id-aes128-wrap OBJECT IDENTIFIER ::= { aes 5 } - /// id-aes192-wrap OBJECT IDENTIFIER ::= { aes 25 } - /// id-aes256-wrap OBJECT IDENTIFIER ::= { aes 45 } - /// - /// In all cases the parameters field MUST be absent. - /// ``` - /// - /// [RFC 3565 Section 2.3.2]: https://datatracker.ietf.org/doc/html/rfc3565#section-2.3.2 - fn oid(&self) -> ObjectIdentifier { - match self { - Self::Aes128 => const_oid::db::rfc5911::ID_AES_128_WRAP, - Self::Aes192 => const_oid::db::rfc5911::ID_AES_192_WRAP, - Self::Aes256 => const_oid::db::rfc5911::ID_AES_256_WRAP, - } - } - - /// Return parameters of the algorithm to be used in the context of `AlgorithmIdentifierOwned`. - /// - /// It should be absent as defined in [RFC 3565 Section 2.3.2] and per usage in [RFC 5753 Section 7.2]. - /// - /// [RFC 3565 Section 2.3.2]: https://datatracker.ietf.org/doc/html/rfc3565#section-2.3.2 - /// [RFC 5753 Section 7.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.2 - fn parameters(&self) -> Option { - match self { - Self::Aes128 => None, - Self::Aes192 => None, - Self::Aes256 => None, - } - } - - /// Return key size of the algorithm in number of bits - pub fn key_size_in_bits(&self) -> u32 { - match self { - Self::Aes128 => 128, - Self::Aes192 => 192, - Self::Aes256 => 256, - } - } -} -impl From for AlgorithmIdentifierOwned { - /// Convert a `KeyWrapAlgorithm` to the corresponding `AlgorithmIdentifierOwned`. - /// - /// Conversion is done as defined in [RFC 3565 Section 2.3.2] and according to [RFC 5753 Section 7.2]: - /// - /// [RFC 3565 Section 2.3.2] - /// ```text - /// keyInfo contains the object identifier of the key-encryption - /// algorithm (used to wrap the CEK) and associated parameters. In - /// this specification, 3DES wrap has NULL parameters while the AES - /// wraps have absent parameters. - /// ``` - /// - /// [RFC 5753 Section 73.2] - /// ```text - /// keyInfo contains the object identifier of the key-encryption - /// algorithm (used to wrap the CEK) and associated parameters. In - /// this specification, 3DES wrap has NULL parameters while the AES - /// wraps have absent parameters. - /// ``` - /// - /// [RFC 3565 Section 2.3.2]: https://datatracker.ietf.org/doc/html/rfc3565#section-2.3.2 - /// [RFC 5753 Section 7.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.2 - fn from(kw_algo: KeyWrapAlgorithm) -> Self { - Self { - oid: kw_algo.oid(), - parameters: kw_algo.parameters(), +impl From<&KeyAgreementAlgorithm> for HashDigest { + fn from(ka_algo: &KeyAgreementAlgorithm) -> Self { + match ka_algo { + KeyAgreementAlgorithm::SinglePassStdDhSha224Kdf => Self::Sha224, + KeyAgreementAlgorithm::SinglePassStdDhSha256Kdf => Self::Sha256, + KeyAgreementAlgorithm::SinglePassStdDhSha384Kdf => Self::Sha384, + KeyAgreementAlgorithm::SinglePassStdDhSha512Kdf => Self::Sha512, } } } From d02147f3365bb769fbaf9d8244e0724e0cfb0355 Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:28:47 -0400 Subject: [PATCH 09/30] cms: ecc-kari support - add EcKeyEncryptionInfo --- Cargo.lock | 116 ++++++++++++++++++++++++++++++++++------ cms/src/builder/kari.rs | 34 +++++++++++- 2 files changed, 134 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2621cdbd..83ac7d920 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,7 +23,18 @@ version = "0.6.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5f451b77e2f92932dc411da6ef9f3d33efad68a6f14a7a83e559453458e85ac" dependencies = [ - "crypto-common", + "crypto-common 0.2.0-rc.1", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", ] [[package]] @@ -33,7 +44,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7856582c758ade85d71daf27ec6bcea6c1c73913692b07b8dffea2dc03531c9" dependencies = [ "cfg-if", - "cipher", + "cipher 0.5.0-pre.7", "cpufeatures", ] @@ -44,13 +55,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cce27af05d45b901bb28da33ff8b2b2b2044f595b24fc0f36d4882dae91d484" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.9.0-pre.2", + "cipher 0.5.0-pre.7", "ctr", "ghash", "subtle", ] +[[package]] +name = "aes-kw" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c" +dependencies = [ + "aes 0.8.4", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -221,7 +241,7 @@ version = "0.2.0-pre.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0729a0a8422deb6056b8fcd89c42b724fe27e69458fa006f00c63cbffffd91b" dependencies = [ - "cipher", + "cipher 0.5.0-pre.7", ] [[package]] @@ -257,14 +277,24 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.6", + "inout 0.1.3", +] + [[package]] name = "cipher" version = "0.5.0-pre.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b1425e6ce000f05a73096556cabcfb6a10a3ffe3bb4d75416ca8f00819c0b6a" dependencies = [ - "crypto-common", - "inout", + "crypto-common 0.2.0-rc.1", + "inout 0.2.0-rc.3", ] [[package]] @@ -308,13 +338,15 @@ dependencies = [ name = "cms" version = "0.3.0-pre.0" dependencies = [ - "aes", + "aes 0.9.0-pre.2", + "aes-kw", "async-signature", "cbc", - "cipher", + "cipher 0.5.0-pre.7", "const-oid", "der", "ecdsa", + "elliptic-curve", "getrandom 0.3.1", "hex-literal", "p256", @@ -416,6 +448,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "crypto-common" version = "0.2.0-rc.1" @@ -433,7 +475,7 @@ version = "0.10.0-pre.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77e1482d284b80d7fddb211666d513dc5e23b0cc3a03ad398ff70543827c789f" dependencies = [ - "cipher", + "cipher 0.5.0-pre.7", ] [[package]] @@ -487,7 +529,7 @@ version = "0.9.0-pre.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76239c731adb4b5204cfeec47bd06ec1071d9477a0d32bbb83dc7d8c599efe63" dependencies = [ - "cipher", + "cipher 0.5.0-pre.7", ] [[package]] @@ -498,7 +540,7 @@ checksum = "cf2e3d6615d99707295a9673e889bf363a04b2a466bd320c65a72536f7577379" dependencies = [ "block-buffer", "const-oid", - "crypto-common", + "crypto-common 0.2.0-rc.1", "subtle", ] @@ -533,6 +575,7 @@ dependencies = [ "digest", "ff", "group", + "hkdf", "hybrid-array", "pem-rfc7468", "pkcs8", @@ -629,6 +672,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -723,6 +776,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hkdf" +version = "0.13.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00176ff81091018d42ff82e8324f8e5adb0b7e0468d1358f653972562dbff031" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.13.0-pre.4" @@ -752,6 +814,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "inout" version = "0.2.0-rc.3" @@ -1000,7 +1071,7 @@ dependencies = [ name = "pkcs5" version = "0.8.0-rc.3" dependencies = [ - "aes", + "aes 0.9.0-pre.2", "aes-gcm", "cbc", "der", @@ -1336,7 +1407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1affa54a576c40080654b494bb3f3198fa2fe46e0954b85196d122e3561c2fd0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.5.0-pre.7", ] [[package]] @@ -1721,11 +1792,26 @@ version = "0.6.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3517d72c5ca6d60f9f2e85d2c772e2652830062a685105a528d19dd823cf87d5" dependencies = [ - "crypto-common", + "crypto-common 0.2.0-rc.1", "subtle", ] [[package]] +<<<<<<< HEAD +======= +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +>>>>>>> 177acc5 (cms: ecc-kari support - add EcKeyEncryptionInfo) name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs index 2639a4d61..157786670 100644 --- a/cms/src/builder/kari.rs +++ b/cms/src/builder/kari.rs @@ -8,7 +8,8 @@ use super::{utils::HashDigest, AlgorithmIdentifierOwned, UserKeyingMaterial}; use const_oid::ObjectIdentifier; -use der::{asn1::OctetString, Sequence}; +use der::{asn1::OctetString, Any, Sequence}; +use elliptic_curve::{CurveArithmetic, PublicKey}; /// The `EccCmsSharedInfo` type is defined in [RFC 5753 Section 7.2]. /// @@ -103,3 +104,34 @@ impl From<&KeyAgreementAlgorithm> for HashDigest { } } } + +/// Contains information required to encrypt the content encryption key with a method based on ECC key agreement +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum EcKeyEncryptionInfo +where + C: CurveArithmetic, +{ + /// Encrypt key with EC + Ec(PublicKey), +} +impl EcKeyEncryptionInfo +where + C: CurveArithmetic + const_oid::AssociatedOid, +{ + /// Returns the OID associated with the curve used in this `EcKeyEncryptionInfo`. + pub fn get_oid(&self) -> ObjectIdentifier { + C::OID + } +} +impl From<&EcKeyEncryptionInfo> for AlgorithmIdentifierOwned +where + C: CurveArithmetic + const_oid::AssociatedOid, +{ + fn from(ec_key_encryption_info: &EcKeyEncryptionInfo) -> Self { + let parameters = Some(Any::from(&ec_key_encryption_info.get_oid())); + AlgorithmIdentifierOwned { + oid: elliptic_curve::ALGORITHM_OID, // id-ecPublicKey + parameters, // Curve OID + } + } +} From 82b24eb9f8ff58af4600ae7adf95c3d322b762f5 Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:47:20 -0400 Subject: [PATCH 10/30] cms: ecc-kari support - add kdf dependency --- cms/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cms/Cargo.toml b/cms/Cargo.toml index 125372872..30e2f26db 100644 --- a/cms/Cargo.toml +++ b/cms/Cargo.toml @@ -23,6 +23,7 @@ x509-cert = { version = "=0.3.0-pre.0", default-features = false } # optional dependencies aes = { version = "=0.9.0-pre.2", optional = true } aes-kw = { version ="0.2.1", optional = true } +ansi-x963-kdf = { git = "https://github.com/RustCrypto/KDFs.git", version = "0.1.0", optional = true } async-signature = { version = "=0.6.0-pre.4", features = ["digest", "rand_core"], optional = true } cbc = { version = "=0.2.0-pre.2", optional = true } cipher = { version = "=0.5.0-pre.7", features = ["alloc", "block-padding", "rand_core"], optional = true } @@ -54,6 +55,7 @@ std = ["der/std", "spki/std"] builder = [ "dep:aes", "dep:aes-kw", + "dep:ansi-x963-kdf", "dep:async-signature", "dep:cbc", "dep:cipher", From 06f4688e88d4ab15e017ef3bdc3cb8e79d674f38 Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sat, 19 Oct 2024 19:36:50 -0400 Subject: [PATCH 11/30] cms: ecc-kari support - move KeyAgreeRecipientInfoBuilder to sub-module --- Cargo.lock | 26 ++++++++++++++++--- cms/src/builder.rs | 62 +++------------------------------------------- 2 files changed, 27 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 83ac7d920..06599e644 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,29 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "ansi-x963-kdf" +version = "0.1.0" +source = "git+https://github.com/RustCrypto/KDFs.git#6f0b04d3fc1e6b7b7fa1ebca4d2be52d9e107dc9" +dependencies = [ + "digest", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.10" @@ -1797,8 +1820,6 @@ dependencies = [ ] [[package]] -<<<<<<< HEAD -======= name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1811,7 +1832,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] ->>>>>>> 177acc5 (cms: ecc-kari support - add EcKeyEncryptionInfo) name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/cms/src/builder.rs b/cms/src/builder.rs index 7aceba2fc..53ef06edb 100644 --- a/cms/src/builder.rs +++ b/cms/src/builder.rs @@ -51,6 +51,10 @@ use zeroize::Zeroize; mod kari; mod utils; +// Exports +pub use kari::{EcKeyEncryptionInfo, KeyAgreeRecipientInfoBuilder, KeyAgreementAlgorithm}; +pub use utils::kw::KeyWrapAlgorithm; + /// Error type #[derive(Debug)] #[non_exhaustive] @@ -699,64 +703,6 @@ where } } -/// Builds a `KeyAgreeRecipientInfo` according to RFC 5652 § 6. -/// This type uses key agreement: the recipient's public key and the sender's -/// private key are used to generate a pairwise symmetric key, then -/// the content-encryption key is encrypted in the pairwise symmetric key. -pub struct KeyAgreeRecipientInfoBuilder { - /// A CHOICE with three alternatives specifying the sender's key agreement public key. - pub originator: OriginatorIdentifierOrKey, - /// Optional information which helps generating different keys every time. - pub ukm: Option, - /// Encryption algorithm to be used for key encryption - pub key_enc_alg: AlgorithmIdentifierOwned, - _rng: PhantomData, -} - -impl KeyAgreeRecipientInfoBuilder { - /// Creates a `KeyAgreeRecipientInfoBuilder` - pub fn new( - originator: OriginatorIdentifierOrKey, - ukm: Option, - key_enc_alg: AlgorithmIdentifierOwned, - ) -> Result { - Ok(KeyAgreeRecipientInfoBuilder { - originator, - ukm, - key_enc_alg, - _rng: PhantomData, - }) - } -} - -impl RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder -where - R: CryptoRngCore, -{ - type Rng = R; - - /// Returns the RecipientInfoType - fn recipient_info_type(&self) -> RecipientInfoType { - RecipientInfoType::Kari - } - - /// Returns the `CMSVersion` for this `RecipientInfo` - fn recipient_info_version(&self) -> CmsVersion { - CmsVersion::V3 - } - - /// Build a `KeyAgreeRecipientInfoBuilder`. See RFC 5652 § 6.2.1 - fn build_with_rng( - &mut self, - _content_encryption_key: &[u8], - _rng: &mut Self::Rng, - ) -> Result { - Err(Error::Builder(String::from( - "Building KeyAgreeRecipientInfo is not implemented, yet.", - ))) - } -} - /// Builds a `KekRecipientInfo` according to RFC 5652 § 6. /// Uses symmetric key-encryption keys: the content-encryption key is /// encrypted in a previously distributed symmetric key-encryption key. From 925c66fb2e888b783cac60928b1eed9cc97f8e24 Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sat, 19 Oct 2024 19:37:28 -0400 Subject: [PATCH 12/30] cms: ecc-kari support - add elliptic-curve/pkcs8 --- cms/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/cms/Cargo.toml b/cms/Cargo.toml index 30e2f26db..1f5fa6882 100644 --- a/cms/Cargo.toml +++ b/cms/Cargo.toml @@ -60,6 +60,7 @@ builder = [ "dep:cbc", "dep:cipher", "elliptic-curve/ecdh", + "elliptic-curve/pkcs8", "dep:rsa", "dep:sha1", "dep:sha2", From 9941ecf937d6662ec491be7a490e097335b3206f Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 20 Oct 2024 09:09:21 -0400 Subject: [PATCH 13/30] cms: ecc-kari support - add KDF utilities --- Cargo.lock | 1 + cms/src/builder/utils/kdf.rs | 175 +++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 cms/src/builder/utils/kdf.rs diff --git a/Cargo.lock b/Cargo.lock index 06599e644..34fef790e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,6 +363,7 @@ version = "0.3.0-pre.0" dependencies = [ "aes 0.9.0-pre.2", "aes-kw", + "ansi-x963-kdf", "async-signature", "cbc", "cipher 0.5.0-pre.7", diff --git a/cms/src/builder/utils/kdf.rs b/cms/src/builder/utils/kdf.rs new file mode 100644 index 000000000..a99b2399c --- /dev/null +++ b/cms/src/builder/utils/kdf.rs @@ -0,0 +1,175 @@ +//! KDF module +//! +//! This module contains KDF logic. +//! + +use crate::builder::{Error, Result}; +use alloc::string::String; + +#[derive(PartialEq, Eq, Debug)] +pub(in crate::builder) enum HashDigest { + Sha224, + Sha256, + Sha384, + Sha512, +} + +/// Wrapper for ANSI-X9.63 KDF +/// +/// This function wraps ansi_x963_kdf, applying a Hash Disgest based on the key agreement algorithm identifier +pub(in crate::builder) fn try_ansi_x963_kdf( + secret: &[u8], + other_info: &[u8], + key: &mut impl AsMut<[u8]>, + digest: &HashDigest, +) -> Result<()> { + match digest { + HashDigest::Sha224 => ansi_x963_kdf_sha224(secret, other_info, key).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf SHA-224", + )) + }), + HashDigest::Sha256 => ansi_x963_kdf_sha256(secret, other_info, key).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf SHA-256", + )) + }), + HashDigest::Sha384 => ansi_x963_kdf_sha384(secret, other_info, key).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf SHA-384", + )) + }), + HashDigest::Sha512 => ansi_x963_kdf_sha512(secret, other_info, key).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf SHA-512", + )) + }), + } +} + +/// ANSI-X9.63-KDF with SHA224 +fn ansi_x963_kdf_sha224( + secret: &[u8], + other_info: &[u8], + key: &mut impl AsMut<[u8]>, +) -> Result<()> { + ansi_x963_kdf::derive_key_into::(secret, other_info, key.as_mut()).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf", + )) + }) +} + +/// ANSI-X9.63-KDF with SHA256 +fn ansi_x963_kdf_sha256( + secret: &[u8], + other_info: &[u8], + key: &mut impl AsMut<[u8]>, +) -> Result<()> { + ansi_x963_kdf::derive_key_into::(secret, other_info, key.as_mut()).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf", + )) + }) +} + +/// ANSI-X9.63-KDF with SHA384 +fn ansi_x963_kdf_sha384( + secret: &[u8], + other_info: &[u8], + key: &mut impl AsMut<[u8]>, +) -> Result<()> { + ansi_x963_kdf::derive_key_into::(secret, other_info, key.as_mut()).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf", + )) + }) +} + +/// ANSI-X9.63-KDF with SHA512 +fn ansi_x963_kdf_sha512( + secret: &[u8], + other_info: &[u8], + key: &mut impl AsMut<[u8]>, +) -> Result<()> { + ansi_x963_kdf::derive_key_into::(secret, other_info, key.as_mut()).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf", + )) + }) +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_try_ansi_x963_kdf() { + let secret = [0u8; 16]; + let other_info = []; + let mut key = [0u8; 16]; + assert!(try_ansi_x963_kdf(&secret, &other_info, &mut key, &HashDigest::Sha224).is_ok()); + assert_eq!( + key, + [33, 35, 2, 122, 169, 122, 164, 137, 12, 5, 195, 31, 101, 142, 44, 237] + ) + } + + #[test] + fn test_try_ansi_x963_kdf_error() { + // Empty secret should trigger error of ansi_x963_kdf + let secret = []; + let other_info = []; + let mut key = [0u8; 16]; + assert!(try_ansi_x963_kdf(&secret, &other_info, &mut key, &HashDigest::Sha224).is_err()); + } + + #[test] + fn test_try_ansi_x963_kdf_sha224() { + let secret = [0u8; 16]; + let other_info = []; + let mut key = [0u8; 16]; + assert!(ansi_x963_kdf_sha224(&secret, &other_info, &mut key).is_ok()); + assert_eq!( + key, + [33, 35, 2, 122, 169, 122, 164, 137, 12, 5, 195, 31, 101, 142, 44, 237] + ) + } + + #[test] + fn test_try_ansi_x963_kdf_sha256() { + let secret = [0u8; 16]; + let other_info = []; + let mut key = [0u8; 16]; + assert!(ansi_x963_kdf_sha256(&secret, &other_info, &mut key).is_ok()); + assert_eq!( + key, + [233, 255, 14, 110, 109, 233, 93, 165, 111, 240, 159, 78, 62, 15, 72, 29] + ) + } + + #[test] + fn test_try_ansi_x963_kdf_sha384() { + let secret = [0u8; 16]; + let other_info = []; + let mut key = [0u8; 16]; + assert!(ansi_x963_kdf_sha384(&secret, &other_info, &mut key).is_ok()); + assert_eq!( + key, + [156, 231, 52, 7, 234, 137, 225, 91, 29, 49, 193, 212, 25, 40, 137, 8] + ) + } + + #[test] + fn test_try_ansi_x963_kdf_sha512() { + let secret = [0u8; 16]; + let other_info = []; + let mut key = [0u8; 16]; + assert!(ansi_x963_kdf_sha512(&secret, &other_info, &mut key).is_ok()); + assert_eq!( + key, + [160, 237, 224, 79, 173, 198, 48, 115, 203, 162, 233, 108, 204, 185, 88, 209] + ) + } +} From f69a16c89229704ca22c0f8204a3113a78ec9e4e Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 20 Oct 2024 09:09:42 -0400 Subject: [PATCH 14/30] cms: ecc-kari support - add key wrap utilities --- cms/src/builder/utils/kw.rs | 531 ++++++++++++++++++++++++++++++++++++ 1 file changed, 531 insertions(+) create mode 100644 cms/src/builder/utils/kw.rs diff --git a/cms/src/builder/utils/kw.rs b/cms/src/builder/utils/kw.rs new file mode 100644 index 000000000..db7b9e825 --- /dev/null +++ b/cms/src/builder/utils/kw.rs @@ -0,0 +1,531 @@ +//! Key wrap module +//! +//! This module contains the key wrapping logic based on aes-kw algorithms +//! + +use alloc::{string::String, vec::Vec}; + +use crate::builder::{Error, Result}; +use aes_kw::Kek; +use const_oid::ObjectIdentifier; +use der::Any; +use spki::AlgorithmIdentifierOwned; + +/// Represents supported key wrap algorithm for ECC - as defined in [RFC 5753 Section 7.1.5]. +/// +/// As per [RFC 5753 Section 8]: +/// ```text +/// Implementations that support EnvelopedData with the ephemeral-static +/// ECDH standard primitive: +/// +/// - MUST support the dhSinglePass-stdDH-sha256kdf-scheme key +/// agreement algorithm, the id-aes128-wrap key wrap algorithm, and +/// the id-aes128-cbc content encryption algorithm; and +/// - MAY support the dhSinglePass-stdDH-sha1kdf-scheme, dhSinglePass- +/// stdDH-sha224kdf-scheme, dhSinglePass-stdDH-sha384kdf-scheme, and +/// dhSinglePass-stdDH-sha512kdf-scheme key agreement algorithms; +/// the id-alg-CMS3DESwrap, id-aes192-wrap, and id-aes256-wrap key +/// wrap algorithms; and the des-ede3-cbc, id-aes192-cbc, and id- +/// aes256-cbc content encryption algorithms; other algorithms MAY +/// also be supported. +/// ``` +/// +/// As such the following algorithm are currently supported +/// - id-aes128-wrap +/// - id-aes192-wrap +/// - id-aes256-wrap +/// +/// [RFC 5753 Section 8]: https://datatracker.ietf.org/doc/html/rfc5753#section-8 +/// [RFC 5753 Section 7.1.5]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.1.5 +#[derive(Copy, Clone)] +pub enum KeyWrapAlgorithm { + /// id-aes128-wrap + Aes128, + /// id-aes192-wrap + Aes192, + /// id-aes256-wrap + Aes256, +} +impl KeyWrapAlgorithm { + /// Return the Object Identifier (OID) of the algorithm. + /// + /// OID are defined in [RFC 3565 Section 2.3.2] + /// + /// [RFC 3565 Section 2.3.2]: + /// ```text + /// NIST has assigned the following OIDs to define the AES key wrap + /// algorithm. + /// + /// id-aes128-wrap OBJECT IDENTIFIER ::= { aes 5 } + /// id-aes192-wrap OBJECT IDENTIFIER ::= { aes 25 } + /// id-aes256-wrap OBJECT IDENTIFIER ::= { aes 45 } + /// + /// In all cases the parameters field MUST be absent. + /// ``` + /// + /// [RFC 3565 Section 2.3.2]: https://datatracker.ietf.org/doc/html/rfc3565#section-2.3.2 + fn oid(&self) -> ObjectIdentifier { + match self { + Self::Aes128 => const_oid::db::rfc5911::ID_AES_128_WRAP, + Self::Aes192 => const_oid::db::rfc5911::ID_AES_192_WRAP, + Self::Aes256 => const_oid::db::rfc5911::ID_AES_256_WRAP, + } + } + + /// Return parameters of the algorithm to be used in the context of `AlgorithmIdentifierOwned`. + /// + /// It should be absent as defined in [RFC 3565 Section 2.3.2] and per usage in [RFC 5753 Section 7.2]. + /// + /// [RFC 3565 Section 2.3.2]: + /// ```text + /// NIST has assigned the following OIDs to define the AES key wrap + /// algorithm. + /// + /// id-aes128-wrap OBJECT IDENTIFIER ::= { aes 5 } + /// id-aes192-wrap OBJECT IDENTIFIER ::= { aes 25 } + /// id-aes256-wrap OBJECT IDENTIFIER ::= { aes 45 } + /// + /// In all cases the parameters field MUST be absent. + /// ``` + /// + /// [RFC 3565 Section 2.3.2]: https://datatracker.ietf.org/doc/html/rfc3565#section-2.3.2 + /// [RFC 5753 Section 7.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.2 + fn parameters(&self) -> Option { + match self { + Self::Aes128 => None, + Self::Aes192 => None, + Self::Aes256 => None, + } + } + + /// Return key size of the algorithm in number of bits + pub fn key_size_in_bits(&self) -> u32 { + match self { + Self::Aes128 => 128, + Self::Aes192 => 192, + Self::Aes256 => 256, + } + } +} +impl From for AlgorithmIdentifierOwned { + /// Convert a `KeyWrapAlgorithm` to the corresponding `AlgorithmIdentifierOwned`. + /// + /// Conversion is done according to [RFC 5753 Section 7.2]: + /// + /// + /// [RFC 5753 Section 7.2] + /// ```text + /// keyInfo contains the object identifier of the key-encryption + /// algorithm (used to wrap the CEK) and associated parameters. In + /// this specification, 3DES wrap has NULL parameters while the AES + /// wraps have absent parameters. + /// ``` + /// + /// [RFC 5753 Section 7.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.2 + fn from(kw_algo: KeyWrapAlgorithm) -> Self { + Self { + oid: kw_algo.oid(), + parameters: kw_algo.parameters(), + } + } +} + +/// This struct can be used to perform key wrapping operation. +/// +/// It abstracts some of the key-wrapping logic over incoming wrapping-key and outgoing wrapped-key of different sizes. +/// It currently implements: +/// - try_new() - initialize a key wrapper with right sized depending on KeyWrapAlgorithm and key-to-wrap size +/// - try_wrap() - wrap a key with the corresponding aes-key-wrap algorithms +/// +/// # Note +/// For convenience KeyWrapper can: +/// - yield the inner wrapping-key as a mutable reference (e.g. to use with a KDF) +/// - convert to Vec to obtain Owned data to the wrapped key +#[derive(Debug, Clone, Copy)] +pub(in crate::builder) struct KeyWrapper { + /// Wrapping key + wrapping_key: WrappingKey, + /// Wrapped key + wrapped_key: WrappedKey, +} +impl KeyWrapper { + /// Initialize a new KeyWrapper based on `KeyWrapAlgorithm` and key-to-wrap size. + pub(in crate::builder) fn try_new(kw_algo: &KeyWrapAlgorithm, key_size: usize) -> Result { + let wrapped_key = WrappedKey::try_from(key_size)?; + let wrapping_key = WrappingKey::from(kw_algo); + + Ok(Self { + wrapping_key, + wrapped_key, + }) + } + /// Wraps a given key. + /// + /// This function attempts to wrap the provided `target_key`. + /// + /// # Arguments + /// * `target_key` - A slice of bytes representing the key to be wrapped. + pub(in crate::builder) fn try_wrap(&mut self, target_key: &[u8]) -> Result<()> { + match self.wrapping_key { + WrappingKey::Aes128(wrap_key) => Kek::from(wrap_key) + .wrap(target_key, self.wrapped_key.as_mut()) + .map_err(|_| { + Error::Builder(String::from( + "could not wrap key with Aes128 key wrap algorithm", + )) + }), + WrappingKey::Aes192(kek) => Kek::from(kek) + .wrap(target_key, self.wrapped_key.as_mut()) + .map_err(|_| { + Error::Builder(String::from( + "could not wrap key with Aes192 key wrap algorithm", + )) + }), + WrappingKey::Aes256(kek) => Kek::from(kek) + .wrap(target_key, self.wrapped_key.as_mut()) + .map_err(|_| { + Error::Builder(String::from( + "could not wrap key with Aes256 key wrap algorithm", + )) + }), + } + } +} +impl AsMut<[u8]> for KeyWrapper { + fn as_mut(&mut self) -> &mut [u8] { + self.wrapping_key.as_mut() + } +} +impl From for Vec { + fn from(wrapper: KeyWrapper) -> Self { + Self::from(wrapper.wrapped_key) + } +} + +/// Represents a wrapping key to be used by [KeyWrapper] +/// +/// This type can be used to abstract over wrapping-key material of different size. +/// The following wrapping key type are currently supported: +/// - Aes128 +/// - Aes192 +/// - Aes256 +/// +#[derive(Debug, Clone, Copy)] +enum WrappingKey { + /// id-aes128-wrap + Aes128([u8; 16]), + /// id-aes192-wrap + Aes192([u8; 24]), + /// id-aes256-wrap + Aes256([u8; 32]), +} +impl From<&KeyWrapAlgorithm> for WrappingKey { + fn from(kw_algo: &KeyWrapAlgorithm) -> Self { + match kw_algo { + KeyWrapAlgorithm::Aes128 => Self::Aes128([0u8; 16]), + KeyWrapAlgorithm::Aes192 => Self::Aes192([0u8; 24]), + KeyWrapAlgorithm::Aes256 => Self::Aes256([0u8; 32]), + } + } +} +impl AsMut<[u8]> for WrappingKey { + fn as_mut(&mut self) -> &mut [u8] { + match self { + Self::Aes128(key) => key, + Self::Aes192(key) => key, + Self::Aes256(key) => key, + } + } +} +/// Represents a wrapped key to be used by [KeyWrapper] +/// +/// This type can be used to abstract over wrapped key of different size for aes-key-wrap algorithms. +/// It currently supports the following incoming key size: +/// - 16 +/// - 24 +/// - 32 +#[derive(Debug, Clone, Copy)] +enum WrappedKey { + Aes128([u8; 24]), + Aes192([u8; 32]), + Aes256([u8; 40]), +} +impl TryFrom for WrappedKey { + type Error = Error; + fn try_from(key_size: usize) -> Result { + match key_size { + 16 => Ok(Self::Aes128([0u8; 24])), + 24 => Ok(Self::Aes192([0u8; 32])), + 32 => Ok(Self::Aes256([0u8; 40])), + _ => Err(Error::Builder(String::from( + "could not wrap key: key size is not supported", + ))), + } + } +} +impl AsMut<[u8]> for WrappedKey { + fn as_mut(&mut self) -> &mut [u8] { + match self { + Self::Aes128(key) => key, + Self::Aes192(key) => key, + Self::Aes256(key) => key, + } + } +} +impl From for Vec { + fn from(key: WrappedKey) -> Self { + match key { + WrappedKey::Aes128(arr) => arr.to_vec(), + WrappedKey::Aes192(arr) => arr.to_vec(), + WrappedKey::Aes256(arr) => arr.to_vec(), + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use alloc::string::ToString; + + #[test] + fn test_keywrapalgorithm_oid() { + assert_eq!( + KeyWrapAlgorithm::Aes128.oid(), + const_oid::db::rfc5911::ID_AES_128_WRAP + ); + assert_eq!( + KeyWrapAlgorithm::Aes192.oid(), + const_oid::db::rfc5911::ID_AES_192_WRAP + ); + assert_eq!( + KeyWrapAlgorithm::Aes256.oid(), + const_oid::db::rfc5911::ID_AES_256_WRAP + ); + } + + #[test] + fn test_keywrapalgorithm_parameters() { + assert_eq!(KeyWrapAlgorithm::Aes128.parameters(), None); + assert_eq!(KeyWrapAlgorithm::Aes192.parameters(), None); + assert_eq!(KeyWrapAlgorithm::Aes256.parameters(), None); + } + + #[test] + fn test_keywrapalgorithm_key_size_in_bits() { + assert_eq!(KeyWrapAlgorithm::Aes128.key_size_in_bits(), 128); + assert_eq!(KeyWrapAlgorithm::Aes192.key_size_in_bits(), 192); + assert_eq!(KeyWrapAlgorithm::Aes256.key_size_in_bits(), 256); + } + + #[test] + fn test_wrappingkey_from_keywrapalgorithm() {} + + #[test] + fn test_algorithmidentifierowned_from_keywrapalgorithm() { + assert_eq!( + AlgorithmIdentifierOwned::from(KeyWrapAlgorithm::Aes128), + AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5911::ID_AES_128_WRAP, + parameters: None, + } + ); + assert_eq!( + AlgorithmIdentifierOwned::from(KeyWrapAlgorithm::Aes192), + AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5911::ID_AES_192_WRAP, + parameters: None, + } + ); + assert_eq!( + AlgorithmIdentifierOwned::from(KeyWrapAlgorithm::Aes256), + AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5911::ID_AES_256_WRAP, + parameters: None, + } + ) + } + + #[test] + fn test_keywrapper_try_new() { + assert!(KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 10).is_err()); + assert!(KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 16).is_ok()); + assert!(KeyWrapper::try_new(&KeyWrapAlgorithm::Aes192, 24).is_ok()); + assert!(KeyWrapper::try_new(&KeyWrapAlgorithm::Aes256, 32).is_ok()); + } + + fn fake_kdf(_in: &[u8], _out: &mut impl AsMut<[u8]>) {} + + #[test] + fn test_keywrapper_try_wrap() { + // Key to wrap + let key_to_wrap = [0u8; 16]; + + // Shared secret + let shared_secret = [1u8; 16]; + + // Define a key wrapper from Aes128-key-wrap and key-to-wrap size + let mut key_wrapper = + KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, key_to_wrap.len()).unwrap(); + + // Derive shared key - one can call .as_mut() on key_wrapper + fake_kdf(&shared_secret, &mut key_wrapper); + + // Wrap the key + let r: core::result::Result<(), Error> = key_wrapper.try_wrap(&key_to_wrap); + + assert!(r.is_ok()); + let wrapped_key = Vec::from(key_wrapper); + assert_eq!( + wrapped_key, + alloc::vec![ + 191, 59, 119, 181, 233, 12, 170, 159, 80, 9, 254, 150, 38, 228, 239, 226, 13, 237, + 117, 238, 59, 26, 192, 213 + ] + ) + } + + #[test] + fn test_keywrapper_try_wrap_error() { + // Key to wrap + let key_to_wrap = [0u8; 16]; + + // Define a key wrapper with unsupported key size + assert_eq!( + KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 15) + .unwrap_err() + .to_string(), + "builder error: could not wrap key: key size is not supported" + ); + + // Define a key wrapper from Aes128-key-wrap but with a different size than key-to-wrap + let mut key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 24).unwrap(); + + // Wrap the key + assert_eq!( + key_wrapper.try_wrap(&key_to_wrap).unwrap_err().to_string(), + "builder error: could not wrap key with Aes128 key wrap algorithm" + ); + } + + #[test] + fn test_keywrapper_as_mut() { + let mut key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 16).unwrap(); + let slice_a128 = key_wrapper.as_mut(); + assert_eq!(slice_a128.len(), 16); + assert_eq!(slice_a128[0], 0); + } + + #[test] + fn test_vecu8_from_keywrapper() { + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 16).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 24); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes192, 16).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 24); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes256, 16).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 24); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes192, 24).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 32); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes192, 24).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 32); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes192, 24).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 32); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 32).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 40); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes192, 32).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 40); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes256, 32).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 40); + assert_eq!(vec[0], 0); + } + + #[test] + fn test_wrappingkey_as_mut() { + let mut key_a128 = WrappingKey::Aes128([0; 16]); + let slice_a128 = key_a128.as_mut(); + assert_eq!(slice_a128.len(), 16); + assert_eq!(slice_a128[0], 0); + + let mut key_a192 = WrappingKey::Aes192([0; 24]); + let slice_a192 = key_a192.as_mut(); + assert_eq!(slice_a192.len(), 24); + assert_eq!(slice_a192[0], 0); + + let mut key_a256 = WrappingKey::Aes256([0; 32]); + let slice_a256 = key_a256.as_mut(); + assert_eq!(slice_a256.len(), 32); + assert_eq!(slice_a256[0], 0); + } + + #[test] + fn test_wrappedkey_from_usize() { + let key_size: usize = 16; + assert!(WrappedKey::try_from(key_size).is_ok()); + + let key_size: usize = 24; + assert!(WrappedKey::try_from(key_size).is_ok()); + + let key_size: usize = 32; + assert!(WrappedKey::try_from(key_size).is_ok()); + + let key_size: usize = 0; + assert!(WrappedKey::try_from(key_size).is_err()); + } + #[test] + fn test_wrappedkey_as_mut() { + let mut key_a128 = WrappedKey::Aes128([0; 24]); + let slice_a128 = key_a128.as_mut(); + assert_eq!(slice_a128.len(), 24); + assert_eq!(slice_a128[0], 0); + + let mut key_a192 = WrappedKey::Aes192([0; 32]); + let slice_a192 = key_a192.as_mut(); + assert_eq!(slice_a192.len(), 32); + assert_eq!(slice_a192[0], 0); + + let mut key_a256 = WrappedKey::Aes256([0; 40]); + let slice_a256 = key_a256.as_mut(); + assert_eq!(slice_a256.len(), 40); + assert_eq!(slice_a256[0], 0); + } + + #[test] + fn test_vecu8_from_wrappedkey() { + let key_a128 = WrappedKey::Aes128([0; 24]); + let vec = Vec::from(key_a128); + assert_eq!(vec.len(), 24); + + let key_a192 = WrappedKey::Aes192([0; 32]); + let vec = Vec::from(key_a192); + assert_eq!(vec.len(), 32); + + let key_a256 = WrappedKey::Aes256([0; 40]); + let vec = Vec::from(key_a256); + assert_eq!(vec.len(), 40); + } +} From 95e2ae08a80b4d1af76f7335c35bae395193a302 Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 20 Oct 2024 09:10:50 -0400 Subject: [PATCH 15/30] cms: ecc-kari support - add p256-priv.der corresponding public key --- cms/tests/examples/p256-pub.der | Bin 0 -> 91 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 cms/tests/examples/p256-pub.der diff --git a/cms/tests/examples/p256-pub.der b/cms/tests/examples/p256-pub.der new file mode 100644 index 0000000000000000000000000000000000000000..67c719c7641d6bc2b1b122bcd287e2947daed3d4 GIT binary patch literal 91 zcmXqrG!SNE*J|@PXUoLM#sOw9GqN)~F|f$2`M)(@U+4Xext*`gz112;Re~CH-z}Ia u#@1+l!}5Ink;Wx1lMH<8zGWR0-}kS1#f5&+c Date: Sun, 20 Oct 2024 09:12:41 -0400 Subject: [PATCH 16/30] cms: ecc-kari support - add comments and exports --- cms/src/builder/utils.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cms/src/builder/utils.rs b/cms/src/builder/utils.rs index e69de29bb..140b2234a 100644 --- a/cms/src/builder/utils.rs +++ b/cms/src/builder/utils.rs @@ -0,0 +1,12 @@ +//! Utilities module +//! +//! Contains various utilities used during KARI building. +//! It currently contains: +//! - kw: AES Key Wrap +//! - kdf: KDF using ANSI-x9.63 Key Derivation Function + +mod kdf; +pub(super) mod kw; + +pub(super) use kdf::{try_ansi_x963_kdf, HashDigest}; +pub(super) use kw::KeyWrapper; From 02fec82759586f9def5363ca784313fcd61130c5 Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 20 Oct 2024 09:15:36 -0400 Subject: [PATCH 17/30] cms: ecc-kari support - add kari test module --- cms/tests/builder.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cms/tests/builder.rs b/cms/tests/builder.rs index ac3863e83..2f27b9afe 100644 --- a/cms/tests/builder.rs +++ b/cms/tests/builder.rs @@ -32,6 +32,10 @@ use spki::AlgorithmIdentifierOwned; use x509_cert::attr::{Attribute, AttributeValue}; use x509_cert::serial_number::SerialNumber; +// Modules +#[path = "builder/kari.rs"] +mod kari; + // TODO bk replace this by const_oid definitions as soon as released const RFC8894_ID_MESSAGE_TYPE: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.113733.1.9.2"); From a7f56f335dffb91549cf190b52b0f98732f73f8b Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 20 Oct 2024 09:16:59 -0400 Subject: [PATCH 18/30] cms: ecc-kari support - add test for kari builder --- cms/tests/builder/kari.rs | 87 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 cms/tests/builder/kari.rs diff --git a/cms/tests/builder/kari.rs b/cms/tests/builder/kari.rs new file mode 100644 index 000000000..503c4c914 --- /dev/null +++ b/cms/tests/builder/kari.rs @@ -0,0 +1,87 @@ +use cms::{ + builder::{ + ContentEncryptionAlgorithm, EcKeyEncryptionInfo, EnvelopedDataBuilder, + KeyAgreeRecipientInfoBuilder, KeyAgreementAlgorithm, KeyWrapAlgorithm, + }, + cert::IssuerAndSerialNumber, + content_info::ContentInfo, + enveloped_data::KeyAgreeRecipientIdentifier, +}; +use der::{Any, AnyRef, Encode}; +use p256::{pkcs8::DecodePrivateKey, SecretKey}; +use pem_rfc7468::LineEnding; +use rand::rngs::OsRng; +use x509_cert::serial_number::SerialNumber; + +fn key_agreement_recipient_identifier(id: i32) -> KeyAgreeRecipientIdentifier { + let issuer = format!("CN=test client {id}").parse().unwrap(); + KeyAgreeRecipientIdentifier::IssuerAndSerialNumber(IssuerAndSerialNumber { + issuer, + serial_number: SerialNumber::new(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06]) + .expect("failed to create a serial number"), + }) +} + +/// Generate a CMS message encrypted with recipient public EC key +/// +/// Can be decrypted using: +/// ```bash +/// openssl cms -decrypt -inkey cms/tests/examples/p256-priv.der -inform PEM +/// ``` +#[test] +fn test_build_enveloped_data_ec() { + // Recipient identifier + let key_agreement_recipient_identifier = key_agreement_recipient_identifier(1); + + // Recipient key material + let recipient_private_key_der = include_bytes!("../examples/p256-priv.der"); + let recipient_private_key = SecretKey::from_pkcs8_der(recipient_private_key_der) + .expect("could not parse in private key"); + let recipient_public_key = recipient_private_key.public_key(); + + // KARI builder + let mut rng = OsRng; + let kari_builder = KeyAgreeRecipientInfoBuilder::new( + None, + key_agreement_recipient_identifier, + EcKeyEncryptionInfo::Ec(recipient_public_key), + KeyAgreementAlgorithm::SinglePassStdDhSha256Kdf, + KeyWrapAlgorithm::Aes128, + &mut rng, + ) + .expect("Could not create a KeyAgreeRecipientInfoBuilder"); + + // Enveloped data builder + let mut rng = OsRng; + let mut builder = EnvelopedDataBuilder::new( + None, + "Arbitrary unencrypted content, encrypted using ECC".as_bytes(), + ContentEncryptionAlgorithm::Aes128Cbc, + None, + ) + .expect("Could not create an EnvelopedData builder."); + + // Enveloped data + let enveloped_data = builder + .add_recipient_info(kari_builder) + .expect("Could not add a recipient info") + .build_with_rng(&mut rng) + .expect("Building EnvelopedData failed"); + let enveloped_data_der = enveloped_data + .to_der() + .expect("conversion of enveloped data to DER failed."); + + // Content info + let content = AnyRef::try_from(enveloped_data_der.as_slice()).unwrap(); + let content_info = ContentInfo { + content_type: const_oid::db::rfc5911::ID_ENVELOPED_DATA, + content: Any::from(content), + }; + let content_info_der = content_info.to_der().unwrap(); + + println!( + "{}", + pem_rfc7468::encode_string("CMS", LineEnding::LF, &content_info_der) + .expect("PEM encoding of enveloped data DER failed") + ); +} From 907c5928f0552c639f4cc64f9791d3d1721e6dfc Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 20 Oct 2024 09:21:53 -0400 Subject: [PATCH 19/30] cms: ecc-kari support - Re-organize imports - Adjust comments - Add KeyAgreeRecipientInfoBuilder build logic - Add tests for KeyAgreementAlgorithm and EcKeyEncryptionInfo --- cms/src/builder/kari.rs | 321 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 314 insertions(+), 7 deletions(-) diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs index 157786670..3eee14d13 100644 --- a/cms/src/builder/kari.rs +++ b/cms/src/builder/kari.rs @@ -6,10 +6,39 @@ //! [RFC 5753]: https://datatracker.ietf.org/doc/html/rfc5753 //! -use super::{utils::HashDigest, AlgorithmIdentifierOwned, UserKeyingMaterial}; -use const_oid::ObjectIdentifier; -use der::{asn1::OctetString, Any, Sequence}; -use elliptic_curve::{CurveArithmetic, PublicKey}; +// Super imports +use super::{ + utils::{try_ansi_x963_kdf, HashDigest, KeyWrapper}, + AlgorithmIdentifierOwned, CryptoRngCore, KeyWrapAlgorithm, RecipientInfoBuilder, + RecipientInfoType, Result, UserKeyingMaterial, +}; + +// Crate imports +use crate::{ + content_info::CmsVersion, + enveloped_data::{ + EncryptedKey, KeyAgreeRecipientIdentifier, KeyAgreeRecipientInfo, + OriginatorIdentifierOrKey, OriginatorPublicKey, RecipientEncryptedKey, RecipientInfo, + }, +}; + +// Internal imports +use const_oid::{AssociatedOid, ObjectIdentifier}; +use der::{ + asn1::{BitString, OctetString}, + Any, Decode, Encode, Sequence, +}; + +// Alloc imports +use alloc::{vec, vec::Vec}; + +// RustCrypto imports +use elliptic_curve::{ + ecdh::EphemeralSecret, + point::PointCompression, + sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}, + AffinePoint, CurveArithmetic, FieldBytesSize, PublicKey, +}; /// The `EccCmsSharedInfo` type is defined in [RFC 5753 Section 7.2]. /// @@ -65,6 +94,8 @@ pub struct EccCmsSharedInfo { /// /// [RFC 5753 Section 7.1.4]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.1.4 /// [RFC 5753 Section 8]: https://datatracker.ietf.org/doc/html/rfc5753#section-8 +#[allow(clippy::enum_variant_names)] +#[derive(Clone, Copy)] pub enum KeyAgreementAlgorithm { /// dhSinglePass-stdDH-sha224kdf-scheme SinglePassStdDhSha224Kdf, @@ -116,16 +147,16 @@ where } impl EcKeyEncryptionInfo where - C: CurveArithmetic + const_oid::AssociatedOid, + C: CurveArithmetic + AssociatedOid, { - /// Returns the OID associated with the curve used in this `EcKeyEncryptionInfo`. + /// Returns the OID associated with the curve used. pub fn get_oid(&self) -> ObjectIdentifier { C::OID } } impl From<&EcKeyEncryptionInfo> for AlgorithmIdentifierOwned where - C: CurveArithmetic + const_oid::AssociatedOid, + C: CurveArithmetic + AssociatedOid, { fn from(ec_key_encryption_info: &EcKeyEncryptionInfo) -> Self { let parameters = Some(Any::from(&ec_key_encryption_info.get_oid())); @@ -135,3 +166,279 @@ where } } } + +/// Builds a `KeyAgreeRecipientInfo` according to RFC 5652 § 6. +/// This type uses key agreement: the recipient's public key and the sender's +/// private key are used to generate a pairwise symmetric key, then +/// the content-encryption key is encrypted in the pairwise symmetric key. +pub struct KeyAgreeRecipientInfoBuilder<'a, R, C> +where + R: CryptoRngCore, + C: CurveArithmetic, +{ + /// Optional information which helps generating different keys every time. + pub ukm: Option, + /// Encryption algorithm to be used for key encryption + pub rid: KeyAgreeRecipientIdentifier, + /// Recipient key info + pub eckey_encryption_info: EcKeyEncryptionInfo, + /// Content encryption algorithm + pub key_agreement_algorithm: KeyAgreementAlgorithm, + /// Content encryption algorithm + pub key_wrap_algorithm: KeyWrapAlgorithm, + /// Rng + rng: &'a mut R, +} + +impl<'a, R, C> KeyAgreeRecipientInfoBuilder<'a, R, C> +where + R: CryptoRngCore, + C: CurveArithmetic, +{ + /// Creates a `KeyAgreeRecipientInfoBuilder` + pub fn new( + ukm: Option, + rid: KeyAgreeRecipientIdentifier, + eckey_encryption_info: EcKeyEncryptionInfo, + key_agreement_algorithm: KeyAgreementAlgorithm, + key_wrap_algorithm: KeyWrapAlgorithm, + rng: &'a mut R, + ) -> Result> { + Ok(KeyAgreeRecipientInfoBuilder { + ukm, + eckey_encryption_info, + key_agreement_algorithm, + key_wrap_algorithm, + rid, + rng, + }) + } +} +impl<'a, R, C> RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder<'a, R, C> +where + R: CryptoRngCore, + C: CurveArithmetic + AssociatedOid + PointCompression, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + /// Returns the RecipientInfoType + fn recipient_info_type(&self) -> RecipientInfoType { + RecipientInfoType::Kari + } + + /// Returns the `CMSVersion` for this `RecipientInfo` + fn recipient_info_version(&self) -> CmsVersion { + CmsVersion::V3 + } + + /// Build a `KeyAgreeRecipientInfo` as per [RFC 5652 Section 6.2.2] and [RFC 5753 Section 3]. + /// + /// For now only [EnvelopedData] using `(ephemeral-static) ECDH` is supported - [RFC 5753 Section 3.1.1] + /// + /// We follow the flow outlined in - [RFC 5753 Section 3.1.2]: + /// + /// Todo: + /// - Add support for `'Co-factor' ECDH` - see [RFC 5753 Section 3.1.1] + /// - Add support for `1-Pass ECMQV` - see [RFC 5753 Section 3.2.1] + /// + /// [RFC 5753 Section 3]: https://datatracker.ietf.org/doc/html/rfc5753#section-3 + /// [RFC 5652 Section 6.2.2]: https://datatracker.ietf.org/doc/html/rfc5652#section-6.2.2 + /// [RFC 5753 Section 3.1.1]: https://datatracker.ietf.org/doc/html/rfc5753#section-3.1.1 + /// [RFC 5753 Section 3.1.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-3.1.2 + /// [RFC 5753 Section 3.2.1]: https://datatracker.ietf.org/doc/html/rfc5753#section-3.2.1 + fn build(&mut self, content_encryption_key: &[u8]) -> Result { + // Encrypt key + let ( + encrypted_key, + ephemeral_pubkey_encoded_point, + originator_algorithm_identifier, + key_encryption_algorithm_identifier, + ) = match self.eckey_encryption_info { + EcKeyEncryptionInfo::Ec(recipient_public_key) => { + // Generate ephemeral key using ecdh + let ephemeral_secret = EphemeralSecret::random(self.rng); + let ephemeral_public_key_encoded_point = + ephemeral_secret.public_key().to_encoded_point(false); + + // Compute a shared secret with recipient public key. Non-uniformly random, but will be used as input for KDF later. + let non_uniformly_random_shared_secret = + ephemeral_secret.diffie_hellman(&recipient_public_key); + let non_uniformly_random_shared_secret_bytes = + non_uniformly_random_shared_secret.raw_secret_bytes(); + + // Generate shared info for KDF + // As per https://datatracker.ietf.org/doc/html/rfc5753#section-7.2" + // ``` + // keyInfo contains the object identifier of the key-encryption + // algorithm (used to wrap the CEK) and associated parameters. In + // this specification, 3DES wrap has NULL parameters while the AES + // wraps have absent parameters. + // ``` + let key_wrap_algorithm_identifier: AlgorithmIdentifierOwned = + self.key_wrap_algorithm.into(); + let key_wrap_algorithm_der = key_wrap_algorithm_identifier.to_der()?; + + // As per https://datatracker.ietf.org/doc/html/rfc5753#section-7.2" + // ``` + // entityUInfo optionally contains additional keying material + // supplied by the sending agent. When used with ECDH and CMS, the + // entityUInfo field contains the octet string ukm. When used with + // ECMQV and CMS, the entityUInfo contains the octet string addedukm + // (encoded in MQVuserKeyingMaterial). + // ``` + let entity_u_info = self.ukm.clone(); + + // As per https://datatracker.ietf.org/doc/html/rfc5753#section-7.2" + // ``` + // suppPubInfo contains the length of the generated KEK, in bits, + // represented as a 32-bit number, as in [CMS-DH] and [CMS-AES]. + // (For example, for AES-256 it would be 00 00 01 00.) + // ``` + let key_wrap_algo_keysize_bits_in_be_bytes: [u8; 4] = + self.key_wrap_algorithm.key_size_in_bits().to_be_bytes(); + + let shared_info = EccCmsSharedInfo { + key_info: key_wrap_algorithm_identifier, + entity_u_info, + supp_pub_info: OctetString::new(key_wrap_algo_keysize_bits_in_be_bytes)?, + }; + let shared_info_der = shared_info.to_der()?; + + // Init a wrapping key (KEK) based on KeyWrapAlgorithm and on CEK (i.e. key to wrap) size + let mut key_wrapper = + KeyWrapper::try_new(&self.key_wrap_algorithm, content_encryption_key.len())?; + + // Derive the Key Encryption Key (KEK) from Shared Secret using ANSI X9.63 KDF + let digest = HashDigest::from(&self.key_agreement_algorithm); + try_ansi_x963_kdf( + non_uniformly_random_shared_secret_bytes.as_slice(), + &shared_info_der, + &mut key_wrapper, + &digest, + )?; + + // Wrap the Content Encryption Key (CEK) with the KEK + key_wrapper.try_wrap(content_encryption_key)?; + + // Return data + ( + Vec::from(key_wrapper), + ephemeral_public_key_encoded_point, + AlgorithmIdentifierOwned::from(&self.eckey_encryption_info), + AlgorithmIdentifierOwned { + oid: self.key_agreement_algorithm.oid(), + parameters: Some(Any::from_der(&key_wrap_algorithm_der)?), + }, + ) + } + }; + + // Build RecipientInfo + Ok(RecipientInfo::Kari(KeyAgreeRecipientInfo { + originator: OriginatorIdentifierOrKey::OriginatorKey(OriginatorPublicKey { + algorithm: originator_algorithm_identifier, + public_key: BitString::from_bytes(ephemeral_pubkey_encoded_point.as_bytes())?, + }), + version: self.recipient_info_version(), + ukm: self.ukm.clone(), + key_enc_alg: key_encryption_algorithm_identifier, + recipient_enc_keys: vec![RecipientEncryptedKey { + rid: self.rid.clone(), + enc_key: EncryptedKey::new(encrypted_key)?, + }], + })) + } +} + +#[cfg(test)] +mod tests { + use std::eprintln; + + use super::*; + use p256::{pkcs8::DecodePublicKey, NistP256, PublicKey}; + + /// Generate a test P256 EcKeyEncryptionInfo + fn get_test_ec_key_info() -> EcKeyEncryptionInfo { + // Public key der bytes: + // ```rust + // let public_key_der_bytes = include_bytes!("../../tests/examples/p256-pub.der"); + // ``` + // OR + // ```bash + // od -An -vtu1 cms/tests/examples/p256-pub.der | tr -s ' ' | tr -d '\n' | sed 's/ /, /g' | sed 's/^, //' |xargs + // ``` + let public_key_der_bytes: &[u8] = &[ + 48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, + 3, 66, 0, 4, 28, 172, 255, 181, 95, 47, 44, 239, 216, 157, 137, 235, 55, 75, 38, 129, + 21, 36, 82, 128, 45, 238, 160, 153, 22, 6, 129, 55, 216, 57, 207, 127, 196, 129, 164, + 68, 146, 48, 77, 126, 246, 106, 193, 23, 190, 254, 131, 168, 208, 143, 21, 95, 43, 82, + 249, 246, 24, 221, 68, 112, 41, 4, 142, 15, + ]; + let p256_public_key = PublicKey::from_public_key_der(public_key_der_bytes) + .map_err(|e| eprintln!("{}", e)) + .expect("Getting PublicKey failed"); + EcKeyEncryptionInfo::Ec(p256_public_key) + } + + #[test] + fn test_keyagreementalgorithm_oid() { + assert_eq!( + KeyAgreementAlgorithm::SinglePassStdDhSha224Kdf.oid(), + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_224_KDF_SCHEME + ); + assert_eq!( + KeyAgreementAlgorithm::SinglePassStdDhSha256Kdf.oid(), + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_256_KDF_SCHEME + ); + assert_eq!( + KeyAgreementAlgorithm::SinglePassStdDhSha384Kdf.oid(), + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_384_KDF_SCHEME + ); + assert_eq!( + KeyAgreementAlgorithm::SinglePassStdDhSha512Kdf.oid(), + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_512_KDF_SCHEME + ); + } + + #[test] + fn test_from_keyagreementalgorithm_for_hashdigest() { + assert_eq!( + HashDigest::from(&KeyAgreementAlgorithm::SinglePassStdDhSha224Kdf), + HashDigest::Sha224 + ); + assert_eq!( + HashDigest::from(&KeyAgreementAlgorithm::SinglePassStdDhSha256Kdf), + HashDigest::Sha256 + ); + assert_eq!( + HashDigest::from(&KeyAgreementAlgorithm::SinglePassStdDhSha384Kdf), + HashDigest::Sha384 + ); + assert_eq!( + HashDigest::from(&KeyAgreementAlgorithm::SinglePassStdDhSha512Kdf), + HashDigest::Sha512 + ); + } + + #[test] + fn test_eckeyencryptioninfo_get_oid() { + let ec_key_encryption_info = get_test_ec_key_info(); + assert_eq!( + ec_key_encryption_info.get_oid(), + ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7") + ); + } + + #[test] + fn test_algorithmidentifierowned_from_eckeyencryptioninfo() { + let ec_key_encryption_info = get_test_ec_key_info(); + + assert_eq!( + AlgorithmIdentifierOwned { + oid: ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"), + parameters: Some(ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7").into()), + }, + AlgorithmIdentifierOwned::from(&ec_key_encryption_info) + ) + } +} From 6cfc94056d1f92d6557e8f23c4138109a193e8c8 Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 20 Oct 2024 09:44:19 -0400 Subject: [PATCH 20/30] cms: ecc-kari support - remove default builder --- cms/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/cms/Cargo.toml b/cms/Cargo.toml index 1f5fa6882..c27ba9795 100644 --- a/cms/Cargo.toml +++ b/cms/Cargo.toml @@ -50,7 +50,6 @@ tokio = { version = "1.43.0", features = ["macros", "rt"] } x509-cert = { version = "=0.3.0-pre.0", features = ["pem"] } [features] -default = ["builder"] std = ["der/std", "spki/std"] builder = [ "dep:aes", From d4b9597e1ddbbb4c90b224e175ec1ec9c365146f Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 20 Oct 2024 10:14:50 -0400 Subject: [PATCH 21/30] cms: ecc-kari support - add From for KeyWrapAlgorithm --- cms/src/builder/utils/kw.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/cms/src/builder/utils/kw.rs b/cms/src/builder/utils/kw.rs index db7b9e825..0d82c9fd7 100644 --- a/cms/src/builder/utils/kw.rs +++ b/cms/src/builder/utils/kw.rs @@ -5,7 +5,7 @@ use alloc::{string::String, vec::Vec}; -use crate::builder::{Error, Result}; +use crate::builder::{ContentEncryptionAlgorithm, Error, Result}; use aes_kw::Kek; use const_oid::ObjectIdentifier; use der::Any; @@ -37,7 +37,7 @@ use spki::AlgorithmIdentifierOwned; /// /// [RFC 5753 Section 8]: https://datatracker.ietf.org/doc/html/rfc5753#section-8 /// [RFC 5753 Section 7.1.5]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.1.5 -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum KeyWrapAlgorithm { /// id-aes128-wrap Aes128, @@ -129,6 +129,18 @@ impl From for AlgorithmIdentifierOwned { } } } +impl From for KeyWrapAlgorithm { + /// Convert a `ContentEncryptionAlgorithm` to a `KeyWrapAlgorithm`. + /// + /// Conversion is done matching encryption strength. + fn from(ce_algo: ContentEncryptionAlgorithm) -> Self { + match ce_algo { + ContentEncryptionAlgorithm::Aes128Cbc => Self::Aes128, + ContentEncryptionAlgorithm::Aes192Cbc => Self::Aes192, + ContentEncryptionAlgorithm::Aes256Cbc => Self::Aes256, + } + } +} /// This struct can be used to perform key wrapping operation. /// @@ -345,6 +357,21 @@ mod tests { } ) } + #[test] + fn test_keywrapalgorithm_from_contentencryptionalgorithm() { + assert_eq!( + KeyWrapAlgorithm::from(ContentEncryptionAlgorithm::Aes128Cbc), + KeyWrapAlgorithm::Aes128 + ); + assert_eq!( + KeyWrapAlgorithm::from(ContentEncryptionAlgorithm::Aes192Cbc), + KeyWrapAlgorithm::Aes192 + ); + assert_eq!( + KeyWrapAlgorithm::from(ContentEncryptionAlgorithm::Aes256Cbc), + KeyWrapAlgorithm::Aes256 + ); + } #[test] fn test_keywrapper_try_new() { From b764a75a82019e6ce78ef6458217b4cdc4c391dd Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 20 Oct 2024 10:21:12 -0400 Subject: [PATCH 22/30] cms: ecc-kari support - add From for WrappingKey test --- cms/src/builder/utils/kw.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/cms/src/builder/utils/kw.rs b/cms/src/builder/utils/kw.rs index 0d82c9fd7..32cf3a349 100644 --- a/cms/src/builder/utils/kw.rs +++ b/cms/src/builder/utils/kw.rs @@ -222,7 +222,7 @@ impl From for Vec { /// - Aes192 /// - Aes256 /// -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum WrappingKey { /// id-aes128-wrap Aes128([u8; 16]), @@ -330,9 +330,6 @@ mod tests { assert_eq!(KeyWrapAlgorithm::Aes256.key_size_in_bits(), 256); } - #[test] - fn test_wrappingkey_from_keywrapalgorithm() {} - #[test] fn test_algorithmidentifierowned_from_keywrapalgorithm() { assert_eq!( @@ -491,6 +488,22 @@ mod tests { assert_eq!(vec[0], 0); } + #[test] + fn test_wrappingkey_from_keywrapalgorithm() { + assert_eq!( + WrappingKey::from(&KeyWrapAlgorithm::Aes128), + WrappingKey::Aes128([0u8; 16]) + ); + assert_eq!( + WrappingKey::from(&KeyWrapAlgorithm::Aes192), + WrappingKey::Aes192([0u8; 24]) + ); + assert_eq!( + WrappingKey::from(&KeyWrapAlgorithm::Aes256), + WrappingKey::Aes256([0u8; 32]) + ); + } + #[test] fn test_wrappingkey_as_mut() { let mut key_a128 = WrappingKey::Aes128([0; 16]); From 0bef8e626097dfa3cce7971547c66c020144ca0f Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:51:59 -0400 Subject: [PATCH 23/30] cms: ecc-kari support - bring EnvelopedData in scope for doc --- cms/src/builder/kari.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs index 3eee14d13..96f5b8559 100644 --- a/cms/src/builder/kari.rs +++ b/cms/src/builder/kari.rs @@ -14,6 +14,8 @@ use super::{ }; // Crate imports +#[cfg(doc)] +use crate::enveloped_data::EnvelopedData; use crate::{ content_info::CmsVersion, enveloped_data::{ From 9dec6a1eaa30a01ead549ebeab3f782e987a5d94 Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Mon, 21 Oct 2024 09:29:29 -0700 Subject: [PATCH 24/30] cms: use aes-kw pre-release --- Cargo.lock | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34fef790e..74d1a1fcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,21 +94,6 @@ dependencies = [ "digest", ] -[[package]] -name = "anstream" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - [[package]] name = "anstyle" version = "1.0.10" @@ -1820,12 +1805,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "version_check" version = "0.9.5" From 07c8a17b3c87f47bd1d0067907a8da3cfe29b2d6 Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Mon, 21 Oct 2024 09:29:29 -0700 Subject: [PATCH 25/30] cms: use aes-kw pre-release --- Cargo.lock | 88 +++++++++++--------------------------------------- Cargo.toml | 8 +++++ cms/Cargo.toml | 4 +-- 3 files changed, 29 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74d1a1fcc..924d9b53a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,18 +23,7 @@ version = "0.6.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5f451b77e2f92932dc411da6ef9f3d33efad68a6f14a7a83e559453458e85ac" dependencies = [ - "crypto-common 0.2.0-rc.1", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures", + "crypto-common", ] [[package]] @@ -44,7 +33,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7856582c758ade85d71daf27ec6bcea6c1c73913692b07b8dffea2dc03531c9" dependencies = [ "cfg-if", - "cipher 0.5.0-pre.7", + "cipher", "cpufeatures", ] @@ -55,8 +44,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cce27af05d45b901bb28da33ff8b2b2b2044f595b24fc0f36d4882dae91d484" dependencies = [ "aead", - "aes 0.9.0-pre.2", - "cipher 0.5.0-pre.7", + "aes", + "cipher", "ctr", "ghash", "subtle", @@ -64,11 +53,11 @@ dependencies = [ [[package]] name = "aes-kw" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c" +version = "0.3.0-pre" +source = "git+https://github.com/RustCrypto/key-wraps.git#c9437ce4933822252863fd64fc06c631405ad8b1" dependencies = [ - "aes 0.8.4", + "aes", + "const-oid", ] [[package]] @@ -249,7 +238,7 @@ version = "0.2.0-pre.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0729a0a8422deb6056b8fcd89c42b724fe27e69458fa006f00c63cbffffd91b" dependencies = [ - "cipher 0.5.0-pre.7", + "cipher", ] [[package]] @@ -285,24 +274,14 @@ dependencies = [ "half", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common 0.1.6", - "inout 0.1.3", -] - [[package]] name = "cipher" version = "0.5.0-pre.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b1425e6ce000f05a73096556cabcfb6a10a3ffe3bb4d75416ca8f00819c0b6a" dependencies = [ - "crypto-common 0.2.0-rc.1", - "inout 0.2.0-rc.3", + "crypto-common", + "inout", ] [[package]] @@ -346,12 +325,12 @@ dependencies = [ name = "cms" version = "0.3.0-pre.0" dependencies = [ - "aes 0.9.0-pre.2", + "aes", "aes-kw", "ansi-x963-kdf", "async-signature", "cbc", - "cipher 0.5.0-pre.7", + "cipher", "const-oid", "der", "ecdsa", @@ -457,16 +436,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - [[package]] name = "crypto-common" version = "0.2.0-rc.1" @@ -484,7 +453,7 @@ version = "0.10.0-pre.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77e1482d284b80d7fddb211666d513dc5e23b0cc3a03ad398ff70543827c789f" dependencies = [ - "cipher 0.5.0-pre.7", + "cipher", ] [[package]] @@ -538,7 +507,7 @@ version = "0.9.0-pre.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76239c731adb4b5204cfeec47bd06ec1071d9477a0d32bbb83dc7d8c599efe63" dependencies = [ - "cipher 0.5.0-pre.7", + "cipher", ] [[package]] @@ -549,7 +518,7 @@ checksum = "cf2e3d6615d99707295a9673e889bf363a04b2a466bd320c65a72536f7577379" dependencies = [ "block-buffer", "const-oid", - "crypto-common 0.2.0-rc.1", + "crypto-common", "subtle", ] @@ -681,16 +650,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" version = "0.2.15" @@ -823,15 +782,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - [[package]] name = "inout" version = "0.2.0-rc.3" @@ -1080,7 +1030,7 @@ dependencies = [ name = "pkcs5" version = "0.8.0-rc.3" dependencies = [ - "aes 0.9.0-pre.2", + "aes", "aes-gcm", "cbc", "der", @@ -1416,7 +1366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1affa54a576c40080654b494bb3f3198fa2fe46e0954b85196d122e3561c2fd0" dependencies = [ "cfg-if", - "cipher 0.5.0-pre.7", + "cipher", ] [[package]] @@ -1801,7 +1751,7 @@ version = "0.6.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3517d72c5ca6d60f9f2e85d2c772e2652830062a685105a528d19dd823cf87d5" dependencies = [ - "crypto-common 0.2.0-rc.1", + "crypto-common", "subtle", ] diff --git a/Cargo.toml b/Cargo.toml index 85cc8f141..7aaa419e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,3 +58,11 @@ tls_codec_derive = { path = "./tls_codec/derive" } x509-tsp = { path = "./x509-tsp" } x509-cert = { path = "./x509-cert" } x509-ocsp = { path = "./x509-ocsp" } + +# https://github.com/RustCrypto/key-wraps/pull/34 +# https://github.com/RustCrypto/key-wraps/pull/35 +aes-kw = { git = "https://github.com/RustCrypto/key-wraps.git" } + +# https://github.com/RustCrypto/KDFs/pull/102 +ansi-x963-kdf = { git = "https://github.com/RustCrypto/KDFs.git" } + diff --git a/cms/Cargo.toml b/cms/Cargo.toml index c27ba9795..00947cd22 100644 --- a/cms/Cargo.toml +++ b/cms/Cargo.toml @@ -22,8 +22,8 @@ x509-cert = { version = "=0.3.0-pre.0", default-features = false } # optional dependencies aes = { version = "=0.9.0-pre.2", optional = true } -aes-kw = { version ="0.2.1", optional = true } -ansi-x963-kdf = { git = "https://github.com/RustCrypto/KDFs.git", version = "0.1.0", optional = true } +aes-kw = { version ="=0.3.0-pre", optional = true } +ansi-x963-kdf = { version = "0.1.0", optional = true } async-signature = { version = "=0.6.0-pre.4", features = ["digest", "rand_core"], optional = true } cbc = { version = "=0.2.0-pre.2", optional = true } cipher = { version = "=0.5.0-pre.7", features = ["alloc", "block-padding", "rand_core"], optional = true } From 876731685d285d52daca06fe2d4a115d54da8fdf Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Mon, 21 Oct 2024 10:10:15 -0700 Subject: [PATCH 26/30] cms: make KeyAgreementAlgorithm a trait --- Cargo.lock | 1 + cms/Cargo.toml | 2 + cms/src/builder.rs | 6 +- cms/src/builder/kari.rs | 173 +++++++++++++++++----------------- cms/src/builder/utils.rs | 2 - cms/src/builder/utils/kdf.rs | 175 ----------------------------------- cms/src/builder/utils/kw.rs | 2 +- cms/tests/builder/kari.rs | 22 ++--- 8 files changed, 103 insertions(+), 280 deletions(-) delete mode 100644 cms/src/builder/utils/kdf.rs diff --git a/Cargo.lock b/Cargo.lock index 924d9b53a..9fe4c28f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,6 +333,7 @@ dependencies = [ "cipher", "const-oid", "der", + "digest", "ecdsa", "elliptic-curve", "getrandom 0.3.1", diff --git a/cms/Cargo.toml b/cms/Cargo.toml index 00947cd22..da97689df 100644 --- a/cms/Cargo.toml +++ b/cms/Cargo.toml @@ -27,6 +27,7 @@ ansi-x963-kdf = { version = "0.1.0", optional = true } async-signature = { version = "=0.6.0-pre.4", features = ["digest", "rand_core"], optional = true } cbc = { version = "=0.2.0-pre.2", optional = true } cipher = { version = "=0.5.0-pre.7", features = ["alloc", "block-padding", "rand_core"], optional = true } +digest = { version = "0.11.0-pre.9", optional = true } elliptic-curve = { version = "=0.14.0-rc.1", optional = true } rsa = { version = "=0.10.0-pre.3", optional = true } sha1 = { version = "=0.11.0-pre.4", optional = true } @@ -58,6 +59,7 @@ builder = [ "dep:async-signature", "dep:cbc", "dep:cipher", + "dep:digest", "elliptic-curve/ecdh", "elliptic-curve/pkcs8", "dep:rsa", diff --git a/cms/src/builder.rs b/cms/src/builder.rs index 53ef06edb..393d05d17 100644 --- a/cms/src/builder.rs +++ b/cms/src/builder.rs @@ -52,8 +52,10 @@ mod kari; mod utils; // Exports -pub use kari::{EcKeyEncryptionInfo, KeyAgreeRecipientInfoBuilder, KeyAgreementAlgorithm}; -pub use utils::kw::KeyWrapAlgorithm; +pub use kari::{ + DhSinglePassStdDhKdf, EcKeyEncryptionInfo, KeyAgreeRecipientInfoBuilder, KeyAgreementAlgorithm, +}; +pub use utils::kw::{KeyWrapAlgorithm, KeyWrapper}; /// Error type #[derive(Debug)] diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs index 96f5b8559..139a019ed 100644 --- a/cms/src/builder/kari.rs +++ b/cms/src/builder/kari.rs @@ -8,9 +8,8 @@ // Super imports use super::{ - utils::{try_ansi_x963_kdf, HashDigest, KeyWrapper}, - AlgorithmIdentifierOwned, CryptoRngCore, KeyWrapAlgorithm, RecipientInfoBuilder, - RecipientInfoType, Result, UserKeyingMaterial, + utils::KeyWrapper, AlgorithmIdentifierOwned, CryptoRngCore, KeyWrapAlgorithm, + RecipientInfoBuilder, RecipientInfoType, Result, UserKeyingMaterial, }; // Crate imports @@ -25,21 +24,25 @@ use crate::{ }; // Internal imports -use const_oid::{AssociatedOid, ObjectIdentifier}; +use const_oid::{db::rfc5753, AssociatedOid, ObjectIdentifier}; use der::{ asn1::{BitString, OctetString}, Any, Decode, Encode, Sequence, }; +// Core imports +use core::marker::PhantomData; + // Alloc imports -use alloc::{vec, vec::Vec}; +use alloc::{string::String, vec, vec::Vec}; // RustCrypto imports +use digest::{Digest, FixedOutputReset}; use elliptic_curve::{ - ecdh::EphemeralSecret, + ecdh::{EphemeralSecret, SharedSecret}, point::PointCompression, sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}, - AffinePoint, CurveArithmetic, FieldBytesSize, PublicKey, + AffinePoint, Curve, CurveArithmetic, FieldBytesSize, PublicKey, }; /// The `EccCmsSharedInfo` type is defined in [RFC 5753 Section 7.2]. @@ -96,48 +99,63 @@ pub struct EccCmsSharedInfo { /// /// [RFC 5753 Section 7.1.4]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.1.4 /// [RFC 5753 Section 8]: https://datatracker.ietf.org/doc/html/rfc5753#section-8 -#[allow(clippy::enum_variant_names)] -#[derive(Clone, Copy)] -pub enum KeyAgreementAlgorithm { - /// dhSinglePass-stdDH-sha224kdf-scheme - SinglePassStdDhSha224Kdf, - /// dhSinglePass-stdDH-sha256kdf-scheme - SinglePassStdDhSha256Kdf, - /// dhSinglePass-stdDH-sha384kdf-scheme - SinglePassStdDhSha384Kdf, - /// dhSinglePass-stdDH-sh512df-scheme - SinglePassStdDhSha512Kdf, +pub trait KeyAgreementAlgorithm { + /// Compute a shared key with the provided parameters + fn kdf( + secret: &SharedSecret, + shared_info: &EccCmsSharedInfo, + key_wrapper: &mut KeyWrapper, + ) -> Result<()> + where + C: Curve; } -impl KeyAgreementAlgorithm { - /// Return the OID of the algorithm. - fn oid(&self) -> ObjectIdentifier { - match self { - Self::SinglePassStdDhSha224Kdf => { - const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_224_KDF_SCHEME - } - Self::SinglePassStdDhSha256Kdf => { - const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_256_KDF_SCHEME - } - Self::SinglePassStdDhSha384Kdf => { - const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_384_KDF_SCHEME - } - Self::SinglePassStdDhSha512Kdf => { - const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_512_KDF_SCHEME - } - } - } + +/// Support for EnvelopedData with the ephemeral-static ECDH cofactor primitive +pub struct DhSinglePassStdDhKdf { + digest: PhantomData, } -impl From<&KeyAgreementAlgorithm> for HashDigest { - fn from(ka_algo: &KeyAgreementAlgorithm) -> Self { - match ka_algo { - KeyAgreementAlgorithm::SinglePassStdDhSha224Kdf => Self::Sha224, - KeyAgreementAlgorithm::SinglePassStdDhSha256Kdf => Self::Sha256, - KeyAgreementAlgorithm::SinglePassStdDhSha384Kdf => Self::Sha384, - KeyAgreementAlgorithm::SinglePassStdDhSha512Kdf => Self::Sha512, - } + +impl KeyAgreementAlgorithm for DhSinglePassStdDhKdf +where + D: Digest + FixedOutputReset, +{ + fn kdf( + secret: &SharedSecret, + shared_info: &EccCmsSharedInfo, + key_wrapper: &mut KeyWrapper, + ) -> Result<()> + where + C: Curve, + { + let shared_info_der = shared_info.to_der()?; + let secret_bytes = secret.raw_secret_bytes(); + + ansi_x963_kdf::derive_key_into::(&secret_bytes, &shared_info_der, key_wrapper.as_mut()) + .map_err(|_| { + super::Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf SHA-224", + )) + })?; + Ok(()) } } +impl AssociatedOid for DhSinglePassStdDhKdf { + const OID: ObjectIdentifier = rfc5753::DH_SINGLE_PASS_STD_DH_SHA_224_KDF_SCHEME; +} + +impl AssociatedOid for DhSinglePassStdDhKdf { + const OID: ObjectIdentifier = rfc5753::DH_SINGLE_PASS_STD_DH_SHA_256_KDF_SCHEME; +} + +impl AssociatedOid for DhSinglePassStdDhKdf { + const OID: ObjectIdentifier = rfc5753::DH_SINGLE_PASS_STD_DH_SHA_384_KDF_SCHEME; +} + +impl AssociatedOid for DhSinglePassStdDhKdf { + const OID: ObjectIdentifier = rfc5753::DH_SINGLE_PASS_STD_DH_SHA_512_KDF_SCHEME; +} + /// Contains information required to encrypt the content encryption key with a method based on ECC key agreement #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum EcKeyEncryptionInfo @@ -173,10 +191,11 @@ where /// This type uses key agreement: the recipient's public key and the sender's /// private key are used to generate a pairwise symmetric key, then /// the content-encryption key is encrypted in the pairwise symmetric key. -pub struct KeyAgreeRecipientInfoBuilder<'a, R, C> +pub struct KeyAgreeRecipientInfoBuilder<'a, R, C, KA> where R: CryptoRngCore, C: CurveArithmetic, + KA: KeyAgreementAlgorithm, { /// Optional information which helps generating different keys every time. pub ukm: Option, @@ -185,40 +204,41 @@ where /// Recipient key info pub eckey_encryption_info: EcKeyEncryptionInfo, /// Content encryption algorithm - pub key_agreement_algorithm: KeyAgreementAlgorithm, - /// Content encryption algorithm pub key_wrap_algorithm: KeyWrapAlgorithm, + /// Content encryption algorithm + key_agreement_algorithm: PhantomData, /// Rng rng: &'a mut R, } -impl<'a, R, C> KeyAgreeRecipientInfoBuilder<'a, R, C> +impl<'a, R, C, KA> KeyAgreeRecipientInfoBuilder<'a, R, C, KA> where R: CryptoRngCore, C: CurveArithmetic, + KA: KeyAgreementAlgorithm, { /// Creates a `KeyAgreeRecipientInfoBuilder` pub fn new( ukm: Option, rid: KeyAgreeRecipientIdentifier, eckey_encryption_info: EcKeyEncryptionInfo, - key_agreement_algorithm: KeyAgreementAlgorithm, key_wrap_algorithm: KeyWrapAlgorithm, rng: &'a mut R, - ) -> Result> { + ) -> Result { Ok(KeyAgreeRecipientInfoBuilder { ukm, eckey_encryption_info, - key_agreement_algorithm, key_wrap_algorithm, rid, + key_agreement_algorithm: PhantomData, rng, }) } } -impl<'a, R, C> RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder<'a, R, C> +impl<'a, R, C, KA> RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder<'a, R, C, KA> where R: CryptoRngCore, + KA: KeyAgreementAlgorithm + AssociatedOid, C: CurveArithmetic + AssociatedOid + PointCompression, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, @@ -265,8 +285,6 @@ where // Compute a shared secret with recipient public key. Non-uniformly random, but will be used as input for KDF later. let non_uniformly_random_shared_secret = ephemeral_secret.diffie_hellman(&recipient_public_key); - let non_uniformly_random_shared_secret_bytes = - non_uniformly_random_shared_secret.raw_secret_bytes(); // Generate shared info for KDF // As per https://datatracker.ietf.org/doc/html/rfc5753#section-7.2" @@ -304,19 +322,16 @@ where entity_u_info, supp_pub_info: OctetString::new(key_wrap_algo_keysize_bits_in_be_bytes)?, }; - let shared_info_der = shared_info.to_der()?; // Init a wrapping key (KEK) based on KeyWrapAlgorithm and on CEK (i.e. key to wrap) size let mut key_wrapper = KeyWrapper::try_new(&self.key_wrap_algorithm, content_encryption_key.len())?; // Derive the Key Encryption Key (KEK) from Shared Secret using ANSI X9.63 KDF - let digest = HashDigest::from(&self.key_agreement_algorithm); - try_ansi_x963_kdf( - non_uniformly_random_shared_secret_bytes.as_slice(), - &shared_info_der, + KA::kdf( + &non_uniformly_random_shared_secret, + &shared_info, &mut key_wrapper, - &digest, )?; // Wrap the Content Encryption Key (CEK) with the KEK @@ -328,7 +343,7 @@ where ephemeral_public_key_encoded_point, AlgorithmIdentifierOwned::from(&self.eckey_encryption_info), AlgorithmIdentifierOwned { - oid: self.key_agreement_algorithm.oid(), + oid: KA::OID, parameters: Some(Any::from_der(&key_wrap_algorithm_der)?), }, ) @@ -385,40 +400,20 @@ mod tests { #[test] fn test_keyagreementalgorithm_oid() { assert_eq!( - KeyAgreementAlgorithm::SinglePassStdDhSha224Kdf.oid(), - const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_224_KDF_SCHEME - ); - assert_eq!( - KeyAgreementAlgorithm::SinglePassStdDhSha256Kdf.oid(), - const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_256_KDF_SCHEME - ); - assert_eq!( - KeyAgreementAlgorithm::SinglePassStdDhSha384Kdf.oid(), - const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_384_KDF_SCHEME - ); - assert_eq!( - KeyAgreementAlgorithm::SinglePassStdDhSha512Kdf.oid(), - const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_512_KDF_SCHEME - ); - } - - #[test] - fn test_from_keyagreementalgorithm_for_hashdigest() { - assert_eq!( - HashDigest::from(&KeyAgreementAlgorithm::SinglePassStdDhSha224Kdf), - HashDigest::Sha224 + DhSinglePassStdDhKdf::::OID, + rfc5753::DH_SINGLE_PASS_STD_DH_SHA_224_KDF_SCHEME ); assert_eq!( - HashDigest::from(&KeyAgreementAlgorithm::SinglePassStdDhSha256Kdf), - HashDigest::Sha256 + DhSinglePassStdDhKdf::::OID, + rfc5753::DH_SINGLE_PASS_STD_DH_SHA_256_KDF_SCHEME ); assert_eq!( - HashDigest::from(&KeyAgreementAlgorithm::SinglePassStdDhSha384Kdf), - HashDigest::Sha384 + DhSinglePassStdDhKdf::::OID, + rfc5753::DH_SINGLE_PASS_STD_DH_SHA_384_KDF_SCHEME ); assert_eq!( - HashDigest::from(&KeyAgreementAlgorithm::SinglePassStdDhSha512Kdf), - HashDigest::Sha512 + DhSinglePassStdDhKdf::::OID, + rfc5753::DH_SINGLE_PASS_STD_DH_SHA_512_KDF_SCHEME ); } diff --git a/cms/src/builder/utils.rs b/cms/src/builder/utils.rs index 140b2234a..e2176bf2a 100644 --- a/cms/src/builder/utils.rs +++ b/cms/src/builder/utils.rs @@ -5,8 +5,6 @@ //! - kw: AES Key Wrap //! - kdf: KDF using ANSI-x9.63 Key Derivation Function -mod kdf; pub(super) mod kw; -pub(super) use kdf::{try_ansi_x963_kdf, HashDigest}; pub(super) use kw::KeyWrapper; diff --git a/cms/src/builder/utils/kdf.rs b/cms/src/builder/utils/kdf.rs deleted file mode 100644 index a99b2399c..000000000 --- a/cms/src/builder/utils/kdf.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! KDF module -//! -//! This module contains KDF logic. -//! - -use crate::builder::{Error, Result}; -use alloc::string::String; - -#[derive(PartialEq, Eq, Debug)] -pub(in crate::builder) enum HashDigest { - Sha224, - Sha256, - Sha384, - Sha512, -} - -/// Wrapper for ANSI-X9.63 KDF -/// -/// This function wraps ansi_x963_kdf, applying a Hash Disgest based on the key agreement algorithm identifier -pub(in crate::builder) fn try_ansi_x963_kdf( - secret: &[u8], - other_info: &[u8], - key: &mut impl AsMut<[u8]>, - digest: &HashDigest, -) -> Result<()> { - match digest { - HashDigest::Sha224 => ansi_x963_kdf_sha224(secret, other_info, key).map_err(|_| { - Error::Builder(String::from( - "Could not generate a shared secret via ansi-x9.63-kdf SHA-224", - )) - }), - HashDigest::Sha256 => ansi_x963_kdf_sha256(secret, other_info, key).map_err(|_| { - Error::Builder(String::from( - "Could not generate a shared secret via ansi-x9.63-kdf SHA-256", - )) - }), - HashDigest::Sha384 => ansi_x963_kdf_sha384(secret, other_info, key).map_err(|_| { - Error::Builder(String::from( - "Could not generate a shared secret via ansi-x9.63-kdf SHA-384", - )) - }), - HashDigest::Sha512 => ansi_x963_kdf_sha512(secret, other_info, key).map_err(|_| { - Error::Builder(String::from( - "Could not generate a shared secret via ansi-x9.63-kdf SHA-512", - )) - }), - } -} - -/// ANSI-X9.63-KDF with SHA224 -fn ansi_x963_kdf_sha224( - secret: &[u8], - other_info: &[u8], - key: &mut impl AsMut<[u8]>, -) -> Result<()> { - ansi_x963_kdf::derive_key_into::(secret, other_info, key.as_mut()).map_err(|_| { - Error::Builder(String::from( - "Could not generate a shared secret via ansi-x9.63-kdf", - )) - }) -} - -/// ANSI-X9.63-KDF with SHA256 -fn ansi_x963_kdf_sha256( - secret: &[u8], - other_info: &[u8], - key: &mut impl AsMut<[u8]>, -) -> Result<()> { - ansi_x963_kdf::derive_key_into::(secret, other_info, key.as_mut()).map_err(|_| { - Error::Builder(String::from( - "Could not generate a shared secret via ansi-x9.63-kdf", - )) - }) -} - -/// ANSI-X9.63-KDF with SHA384 -fn ansi_x963_kdf_sha384( - secret: &[u8], - other_info: &[u8], - key: &mut impl AsMut<[u8]>, -) -> Result<()> { - ansi_x963_kdf::derive_key_into::(secret, other_info, key.as_mut()).map_err(|_| { - Error::Builder(String::from( - "Could not generate a shared secret via ansi-x9.63-kdf", - )) - }) -} - -/// ANSI-X9.63-KDF with SHA512 -fn ansi_x963_kdf_sha512( - secret: &[u8], - other_info: &[u8], - key: &mut impl AsMut<[u8]>, -) -> Result<()> { - ansi_x963_kdf::derive_key_into::(secret, other_info, key.as_mut()).map_err(|_| { - Error::Builder(String::from( - "Could not generate a shared secret via ansi-x9.63-kdf", - )) - }) -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn test_try_ansi_x963_kdf() { - let secret = [0u8; 16]; - let other_info = []; - let mut key = [0u8; 16]; - assert!(try_ansi_x963_kdf(&secret, &other_info, &mut key, &HashDigest::Sha224).is_ok()); - assert_eq!( - key, - [33, 35, 2, 122, 169, 122, 164, 137, 12, 5, 195, 31, 101, 142, 44, 237] - ) - } - - #[test] - fn test_try_ansi_x963_kdf_error() { - // Empty secret should trigger error of ansi_x963_kdf - let secret = []; - let other_info = []; - let mut key = [0u8; 16]; - assert!(try_ansi_x963_kdf(&secret, &other_info, &mut key, &HashDigest::Sha224).is_err()); - } - - #[test] - fn test_try_ansi_x963_kdf_sha224() { - let secret = [0u8; 16]; - let other_info = []; - let mut key = [0u8; 16]; - assert!(ansi_x963_kdf_sha224(&secret, &other_info, &mut key).is_ok()); - assert_eq!( - key, - [33, 35, 2, 122, 169, 122, 164, 137, 12, 5, 195, 31, 101, 142, 44, 237] - ) - } - - #[test] - fn test_try_ansi_x963_kdf_sha256() { - let secret = [0u8; 16]; - let other_info = []; - let mut key = [0u8; 16]; - assert!(ansi_x963_kdf_sha256(&secret, &other_info, &mut key).is_ok()); - assert_eq!( - key, - [233, 255, 14, 110, 109, 233, 93, 165, 111, 240, 159, 78, 62, 15, 72, 29] - ) - } - - #[test] - fn test_try_ansi_x963_kdf_sha384() { - let secret = [0u8; 16]; - let other_info = []; - let mut key = [0u8; 16]; - assert!(ansi_x963_kdf_sha384(&secret, &other_info, &mut key).is_ok()); - assert_eq!( - key, - [156, 231, 52, 7, 234, 137, 225, 91, 29, 49, 193, 212, 25, 40, 137, 8] - ) - } - - #[test] - fn test_try_ansi_x963_kdf_sha512() { - let secret = [0u8; 16]; - let other_info = []; - let mut key = [0u8; 16]; - assert!(ansi_x963_kdf_sha512(&secret, &other_info, &mut key).is_ok()); - assert_eq!( - key, - [160, 237, 224, 79, 173, 198, 48, 115, 203, 162, 233, 108, 204, 185, 88, 209] - ) - } -} diff --git a/cms/src/builder/utils/kw.rs b/cms/src/builder/utils/kw.rs index 32cf3a349..ed6ab1311 100644 --- a/cms/src/builder/utils/kw.rs +++ b/cms/src/builder/utils/kw.rs @@ -154,7 +154,7 @@ impl From for KeyWrapAlgorithm { /// - yield the inner wrapping-key as a mutable reference (e.g. to use with a KDF) /// - convert to Vec to obtain Owned data to the wrapped key #[derive(Debug, Clone, Copy)] -pub(in crate::builder) struct KeyWrapper { +pub struct KeyWrapper { /// Wrapping key wrapping_key: WrappingKey, /// Wrapped key diff --git a/cms/tests/builder/kari.rs b/cms/tests/builder/kari.rs index 503c4c914..22e2f1722 100644 --- a/cms/tests/builder/kari.rs +++ b/cms/tests/builder/kari.rs @@ -1,7 +1,7 @@ use cms::{ builder::{ - ContentEncryptionAlgorithm, EcKeyEncryptionInfo, EnvelopedDataBuilder, - KeyAgreeRecipientInfoBuilder, KeyAgreementAlgorithm, KeyWrapAlgorithm, + ContentEncryptionAlgorithm, DhSinglePassStdDhKdf, EcKeyEncryptionInfo, + EnvelopedDataBuilder, KeyAgreeRecipientInfoBuilder, KeyWrapAlgorithm, }, cert::IssuerAndSerialNumber, content_info::ContentInfo, @@ -41,15 +41,15 @@ fn test_build_enveloped_data_ec() { // KARI builder let mut rng = OsRng; - let kari_builder = KeyAgreeRecipientInfoBuilder::new( - None, - key_agreement_recipient_identifier, - EcKeyEncryptionInfo::Ec(recipient_public_key), - KeyAgreementAlgorithm::SinglePassStdDhSha256Kdf, - KeyWrapAlgorithm::Aes128, - &mut rng, - ) - .expect("Could not create a KeyAgreeRecipientInfoBuilder"); + let kari_builder = + KeyAgreeRecipientInfoBuilder::<_, _, DhSinglePassStdDhKdf>::new( + None, + key_agreement_recipient_identifier, + EcKeyEncryptionInfo::Ec(recipient_public_key), + KeyWrapAlgorithm::Aes128, + &mut rng, + ) + .expect("Could not create a KeyAgreeRecipientInfoBuilder"); // Enveloped data builder let mut rng = OsRng; From c4ef661a67d8dd7f03a4fe4b3f34026cc3a7f12e Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:08:16 -0500 Subject: [PATCH 27/30] Switch to build_with_rng --- cms/src/builder/kari.rs | 22 ++++++++++++++-------- cms/tests/builder/kari.rs | 4 +--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs index 139a019ed..97d9fb137 100644 --- a/cms/src/builder/kari.rs +++ b/cms/src/builder/kari.rs @@ -191,7 +191,7 @@ where /// This type uses key agreement: the recipient's public key and the sender's /// private key are used to generate a pairwise symmetric key, then /// the content-encryption key is encrypted in the pairwise symmetric key. -pub struct KeyAgreeRecipientInfoBuilder<'a, R, C, KA> +pub struct KeyAgreeRecipientInfoBuilder where R: CryptoRngCore, C: CurveArithmetic, @@ -208,10 +208,10 @@ where /// Content encryption algorithm key_agreement_algorithm: PhantomData, /// Rng - rng: &'a mut R, + _rng: PhantomData, } -impl<'a, R, C, KA> KeyAgreeRecipientInfoBuilder<'a, R, C, KA> +impl KeyAgreeRecipientInfoBuilder where R: CryptoRngCore, C: CurveArithmetic, @@ -223,7 +223,6 @@ where rid: KeyAgreeRecipientIdentifier, eckey_encryption_info: EcKeyEncryptionInfo, key_wrap_algorithm: KeyWrapAlgorithm, - rng: &'a mut R, ) -> Result { Ok(KeyAgreeRecipientInfoBuilder { ukm, @@ -231,11 +230,11 @@ where key_wrap_algorithm, rid, key_agreement_algorithm: PhantomData, - rng, + _rng: PhantomData, }) } } -impl<'a, R, C, KA> RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder<'a, R, C, KA> +impl RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder where R: CryptoRngCore, KA: KeyAgreementAlgorithm + AssociatedOid, @@ -243,6 +242,9 @@ where AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { + /// Associated Rng type + type Rng = R; + /// Returns the RecipientInfoType fn recipient_info_type(&self) -> RecipientInfoType { RecipientInfoType::Kari @@ -268,7 +270,11 @@ where /// [RFC 5753 Section 3.1.1]: https://datatracker.ietf.org/doc/html/rfc5753#section-3.1.1 /// [RFC 5753 Section 3.1.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-3.1.2 /// [RFC 5753 Section 3.2.1]: https://datatracker.ietf.org/doc/html/rfc5753#section-3.2.1 - fn build(&mut self, content_encryption_key: &[u8]) -> Result { + fn build_with_rng( + &mut self, + content_encryption_key: &[u8], + rng: &mut Self::Rng, + ) -> Result { // Encrypt key let ( encrypted_key, @@ -278,7 +284,7 @@ where ) = match self.eckey_encryption_info { EcKeyEncryptionInfo::Ec(recipient_public_key) => { // Generate ephemeral key using ecdh - let ephemeral_secret = EphemeralSecret::random(self.rng); + let ephemeral_secret = EphemeralSecret::random(rng); let ephemeral_public_key_encoded_point = ephemeral_secret.public_key().to_encoded_point(false); diff --git a/cms/tests/builder/kari.rs b/cms/tests/builder/kari.rs index 22e2f1722..ff5a7b186 100644 --- a/cms/tests/builder/kari.rs +++ b/cms/tests/builder/kari.rs @@ -40,14 +40,12 @@ fn test_build_enveloped_data_ec() { let recipient_public_key = recipient_private_key.public_key(); // KARI builder - let mut rng = OsRng; let kari_builder = - KeyAgreeRecipientInfoBuilder::<_, _, DhSinglePassStdDhKdf>::new( + KeyAgreeRecipientInfoBuilder::>::new( None, key_agreement_recipient_identifier, EcKeyEncryptionInfo::Ec(recipient_public_key), KeyWrapAlgorithm::Aes128, - &mut rng, ) .expect("Could not create a KeyAgreeRecipientInfoBuilder"); From 5e9dd40de5f1e0918bfc6dbb00bba22c6bb845e8 Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:12:50 -0500 Subject: [PATCH 28/30] Remove unused dependency --- cms/src/builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cms/src/builder.rs b/cms/src/builder.rs index 393d05d17..3ac879db2 100644 --- a/cms/src/builder.rs +++ b/cms/src/builder.rs @@ -6,8 +6,8 @@ use crate::cert::CertificateChoices; use crate::content_info::{CmsVersion, ContentInfo}; use crate::enveloped_data::{ EncryptedContentInfo, EncryptedKey, EnvelopedData, KekIdentifier, KeyTransRecipientInfo, - OriginatorIdentifierOrKey, OriginatorInfo, PasswordRecipientInfo, RecipientIdentifier, - RecipientInfo, RecipientInfos, UserKeyingMaterial, + OriginatorInfo, PasswordRecipientInfo, RecipientIdentifier, RecipientInfo, RecipientInfos, + UserKeyingMaterial, }; use crate::revocation::{RevocationInfoChoice, RevocationInfoChoices}; use crate::signed_data::{ From 00557361d8e2b0486eb3b3fdb5ddb0fc4ad0de7c Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:13:52 -0500 Subject: [PATCH 29/30] Fix clippy and error message in KeyAgreementAlgorithm implementation --- cms/src/builder/kari.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs index 97d9fb137..5a17680b0 100644 --- a/cms/src/builder/kari.rs +++ b/cms/src/builder/kari.rs @@ -130,12 +130,12 @@ where let shared_info_der = shared_info.to_der()?; let secret_bytes = secret.raw_secret_bytes(); - ansi_x963_kdf::derive_key_into::(&secret_bytes, &shared_info_der, key_wrapper.as_mut()) + ansi_x963_kdf::derive_key_into::(secret_bytes, &shared_info_der, key_wrapper.as_mut()) .map_err(|_| { - super::Error::Builder(String::from( - "Could not generate a shared secret via ansi-x9.63-kdf SHA-224", - )) - })?; + super::Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf", + )) + })?; Ok(()) } } From 3331518f8dc5206febed8f9d1a27048786b3a8ce Mon Sep 17 00:00:00 2001 From: nemynm <180121731+nemynm@users.noreply.github.com> Date: Mon, 17 Feb 2025 20:53:58 -0500 Subject: [PATCH 30/30] cms: ecc-kari support - make KeyWrapAlgorithm a trait --------- Co-authored-by: Arthur Gautier --- Cargo.lock | 8 +- Cargo.toml | 2 + cms/src/builder.rs | 2 +- cms/src/builder/kari.rs | 79 +++++---- cms/src/builder/utils.rs | 2 - cms/src/builder/utils/kw.rs | 323 +++++++++++------------------------- cms/tests/builder/kari.rs | 23 ++- 7 files changed, 166 insertions(+), 273 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9fe4c28f7..f676e79ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,7 +54,7 @@ dependencies = [ [[package]] name = "aes-kw" version = "0.3.0-pre" -source = "git+https://github.com/RustCrypto/key-wraps.git#c9437ce4933822252863fd64fc06c631405ad8b1" +source = "git+https://github.com/RustCrypto/key-wraps.git#bb4402822ec6b876d87b85f6495b8a4ca9a295bd" dependencies = [ "aes", "const-oid", @@ -1756,12 +1756,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 7aaa419e2..291f3a790 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,8 +61,10 @@ x509-ocsp = { path = "./x509-ocsp" } # https://github.com/RustCrypto/key-wraps/pull/34 # https://github.com/RustCrypto/key-wraps/pull/35 +# https://github.com/RustCrypto/key-wraps/pull/39 aes-kw = { git = "https://github.com/RustCrypto/key-wraps.git" } + # https://github.com/RustCrypto/KDFs/pull/102 ansi-x963-kdf = { git = "https://github.com/RustCrypto/KDFs.git" } diff --git a/cms/src/builder.rs b/cms/src/builder.rs index 3ac879db2..57650b338 100644 --- a/cms/src/builder.rs +++ b/cms/src/builder.rs @@ -55,7 +55,7 @@ mod utils; pub use kari::{ DhSinglePassStdDhKdf, EcKeyEncryptionInfo, KeyAgreeRecipientInfoBuilder, KeyAgreementAlgorithm, }; -pub use utils::kw::{KeyWrapAlgorithm, KeyWrapper}; +pub use utils::kw::KeyWrapAlgorithm; /// Error type #[derive(Debug)] diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs index 5a17680b0..3d541ccbc 100644 --- a/cms/src/builder/kari.rs +++ b/cms/src/builder/kari.rs @@ -8,8 +8,9 @@ // Super imports use super::{ - utils::KeyWrapper, AlgorithmIdentifierOwned, CryptoRngCore, KeyWrapAlgorithm, - RecipientInfoBuilder, RecipientInfoType, Result, UserKeyingMaterial, + utils::kw::{KeyWrapAlgorithm, WrappedKey}, + AlgorithmIdentifierOwned, CryptoRngCore, RecipientInfoBuilder, RecipientInfoType, Result, + UserKeyingMaterial, }; // Crate imports @@ -31,12 +32,17 @@ use der::{ }; // Core imports -use core::marker::PhantomData; +use core::{marker::PhantomData, ops::Add}; // Alloc imports use alloc::{string::String, vec, vec::Vec}; // RustCrypto imports +use aes::cipher::{ + array::ArraySize, + typenum::{Sum, U8}, + KeySizeUser, +}; use digest::{Digest, FixedOutputReset}; use elliptic_curve::{ ecdh::{EphemeralSecret, SharedSecret}, @@ -101,13 +107,16 @@ pub struct EccCmsSharedInfo { /// [RFC 5753 Section 8]: https://datatracker.ietf.org/doc/html/rfc5753#section-8 pub trait KeyAgreementAlgorithm { /// Compute a shared key with the provided parameters - fn kdf( + fn kdf( secret: &SharedSecret, shared_info: &EccCmsSharedInfo, - key_wrapper: &mut KeyWrapper, + key_wrapper: &mut WrappedKey, ) -> Result<()> where - C: Curve; + C: Curve, + Enc: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add; } /// Support for EnvelopedData with the ephemeral-static ECDH cofactor primitive @@ -119,13 +128,16 @@ impl KeyAgreementAlgorithm for DhSinglePassStdDhKdf where D: Digest + FixedOutputReset, { - fn kdf( + fn kdf( secret: &SharedSecret, shared_info: &EccCmsSharedInfo, - key_wrapper: &mut KeyWrapper, + key_wrapper: &mut WrappedKey, ) -> Result<()> where C: Curve, + Enc: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add, { let shared_info_der = shared_info.to_der()?; let secret_bytes = secret.raw_secret_bytes(); @@ -174,11 +186,11 @@ where C::OID } } -impl From<&EcKeyEncryptionInfo> for AlgorithmIdentifierOwned +impl From> for AlgorithmIdentifierOwned where C: CurveArithmetic + AssociatedOid, { - fn from(ec_key_encryption_info: &EcKeyEncryptionInfo) -> Self { + fn from(ec_key_encryption_info: EcKeyEncryptionInfo) -> Self { let parameters = Some(Any::from(&ec_key_encryption_info.get_oid())); AlgorithmIdentifierOwned { oid: elliptic_curve::ALGORITHM_OID, // id-ecPublicKey @@ -191,56 +203,66 @@ where /// This type uses key agreement: the recipient's public key and the sender's /// private key are used to generate a pairwise symmetric key, then /// the content-encryption key is encrypted in the pairwise symmetric key. -pub struct KeyAgreeRecipientInfoBuilder +pub struct KeyAgreeRecipientInfoBuilder where R: CryptoRngCore, C: CurveArithmetic, KA: KeyAgreementAlgorithm, + KW: KeyWrapAlgorithm, + Enc: KeySizeUser, { /// Optional information which helps generating different keys every time. pub ukm: Option, - /// Encryption algorithm to be used for key encryption + /// Recipient identifier pub rid: KeyAgreeRecipientIdentifier, /// Recipient key info pub eckey_encryption_info: EcKeyEncryptionInfo, - /// Content encryption algorithm - pub key_wrap_algorithm: KeyWrapAlgorithm, - /// Content encryption algorithm + /// Key agreement algorithm key_agreement_algorithm: PhantomData, + /// Key wrap algorithm + key_wrap_algorithm: PhantomData, + /// Content encryption cipher + enc_cipher: PhantomData, /// Rng _rng: PhantomData, } -impl KeyAgreeRecipientInfoBuilder +impl KeyAgreeRecipientInfoBuilder where R: CryptoRngCore, C: CurveArithmetic, KA: KeyAgreementAlgorithm, + KW: KeyWrapAlgorithm, + Enc: KeySizeUser, { /// Creates a `KeyAgreeRecipientInfoBuilder` pub fn new( ukm: Option, rid: KeyAgreeRecipientIdentifier, eckey_encryption_info: EcKeyEncryptionInfo, - key_wrap_algorithm: KeyWrapAlgorithm, ) -> Result { Ok(KeyAgreeRecipientInfoBuilder { ukm, eckey_encryption_info, - key_wrap_algorithm, rid, key_agreement_algorithm: PhantomData, + key_wrap_algorithm: PhantomData, + enc_cipher: PhantomData, _rng: PhantomData, }) } } -impl RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder +impl RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder where R: CryptoRngCore, KA: KeyAgreementAlgorithm + AssociatedOid, C: CurveArithmetic + AssociatedOid + PointCompression, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, + KW: KeyWrapAlgorithm, + Enc: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add, { /// Associated Rng type type Rng = R; @@ -300,8 +322,7 @@ where // this specification, 3DES wrap has NULL parameters while the AES // wraps have absent parameters. // ``` - let key_wrap_algorithm_identifier: AlgorithmIdentifierOwned = - self.key_wrap_algorithm.into(); + let key_wrap_algorithm_identifier = KW::algorithm_identifier(); let key_wrap_algorithm_der = key_wrap_algorithm_identifier.to_der()?; // As per https://datatracker.ietf.org/doc/html/rfc5753#section-7.2" @@ -321,7 +342,7 @@ where // (For example, for AES-256 it would be 00 00 01 00.) // ``` let key_wrap_algo_keysize_bits_in_be_bytes: [u8; 4] = - self.key_wrap_algorithm.key_size_in_bits().to_be_bytes(); + KW::key_size_in_bits().to_be_bytes(); let shared_info = EccCmsSharedInfo { key_info: key_wrap_algorithm_identifier, @@ -330,24 +351,24 @@ where }; // Init a wrapping key (KEK) based on KeyWrapAlgorithm and on CEK (i.e. key to wrap) size - let mut key_wrapper = - KeyWrapper::try_new(&self.key_wrap_algorithm, content_encryption_key.len())?; + let kek = KW::init_kek(); + let mut wrapped: WrappedKey = KW::init_wrapped(); // Derive the Key Encryption Key (KEK) from Shared Secret using ANSI X9.63 KDF KA::kdf( &non_uniformly_random_shared_secret, &shared_info, - &mut key_wrapper, + &mut wrapped, )?; // Wrap the Content Encryption Key (CEK) with the KEK - key_wrapper.try_wrap(content_encryption_key)?; + KW::try_wrap(&kek, content_encryption_key, wrapped.as_mut())?; // Return data ( - Vec::from(key_wrapper), + Vec::from(wrapped), ephemeral_public_key_encoded_point, - AlgorithmIdentifierOwned::from(&self.eckey_encryption_info), + self.eckey_encryption_info.into(), AlgorithmIdentifierOwned { oid: KA::OID, parameters: Some(Any::from_der(&key_wrap_algorithm_der)?), @@ -441,7 +462,7 @@ mod tests { oid: ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"), parameters: Some(ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7").into()), }, - AlgorithmIdentifierOwned::from(&ec_key_encryption_info) + AlgorithmIdentifierOwned::from(ec_key_encryption_info) ) } } diff --git a/cms/src/builder/utils.rs b/cms/src/builder/utils.rs index e2176bf2a..311db1534 100644 --- a/cms/src/builder/utils.rs +++ b/cms/src/builder/utils.rs @@ -6,5 +6,3 @@ //! - kdf: KDF using ANSI-x9.63 Key Derivation Function pub(super) mod kw; - -pub(super) use kw::KeyWrapper; diff --git a/cms/src/builder/utils/kw.rs b/cms/src/builder/utils/kw.rs index ed6ab1311..d9b87d382 100644 --- a/cms/src/builder/utils/kw.rs +++ b/cms/src/builder/utils/kw.rs @@ -3,14 +3,27 @@ //! This module contains the key wrapping logic based on aes-kw algorithms //! -use alloc::{string::String, vec::Vec}; +// Self imports +use crate::builder::{Error, Result}; -use crate::builder::{ContentEncryptionAlgorithm, Error, Result}; -use aes_kw::Kek; -use const_oid::ObjectIdentifier; -use der::Any; +// Internal imports +use const_oid::AssociatedOid; use spki::AlgorithmIdentifierOwned; +// Alloc imports +use alloc::{string::String, vec::Vec}; + +// Core imports +use core::ops::Add; + +// Rust crypto imports +use aes::cipher::{ + array::{Array, ArraySize}, + typenum::{Sum, Unsigned, U16, U8}, + BlockCipherDecrypt, BlockCipherEncrypt, BlockSizeUser, Key, KeyInit, KeySizeUser, +}; +use aes_kw::AesKw; + /// Represents supported key wrap algorithm for ECC - as defined in [RFC 5753 Section 7.1.5]. /// /// As per [RFC 5753 Section 8]: @@ -37,44 +50,13 @@ use spki::AlgorithmIdentifierOwned; /// /// [RFC 5753 Section 8]: https://datatracker.ietf.org/doc/html/rfc5753#section-8 /// [RFC 5753 Section 7.1.5]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.1.5 -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum KeyWrapAlgorithm { - /// id-aes128-wrap - Aes128, - /// id-aes192-wrap - Aes192, - /// id-aes256-wrap - Aes256, -} -impl KeyWrapAlgorithm { - /// Return the Object Identifier (OID) of the algorithm. - /// - /// OID are defined in [RFC 3565 Section 2.3.2] - /// - /// [RFC 3565 Section 2.3.2]: - /// ```text - /// NIST has assigned the following OIDs to define the AES key wrap - /// algorithm. - /// - /// id-aes128-wrap OBJECT IDENTIFIER ::= { aes 5 } - /// id-aes192-wrap OBJECT IDENTIFIER ::= { aes 25 } - /// id-aes256-wrap OBJECT IDENTIFIER ::= { aes 45 } - /// - /// In all cases the parameters field MUST be absent. - /// ``` - /// - /// [RFC 3565 Section 2.3.2]: https://datatracker.ietf.org/doc/html/rfc3565#section-2.3.2 - fn oid(&self) -> ObjectIdentifier { - match self { - Self::Aes128 => const_oid::db::rfc5911::ID_AES_128_WRAP, - Self::Aes192 => const_oid::db::rfc5911::ID_AES_192_WRAP, - Self::Aes256 => const_oid::db::rfc5911::ID_AES_256_WRAP, - } - } +/// +/// Represents key wrap algorithms methods. +pub trait KeyWrapAlgorithm: AssociatedOid + KeySizeUser { + /// Return key size of the key-wrap algorithm in bits + fn key_size_in_bits() -> u32; - /// Return parameters of the algorithm to be used in the context of `AlgorithmIdentifierOwned`. - /// - /// It should be absent as defined in [RFC 3565 Section 2.3.2] and per usage in [RFC 5753 Section 7.2]. + /// Return algorithm identifier AlgorithmIdentifierOwned` associated with the key-wrap algorithm /// /// [RFC 3565 Section 2.3.2]: /// ```text @@ -90,210 +72,100 @@ impl KeyWrapAlgorithm { /// /// [RFC 3565 Section 2.3.2]: https://datatracker.ietf.org/doc/html/rfc3565#section-2.3.2 /// [RFC 5753 Section 7.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.2 - fn parameters(&self) -> Option { - match self { - Self::Aes128 => None, - Self::Aes192 => None, - Self::Aes256 => None, - } - } + fn algorithm_identifier() -> AlgorithmIdentifierOwned; - /// Return key size of the algorithm in number of bits - pub fn key_size_in_bits(&self) -> u32 { - match self { - Self::Aes128 => 128, - Self::Aes192 => 192, - Self::Aes256 => 256, - } - } -} -impl From for AlgorithmIdentifierOwned { - /// Convert a `KeyWrapAlgorithm` to the corresponding `AlgorithmIdentifierOwned`. - /// - /// Conversion is done according to [RFC 5753 Section 7.2]: - /// - /// - /// [RFC 5753 Section 7.2] - /// ```text - /// keyInfo contains the object identifier of the key-encryption - /// algorithm (used to wrap the CEK) and associated parameters. In - /// this specification, 3DES wrap has NULL parameters while the AES - /// wraps have absent parameters. - /// ``` - /// - /// [RFC 5753 Section 7.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.2 - fn from(kw_algo: KeyWrapAlgorithm) -> Self { - Self { - oid: kw_algo.oid(), - parameters: kw_algo.parameters(), - } - } -} -impl From for KeyWrapAlgorithm { - /// Convert a `ContentEncryptionAlgorithm` to a `KeyWrapAlgorithm`. - /// - /// Conversion is done matching encryption strength. - fn from(ce_algo: ContentEncryptionAlgorithm) -> Self { - match ce_algo { - ContentEncryptionAlgorithm::Aes128Cbc => Self::Aes128, - ContentEncryptionAlgorithm::Aes192Cbc => Self::Aes192, - ContentEncryptionAlgorithm::Aes256Cbc => Self::Aes256, - } - } + /// Return an empty wrapping key (KEK) with the adequate size to be used with aes-key-wrap + fn init_kek() -> Key; + + /// Return an empty wrapped key with the adequate size to be used with aes-key-wrap + fn init_wrapped() -> WrappedKey + where + T: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add; + + /// Try to wrap some data using given wrapping key + fn try_wrap(key: &Key, data: &[u8], out: &mut [u8]) -> Result<()>; } -/// This struct can be used to perform key wrapping operation. +/// Struct representing a wrapped key /// -/// It abstracts some of the key-wrapping logic over incoming wrapping-key and outgoing wrapped-key of different sizes. -/// It currently implements: -/// - try_new() - initialize a key wrapper with right sized depending on KeyWrapAlgorithm and key-to-wrap size -/// - try_wrap() - wrap a key with the corresponding aes-key-wrap algorithms -/// -/// # Note -/// For convenience KeyWrapper can: -/// - yield the inner wrapping-key as a mutable reference (e.g. to use with a KDF) -/// - convert to Vec to obtain Owned data to the wrapped key -#[derive(Debug, Clone, Copy)] -pub struct KeyWrapper { - /// Wrapping key - wrapping_key: WrappingKey, - /// Wrapped key - wrapped_key: WrappedKey, +/// Can be used to abstract wrapped key over different incoming key sizes. +pub struct WrappedKey +where + T: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add, +{ + inner: Array>, } -impl KeyWrapper { - /// Initialize a new KeyWrapper based on `KeyWrapAlgorithm` and key-to-wrap size. - pub(in crate::builder) fn try_new(kw_algo: &KeyWrapAlgorithm, key_size: usize) -> Result { - let wrapped_key = WrappedKey::try_from(key_size)?; - let wrapping_key = WrappingKey::from(kw_algo); - - Ok(Self { - wrapping_key, - wrapped_key, - }) - } - /// Wraps a given key. - /// - /// This function attempts to wrap the provided `target_key`. - /// - /// # Arguments - /// * `target_key` - A slice of bytes representing the key to be wrapped. - pub(in crate::builder) fn try_wrap(&mut self, target_key: &[u8]) -> Result<()> { - match self.wrapping_key { - WrappingKey::Aes128(wrap_key) => Kek::from(wrap_key) - .wrap(target_key, self.wrapped_key.as_mut()) - .map_err(|_| { - Error::Builder(String::from( - "could not wrap key with Aes128 key wrap algorithm", - )) - }), - WrappingKey::Aes192(kek) => Kek::from(kek) - .wrap(target_key, self.wrapped_key.as_mut()) - .map_err(|_| { - Error::Builder(String::from( - "could not wrap key with Aes192 key wrap algorithm", - )) - }), - WrappingKey::Aes256(kek) => Kek::from(kek) - .wrap(target_key, self.wrapped_key.as_mut()) - .map_err(|_| { - Error::Builder(String::from( - "could not wrap key with Aes256 key wrap algorithm", - )) - }), - } - } -} -impl AsMut<[u8]> for KeyWrapper { + +impl AsMut<[u8]> for WrappedKey +where + T: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add, +{ fn as_mut(&mut self) -> &mut [u8] { - self.wrapping_key.as_mut() + self.inner.as_mut() } } -impl From for Vec { - fn from(wrapper: KeyWrapper) -> Self { - Self::from(wrapper.wrapped_key) + +impl From> for Vec +where + T: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add, +{ + fn from(wrapped_key: WrappedKey) -> Self { + wrapped_key.inner.to_vec() } } -/// Represents a wrapping key to be used by [KeyWrapper] -/// -/// This type can be used to abstract over wrapping-key material of different size. -/// The following wrapping key type are currently supported: -/// - Aes128 -/// - Aes192 -/// - Aes256 -/// -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum WrappingKey { - /// id-aes128-wrap - Aes128([u8; 16]), - /// id-aes192-wrap - Aes192([u8; 24]), - /// id-aes256-wrap - Aes256([u8; 32]), -} -impl From<&KeyWrapAlgorithm> for WrappingKey { - fn from(kw_algo: &KeyWrapAlgorithm) -> Self { - match kw_algo { - KeyWrapAlgorithm::Aes128 => Self::Aes128([0u8; 16]), - KeyWrapAlgorithm::Aes192 => Self::Aes192([0u8; 24]), - KeyWrapAlgorithm::Aes256 => Self::Aes256([0u8; 32]), - } +impl KeyWrapAlgorithm for AesKw +where + AesWrap: KeyInit + BlockSizeUser + BlockCipherEncrypt + BlockCipherDecrypt, + AesKw: AssociatedOid + KeyInit, +{ + fn key_size_in_bits() -> u32 { + AesWrap::KeySize::U32 * 8u32 } -} -impl AsMut<[u8]> for WrappingKey { - fn as_mut(&mut self) -> &mut [u8] { - match self { - Self::Aes128(key) => key, - Self::Aes192(key) => key, - Self::Aes256(key) => key, + + fn algorithm_identifier() -> AlgorithmIdentifierOwned { + AlgorithmIdentifierOwned { + oid: Self::OID, + parameters: None, } } -} -/// Represents a wrapped key to be used by [KeyWrapper] -/// -/// This type can be used to abstract over wrapped key of different size for aes-key-wrap algorithms. -/// It currently supports the following incoming key size: -/// - 16 -/// - 24 -/// - 32 -#[derive(Debug, Clone, Copy)] -enum WrappedKey { - Aes128([u8; 24]), - Aes192([u8; 32]), - Aes256([u8; 40]), -} -impl TryFrom for WrappedKey { - type Error = Error; - fn try_from(key_size: usize) -> Result { - match key_size { - 16 => Ok(Self::Aes128([0u8; 24])), - 24 => Ok(Self::Aes192([0u8; 32])), - 32 => Ok(Self::Aes256([0u8; 40])), - _ => Err(Error::Builder(String::from( - "could not wrap key: key size is not supported", - ))), - } + + fn init_kek() -> Key { + Key::::default() } -} -impl AsMut<[u8]> for WrappedKey { - fn as_mut(&mut self) -> &mut [u8] { - match self { - Self::Aes128(key) => key, - Self::Aes192(key) => key, - Self::Aes256(key) => key, + + fn init_wrapped() -> WrappedKey + where + AesEnc: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add, + { + WrappedKey:: { + inner: Array::>::default(), } } -} -impl From for Vec { - fn from(key: WrappedKey) -> Self { - match key { - WrappedKey::Aes128(arr) => arr.to_vec(), - WrappedKey::Aes192(arr) => arr.to_vec(), - WrappedKey::Aes256(arr) => arr.to_vec(), + + fn try_wrap(key: &Key, data: &[u8], out: &mut [u8]) -> Result<()> { + let kek = AesKw::new(key); + let res = kek + .wrap_key(data, out) + .map_err(|_| Error::Builder(String::from("could not wrap key")))?; + if res.len() != out.len() { + return Err(Error::Builder(String::from("output buffer invalid size"))); } + Ok(()) } } +/* #[cfg(test)] mod tests { @@ -569,3 +441,4 @@ mod tests { assert_eq!(vec.len(), 40); } } +*/ diff --git a/cms/tests/builder/kari.rs b/cms/tests/builder/kari.rs index ff5a7b186..1e7e6927d 100644 --- a/cms/tests/builder/kari.rs +++ b/cms/tests/builder/kari.rs @@ -1,7 +1,8 @@ +use aes_kw::AesKw; use cms::{ builder::{ ContentEncryptionAlgorithm, DhSinglePassStdDhKdf, EcKeyEncryptionInfo, - EnvelopedDataBuilder, KeyAgreeRecipientInfoBuilder, KeyWrapAlgorithm, + EnvelopedDataBuilder, KeyAgreeRecipientInfoBuilder, }, cert::IssuerAndSerialNumber, content_info::ContentInfo, @@ -40,14 +41,18 @@ fn test_build_enveloped_data_ec() { let recipient_public_key = recipient_private_key.public_key(); // KARI builder - let kari_builder = - KeyAgreeRecipientInfoBuilder::>::new( - None, - key_agreement_recipient_identifier, - EcKeyEncryptionInfo::Ec(recipient_public_key), - KeyWrapAlgorithm::Aes128, - ) - .expect("Could not create a KeyAgreeRecipientInfoBuilder"); + let kari_builder = KeyAgreeRecipientInfoBuilder::< + OsRng, + _, + DhSinglePassStdDhKdf, + AesKw, + aes::Aes128, + >::new( + None, + key_agreement_recipient_identifier, + EcKeyEncryptionInfo::Ec(recipient_public_key), + ) + .expect("Could not create a KeyAgreeRecipientInfoBuilder"); // Enveloped data builder let mut rng = OsRng;