diff --git a/src/mockhsm/object/objects.rs b/src/mockhsm/object/objects.rs index 51b7fc6b..a54c8cef 100644 --- a/src/mockhsm/object/objects.rs +++ b/src/mockhsm/object/objects.rs @@ -281,10 +281,24 @@ impl Objects { let unwrapped_object: WrappedObject = deserialize(&wrapped_data).unwrap(); - let payload = Payload::new( - unwrapped_object.object_info.algorithm, - &unwrapped_object.data, - ); + let payload = match unwrapped_object.object_info.algorithm { + Algorithm::Asymmetric(alg) if alg.is_rsa() => Payload::new( + unwrapped_object.object_info.algorithm, + // RSA encoding will include: + // - p + // - q + // - dp -\ + // - dq +- internal state + // - qinv -/ + // + // We can rebuild the key from the primes and we'll just discard the internal state here + &unwrapped_object.data[..alg.key_len()], + ), + _ => Payload::new( + unwrapped_object.object_info.algorithm, + &unwrapped_object.data, + ), + }; let object_key = Handle::new( unwrapped_object.object_info.object_id, diff --git a/src/wrap.rs b/src/wrap.rs index 8a48a5ec..f3c24a80 100644 --- a/src/wrap.rs +++ b/src/wrap.rs @@ -14,6 +14,6 @@ pub use self::{ error::{Error, ErrorKind}, info::Info, key::Key, - message::Message, + message::{Message, Plaintext}, nonce::Nonce, }; diff --git a/src/wrap/error.rs b/src/wrap/error.rs index 849bc2ff..ac14b344 100644 --- a/src/wrap/error.rs +++ b/src/wrap/error.rs @@ -12,6 +12,22 @@ pub enum ErrorKind { /// Wrap message is an invalid length #[error("invalid message length")] LengthInvalid, + + /// RSA key did not have 2 primes - multi-primes are not supported + #[error("RSA key did not have 2 primes")] + InvalidPrimes, + + /// RSA precomputation failed + #[error("RSA precomputation failed")] + RsaPrecomputeFailed, + + /// Unsupported key size + #[error("unsupported key size")] + UnsupportedKeySize, + + /// Wrapping key algorithm mismatch + #[error("Wrap key algorithm mismatch")] + AlgorithmMismatch, } impl ErrorKind { diff --git a/src/wrap/key.rs b/src/wrap/key.rs index 1f6a7782..633c589d 100644 --- a/src/wrap/key.rs +++ b/src/wrap/key.rs @@ -168,6 +168,11 @@ impl Key { Ok(()) } + + /// Return the length of the key + pub fn key_len(&self) -> usize { + self.data.len() + } } impl Debug for Key { diff --git a/src/wrap/message.rs b/src/wrap/message.rs index 5b6664ad..9005acc0 100644 --- a/src/wrap/message.rs +++ b/src/wrap/message.rs @@ -5,8 +5,9 @@ use super::{Algorithm, Error, ErrorKind}; use crate::{ algorithm, asymmetric, ecdsa::algorithm::CurveAlgorithm, + object, serialization::{deserialize, serialize}, - wrap, + wrap, Capability, Domain, }; use aes::cipher::Unsigned; use ccm::aead::Aead; @@ -18,7 +19,10 @@ use ecdsa::{ PrimeCurve, }; use num_traits::cast::FromPrimitive; -use rsa::{BigUint, RsaPrivateKey}; +use rsa::{ + traits::{PrivateKeyParts, PublicKeyParts}, + BigUint, RsaPrivateKey, +}; use serde::{Deserialize, Serialize}; /// Wrap wessage (encrypted HSM object or arbitrary data) encrypted under a wrap key @@ -93,15 +97,29 @@ impl Message { } } +/// Plaintext message to be encrypted under a wrap key #[derive(Serialize, Deserialize)] pub struct Plaintext { - pub alg_id: Algorithm, + /// Algorithm used for wrapping this message + pub algorithm: Algorithm, + /// Information about the object being wrapped pub object_info: wrap::Info, + /// Payload of the plaintext pub data: Vec, } impl Plaintext { + /// Wrapped the plaintext under a wrapping key pub fn encrypt(&self, key: &super::Key) -> Result { + if self.algorithm.key_len() != key.key_len() { + fail!( + ErrorKind::AlgorithmMismatch, + "Expected wrapping key with length {expected} but got {len}", + expected = self.algorithm.key_len(), + len = key.key_len() + ); + } + let cipher: super::key::AesCcm = key.into(); let nonce = Nonce::generate(); let wire = serialize(&self).unwrap(); @@ -110,6 +128,7 @@ impl Plaintext { Ok(Message { nonce, ciphertext }) } + /// Return the ecdsa key of this [`Plaintext`] if it was an EC key. pub fn ecdsa(&self) -> Option> where C: PrimeCurve + CurveAlgorithm + ValidatePublicKey, @@ -128,6 +147,7 @@ impl Plaintext { } } + /// Return the rsa key of this [`Plaintext`] if it was an RSA key. pub fn rsa(&self) -> Option { let (component_size, modulus_size) = match self.object_info.algorithm { algorithm::Algorithm::Asymmetric(asymmetric::Algorithm::Rsa2048) => (128, 256), @@ -151,6 +171,69 @@ impl Plaintext { Some(private_key) } + + /// Build a [`Plaintext`] from an [`RsaPrivateKey`]. + pub fn from_rsa( + algorithm: Algorithm, + object_id: object::Id, + capabilities: Capability, + domains: Domain, + label: object::Label, + mut key: RsaPrivateKey, + ) -> Result { + let mut object_info = wrap::Info { + capabilities, + object_id, + length: 0, + domains, + object_type: object::Type::AsymmetricKey, + algorithm: algorithm::Algorithm::Asymmetric(asymmetric::Algorithm::Rsa2048), + sequence: 0, + origin: object::Origin::Imported, + label, + }; + + object_info.algorithm = match key.size() { + 256 => algorithm::Algorithm::Asymmetric(asymmetric::Algorithm::Rsa2048), + 384 => algorithm::Algorithm::Asymmetric(asymmetric::Algorithm::Rsa3072), + 512 => algorithm::Algorithm::Asymmetric(asymmetric::Algorithm::Rsa4096), + other => fail!( + ErrorKind::UnsupportedKeySize, + "RSA key size {} is not supported", + other + ), + }; + + // Make sure we have qinv, dp and dq + key.precompute() + .map_err(|_| format_err!(ErrorKind::RsaPrecomputeFailed, "Rsa precompute failed"))?; + + let primes = key.primes(); + if primes.len() != 2 { + fail!(ErrorKind::InvalidPrimes, "multi-primes is not supported"); + } + + let p = &primes[0]; + let q = &primes[1]; + + let mut data = Vec::new(); + data.extend_from_slice(&p.to_bytes_be()); + data.extend_from_slice(&q.to_bytes_be()); + // Unwrap here is okay, we have ownership of the key and we already precomputed the values. + data.extend_from_slice(&key.dp().unwrap().to_bytes_be()); + data.extend_from_slice(&key.dq().unwrap().to_bytes_be()); + // TODO: the second unwrap for int -> uint conversion is unfortunate. + data.extend_from_slice(&key.qinv().unwrap().to_biguint().unwrap().to_bytes_be()); + data.extend_from_slice(&key.n().to_bytes_be()); + + object_info.length = data.len() as u16; + + Ok(Self { + algorithm, + object_info, + data, + }) + } } /// Support structure to read from a slice like a reader diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index b7a6fb20..44cf85e4 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -1,7 +1,11 @@ //! RSA (Rivest–Shamir–Adleman) asymmetric cryptosystem tests +use crate::{ + clear_test_key_slot, test_vectors::AESCCM_TEST_VECTORS, TEST_DOMAINS, TEST_KEY_ID, + TEST_KEY_LABEL, +}; use ::rsa::{pkcs8::DecodePrivateKey, traits::PrivateKeyParts, RsaPrivateKey}; -use yubihsm::object; +use yubihsm::{object, wrap, Capability}; /// Domain IDs for test key const TEST_SIGNING_KEY_DOMAINS: yubihsm::Domain = yubihsm::Domain::DOM1; @@ -39,3 +43,55 @@ fn rsa_put_asymmetric_key() { assert_eq!(public, key.as_ref().clone()); } + +#[test] +fn rsa_import_wrapped_key() { + let key = RsaPrivateKey::from_pkcs8_der(RSA_2048_PRIV_DER).unwrap(); + let algorithm = wrap::Algorithm::Aes128Ccm; + let capabilities = Capability::EXPORT_WRAPPED | Capability::IMPORT_WRAPPED; + let delegated_capabilities = Capability::all(); + let asymmetric_key_id = 224; + + let plaintext = wrap::Plaintext::from_rsa( + algorithm, + asymmetric_key_id, + Capability::empty(), + TEST_DOMAINS, + TEST_KEY_LABEL.into(), + key.clone(), + ) + .expect("build message with RSA key"); + + let wrap_key = wrap::Key::from_bytes(TEST_KEY_ID, AESCCM_TEST_VECTORS[0].key).unwrap(); + let message = plaintext + .encrypt(&wrap_key) + .expect("failed to encrypt the wrapped key"); + + let client = crate::get_hsm_client(); + clear_test_key_slot(&client, object::Type::WrapKey); + let _ = client.delete_object(asymmetric_key_id, object::Type::AsymmetricKey); + + let _key_id = client + .put_wrap_key( + TEST_KEY_ID, + TEST_KEY_LABEL.into(), + TEST_DOMAINS, + capabilities, + delegated_capabilities, + algorithm, + AESCCM_TEST_VECTORS[0].key, + ) + .unwrap_or_else(|err| panic!("error generating wrap key: {err}")); + + let handle = client + .import_wrapped(TEST_KEY_ID, message) + .expect("impot asymmetric key"); + + assert_eq!(handle.object_id, asymmetric_key_id); + let public = client + .get_public_key(handle.object_id) + .expect("read public key"); + let public = public.rsa().expect("rsa public key expected"); + + assert_eq!(public, key.as_ref().clone()); +}