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

Allow gpg to run factory-reset when the state is corrupted #103

Merged
merged 6 commits into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
229 changes: 148 additions & 81 deletions src/command/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,29 +348,29 @@ impl GetDataObject {
match self {
Self::HistoricalBytes => historical_bytes(context)?,
Self::ApplicationIdentifier => context.reply.expand(&context.options.aid())?,
Self::PwStatusBytes => pw_status_bytes(context.load_state()?)?,
Self::PwStatusBytes => pw_status_bytes(context)?,
Self::ExtendedLengthInformation => context.reply.expand(&EXTENDED_LENGTH_INFO)?,
Self::ExtendedCapabilities => context.reply.expand(&EXTENDED_CAPABILITIES)?,
Self::GeneralFeatureManagement => context
.reply
.expand(&general_feature_management(context.options))?,
Self::AlgorithmAttributesSignature => alg_attr_sign(context.load_state()?)?,
Self::AlgorithmAttributesDecryption => alg_attr_dec(context.load_state()?)?,
Self::AlgorithmAttributesAuthentication => alg_attr_aut(context.load_state()?)?,
Self::AlgorithmAttributesSignature => alg_attr_sign(context)?,
Self::AlgorithmAttributesDecryption => alg_attr_dec(context)?,
Self::AlgorithmAttributesAuthentication => alg_attr_aut(context)?,
Self::AlgorithmInformation => algo_info(context)?,
Self::Fingerprints => fingerprints(context.load_state()?)?,
Self::CAFingerprints => ca_fingerprints(context.load_state()?)?,
Self::KeyGenerationDates => keygen_dates(context.load_state()?)?,
Self::KeyInformation => key_info(context.load_state()?)?,
Self::UifCds => uif(context.load_state()?, KeyType::Sign)?,
Self::UifDec => uif(context.load_state()?, KeyType::Dec)?,
Self::UifAut => uif(context.load_state()?, KeyType::Aut)?,
Self::CardHolderName => cardholder_name(context.load_state()?)?,
Self::CardHolderSex => cardholder_sex(context.load_state()?)?,
Self::LanguagePreferences => language_preferences(context.load_state()?)?,
Self::Fingerprints => fingerprints(context)?,
Self::CAFingerprints => ca_fingerprints(context)?,
Self::KeyGenerationDates => keygen_dates(context)?,
Self::KeyInformation => key_info(context)?,
Self::UifCds => uif(context, KeyType::Sign)?,
Self::UifDec => uif(context, KeyType::Dec)?,
Self::UifAut => uif(context, KeyType::Aut)?,
Self::CardHolderName => cardholder_name(context)?,
Self::CardHolderSex => cardholder_sex(context)?,
Self::LanguagePreferences => language_preferences(context)?,
Self::Url => get_arbitrary_do(context, ArbitraryDO::Url)?,
Self::LoginData => get_arbitrary_do(context, ArbitraryDO::LoginData)?,
Self::DigitalSignatureCounter => signature_counter(context.load_state()?)?,
Self::DigitalSignatureCounter => signature_counter(context)?,
Self::KdfDo => get_arbitrary_do(context, ArbitraryDO::KdfDo)?,
Self::PrivateUse1 => get_arbitrary_do(context, ArbitraryDO::PrivateUse1)?,
Self::PrivateUse2 => get_arbitrary_do(context, ArbitraryDO::PrivateUse2)?,
Expand Down Expand Up @@ -555,18 +555,31 @@ fn cardholder_cert<const R: usize, T: trussed::Client>(
}

fn pw_status_bytes<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
mut ctx: Context<'_, R, T>,
) -> Result<(), Status> {
let status = PasswordStatus {
pw1_valid_multiple: ctx.state.internal.pw1_valid_multiple(),
max_length_pw1: MAX_PIN_LENGTH as u8,
max_length_rc: MAX_PIN_LENGTH as u8,
max_length_pw3: MAX_PIN_LENGTH as u8,
error_counter_pw1: ctx.state.internal.remaining_tries(Password::Pw1),
// TODO when implementing RESET RETRY COUNTER
error_counter_rc: 3,
error_counter_pw3: ctx.state.internal.remaining_tries(Password::Pw3),
let status = if let Ok(ctx) = ctx.load_state() {
PasswordStatus {
pw1_valid_multiple: ctx.state.internal.pw1_valid_multiple(),
max_length_pw1: MAX_PIN_LENGTH as u8,
max_length_rc: MAX_PIN_LENGTH as u8,
max_length_pw3: MAX_PIN_LENGTH as u8,
error_counter_pw1: ctx.state.internal.remaining_tries(Password::Pw1),
error_counter_rc: ctx.state.internal.remaining_tries(Password::ResetCode),
error_counter_pw3: ctx.state.internal.remaining_tries(Password::Pw3),
}
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
PasswordStatus {
pw1_valid_multiple: false,
max_length_pw1: MAX_PIN_LENGTH as u8,
max_length_rc: MAX_PIN_LENGTH as u8,
max_length_pw3: MAX_PIN_LENGTH as u8,
error_counter_pw1: 3,
error_counter_rc: 3,
error_counter_pw3: 3,
}
};

let status: [u8; 7] = status.into();
ctx.reply.expand(&status)
}
Expand Down Expand Up @@ -594,48 +607,71 @@ fn algo_info<const R: usize, T: trussed::Client>(mut ctx: Context<'_, R, T>) ->
}

fn alg_attr_sign<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
mut ctx: Context<'_, R, T>,
) -> Result<(), Status> {
ctx.reply
.expand(ctx.state.internal.sign_alg().attributes())?;
Ok(())
if let Ok(mut ctx) = ctx.load_state() {
ctx.reply.expand(ctx.state.internal.sign_alg().attributes())
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
ctx.reply.expand(SignatureAlgorithm::default().attributes())
}
}

fn alg_attr_dec<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
mut ctx: Context<'_, R, T>,
) -> Result<(), Status> {
ctx.reply
.expand(ctx.state.internal.dec_alg().attributes())?;
Ok(())
if let Ok(mut ctx) = ctx.load_state() {
ctx.reply.expand(ctx.state.internal.dec_alg().attributes())
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
ctx.reply
.expand(DecryptionAlgorithm::default().attributes())
}
}

