Skip to content

Commit

Permalink
oaep: implement mockhsm backend for decrypt_oaep
Browse files Browse the repository at this point in the history
  • Loading branch information
baloo committed Jun 28, 2024
1 parent 865d03c commit de2b966
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 13 deletions.
132 changes: 121 additions & 11 deletions src/mockhsm/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ use crate::{
opaque::{self, commands::*},
otp,
response::{self, Response},
rsa::{self, pkcs1::commands::*, pss::commands::*},
rsa::{
self, mgf,
oaep::{commands::*, DecryptedData},
pkcs1::commands::*,
pss::commands::*,
},
serialization::deserialize,
session::{self, commands::*},
template,
Expand All @@ -29,10 +34,10 @@ use ::ecdsa::{
hazmat::SignPrimitive,
};
use ::hmac::{Hmac, Mac};
use ::rsa::{pkcs1v15, pss, RsaPrivateKey};
use ::rsa::{oaep::Oaep, pkcs1v15, pss, traits::PaddingScheme, RsaPrivateKey};
use digest::{
const_oid::AssociatedOid, crypto_common::OutputSizeUser, typenum::Unsigned, Digest,
FixedOutputReset,
FixedOutput, FixedOutputReset, Output, Reset,
};
use rand_core::{OsRng, RngCore};
use sha1::Sha1;
Expand Down Expand Up @@ -130,6 +135,7 @@ pub(crate) fn session_message(
Code::VerifyHmac => verify_hmac(state, &command.data),
Code::SignPss => sign_pss(state, &command.data),
Code::SignPkcs1 => sign_pkcs1v15(state, &command.data),
Code::DecryptOaep => decrypt_oaep(state, &command.data),
unsupported => panic!("unsupported command type: {unsupported:?}"),
};

Expand Down Expand Up @@ -207,10 +213,10 @@ fn device_info() -> response::Message {
Algorithm::Wrap(wrap::Algorithm::Aes128Ccm),
Algorithm::Opaque(opaque::Algorithm::Data),
Algorithm::Opaque(opaque::Algorithm::X509Certificate),
Algorithm::Mgf(rsa::mgf::Algorithm::Sha1),
Algorithm::Mgf(rsa::mgf::Algorithm::Sha256),
Algorithm::Mgf(rsa::mgf::Algorithm::Sha384),
Algorithm::Mgf(rsa::mgf::Algorithm::Sha512),
Algorithm::Mgf(mgf::Algorithm::Sha1),
Algorithm::Mgf(mgf::Algorithm::Sha256),
Algorithm::Mgf(mgf::Algorithm::Sha384),
Algorithm::Mgf(mgf::Algorithm::Sha512),
Algorithm::Template(template::Algorithm::Ssh),
Algorithm::YubicoOtp(otp::Algorithm::Aes128),
Algorithm::Authentication(authentication::Algorithm::YubicoAes),
Expand Down Expand Up @@ -740,16 +746,16 @@ fn sign_pss(state: &State, cmd_data: &[u8]) -> response::Message {
{
if let Payload::RsaKey(private_key) = &obj.payload {
let signature = match command.mgf1_hash_alg {
rsa::mgf::Algorithm::Sha1 => {
mgf::Algorithm::Sha1 => {
sign_pss_digest::<Sha1>(private_key, command.digest.as_ref())
}
rsa::mgf::Algorithm::Sha256 => {
mgf::Algorithm::Sha256 => {
sign_pss_digest::<Sha256>(private_key, command.digest.as_ref())
}
rsa::mgf::Algorithm::Sha384 => {
mgf::Algorithm::Sha384 => {
sign_pss_digest::<Sha384>(private_key, command.digest.as_ref())
}
rsa::mgf::Algorithm::Sha512 => {
mgf::Algorithm::Sha512 => {
sign_pss_digest::<Sha512>(private_key, command.digest.as_ref())
}
};
Expand Down Expand Up @@ -843,3 +849,107 @@ fn verify_hmac(state: &State, cmd_data: &[u8]) -> response::Message {
device::ErrorKind::ObjectNotFound.into()
}
}

/// [`PrecomputedHashDigest`] provides a backend for storing a fixed hash.
///
/// When an OAEP decrypt command is sent by the client, it will carry the hash of the label (and
/// not the label itself).
/// Sadly [`::rsa::oaep::Oaep`] implementation for decrypt does not accept that but expects an object
/// implementing [`Digest`]. If you don't provide a label, it will feed an empty slice to the
/// digest and use its output.
///
/// [`PrecomputedHashDigest`] provides a compatible implementation, but will ignore whatever
/// it's fed, and only reply with the pre-hashed content instead.
///
/// # Panics
///
/// Trying to reset the fixed hash will trigger a panic, and should be treated as a
/// bug.
#[derive(Clone)]
struct PrecomputedHashDigest<D: OutputSizeUser> {
fixed: GenericArray<u8, D::OutputSize>,
}

impl<D: OutputSizeUser> OutputSizeUser for PrecomputedHashDigest<D> {
type OutputSize = D::OutputSize;
fn output_size() -> usize {
D::output_size()
}
}

impl<D: OutputSizeUser> FixedOutput for PrecomputedHashDigest<D> {
fn finalize_into(self, out: &mut Output<Self>) {
out.clone_from_slice(self.fixed.as_slice())
}
}

impl<D: OutputSizeUser> digest::Update for PrecomputedHashDigest<D> {
fn update(&mut self, _data: &[u8]) {}
}

impl<D: OutputSizeUser> Reset for PrecomputedHashDigest<D> {
fn reset(&mut self) {
unimplemented!("Tried to update PrecomputedHashDigest, this is unexpected")
}
}

impl<D: OutputSizeUser> FixedOutputReset for PrecomputedHashDigest<D> {
fn finalize_into_reset(&mut self, out: &mut Output<Self>) {
out.clone_from_slice(self.fixed.as_slice())
}
}

fn decrypt_oaep(state: &State, cmd_data: &[u8]) -> response::Message {
let command: DecryptOaepCommand = deserialize(cmd_data)
.unwrap_or_else(|e| panic!("error parsing Code::DecryptOaepCommand: {e:?}"));

if let Some(obj) = state
.objects
.get(command.key_id, object::Type::AsymmetricKey)
{
if let Payload::RsaKey(private_key) = &obj.payload {
macro_rules! decrypt_oaep {
($hash:ty) => {{
let oaep = Oaep {
digest: Box::new(PrecomputedHashDigest::<$hash> {
fixed: GenericArray::clone_from_slice(command.label_hash.as_slice()),
}),
mgf_digest: Box::new(<$hash>::new()),
label: None,
};
oaep.decrypt(Some(&mut OsRng), private_key, &command.data)
}};
}

let plaintext = match command.mgf1_hash_alg {
mgf::Algorithm::Sha1 => {
decrypt_oaep!(Sha1)
}
mgf::Algorithm::Sha256 => {
decrypt_oaep!(Sha256)
}
mgf::Algorithm::Sha384 => {
decrypt_oaep!(Sha384)
}
mgf::Algorithm::Sha512 => {
decrypt_oaep!(Sha512)
}
};

let plaintext = if let Ok(plaintext) = plaintext {
plaintext
} else {
debug!("decrypt failed");
return device::ErrorKind::InvalidData.into();
};

DecryptOaepResponse(DecryptedData(plaintext)).serialize()
} else {
debug!("not an Rsa key: {:?}", obj.algorithm());
device::ErrorKind::InvalidCommand.into()
}
} else {
debug!("no such object ID: {:?}", command.key_id);
device::ErrorKind::ObjectNotFound.into()
}
}
2 changes: 1 addition & 1 deletion src/rsa/oaep/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl Command for DecryptOaepCommand {

/// RSA OAEP decrypted data
#[derive(Serialize, Deserialize, Debug)]
pub struct DecryptOaepResponse(rsa::oaep::DecryptedData);
pub struct DecryptOaepResponse(pub(crate) rsa::oaep::DecryptedData);

impl Response for DecryptOaepResponse {
const COMMAND_CODE: command::Code = command::Code::DecryptOaep;
Expand Down
1 change: 0 additions & 1 deletion tests/command/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Integration tests for YubiHSM 2 commands

pub mod blink_device;
#[cfg(not(feature = "mockhsm"))]
pub mod decrypt_oaep;
pub mod delete_object;
pub mod device_info;
Expand Down

0 comments on commit de2b966

Please sign in to comment.