Skip to content

Commit

Permalink
Add HPKE implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
sosthene-nitrokey authored and robin-nitrokey committed Oct 17, 2024
1 parent cac4bd1 commit 7c72223
Show file tree
Hide file tree
Showing 5 changed files with 822 additions and 73 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

## [Unreleased][]

-
- Add implementation of the `trussed-hpke` extension ([#36][])

[#36]: https://github.com/Nitrokey/trussed-se050-backend/pull/36

[Unreleased]: https://github.com/trussed-dev/trussed-staging/compare/v0.3.0...HEAD

Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ trussed-auth = "0.3.0"
trussed-manage = "0.1.0"
trussed-se050-manage = "0.1.0"
trussed-wrap-key-to-file = "0.1.0"
trussed-hpke = "0.1.0"
delog = "0.1.6"
embedded-hal = "0.2.7"
hkdf = { version = "0.12.3", default-features = false }
Expand All @@ -49,6 +50,7 @@ p256-cortex-m4 = { version = "0.1.0-alpha.6", features = ["prehash", "sec1-signa
admin-app = "0.1.0"
bitflags = "2.5.0"
der = "0.7.9"
chacha20poly1305 = { version = "0.10.1", default-features = false }

[dev-dependencies]
admin-app = { version = "0.1.0", features = ["migration-tests"] }
Expand All @@ -64,6 +66,7 @@ trussed-manage = { git = "https://github.com/trussed-dev/trussed-staging.git", t
trussed-rsa-alloc = { git = "https://github.com/trussed-dev/trussed-rsa-backend.git", tag = "v0.2.1" }
trussed-wrap-key-to-file = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "wrap-key-to-file-v0.1.0" }
admin-app = { git = "https://github.com/Nitrokey/admin-app.git", tag = "v0.1.0-nitrokey.12" }
trussed-hpke = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "f0babe53813e7882cfe5ce749ebe3a65fc143fd7" }

trussed-se050-manage = { path = "extensions/se050-manage" }

Expand Down
155 changes: 88 additions & 67 deletions src/core_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ struct VolatileRsaKey {
intermediary_key: [u8; 16],
}

#[derive(Serialize, Deserialize, Debug, Clone)]
enum WrappedKeyType {
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub(crate) enum WrappedKeyType {
Volatile,
VolatileRsa,
}
Expand Down Expand Up @@ -445,7 +445,7 @@ impl<Twi: I2CForT1, D: DelayUs<u32>> Se050Backend<Twi, D> {
/// Put a volatile key stored on the filesystem back into the SE050
///
/// This is used to perform one operation, the key must then be cleared again with [`clear_volatile_key`](Se050Backend::clear_volatile_key)
fn reimport_volatile_key(
pub(crate) fn reimport_volatile_key(
&mut self,
key: KeyId,
kind: Kind,
Expand Down Expand Up @@ -775,7 +775,7 @@ impl<Twi: I2CForT1, D: DelayUs<u32>> Se050Backend<Twi, D> {
Ok(reply::DeriveKey { key })
}

fn clear_volatile_key(&mut self, object_id: ObjectId) -> Result<(), Error> {
pub(crate) fn clear_volatile_key(&mut self, object_id: ObjectId) -> Result<(), Error> {
self.se
.run_command(&DeleteSecureObject { object_id }, &mut [0; 128])
.map_err(|_err| {
Expand Down Expand Up @@ -2568,21 +2568,15 @@ impl<Twi: I2CForT1, D: DelayUs<u32>> Se050Backend<Twi, D> {
Ok(reply::Clear { success })
}

pub(crate) fn wrap_key(
pub(crate) fn wrap_key_data(
&mut self,
req: &request::WrapKey,
core_keystore: &mut impl Keystore,
key: KeyId,
se050_keystore: &mut impl Keystore,
ns: NamespaceValue,
) -> Result<reply::WrapKey, Error> {
if !matches!(req.mechanism, Mechanism::Chacha8Poly1305) {
return Err(Error::RequestNotAvailable);
}
let (parsed_key, _parsed_ty) = parse_key_id(req.key, ns).ok_or_else(|| {
debug!("Failed to parse key id to wrap: {:?}", req.key);
// Let non-se050 keys be wrapped by the core backend
Error::RequestNotAvailable
})?;
) -> Result<Option<(key::Key, WrappedKeyType)>, Error> {
let Some((parsed_key, _parsed_ty)) = parse_key_id(key, ns) else {
return Ok(None);
};

let ty = match parsed_key {
ParsedObjectId::VolatileKey(_) => WrappedKeyType::Volatile,
Expand All @@ -2592,7 +2586,7 @@ impl<Twi: I2CForT1, D: DelayUs<u32>> Se050Backend<Twi, D> {
return Err(Error::ObjectHandleInvalid);
}
};
debug!("trussed: Chacha8Poly1305::WrapKey {:?}", req.key);
debug!("trussed: Chacha8Poly1305::WrapKey {:?}", key);
match parsed_key {
ParsedObjectId::VolatileKey(_id) => {
debug!("Id: {:?}", _id.0);
Expand All @@ -2603,14 +2597,31 @@ impl<Twi: I2CForT1, D: DelayUs<u32>> Se050Backend<Twi, D> {
_ => unreachable!(),
}

let serialized_key = se050_keystore.load_key(key::Secrecy::Secret, None, &req.key)?;
let serialized_key = se050_keystore.load_key(key::Secrecy::Secret, None, &key)?;

Ok(Some((serialized_key, ty)))
}

let message = Message::from_slice(&serialized_key.serialize()).unwrap();
pub(crate) fn wrap_key(
&mut self,
req: &request::WrapKey,
core_keystore: &mut impl Keystore,
se050_keystore: &mut impl Keystore,
ns: NamespaceValue,
) -> Result<reply::WrapKey, Error> {
if !matches!(req.mechanism, Mechanism::Chacha8Poly1305) {
return Err(Error::RequestNotAvailable);
}
let Some((key, ty)) = self.wrap_key_data(req.key, se050_keystore, ns)? else {
debug!("Failed to parse key id");
// Let non-se050 keys be wrapped by the core backend
return Err(Error::RequestNotAvailable);
};

let encryption_request = request::Encrypt {
mechanism: Mechanism::Chacha8Poly1305,
key: req.wrapping_key,
message,
message: Bytes::from_slice(&key.serialize()).unwrap(),
associated_data: req.associated_data.clone(),
nonce: req.nonce.clone(),
};
Expand Down Expand Up @@ -2647,61 +2658,19 @@ impl<Twi: I2CForT1, D: DelayUs<u32>> Se050Backend<Twi, D> {
Ok(())
}

pub(crate) fn unwrap_key(
pub(crate) fn store_unwrapped_key_data(
&mut self,
req: &request::UnwrapKey,
core_keystore: &mut impl Keystore,
ty: WrappedKeyType,
serialized_key: &[u8],
se050_keystore: &mut impl Keystore,
ns: NamespaceValue,
) -> Result<reply::UnwrapKey, Error> {
debug!("Unwrapping_key");
// SE050 wrapped keys start with a `0`, see `wrap_key` for details
if !matches!(req.mechanism, Mechanism::Chacha8Poly1305)
|| req.wrapped_key.first() != Some(&0)
{
return Err(Error::RequestNotAvailable);
}
if !matches!(req.attributes.persistence, Location::Volatile) {
return Err(Error::FunctionNotSupported);
}

let WrappedKeyData {
encrypted_data:
reply::Encrypt {
ciphertext,
nonce,
tag,
},
ty,
} = postcard::from_bytes(&req.wrapped_key[1..]).map_err(|_| Error::CborError)?;

let decryption_request = request::Decrypt {
mechanism: Mechanism::Chacha8Poly1305,
key: req.wrapping_key,
message: ciphertext,
associated_data: req.associated_data.clone(),
nonce,
tag,
};

let serialized_key = if let Some(serialized_key) =
<trussed::mechanisms::Chacha8Poly1305 as trussed::service::Decrypt>::decrypt(
core_keystore,
&decryption_request,
)?
.plaintext
{
serialized_key
} else {
return Ok(reply::UnwrapKey { key: None });
};

) -> Result<KeyId, Error> {
// TODO: probably change this to returning Option<key> too
let key::Key {
flags: _,
kind,
material,
} = key::Key::try_deserialize(&serialized_key)?;
} = key::Key::try_deserialize(serialized_key)?;

let key_ty = match kind {
Kind::Rsa2048 => KeyType::Rsa2048,
Expand Down Expand Up @@ -2759,6 +2728,58 @@ impl<Twi: I2CForT1, D: DelayUs<u32>> Se050Backend<Twi, D> {
&key_id,
&material,
)?;
Ok(key_id)
}

pub(crate) fn unwrap_key(
&mut self,
req: &request::UnwrapKey,
core_keystore: &mut impl Keystore,
se050_keystore: &mut impl Keystore,
ns: NamespaceValue,
) -> Result<reply::UnwrapKey, Error> {
debug!("Unwrapping_key");
// SE050 wrapped keys start with a `0`, see `wrap_key` for details
if !matches!(req.mechanism, Mechanism::Chacha8Poly1305)
|| req.wrapped_key.first() != Some(&0)
{
return Err(Error::RequestNotAvailable);
}
if !matches!(req.attributes.persistence, Location::Volatile) {
return Err(Error::FunctionNotSupported);
}

let WrappedKeyData {
encrypted_data:
reply::Encrypt {
ciphertext,
nonce,
tag,
},
ty,
} = postcard::from_bytes(&req.wrapped_key[1..]).map_err(|_| Error::CborError)?;

let decryption_request = request::Decrypt {
mechanism: Mechanism::Chacha8Poly1305,
key: req.wrapping_key,
message: ciphertext,
associated_data: req.associated_data.clone(),
nonce,
tag,
};

let decryption_result =
<trussed::mechanisms::Chacha8Poly1305 as trussed::service::Decrypt>::decrypt(
core_keystore,
&decryption_request,
)?
.plaintext;

let Some(serialized_key) = decryption_result else {
return Ok(reply::UnwrapKey { key: None });
};

let key_id = self.store_unwrapped_key_data(ty, &serialized_key, se050_keystore, ns)?;

Ok(reply::UnwrapKey { key: Some(key_id) })
}
Expand Down
Loading

0 comments on commit 7c72223

Please sign in to comment.