From a1d7d18e1129932f972982a30730be90dbff58da Mon Sep 17 00:00:00 2001 From: James Hagborg Date: Tue, 6 Dec 2022 13:25:07 -0500 Subject: [PATCH] Add AES-GCM mechanism Co-authored-by: Wiktor Kwapisiewicz Signed-off-by: Wiktor Kwapisiewicz --- cryptoki/src/mechanism/aead.rs | 91 ++++++++++++++++++++++++++++++++++ cryptoki/src/mechanism/mod.rs | 13 +++++ cryptoki/tests/basic.rs | 65 ++++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 cryptoki/src/mechanism/aead.rs diff --git a/cryptoki/src/mechanism/aead.rs b/cryptoki/src/mechanism/aead.rs new file mode 100644 index 00000000..5c482e89 --- /dev/null +++ b/cryptoki/src/mechanism/aead.rs @@ -0,0 +1,91 @@ +// Copyright 2023 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +//! AEAD block cipher mechanism types + +use crate::types::Ulong; +use cryptoki_sys::*; +use std::convert::TryInto; +use std::marker::PhantomData; +use std::slice; + +/// Parameters for AES-GCM. +#[derive(Debug, Clone, Copy)] +#[repr(transparent)] +pub struct GcmParams<'a> { + inner: CK_GCM_PARAMS, + _marker: PhantomData<&'a [u8]>, +} + +impl<'a> GcmParams<'a> { + /// Construct GCM parameters. + /// + /// # Arguments + /// + /// `iv` - The initialization vector. This must be non-empty. In PKCS#11 + /// 2.40, the maximum length of the IV is 256 bytes. A 12-byte IV may be + /// processed more efficiently than other lengths. + /// + /// `aad` - The additional authenticated data. This data is authenticated + /// but not encrypted. This may be between 0 and 2^32-1 bytes. + /// + /// `tag_bits` - The length, in **bits**, of the authentication tag. Must + /// be between 0 and 128. The tag is appended to the end of the + /// ciphertext. + /// + /// # Panics + /// + /// This function panics if the length of `iv` or `aad` does not + /// fit into an [Ulong]. + pub fn new(iv: &'a [u8], aad: &'a [u8], tag_bits: Ulong) -> Self { + // The ulIvBits parameter seems to be missing from the 2.40 spec, + // although it is included in the header file. In [1], OASIS clarified + // that the header file is normative. In 3.0, they added the parameter + // to the spec, but it seems to be unused: + // + // > Do not use ulIvBits to specify the length of the initialization + // > vector, but ulIvLen instead. + // + // Further, in v3.0, the IV is permitted to be up to 2^32-1 bytes, + // which would cause ulIvBits to overflow on platforms where + // sizeof(CK_ULONG) = 4. + // + // In light of all this, we include ulIvBits in the struct, but always + // set it to zero. + // + // [1]: https://www.oasis-open.org/committees/document.php?document_id=58032&wg_abbrev=pkcs11 + GcmParams { + inner: CK_GCM_PARAMS { + pIv: iv.as_ptr() as *mut _, + ulIvLen: iv + .len() + .try_into() + .expect("iv length does not fit in CK_ULONG"), + ulIvBits: 0, + pAAD: aad.as_ptr() as *mut _, + ulAADLen: aad + .len() + .try_into() + .expect("aad length does not fit in CK_ULONG"), + ulTagBits: tag_bits.into(), + }, + _marker: PhantomData, + } + } + + /// The initialization vector. + pub fn iv(&self) -> &'a [u8] { + // SAFETY: In the constructor, the IV always comes from a &'a [u8] + unsafe { slice::from_raw_parts(self.inner.pIv, self.inner.ulIvLen as _) } + } + + /// The additional authenticated data. + pub fn aad(&self) -> &'a [u8] { + // SAEFTY: In the constructor, the AAD always comes from a &'a [u8] + unsafe { slice::from_raw_parts(self.inner.pAAD, self.inner.ulAADLen as _) } + } + + /// The length, in bits, of the authentication tag. + pub fn tag_bits(&self) -> Ulong { + self.inner.ulTagBits.into() + } +} diff --git a/cryptoki/src/mechanism/mod.rs b/cryptoki/src/mechanism/mod.rs index ab8af4bb..e6bed66e 100644 --- a/cryptoki/src/mechanism/mod.rs +++ b/cryptoki/src/mechanism/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 //! Data types for mechanisms +pub mod aead; pub mod elliptic_curve; mod mechanism_info; pub mod rsa; @@ -60,6 +61,8 @@ impl MechanismType { pub const AES_KEY_WRAP_PAD: MechanismType = MechanismType { val: CKM_AES_KEY_WRAP_PAD, }; + /// AES-GCM mechanism + pub const AES_GCM: MechanismType = MechanismType { val: CKM_AES_GCM }; // RSA /// PKCS #1 RSA key pair generation mechanism @@ -684,6 +687,8 @@ pub enum Mechanism<'a> { AesKeyWrap, /// AES key wrap with padding block AesKeyWrapPad, + /// AES-GCM mechanism + AesGcm(aead::GcmParams<'a>), // RSA /// PKCS #1 RSA key pair generation mechanism @@ -823,6 +828,7 @@ impl Mechanism<'_> { Mechanism::AesCbcPad(_) => MechanismType::AES_CBC_PAD, Mechanism::AesKeyWrap => MechanismType::AES_KEY_WRAP, Mechanism::AesKeyWrapPad => MechanismType::AES_KEY_WRAP_PAD, + Mechanism::AesGcm(_) => MechanismType::AES_GCM, Mechanism::RsaPkcsKeyPairGen => MechanismType::RSA_PKCS_KEY_PAIR_GEN, Mechanism::RsaPkcs => MechanismType::RSA_PKCS, @@ -884,6 +890,13 @@ impl From<&Mechanism<'_>> for CK_MECHANISM { | Mechanism::Des3Cbc(params) | Mechanism::DesCbcPad(params) | Mechanism::Des3CbcPad(params) => make_mechanism(mechanism, params), + Mechanism::AesGcm(params) => CK_MECHANISM { + mechanism, + pParameter: params as *const _ as *mut c_void, + ulParameterLen: std::mem::size_of::() + .try_into() + .expect("usize can not fit in CK_ULONG"), + }, Mechanism::RsaPkcsPss(params) | Mechanism::Sha1RsaPkcsPss(params) | Mechanism::Sha256RsaPkcsPss(params) diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index e0340636..6a0fb197 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -5,6 +5,7 @@ mod common; use crate::common::{SO_PIN, USER_PIN}; use common::init_pins; use cryptoki::error::{Error, RvError}; +use cryptoki::mechanism::aead::GcmParams; use cryptoki::mechanism::Mechanism; use cryptoki::object::{Attribute, AttributeInfo, AttributeType, KeyType, ObjectClass}; use cryptoki::session::{SessionState, UserType}; @@ -938,3 +939,67 @@ fn sha256_digest() -> TestResult { Ok(()) } + +#[test] +#[serial] +// Currently empty AAD crashes SoftHSM, see: https://github.com/opendnssec/SoftHSMv2/issues/605 +#[ignore] +fn aes_gcm_no_aad() -> TestResult { + // Encrypt two blocks of zeros with AES-128-GCM + let key = vec![0; 16]; + let iv = [0; 12]; + let aad = []; + let plain = [0; 32]; + let expected_cipher_and_tag = [ + 0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, + 0x78, 0xf7, 0x95, 0xaa, 0xab, 0x49, 0x4b, 0x59, 0x23, 0xf7, 0xfd, 0x89, 0xff, 0x94, 0x8b, + 0xc1, 0xe0, 0x40, 0x49, 0x0a, 0xf4, 0x80, 0x56, 0x06, 0xb2, 0xa3, 0xa2, 0xe7, 0x93, + ]; + + let (pkcs11, slot) = init_pins(); + let session = pkcs11.open_rw_session(slot)?; + session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?; + + let template = [ + Attribute::Class(ObjectClass::SECRET_KEY), + Attribute::KeyType(KeyType::AES), + Attribute::Value(key), + Attribute::Encrypt(true), + ]; + let key_handle = session.create_object(&template)?; + let mechanism = Mechanism::AesGcm(GcmParams::new(&iv, &aad, 96.into())); + let cipher_and_tag = session.encrypt(&mechanism, key_handle, &plain)?; + assert_eq!(expected_cipher_and_tag[..], cipher_and_tag[..]); + Ok(()) +} + +#[test] +#[serial] +fn aes_gcm_with_aad() -> TestResult { + // Encrypt a block of zeros with AES-128-GCM. + // Use another block of zeros for AAD. + let key = vec![0; 16]; + let iv = [0; 12]; + let aad = [0; 16]; + let plain = [0; 16]; + let expected_cipher_and_tag = [ + 0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, + 0x78, 0xd2, 0x4e, 0x50, 0x3a, 0x1b, 0xb0, 0x37, 0x07, 0x1c, 0x71, 0xb3, 0x5d, + ]; + + let (pkcs11, slot) = init_pins(); + let session = pkcs11.open_rw_session(slot)?; + session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?; + + let template = [ + Attribute::Class(ObjectClass::SECRET_KEY), + Attribute::KeyType(KeyType::AES), + Attribute::Value(key), + Attribute::Encrypt(true), + ]; + let key_handle = session.create_object(&template)?; + let mechanism = Mechanism::AesGcm(GcmParams::new(&iv, &aad, 96.into())); + let cipher_and_tag = session.encrypt(&mechanism, key_handle, &plain)?; + assert_eq!(expected_cipher_and_tag[..], cipher_and_tag[..]); + Ok(()) +}