fn alg_attr_aut<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
mut ctx: Context<'_, R, T>,
) -> Result<(), Status> {
ctx.reply
.expand(ctx.state.internal.aut_alg().attributes())?;
Ok(())
if let Ok(mut ctx) = ctx.load_state() {
ctx.reply.expand(ctx.state.internal.aut_alg().attributes())
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
ctx.reply
.expand(AuthenticationAlgorithm::default().attributes())
}
}

fn fingerprints<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
mut ctx: Context<'_, R, T>,
) -> Result<(), Status> {
ctx.reply.expand(&ctx.state.internal.fingerprints().0)?;
Ok(())
if let Ok(mut ctx) = ctx.load_state() {
ctx.reply.expand(&ctx.state.internal.fingerprints().0)
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
ctx.reply.expand(&[0; 60])
}
}

fn ca_fingerprints<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
mut ctx: Context<'_, R, T>,
) -> Result<(), Status> {
ctx.reply.expand(&ctx.state.internal.ca_fingerprints().0)?;
Ok(())
if let Ok(mut ctx) = ctx.load_state() {
ctx.reply.expand(&ctx.state.internal.ca_fingerprints().0)
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
ctx.reply.expand(&[0; 60])
}
}

fn keygen_dates<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
mut ctx: Context<'_, R, T>,
) -> Result<(), Status> {
ctx.reply.expand(&ctx.state.internal.keygen_dates().0)?;
Ok(())
if let Ok(mut ctx) = ctx.load_state() {
ctx.reply.expand(&ctx.state.internal.keygen_dates().0)
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
ctx.reply.expand(&[0; 12])
}
}

fn key_info_byte(data: Option<KeyOrigin>) -> u8 {
Expand All @@ -646,64 +682,95 @@ fn key_info_byte(data: Option<KeyOrigin>) -> u8 {
}
}

fn key_info<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
) -> Result<(), Status> {
// Key-Ref. : Sig = 1, Dec = 2, Aut = 3 (see §7.2.18)
ctx.reply.expand(&[
0x01,
key_info_byte(ctx.state.internal.key_origin(KeyType::Sign)),
])?;
ctx.reply.expand(&[
0x02,
key_info_byte(ctx.state.internal.key_origin(KeyType::Dec)),
])?;
ctx.reply.expand(&[
0x03,
key_info_byte(ctx.state.internal.key_origin(KeyType::Aut)),
])?;
Ok(())
fn key_info<const R: usize, T: trussed::Client>(mut ctx: Context<'_, R, T>) -> Result<(), Status> {
if let Ok(mut ctx) = ctx.load_state() {
// Key-Ref. : Sig = 1, Dec = 2, Aut = 3 (see §7.2.18)
ctx.reply.expand(&[
0x01,
key_info_byte(ctx.state.internal.key_origin(KeyType::Sign)),
])?;
ctx.reply.expand(&[
0x02,
key_info_byte(ctx.state.internal.key_origin(KeyType::Dec)),
])?;
ctx.reply.expand(&[
0x03,
key_info_byte(ctx.state.internal.key_origin(KeyType::Aut)),
])?;
Ok(())
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
ctx.reply.expand(&hex!("010002000300"))
}
}

fn uif<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
mut ctx: Context<'_, R, T>,
key: KeyType,
) -> Result<(), Status> {
if !ctx.options.button_available {
warn!("GET DAT for uif without a button available");
return Err(Status::FunctionNotSupported);
}
if let Ok(mut ctx) = ctx.load_state() {
if !ctx.options.button_available {
warn!("GET DAT for uif without a button available");
return Err(Status::FunctionNotSupported);
}

let state_byte = ctx.state.internal.uif(key).as_byte();
let button_byte = general_feature_management_byte(ctx.options);
ctx.reply.expand(&[state_byte, button_byte])
let state_byte = ctx.state.internal.uif(key).as_byte();
let button_byte = general_feature_management_byte(ctx.options);
ctx.reply.expand(&[state_byte, button_byte])
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
ctx.reply.expand(&[
Uif::Disabled as u8,
general_feature_management_byte(ctx.options),
])
}
}

