Skip to content

Commit

Permalink
rsa: adds import wrapped keys support (#511)
Browse files Browse the repository at this point in the history
  • Loading branch information
baloo authored Dec 3, 2023
1 parent f15777b commit 9aedc98
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 9 deletions.
22 changes: 18 additions & 4 deletions src/mockhsm/object/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/wrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ pub use self::{
error::{Error, ErrorKind},
info::Info,
key::Key,
message::Message,
message::{Message, Plaintext},
nonce::Nonce,
};
16 changes: 16 additions & 0 deletions src/wrap/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions src/wrap/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
89 changes: 86 additions & 3 deletions src/wrap/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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<u8>,
}

impl Plaintext {
/// Wrapped the plaintext under a wrapping key
pub fn encrypt(&self, key: &super::Key) -> Result<Message, Error> {
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();
Expand All @@ -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<C>(&self) -> Option<SecretKey<C>>
where
C: PrimeCurve + CurveAlgorithm + ValidatePublicKey,
Expand All @@ -128,6 +147,7 @@ impl Plaintext {
}
}

/// Return the rsa key of this [`Plaintext`] if it was an RSA key.
pub fn rsa(&self) -> Option<RsaPrivateKey> {
let (component_size, modulus_size) = match self.object_info.algorithm {
algorithm::Algorithm::Asymmetric(asymmetric::Algorithm::Rsa2048) => (128, 256),
Expand All @@ -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<Self, Error> {
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
Expand Down
58 changes: 57 additions & 1 deletion tests/rsa/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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());
}

0 comments on commit 9aedc98

Please sign in to comment.