diff --git a/Cargo.toml b/Cargo.toml index 9dc6a04..0399100 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,17 @@ delog = "0.1.6" littlefs2 = "0.4.0" serde-byte-array = "0.1.2" +# For hmacsha256p256 +hmac = { version = "0.12", optional = true } +sha2 = { version = "0.10", default-features = false, optional = true } + [dev-dependencies] trussed = { version = "0.1.0", default-features = false, features = ["serde-extensions", "virt"] } [features] default = [] +hmacsha256p256 = ["hmac", "sha2"] wrap-key-to-file = ["chacha20poly1305"] chunked = [] encrypted-chunked = ["chunked", "chacha20poly1305/stream"] diff --git a/src/hmacsha256p256/mod.rs b/src/hmacsha256p256/mod.rs new file mode 100644 index 0000000..519f619 --- /dev/null +++ b/src/hmacsha256p256/mod.rs @@ -0,0 +1,241 @@ +// Copyright (C) Nitrokey GmbH +// SPDX-License-Identifier: Apache-2.0 or MIT + +use serde::{Deserialize, Serialize}; +use trussed::types::Message; +use trussed::{ + client::ClientError, + key::{self, Kind}, + serde_extensions::{Extension, ExtensionClient, ExtensionImpl, ExtensionResult}, + service::{Keystore, ServiceResources}, + types::{Bytes, CoreContext, KeyId, Location, Mechanism}, + Error, +}; + +#[derive(Debug, Default)] +pub struct HmacSha256P256Extension; + +#[derive(Debug, Deserialize, Serialize)] +#[allow(missing_docs)] +pub enum HmacSha256P256Request { + DeriveFromHash(request::DeriveFromHash), + InjectAnyKey(request::InjectAnyKey), +} + +mod request { + use super::*; + use serde::{Deserialize, Serialize}; + use trussed::types::{KeyId, Location, Mechanism, Message}; + use trussed::Error; + + #[derive(Debug, Deserialize, Serialize)] + pub struct DeriveFromHash { + pub mechanism: Mechanism, + pub key: KeyId, + pub location: Location, + pub data: Option, + } + + impl TryFrom for DeriveFromHash { + type Error = Error; + fn try_from(request: HmacSha256P256Request) -> Result { + match request { + HmacSha256P256Request::DeriveFromHash(request) => Ok(request), + _ => Err(Error::InternalError), + } + } + } + + impl From for HmacSha256P256Request { + fn from(request: DeriveFromHash) -> Self { + Self::DeriveFromHash(request) + } + } + + #[derive(Debug, Deserialize, Serialize)] + pub struct InjectAnyKey { + pub location: Location, + pub kind: Kind, + // pub raw_key: SerializedKey, + pub raw_key: Message, + } + + impl TryFrom for InjectAnyKey { + type Error = Error; + fn try_from(request: HmacSha256P256Request) -> Result { + match request { + HmacSha256P256Request::InjectAnyKey(request) => Ok(request), + _ => Err(Error::InternalError), + } + } + } + + impl From for HmacSha256P256Request { + fn from(request: InjectAnyKey) -> Self { + Self::InjectAnyKey(request) + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +#[allow(missing_docs)] +pub enum HmacSha256P256Reply { + DeriveFromHash(reply::DeriveFromHash), + InjectAnyKey(reply::InjectAnyKey), +} + +mod reply { + use serde::{Deserialize, Serialize}; + use trussed::{types::KeyId, Error}; + + use super::*; + + #[derive(Debug, Deserialize, Serialize)] + #[non_exhaustive] + pub struct DeriveFromHash { + pub key: Option, + } + + impl TryFrom for DeriveFromHash { + type Error = Error; + fn try_from(reply: HmacSha256P256Reply) -> Result { + match reply { + HmacSha256P256Reply::DeriveFromHash(reply) => Ok(reply), + _ => Err(Error::InternalError), + } + } + } + + impl From for HmacSha256P256Reply { + fn from(reply: DeriveFromHash) -> Self { + Self::DeriveFromHash(reply) + } + } + + #[derive(Debug, Deserialize, Serialize)] + pub struct InjectAnyKey { + pub key: Option, + } + + impl TryFrom for InjectAnyKey { + type Error = Error; + fn try_from(reply: HmacSha256P256Reply) -> Result { + match reply { + HmacSha256P256Reply::InjectAnyKey(reply) => Ok(reply), + _ => Err(Error::InternalError), + } + } + } + + impl From for HmacSha256P256Reply { + fn from(reply: InjectAnyKey) -> Self { + Self::InjectAnyKey(reply) + } + } +} + +impl Extension for HmacSha256P256Extension { + type Request = HmacSha256P256Request; + type Reply = HmacSha256P256Reply; +} + +pub fn derive_key_from_hash( + keystore: &mut impl Keystore, + request: &request::DeriveFromHash, +) -> Result { + use hmac::{Hmac, Mac}; + type HmacSha256P256 = Hmac; + + let key_id = request.key; + let key = keystore.load_key(key::Secrecy::Secret, None, &key_id)?; + let shared_secret = key.material; + + let mut mac = + HmacSha256P256::new_from_slice(shared_secret.as_ref()).map_err(|_| Error::InternalError)?; + + if let Some(data) = &request.data { + mac.update(data); + } + let derived_key: [u8; 32] = mac + .finalize() + .into_bytes() + .try_into() + .map_err(|_| Error::InternalError)?; + let key_id = keystore.store_key( + request.location, + key::Secrecy::Secret, + key::Kind::P256, // TODO use mechanism/kind from the request + &derived_key, + )?; + Ok(reply::DeriveFromHash { key: Some(key_id) }) +} + +pub fn inject_any_key( + keystore: &mut impl Keystore, + request: &request::InjectAnyKey, +) -> Result { + let key_id = keystore.store_key( + request.location, + key::Secrecy::Secret, + request.kind, + &request.raw_key, + )?; + + Ok(reply::InjectAnyKey { key: Some(key_id) }) +} + +impl ExtensionImpl for super::StagingBackend { + fn extension_request( + &mut self, + core_ctx: &mut CoreContext, + _backend_ctx: &mut Self::Context, + request: &HmacSha256P256Request, + resources: &mut ServiceResources