fn cardholder_name<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
mut ctx: Context<'_, R, T>,
) -> Result<(), Status> {
ctx.reply.expand(ctx.state.internal.cardholder_name())
if let Ok(mut ctx) = ctx.load_state() {
ctx.reply.expand(ctx.state.internal.cardholder_name())
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
ctx.reply.expand(b"Card state corrupted.")
}
}

fn cardholder_sex<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
mut ctx: Context<'_, R, T>,
) -> Result<(), Status> {
ctx.reply
.expand(&[ctx.state.internal.cardholder_sex() as u8])
if let Ok(mut ctx) = ctx.load_state() {
ctx.reply
.expand(&[ctx.state.internal.cardholder_sex() as u8])
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
ctx.reply.expand(&[Sex::NotKnown as u8])
}
}

fn language_preferences<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
mut ctx: Context<'_, R, T>,
) -> Result<(), Status> {
ctx.reply.expand(ctx.state.internal.language_preferences())
if let Ok(mut ctx) = ctx.load_state() {
ctx.reply.expand(ctx.state.internal.language_preferences())
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
ctx.reply.expand(b"")
}
}

fn signature_counter<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
mut ctx: Context<'_, R, T>,
) -> Result<(), Status> {
// Counter is only on 3 bytes
let resp = &ctx.state.internal.sign_count().to_be_bytes()[1..];
ctx.reply.expand(resp)
if let Ok(mut ctx) = ctx.load_state() {
let resp = &ctx.state.internal.sign_count().to_be_bytes()[1..];
ctx.reply.expand(resp)
} else {
// If the state doesn't load, return placeholder so that gpg presents the option to factory reset
ctx.reply.expand(&0u32.to_be_bytes()[1..])
}
}

fn get_arbitrary_do<const R: usize, T: trussed::Client>(
Expand Down
1 change: 0 additions & 1 deletion src/state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Copyright (C) 2022 Nitrokey GmbH
// let algo = ctx.state.internal.
// SPDX-License-Identifier: LGPL-3.0-only

use core::mem::swap;
Expand Down
2 changes: 1 addition & 1 deletion src/tlv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub fn take_len(data: &[u8]) -> Option<(usize, &[u8])> {
let l2 = *data.get(1)?;
let l3 = *data.get(2)?;
let len = u16::from_be_bytes([l2, l3]) as usize;
Some((len as usize, &data[3..]))
Some((len, &data[3..]))
}
}

Expand Down
10 changes: 5 additions & 5 deletions tests/command-response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ impl Default for OutputMatcher {

fn parse_hex(data: &str) -> Vec<u8> {
let tmp: String = data.split_whitespace().collect();
hex::decode(&tmp).unwrap()
hex::decode(tmp).unwrap()
}

impl OutputMatcher {
Expand Down Expand Up @@ -414,10 +414,10 @@ impl IoCmd {
expected_status: Status,
card: &mut opcard::Card<T>,
) {
println!("Command: {:x?}", input);
println!("Command: {input:x?}");
let mut rep: heapless::Vec<u8, 1024> = heapless::Vec::new();
let cmd: iso7816::Command<1024> = iso7816::Command::try_from(input).unwrap_or_else(|err| {
panic!("Bad command: {err:?}, for command: {}", hex::encode(&input))
panic!("Bad command: {err:?}, for command: {}", hex::encode(input))
});
let status: Status = card
.handle(&cmd, &mut rep)
Expand All @@ -428,10 +428,10 @@ impl IoCmd {
println!("Output: {:?}\nStatus: {status:?}", hex::encode(&rep));

if !output.validate(&rep) {
panic!("Bad output. Expected {:?}", output);
panic!("Bad output. Expected {output:?}");
}
if status != expected_status {
panic!("Bad status. Expected {:?}", expected_status);
panic!("Bad status. Expected {expected_status:?}");
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/gpg-status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fn gpg_card_status() {

let stdout = String::from_utf8_lossy(&output.stdout);
println!("=== stdout ===");
println!("{}", stdout);
println!("{stdout}");
println!("=== end stdout ===");

println!();
Expand Down