Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reset_pin_key #17

Merged
merged 5 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,24 @@ impl ExtensionImpl<AuthExtension> for AuthBackend {
.save(fs, self.location)?;
Ok(reply::SetPin.into())
}
AuthRequest::SetPinWithKey(request) => {
let app_key = self.get_app_key(client_id, trussed_fs, ctx, rng)?;
let key_to_wrap =
keystore.load_key(Secrecy::Secret, Some(Kind::Symmetric(32)), &request.key)?;
let key_to_wrap = (&*key_to_wrap.material)
.try_into()
.map_err(|_| Error::ReadFailed)?;
PinData::reset_with_key(
request.id,
&request.pin,
request.retries,
rng,
&app_key,
key_to_wrap,
)
.save(fs, self.location)?;
Ok(reply::SetPinWithKey.into())
}
AuthRequest::DeletePin(request) => {
let path = request.id.path();
if fs.exists(&path, self.location) {
Expand Down
33 changes: 33 additions & 0 deletions src/backend/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,39 @@ impl PinData {
}
}

pub fn reset_with_key<R>(
id: PinId,
pin: &Pin,
retries: Option<u8>,
rng: &mut R,
application_key: &Key,
mut key_to_wrap: Key,
) -> Self
where
R: CryptoRng + RngCore,
{
use chacha20poly1305::{AeadInPlace, KeyInit};
let mut salt = Salt::default();
rng.fill_bytes(salt.as_mut());
let pin_key = derive_key(id, pin, &salt, application_key);
let aead = ChaCha8Poly1305::new((&*pin_key).into());
let nonce = Default::default();
#[allow(clippy::expect_used)]
let tag: [u8; CHACHA_TAG_LEN] = aead
.encrypt_in_place_detached(&nonce, &[u8::from(id)], &mut *key_to_wrap)
.expect("Wrapping the key should always work, length are acceptable")
.into();
Self {
id,
retries: retries.map(From::from),
salt,
data: KeyOrHash::Key(WrappedKeyData {
wrapped_key: key_to_wrap,
tag: tag.into(),
}),
}
}

pub fn load<S: Filestore>(fs: &mut S, location: Location, id: PinId) -> Result<Self, Error> {
let path = id.path();
if !fs.exists(&path, location) {
Expand Down
27 changes: 26 additions & 1 deletion src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ pub mod reply;
pub mod request;

use serde::{Deserialize, Serialize};
use trussed::serde_extensions::{Extension, ExtensionClient, ExtensionResult};
use trussed::{
serde_extensions::{Extension, ExtensionClient, ExtensionResult},
types::KeyId,
};

use crate::{Pin, PinId};

Expand All @@ -32,6 +35,7 @@ pub enum AuthRequest {
CheckPin(request::CheckPin),
GetPinKey(request::GetPinKey),
SetPin(request::SetPin),
SetPinWithKey(request::SetPinWithKey),
ChangePin(request::ChangePin),
DeletePin(request::DeletePin),
DeleteAllPins(request::DeleteAllPins),
Expand All @@ -45,6 +49,7 @@ pub enum AuthReply {
CheckPin(reply::CheckPin),
GetPinKey(reply::GetPinKey),
SetPin(reply::SetPin),
SetPinWithKey(reply::SetPinWithKey),
ChangePin(reply::ChangePin),
DeletePin(reply::DeletePin),
DeleteAllPins(reply::DeleteAllPins),
Expand Down Expand Up @@ -114,6 +119,26 @@ pub trait AuthClient: ExtensionClient<AuthExtension> {
})
}

/// Set a pin, resetting it's retry counter and setting the key to be wrapped
sosthene-nitrokey marked this conversation as resolved.
Show resolved Hide resolved
///
/// Similar to [`set_pin`](AuthClient::set_pin), but allows the key that the pin will unwrap to be configured.
/// Currently only symmetric 256 bit keys are accepted. This method should be used only with keys that were obtained through [`get_pin_key`](AuthClient::get_pin_key)
/// This allows for example backing up the key for a pin, to be able to restore it from another source.
fn set_pin_with_key<I: Into<PinId>>(
&mut self,
id: I,
pin: Pin,
retries: Option<u8>,
key: KeyId,
) -> AuthResult<'_, reply::SetPinWithKey, Self> {
self.extension(request::SetPinWithKey {
id: id.into(),
pin,
retries,
key,
})
}

/// Change the given PIN and resets its retry counter.
///
/// The key obtained by [`get_pin_key`](AuthClient::get_pin_key) will stay the same
Expand Down
20 changes: 20 additions & 0 deletions src/extension/reply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,26 @@ impl TryFrom<AuthReply> for SetPin {
}
}

#[derive(Debug, Deserialize, Serialize)]
pub struct SetPinWithKey;

impl From<SetPinWithKey> for AuthReply {
fn from(reply: SetPinWithKey) -> Self {
Self::SetPinWithKey(reply)
}
}

impl TryFrom<AuthReply> for SetPinWithKey {
type Error = Error;

fn try_from(reply: AuthReply) -> Result<Self> {
match reply {
AuthReply::SetPinWithKey(reply) => Ok(reply),
_ => Err(Error::InternalError),
}
}
}

#[derive(Debug, Deserialize, Serialize)]
pub struct ChangePin {
pub success: bool,
Expand Down
18 changes: 17 additions & 1 deletion src/extension/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 or MIT

use serde::{Deserialize, Serialize};
use trussed::types::KeyId;

use super::AuthRequest;
use crate::{Pin, PinId};
Expand Down Expand Up @@ -46,7 +47,7 @@ pub struct SetPin {
pub id: PinId,
pub pin: Pin,
pub retries: Option<u8>,
/// If true, the PIN can be used to wrap/unwrap an application key
/// If true, the PIN can be used to wrap/unwrap a PIN key
pub derive_key: bool,
}

Expand All @@ -56,6 +57,21 @@ impl From<SetPin> for AuthRequest {
}
}

#[derive(Debug, Deserialize, Serialize)]
pub struct SetPinWithKey {
pub id: PinId,
pub pin: Pin,
pub retries: Option<u8>,
/// This key will be wrapped. It can be obtained again via a `GetPinKey` request
pub key: KeyId,
}

impl From<SetPinWithKey> for AuthRequest {
fn from(request: SetPinWithKey) -> Self {
Self::SetPinWithKey(request)
}
}

#[derive(Debug, Deserialize, Serialize)]
pub struct ChangePin {
pub id: PinId,
Expand Down
52 changes: 52 additions & 0 deletions tests/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,58 @@ fn pin_key() {
)
}

#[test]
fn reset_pin_key() {
run_with_hw_key(
BACKENDS,
Bytes::from_slice(b"Some HW ikm").unwrap(),
|client| {
let pin1 = Bytes::from_slice(b"12345678").unwrap();
let pin2 = Bytes::from_slice(b"123456").unwrap();
let pin3 = Bytes::from_slice(b"1234567890").unwrap();

syscall!(client.set_pin(Pin::User, pin1.clone(), Some(3), true));
assert!(syscall!(client.get_pin_key(Pin::User, pin2.clone()))
.result
.is_none());
assert_eq!(syscall!(client.pin_retries(Pin::User)).retries, Some(2));
assert!(!syscall!(client.check_pin(Pin::User, pin2.clone())).success);
assert_eq!(syscall!(client.pin_retries(Pin::User)).retries, Some(1));
assert!(syscall!(client.check_pin(Pin::User, pin1.clone())).success);
let key = syscall!(client.get_pin_key(Pin::User, pin1))
.result
.unwrap();
assert_eq!(syscall!(client.pin_retries(Pin::User)).retries, Some(3));
let mac = syscall!(client.sign_hmacsha256(key, b"Some data")).signature;

syscall!(client.set_pin_with_key(Pin::User, pin3.clone(), Some(3), key));

let key2 = syscall!(client.get_pin_key(Pin::User, pin3.clone()))
.result
.unwrap();
let mac2 = syscall!(client.sign_hmacsha256(key2, b"Some data")).signature;
assert_eq!(mac, mac2);

assert!(syscall!(client.change_pin(Pin::User, pin3.clone(), pin2.clone())).success);

let key3 = syscall!(client.get_pin_key(Pin::User, pin2.clone()))
.result
.unwrap();
let mac3 = syscall!(client.sign_hmacsha256(key3, b"Some data")).signature;
assert_eq!(mac, mac3);

assert!(!syscall!(client.check_pin(Pin::User, pin3.clone())).success);
assert!(!syscall!(client.check_pin(Pin::User, pin3.clone())).success);
assert!(!syscall!(client.check_pin(Pin::User, pin3)).success);
assert!(!syscall!(client.check_pin(Pin::User, pin2.clone())).success);
assert!(syscall!(client.get_pin_key(Pin::User, pin2))
.result
.is_none());
assert_eq!(syscall!(client.pin_retries(Pin::User)).retries, Some(0));
},
)
}

#[test]
fn blocked_pin() {
run(BACKENDS, |client| {
Expand Down