Skip to content

Commit

Permalink
Merge pull request #103 from Nitrokey/corrupted-state-gpg
Browse files Browse the repository at this point in the history
Allow gpg to run factory-reset when the state is corrupted
  • Loading branch information
sosthene-nitrokey authored Feb 1, 2023
2 parents 53673fe + c51db89 commit f318ed5
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 89 deletions.
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 @@ -597,48 +610,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 @@ -649,64 +685,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

0 comments on commit f318ed5

Please sign in to comment.