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

Encrypt keys with trussed-auth #127

Closed
wants to merge 13 commits into from
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ trussed = "0.1.0"
trussed-rsa-alloc = { version = "0.1.0", optional = true }
serde_repr = "0.1"
hex-literal = "0.3.4"
trussed-auth = "0.1.0"
trussed-auth = "0.2.0"

# optional dependencies
apdu-dispatch = { version = "0.1", optional = true }
Expand Down Expand Up @@ -85,8 +85,8 @@ log-error = []
interchange = { git = "https://github.com/trussed-dev/interchange", rev = "fe5633466640e1e9a8c06d9b5dd1d0af08c272af" }
littlefs2 = { git = "https://github.com/Nitrokey/littlefs2", tag = "v0.3.2-nitrokey-2" }
p256-cortex-m4 = { git = "https://github.com/Nitrokey/p256-cortex-m4", tag = "v0.1.0-alpha.6-nitrokey-1" }
trussed = { git = "https://github.com/Nitrokey/trussed" , tag = "v0.1.0-nitrokey.8" }
trussed-auth = { git = "https://github.com/trussed-dev/trussed-auth.git", tag= "v0.1.0"}
trussed = { git = "https://github.com/Nitrokey/trussed" , rev = "fc4758a0ee3b23b3f358e94dec461ace843eecc5" }
trussed-auth = { git = "https://github.com/trussed-dev/trussed-auth.git", tag= "v0.2.0"}
trussed-rsa-alloc = { git = "https://github.com/Nitrokey/trussed-rsa-backend", rev = "311d2366f99cc300b03d61e7f6a0a07abd3e8700" }

[package.metadata.docs.rs]
Expand Down
28 changes: 9 additions & 19 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ fn reset_retry_conter<const R: usize, T: trussed::Client + AuthClient>(
}

