From 7acc1deb98a2d3ff880b5116d35fee8f93fca337 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 6 Aug 2021 20:14:02 +0200 Subject: [PATCH] - Implement key generation (without specifying an algorithm so the current algo is used. only supports RSA for now) - Refactor: rename key_upload.rs -> keys.rs - Fix handling of key timestamps --- card-functionality/src/main.rs | 41 +++++- openpgp-card-sequoia/src/lib.rs | 32 ++++- openpgp-card/src/apdu/commands.rs | 5 + openpgp-card/src/card_app.rs | 41 +++++- openpgp-card/src/{key_upload.rs => keys.rs} | 124 +++++++++++++++--- openpgp-card/src/lib.rs | 41 +++++- .../src/parse/key_generation_times.rs | 4 +- 7 files changed, 248 insertions(+), 40 deletions(-) rename openpgp-card/src/{key_upload.rs => keys.rs} (80%) diff --git a/card-functionality/src/main.rs b/card-functionality/src/main.rs index e712712..ccec2d2 100644 --- a/card-functionality/src/main.rs +++ b/card-functionality/src/main.rs @@ -26,6 +26,8 @@ //! the command data field"). use anyhow::{Error, Result}; +use std::convert::TryInto; +use std::time::SystemTime; use thiserror::Error; use sequoia_openpgp::parse::Parse; @@ -33,7 +35,7 @@ use sequoia_openpgp::Cert; use openpgp_card::card_app::CardApp; use openpgp_card::errors::{OcErrorStatus, OpenpgpCardError}; -use openpgp_card::Sex; +use openpgp_card::{KeyType, PublicKeyMaterial, Sex}; use crate::cards::{TestCard, TestConfig}; @@ -203,10 +205,32 @@ fn test_upload_keys( Ok(vec![]) } -fn test_keygen() { - // FIXME - // (implementation of this functionality is still missing in openpgp-card) - unimplemented!() +/// Generate keys for each of the three KeyTypes +fn test_keygen( + ca: &mut CardApp, + _param: &[&str], +) -> Result { + let verify = ca.verify_pw3("12345678")?; + verify.check_ok()?; + + let fp = |pkm: &PublicKeyMaterial, ts: SystemTime| { + // FIXME: store creation timestamp + + let key = openpgp_card_sequoia::public_key_material_to_key(pkm, ts)?; + + let fp = key.fingerprint(); + let fp = fp.as_bytes(); + assert_eq!(fp.len(), 20); + + println!("fp {:?}", fp); + Ok(fp.try_into().unwrap()) + }; + + ca.generate_key(fp, KeyType::Signing)?; + ca.generate_key(fp, KeyType::Decryption)?; + ca.generate_key(fp, KeyType::Authentication)?; + + Ok(vec![]) } fn test_reset( @@ -342,6 +366,11 @@ fn main() -> Result<()> { println!("Reset"); let _ = run_test(&mut card, test_reset, &[])?; + println!("Generate key"); + let _ = run_test(&mut card, test_keygen, &[])?; + + panic!(); + print!("Verify"); let verify_out = run_test(&mut card, test_verify, &[])?; println!(" {:x?}", verify_out); @@ -385,8 +414,6 @@ fn main() -> Result<()> { println!(" {:x?}", sign_out); } - // FIXME: generate keys - // FIXME: upload key with password println!(); diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 007ac2e..df60cd5 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -4,11 +4,12 @@ //! This library supports using openpgp-card functionality with //! sequoia_openpgp data structures. +use std::convert::TryFrom; use std::error::Error; use std::io; +use std::time::SystemTime; use anyhow::{anyhow, Context, Result}; -use chrono::prelude::*; use openpgp::armor; use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation; use openpgp::crypto::mpi; @@ -23,8 +24,10 @@ use sequoia_openpgp as openpgp; use openpgp_card::card_app::CardApp; use openpgp_card::{ errors::OpenpgpCardError, CardAdmin, CardUploadableKey, EccKey, EccType, - KeyType, PrivateKeyMaterial, RSAKey, + KeyType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey, }; +use sequoia_openpgp::packet::key::{Key4, PublicParts}; +use sequoia_openpgp::types::Timestamp; mod decryptor; mod signer; @@ -67,6 +70,22 @@ pub fn vka_as_uploadable_key( Box::new(sqk) } +/// Helper fn: get a Key for a PublicKeyMaterial +pub fn public_key_material_to_key( + pkm: &PublicKeyMaterial, + time: SystemTime, +) -> Result> { + match pkm { + PublicKeyMaterial::R(rsa) => { + let k4: Key4 = + Key4::import_public_rsa(&rsa.v, &rsa.n, Some(time))?; + + Ok(Key::from(k4)) + } + _ => unimplemented!("ECC not implemented yet"), + } +} + /// Implement the `CardUploadableKey` trait that openpgp-card uses to /// upload (sub)keys to a card. impl CardUploadableKey for SequoiaKey { @@ -144,9 +163,12 @@ impl CardUploadableKey for SequoiaKey { } } - fn get_ts(&self) -> u64 { - let key_creation: DateTime = self.key.creation_time().into(); - key_creation.timestamp() as u64 + /// Number of non-leap seconds since January 1, 1970 0:00:00 UTC + /// (aka "UNIX timestamp") + fn get_ts(&self) -> u32 { + let ts: Timestamp = Timestamp::try_from(self.key.creation_time()) + .expect("Creation time cannot be converted into u32 timestamp"); + ts.into() } fn get_fp(&self) -> Vec { diff --git a/openpgp-card/src/apdu/commands.rs b/openpgp-card/src/apdu/commands.rs index 050e651..4621906 100644 --- a/openpgp-card/src/apdu/commands.rs +++ b/openpgp-card/src/apdu/commands.rs @@ -126,3 +126,8 @@ pub fn decryption(data: Vec) -> Command { pub fn signature(data: Vec) -> Command { Command::new(0x00, 0x2A, 0x9e, 0x9a, data) } + +/// Creates new APDU for "GENERATE ASYMMETRIC KEY PAIR" +pub fn gen_key(data: Vec) -> Command { + Command::new(0x00, 0x47, 0x80, 0x00, data) +} diff --git a/openpgp-card/src/card_app.rs b/openpgp-card/src/card_app.rs index 79a516a..e54bdb1 100644 --- a/openpgp-card/src/card_app.rs +++ b/openpgp-card/src/card_app.rs @@ -11,6 +11,7 @@ use std::borrow::BorrowMut; use std::convert::TryFrom; +use std::time::SystemTime; use anyhow::{anyhow, Result}; @@ -20,13 +21,12 @@ use crate::parse::{ algo_attrs::Algo, algo_info::AlgoInfo, application_id::ApplicationId, cardholder::CardHolder, extended_cap::ExtendedCap, extended_length_info::ExtendedLengthInfo, fingerprint, - historical::Historical, key_generation_times, - key_generation_times::KeyGeneration, pw_status::PWStatus, KeySet, + historical::Historical, key_generation_times, pw_status::PWStatus, KeySet, }; use crate::tlv::{tag::Tag, Tlv, TlvEntry}; use crate::{ - apdu, key_upload, CardCaps, CardClientBox, CardUploadableKey, DecryptMe, - Hash, KeyType, Sex, + apdu, keys, CardCaps, CardClientBox, CardUploadableKey, DecryptMe, Hash, + KeyGeneration, KeyType, PublicKeyMaterial, Sex, }; pub struct CardApp { @@ -524,6 +524,27 @@ impl CardApp { apdu::send_command(&mut self.card_client, put_url, false) } + pub fn set_creation_time( + &mut self, + time: u32, + key_type: KeyType, + ) -> Result { + // Timestamp update + let time_value: Vec = time + .to_be_bytes() + .iter() + .skip_while(|&&e| e == 0) + .copied() + .collect(); + + let time_cmd = commands::put_data( + &[key_type.get_timestamp_put_tag()], + time_value, + ); + + apdu::send_command(&mut self.card_client, time_cmd, false) + } + pub fn upload_key( &mut self, key: Box, @@ -539,6 +560,16 @@ impl CardApp { None }; - key_upload::upload_key(self, key, key_type, algo_list) + keys::upload_key(self, key, key_type, algo_list) + } + + // FIXME: use subset of CardUploadableKey to specify algo? + pub fn generate_key( + &mut self, + fp_from_pub: fn(&PublicKeyMaterial, SystemTime) -> Result<[u8; 20]>, + key_type: KeyType, + ) -> Result<(), OpenpgpCardError> { + // FIXME: specify algo; pass in algo list? + keys::gen_key_with_metadata(self, fp_from_pub, key_type) } } diff --git a/openpgp-card/src/key_upload.rs b/openpgp-card/src/keys.rs similarity index 80% rename from openpgp-card/src/key_upload.rs rename to openpgp-card/src/keys.rs index 3c170d7..294e91c 100644 --- a/openpgp-card/src/key_upload.rs +++ b/openpgp-card/src/keys.rs @@ -1,7 +1,10 @@ // SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 +//! Generate and import keys + use anyhow::{anyhow, Result}; +use std::time::{SystemTime, UNIX_EPOCH}; use crate::apdu::command::Command; use crate::apdu::commands; @@ -10,12 +13,109 @@ use crate::errors::OpenpgpCardError; use crate::parse::algo_attrs::{Algo, RsaAttrs}; use crate::parse::algo_info::AlgoInfo; use crate::tlv::{tag::Tag, Tlv, TlvEntry}; -use crate::{apdu, CardClientBox}; +use crate::{apdu, EccPub, PublicKeyMaterial, RSAPub}; use crate::{ tlv, CardUploadableKey, EccKey, EccType, KeyType, PrivateKeyMaterial, RSAKey, }; +/// `fp_from_pub` calculates the fingerprint for a public key data object +pub(crate) fn gen_key_with_metadata( + card_app: &mut CardApp, + fp_from_pub: fn(&PublicKeyMaterial, SystemTime) -> Result<[u8; 20]>, + key_type: KeyType, +) -> Result<(), OpenpgpCardError> { + let pubkey = gen_key(card_app, key_type)?; + + // set creation time + let time = SystemTime::now(); + + // Store creation timestamp (unix time format, limited to u32) + let ts = time + .duration_since(UNIX_EPOCH) + .map_err(|e| OpenpgpCardError::InternalError(anyhow!(e)))? + .as_secs() as u32; + + card_app.set_creation_time(ts, key_type)?.check_ok()?; + + // calculate/store fingerprint + let fp = fp_from_pub(&pubkey, time)?; + let fp_cmd = + commands::put_data(&[key_type.get_fingerprint_put_tag()], fp.to_vec()); + + apdu::send_command(card_app.card(), fp_cmd, true)?.check_ok()?; + + Ok(()) +} + +fn tlv_to_pubkey(tlv: &Tlv) -> Result { + let n = tlv.find(&Tag::new(vec![0x81])); + let v = tlv.find(&Tag::new(vec![0x82])); + + let ec = tlv.find(&Tag::new(vec![0x86])); + + match (n, v, ec) { + (Some(n), Some(v), None) => { + let rsa = RSAPub { + n: n.serialize(), + v: v.serialize(), + }; + + Ok(PublicKeyMaterial::R(rsa)) + } + (None, None, Some(ec)) => { + let ec = ec.serialize(); + + // The public key for ECDSA/DH consists of of two raw + // big-endian integers with the same length as a field element + // each. In compliance with EN 419212 the format is 04 || x || y + // where the first byte (04) indicates an uncompressed raw format. + + assert_eq!(ec[0], 0x4); + + let len = ec.len(); + assert_eq!(len % 2, 1); // odd number of bytes + + // len 3 -> 4/2 = 2 + let middle = (len + 1) / 2; + let x = ec[1..middle].to_vec(); + let y = ec[middle..].to_vec(); + + let ecc = EccPub { x, y }; + + Ok(PublicKeyMaterial::E(ecc)) + } + + (_, _, _) => { + unimplemented!() + } + } +} + +pub(crate) fn gen_key( + card_app: &mut CardApp, + key_type: KeyType, +) -> Result { + println!("gen key for {:?}", key_type); + + // generate key + let crt = get_crt(key_type)?; + let gen_key_cmd = commands::gen_key(crt.serialize().to_vec()); + + let card_client = card_app.card(); + + let resp = apdu::send_command(card_client, gen_key_cmd, true)?; + resp.check_ok()?; + + let tlv = Tlv::try_from(resp.data()?)?; + + let pubkey = tlv_to_pubkey(&tlv)?; + + println!("public {:x?}", pubkey); + + Ok(pubkey) +} + /// Upload an explicitly selected Key to the card as a specific KeyType. /// /// The client needs to make sure that the key is suitable for `key_type`. @@ -87,7 +187,7 @@ pub(crate) fn upload_key( }; copy_key_to_card( - card_app.card(), + card_app, key_type, key.get_ts(), key.get_fp(), @@ -385,28 +485,17 @@ fn ecc_algo_attrs_cmd( } fn copy_key_to_card( - card_client: &mut CardClientBox, + card_app: &mut CardApp, key_type: KeyType, - ts: u64, + ts: u32, fp: Vec, algo_cmd: Command, key_cmd: Command, ) -> Result<(), OpenpgpCardError> { let fp_cmd = commands::put_data(&[key_type.get_fingerprint_put_tag()], fp); - // Timestamp update - let time_value: Vec = ts - .to_be_bytes() - .iter() - .skip_while(|&&e| e == 0) - .copied() - .collect(); - - // Generation date/time - let time_cmd = - commands::put_data(&[key_type.get_timestamp_put_tag()], time_value); - // Send all the commands + let card_client = card_app.card(); // FIXME: Only write algo attributes to the card if "extended // capabilities" show that they are changeable! @@ -414,7 +503,8 @@ fn copy_key_to_card( apdu::send_command(card_client, key_cmd, false)?.check_ok()?; apdu::send_command(card_client, fp_cmd, false)?.check_ok()?; - apdu::send_command(card_client, time_cmd, false)?.check_ok()?; + + card_app.set_creation_time(ts, key_type)?.check_ok()?; Ok(()) } diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index b81b7a5..ffb9146 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -21,7 +21,7 @@ pub mod apdu; mod card; pub mod card_app; pub mod errors; -mod key_upload; +mod keys; mod parse; mod tlv; @@ -66,6 +66,16 @@ impl CardCaps { } } +/// An OpenPGP key generation Time +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct KeyGeneration(u32); + +impl KeyGeneration { + pub fn get(&self) -> u32 { + self.0 + } +} + /// Container for a hash value. /// These hash values can be signed by the card. pub enum Hash<'a> { @@ -111,12 +121,37 @@ pub trait CardUploadableKey { fn get_key(&self) -> Result; /// timestamp of (sub)key creation - fn get_ts(&self) -> u64; + fn get_ts(&self) -> u32; /// fingerprint fn get_fp(&self) -> Vec; } +/// Algorithm-independent container for public key material retrieved from +/// an OpenPGP card +#[derive(Debug)] +pub enum PublicKeyMaterial { + R(RSAPub), + E(EccPub), +} + +/// RSA-specific container for public key material from an OpenPGP card. +#[derive(Debug)] +pub struct RSAPub { + /// Modulus (a number denoted as n coded on x bytes) + pub n: Vec, + + /// Public exponent (a number denoted as v, e.g. 65537 dec.) + pub v: Vec, +} + +/// ECC-specific container for public key material from an OpenPGP card. +#[derive(Debug)] +pub struct EccPub { + pub x: Vec, + pub y: Vec, +} + /// Algorithm-independent container for private key material to upload to /// an OpenPGP card pub enum PrivateKeyMaterial { @@ -678,7 +713,7 @@ impl CardAdmin { ) -> Result<(), OpenpgpCardError> { let algo_list = self.list_supported_algo()?; - key_upload::upload_key(&mut self.card_app, key, key_type, algo_list) + keys::upload_key(&mut self.card_app, key, key_type, algo_list) } } diff --git a/openpgp-card/src/parse/key_generation_times.rs b/openpgp-card/src/parse/key_generation_times.rs index f4a624e..b8c1a97 100644 --- a/openpgp-card/src/parse/key_generation_times.rs +++ b/openpgp-card/src/parse/key_generation_times.rs @@ -7,9 +7,7 @@ use nom::{combinator, number::complete as number, sequence}; use crate::errors::OpenpgpCardError; use crate::parse::KeySet; - -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct KeyGeneration(u32); +use crate::KeyGeneration; impl From for DateTime { fn from(kg: KeyGeneration) -> Self {