Skip to content

Commit

Permalink
Encrypt private use DOs
Browse files Browse the repository at this point in the history
  • Loading branch information
sosthene-nitrokey authored and robin-nitrokey committed Apr 11, 2023
1 parent 8bd6317 commit 9ac1085
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 8 deletions.
127 changes: 121 additions & 6 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,57 @@ 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> {
if !ctx.state.volatile.other_verified() {
return Err(Status::SecurityStatusNotSatisfied);
}
// Unwrap cannnot fail becaus of above check
#[allow(clippy::unwrap_used)]
let k = ctx.state.volatile.user_kek().unwrap();
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 +947,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 +959,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 @@ -1194,6 +1256,59 @@ 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> {
if !ctx.state.volatile.other_verified() {
return Err(Status::SecurityStatusNotSatisfied);
}
// Unwrap cannnot fail becaus of above check
#[allow(clippy::unwrap_used)]
let k = ctx.state.volatile.user_kek().unwrap();
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
14 changes: 14 additions & 0 deletions tests/card/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ impl<T: trussed::Client + AuthClient + Send + Sync + 'static> Card<T> {
let tx = openpgp.transaction().expect("failed to create transaction");
f(tx)
}
pub fn with_many_tx(
&mut self,
fs: impl IntoIterator<Item = impl FnOnce(OpenPgpTransaction<'_>)>,
) {
for f in fs {
self.with_tx(f);
self.0.lock().unwrap().reset();
}
}

pub fn reset(&self) {
self.0.lock().unwrap().reset();
Expand Down Expand Up @@ -139,6 +148,11 @@ pub fn with_tx<F: FnOnce(OpenPgpTransaction<'_>) -> R, R>(f: F) -> R {
with_card(move |mut card| card.with_tx(f))
}

#[cfg(not(feature = "dangerous-test-real-card"))]
pub fn with_many_tx(fs: impl IntoIterator<Item = impl FnOnce(OpenPgpTransaction<'_>)>) {
with_card(move |mut card| card.with_many_tx(fs))
}

#[cfg(not(feature = "dangerous-test-real-card"))]
pub fn error_to_retries(err: Result<(), openpgp_card::Error>) -> Option<u8> {
match err {
Expand Down
91 changes: 89 additions & 2 deletions tests/dos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ use hex_literal::hex;
mod card;
use test_log::test;

use card::with_tx_options;
use card::{with_many_tx, with_tx_options};

use opcard::Options;

use openpgp_card::card_do::{ApplicationIdentifier, HistoricalBytes, Lang, Sex, TouchPolicy};
use openpgp_card::{
card_do::{ApplicationIdentifier, HistoricalBytes, Lang, Sex, TouchPolicy},
OpenPgpTransaction,
};

#[test]
fn get_data() {
Expand Down Expand Up @@ -131,3 +134,87 @@ fn get_data() {
}
});
}

#[test]
fn arbitrary() {
with_many_tx([|mut tx: OpenPgpTransaction<'_>| {
assert_eq!(tx.private_use_do(1).unwrap(), b"");
assert_eq!(tx.private_use_do(2).unwrap(), b"");
assert!(tx.private_use_do(3).is_err());
assert!(tx.private_use_do(4).is_err());
tx.verify_pw3(b"12345678").unwrap();
assert!(tx.private_use_do(3).is_err());
assert_eq!(tx.private_use_do(4).unwrap(), b"");
tx.set_private_use_do(2, b"private use 2".to_vec()).unwrap();
assert_eq!(tx.private_use_do(2).unwrap(), b"private use 2");
tx.set_private_use_do(4, b"private use 4".to_vec()).unwrap();
assert_eq!(tx.private_use_do(4).unwrap(), b"private use 4");

// Check that password change doesn't prevent reading
tx.change_pw3(b"12345678", b"new admin pin").unwrap();
tx.verify_pw3(b"new admin pin").unwrap();
assert_eq!(tx.private_use_do(2).unwrap(), b"private use 2");
assert_eq!(tx.private_use_do(4).unwrap(), b"private use 4");
}]);
with_many_tx([
|mut tx: OpenPgpTransaction<'_>| {
assert_eq!(tx.private_use_do(1).unwrap(), b"");
assert_eq!(tx.private_use_do(2).unwrap(), b"");
assert!(tx.private_use_do(3).is_err());
assert!(tx.private_use_do(4).is_err());
tx.verify_pw1_user(b"123456").unwrap();
assert_eq!(tx.private_use_do(3).unwrap(), b"");
assert!(tx.private_use_do(4).is_err());
tx.set_private_use_do(1, b"private use 1".to_vec()).unwrap();
assert_eq!(tx.private_use_do(1).unwrap(), b"private use 1");
tx.set_private_use_do(3, b"private use 3".to_vec()).unwrap();
assert_eq!(tx.private_use_do(3).unwrap(), b"private use 3");

// Check that password change doesn't prevent reading
tx.change_pw1(b"123456", b"new user pin").unwrap();
tx.verify_pw1_user(b"new user pin").unwrap();
assert_eq!(tx.private_use_do(1).unwrap(), b"private use 1");
assert_eq!(tx.private_use_do(3).unwrap(), b"private use 3");

// Check that password reset code use doesn't prevent reading

tx.verify_pw3(b"12345678").unwrap();
tx.reset_retry_counter_pw1(b"pin from PW3", None).unwrap();
tx.set_resetting_code(b"reseting code").unwrap();
},
|mut tx: OpenPgpTransaction<'_>| {
tx.verify_pw1_user(b"pin from PW3").unwrap();
assert_eq!(tx.private_use_do(1).unwrap(), b"private use 1");
assert_eq!(tx.private_use_do(3).unwrap(), b"private use 3");
},
|mut tx: OpenPgpTransaction<'_>| {
tx.reset_retry_counter_pw1(b"pin from RC", Some(b"reseting code"))
.unwrap();
tx.verify_pw1_user(b"pin from RC").unwrap();
assert_eq!(tx.private_use_do(1).unwrap(), b"private use 1");
assert_eq!(tx.private_use_do(3).unwrap(), b"private use 3");

tx.change_pw3(b"12345678", b"changed admin pin").unwrap();
tx.verify_pw3(b"changed admin pin").unwrap();
tx.reset_retry_counter_pw1(b"pin from changed PW3", None)
.unwrap();
tx.set_resetting_code(b"reseting code with changed PW3")
.unwrap();
},
|mut tx: OpenPgpTransaction<'_>| {
tx.verify_pw1_user(b"pin from changed PW3").unwrap();
assert_eq!(tx.private_use_do(1).unwrap(), b"private use 1");
assert_eq!(tx.private_use_do(3).unwrap(), b"private use 3");
},
|mut tx: OpenPgpTransaction<'_>| {
tx.reset_retry_counter_pw1(
b"pin from RC with changed PW3",
Some(b"reseting code with changed PW3"),
)
.unwrap();
tx.verify_pw1_user(b"pin from RC with changed PW3").unwrap();
assert_eq!(tx.private_use_do(1).unwrap(), b"private use 1");
assert_eq!(tx.private_use_do(3).unwrap(), b"private use 3");
},
]);
}

0 comments on commit 9ac1085

Please sign in to comment.