fn reset_retry_conter_with_p3<const R: usize, T: trussed::Client + AuthClient>(
ctx: LoadedContext<'_, R, T>,
mut ctx: LoadedContext<'_, R, T>,
) -> Result<(), Status> {
if ctx.data.len() < MIN_LENGTH_USER_PIN || ctx.data.len() > MAX_PIN_LENGTH {
warn!(
Expand All @@ -568,13 +568,7 @@ fn reset_retry_conter_with_p3<const R: usize, T: trussed::Client + AuthClient>(
}

ctx.state
.persistent
.set_pin(
ctx.backend.client_mut(),
ctx.options.storage,
ctx.data,
Password::Pw1,
)
.reset_user_code_with_pw3(ctx.backend.client_mut(), ctx.options.storage, ctx.data)
.map_err(|_err| {
error!("Failed to change PIN: {_err}");
Status::UnspecifiedNonpersistentExecutionError
Expand All @@ -601,7 +595,7 @@ fn reset_retry_conter_with_code<const R: usize, T: trussed::Client + AuthClient>
let res = ctx
.state
.check_pin(ctx.backend.client_mut(), old, Password::ResetCode);
match res {
let rc_key = match res {
Err(Error::InvalidPin) => {
return Err(Status::RemainingRetries(
ctx.state
Expand All @@ -613,26 +607,22 @@ fn reset_retry_conter_with_code<const R: usize, T: trussed::Client + AuthClient>
error!("Failed to check reset code: {_err:?}");
return Err(Status::UnspecifiedNonpersistentExecutionError);
}
Ok(_reset_kek) => {}
}
Ok(rc_key) => rc_key,
};

if new.len() > MAX_PIN_LENGTH || new.len() < MIN_LENGTH_USER_PIN {
warn!("Attempt to set resetting code with invalid length");
return Err(Status::IncorrectDataParameter);
}

ctx.state
.persistent
.set_pin(
ctx.backend.client_mut(),
ctx.options.storage,
new,
Password::Pw1,
)
.reset_user_code_with_rc(ctx.backend.client_mut(), ctx.options.storage, new, rc_key)
.map_err(|_err| {
error!("Failed to change PIN: {_err:?}");
Status::UnspecifiedNonpersistentExecutionError
})
})?;
syscall!(ctx.backend.client_mut().delete(rc_key));
Ok(())
}

// § 7.2.5
Expand Down
142 changes: 120 additions & 22 deletions src/command/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ use hex_literal::hex;
use iso7816::Status;
use trussed::{
syscall, try_syscall,
types::{KeySerialization, Mechanism},
types::{KeyId, KeySerialization, Mechanism},
};
use trussed_auth::AuthClient;

const CHACHA_NONCE_SIZE: usize = 12;
const CHACHA_TAG_SIZE: usize = 16;

use crate::{
card::{Context, LoadedContext, Options},
command::{GetDataMode, Password, PutDataMode, Tag},
Expand Down Expand Up @@ -375,8 +378,12 @@ impl GetDataObject {
Self::KdfDo => get_arbitrary_do(context, ArbitraryDO::KdfDo)?,
Self::PrivateUse1 => get_arbitrary_do(context, ArbitraryDO::PrivateUse1)?,
Self::PrivateUse2 => get_arbitrary_do(context, ArbitraryDO::PrivateUse2)?,
Self::PrivateUse3 => get_arbitrary_do(context, ArbitraryDO::PrivateUse3)?,
Self::PrivateUse4 => get_arbitrary_do(context, ArbitraryDO::PrivateUse4)?,
Self::PrivateUse3 => {
get_arbitrary_user_enc_do(context.load_state()?, ArbitraryDO::PrivateUse3)?
}
Self::PrivateUse4 => {
get_arbitrary_admin_enc_do(context.load_state()?, ArbitraryDO::PrivateUse4)?
}
Self::CardHolderCertificate => cardholder_cert(context)?,
Self::SecureMessagingCertificate => return Err(Status::SecureMessagingNotSupported),
Self::CardHolderRelatedData
Expand Down Expand Up @@ -811,6 +818,54 @@ fn get_arbitrary_do<const R: usize, T: trussed::Client + AuthClient>(
ctx.reply.expand(&data)
}

fn get_arbitrary_enc_do<const R: usize, T: trussed::Client + AuthClient>(
mut ctx: LoadedContext<'_, R, T>,
obj: ArbitraryDO,
key: KeyId,
) -> Result<(), Status> {
let data = obj
.load(ctx.backend.client_mut(), ctx.options.storage)
.map_err(|_| Status::UnspecifiedNonpersistentExecutionError)?;
if data.is_empty() {
return Ok(());
}
let (data, tag) = data.split_at(data.len() - CHACHA_TAG_SIZE);
let (data, nonce) = data.split_at(data.len() - CHACHA_NONCE_SIZE);
let decrypted = syscall!(ctx.backend.client_mut().decrypt(
Mechanism::Chacha8Poly1305,
key,
data,
&[],
nonce,
tag
))
.plaintext
.ok_or(Status::UnspecifiedNonpersistentExecutionError)?;
ctx.reply.expand(&decrypted)
}

/// Get an arbitrary DO encrypted with the user key
fn get_arbitrary_user_enc_do<const R: usize, T: trussed::Client + AuthClient>(
ctx: LoadedContext<'_, R, T>,
obj: ArbitraryDO,
) -> Result<(), Status> {
let Some(k) = ctx.state.volatile.other_verified_kek() else {
return Err(Status::SecurityStatusNotSatisfied);
};
get_arbitrary_enc_do(ctx, obj, k)
}

/// Get an arbitrary DO encrypted with the admin key
fn get_arbitrary_admin_enc_do<const R: usize, T: trussed::Client + AuthClient>(
ctx: LoadedContext<'_, R, T>,
obj: ArbitraryDO,
) -> Result<(), Status> {
let Some(k) = ctx.state.volatile.admin_kek() else {
return Err(Status::SecurityStatusNotSatisfied);
};
get_arbitrary_enc_do(ctx, obj, k)
}

// § 7.2.8
pub fn put_data<const R: usize, T: trussed::Client + AuthClient>(
mut context: Context<'_, R, T>,
Expand Down Expand Up @@ -889,7 +944,7 @@ enum_subset! {
impl PutDataObject {
fn write_perm(&self) -> PermissionRequirement {
match self {
Self::PrivateUse2 | Self::PrivateUse4 => PermissionRequirement::User,
Self::PrivateUse1 | Self::PrivateUse3 => PermissionRequirement::User,
_ => PermissionRequirement::Admin,
}
}
Expand All @@ -901,8 +956,12 @@ impl PutDataObject {
match self {
Self::PrivateUse1 => put_arbitrary_do(ctx, ArbitraryDO::PrivateUse1)?,
Self::PrivateUse2 => put_arbitrary_do(ctx, ArbitraryDO::PrivateUse2)?,
Self::PrivateUse3 => put_arbitrary_do(ctx, ArbitraryDO::PrivateUse3)?,
Self::PrivateUse4 => put_arbitrary_do(ctx, ArbitraryDO::PrivateUse4)?,
Self::PrivateUse3 => {
put_arbitrary_user_enc_do(ctx.load_state()?, ArbitraryDO::PrivateUse3)?
}
Self::PrivateUse4 => {
put_arbitrary_admin_enc_do(ctx.load_state()?, ArbitraryDO::PrivateUse4)?
}
Self::LoginData => put_arbitrary_do(ctx, ArbitraryDO::LoginData)?,
Self::ExtendedHeaderList => {
super::private_key_template::put_private_key_template(ctx.load_state()?)?
Expand Down Expand Up @@ -954,7 +1013,7 @@ fn put_cardholder_cert<const R: usize, T: trussed::Client + AuthClient>(
const AES256_KEY_LEN: usize = 32;

fn put_enc_dec_key<const R: usize, T: trussed::Client + AuthClient>(
ctx: LoadedContext<'_, R, T>,
mut ctx: LoadedContext<'_, R, T>,
) -> Result<(), Status> {
if ctx.data.len() != AES256_KEY_LEN {
warn!(
Expand All @@ -976,23 +1035,18 @@ fn put_enc_dec_key<const R: usize, T: trussed::Client + AuthClient>(
})?
.key;

let old_key = ctx
.state
.persistent
.set_aes_key_id(Some(new_key), ctx.backend.client_mut(), ctx.options.storage)
ctx.state
.set_aes_key(new_key, ctx.backend.client_mut(), ctx.options.storage)
.map_err(|_err| {
error!("Failed to set new key: {_err:?}");
Status::UnspecifiedNonpersistentExecutionError
})?;
if let Some(old_key) = old_key {
syscall!(ctx.backend.client_mut().delete(old_key));
}

Ok(())
}

fn put_resetting_code<const R: usize, T: trussed::Client + AuthClient>(
ctx: LoadedContext<'_, R, T>,
mut ctx: LoadedContext<'_, R, T>,
) -> Result<(), Status> {
if ctx.data.is_empty() {
info!("Removing resetting code");
Expand All @@ -1014,13 +1068,7 @@ fn put_resetting_code<const R: usize, T: trussed::Client + AuthClient>(
}

ctx.state
.persistent
.set_pin(
ctx.backend.client_mut(),
ctx.options.storage,
ctx.data,
Password::ResetCode,
)
.set_reset_code(ctx.backend.client_mut(), ctx.options.storage, ctx.data)
.map_err(|_err| {
error!("Failed to change resetting code: {_err}");
Status::UnspecifiedNonpersistentExecutionError
Expand Down Expand Up @@ -1200,6 +1248,56 @@ fn put_alg_attributes_aut<const R: usize, T: trussed::Client + AuthClient>(
.map_err(|_| Status::UnspecifiedNonpersistentExecutionError)
}

fn put_arbitrary_admin_enc_do<const R: usize, T: trussed::Client + AuthClient>(
ctx: LoadedContext<'_, R, T>,
obj: ArbitraryDO,
) -> Result<(), Status> {
let Some(k) = ctx.state.volatile.admin_kek() else {
return Err(Status::SecurityStatusNotSatisfied);
};
put_arbitrary_enc_do(ctx, obj, k)
}
fn put_arbitrary_user_enc_do<const R: usize, T: trussed::Client + AuthClient>(
ctx: LoadedContext<'_, R, T>,
obj: ArbitraryDO,
) -> Result<(), Status> {
let Some(k) =ctx.state.volatile.other_verified_kek() else {
return Err(Status::SecurityStatusNotSatisfied);
};
put_arbitrary_enc_do(ctx, obj, k)
}

fn put_arbitrary_enc_do<const R: usize, T: trussed::Client + AuthClient>(
ctx: LoadedContext<'_, R, T>,
obj: ArbitraryDO,
key: KeyId,
) -> Result<(), Status> {
if ctx.data.len() + CHACHA_NONCE_SIZE + CHACHA_TAG_SIZE > MAX_GENERIC_LENGTH {
return Err(Status::WrongLength);
}
let mut encrypted = syscall!(ctx.backend.client_mut().encrypt(
Mechanism::Chacha8Poly1305,
key,
ctx.data,
&[],
None
));
encrypted
.ciphertext
.extend_from_slice(&encrypted.nonce)
.map_err(|_| Status::NotEnoughMemory)?;
encrypted
.ciphertext
.extend_from_slice(&encrypted.tag)
.map_err(|_| Status::NotEnoughMemory)?;
obj.save(
ctx.backend.client_mut(),
ctx.options.storage,
&encrypted.ciphertext,
)
.map_err(|_| Status::UnspecifiedPersistentExecutionError)
}

fn put_arbitrary_do<const R: usize, T: trussed::Client + AuthClient>(
ctx: Context<'_, R, T>,
obj: ArbitraryDO,
Expand Down
Loading