diff --git a/CHANGELOG.md b/CHANGELOG.md index dbd2170f..d273f0f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,14 @@ SPDX-License-Identifier: CC0-1.0 ### Features +- Support using authentication keys for decryption and vice-versa with MANAGE SECURITY ENVIRONMENT ([#60][]) - Support PIN resets using a resetting code ([#63][]) ### Bugfixes - Fix the length of the Digital signature counter DO 0x93 ([#76][]) +[#60]: https://github.com/Nitrokey/opcard-rs/pull/60 [#63]: https://github.com/Nitrokey/opcard-rs/pull/63 [#76]: https://github.com/Nitrokey/opcard-rs/pull/76 diff --git a/src/command.rs b/src/command.rs index 5e28be4c..a5400191 100644 --- a/src/command.rs +++ b/src/command.rs @@ -6,12 +6,13 @@ mod gen; mod private_key_template; mod pso; +use hex_literal::hex; use iso7816::Status; use crate::card::{Context, LoadedContext, RID}; use crate::error::Error; use crate::state::{ - LifeCycle, State, MAX_GENERIC_LENGTH, MAX_PIN_LENGTH, MIN_LENGTH_ADMIN_PIN, + KeyRef, LifeCycle, State, MAX_GENERIC_LENGTH, MAX_PIN_LENGTH, MIN_LENGTH_ADMIN_PIN, MIN_LENGTH_RESET_CODE, MIN_LENGTH_USER_PIN, }; use crate::tlv; @@ -82,6 +83,7 @@ impl Command { Self::SelectData(occurrence) => select_data(context, *occurrence), Self::GetChallenge(length) => get_challenge(context, *length), Self::ResetRetryCounter(mode) => reset_retry_conter(context.load_state()?, *mode), + Self::ManageSecurityEnvironment(mode) => manage_security_environment(context, *mode), _ => { error!("Command not yet implemented: {:x?}", self); Err(Status::FunctionNotSupported) @@ -299,7 +301,7 @@ impl TryFrom for GenerateAsymmetricKeyPairMode { } } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum ManageSecurityEnvironmentMode { Authentication, Dec, @@ -321,6 +323,7 @@ impl TryFrom for ManageSecurityEnvironmentMode { fn select(context: Context<'_, R, T>) -> Result<(), Status> { if context.data.starts_with(&RID) { context.state.runtime.cur_do = None; + context.state.runtime.keyrefs = Default::default(); Ok(()) } else { info!("Selected application {:x?} not found", context.data); @@ -630,3 +633,30 @@ fn get_challenge( Ok(()) } + +// § 7.2.18 +fn manage_security_environment( + ctx: Context<'_, R, T>, + mode: ManageSecurityEnvironmentMode, +) -> Result<(), Status> { + let key_ref = match ctx.data { + hex!("83 01 02") => KeyRef::Dec, + hex!("83 01 03") => KeyRef::Aut, + _ => { + warn!( + "Manage Security Environment called with invalid reference: {:x?}", + ctx.data + ); + return Err(Status::IncorrectDataParameter); + } + }; + info!("MANAGE SECURITY ENVIRONMENT: mode = {mode:?}, ref = {key_ref:?}"); + + match mode { + ManageSecurityEnvironmentMode::Dec => ctx.state.runtime.keyrefs.pso_decipher = key_ref, + ManageSecurityEnvironmentMode::Authentication => { + ctx.state.runtime.keyrefs.internal_aut = key_ref + } + } + Ok(()) +} diff --git a/src/command/pso.rs b/src/command/pso.rs index 8d525530..0dd7207d 100644 --- a/src/command/pso.rs +++ b/src/command/pso.rs @@ -7,6 +7,7 @@ use trussed::try_syscall; use trussed::types::*; use crate::card::LoadedContext; +use crate::state::KeyRef; use crate::tlv::get_do; use crate::types::*; @@ -14,18 +15,29 @@ fn check_uif( ctx: LoadedContext<'_, R, T>, key: KeyType, ) -> Result<(), Status> { - if ctx.state.internal.uif(key).is_enabled() - && !ctx - .backend - .confirm_user_present() - .map_err(|_| Status::UnspecifiedNonpersistentExecutionError)? - { + if ctx.state.internal.uif(key).is_enabled() { + prompt_uif(ctx) + } else { + Ok(()) + } +} + +fn prompt_uif( + ctx: LoadedContext<'_, R, T>, +) -> Result<(), Status> { + let success = ctx + .backend + .confirm_user_present() + .map_err(|_| Status::UnspecifiedNonpersistentExecutionError)?; + if !success { warn!("User presence confirmation timed out"); // FIXME SecurityRelatedIssues (0x6600 is not available?) - return Err(Status::SecurityStatusNotSatisfied); + Err(Status::SecurityStatusNotSatisfied) + } else { + Ok(()) } - Ok(()) } + // § 7.2.10 pub fn sign( mut ctx: LoadedContext<'_, R, T>, @@ -78,68 +90,129 @@ fn sign_ec( ctx.reply.expand(&signature) } +pub fn int_aut_key_mecha_uif( + ctx: LoadedContext<'_, R, T>, +) -> Result<(KeyId, Mechanism, bool), Status> { + let (key_type, mechanism) = match ctx.state.runtime.keyrefs.internal_aut { + KeyRef::Aut => ( + KeyType::Aut, + match ctx.state.internal.aut_alg() { + AuthenticationAlgorithm::EcDsaP256 => Mechanism::P256Prehashed, + AuthenticationAlgorithm::Ed255 => Mechanism::Ed255, + + AuthenticationAlgorithm::Rsa2k | AuthenticationAlgorithm::Rsa4k => { + error!("RSA is not implemented"); + return Err(Status::ConditionsOfUseNotSatisfied); + } + }, + ), + KeyRef::Dec => ( + KeyType::Dec, + match ctx.state.internal.dec_alg() { + DecryptionAlgorithm::X255 => { + warn!("Attempt to authenticate with X25519 key"); + return Err(Status::ConditionsOfUseNotSatisfied); + } + DecryptionAlgorithm::EcDhP256 => Mechanism::P256Prehashed, + DecryptionAlgorithm::Rsa2k | DecryptionAlgorithm::Rsa4k => { + error!("RSA is not implemented"); + return Err(Status::ConditionsOfUseNotSatisfied); + } + }, + ), + }; + + if mechanism == Mechanism::P256Prehashed && ctx.data.len() != 32 { + warn!( + "Attempt to sign with P256 with data length != 32: {}", + ctx.data.len() + ); + return Err(Status::ConditionsOfUseNotSatisfied); + } + + Ok(( + ctx.state.internal.key_id(key_type).ok_or_else(|| { + warn!("Attempt to INTERNAL AUTHENTICATE without a key set"); + Status::KeyReferenceNotFound + })?, + mechanism, + ctx.state.internal.uif(key_type).is_enabled(), + )) +} // § 7.2.13 pub fn internal_authenticate( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { - let key_id = ctx.state.internal.key_id(KeyType::Aut).ok_or_else(|| { - warn!("Attempt to authenticate without a key set"); - Status::KeyReferenceNotFound - })?; if !ctx.state.runtime.other_verified { warn!("Attempt to sign without PW1 verified"); return Err(Status::SecurityStatusNotSatisfied); } - check_uif(ctx.lend(), KeyType::Aut)?; - - match ctx.state.internal.aut_alg() { - AuthenticationAlgorithm::Ed255 => sign_ec(ctx, key_id, Mechanism::Ed255), - AuthenticationAlgorithm::EcDsaP256 => { - if ctx.data.len() != 32 { - return Err(Status::ConditionsOfUseNotSatisfied); - } - sign_ec(ctx, key_id, Mechanism::P256Prehashed) - } - _ => { - error!("Unimplemented operation"); - Err(Status::ConditionsOfUseNotSatisfied) - } + let (key_id, mechanism, uif) = int_aut_key_mecha_uif(ctx.lend())?; + if uif { + prompt_uif(ctx.lend())?; } + + sign_ec(ctx, key_id, mechanism) +} + +pub fn decipher_key_mecha_uif( + ctx: LoadedContext<'_, R, T>, +) -> Result<(KeyId, Mechanism, bool), Status> { + let (key_type, mechanism) = match ctx.state.runtime.keyrefs.pso_decipher { + KeyRef::Dec => ( + KeyType::Dec, + match ctx.state.internal.dec_alg() { + DecryptionAlgorithm::X255 => Mechanism::X255, + DecryptionAlgorithm::EcDhP256 => Mechanism::P256, + DecryptionAlgorithm::Rsa2k | DecryptionAlgorithm::Rsa4k => { + error!("RSA is not implemented"); + return Err(Status::ConditionsOfUseNotSatisfied); + } + }, + ), + KeyRef::Aut => ( + KeyType::Aut, + match ctx.state.internal.aut_alg() { + AuthenticationAlgorithm::EcDsaP256 => Mechanism::P256, + AuthenticationAlgorithm::Ed255 => { + warn!("Attempt to decipher with Ed255 key"); + return Err(Status::ConditionsOfUseNotSatisfied); + } + + AuthenticationAlgorithm::Rsa2k | AuthenticationAlgorithm::Rsa4k => { + error!("RSA is not implemented"); + return Err(Status::ConditionsOfUseNotSatisfied); + } + }, + ), + }; + + Ok(( + ctx.state.internal.key_id(key_type).ok_or_else(|| { + warn!("Attempt to decrypt without a key set"); + Status::KeyReferenceNotFound + })?, + mechanism, + ctx.state.internal.uif(key_type).is_enabled(), + )) } // § 7.2.11 pub fn decipher( - ctx: LoadedContext<'_, R, T>, + mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { - let key_id = ctx.state.internal.key_id(KeyType::Dec).ok_or_else(|| { - warn!("Attempt to authenticat without a key set"); - Status::KeyReferenceNotFound - })?; if !ctx.state.runtime.other_verified { warn!("Attempt to sign without PW1 verified"); return Err(Status::SecurityStatusNotSatisfied); } - if ctx.state.internal.uif(KeyType::Dec).is_enabled() - && !ctx - .backend - .confirm_user_present() - .map_err(|_| Status::UnspecifiedNonpersistentExecutionError)? - { - warn!("User presence confirmation timed out"); - // FIXME SecurityRelatedIssues (0x6600 is not available?) - return Err(Status::SecurityStatusNotSatisfied); + let (key_id, mechanism, uif) = decipher_key_mecha_uif(ctx.lend())?; + if uif { + prompt_uif(ctx.lend())?; } - match ctx.state.internal.dec_alg() { - DecryptionAlgorithm::X255 => decrypt_ec(ctx, key_id, Mechanism::X255), - DecryptionAlgorithm::EcDhP256 => decrypt_ec(ctx, key_id, Mechanism::P256), - _ => { - error!("Unimplemented operation"); - Err(Status::ConditionsOfUseNotSatisfied) - } - } + decrypt_ec(ctx, key_id, mechanism) } fn decrypt_ec( diff --git a/src/state.rs b/src/state.rs index 6e855aec..bf720744 100644 --- a/src/state.rs +++ b/src/state.rs @@ -709,12 +709,35 @@ impl Internal { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum KeyRef { + Dec, + Aut, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct KeyRefs { + // We can't use `KeyType` because the Signing key cannot be reassigned + pub pso_decipher: KeyRef, + pub internal_aut: KeyRef, +} + +impl Default for KeyRefs { + fn default() -> KeyRefs { + KeyRefs { + pso_decipher: KeyRef::Dec, + internal_aut: KeyRef::Aut, + } + } +} + #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct Runtime { pub sign_verified: bool, pub other_verified: bool, pub admin_verified: bool, pub cur_do: Option<(Tag, Occurrence)>, + pub keyrefs: KeyRefs, } /// DOs that can store arbitrary data from the user diff --git a/src/types.rs b/src/types.rs index 1b28d8b5..3db0532c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -221,7 +221,6 @@ impl TryFrom<&[u8]> for AuthenticationAlgorithm { } #[derive(Clone, Debug, Copy)] -#[allow(unused)] pub enum KeyType { Sign, Dec, diff --git a/tests/crypto-sequoia.rs b/tests/crypto-sequoia.rs index 2be84f38..fb5643ab 100644 --- a/tests/crypto-sequoia.rs +++ b/tests/crypto-sequoia.rs @@ -30,6 +30,9 @@ fn sequoia_gen_key() { let dec_pubk = public_key_material_to_key(&material, KeyType::Decryption, &gendate, None, None) .unwrap(); + let dec_pubk_aut = + public_key_material_to_key(&material, KeyType::Authentication, &gendate, None, None) + .unwrap(); let (material, gendate) = admin .generate_key_simple(KeyType::Authentication, Some(AlgoSimple::NIST256)) @@ -37,6 +40,9 @@ fn sequoia_gen_key() { let aut_pubk = public_key_material_to_key(&material, KeyType::Authentication, &gendate, None, None) .unwrap(); + let aut_pubk_dec = + public_key_material_to_key(&material, KeyType::Decryption, &gendate, None, None) + .unwrap(); let (material, gendate) = admin .generate_key_simple(KeyType::Signing, Some(AlgoSimple::NIST256)) @@ -58,6 +64,9 @@ fn sequoia_gen_key() { let mut authenticator = user_card.authenticator_from_public(aut_pubk.clone(), &|| {}); let data = [2; 32]; let signature = authenticator.sign(HashAlgorithm::SHA256, &data).unwrap(); + assert!(dec_pubk_aut + .verify(&signature, HashAlgorithm::SHA256, &data) + .is_err()); assert!(aut_pubk .verify(&signature, HashAlgorithm::SHA256, &data) .is_ok()); @@ -67,6 +76,28 @@ fn sequoia_gen_key() { let ciphertext = dec_pubk.encrypt(&session).unwrap(); let mut decryptor = user_card.decryptor_from_public(dec_pubk, &|| {}); assert_eq!(session, decryptor.decrypt(&ciphertext, Some(32)).unwrap()); + + open.manage_security_environment(KeyType::Authentication, KeyType::Decryption) + .unwrap(); + let mut user_card = open.user_card().unwrap(); + let mut authenticator = user_card.authenticator_from_public(aut_pubk.clone(), &|| {}); + let data = [3; 32]; + let signature = authenticator.sign(HashAlgorithm::SHA256, &data).unwrap(); + assert!(aut_pubk + .verify(&signature, HashAlgorithm::SHA256, &data) + .is_err()); + dec_pubk_aut + .verify(&signature, HashAlgorithm::SHA256, &data) + .unwrap(); + + open.manage_security_environment(KeyType::Decryption, KeyType::Authentication) + .unwrap(); + let mut user_card = open.user_card().unwrap(); + let mut session = SessionKey::new(19); + session[0] = 7; + let ciphertext = aut_pubk_dec.encrypt(&session).unwrap(); + let mut decryptor = user_card.decryptor_from_public(aut_pubk_dec, &|| {}); + assert_eq!(session, decryptor.decrypt(&ciphertext, Some(32)).unwrap()); }); virt::with_vsc(|| { @@ -117,7 +148,28 @@ fn sequoia_gen_key() { let mut session = SessionKey::new(19); session[0] = 7; let ciphertext = dec_pubk.encrypt(&session).unwrap(); - let mut decryptor = user_card.decryptor_from_public(dec_pubk, &|| {}); + let mut decryptor = user_card.decryptor_from_public(dec_pubk.clone(), &|| {}); assert_eq!(session, decryptor.decrypt(&ciphertext, None).unwrap()); + + open.manage_security_environment(KeyType::Authentication, KeyType::Decryption) + .unwrap(); + let mut user_card = open.user_card().unwrap(); + let mut authenticator = user_card.authenticator_from_public(aut_pubk, &|| {}); + let data = [3; 32]; + // Signature with X25519 key should fail + let _ = authenticator + .sign(HashAlgorithm::SHA256, &data) + .unwrap_err(); + + open.manage_security_environment(KeyType::Decryption, KeyType::Authentication) + .unwrap(); + let mut user_card = open.user_card().unwrap(); + let mut session = SessionKey::new(19); + session[0] = 7; + let ciphertext = dec_pubk.encrypt(&session).unwrap(); + let mut decryptor = user_card.decryptor_from_public(dec_pubk, &|| {}); + + // X25519 with and EdDSA key should fail + decryptor.decrypt(&ciphertext, None).unwrap_err(); }); }