, + ) -> Result { + let keystore = &mut resources.keystore(core_ctx)?; + match request { + HmacSha256P256Request::DeriveFromHash(request) => { + derive_key_from_hash(keystore, request).map(Into::into) + } + HmacSha256P256Request::InjectAnyKey(request) => { + inject_any_key(keystore, request).map(Into::into) + } + } + } +} + +type HmacSha256P256Result<'a, R, C> = ExtensionResult<'a, HmacSha256P256Extension, R, C>; + +pub trait HmacSha256P256Client: ExtensionClient { + fn derive_from_hash( + &mut self, + mechanism: Mechanism, + key: KeyId, + location: Location, + data: &[u8], + ) -> HmacSha256P256Result<'_, reply::DeriveFromHash, Self> { + let data = Bytes::from_slice(data).map_err(|_| ClientError::DataTooLarge)?; + self.extension(request::DeriveFromHash { + mechanism, + key, + location, + data: Some(data), + }) + } + + fn inject_any_key( + &mut self, + // raw_key: SerializedKey, + raw_key: Message, + location: Location, + kind: Kind, + ) -> HmacSha256P256Result<'_, reply::InjectAnyKey, Self> { + self.extension(request::InjectAnyKey { + location, + kind, + raw_key, + }) + } +} + +impl> HmacSha256P256Client for C {} diff --git a/src/lib.rs b/src/lib.rs index 919dbf4..57827aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,9 @@ pub mod wrap_key_to_file; #[cfg(feature = "chunked")] pub mod streaming; +#[cfg(feature = "hmacsha256p256")] +pub mod hmacsha256p256; + #[derive(Clone, Debug, Default)] #[non_exhaustive] pub struct StagingBackend {} diff --git a/src/virt.rs b/src/virt.rs index f22cf47..d4ffed5 100644 --- a/src/virt.rs +++ b/src/virt.rs @@ -11,6 +11,9 @@ use crate::{StagingBackend, StagingContext}; #[cfg(feature = "chunked")] use crate::streaming::ChunkedExtension; +#[cfg(feature = "hmacsha256p256")] +use crate::hmacsha256p256::HmacSha256P256Extension; + #[derive(Default, Debug)] pub struct Dispatcher { backend: StagingBackend, @@ -27,6 +30,14 @@ pub enum ExtensionIds { WrapKeyToFile, #[cfg(feature = "chunked")] Chunked, + #[cfg(feature = "hmacsha256p256")] + HmacShaP256, +} + +#[cfg(feature = "hmacsha256p256")] +impl ExtensionId for Dispatcher { + type Id = ExtensionIds; + const ID: ExtensionIds = ExtensionIds::HmacShaP256; } #[cfg(feature = "wrap-key-to-file")] @@ -48,6 +59,8 @@ impl From for u8 { ExtensionIds::WrapKeyToFile => 0, #[cfg(feature = "chunked")] ExtensionIds::Chunked => 1, + #[cfg(feature = "hmacsha256p256")] + ExtensionIds::HmacShaP256 => 2, } } } @@ -60,6 +73,8 @@ impl TryFrom for ExtensionIds { 0 => Ok(Self::WrapKeyToFile), #[cfg(feature = "chunked")] 1 => Ok(Self::Chunked), + #[cfg(feature = "hmacsha256p256")] + 2 => Ok(Self::HmacShaP256), _ => Err(Error::FunctionNotSupported), } } @@ -105,6 +120,16 @@ impl ExtensionDispatch for Dispatcher { request, resources, ), + #[cfg(feature = "hmacsha256p256")] + ExtensionIds::HmacShaP256 => >::extension_request_serialized( + &mut self.backend, + &mut ctx.core, + &mut ctx.backends, + request, + resources, + ), #[cfg(feature = "chunked")] ExtensionIds::Chunked => { >::extension_request_serialized( diff --git a/tests/hmacsha256p256.rs b/tests/hmacsha256p256.rs new file mode 100644 index 0000000..757d576 --- /dev/null +++ b/tests/hmacsha256p256.rs @@ -0,0 +1,38 @@ +// Copyright (C) Nitrokey GmbH +// SPDX-License-Identifier: Apache-2.0 or MIT + +#![cfg(all(feature = "virt", feature = "hmacsha256p256"))] + +use trussed::client::CryptoClient; +use trussed::key::Kind; +use trussed::syscall; +use trussed::types::{Location::*, Mechanism, SignatureSerialization}; + +use trussed::types::Location; + +use trussed_staging::virt::with_ram_client; + +use trussed::client::P256; +use trussed_staging::hmacsha256p256::HmacSha256P256Client; + +#[test] +fn hmac_inject_any() { + use trussed::types::Message; + with_ram_client("staging-tests", |mut client| { + let client = &mut client; + + let key = syscall!(client.inject_any_key( + Message::from_slice(b"12345678123456781234567812345678").unwrap(), + Volatile, + Kind::P256 + )) + .key + .unwrap(); + + let _pk = syscall!(client.derive_p256_public_key(key, Location::Volatile)).key; + + let signature = + syscall!(client.sign(Mechanism::P256, key, &[], SignatureSerialization::Raw)).signature; + assert!(signature.len() > 0); + }); +}