From 48c7bcc30ca3faacd02054949a4cf42bb65b0e07 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Mon, 24 Aug 2026 13:28:43 +0300 Subject: [PATCH 1/2] `pkcs5`: add `belt-kwp` support, tweak iv parsing in pbes2 --- Cargo.lock | 29 ++++++++++++ Cargo.toml | 2 + pkcs5/CHANGELOG.md | 6 +++ pkcs5/Cargo.toml | 3 ++ pkcs5/src/pbes2.rs | 88 ++++++++++++++++++++++++++--------- pkcs5/src/pbes2/encryption.rs | 27 +++++++++++ pkcs5/src/pbes2/kdf.rs | 32 +++++++++++++ pkcs5/tests/encryption.rs | 49 +++++++++++++++++++ pkcs5/tests/pbes2.rs | 43 +++++++++++++++++ 9 files changed, 257 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb9acff68..7f47d8afc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -133,6 +133,33 @@ dependencies = [ "proptest", ] +[[package]] +name = "belt-block" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0304188fd8684b910d24cae451c724cd5140a037c407e696247e94bb57b06434" +dependencies = [ + "cipher", +] + +[[package]] +name = "belt-hash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84605d8024cc6794dc339947b933532675297372feef3ce1f45feeaa8da35fb8" +dependencies = [ + "belt-block", + "digest", +] + +[[package]] +name = "belt-kwp" +version = "0.2.0" +source = "git+https://github.com/makavity/key-wraps.git#d6da937ed135db2296f95f0f851800fff04b751d" +dependencies = [ + "belt-block", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -1038,6 +1065,8 @@ version = "0.8.1" dependencies = [ "aes", "aes-gcm", + "belt-hash", + "belt-kwp", "cbc", "der", "des", diff --git a/Cargo.toml b/Cargo.toml index 76339a17c..f1944e970 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,8 @@ tls_codec_derive = { path = "./tls_codec/derive" } x509-tsp = { path = "./x509-tsp" } x509-cert = { path = "./x509-cert" } x509-ocsp = { path = "./x509-ocsp" } +# TODO: Remove after https://github.com/RustCrypto/key-wraps/pull/98 +belt-kwp = { git = "https://github.com/makavity/key-wraps.git" } [workspace.lints.clippy] borrow_as_ptr = "warn" diff --git a/pkcs5/CHANGELOG.md b/pkcs5/CHANGELOG.md index 729b01546..87ed857e4 100644 --- a/pkcs5/CHANGELOG.md +++ b/pkcs5/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +### Added +- Support for using BELT-KWP with PBES2 ([#2408]) + +[#98]: https://github.com/RustCrypto/formats/pull/2408 + ## 0.8.1 (2026-06-28) ### Added - Support for using AES-GCM with PBES2 ([#1433], [#2313]) diff --git a/pkcs5/Cargo.toml b/pkcs5/Cargo.toml index bb2338d6e..b07803294 100644 --- a/pkcs5/Cargo.toml +++ b/pkcs5/Cargo.toml @@ -23,6 +23,8 @@ spki = "0.8" cbc = { version = "0.2", optional = true } aes = { version = "0.9", optional = true, default-features = false } aes-gcm = { version = "0.11", optional = true, default-features = false, features = ["aes"] } +belt-hash = { version = "0.2", optional = true, default-features = false } +belt-kwp = { version = "0.2", optional = true, default-features = false } des = { version = "0.9", optional = true, default-features = false } pbkdf2 = { version = "0.13", optional = true, default-features = false, features = ["hmac"] } getrandom = { version = "0.4", optional = true, features = ["sys_rng"] } @@ -39,6 +41,7 @@ alloc = [] 3des = ["dep:des", "pbes2"] des-insecure = ["dep:des", "pbes2"] +belt = ["dep:belt-hash", "dep:belt-kwp", "pbes2"] getrandom = ["dep:getrandom", "rand_core"] pbes2 = ["dep:aes", "dep:cbc", "dep:pbkdf2", "dep:scrypt", "dep:sha2", "dep:aes-gcm"] rand_core = ["dep:rand_core"] diff --git a/pkcs5/src/pbes2.rs b/pkcs5/src/pbes2.rs index f896be4a6..15c135c71 100644 --- a/pkcs5/src/pbes2.rs +++ b/pkcs5/src/pbes2.rs @@ -55,6 +55,11 @@ pub const DES_CBC_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.14.3 #[cfg(feature = "3des")] pub const DES_EDE3_CBC_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.3.7"); +/// `belt-kwp256` key wrap algorithm as defined in STB 34.101.31 Section 7.2. +#[cfg(feature = "belt")] +pub const BELT_KWP_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.31.73"); + /// Password-Based Encryption Scheme 2 (PBES2) OID. /// /// @@ -185,6 +190,19 @@ impl Parameters { Ok(Self { kdf, encryption }) } + /// Initialize PBES2 parameters using PBKDF2-HMAC-HBELT as the + /// password-based key derivation function and `belt-kwp256` as the key wrap + /// algorithm, as used by the STB 34.101.78 (`bpki`) private key container. + /// + /// # Errors + /// Propagates errors from [`Pbkdf2Params::hmac_hbelt`]. + #[cfg(feature = "belt")] + pub fn pbkdf2_hmac_hbelt_belt_kwp(pbkdf2_iterations: u32, pbkdf2_salt: &[u8]) -> Result { + let kdf = Pbkdf2Params::hmac_hbelt(pbkdf2_iterations, pbkdf2_salt)?.into(); + let encryption = EncryptionScheme::BeltKwp; + Ok(Self { kdf, encryption }) + } + /// Generate PBES2 parameters using scrypt as the password hashing /// algorithm, using that algorithm's recommended algorithm settings /// along with a randomly generated salt and IV. @@ -463,6 +481,10 @@ pub enum EncryptionScheme { /// Initialisation vector iv: [u8; DES_BLOCK_SIZE], }, + + /// BELT-KWP + #[cfg(feature = "belt")] + BeltKwp, } impl EncryptionScheme { @@ -479,6 +501,8 @@ impl EncryptionScheme { Self::DesCbc { .. } => 8, #[cfg(feature = "3des")] Self::DesEde3Cbc { .. } => 24, + #[cfg(feature = "belt")] + Self::BeltKwp => 32, } } @@ -495,6 +519,8 @@ impl EncryptionScheme { Self::DesCbc { .. } => DES_CBC_OID, #[cfg(feature = "3des")] Self::DesEde3Cbc { .. } => DES_EDE3_CBC_OID, + #[cfg(feature = "belt")] + Self::BeltKwp => BELT_KWP_OID, } } @@ -515,40 +541,55 @@ impl<'a> Decode<'a> for EncryptionScheme { } } +/// Decode an IV/nonce of exactly `N` bytes from an `AlgorithmIdentifier`'s +/// OCTET STRING parameters. +fn decode_iv(params: Option>) -> der::Result<[u8; N]> { + params + .ok_or_else(|| Tag::OctetString.value_error())? + .decode_as::<&OctetStringRef>()? + .as_bytes() + .try_into() + .map_err(|_| Tag::OctetString.value_error().into()) +} + impl TryFrom> for EncryptionScheme { type Error = der::Error; fn try_from(alg: AlgorithmIdentifierRef<'_>) -> der::Result { // TODO(tarcieri): support for non-AES algorithms? - let iv = match alg.parameters { - Some(params) => params.decode_as::<&OctetStringRef>()?.as_bytes(), - None => return Err(Tag::OctetString.value_error().into()), - }; - match alg.oid { AES_128_CBC_OID => Ok(Self::Aes128Cbc { - iv: iv.try_into().map_err(|_| Tag::OctetString.value_error())?, + iv: decode_iv(alg.parameters)?, }), AES_192_CBC_OID => Ok(Self::Aes192Cbc { - iv: iv.try_into().map_err(|_| Tag::OctetString.value_error())?, + iv: decode_iv(alg.parameters)?, }), AES_256_CBC_OID => Ok(Self::Aes256Cbc { - iv: iv.try_into().map_err(|_| Tag::OctetString.value_error())?, + iv: decode_iv(alg.parameters)?, }), AES_128_GCM_OID => Ok(Self::Aes128Gcm { - nonce: iv.try_into().map_err(|_| Tag::OctetString.value_error())?, + nonce: decode_iv(alg.parameters)?, }), AES_256_GCM_OID => Ok(Self::Aes256Gcm { - nonce: iv.try_into().map_err(|_| Tag::OctetString.value_error())?, + nonce: decode_iv(alg.parameters)?, }), #[cfg(feature = "des-insecure")] DES_CBC_OID => Ok(Self::DesCbc { - iv: iv.try_into().map_err(|_| Tag::OctetString.value_error())?, + iv: decode_iv(alg.parameters)?, }), #[cfg(feature = "3des")] DES_EDE3_CBC_OID => Ok(Self::DesEde3Cbc { - iv: iv.try_into().map_err(|_| Tag::OctetString.value_error())?, + iv: decode_iv(alg.parameters)?, }), + // `belt-kwp` has no IV: STB 34.101.78 encodes NULL parameters. + #[cfg(feature = "belt")] + BELT_KWP_OID => { + if let Some(params) = alg.parameters { + params.decode_as::<()>()?; + } + + Ok(Self::BeltKwp) + } oid => Err(ErrorKind::OidUnknown { oid }.into()), } } @@ -558,21 +599,24 @@ impl<'a> TryFrom<&'a EncryptionScheme> for AlgorithmIdentifierRef<'a> { type Error = der::Error; fn try_from(scheme: &'a EncryptionScheme) -> der::Result { - let parameters = OctetStringRef::new(match scheme { - EncryptionScheme::Aes128Cbc { iv } => iv.as_slice(), - EncryptionScheme::Aes192Cbc { iv } => iv.as_slice(), - EncryptionScheme::Aes256Cbc { iv } => iv.as_slice(), - EncryptionScheme::Aes128Gcm { nonce } => nonce.as_slice(), - EncryptionScheme::Aes256Gcm { nonce } => nonce.as_slice(), + let parameters = match scheme { + EncryptionScheme::Aes128Cbc { iv } => OctetStringRef::new(iv)?.into(), + EncryptionScheme::Aes192Cbc { iv } => OctetStringRef::new(iv)?.into(), + EncryptionScheme::Aes256Cbc { iv } => OctetStringRef::new(iv)?.into(), + EncryptionScheme::Aes128Gcm { nonce } => OctetStringRef::new(nonce)?.into(), + EncryptionScheme::Aes256Gcm { nonce } => OctetStringRef::new(nonce)?.into(), #[cfg(feature = "des-insecure")] - EncryptionScheme::DesCbc { iv } => iv.as_slice(), + EncryptionScheme::DesCbc { iv } => OctetStringRef::new(iv)?.into(), #[cfg(feature = "3des")] - EncryptionScheme::DesEde3Cbc { iv } => iv.as_slice(), - })?; + EncryptionScheme::DesEde3Cbc { iv } => OctetStringRef::new(iv)?.into(), + // `belt-kwp` has no IV and encodes NULL parameters (STB 34.101.78). + #[cfg(feature = "belt")] + EncryptionScheme::BeltKwp => AnyRef::NULL, + }; Ok(AlgorithmIdentifierRef { oid: scheme.oid(), - parameters: Some(parameters.into()), + parameters: Some(parameters), }) } } diff --git a/pkcs5/src/pbes2/encryption.rs b/pkcs5/src/pbes2/encryption.rs index 426f3d62f..593cc3939 100644 --- a/pkcs5/src/pbes2/encryption.rs +++ b/pkcs5/src/pbes2/encryption.rs @@ -3,6 +3,8 @@ use super::{EncryptionScheme, Kdf, Parameters, Pbkdf2Params, Pbkdf2Prf, ScryptParams}; use crate::{Error, Result}; use aes_gcm::{KeyInit as GcmKeyInit, Nonce, Tag, aead::AeadInOut}; +#[cfg(feature = "belt")] +use belt_hash::BeltHash; use cbc::cipher::{ BlockCipherDecrypt, BlockCipherEncrypt, BlockModeDecrypt, BlockModeEncrypt, KeyInit, KeyIvInit, block_padding::Pkcs7, @@ -136,6 +138,10 @@ pub fn encrypt_in_place<'b>( EncryptionScheme::DesCbc { .. } => Err(Error::UnsupportedAlgorithm { oid: super::DES_CBC_OID, }), + #[cfg(feature = "belt")] + EncryptionScheme::BeltKwp => belt_kwp(&key)? + .wrap_key_in_place(buf, pos, &BELT_KWP_HEADER) + .map_err(|_| Error::EncryptFailed), } } @@ -162,9 +168,24 @@ pub fn decrypt_in_place<'a>( EncryptionScheme::DesEde3Cbc { iv } => cbc_decrypt::(es, key, &iv, buf), #[cfg(feature = "des-insecure")] EncryptionScheme::DesCbc { iv } => cbc_decrypt::(es, key, &iv, buf), + #[cfg(feature = "belt")] + EncryptionScheme::BeltKwp => belt_kwp(&key)? + .unwrap_key_in_place(buf, &BELT_KWP_HEADER) + .map_err(|_| Error::DecryptFailed), } } +/// The `belt-kwp` header `I`, which STB 34.101.78 fixes to `0^128`. +#[cfg(feature = "belt")] +const BELT_KWP_HEADER: [u8; belt_kwp::IV_LEN] = [0u8; belt_kwp::IV_LEN]; + +/// Build a `belt-kwp` instance from the derived key. +#[cfg(feature = "belt")] +fn belt_kwp(key: &EncryptionKey) -> Result { + belt_kwp::BeltKwp::new_from_slice(key.as_slice()) + .map_err(|_| EncryptionScheme::BeltKwp.to_alg_params_invalid()) +} + /// Encryption key as derived by PBKDF2 // TODO(tarcieri): zeroize? struct EncryptionKey { @@ -217,6 +238,12 @@ impl EncryptionKey { pbkdf2_params, key_size, ), + #[cfg(feature = "belt")] + Pbkdf2Prf::HmacHbelt => EncryptionKey::derive_with_pbkdf2::( + password, + pbkdf2_params, + key_size, + ), }; Ok(key) diff --git a/pkcs5/src/pbes2/kdf.rs b/pkcs5/src/pbes2/kdf.rs index 59bee4133..5b30ade8c 100644 --- a/pkcs5/src/pbes2/kdf.rs +++ b/pkcs5/src/pbes2/kdf.rs @@ -33,6 +33,11 @@ pub const HMAC_WITH_SHA384_OID: ObjectIdentifier = pub const HMAC_WITH_SHA512_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.2.11"); +/// HMAC-HBELT (for use with PBKDF2) +#[cfg(feature = "belt")] +pub const HMAC_WITH_HBELT_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.47.12"); + /// `id-scrypt` ([RFC 7914]) /// /// [RFC 7914]: https://datatracker.ietf.org/doc/html/rfc7914#section-7 @@ -236,6 +241,25 @@ impl Pbkdf2Params { prf: Pbkdf2Prf::HmacWithSha256, }) } + + /// Initialize PBKDF2-HMAC-HBELT with the given iteration count and salt. + /// + /// # Errors + /// Returns [`Error::AlgorithmParametersInvalid`] if `iteration_count` exceeds + /// [`Pbkdf2Params::MAX_ITERATION_COUNT`] or `salt` exceeds [`Salt::MAX_LEN`]. + #[cfg(feature = "belt")] + pub fn hmac_hbelt(iteration_count: u32, salt: &[u8]) -> Result { + if iteration_count > Self::MAX_ITERATION_COUNT { + return Err(Self::INVALID_ERR); + } + + Ok(Self { + salt: salt.try_into().map_err(|_| Self::INVALID_ERR)?, + iteration_count, + key_length: None, + prf: Pbkdf2Prf::HmacHbelt, + }) + } } impl<'a> DecodeValue<'a> for Pbkdf2Params { @@ -310,6 +334,10 @@ pub enum Pbkdf2Prf { /// HMAC with SHA-512 HmacWithSha512, + + /// HMAC with HBELT + #[cfg(feature = "belt")] + HmacHbelt, } impl Pbkdf2Prf { @@ -322,6 +350,8 @@ impl Pbkdf2Prf { Self::HmacWithSha256 => HMAC_WITH_SHA256_OID, Self::HmacWithSha384 => HMAC_WITH_SHA384_OID, Self::HmacWithSha512 => HMAC_WITH_SHA512_OID, + #[cfg(feature = "belt")] + Self::HmacHbelt => HMAC_WITH_HBELT_OID, } } } @@ -357,6 +387,8 @@ impl TryFrom> for Pbkdf2Prf { HMAC_WITH_SHA256_OID => Ok(Self::HmacWithSha256), HMAC_WITH_SHA384_OID => Ok(Self::HmacWithSha384), HMAC_WITH_SHA512_OID => Ok(Self::HmacWithSha512), + #[cfg(feature = "belt")] + HMAC_WITH_HBELT_OID => Ok(Self::HmacHbelt), oid => Err(ErrorKind::OidUnknown { oid }.into()), } } diff --git a/pkcs5/tests/encryption.rs b/pkcs5/tests/encryption.rs index d58e5cf2f..e10ccf986 100644 --- a/pkcs5/tests/encryption.rs +++ b/pkcs5/tests/encryption.rs @@ -91,9 +91,40 @@ const ED25519_PKCS8_KEY_CIPHERTEXT_DESCBC: &[u8] = &hex!( 93E4E3893840181FBC63D75297B416A0B96CB7F9AB45CEABA" ); +/// PBES2 + PBKDF2-HMAC-HBELT + belt-kwp256 `AlgorithmIdentifier`, taken from the +/// STB 34.101.78 (`bpki`) private key container `cmd/test/zed.sk` shipped with +/// [bee2], the reference implementation. +/// +/// [bee2]: https://github.com/agievich/bee2 +#[cfg(feature = "belt")] +const PBES2_PBKDF2_HBELT_BELT_KWP_ALG_ID: &[u8] = &hex!( + "304806092a864886f70d01050d303b302a06092a864886f70d01050c301d0408 + 0c7f910ebb68478502022710300d06092a7000020022652f0c0500300d06092a + 7000020022651f490500" +); + +/// Plaintext of the `bign-curve256v1` PKCS#8 private key wrapped by `zed.sk`. +#[cfg(feature = "belt")] +const BIGN_PKCS8_KEY_PLAINTEXT: &[u8] = &hex!( + "303f0201003018060a2a7000020022652d0201060a2a7000020022652d030104 + 200100000000000000000000000000000000000000000000000000000000000000" +); + +/// `encryptedData` of `zed.sk`, i.e. the above key wrapped with belt-kwp256. +#[cfg(feature = "belt")] +const BIGN_PKCS8_KEY_CIPHERTEXT_BELT_KWP: &[u8] = &hex!( + "a3f18c851870081760a74c01d34581e45e9dd333cda723fed51e4525d81eb91b + 02742e8ce72906e3dafb40e450b6e989bc832daa0bdd9b50128a10cca5e052d4 + bb4383ef9b96d6af48830820fefe7cdc58" +); + /// Password used to encrypt the keys. const PASSWORD: &[u8] = b"hunter42"; // Bad password; don't actually use outside tests! +/// Password protecting the `zed.sk` container. +#[cfg(feature = "belt")] +const BELT_PASSWORD: &[u8] = b"zed"; + #[test] fn decrypt_pbes2_pbkdf2_sha256_aes256cbc() { let scheme = pkcs5::EncryptionScheme::try_from(PBES2_PBKDF2_SHA256_AES256CBC_ALG_ID).unwrap(); @@ -127,3 +158,21 @@ fn decrypt_pbes2_pbkdf2_sha256_descbc() { let plaintext = scheme.decrypt_in_place(PASSWORD, &mut buffer).unwrap(); assert_eq!(plaintext, ED25519_PKCS8_KEY_PLAINTEXT); } + +#[test] +#[cfg(feature = "belt")] +fn encrypt_decrypt_pbes2_pbkdf2_hbelt_belt_kwp() { + let scheme = pkcs5::EncryptionScheme::try_from(PBES2_PBKDF2_HBELT_BELT_KWP_ALG_ID).unwrap(); + + let mut buffer = Vec::from(BIGN_PKCS8_KEY_CIPHERTEXT_BELT_KWP); + let plaintext = scheme.decrypt_in_place(BELT_PASSWORD, &mut buffer).unwrap(); + assert_eq!(plaintext, BIGN_PKCS8_KEY_PLAINTEXT); + + let pos = BIGN_PKCS8_KEY_PLAINTEXT.len(); + let mut buffer = Vec::from(BIGN_PKCS8_KEY_PLAINTEXT); + buffer.extend_from_slice(&[0u8; 16]); + let ciphertext = scheme + .encrypt_in_place(BELT_PASSWORD, &mut buffer, pos) + .unwrap(); + assert_eq!(ciphertext, BIGN_PKCS8_KEY_CIPHERTEXT_BELT_KWP); +} diff --git a/pkcs5/tests/pbes2.rs b/pkcs5/tests/pbes2.rs index 60f68411b..fb6554480 100644 --- a/pkcs5/tests/pbes2.rs +++ b/pkcs5/tests/pbes2.rs @@ -24,6 +24,18 @@ const PBES2_PBKDF2_SHA256_AES256CBC_ALG_ID: &[u8] = &hex!( 4801650304012a0410b2d02d78b2efd9dff694cf8e0af40925" ); +/// PBES2 + PBKDF2-HMAC-HBELT + belt-kwp256 `AlgorithmIdentifier`, taken from the +/// STB 34.101.78 (`bpki`) private key container `cmd/test/zed.sk` shipped with +/// [bee2], the reference implementation. The container's password is `zed`. +/// +/// [bee2]: https://github.com/agievich/bee2 +#[cfg(feature = "belt")] +const PBES2_PBKDF2_HBELT_BELT_KWP_ALG_ID: &[u8] = &hex!( + "304806092a864886f70d01050d303b302a06092a864886f70d01050c301d0408 + 0c7f910ebb68478502022710300d06092a7000020022652f0c0500300d06092a + 7000020022651f490500" +); + /// PBES2 + PBKDF2-SHA256 + AES-256-CBC `AlgorithmIdentifier` example without PRF NULL parameter. /// /// Generated by Smallstep CLI: `step certificate p12 out.p12 in.crt in.key`, extracted from PKCS#12. @@ -273,3 +285,34 @@ fn encode_pbes2_scrypt_aes256cbc() { let encoded_der = encoder.finish().unwrap(); assert_eq!(encoded_der, PBES2_SCRYPT_AES256CBC_ALG_ID); } + +/// Decoding test for PBES2 + PBKDF2-HMAC-HBELT + belt-kwp `AlgorithmIdentifier` +#[test] +#[cfg(feature = "belt")] +fn decode_pbes2_pbkdf2_hmac_hbelt_belt_kwp() { + let scheme = pkcs5::EncryptionScheme::try_from(PBES2_PBKDF2_HBELT_BELT_KWP_ALG_ID).unwrap(); + let params = scheme.pbes2().unwrap(); + + let pbkdf2_params = params.kdf.pbkdf2().unwrap(); + assert_eq!(pbkdf2_params.salt.as_bytes(), &hex!("0c7f910ebb684785")); + assert_eq!(pbkdf2_params.iteration_count, 10000); + assert_eq!(pbkdf2_params.key_length, None); + assert_eq!(pbkdf2_params.prf, pbes2::Pbkdf2Prf::HmacHbelt); + + // `belt-kwp` carries no IV: it is decoded from NULL parameters. + assert_eq!(params.encryption, pbes2::EncryptionScheme::BeltKwp); +} + +/// Encoding test for PBES2 + PBKDF2-HMAC-HBELT + belt-kwp `AlgorithmIdentifier` +#[test] +#[cfg(feature = "belt")] +fn encode_pbes2_pbkdf2_hmac_hbelt_belt_kwp() { + let mut buffer = [0u8; 1024]; + + let scheme = pkcs5::EncryptionScheme::try_from(PBES2_PBKDF2_HBELT_BELT_KWP_ALG_ID).unwrap(); + let mut encoder = der::SliceWriter::new(&mut buffer); + scheme.encode(&mut encoder).unwrap(); + + let encoded_der = encoder.finish().unwrap(); + assert_eq!(encoded_der, PBES2_PBKDF2_HBELT_BELT_KWP_ALG_ID); +} From 8e88e4a516d587b15a8ee4cc6ae5010ed04e3ba8 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Mon, 24 Aug 2026 13:29:54 +0300 Subject: [PATCH 2/2] `pkcs5`: fix changelog entry --- pkcs5/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkcs5/CHANGELOG.md b/pkcs5/CHANGELOG.md index 87ed857e4..f78a733b0 100644 --- a/pkcs5/CHANGELOG.md +++ b/pkcs5/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Support for using BELT-KWP with PBES2 ([#2408]) -[#98]: https://github.com/RustCrypto/formats/pull/2408 +[#2408]: https://github.com/RustCrypto/formats/pull/2408 ## 0.8.1 (2026-06-28) ### Added