diff --git a/Cargo.toml b/Cargo.toml index ee3e384..755c855 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,9 @@ hex = { version = "0.4", features = ["serde"] } [features] std = [] virtual = ["std", "vpicc"] +rsa2048 = ["trussed/rsa2048"] +rsa4096 = ["rsa2048", "trussed/rsa4096"] +rsa4096-gen = ["rsa4096"] # used for delog log-all = [] @@ -63,9 +66,17 @@ log-warn = [] log-error = [] [patch.crates-io] -trussed = { git = "https://github.com/trussed-dev/trussed" , rev = "6de826f3bcbef247e55fd890d80d9ed6ce9f0abc" } +# trussed = { git = "https://github.com/trussed-dev/trussed" , rev = "6de826f3bcbef247e55fd890d80d9ed6ce9f0abc" } +trussed = { git = "https://github.com/nitrokey/trussed" , branch = "rsa-import" } littlefs2-sys = { git = "https://github.com/sosthene-nitrokey/littlefs2-sys.git", branch = "bindgen-runtime-feature" } interchange = { git = "https://github.com/trussed-dev/interchange.git", rev = "fe5633466640e1e9a8c06d9b5dd1d0af08c272af" } +p256-cortex-m4 = { git = "https://github.com/sosthene-nitrokey/p256-cortex-m4.git", branch = "upgrade" } [package.metadata.docs.rs] all-features = true + +[profile.dev.package.rsa] +opt-level = 2 + +[profile.dev.package.num-bigint-dig] +opt-level = 2 diff --git a/Makefile b/Makefile index 399d63f..8d68136 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ FUZZ_DURATION?="0" .PHONY: check check: cargo check --all-features --all-targets --workspace - cargo check --no-default-features + cargo check --no-default-features --all-targets cargo clippy --all-features --all-targets -- --deny warnings cargo fmt -- --check RUSTDOCFLAGS='-Dwarnings' cargo doc --all-features --package opcard @@ -24,7 +24,8 @@ fix: .PHONY: test test: - cargo test --features virtual + cargo test --features virtual,rsa2048,rsa4096-gen gpg_crypto,sequoia_gen_key + cargo test --features virtual,rsa2048,rsa4096 .PHONY: fuzz fuzz: fuzz-corpus @@ -45,7 +46,7 @@ fuzz-cov: .PHONY: tarpaulin tarpaulin: - cargo tarpaulin --features virtual -o Html -o Xml + cargo tarpaulin --features virtual,rsa4096-gen -o Html -o Xml .PHONY: ci ci: check tarpaulin diff --git a/src/command/data.rs b/src/command/data.rs index 2917599..42e64ec 100644 --- a/src/command/data.rs +++ b/src/command/data.rs @@ -1300,15 +1300,15 @@ mod tests { (DataObject::ExtendedCapabilities, &EXTENDED_CAPABILITIES), ( DataObject::AlgorithmAttributesSignature, - SignatureAlgorithm::Rsa2k.attributes(), + SignatureAlgorithm::Rsa2048.attributes(), ), ( DataObject::AlgorithmAttributesDecryption, - DecryptionAlgorithm::Rsa2k.attributes(), + DecryptionAlgorithm::Rsa2048.attributes(), ), ( DataObject::AlgorithmAttributesAuthentication, - AuthenticationAlgorithm::Rsa2k.attributes(), + AuthenticationAlgorithm::Rsa2048.attributes(), ), ( DataObject::PwStatusBytes, diff --git a/src/command/gen.rs b/src/command/gen.rs index 3a8707e..5896ffa 100644 --- a/src/command/gen.rs +++ b/src/command/gen.rs @@ -3,7 +3,7 @@ use hex_literal::hex; use iso7816::Status; -use trussed::types::{KeyId, KeySerialization, Location, StorageAttributes}; +use trussed::types::{KeyId, KeySerialization, Location, Mechanism, StorageAttributes}; use trussed::{syscall, try_syscall}; use crate::card::LoadedContext; @@ -34,9 +34,14 @@ pub fn sign( SignatureAlgorithm::EcDsaP256 => { gen_ec_key(ctx.lend(), KeyType::Sign, CurveAlgo::EcDsaP256) } - _ => { - error!("Unimplemented operation"); - Err(Status::ConditionsOfUseNotSatisfied) + SignatureAlgorithm::Rsa2048 => { + gen_rsa_key(ctx.lend(), KeyType::Sign, Mechanism::Rsa2048Pkcs) + } + SignatureAlgorithm::Rsa4096 => { + #[cfg(feature = "rsa4096-gen")] + return gen_rsa_key(ctx.lend(), KeyType::Sign, Mechanism::Rsa4096Pkcs); + #[cfg(not(feature = "rsa4096-gen"))] + return Err(Status::FunctionNotSupported); } } } @@ -49,9 +54,14 @@ pub fn dec( match algo { DecryptionAlgorithm::X255 => gen_ec_key(ctx.lend(), KeyType::Dec, CurveAlgo::X255), DecryptionAlgorithm::EcDhP256 => gen_ec_key(ctx.lend(), KeyType::Dec, CurveAlgo::EcDhP256), - _ => { - error!("Unimplemented operation"); - Err(Status::ConditionsOfUseNotSatisfied) + DecryptionAlgorithm::Rsa2048 => { + gen_rsa_key(ctx.lend(), KeyType::Dec, Mechanism::Rsa2048Pkcs) + } + DecryptionAlgorithm::Rsa4096 => { + #[cfg(feature = "rsa4096-gen")] + return gen_rsa_key(ctx.lend(), KeyType::Dec, Mechanism::Rsa4096Pkcs); + #[cfg(not(feature = "rsa4096-gen"))] + return Err(Status::FunctionNotSupported); } } } @@ -66,13 +76,51 @@ pub fn aut( AuthenticationAlgorithm::EcDsaP256 => { gen_ec_key(ctx.lend(), KeyType::Aut, CurveAlgo::EcDsaP256) } - _ => { - error!("Unimplemented operation"); - Err(Status::ConditionsOfUseNotSatisfied) + AuthenticationAlgorithm::Rsa2048 => { + gen_rsa_key(ctx.lend(), KeyType::Aut, Mechanism::Rsa2048Pkcs) + } + AuthenticationAlgorithm::Rsa4096 => { + #[cfg(feature = "rsa4096-gen")] + return gen_rsa_key(ctx.lend(), KeyType::Aut, Mechanism::Rsa4096Pkcs); + #[cfg(not(feature = "rsa4096-gen"))] + return Err(Status::FunctionNotSupported); } } } +#[cfg(feature = "rsa2048")] +fn gen_rsa_key( + ctx: LoadedContext<'_, R, T>, + key: KeyType, + mechanism: Mechanism, +) -> Result<(), Status> { + let client = ctx.backend.client_mut(); + let key_id = try_syscall!(client.generate_key( + mechanism, + StorageAttributes::new().set_persistence(Location::Internal) + )) + .map_err(|_err| { + error!("Failed to generate key: {_err:?}"); + Status::UnspecifiedNonpersistentExecutionError + })? + .key; + + if let Some((old_key, _)) = ctx + .state + .internal + .set_key_id(key, Some((key_id, KeyOrigin::Generated)), client) + .map_err(|_| Status::UnspecifiedNonpersistentExecutionError)? + { + // Deletion is not a fatal error + try_syscall!(client.delete(old_key)) + .inspect_err_stable(|_err| { + error!("Failed to delete old key: {_err:?}"); + }) + .ok(); + } + read_rsa_key(ctx, key_id, mechanism) +} + fn gen_ec_key( ctx: LoadedContext<'_, R, T>, key: KeyType, @@ -117,10 +165,8 @@ pub fn read_sign( match algo { SignatureAlgorithm::Ed255 => read_ec_key(ctx.lend(), key_id, CurveAlgo::Ed255), SignatureAlgorithm::EcDsaP256 => read_ec_key(ctx.lend(), key_id, CurveAlgo::EcDsaP256), - _ => { - error!("Unimplemented operation"); - Err(Status::ConditionsOfUseNotSatisfied) - } + SignatureAlgorithm::Rsa2048 => read_rsa_key(ctx.lend(), key_id, Mechanism::Rsa2048Pkcs), + SignatureAlgorithm::Rsa4096 => read_rsa_key(ctx.lend(), key_id, Mechanism::Rsa4096Pkcs), } } @@ -137,10 +183,8 @@ pub fn read_dec( match algo { DecryptionAlgorithm::X255 => read_ec_key(ctx.lend(), key_id, CurveAlgo::X255), DecryptionAlgorithm::EcDhP256 => read_ec_key(ctx.lend(), key_id, CurveAlgo::EcDhP256), - _ => { - error!("Unimplemented operation"); - Err(Status::ConditionsOfUseNotSatisfied) - } + DecryptionAlgorithm::Rsa2048 => read_rsa_key(ctx.lend(), key_id, Mechanism::Rsa2048Pkcs), + DecryptionAlgorithm::Rsa4096 => read_rsa_key(ctx.lend(), key_id, Mechanism::Rsa4096Pkcs), } } @@ -157,9 +201,11 @@ pub fn read_aut( match algo { AuthenticationAlgorithm::Ed255 => read_ec_key(ctx.lend(), key_id, CurveAlgo::Ed255), AuthenticationAlgorithm::EcDsaP256 => read_ec_key(ctx.lend(), key_id, CurveAlgo::EcDsaP256), - _ => { - error!("Unimplemented operation"); - Err(Status::ConditionsOfUseNotSatisfied) + AuthenticationAlgorithm::Rsa2048 => { + read_rsa_key(ctx.lend(), key_id, Mechanism::Rsa2048Pkcs) + } + AuthenticationAlgorithm::Rsa4096 => { + read_rsa_key(ctx.lend(), key_id, Mechanism::Rsa4096Pkcs) } } } @@ -210,3 +256,69 @@ fn read_ec_key( serialize_pub(curve, ctx.lend(), &serialized)?; ctx.reply.prepend_len(offset) } + +#[cfg(feature = "rsa2048")] +fn read_rsa_key( + mut ctx: LoadedContext<'_, R, T>, + key_id: KeyId, + mechanism: Mechanism, +) -> Result<(), Status> { + let client = ctx.backend.client_mut(); + let public_key = syscall!(client.derive_key( + mechanism, + key_id, + None, + StorageAttributes::new().set_persistence(Location::Volatile) + )) + .key; + ctx.reply.expand(KEYGEN_DO_TAG)?; + let offset = ctx.reply.len(); + + let serialized_n = + try_syscall!(client.serialize_key(mechanism, public_key, KeySerialization::RsaN)) + .map_err(|_err| { + error!("Failed to serialize public key N: {_err:?}"); + syscall!(client.delete(public_key)); + Status::UnspecifiedNonpersistentExecutionError + })? + .serialized_key; + ctx.reply.expand(&[0x81])?; + ctx.reply.append_len(serialized_n.len())?; + ctx.reply.expand(&serialized_n)?; + drop(serialized_n); + + let serialized_e = + try_syscall!(client.serialize_key(mechanism, public_key, KeySerialization::RsaE)) + .map_err(|_err| { + error!("Failed to serialize public key E: {_err:?}"); + syscall!(client.delete(public_key)); + Status::UnspecifiedNonpersistentExecutionError + })? + .serialized_key; + ctx.reply.expand(&[0x82])?; + ctx.reply.append_len(serialized_e.len())?; + ctx.reply.expand(&serialized_e)?; + + ctx.reply.prepend_len(offset)?; + + syscall!(client.delete(public_key)); + Ok(()) +} + +#[cfg(not(feature = "rsa2048"))] +fn gen_rsa_key( + _ctx: LoadedContext<'_, R, T>, + _key: KeyType, + _mechanism: Mechanism, +) -> Result<(), Status> { + Err(Status::FunctionNotSupported) +} + +#[cfg(not(feature = "rsa2048"))] +fn read_rsa_key( + _ctx: LoadedContext<'_, R, T>, + _key_id: KeyId, + _mechanism: Mechanism, +) -> Result<(), Status> { + Err(Status::FunctionNotSupported) +} diff --git a/src/command/private_key_template.rs b/src/command/private_key_template.rs index 6622fbf..ecd4d41 100644 --- a/src/command/private_key_template.rs +++ b/src/command/private_key_template.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only use iso7816::Status; -use trussed::types::{KeyId, KeySerialization, Location}; +use trussed::types::{KeyId, KeySerialization, Location, Mechanism}; use trussed::{syscall, try_syscall}; use crate::card::LoadedContext; @@ -40,10 +40,8 @@ pub fn put_sign( let key_id = match attr { SignatureAlgorithm::EcDsaP256 => put_ec(ctx.lend(), CurveAlgo::EcDsaP256)?, SignatureAlgorithm::Ed255 => put_ec(ctx.lend(), CurveAlgo::Ed255)?, - SignatureAlgorithm::Rsa2k | SignatureAlgorithm::Rsa4k => { - warn!("Key import for RSA not supported"); - return Err(Status::FunctionNotSupported); - } + SignatureAlgorithm::Rsa2048 => put_rsa(ctx.lend(), Mechanism::Rsa2048Pkcs)?, + SignatureAlgorithm::Rsa4096 => put_rsa(ctx.lend(), Mechanism::Rsa4096Pkcs)?, } .map(|key_id| (key_id, KeyOrigin::Imported)); let old_key_id = ctx @@ -67,10 +65,8 @@ pub fn put_dec( let key_id = match attr { DecryptionAlgorithm::EcDhP256 => put_ec(ctx.lend(), CurveAlgo::EcDhP256)?, DecryptionAlgorithm::X255 => put_ec(ctx.lend(), CurveAlgo::X255)?, - DecryptionAlgorithm::Rsa2k | DecryptionAlgorithm::Rsa4k => { - warn!("Key import for RSA not supported"); - return Err(Status::FunctionNotSupported); - } + DecryptionAlgorithm::Rsa2048 => put_rsa(ctx.lend(), Mechanism::Rsa2048Pkcs)?, + DecryptionAlgorithm::Rsa4096 => put_rsa(ctx.lend(), Mechanism::Rsa4096Pkcs)?, } .map(|key_id| (key_id, KeyOrigin::Imported)); let old_key_id = ctx @@ -94,10 +90,8 @@ pub fn put_aut( let key_id = match attr { AuthenticationAlgorithm::EcDsaP256 => put_ec(ctx.lend(), CurveAlgo::EcDsaP256)?, AuthenticationAlgorithm::Ed255 => put_ec(ctx.lend(), CurveAlgo::Ed255)?, - AuthenticationAlgorithm::Rsa2k | AuthenticationAlgorithm::Rsa4k => { - warn!("Key import for RSA not supported"); - return Err(Status::FunctionNotSupported); - } + AuthenticationAlgorithm::Rsa2048 => put_rsa(ctx.lend(), Mechanism::Rsa2048Pkcs)?, + AuthenticationAlgorithm::Rsa4096 => put_rsa(ctx.lend(), Mechanism::Rsa4096Pkcs)?, } .map(|key_id| (key_id, KeyOrigin::Imported)); let old_key_id = ctx @@ -159,3 +153,72 @@ fn put_ec( .key; Ok(Some(key)) } + +#[cfg(feature = "rsa2048")] +fn parse_rsa_template(data: &[u8]) -> Option> { + use crate::tlv::take_len; + const TEMPLATE_DO: u16 = 0x7F48; + + let mut template = get_do(&[PRIVATE_KEY_TEMPLATE_DO, TEMPLATE_DO], data)?; + let mut res = [(0, 0); 6]; + let mut acc = 0; + for i in 0..6 { + if *template.first()? != i + 0x91 { + warn!("Unexpected template data: {}", template.first()?); + return None; + } + + let (size, d) = take_len(&template[1..])?; + res[i as usize] = (acc, acc + size); + acc += size; + template = d; + } + + let key_data = get_do(&[PRIVATE_KEY_TEMPLATE_DO, CONCATENATION_KEY_DATA_DO], data)?; + Some(trussed::types::RsaCrtImportFormat { + e: key_data.get(res[0].0..res[0].1)?, + p: key_data.get(res[1].0..res[1].1)?, + q: key_data.get(res[2].0..res[2].1)?, + qinv: key_data.get(res[3].0..res[3].1)?, + dp: key_data.get(res[4].0..res[4].1)?, + dq: key_data.get(res[5].0..res[5].1)?, + }) +} + +#[cfg(feature = "rsa2048")] +fn put_rsa( + ctx: LoadedContext<'_, R, T>, + mechanism: Mechanism, +) -> Result, Status> { + use trussed::{postcard_serialize_bytes, types::SerializedKey}; + + let key_data = parse_rsa_template(ctx.data).ok_or_else(|| { + warn!("Unable to parse RSA key"); + Status::IncorrectDataParameter + })?; + + let key_message: SerializedKey = postcard_serialize_bytes(&key_data).map_err(|_err| { + error!("Failed to serialize RSA key: {_err:?}"); + Status::UnspecifiedNonpersistentExecutionError + })?; + let key = try_syscall!(ctx.backend.client_mut().unsafe_inject_key( + mechanism, + &key_message, + Location::Internal, + KeySerialization::RsaCrt + )) + .map_err(|_err| { + warn!("Failed to store key: {_err:?}"); + Status::UnspecifiedNonpersistentExecutionError + })? + .key; + Ok(Some(key)) +} + +#[cfg(not(feature = "rsa2048"))] +fn put_rsa( + _ctx: LoadedContext<'_, R, T>, + _mechanism: Mechanism, +) -> Result, Status> { + Err(Status::FunctionNotSupported) +} diff --git a/src/command/pso.rs b/src/command/pso.rs index ab0ca57..3fdea4d 100644 --- a/src/command/pso.rs +++ b/src/command/pso.rs @@ -71,10 +71,8 @@ pub fn sign( } sign_ec(ctx, key_id, Mechanism::P256Prehashed) } - _ => { - error!("Unimplemented operation"); - Err(Status::ConditionsOfUseNotSatisfied) - } + SignatureAlgorithm::Rsa2048 => sign_rsa(ctx, key_id, Mechanism::Rsa2048Pkcs), + SignatureAlgorithm::Rsa4096 => sign_rsa(ctx, key_id, Mechanism::Rsa4096Pkcs), } } @@ -97,20 +95,42 @@ fn sign_ec( ctx.reply.expand(&signature) } -pub fn int_aut_key_mecha_uif( +fn sign_rsa( + mut ctx: LoadedContext<'_, R, T>, + key_id: KeyId, + mechanism: Mechanism, +) -> Result<(), Status> { + let signature = try_syscall!(ctx.backend.client_mut().sign( + mechanism, + key_id, + ctx.data, + SignatureSerialization::Raw + )) + .map_err(|_err| { + error!("Failed to sign data: {_err:?}"); + Status::UnspecifiedNonpersistentExecutionError + })? + .signature; + ctx.reply.expand(&signature) +} + +enum RsaOrEcc { + Rsa, + Ecc, +} + +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 { +) -> Result<(KeyId, Mechanism, bool, RsaOrEcc), Status> { + let (key_type, (mechanism, key_kind)) = 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::EcDsaP256 => (Mechanism::P256Prehashed, RsaOrEcc::Ecc), + AuthenticationAlgorithm::Ed255 => (Mechanism::Ed255, RsaOrEcc::Ecc), - AuthenticationAlgorithm::Rsa2k | AuthenticationAlgorithm::Rsa4k => { - error!("RSA is not implemented"); - return Err(Status::ConditionsOfUseNotSatisfied); - } + AuthenticationAlgorithm::Rsa2048 => (Mechanism::Rsa2048Pkcs, RsaOrEcc::Rsa), + AuthenticationAlgorithm::Rsa4096 => (Mechanism::Rsa4096Pkcs, RsaOrEcc::Rsa), }, ), KeyRef::Dec => ( @@ -120,11 +140,9 @@ pub fn int_aut_key_mecha_uif( 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); - } + DecryptionAlgorithm::EcDhP256 => (Mechanism::P256Prehashed, RsaOrEcc::Ecc), + DecryptionAlgorithm::Rsa2048 => (Mechanism::Rsa2048Pkcs, RsaOrEcc::Rsa), + DecryptionAlgorithm::Rsa4096 => (Mechanism::Rsa4096Pkcs, RsaOrEcc::Rsa), }, ), }; @@ -144,8 +162,10 @@ pub fn int_aut_key_mecha_uif( })?, mechanism, ctx.state.internal.uif(key_type).is_enabled(), + key_kind, )) } + // ยง 7.2.13 pub fn internal_authenticate( mut ctx: LoadedContext<'_, R, T>, @@ -155,42 +175,41 @@ pub fn internal_authenticate( return Err(Status::SecurityStatusNotSatisfied); } - let (key_id, mechanism, uif) = int_aut_key_mecha_uif(ctx.lend())?; + let (key_id, mechanism, uif, key_kind) = int_aut_key_mecha_uif(ctx.lend())?; if uif { prompt_uif(ctx.lend())?; } - sign_ec(ctx, key_id, mechanism) + match key_kind { + RsaOrEcc::Ecc => sign_ec(ctx, key_id, mechanism), + RsaOrEcc::Rsa => sign_rsa(ctx, key_id, mechanism), + } } -pub fn decipher_key_mecha_uif( +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 { +) -> Result<(KeyId, Mechanism, bool, RsaOrEcc), Status> { + let (key_type, (mechanism, key_kind)) = 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); - } + DecryptionAlgorithm::X255 => (Mechanism::X255, RsaOrEcc::Ecc), + DecryptionAlgorithm::EcDhP256 => (Mechanism::P256, RsaOrEcc::Ecc), + DecryptionAlgorithm::Rsa2048 => (Mechanism::Rsa2048Pkcs, RsaOrEcc::Rsa), + DecryptionAlgorithm::Rsa4096 => (Mechanism::Rsa4096Pkcs, RsaOrEcc::Rsa), }, ), KeyRef::Aut => ( KeyType::Aut, match ctx.state.internal.aut_alg() { - AuthenticationAlgorithm::EcDsaP256 => Mechanism::P256, + AuthenticationAlgorithm::EcDsaP256 => (Mechanism::P256, RsaOrEcc::Ecc), 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); - } + AuthenticationAlgorithm::Rsa2048 => (Mechanism::Rsa2048Pkcs, RsaOrEcc::Rsa), + AuthenticationAlgorithm::Rsa4096 => (Mechanism::Rsa4096Pkcs, RsaOrEcc::Rsa), }, ), }; @@ -202,6 +221,7 @@ pub fn decipher_key_mecha_uif( })?, mechanism, ctx.state.internal.uif(key_type).is_enabled(), + key_kind, )) } @@ -221,12 +241,42 @@ pub fn decipher( return decipher_aes(ctx); } - let (key_id, mechanism, uif) = decipher_key_mecha_uif(ctx.lend())?; + let (key_id, mechanism, uif, key_kind) = decipher_key_mecha_uif(ctx.lend())?; if uif { prompt_uif(ctx.lend())?; } + match key_kind { + RsaOrEcc::Ecc => decrypt_ec(ctx, key_id, mechanism), + RsaOrEcc::Rsa => decrypt_rsa(ctx, key_id, mechanism), + } +} - decrypt_ec(ctx, key_id, mechanism) +fn decrypt_rsa( + mut ctx: LoadedContext<'_, R, T>, + private_key: KeyId, + mechanism: Mechanism, +) -> Result<(), Status> { + if ctx.data.is_empty() { + return Err(Status::IncorrectDataParameter); + } + let plaintext = try_syscall!(ctx.backend.client_mut().decrypt( + mechanism, + private_key, + &ctx.data[1..], + &[], + &[], + &[] + )) + .map_err(|_err| { + error!("Failed to decrypt data: {_err:?}"); + Status::IncorrectDataParameter + })? + .plaintext + .ok_or_else(|| { + warn!("No plaintext"); + Status::IncorrectDataParameter + })?; + ctx.reply.expand(&plaintext) } fn decrypt_ec( diff --git a/src/tlv.rs b/src/tlv.rs index ad6ff76..372216c 100644 --- a/src/tlv.rs +++ b/src/tlv.rs @@ -52,7 +52,7 @@ fn take_tag(data: &[u8]) -> Option<(u16, &[u8])> { } } -fn take_len(data: &[u8]) -> Option<(usize, &[u8])> { +pub fn take_len(data: &[u8]) -> Option<(usize, &[u8])> { let l1 = *data.first()?; if l1 <= 0x7F { Some((l1 as usize, &data[1..])) @@ -60,7 +60,10 @@ fn take_len(data: &[u8]) -> Option<(usize, &[u8])> { Some((*data.get(1)? as usize, &data[2..])) } else { if l1 != 0x82 { - warn!("Got an unexpected length tag: {l1:x}"); + warn!( + "Got an unexpected length tag: {l1:x}, data: {:x?}", + &data[..3] + ); return None; } let l2 = *data.get(1)?; diff --git a/src/types.rs b/src/types.rs index 3db0532..f0d800f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -46,14 +46,14 @@ const RSA_2K_ATTRIBUTES: &[u8] = hex!(" 01 0800 // Length modulus (in bit): 2048 0020 // Length exponent (in bit): 32 - 00 // 0: Acceptable format is: P and Q + 02 // import in CRT Format ").as_slice(); const RSA_4K_ATTRIBUTES: &[u8] = hex!( " 01 1000 // Length modulus (in bit): 4096 0020 // Length exponent (in bit): 32 - 00 // 0: Acceptable format is: P and Q + 02 // import in CRT Format " ) .as_slice(); @@ -68,14 +68,14 @@ iterable_enum! { // Part of draft https://datatracker.ietf.org/doc/draft-ietf-openpgp-crypto-refresh/ Ed255, EcDsaP256, - Rsa2k, - Rsa4k, + Rsa2048, + Rsa4096, } } impl Default for SignatureAlgorithm { fn default() -> Self { - Self::Rsa2k + Self::Rsa2048 } } @@ -89,8 +89,8 @@ impl SignatureAlgorithm { match self { Self::Ed255 => ED255_ATTRIBUTES, Self::EcDsaP256 => ECDSA_P256_ATTRIBUTES, - Self::Rsa2k => RSA_2K_ATTRIBUTES, - Self::Rsa4k => RSA_4K_ATTRIBUTES, + Self::Rsa2048 => RSA_2K_ATTRIBUTES, + Self::Rsa4096 => RSA_4K_ATTRIBUTES, } } @@ -107,8 +107,8 @@ impl TryFrom<&[u8]> for SignatureAlgorithm { match v { ED255_ATTRIBUTES => Ok(Self::Ed255), ECDSA_P256_ATTRIBUTES => Ok(Self::EcDsaP256), - RSA_2K_ATTRIBUTES => Ok(Self::Rsa2k), - RSA_4K_ATTRIBUTES => Ok(Self::Rsa4k), + RSA_2K_ATTRIBUTES => Ok(Self::Rsa2048), + RSA_4K_ATTRIBUTES => Ok(Self::Rsa4096), _ => Err(AlgorithmFromAttributesError), } } @@ -121,14 +121,14 @@ iterable_enum! { // Part of draft https://datatracker.ietf.org/doc/draft-ietf-openpgp-crypto-refresh/ X255, EcDhP256, - Rsa2k, - Rsa4k, + Rsa2048, + Rsa4096, } } impl Default for DecryptionAlgorithm { fn default() -> Self { - Self::Rsa2k + Self::Rsa2048 } } @@ -142,8 +142,8 @@ impl DecryptionAlgorithm { match self { Self::X255 => X255_ATTRIBUTES, Self::EcDhP256 => ECDH_P256_ATTRIBUTES, - Self::Rsa2k => RSA_2K_ATTRIBUTES, - Self::Rsa4k => RSA_4K_ATTRIBUTES, + Self::Rsa2048 => RSA_2K_ATTRIBUTES, + Self::Rsa4096 => RSA_4K_ATTRIBUTES, } } @@ -160,8 +160,8 @@ impl TryFrom<&[u8]> for DecryptionAlgorithm { match v { X255_ATTRIBUTES => Ok(Self::X255), ECDH_P256_ATTRIBUTES => Ok(Self::EcDhP256), - RSA_2K_ATTRIBUTES => Ok(Self::Rsa2k), - RSA_4K_ATTRIBUTES => Ok(Self::Rsa4k), + RSA_2K_ATTRIBUTES => Ok(Self::Rsa2048), + RSA_4K_ATTRIBUTES => Ok(Self::Rsa4096), _ => Err(AlgorithmFromAttributesError), } } @@ -174,14 +174,14 @@ iterable_enum! { // Part of draft https://datatracker.ietf.org/doc/draft-ietf-openpgp-crypto-refresh/ Ed255, EcDsaP256, - Rsa2k, - Rsa4k, + Rsa2048, + Rsa4096, } } impl Default for AuthenticationAlgorithm { fn default() -> Self { - Self::Rsa2k + Self::Rsa2048 } } @@ -195,8 +195,8 @@ impl AuthenticationAlgorithm { match self { Self::Ed255 => ED255_ATTRIBUTES, Self::EcDsaP256 => ECDSA_P256_ATTRIBUTES, - Self::Rsa2k => RSA_2K_ATTRIBUTES, - Self::Rsa4k => RSA_4K_ATTRIBUTES, + Self::Rsa2048 => RSA_2K_ATTRIBUTES, + Self::Rsa4096 => RSA_4K_ATTRIBUTES, } } @@ -213,8 +213,8 @@ impl TryFrom<&[u8]> for AuthenticationAlgorithm { match v { ED255_ATTRIBUTES => Ok(Self::Ed255), ECDSA_P256_ATTRIBUTES => Ok(Self::EcDsaP256), - RSA_2K_ATTRIBUTES => Ok(Self::Rsa2k), - RSA_4K_ATTRIBUTES => Ok(Self::Rsa4k), + RSA_2K_ATTRIBUTES => Ok(Self::Rsa2048), + RSA_4K_ATTRIBUTES => Ok(Self::Rsa4096), _ => Err(AlgorithmFromAttributesError), } } diff --git a/tests/crypto-gpg-import.rs b/tests/crypto-gpg-import.rs index 601f160..2f9b722 100644 --- a/tests/crypto-gpg-import.rs +++ b/tests/crypto-gpg-import.rs @@ -225,7 +225,7 @@ fn gpg_255() { &[ vec![ r"\[GNUPG:\] CARDCTRL 3 D2760001240103040000000000000000", - r"\[GNUPG:\] BEGIN_SIGNING H8", + r"\[GNUPG:\] BEGIN_SIGNING H\d*", &custom1, r"\[GNUPG:\] NEED_PASSPHRASE [a-fA-F0-9]{16} [a-fA-F0-9]{16} 22 0", ], @@ -368,7 +368,7 @@ fn gpg_p256() { Generate, ); - println!("================ FINISHED GENERATING 25519 KEYS ================"); + println!("================ FINISHED GENERATING P256 KEYS ================"); gnupg_test( &["key *", "keytocard", "2", DEFAULT_PW3, DEFAULT_PW3, "save"], @@ -503,7 +503,7 @@ fn gpg_p256() { &[ vec![ r"\[GNUPG:\] CARDCTRL 3 D2760001240103040000000000000000", - r"\[GNUPG:\] BEGIN_SIGNING H8", + r"\[GNUPG:\] BEGIN_SIGNING H\d*", &custom1, r"\[GNUPG:\] NEED_PASSPHRASE [a-fA-F0-9]{16} [a-fA-F0-9]{16} 19 0", ], @@ -577,8 +577,592 @@ fn gpg_p256() { }); } +#[cfg(feature = "rsa2048")] +fn gpg_rsa_2048() { + with_vsc(|| { + let file_number: u32 = rand::rngs::OsRng.gen(); + let tmp = format!("/tmp/opcard-tests-{file_number}.gpg"); + let encrypted_file = &tmp; + let tmp = format!("/tmp/opcard-tests-{file_number}-sig.gpg"); + let sign_file = &tmp; + let tmp = format!("/tmp/opcard-tests-{file_number}.toml"); + let decrypted_file = &tmp; + let _dropper = FileDropper { + temp_file_name: encrypted_file, + }; + let _dropper = FileDropper { + temp_file_name: sign_file, + }; + let _dropper = FileDropper { + temp_file_name: decrypted_file, + }; + + let tmp = format!("test name{file_number}"); + let temp_name = &tmp; + + let tmp = format!("test{file_number}@email.com"); + let temp_email = &tmp; + + let custom_match = format!( + r"uid:u::::\d{{10}}::[0-9A-F]{{40}}::{temp_name} \(no comment\) <{temp_email}>::::::::::0:" + ); + + let custom_match2 = format!( + r"uid:u::::::::{temp_name} \(no comment\) <{temp_email}>:::.*,mdc,no-ks-modify:1,p::" + ); + + gnupg_test( + &[ + "1", + "2048", + "2048", + "0", + temp_name, + temp_email, + "no comment", + "", + "", + ], + &[ + vec![ + r"\[GNUPG:\] GET_LINE keygen.algo", + r"\[GNUPG:\] GET_LINE keygen.size", + r"\[GNUPG:\] GET_LINE keygen.size", + r"\[GNUPG:\] GET_LINE keygen.valid", + r"\[GNUPG:\] GET_LINE keygen.name", + r"\[GNUPG:\] GET_LINE keygen.email", + r"\[GNUPG:\] GET_LINE keygen.comment", + ], + virt::gpg_inquire_pin(), + virt::gpg_inquire_pin(), + vec![ + r"pub:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:::u:::scESC:::\+:::23::0:", + r"fpr:::::::::[0-9A-F]{40}:", + r"grp:::::::::[0-9A-F]{40}:", + &custom_match, + r"sub:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}::::::e:::\+:::23:", + r"fpr:::::::::[0-9A-F]{40}:", + r"grp:::::::::[0-9A-F]{40}:", + r"\[GNUPG:\] KEY_CREATED B [A-F0-9]{40}", + ], + ] + .into_iter() + .flatten() + .collect::>(), + &[ + r"gpg: revocation certificate stored as '.*\.rev'", + r"gpg: checking the trustdb", + r"gpg: marginals needed: \d completes needed: \d trust model: pgp", + r"gpg: depth:[ 0-9]*valid:[ 0-9]*signed:[ 0-9]*trust: \d*-, \d*q, \d*n, \d*m, \d*f, \d*u", + ], + Generate, + ); + + println!("================ FINISHED GENERATING Rsa2048 KEYS ================"); + + gnupg_test( + &["key *", "keytocard", "2", DEFAULT_PW3, "save"], + &[ + vec![ + r"sec:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0::u:::sc", + r"fpr:::::::::[0-9A-F]{40}:", + r"ssb:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0:::::e", + r"fpr:::::::::[0-9A-F]{40}:", + &custom_match2, + r"\[GNUPG:\] GET_LINE keyedit.prompt", + r"sec:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0::u:::sc", + r"fpr:::::::::[0-9A-F]{40}:", + r"ssb:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0:::::e", + r"fpr:::::::::[0-9A-F]{40}:", + &custom_match2, + r"\[GNUPG:\] GET_LINE keyedit.prompt", + r"\[GNUPG:\] CARDCTRL 3 D2760001240103040000000000000000", + r"\[GNUPG:\] GET_LINE cardedit.genkeys.storekeytype", + ], + virt::gpg_inquire_pin(), + // virt::gpg_inquire_pin(), + vec![ + r"sec:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0::u:::sc", + r"fpr:::::::::[0-9A-F]{40}:", + r"ssb:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0:::::e", + r"fpr:::::::::[0-9A-F]{40}:", + &custom_match2, + r"\[GNUPG:\] GET_LINE keyedit.prompt", + ], + ] + .into_iter() + .flatten() + .collect::>(), + &[], + EditKey { o: temp_email }, + ); + + println!("================ FINISHED IMPORTING DECRYPTION KEY ================"); + + gnupg_test( + &["keytocard", "y", "1", DEFAULT_PW3, "save"], + &[ + vec![ + r"sec:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0::u:::sc", + r"fpr:::::::::[0-9A-F]{40}:", + r"ssb:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0:::::e", + r"fpr:::::::::[0-9A-F]{40}:", + &custom_match2, + r"\[GNUPG:\] GET_LINE keyedit.prompt", + r"\[GNUPG:\] GET_BOOL keyedit.keytocard.use_primary", + r"\[GNUPG:\] CARDCTRL 3 D2760001240103040000000000000000", + r"\[GNUPG:\] GET_LINE cardedit.genkeys.storekeytype", + ], + vec![ + r"sec:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0::u:::sc", + r"fpr:::::::::[0-9A-F]{40}:", + r"ssb:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0:::::e", + r"fpr:::::::::[0-9A-F]{40}:", + &custom_match2, + r"\[GNUPG:\] GET_LINE keyedit.prompt", + r"\[GNUPG:\] GET_LINE keyedit.prompt", + ], + ] + .into_iter() + .flatten() + .collect::>(), + &[], + EditKey { o: temp_email }, + ); + + println!("================ FINISHED IMPORTING Rsa2048 KEYS ================"); + + gnupg_test( + &[], + &[ + r"\[GNUPG:\] BEGIN_ENCRYPTION \d \d", + r"\[GNUPG:\] END_ENCRYPTION", + ], + &[], + Encrypt { + i: "Cargo.toml", + o: encrypted_file, + r: temp_email, + }, + ); + + println!("================ FINISHED ENCRYPTION ================"); + + let custom1 = format!( + r"\[GNUPG:\] USERID_HINT [a-fA-F0-9]{{16}} {temp_name} \(no comment\) <{temp_email}>" + ); + let custom2 = format!(r"{temp_name} \(no comment\) <{temp_email}>"); + + gnupg_test( + &[DEFAULT_PW1], + &[ + vec![ + r"\[GNUPG:\] ENC_TO [a-fA-F0-9]{16} \d* \d*", + &custom1, + r"\[GNUPG:\] NEED_PASSPHRASE [a-fA-F0-9]{16} [a-fA-F0-9]{16} 1 0", + ], + virt::gpg_inquire_pin(), + vec![ + r"\[GNUPG:\] DECRYPTION_KEY [a-fA-F0-9]{40} [a-fA-F0-9]{40} u", + r"\[GNUPG:\] BEGIN_DECRYPTION", + r"\[GNUPG:\] DECRYPTION_INFO \d \d \d", + r"\[GNUPG:\] PLAINTEXT \d* \d* Cargo.toml", + r"\[GNUPG:\] PLAINTEXT_LENGTH \d*", + r"\[GNUPG:\] DECRYPTION_OKAY", + r"\[GNUPG:\] GOODMDC", + r"\[GNUPG:\] END_DECRYPTION", + ], + ] + .into_iter() + .flatten() + .collect::>(), + &[ + r"gpg: encrypted with \d*-bit RSA key, ID [a-fA-F0-9]{16}, created \d{4}-\d\d-\d\d", + &custom2, + ], + Decrypt { + i: encrypted_file, + o: decrypted_file, + }, + ); + + println!("================ FINISHED DECRYPTION ================"); + + gnupg_test( + &[DEFAULT_PW1], + &[ + vec![ + r"\[GNUPG:\] CARDCTRL 3 D2760001240103040000000000000000", + r"\[GNUPG:\] BEGIN_SIGNING H\d*", + &custom1, + r"\[GNUPG:\] NEED_PASSPHRASE [a-fA-F0-9]{16} [a-fA-F0-9]{16} 1 0", + ], + virt::gpg_inquire_pin(), + vec![r"\[GNUPG:\] SIG_CREATED S 1 \d* 00 [a-fA-F0-9]{10} [a-fA-F0-9]{40}"], + ] + .into_iter() + .flatten() + .collect::>(), + &[r#"gpg: using "test\d*@email.com" as default secret key for signing"#], + Sign { + i: "Cargo.toml", + o: sign_file, + s: temp_email, + }, + ); + + println!("================ FINISHED SIGNATURE ================"); + + gnupg_test( + &[], + &[ + r"\[GNUPG:\] NEWSIG test\d*@email.com", + r"\[GNUPG:\] SIG_ID [^ ]* \d{4}-\d\d-\d\d [a-fA-F0-9]{10}", + r"\[GNUPG:\] GOODSIG [a-fA-F0-9]{16} test name\d* \(no comment\) ", + r"\[GNUPG:\] VALIDSIG [a-fA-F0-9]{40} \d{4}-\d\d-\d\d [a-fA-F0-9]{10} \d \d \d 1 \d* 00 [a-fA-F0-9]{40}", + r"\[GNUPG:\] TRUST_ULTIMATE 0 pgp", + ], + &[ + r"gpg: Signature made .*", + r"gpg: using RSA key [a-fA-F0-9]{40}", + r#"gpg: issuer "test\d*@email.com""#, + r#"pg: Good signature from "test name\d* \(no comment\) "#, + ], + Verify { i: sign_file }, + ); + + gnupg_test( + &[ + "admin", + "factory-reset", + "y", + "yes", + "verify", + DEFAULT_PW1, + "quit", + ], + &[ + vec![r"\[GNUPG:\] CARDCTRL \d D2760001240103040000000000000000"], + virt::gpg_status(virt::KeyType::RsaNoAut, 1), + vec![ + r"\[GNUPG:\] GET_LINE cardedit.prompt", + r"\[GNUPG:\] GET_LINE cardedit.prompt", + r"\[GNUPG:\] GET_BOOL cardedit.factory-reset.proceed", + r"\[GNUPG:\] GET_LINE cardedit.factory-reset.really", + r"\[GNUPG:\] GET_LINE cardedit.prompt", + ], + virt::gpg_inquire_pin(), + virt::gpg_status(virt::KeyType::RsaNone, 0), + vec![r"\[GNUPG:\] GET_LINE cardedit.prompt"], + ] + .into_iter() + .flatten() + .collect::>(), + &[ + r"gpg: OpenPGP card no. [0-9A-F]{32} detected", + r"gpg: Note: This command destroys all keys stored on the card!", + ], + EditCard, + ); + }); +} + +#[cfg(feature = "rsa4096")] +fn gpg_rsa_4096() { + with_vsc(|| { + let file_number: u32 = rand::rngs::OsRng.gen(); + let tmp = format!("/tmp/opcard-tests-{file_number}.gpg"); + let encrypted_file = &tmp; + let tmp = format!("/tmp/opcard-tests-{file_number}-sig.gpg"); + let sign_file = &tmp; + let tmp = format!("/tmp/opcard-tests-{file_number}.toml"); + let decrypted_file = &tmp; + let _dropper = FileDropper { + temp_file_name: encrypted_file, + }; + let _dropper = FileDropper { + temp_file_name: sign_file, + }; + let _dropper = FileDropper { + temp_file_name: decrypted_file, + }; + + let tmp = format!("test name{file_number}"); + let temp_name = &tmp; + + let tmp = format!("test{file_number}@email.com"); + let temp_email = &tmp; + + let custom_match = format!( + r"uid:u::::\d{{10}}::[0-9A-F]{{40}}::{temp_name} \(no comment\) <{temp_email}>::::::::::0:" + ); + + let custom_match2 = format!( + r"uid:u::::::::{temp_name} \(no comment\) <{temp_email}>:::.*,mdc,no-ks-modify:1,p::" + ); + + gnupg_test( + &[ + "1", + "4096", + "4096", + "0", + temp_name, + temp_email, + "no comment", + "", + "", + ], + &[ + vec![ + r"\[GNUPG:\] GET_LINE keygen.algo", + r"\[GNUPG:\] GET_LINE keygen.size", + r"\[GNUPG:\] GET_LINE keygen.size", + r"\[GNUPG:\] GET_LINE keygen.valid", + r"\[GNUPG:\] GET_LINE keygen.name", + r"\[GNUPG:\] GET_LINE keygen.email", + r"\[GNUPG:\] GET_LINE keygen.comment", + ], + virt::gpg_inquire_pin(), + virt::gpg_inquire_pin(), + vec![ + r"pub:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:::u:::scESC:::\+:::23::0:", + r"fpr:::::::::[0-9A-F]{40}:", + r"grp:::::::::[0-9A-F]{40}:", + &custom_match, + r"sub:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}::::::e:::\+:::23:", + r"fpr:::::::::[0-9A-F]{40}:", + r"grp:::::::::[0-9A-F]{40}:", + r"\[GNUPG:\] KEY_CREATED B [A-F0-9]{40}", + ], + ] + .into_iter() + .flatten() + .collect::>(), + &[ + r"gpg: revocation certificate stored as '.*\.rev'", + r"gpg: checking the trustdb", + r"gpg: marginals needed: \d completes needed: \d trust model: pgp", + r"gpg: depth:[ 0-9]*valid:[ 0-9]*signed:[ 0-9]*trust: \d*-, \d*q, \d*n, \d*m, \d*f, \d*u", + ], + Generate, + ); + + println!("================ FINISHED GENERATING Rsa4096 KEYS ================"); + + gnupg_test( + &["key *", "keytocard", "2", DEFAULT_PW3, DEFAULT_PW3, "save"], + &[ + vec![ + r"sec:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0::u:::sc", + r"fpr:::::::::[0-9A-F]{40}:", + r"ssb:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0:::::e", + r"fpr:::::::::[0-9A-F]{40}:", + &custom_match2, + r"\[GNUPG:\] GET_LINE keyedit.prompt", + r"sec:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0::u:::sc", + r"fpr:::::::::[0-9A-F]{40}:", + r"ssb:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0:::::e", + r"fpr:::::::::[0-9A-F]{40}:", + &custom_match2, + r"\[GNUPG:\] GET_LINE keyedit.prompt", + r"\[GNUPG:\] CARDCTRL 3 D2760001240103040000000000000000", + r"\[GNUPG:\] GET_LINE cardedit.genkeys.storekeytype", + ], + virt::gpg_inquire_pin(), + virt::gpg_inquire_pin(), + vec![ + r"sec:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0::u:::sc", + r"fpr:::::::::[0-9A-F]{40}:", + r"ssb:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0:::::e", + r"fpr:::::::::[0-9A-F]{40}:", + &custom_match2, + r"\[GNUPG:\] GET_LINE keyedit.prompt", + ], + ] + .into_iter() + .flatten() + .collect::>(), + &[], + EditKey { o: temp_email }, + ); + + println!("================ FINISHED IMPORTING DECRYPTION KEY ================"); + + gnupg_test( + &["keytocard", "y", "1", DEFAULT_PW3, "save"], + &[ + vec![ + r"sec:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0::u:::sc", + r"fpr:::::::::[0-9A-F]{40}:", + r"ssb:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0:::::e", + r"fpr:::::::::[0-9A-F]{40}:", + &custom_match2, + r"\[GNUPG:\] GET_LINE keyedit.prompt", + r"\[GNUPG:\] GET_BOOL keyedit.keytocard.use_primary", + r"\[GNUPG:\] CARDCTRL 3 D2760001240103040000000000000000", + r"\[GNUPG:\] GET_LINE cardedit.genkeys.storekeytype", + ], + virt::gpg_inquire_pin(), + vec![ + r"sec:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0::u:::sc", + r"fpr:::::::::[0-9A-F]{40}:", + r"ssb:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:0:::::e", + r"fpr:::::::::[0-9A-F]{40}:", + &custom_match2, + r"\[GNUPG:\] GET_LINE keyedit.prompt", + ], + ] + .into_iter() + .flatten() + .collect::>(), + &[], + EditKey { o: temp_email }, + ); + + println!("================ FINISHED IMPORTING Rsa4096 KEYS ================"); + + gnupg_test( + &[], + &[ + r"\[GNUPG:\] BEGIN_ENCRYPTION \d \d", + r"\[GNUPG:\] END_ENCRYPTION", + ], + &[], + Encrypt { + i: "Cargo.toml", + o: encrypted_file, + r: temp_email, + }, + ); + + println!("================ FINISHED ENCRYPTION ================"); + + let custom1 = format!( + r"\[GNUPG:\] USERID_HINT [a-fA-F0-9]{{16}} {temp_name} \(no comment\) <{temp_email}>" + ); + let custom2 = format!(r"{temp_name} \(no comment\) <{temp_email}>"); + + gnupg_test( + &[DEFAULT_PW1], + &[ + vec![ + r"\[GNUPG:\] ENC_TO [a-fA-F0-9]{16} \d* \d*", + &custom1, + r"\[GNUPG:\] NEED_PASSPHRASE [a-fA-F0-9]{16} [a-fA-F0-9]{16} 1 0", + ], + virt::gpg_inquire_pin(), + vec![ + r"\[GNUPG:\] DECRYPTION_KEY [a-fA-F0-9]{40} [a-fA-F0-9]{40} u", + r"\[GNUPG:\] BEGIN_DECRYPTION", + r"\[GNUPG:\] DECRYPTION_INFO \d \d \d", + r"\[GNUPG:\] PLAINTEXT \d* \d* Cargo.toml", + r"\[GNUPG:\] PLAINTEXT_LENGTH \d*", + r"\[GNUPG:\] DECRYPTION_OKAY", + r"\[GNUPG:\] GOODMDC", + r"\[GNUPG:\] END_DECRYPTION", + ], + ] + .into_iter() + .flatten() + .collect::>(), + &[ + r"gpg: encrypted with \d*-bit RSA key, ID [a-fA-F0-9]{16}, created \d{4}-\d\d-\d\d", + &custom2, + ], + Decrypt { + i: encrypted_file, + o: decrypted_file, + }, + ); + + println!("================ FINISHED DECRYPTION ================"); + + gnupg_test( + &[DEFAULT_PW1], + &[ + vec![ + r"\[GNUPG:\] CARDCTRL 3 D2760001240103040000000000000000", + r"\[GNUPG:\] BEGIN_SIGNING H\d*", + &custom1, + r"\[GNUPG:\] NEED_PASSPHRASE [a-fA-F0-9]{16} [a-fA-F0-9]{16} 1 0", + ], + virt::gpg_inquire_pin(), + vec![r"\[GNUPG:\] SIG_CREATED S 1 \d* 00 [a-fA-F0-9]{10} [a-fA-F0-9]{40}"], + ] + .into_iter() + .flatten() + .collect::>(), + &[r#"gpg: using "test\d*@email.com" as default secret key for signing"#], + Sign { + i: "Cargo.toml", + o: sign_file, + s: temp_email, + }, + ); + + println!("================ FINISHED SIGNATURE ================"); + + gnupg_test( + &[], + &[ + r"\[GNUPG:\] NEWSIG test\d*@email.com", + r"\[GNUPG:\] SIG_ID [^ ]* \d{4}-\d\d-\d\d [a-fA-F0-9]{10}", + r"\[GNUPG:\] GOODSIG [a-fA-F0-9]{16} test name\d* \(no comment\) ", + r"\[GNUPG:\] VALIDSIG [a-fA-F0-9]{40} \d{4}-\d\d-\d\d [a-fA-F0-9]{10} \d \d \d 1 \d* 00 [a-fA-F0-9]{40}", + r"\[GNUPG:\] TRUST_ULTIMATE 0 pgp", + ], + &[ + r"gpg: Signature made .*", + r"gpg: using RSA key [a-fA-F0-9]{40}", + r#"gpg: issuer "test\d*@email.com""#, + r#"pg: Good signature from "test name\d* \(no comment\) "#, + ], + Verify { i: sign_file }, + ); + + gnupg_test( + &[ + "admin", + "factory-reset", + "y", + "yes", + "verify", + DEFAULT_PW1, + "quit", + ], + &[ + vec![r"\[GNUPG:\] CARDCTRL \d D2760001240103040000000000000000"], + virt::gpg_status(virt::KeyType::Rsa4096NoAut, 1), + vec![ + r"\[GNUPG:\] GET_LINE cardedit.prompt", + r"\[GNUPG:\] GET_LINE cardedit.prompt", + r"\[GNUPG:\] GET_BOOL cardedit.factory-reset.proceed", + r"\[GNUPG:\] GET_LINE cardedit.factory-reset.really", + r"\[GNUPG:\] GET_LINE cardedit.prompt", + ], + virt::gpg_inquire_pin(), + virt::gpg_status(virt::KeyType::RsaNone, 0), + vec![r"\[GNUPG:\] GET_LINE cardedit.prompt"], + ] + .into_iter() + .flatten() + .collect::>(), + &[ + r"gpg: OpenPGP card no. [0-9A-F]{32} detected", + r"gpg: Note: This command destroys all keys stored on the card!", + ], + EditCard, + ); + }); +} + #[test] fn gpg_crypto_import() { + #[cfg(feature = "rsa2048")] + gpg_rsa_2048(); + #[cfg(feature = "rsa2048")] + gpg_rsa_4096(); gpg_255(); gpg_p256(); } diff --git a/tests/crypto-gpg.rs b/tests/crypto-gpg.rs index 4ce8deb..abcfe1e 100644 --- a/tests/crypto-gpg.rs +++ b/tests/crypto-gpg.rs @@ -26,6 +26,21 @@ fn attr_ec_ask() -> Vec<&'static str> { .collect() } +#[cfg(feature = "rsa4096-gen")] +fn attr_rsa_ask() -> Vec<&'static str> { + iter::repeat( + [ + r"\[GNUPG:\] GET_LINE cardedit.genkeys.algo", + r"\[GNUPG:\] GET_LINE cardedit.genkeys.size", + ] + .into_iter() + .chain(virt::gpg_inquire_pin()), + ) + .take(3) + .flatten() + .collect() +} + const DEFAULT_PW3: &str = "12345678"; const DEFAULT_PW1: &str = "123456"; @@ -200,7 +215,7 @@ fn gpg_255() { &[ vec![ r"\[GNUPG:\] CARDCTRL 3 D2760001240103040000000000000000", - r"\[GNUPG:\] BEGIN_SIGNING H8", + r"\[GNUPG:\] BEGIN_SIGNING H\d*", &custom1, r"\[GNUPG:\] NEED_PASSPHRASE [a-fA-F0-9]{16} [a-fA-F0-9]{16} 22 0", ], @@ -436,7 +451,7 @@ fn gpg_p256() { &[ vec![ r"\[GNUPG:\] CARDCTRL 3 D2760001240103040000000000000000", - r"\[GNUPG:\] BEGIN_SIGNING H8", + r"\[GNUPG:\] BEGIN_SIGNING H\d*", &custom1, r"\[GNUPG:\] NEED_PASSPHRASE [a-fA-F0-9]{16} [a-fA-F0-9]{16} 19 0", ], @@ -510,8 +525,470 @@ fn gpg_p256() { }); } +#[cfg(feature = "rsa2048")] +fn gpg_rsa_2048() { + with_vsc(|| { + let file_number: u32 = rand::rngs::OsRng.gen(); + let tmp = format!("/tmp/opcard-tests-{file_number}.gpg"); + let encrypted_file = &tmp; + let tmp = format!("/tmp/opcard-tests-{file_number}-sig.gpg"); + let sign_file = &tmp; + let tmp = format!("/tmp/opcard-tests-{file_number}.toml"); + let decrypted_file = &tmp; + let _dropper = FileDropper { + temp_file_name: encrypted_file, + }; + let _dropper = FileDropper { + temp_file_name: sign_file, + }; + let _dropper = FileDropper { + temp_file_name: decrypted_file, + }; + + let tmp = format!("test name{file_number}"); + let temp_name = &tmp; + + let tmp = format!("test{file_number}@email.com"); + let temp_email = &tmp; + + let custom_match = format!( + r"uid:u::::\d{{10}}::[0-9A-F]{{40}}::{temp_name} \(no comment\) <{temp_email}>::::::::::0:" + ); + + gnupg_test( + &[ + "admin", + "generate", + "n", + DEFAULT_PW3, + DEFAULT_PW1, + "0", + temp_name, + temp_email, + "no comment", + DEFAULT_PW1, + "quit", + ], + &[ + vec![r"\[GNUPG:\] CARDCTRL \d D2760001240103040000000000000000"], + virt::gpg_status(virt::KeyType::RsaNone,0), + vec![ + r"\[GNUPG:\] GET_LINE cardedit.prompt", + r"\[GNUPG:\] GET_LINE cardedit.prompt", + r"\[GNUPG:\] GET_LINE cardedit.genkeys.backup_enc", + ], + virt::gpg_inquire_pin(), + virt::gpg_inquire_pin(), + vec![ + r"\[GNUPG:\] GET_LINE keygen.valid", + r"\[GNUPG:\] GET_LINE keygen.name", + r"\[GNUPG:\] GET_LINE keygen.email", + r"\[GNUPG:\] GET_LINE keygen.comment", + r"\[GNUPG:\] USERID_HINT [0-9A-F]{16} \[\?\]", + r"\[GNUPG:\] NEED_PASSPHRASE [0-9A-F]{16} [0-9A-F]{16} 1 \d", + ], + virt::gpg_inquire_pin(), + vec![ + r"pub:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:::u:::scESCA:::D2760001240103040000000000000000:::23::0:", + r"fpr:::::::::[0-9A-F]{40}:", + r"grp:::::::::[0-9A-F]{40}:", + &custom_match, + r"sub:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}::::::a:::D2760001240103040000000000000000:::23:", + r"fpr:::::::::[0-9A-F]{40}:", + r"grp:::::::::[0-9A-F]{40}:", + r"sub:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}::::::e:::D2760001240103040000000000000000:::23:", + r"fpr:::::::::[0-9A-F]{40}:", + r"grp:::::::::[0-9A-F]{40}:", + r"\[GNUPG:\] KEY_CREATED B [0-9A-F]{40}", + r"\[GNUPG:\] GET_LINE cardedit.prompt", + ] + ].into_iter().flatten().collect::>(), + &[ + r"gpg: revocation certificate stored as '.*\.rev'", + r"gpg: checking the trustdb", + r"gpg: marginals needed: \d completes needed: \d trust model: pgp", + r"gpg: depth:[ 0-9]*valid:[ 0-9]*signed:[ 0-9]*trust: \d*-, \d*q, \d*n, \d*m, \d*f, \d*u", + ], + EditCard, + ); + + println!("================ FINISHED GENERATING 25519 KEYS ================"); + + gnupg_test( + &[], + &[ + r"\[GNUPG:\] BEGIN_ENCRYPTION \d \d", + r"\[GNUPG:\] END_ENCRYPTION", + ], + &[], + Encrypt { + i: "Cargo.toml", + o: encrypted_file, + r: temp_email, + }, + ); + + println!("================ FINISHED ENCRYPTION ================"); + + let custom1 = format!( + r"\[GNUPG:\] USERID_HINT [a-fA-F0-9]{{16}} {temp_name} \(no comment\) <{temp_email}>" + ); + let custom2 = format!(r"{temp_name} \(no comment\) <{temp_email}>"); + gnupg_test( + &[DEFAULT_PW1], + &[ + vec![ + r"\[GNUPG:\] ENC_TO [a-fA-F0-9]{16} \d* \d*", + &custom1, + r"\[GNUPG:\] NEED_PASSPHRASE [a-fA-F0-9]{16} [a-fA-F0-9]{16} 1 0", + ], + virt::gpg_inquire_pin(), + vec![ + r"\[GNUPG:\] DECRYPTION_KEY [a-fA-F0-9]{40} [a-fA-F0-9]{40} u", + r"\[GNUPG:\] BEGIN_DECRYPTION", + r"\[GNUPG:\] DECRYPTION_INFO \d \d \d", + r"\[GNUPG:\] PLAINTEXT \d* \d* Cargo.toml", + r"\[GNUPG:\] PLAINTEXT_LENGTH \d*", + r"\[GNUPG:\] DECRYPTION_OKAY", + r"\[GNUPG:\] GOODMDC", + r"\[GNUPG:\] END_DECRYPTION", + ], + ] + .into_iter() + .flatten() + .collect::>(), + &[ + r"gpg: encrypted with \d*-bit RSA key, ID [a-fA-F0-9]{16}, created \d{4}-\d\d-\d\d", + &custom2, + ], + Decrypt { + i: encrypted_file, + o: decrypted_file, + }, + ); + + println!("================ FINISHED DECRYPTION ================"); + + gnupg_test( + &[DEFAULT_PW1], + &[ + vec![ + r"\[GNUPG:\] CARDCTRL 3 D2760001240103040000000000000000", + r"\[GNUPG:\] BEGIN_SIGNING H\d*", + &custom1, + r"\[GNUPG:\] NEED_PASSPHRASE [a-fA-F0-9]{16} [a-fA-F0-9]{16} 1 0", + ], + virt::gpg_inquire_pin(), + vec![r"\[GNUPG:\] SIG_CREATED S 1 \d* 00 [a-fA-F0-9]{10} [a-fA-F0-9]{40}"], + ] + .into_iter() + .flatten() + .collect::>(), + &[r#"gpg: using "test\d*@email.com" as default secret key for signing"#], + Sign { + i: "Cargo.toml", + o: sign_file, + s: temp_email, + }, + ); + + println!("================ FINISHED SIGNATURE ================"); + + gnupg_test( + &[], + &[ + r"\[GNUPG:\] NEWSIG test\d*@email.com", + r"\[GNUPG:\] SIG_ID [^ ]* \d{4}-\d\d-\d\d [a-fA-F0-9]{10}", + r"\[GNUPG:\] GOODSIG [a-fA-F0-9]{16} test name\d* \(no comment\) ", + r"\[GNUPG:\] VALIDSIG [a-fA-F0-9]{40} \d{4}-\d\d-\d\d [a-fA-F0-9]{10} \d \d \d 1 \d* 00 [a-fA-F0-9]{40}", + r"\[GNUPG:\] TRUST_ULTIMATE 0 pgp", + ], + &[ + r"gpg: Signature made .*", + r"gpg: using RSA key [a-fA-F0-9]{40}", + r#"gpg: issuer "test\d*@email.com""#, + r#"pg: Good signature from "test name\d* \(no comment\) "#, + ], + Verify { i: sign_file }, + ); + gnupg_test( + &[ + "admin", + "factory-reset", + "y", + "yes", + "verify", + DEFAULT_PW1, + "quit", + ], + &[ + vec![r"\[GNUPG:\] CARDCTRL \d D2760001240103040000000000000000"], + virt::gpg_status(virt::KeyType::Rsa, 5), + vec![ + r"\[GNUPG:\] GET_LINE cardedit.prompt", + r"\[GNUPG:\] GET_LINE cardedit.prompt", + r"\[GNUPG:\] GET_BOOL cardedit.factory-reset.proceed", + r"\[GNUPG:\] GET_LINE cardedit.factory-reset.really", + r"\[GNUPG:\] GET_LINE cardedit.prompt", + ], + virt::gpg_inquire_pin(), + virt::gpg_status(virt::KeyType::RsaNone, 0), + vec![r"\[GNUPG:\] GET_LINE cardedit.prompt"], + ] + .into_iter() + .flatten() + .collect::>(), + &[ + r"gpg: OpenPGP card no. [0-9A-F]{32} detected", + r"gpg: Note: This command destroys all keys stored on the card!", + ], + EditCard, + ); + }); +} + +#[cfg(feature = "rsa4096-gen")] +fn gpg_rsa_4096() { + with_vsc(|| { + let file_number: u32 = rand::rngs::OsRng.gen(); + let tmp = format!("/tmp/opcard-tests-{file_number}.gpg"); + let encrypted_file = &tmp; + let tmp = format!("/tmp/opcard-tests-{file_number}-sig.gpg"); + let sign_file = &tmp; + let tmp = format!("/tmp/opcard-tests-{file_number}.toml"); + let decrypted_file = &tmp; + let _dropper = FileDropper { + temp_file_name: encrypted_file, + }; + let _dropper = FileDropper { + temp_file_name: sign_file, + }; + let _dropper = FileDropper { + temp_file_name: decrypted_file, + }; + + let tmp = format!("test name{file_number}"); + let temp_name = &tmp; + + let tmp = format!("test{file_number}@email.com"); + let temp_email = &tmp; + + let custom_match = format!( + r"uid:u::::\d{{10}}::[0-9A-F]{{40}}::{temp_name} \(no comment\) <{temp_email}>::::::::::0:" + ); + + gnupg_test( + &[ + "admin", + "key-attr", + "1", + "4096", + DEFAULT_PW3, + "1", + "4096", + DEFAULT_PW3, + "1", + "4096", + DEFAULT_PW3, + "generate", + "n", + DEFAULT_PW3, + DEFAULT_PW1, + "0", + temp_name, + temp_email, + "no comment", + DEFAULT_PW1, + "quit", + ], + &[ + vec![r"\[GNUPG:\] CARDCTRL \d D2760001240103040000000000000000"], + virt::gpg_status(virt::KeyType::RsaNone,0), + vec![ + r"\[GNUPG:\] GET_LINE cardedit.prompt", + r"\[GNUPG:\] GET_LINE cardedit.prompt", + ], + attr_rsa_ask(), + vec![ + r"\[GNUPG:\] GET_LINE cardedit.prompt", + r"\[GNUPG:\] GET_LINE cardedit.genkeys.backup_enc", + ], + virt::gpg_inquire_pin(), + virt::gpg_inquire_pin(), + vec![ + r"\[GNUPG:\] GET_LINE keygen.valid", + r"\[GNUPG:\] GET_LINE keygen.name", + r"\[GNUPG:\] GET_LINE keygen.email", + r"\[GNUPG:\] GET_LINE keygen.comment", + r"\[GNUPG:\] USERID_HINT [0-9A-F]{16} \[\?\]", + r"\[GNUPG:\] NEED_PASSPHRASE [0-9A-F]{16} [0-9A-F]{16} 1 \d", + ], + virt::gpg_inquire_pin(), + vec![ + r"pub:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}:::u:::scESCA:::D2760001240103040000000000000000:::23::0:", + r"fpr:::::::::[0-9A-F]{40}:", + r"grp:::::::::[0-9A-F]{40}:", + &custom_match, + r"sub:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}::::::a:::D2760001240103040000000000000000:::23:", + r"fpr:::::::::[0-9A-F]{40}:", + r"grp:::::::::[0-9A-F]{40}:", + r"sub:u:\d*:1:[0-9A-F]{16}:[0-9A-F]{10}::::::e:::D2760001240103040000000000000000:::23:", + r"fpr:::::::::[0-9A-F]{40}:", + r"grp:::::::::[0-9A-F]{40}:", + r"\[GNUPG:\] KEY_CREATED B [0-9A-F]{40}", + r"\[GNUPG:\] GET_LINE cardedit.prompt", + ] + ].into_iter().flatten().collect::>(), + &[ + r"gpg: revocation certificate stored as '.*\.rev'", + r"gpg: checking the trustdb", + r"gpg: marginals needed: \d completes needed: \d trust model: pgp", + r"gpg: depth:[ 0-9]*valid:[ 0-9]*signed:[ 0-9]*trust: \d*-, \d*q, \d*n, \d*m, \d*f, \d*u", + ], + EditCard, + ); + + println!("================ FINISHED GENERATING 25519 KEYS ================"); + + gnupg_test( + &[], + &[ + r"\[GNUPG:\] BEGIN_ENCRYPTION \d \d", + r"\[GNUPG:\] END_ENCRYPTION", + ], + &[], + Encrypt { + i: "Cargo.toml", + o: encrypted_file, + r: temp_email, + }, + ); + + println!("================ FINISHED ENCRYPTION ================"); + + let custom1 = format!( + r"\[GNUPG:\] USERID_HINT [a-fA-F0-9]{{16}} {temp_name} \(no comment\) <{temp_email}>" + ); + let custom2 = format!(r"{temp_name} \(no comment\) <{temp_email}>"); + gnupg_test( + &[DEFAULT_PW1], + &[ + vec![ + r"\[GNUPG:\] ENC_TO [a-fA-F0-9]{16} \d* \d*", + &custom1, + r"\[GNUPG:\] NEED_PASSPHRASE [a-fA-F0-9]{16} [a-fA-F0-9]{16} 1 0", + ], + virt::gpg_inquire_pin(), + vec![ + r"\[GNUPG:\] DECRYPTION_KEY [a-fA-F0-9]{40} [a-fA-F0-9]{40} u", + r"\[GNUPG:\] BEGIN_DECRYPTION", + r"\[GNUPG:\] DECRYPTION_INFO \d \d \d", + r"\[GNUPG:\] PLAINTEXT \d* \d* Cargo.toml", + r"\[GNUPG:\] PLAINTEXT_LENGTH \d*", + r"\[GNUPG:\] DECRYPTION_OKAY", + r"\[GNUPG:\] GOODMDC", + r"\[GNUPG:\] END_DECRYPTION", + ], + ] + .into_iter() + .flatten() + .collect::>(), + &[ + r"gpg: encrypted with \d*-bit RSA key, ID [a-fA-F0-9]{16}, created \d{4}-\d\d-\d\d", + &custom2, + ], + Decrypt { + i: encrypted_file, + o: decrypted_file, + }, + ); + + println!("================ FINISHED DECRYPTION ================"); + + gnupg_test( + &[DEFAULT_PW1], + &[ + vec![ + r"\[GNUPG:\] CARDCTRL 3 D2760001240103040000000000000000", + r"\[GNUPG:\] BEGIN_SIGNING H\d*", + &custom1, + r"\[GNUPG:\] NEED_PASSPHRASE [a-fA-F0-9]{16} [a-fA-F0-9]{16} 1 0", + ], + virt::gpg_inquire_pin(), + vec![r"\[GNUPG:\] SIG_CREATED S 1 \d* 00 [a-fA-F0-9]{10} [a-fA-F0-9]{40}"], + ] + .into_iter() + .flatten() + .collect::>(), + &[r#"gpg: using "test\d*@email.com" as default secret key for signing"#], + Sign { + i: "Cargo.toml", + o: sign_file, + s: temp_email, + }, + ); + + println!("================ FINISHED SIGNATURE ================"); + + gnupg_test( + &[], + &[ + r"\[GNUPG:\] NEWSIG test\d*@email.com", + r"\[GNUPG:\] SIG_ID [^ ]* \d{4}-\d\d-\d\d [a-fA-F0-9]{10}", + r"\[GNUPG:\] GOODSIG [a-fA-F0-9]{16} test name\d* \(no comment\) ", + r"\[GNUPG:\] VALIDSIG [a-fA-F0-9]{40} \d{4}-\d\d-\d\d [a-fA-F0-9]{10} \d \d \d 1 \d* 00 [a-fA-F0-9]{40}", + r"\[GNUPG:\] TRUST_ULTIMATE 0 pgp", + ], + &[ + r"gpg: Signature made .*", + r"gpg: using RSA key [a-fA-F0-9]{40}", + r#"gpg: issuer "test\d*@email.com""#, + r#"pg: Good signature from "test name\d* \(no comment\) "#, + ], + Verify { i: sign_file }, + ); + gnupg_test( + &[ + "admin", + "factory-reset", + "y", + "yes", + "verify", + DEFAULT_PW1, + "quit", + ], + &[ + vec![r"\[GNUPG:\] CARDCTRL \d D2760001240103040000000000000000"], + virt::gpg_status(virt::KeyType::Rsa4096, 5), + vec![ + r"\[GNUPG:\] GET_LINE cardedit.prompt", + r"\[GNUPG:\] GET_LINE cardedit.prompt", + r"\[GNUPG:\] GET_BOOL cardedit.factory-reset.proceed", + r"\[GNUPG:\] GET_LINE cardedit.factory-reset.really", + r"\[GNUPG:\] GET_LINE cardedit.prompt", + ], + virt::gpg_inquire_pin(), + virt::gpg_status(virt::KeyType::RsaNone, 0), + vec![r"\[GNUPG:\] GET_LINE cardedit.prompt"], + ] + .into_iter() + .flatten() + .collect::>(), + &[ + r"gpg: OpenPGP card no. [0-9A-F]{32} detected", + r"gpg: Note: This command destroys all keys stored on the card!", + ], + EditCard, + ); + }); +} + #[test] fn gpg_crypto() { + #[cfg(feature = "rsa2048")] + gpg_rsa_2048(); + #[cfg(feature = "rsa4096-gen")] + gpg_rsa_4096(); gpg_255(); gpg_p256(); } diff --git a/tests/crypto-sequoia.rs b/tests/crypto-sequoia.rs index fb5643a..7d85e48 100644 --- a/tests/crypto-sequoia.rs +++ b/tests/crypto-sequoia.rs @@ -18,6 +18,111 @@ use test_log::test; #[test] fn sequoia_gen_key() { + #[cfg(feature = "rsa2048")] + virt::with_vsc(|| { + let mut cards = PcscBackend::cards(None).unwrap(); + let mut pgp = OpenPgp::new(cards.pop().unwrap()); + let mut open = Open::new(pgp.transaction().unwrap()).unwrap(); + open.verify_admin(b"12345678").unwrap(); + let mut admin = open.admin_card().unwrap(); + + let (material, gendate) = admin + .generate_key_simple(KeyType::Decryption, Some(AlgoSimple::RSA2k)) + .unwrap(); + let dec_pubk = + public_key_material_to_key(&material, KeyType::Decryption, &gendate, None, None) + .unwrap(); + + let (material, gendate) = admin + .generate_key_simple(KeyType::Authentication, Some(AlgoSimple::RSA2k)) + .unwrap(); + let aut_pubk = + public_key_material_to_key(&material, KeyType::Authentication, &gendate, None, None) + .unwrap(); + + let (material, gendate) = admin + .generate_key_simple(KeyType::Signing, Some(AlgoSimple::RSA2k)) + .unwrap(); + let pubk = + public_key_material_to_key(&material, KeyType::Signing, &gendate, None, None).unwrap(); + + open.verify_user_for_signing(b"123456").unwrap(); + let mut sign_card = open.signing_card().unwrap(); + let mut signer = sign_card.signer_from_public(pubk.clone(), &|| {}); + let data = [1; 32]; + let signature = signer.sign(HashAlgorithm::SHA256, &data).unwrap(); + assert!(pubk + .verify(&signature, HashAlgorithm::SHA256, &data) + .is_ok()); + + open.verify_user(b"123456").unwrap(); + let mut user_card = open.user_card().unwrap(); + let mut authenticator = user_card.authenticator_from_public(aut_pubk.clone(), &|| {}); + let data = [2; 32]; + let signature = authenticator.sign(HashAlgorithm::SHA256, &data).unwrap(); + assert!(aut_pubk + .verify(&signature, HashAlgorithm::SHA256, &data) + .is_ok()); + + 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, &|| {}); + assert_eq!(session, decryptor.decrypt(&ciphertext, None).unwrap()); + }); + + #[cfg(feature = "rsa4096-gen")] + virt::with_vsc(|| { + let mut cards = PcscBackend::cards(None).unwrap(); + let mut pgp = OpenPgp::new(cards.pop().unwrap()); + let mut open = Open::new(pgp.transaction().unwrap()).unwrap(); + open.verify_admin(b"12345678").unwrap(); + let mut admin = open.admin_card().unwrap(); + + let (material, gendate) = admin + .generate_key_simple(KeyType::Decryption, Some(AlgoSimple::RSA4k)) + .unwrap(); + let dec_pubk = + public_key_material_to_key(&material, KeyType::Decryption, &gendate, None, None) + .unwrap(); + + let (material, gendate) = admin + .generate_key_simple(KeyType::Authentication, Some(AlgoSimple::RSA4k)) + .unwrap(); + let aut_pubk = + public_key_material_to_key(&material, KeyType::Authentication, &gendate, None, None) + .unwrap(); + + let (material, gendate) = admin + .generate_key_simple(KeyType::Signing, Some(AlgoSimple::RSA4k)) + .unwrap(); + let pubk = + public_key_material_to_key(&material, KeyType::Signing, &gendate, None, None).unwrap(); + + open.verify_user_for_signing(b"123456").unwrap(); + let mut sign_card = open.signing_card().unwrap(); + let mut signer = sign_card.signer_from_public(pubk.clone(), &|| {}); + let data = [1; 64]; + let signature = signer.sign(HashAlgorithm::SHA512, &data).unwrap(); + assert!(pubk + .verify(&signature, HashAlgorithm::SHA512, &data) + .is_ok()); + open.verify_user(b"123456").unwrap(); + let mut user_card = open.user_card().unwrap(); + let mut authenticator = user_card.authenticator_from_public(aut_pubk.clone(), &|| {}); + let data = [2; 64]; + let signature = authenticator.sign(HashAlgorithm::SHA512, &data).unwrap(); + assert!(aut_pubk + .verify(&signature, HashAlgorithm::SHA512, &data) + .is_ok()); + + 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, &|| {}); + assert_eq!(session, decryptor.decrypt(&ciphertext, None).unwrap()); + }); + virt::with_vsc(|| { let mut cards = PcscBackend::cards(None).unwrap(); let mut pgp = OpenPgp::new(cards.pop().unwrap()); diff --git a/tests/virt/mod.rs b/tests/virt/mod.rs index 9d71a80..4fe8a57 100644 --- a/tests/virt/mod.rs +++ b/tests/virt/mod.rs @@ -17,6 +17,9 @@ use stoppable_thread::spawn; const STDOUT_FILTER: &[&str] = &[ r"\[GNUPG:\] KEY_CONSIDERED [0-9A-F]{40} \d", + r"\[GNUPG:\] ENCRYPTION_COMPLIANCE_MODE \d*", + r"\[GNUPG:\] DECRYPTION_COMPLIANCE_MODE \d*", + r"\[GNUPG:\] VERIFICATION_COMPLIANCE_MODE \d*", r"\[GNUPG:\] GOT_IT", ]; @@ -67,6 +70,10 @@ pub fn with_vsc R, R>(f: F) -> R { #[allow(unused)] pub enum KeyType { RsaNone, + Rsa, + Rsa4096, + RsaNoAut, + Rsa4096NoAut, Cv25519, Cv25519NoAut, P256, @@ -104,6 +111,34 @@ pub fn gpg_status(key: KeyType, sign_count: usize) -> Vec<&'static str> { "fpr:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}::", "grp:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}:[0]{40}:", ), + KeyType::Rsa => ( + r"keyattr:1:1:2048:", + r"keyattr:2:1:2048:", + r"keyattr:3:1:2048:", + "fpr:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}:", + "grp:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}:", + ), + KeyType::Rsa4096 => ( + r"keyattr:1:1:4096:", + r"keyattr:2:1:4096:", + r"keyattr:3:1:4096:", + "fpr:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}:", + "grp:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}:", + ), + KeyType::RsaNoAut => ( + r"keyattr:1:1:2048:", + r"keyattr:2:1:2048:", + r"keyattr:3:1:2048:", + "fpr:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}::", + "grp:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}:[0]{40}:", + ), + KeyType::Rsa4096NoAut => ( + r"keyattr:1:1:4096:", + r"keyattr:2:1:4096:", + r"keyattr:3:1:2048:", + "fpr:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}::", + "grp:[0-9a-zA-Z]{40}:[0-9a-zA-Z]{40}:[0]{40}:", + ), KeyType::RsaNone => ( r"keyattr:1:1:2048:", r"keyattr:2:1:2048:",