// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use anyhow::Result; use std::convert::TryFrom; use std::str::FromStr; use std::string::FromUtf8Error; use thiserror; use sequoia_openpgp::parse::Parse; use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::serialize::SerializeInto; use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; use sequoia_openpgp::Cert; use openpgp_card::algorithm::AlgoSimple; use openpgp_card::card_do::{KeyGenerationTime, Sex}; use openpgp_card::{Error, KeyType, OpenPgp, OpenPgpTransaction, StatusBytes}; use openpgp_card_sequoia::card::Open; use openpgp_card_sequoia::util::{ make_cert, public_key_material_and_fp_to_key, public_key_material_to_key, }; use crate::cards::TestCardData; use crate::util; #[derive(Debug)] pub enum TestResult { Status(StatusBytes), StatusOk, Text(String), } type TestOutput = Vec; #[derive(thiserror::Error, Debug)] pub enum TestError { #[error("Failed to upload key {0} ({1})")] KeyUploadError(String, anyhow::Error), #[error(transparent)] OPGP(#[from] Error), #[error(transparent)] OCard(#[from] StatusBytes), #[error(transparent)] Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error #[error(transparent)] Utf8Error(#[from] FromUtf8Error), } /// Run after each "upload keys", if key *was* uploaded (?) pub fn test_decrypt(pgp: &mut OpenPgp, param: &[&str]) -> Result { let mut pgpt = pgp.transaction()?; assert_eq!( param.len(), 2, "test_decrypt needs filenames for 'cert' and 'encrypted'" ); let msg = param[1].to_string(); pgpt.verify_pw1_user(b"123456")?; let p = StandardPolicy::new(); let mut open = Open::new(pgpt)?; let mut user = open.user_card().unwrap(); let d = user.decryptor(&|| {})?; let res = openpgp_card_sequoia::util::decrypt(d, msg.into_bytes(), &p)?; let plain = String::from_utf8_lossy(&res); assert_eq!(plain, "Hello world!\n"); Ok(vec![]) } /// Run after each "upload keys", if key *was* uploaded (?) pub fn test_sign(pgp: &mut OpenPgp, param: &[&str]) -> Result { let mut pgpt = pgp.transaction()?; assert_eq!(param.len(), 1, "test_sign needs a filename for 'cert'"); pgpt.verify_pw1_sign(b"123456")?; let cert = Cert::from_str(param[0])?; let mut open = Open::new(pgpt)?; let mut sign = open.signing_card().unwrap(); let s = sign.signer(&|| {})?; let msg = "Hello world, I am signed."; let sig = openpgp_card_sequoia::util::sign(s, &mut msg.as_bytes())?; // validate sig assert!(util::verify_sig(&cert, msg.as_bytes(), sig.as_bytes())?); Ok(vec![]) } fn check_key_upload_metadata( pgpt: &mut OpenPgpTransaction, meta: &[(String, KeyGenerationTime)], ) -> Result<()> { let ard = pgpt.application_related_data()?; // check fingerprints let card_fp = ard.fingerprints()?; let sig = card_fp.signature().expect("signature fingerprint"); assert_eq!(format!("{:X}", sig), meta[0].0); let dec = card_fp.decryption().expect("decryption fingerprint"); assert_eq!(format!("{:X}", dec), meta[1].0); let auth = card_fp .authentication() .expect("authentication fingerprint"); assert_eq!(format!("{:X}", auth), meta[2].0); // get_key_generation_times let card_kg = ard.key_generation_times()?; let sig = card_kg.signature().expect("signature creation time"); assert_eq!(sig, &meta[0].1); let dec = card_kg.decryption().expect("decryption creation time"); assert_eq!(dec, &meta[1].1); let auth = card_kg .authentication() .expect("authentication creation time"); assert_eq!(auth, &meta[2].1); Ok(()) } fn check_key_upload_algo_attrs() -> Result<()> { // get_algorithm_attributes // FIXME Ok(()) } pub fn test_print_caps(pgp: &mut OpenPgp, _param: &[&str]) -> Result { let mut pgpt = pgp.transaction()?; let ard = pgpt.application_related_data()?; let aid = ard.application_id()?; println!("aid: {:#x?}", aid); let hist = ard.historical_bytes()?; println!("hist: {:#?}", hist); let ecap = ard.extended_capabilities()?; println!("ecap: {:#?}", ecap); let eli = ard.extended_length_information()?; println!("eli: {:#?}", eli); Ok(vec![]) } pub fn test_print_algo_info(pgp: &mut OpenPgp, _param: &[&str]) -> Result { let mut pgpt = pgp.transaction()?; let ard = pgpt.application_related_data()?; let dec = ard.algorithm_attributes(KeyType::Decryption)?; println!("Current algorithm for the decrypt slot: {}", dec); println!(); let algo = pgpt.algorithm_information(); if let Ok(Some(algo)) = algo { println!("Card algorithm list:\n{}", algo); } Ok(vec![]) } pub fn test_upload_keys(pgp: &mut OpenPgp, param: &[&str]) -> Result { let mut pgpt = pgp.transaction()?; assert_eq!( param.len(), 1, "test_upload_keys needs a filename for 'cert'" ); pgpt.verify_pw3(b"12345678")?; let cert = Cert::from_file(param[0])?; let p = StandardPolicy::new(); let meta = util::upload_subkeys(&mut pgpt, &cert, &p) .map_err(|e| TestError::KeyUploadError(param[0].to_string(), e))?; check_key_upload_metadata(&mut pgpt, &meta)?; // FIXME: implement check_key_upload_algo_attrs()?; Ok(vec![]) } /// Generate keys for each of the three KeyTypes pub fn test_keygen(pgp: &mut OpenPgp, param: &[&str]) -> Result { let pgpt = pgp.transaction()?; let mut open = Open::new(pgpt)?; open.verify_admin(b"12345678")?; let mut admin = open.admin_card().expect("Couldn't get Admin card"); // Generate all three subkeys on card let algo = param[0]; let alg = AlgoSimple::try_from(algo)?; println!(" Generate subkey for Signing"); let (pkm, ts) = admin.generate_key_simple(KeyType::Signing, Some(alg))?; let key_sig = public_key_material_to_key(&pkm, KeyType::Signing, &ts, None, None)?; println!(" Generate subkey for Decryption"); let (pkm, ts) = admin.generate_key_simple(KeyType::Decryption, Some(alg))?; let key_dec = public_key_material_to_key( &pkm, KeyType::Decryption, &ts, Some(HashAlgorithm::SHA256), Some(SymmetricAlgorithm::AES128), )?; println!(" Generate subkey for Authentication"); let (pkm, ts) = admin.generate_key_simple(KeyType::Authentication, Some(alg))?; let key_aut = public_key_material_to_key(&pkm, KeyType::Authentication, &ts, None, None)?; // Generate a Cert for this set of generated keys let cert = make_cert( &mut open, key_sig, Some(key_dec), Some(key_aut), Some(b"123456"), &|| {}, &|| {}, &[], )?; let armored = String::from_utf8(cert.armored().to_vec()?)?; let res = TestResult::Text(armored); Ok(vec![res]) } /// Construct public key based on data from the card pub fn test_get_pub(pgp: &mut OpenPgp, _param: &[&str]) -> Result { let mut pgpt = pgp.transaction()?; let ard = pgpt.application_related_data()?; let times = ard.key_generation_times()?; let fps = ard.fingerprints()?; // -- let sig = pgpt.public_key(KeyType::Signing)?; let ts = times.signature().unwrap().get().into(); let key = public_key_material_and_fp_to_key(&sig, KeyType::Signing, &ts, fps.signature().unwrap())?; println!(" sig key data from card -> {:x?}", key); // -- let dec = pgpt.public_key(KeyType::Decryption)?; let ts = times.decryption().unwrap().get().into(); let key = public_key_material_and_fp_to_key( &dec, KeyType::Decryption, &ts, fps.decryption().unwrap(), )?; println!(" dec key data from card -> {:x?}", key); // -- let auth = pgpt.public_key(KeyType::Authentication)?; let ts = times.authentication().unwrap().get().into(); let key = public_key_material_and_fp_to_key( &auth, KeyType::Authentication, &ts, fps.authentication().unwrap(), )?; println!(" auth key data from card -> {:x?}", key); Ok(vec![]) } pub fn test_reset(pgp: &mut OpenPgp, _param: &[&str]) -> Result { let mut pgpt = pgp.transaction()?; pgpt.factory_reset()?; Ok(vec![]) } /// Sets name, lang, sex, url; then reads the fields from the card and /// compares the values with the expected values. /// /// Returns an empty TestOutput, throws errors for unexpected Status codes /// and for unequal field values. pub fn test_set_user_data(pgp: &mut OpenPgp, _param: &[&str]) -> Result { let mut pgpt = pgp.transaction()?; pgpt.verify_pw3(b"12345678")?; // name pgpt.set_name(b"Bar< Result { let mut pgpt = pgp.transaction()?; let out = vec![]; println!(); let d = pgpt.private_use_do(1)?; println!("data 1 {:?}", d); pgpt.verify_pw1_user(b"123456")?; pgpt.set_private_use_do(1, "Foo bar1!".as_bytes().to_vec())?; pgpt.set_private_use_do(3, "Foo bar3!".as_bytes().to_vec())?; pgpt.verify_pw3(b"12345678")?; pgpt.set_private_use_do(2, "Foo bar2!".as_bytes().to_vec())?; pgpt.set_private_use_do(4, "Foo bar4!".as_bytes().to_vec())?; let d = pgpt.private_use_do(1)?; println!("data 1 {:?}", d); let d = pgpt.private_use_do(2)?; println!("data 2 {:?}", d); let d = pgpt.private_use_do(3)?; println!("data 3 {:?}", d); let d = pgpt.private_use_do(4)?; println!("data 4 {:?}", d); Ok(out) } // pub fn test_cardholder_cert( // card_tx: &mut CardApp, // _param: &[&str], // ) -> Result { // let mut out = vec![]; // // println!(); // // match card_tx.cardholder_certificate() { // Ok(res) => { // out.push(TestResult::Text(format!("got cert {:x?}", res.data()))) // } // Err(e) => { // out.push(TestResult::Text(format!( // "get_cardholder_certificate failed: {:?}", // e // ))); // return Ok(out); // } // }; // // card_tx.verify_pw3("12345678")?; // // let data = "Foo bar baz!".as_bytes(); // // match card_tx.set_cardholder_certificate(data.to_vec()) { // Ok(_resp) => out.push(TestResult::Text("set cert ok".to_string())), // Err(e) => { // out.push(TestResult::Text(format!( // "set_cardholder_certificate: {:?}", // e // ))); // return Ok(out); // } // } // // let res = card_tx.cardholder_certificate()?; // out.push(TestResult::Text("get cert ok".to_string())); // // if res.data() != data { // out.push(TestResult::Text(format!( // "get after set doesn't match original data: {:x?}", // data // ))); // return Ok(out); // }; // // // try using slot 2 // // match card_tx.select_data(2, &[0x7F, 0x21]) { // Ok(_res) => out.push(TestResult::Text("select_data ok".to_string())), // Err(e) => { // out.push(TestResult::Text(format!("select_data: {:?}", e))); // return Ok(out); // } // } // // Ok(out) // } pub fn test_pw_status(pgp: &mut OpenPgp, _param: &[&str]) -> Result { let mut pgpt = pgp.transaction()?; let out = vec![]; let ard = pgpt.application_related_data()?; let mut pws = ard.pw_status_bytes()?; println!("pws {:?}", pws); pgpt.verify_pw3(b"12345678")?; pws.set_pw1_cds_valid_once(false); pws.set_pw1_pin_block(true); pgpt.set_pw_status_bytes(&pws, false)?; let ard = pgpt.application_related_data()?; let pws = ard.pw_status_bytes()?; println!("pws {:?}", pws); Ok(out) } /// Outputs: /// - verify pw3 (check) -> Status /// - verify pw1 (check) -> Status pub fn test_verify(pgp: &mut OpenPgp, _param: &[&str]) -> Result { let mut pgpt = pgp.transaction()?; // Steps: // // - try to set name without verify, assert result is not ok // - verify pw3 + pin -> Status // - verify pw3 (check) -> Status // - set name -> Status // - get name -> Text(name) // - verify pw1 + pin -> Status // - verify pw1 (check) -> Status // - set name -> Status // - get name -> Text(name) let mut out = vec![]; // try to set name without verify, assert result is not ok! let res = pgpt.set_name("Notverified< { // e.g. yubikey5 returns an error status! out.push(TestResult::Status(s)); } Err(_) => { panic!("unexpected error"); } Ok(_) => out.push(TestResult::StatusOk), } pgpt.set_name(b"Admin< { // e.g. yubikey5 returns an error status! out.push(TestResult::Status(s)); } Err(_) => { panic!("unexpected error"); } Ok(_) => out.push(TestResult::StatusOk), } pgpt.set_name(b"There< Result { let mut pgpt = pgp.transaction()?; let out = vec![]; // first do admin-less pw1 on gnuk // (NOTE: Gnuk requires a key to be loaded before allowing pw changes!) println!("change pw1"); pgpt.change_pw1(b"123456", b"abcdef00")?; // also set admin pw, which means pw1 is now only user-pw again, on gnuk println!("change pw3"); // ca.change_pw3("abcdef00", "abcdefgh")?; // gnuk pgpt.change_pw3(b"12345678", b"abcdefgh")?; println!("change pw1"); pgpt.change_pw1(b"abcdef00", b"abcdef")?; // gnuk // ca.change_pw1("123456", "abcdef")?; println!("verify bad pw1"); match pgpt.verify_pw1_user(b"123456ab") { Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => { // this is expected } Err(_) => { panic!("unexpected error"); } Ok(_) => panic!("this value for pw1 should be considered wrong!"), } println!("verify good pw1"); pgpt.verify_pw1_user(b"abcdef")?; println!("verify bad pw3"); match pgpt.verify_pw3(b"00000000") { Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => { // this is expected } Err(_) => { panic!("unexpected error"); } Ok(_) => panic!("this value for pw3 should be considered wrong!"), } println!("verify good pw3"); pgpt.verify_pw3(b"abcdefgh")?; println!("change pw3 back to default"); pgpt.change_pw3(b"abcdefgh", b"12345678")?; println!("change pw1 back to default"); pgpt.change_pw1(b"abcdef", b"123456")?; Ok(out) } pub fn test_reset_retry_counter( pgp: &mut OpenPgp, _param: &[&str], ) -> Result { let mut pgpt = pgp.transaction()?; let out = vec![]; // set pw3, then pw1 (to bring gnuk into non-admin mode) println!("set pw3"); pgpt.change_pw3(b"12345678", b"12345678")?; println!("set pw1"); pgpt.change_pw1(b"123456", b"123456")?; println!("break pw1"); let _ = pgpt.verify_pw1_user(b"wrong0"); let _ = pgpt.verify_pw1_user(b"wrong0"); let _ = pgpt.verify_pw1_user(b"wrong0"); let res = pgpt.verify_pw1_user(b"wrong0"); match res { Err(Error::CardStatus(StatusBytes::AuthenticationMethodBlocked)) => { // this is expected } Err(Error::CardStatus(StatusBytes::IncorrectParametersCommandDataField)) => { println!( "yk says IncorrectParametersCommandDataField when PW \ error count is exceeded" ); } Err(e) => { panic!("unexpected error {:?}", e); } Ok(_) => panic!("use of pw1 should be blocked!"), } println!("verify pw3"); pgpt.verify_pw3(b"12345678")?; println!("set resetting code"); pgpt.set_resetting_code(b"abcdefgh")?; println!("reset retry counter"); // ca.reset_retry_counter_pw1("abcdef".as_bytes().to_vec(), None)?; let _res = pgpt.reset_retry_counter_pw1(b"abcdef", Some(b"abcdefgh")); println!("verify good pw1"); pgpt.verify_pw1_user(b"abcdef")?; println!("verify bad pw1"); match pgpt.verify_pw1_user(b"00000000") { Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => { // this is expected } Err(_) => { panic!("unexpected error"); } Ok(_) => panic!("this value for pw1 should be considered wrong!"), } Ok(out) } pub fn run_test( tc: &mut TestCardData, t: fn(&mut OpenPgp, &[&str]) -> Result, param: &[&str], ) -> Result { let card = tc.get_card()?; let mut pgp = OpenPgp::new(card); t(&mut pgp, param) }