diff --git a/cryptoki/src/mechanism/elliptic_curve.rs b/cryptoki/src/mechanism/elliptic_curve.rs index 69e43c9f..bfdb6c81 100644 --- a/cryptoki/src/mechanism/elliptic_curve.rs +++ b/cryptoki/src/mechanism/elliptic_curve.rs @@ -82,7 +82,45 @@ pub struct EcKdf<'a> { shared_data: Option<&'a [u8]>, } -impl EcKdf<'_> { +macro_rules! ansi { + { $func_name: ident, $algo: ident, $algo_name: literal } => { + #[doc = "The key derivation function based on "] + #[doc = $algo_name] + #[doc = " as defined in the ANSI X9.63 standard. The + derived key is produced by concatenating hashes of + the shared value followed by 00000001, 00000002, + etc. until we find enough bytes to fill the + `CKA_VALUE_LEN` of the derived key."] + pub fn $func_name(shared_data: &'a [u8]) -> Self { + Self { + kdf_type: $algo, + shared_data: Some(shared_data), + } + } + } +} + +macro_rules! sp800 { + { $func_name: ident, $algo: ident, $algo_name: literal } => { + #[doc = "The key derivation function based on "] + #[doc = $algo_name] + #[doc = " as defined in the [NIST SP800-56A standard, revision + 2](http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf), + section 5.8.1.1. The derived key is produced by + concatenating hashes of 00000001, 00000002, + etc. followed by the shared value until we find + enough bytes to fill the `CKA_VALUE_LEN` of the + derived key."] + pub fn $func_name(shared_data: &'a [u8]) -> Self { + Self { + kdf_type: $algo, + shared_data: Some(shared_data), + } + } + } +} + +impl<'a> EcKdf<'a> { /// The null transformation. The derived key value is produced by /// taking bytes from the left of the agreed value. The new key /// size is limited to the size of the agreed value. @@ -93,16 +131,25 @@ impl EcKdf<'_> { } } - /// The key derivation function based on sha256 as defined in the ANSI X9.63 standard. The - /// derived key is produced by concatenating hashes of the shared - /// value followed by 00000001, 00000002, etc. until we find - /// enough bytes to fill the `CKA_VALUE_LEN` of the derived key. - pub fn sha256() -> Self { - Self { - kdf_type: CKD_SHA256_KDF, - shared_data: None, - } - } + ansi!(sha1, CKD_SHA1_KDF, "SHA1"); + ansi!(sha224, CKD_SHA224_KDF, "SHA224"); + ansi!(sha256, CKD_SHA256_KDF, "SHA256"); + ansi!(sha384, CKD_SHA384_KDF, "SHA384"); + ansi!(sha512, CKD_SHA512_KDF, "SHA512"); + ansi!(sha3_224, CKD_SHA3_224_KDF, "SHA3_224"); + ansi!(sha3_256, CKD_SHA3_256_KDF, "SHA3_256"); + ansi!(sha3_384, CKD_SHA3_384_KDF, "SHA3_384"); + ansi!(sha3_512, CKD_SHA3_512_KDF, "SHA3_512"); + + sp800!(sha1_sp800, CKD_SHA1_KDF_SP800, "SHA1"); + sp800!(sha224_sp800, CKD_SHA224_KDF_SP800, "SHA224"); + sp800!(sha256_sp800, CKD_SHA256_KDF_SP800, "SHA256"); + sp800!(sha384_sp800, CKD_SHA384_KDF_SP800, "SHA384"); + sp800!(sha512_sp800, CKD_SHA512_KDF_SP800, "SHA512"); + sp800!(sha3_224_sp800, CKD_SHA3_224_KDF_SP800, "SHA3_224"); + sp800!(sha3_256_sp800, CKD_SHA3_256_KDF_SP800, "SHA3_256"); + sp800!(sha3_384_sp800, CKD_SHA3_384_KDF_SP800, "SHA3_384"); + sp800!(sha3_512_sp800, CKD_SHA3_512_KDF_SP800, "SHA3_512"); // The intention here is to be able to support other methods with // shared data, without it being a breaking change, by just adding diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index 2cd3d4ab..89607c95 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 mod common; -use crate::common::{get_pkcs11, is_softhsm, SO_PIN, USER_PIN}; +use crate::common::{get_firmware_version, get_pkcs11, is_kryoptic, is_softhsm, SO_PIN, USER_PIN}; use common::init_pins; use cryptoki::context::Function; use cryptoki::error::{Error, RvError}; @@ -687,6 +687,94 @@ fn derive_key() -> TestResult { Ok(()) } +#[test] +#[serial] +fn derive_key_sp800() -> TestResult { + if is_softhsm() { + return Ok(()); + } + + use cryptoki::mechanism::elliptic_curve::*; + + let (pkcs11, slot) = init_pins(); + + if is_kryoptic() { + let (major, minor) = get_firmware_version(&pkcs11, slot); + // Kryoptic added support for sha256_sp800 in version 1.3. + if !(major > 1 || minor >= 3) { + eprintln!( + "Skipping test: Kryoptic is too old (need 1.3, got {}.{})", + major, minor + ); + return Ok(()); + } + } + + // open a session + let session = pkcs11.open_rw_session(slot)?; + + // log in the session + session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?; + + // sha256_sp800 + let key = hex::decode("F00670C5F139E7E6C2511EA04FF507AFBBE237CE3C71A89CA59A1C5CF8856562") + .expect("valid hex"); + let ephemeral_key = + hex::decode("533B3F09E53B3DEED661A13F7A7D9694AB71CE156C728E00DEE87A1EE3A14C4A") + .expect("valid hex"); + let kdf_param = hex::decode("0A2B0601040197550105011203010807416E6F6E796D6F75732053656E646572202020205633A4C5AE4305BC0FE2ABB699A8EE54632790A0").expect("valid hex"); + let derivation = hex::decode("AF8CE51D0139A6D60831A9BABAB20186").expect("valid hex"); + + let template = [ + Attribute::Class(ObjectClass::PRIVATE_KEY), + Attribute::KeyType(KeyType::EC_MONTGOMERY), + Attribute::EcParams(vec![ + 0x13, 0x0a, 0x63, 0x75, 0x72, 0x76, 0x65, 0x32, 0x35, 0x35, 0x31, 0x39, + ]), + Attribute::Value(key), + Attribute::Id(b"foo".to_vec()), + Attribute::Label(b"bar".to_vec()), + Attribute::Sensitive(true), + Attribute::Token(true), + Attribute::Derive(true), + ]; + + let private = session.create_object(&template)?; + + let kdf = EcKdf::sha256_sp800(&kdf_param); + + let params = Ecdh1DeriveParams::new(kdf, &ephemeral_key); + + let shared_secret = session.derive_key( + &Mechanism::Ecdh1Derive(params), + private, + &[ + Attribute::Class(ObjectClass::SECRET_KEY), + Attribute::KeyType(KeyType::GENERIC_SECRET), + Attribute::ValueLen(Ulong::new(derivation.len().try_into().unwrap())), + Attribute::Sensitive(false), + Attribute::Extractable(true), + Attribute::Token(false), + ], + )?; + + let value_attribute = session + .get_attributes(shared_secret, &[AttributeType::Value])? + .remove(0); + let value = if let Attribute::Value(value) = value_attribute { + value + } else { + panic!("Expected value attribute."); + }; + + assert_eq!(value, derivation); + + // delete keys + session.destroy_object(private)?; + + Ok(()) +} + #[test] #[serial] fn import_export() -> TestResult { diff --git a/cryptoki/tests/common.rs b/cryptoki/tests/common.rs index 65be5c60..e5b6973e 100644 --- a/cryptoki/tests/common.rs +++ b/cryptoki/tests/common.rs @@ -20,6 +20,10 @@ pub fn is_softhsm() -> bool { get_pkcs11_path().contains("softhsm") } +pub fn is_kryoptic() -> bool { + get_pkcs11_path().contains("kryoptic") +} + pub fn get_pkcs11() -> Pkcs11 { Pkcs11::new(get_pkcs11_path()).unwrap() } @@ -46,3 +50,10 @@ pub fn init_pins() -> (Pkcs11, Slot) { (pkcs11, slot) } + +pub fn get_firmware_version(pkcs11: &Pkcs11, slot: Slot) -> (u8, u8) { + let info = pkcs11.get_slot_info(slot).unwrap(); + + let v = info.firmware_version(); + (v.major(), v.minor()) +}