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 3 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
231 changes: 150 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,32 @@ 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),
// TODO when implementing RESET RETRY COUNTER
sosthene-nitrokey marked this conversation as resolved.
Show resolved Hide resolved
error_counter_rc: 3,
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 +608,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 +683,96 @@ 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. Factory reset recommended")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove the second sentence because I’d prefer people telling us in support if they run into this issue. We could still document it in the release notes, but if this happens in a different context, I’d like to know.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I recommend them to open an issue ? The limit is 39 characters so I don't think we can put a link.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use the URL DO for that

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The limit at 39 characters doesn't seem to be an issue with GPG.

}
}

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
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
4 changes: 2 additions & 2 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 @@ -417,7 +417,7 @@ impl IoCmd {
println!("Command: {:x?}", input);
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 Down