mirror of
https://github.com/trussed-dev/trussed-auth.git
synced 2026-06-20 04:16:21 -07:00
Add reset_pin_key
This syscall allows resetting a pin. Unlike `set_pin`, it takes a key as parameter. This key will be returned by future calls to `get_pin_key`. Unlike `change_pin` this doesn't require knowledge of the current value of the PIN. The goal is to allow resetting a PIN from another source. For example, OpenPGP smartcards need to be able to reset the user pin given an admin pin With this patch, this can be done by using the admin key to wrap the user key.
This commit is contained in:
committed by
sosthene-nitrokey
parent
308567df8c
commit
02eb7df2e1
@@ -284,6 +284,24 @@ impl ExtensionImpl<AuthExtension> for AuthBackend {
|
||||
.save(fs, self.location)?;
|
||||
Ok(reply::SetPin.into())
|
||||
}
|
||||
AuthRequest::ResetPinKey(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_given_key(
|
||||
request.id,
|
||||
&request.pin,
|
||||
request.retries,
|
||||
rng,
|
||||
&app_key,
|
||||
key_to_wrap,
|
||||
)
|
||||
.save(fs, self.location)?;
|
||||
Ok(reply::ResetPinKey.into())
|
||||
}
|
||||
AuthRequest::DeletePin(request) => {
|
||||
let path = request.id.path();
|
||||
if fs.exists(&path, self.location) {
|
||||
|
||||
@@ -171,6 +171,39 @@ impl PinData {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_given_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) {
|
||||
|
||||
+25
-1
@@ -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};
|
||||
|
||||
@@ -32,6 +35,7 @@ pub enum AuthRequest {
|
||||
CheckPin(request::CheckPin),
|
||||
GetPinKey(request::GetPinKey),
|
||||
SetPin(request::SetPin),
|
||||
ResetPinKey(request::ResetPinKey),
|
||||
ChangePin(request::ChangePin),
|
||||
DeletePin(request::DeletePin),
|
||||
DeleteAllPins(request::DeleteAllPins),
|
||||
@@ -45,6 +49,7 @@ pub enum AuthReply {
|
||||
CheckPin(reply::CheckPin),
|
||||
GetPinKey(reply::GetPinKey),
|
||||
SetPin(reply::SetPin),
|
||||
ResetPinKey(reply::ResetPinKey),
|
||||
ChangePin(reply::ChangePin),
|
||||
DeletePin(reply::DeletePin),
|
||||
DeleteAllPins(reply::DeleteAllPins),
|
||||
@@ -114,6 +119,25 @@ pub trait AuthClient: ExtensionClient<AuthExtension> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Reset a pin.
|
||||
///
|
||||
/// Similar to [`set_pin`](AuthClient::set_pin), but allows the key that the pin will unwrap to be configured.
|
||||
/// This allows for example backing up the key for a pin, to be able to restore it from another source.
|
||||
fn reset_set_pin_key<I: Into<PinId>>(
|
||||
&mut self,
|
||||
id: I,
|
||||
pin: Pin,
|
||||
retries: Option<u8>,
|
||||
key: KeyId,
|
||||
) -> AuthResult<'_, reply::ResetPinKey, Self> {
|
||||
self.extension(request::ResetPinKey {
|
||||
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
|
||||
|
||||
@@ -99,6 +99,26 @@ impl TryFrom<AuthReply> for SetPin {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ResetPinKey;
|
||||
|
||||
impl From<ResetPinKey> for AuthReply {
|
||||
fn from(reply: ResetPinKey) -> Self {
|
||||
Self::ResetPinKey(reply)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AuthReply> for ResetPinKey {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(reply: AuthReply) -> Result<Self> {
|
||||
match reply {
|
||||
AuthReply::ResetPinKey(reply) => Ok(reply),
|
||||
_ => Err(Error::InternalError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ChangePin {
|
||||
pub success: bool,
|
||||
|
||||
@@ -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};
|
||||
@@ -56,6 +57,21 @@ impl From<SetPin> for AuthRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ResetPinKey {
|
||||
pub id: PinId,
|
||||
pub pin: Pin,
|
||||
pub retries: Option<u8>,
|
||||
/// If true, the PIN can be used to wrap/unwrap an application key
|
||||
pub key: KeyId,
|
||||
}
|
||||
|
||||
impl From<ResetPinKey> for AuthRequest {
|
||||
fn from(request: ResetPinKey) -> Self {
|
||||
Self::ResetPinKey(request)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ChangePin {
|
||||
pub id: PinId,
|
||||
|
||||
@@ -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.reset_set_pin_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| {
|
||||
|
||||
Reference in New Issue
Block a user