- 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
This commit is contained in:
Heiko Schaefer 2021-08-06 20:14:02 +02:00
parent 538bfb51d4
commit 7acc1deb98
7 changed files with 248 additions and 40 deletions

View file

@ -26,6 +26,8 @@
//! the command data field"). //! the command data field").
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use std::convert::TryInto;
use std::time::SystemTime;
use thiserror::Error; use thiserror::Error;
use sequoia_openpgp::parse::Parse; use sequoia_openpgp::parse::Parse;
@ -33,7 +35,7 @@ use sequoia_openpgp::Cert;
use openpgp_card::card_app::CardApp; use openpgp_card::card_app::CardApp;
use openpgp_card::errors::{OcErrorStatus, OpenpgpCardError}; use openpgp_card::errors::{OcErrorStatus, OpenpgpCardError};
use openpgp_card::Sex; use openpgp_card::{KeyType, PublicKeyMaterial, Sex};
use crate::cards::{TestCard, TestConfig}; use crate::cards::{TestCard, TestConfig};
@ -203,10 +205,32 @@ fn test_upload_keys(
Ok(vec![]) Ok(vec![])
} }
fn test_keygen() { /// Generate keys for each of the three KeyTypes
// FIXME fn test_keygen(
// (implementation of this functionality is still missing in openpgp-card) ca: &mut CardApp,
unimplemented!() _param: &[&str],
) -> Result<TestOutput, TestError> {
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( fn test_reset(
@ -342,6 +366,11 @@ fn main() -> Result<()> {
println!("Reset"); println!("Reset");
let _ = run_test(&mut card, test_reset, &[])?; let _ = run_test(&mut card, test_reset, &[])?;
println!("Generate key");
let _ = run_test(&mut card, test_keygen, &[])?;
panic!();
print!("Verify"); print!("Verify");
let verify_out = run_test(&mut card, test_verify, &[])?; let verify_out = run_test(&mut card, test_verify, &[])?;
println!(" {:x?}", verify_out); println!(" {:x?}", verify_out);
@ -385,8 +414,6 @@ fn main() -> Result<()> {
println!(" {:x?}", sign_out); println!(" {:x?}", sign_out);
} }
// FIXME: generate keys
// FIXME: upload key with password // FIXME: upload key with password
println!(); println!();

View file

@ -4,11 +4,12 @@
//! This library supports using openpgp-card functionality with //! This library supports using openpgp-card functionality with
//! sequoia_openpgp data structures. //! sequoia_openpgp data structures.
use std::convert::TryFrom;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::time::SystemTime;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use chrono::prelude::*;
use openpgp::armor; use openpgp::armor;
use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation; use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation;
use openpgp::crypto::mpi; use openpgp::crypto::mpi;
@ -23,8 +24,10 @@ use sequoia_openpgp as openpgp;
use openpgp_card::card_app::CardApp; use openpgp_card::card_app::CardApp;
use openpgp_card::{ use openpgp_card::{
errors::OpenpgpCardError, CardAdmin, CardUploadableKey, EccKey, EccType, 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 decryptor;
mod signer; mod signer;
@ -67,6 +70,22 @@ pub fn vka_as_uploadable_key(
Box::new(sqk) Box::new(sqk)
} }
/// Helper fn: get a Key<PublicParts, UnspecifiedRole> for a PublicKeyMaterial
pub fn public_key_material_to_key(
pkm: &PublicKeyMaterial,
time: SystemTime,
) -> Result<Key<PublicParts, UnspecifiedRole>> {
match pkm {
PublicKeyMaterial::R(rsa) => {
let k4: Key4<key::PublicParts, key::UnspecifiedRole> =
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 /// Implement the `CardUploadableKey` trait that openpgp-card uses to
/// upload (sub)keys to a card. /// upload (sub)keys to a card.
impl CardUploadableKey for SequoiaKey { impl CardUploadableKey for SequoiaKey {
@ -144,9 +163,12 @@ impl CardUploadableKey for SequoiaKey {
} }
} }
fn get_ts(&self) -> u64 { /// Number of non-leap seconds since January 1, 1970 0:00:00 UTC
let key_creation: DateTime<Utc> = self.key.creation_time().into(); /// (aka "UNIX timestamp")
key_creation.timestamp() as u64 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<u8> { fn get_fp(&self) -> Vec<u8> {

View file

@ -126,3 +126,8 @@ pub fn decryption(data: Vec<u8>) -> Command {
pub fn signature(data: Vec<u8>) -> Command { pub fn signature(data: Vec<u8>) -> Command {
Command::new(0x00, 0x2A, 0x9e, 0x9a, data) Command::new(0x00, 0x2A, 0x9e, 0x9a, data)
} }
/// Creates new APDU for "GENERATE ASYMMETRIC KEY PAIR"
pub fn gen_key(data: Vec<u8>) -> Command {
Command::new(0x00, 0x47, 0x80, 0x00, data)
}

View file

@ -11,6 +11,7 @@
use std::borrow::BorrowMut; use std::borrow::BorrowMut;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::time::SystemTime;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
@ -20,13 +21,12 @@ use crate::parse::{
algo_attrs::Algo, algo_info::AlgoInfo, application_id::ApplicationId, algo_attrs::Algo, algo_info::AlgoInfo, application_id::ApplicationId,
cardholder::CardHolder, extended_cap::ExtendedCap, cardholder::CardHolder, extended_cap::ExtendedCap,
extended_length_info::ExtendedLengthInfo, fingerprint, extended_length_info::ExtendedLengthInfo, fingerprint,
historical::Historical, key_generation_times, historical::Historical, key_generation_times, pw_status::PWStatus, KeySet,
key_generation_times::KeyGeneration, pw_status::PWStatus, KeySet,
}; };
use crate::tlv::{tag::Tag, Tlv, TlvEntry}; use crate::tlv::{tag::Tag, Tlv, TlvEntry};
use crate::{ use crate::{
apdu, key_upload, CardCaps, CardClientBox, CardUploadableKey, DecryptMe, apdu, keys, CardCaps, CardClientBox, CardUploadableKey, DecryptMe, Hash,
Hash, KeyType, Sex, KeyGeneration, KeyType, PublicKeyMaterial, Sex,
}; };
pub struct CardApp { pub struct CardApp {
@ -524,6 +524,27 @@ impl CardApp {
apdu::send_command(&mut self.card_client, put_url, false) apdu::send_command(&mut self.card_client, put_url, false)
} }
pub fn set_creation_time(
&mut self,
time: u32,
key_type: KeyType,
) -> Result<Response, OpenpgpCardError> {
// Timestamp update
let time_value: Vec<u8> = 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( pub fn upload_key(
&mut self, &mut self,
key: Box<dyn CardUploadableKey>, key: Box<dyn CardUploadableKey>,
@ -539,6 +560,16 @@ impl CardApp {
None 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)
} }
} }

View file

@ -1,7 +1,10 @@
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name> // SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
//! Generate and import keys
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::apdu::command::Command; use crate::apdu::command::Command;
use crate::apdu::commands; use crate::apdu::commands;
@ -10,12 +13,109 @@ use crate::errors::OpenpgpCardError;
use crate::parse::algo_attrs::{Algo, RsaAttrs}; use crate::parse::algo_attrs::{Algo, RsaAttrs};
use crate::parse::algo_info::AlgoInfo; use crate::parse::algo_info::AlgoInfo;
use crate::tlv::{tag::Tag, Tlv, TlvEntry}; use crate::tlv::{tag::Tag, Tlv, TlvEntry};
use crate::{apdu, CardClientBox}; use crate::{apdu, EccPub, PublicKeyMaterial, RSAPub};
use crate::{ use crate::{
tlv, CardUploadableKey, EccKey, EccType, KeyType, PrivateKeyMaterial, tlv, CardUploadableKey, EccKey, EccType, KeyType, PrivateKeyMaterial,
RSAKey, 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<PublicKeyMaterial> {
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<PublicKeyMaterial, OpenpgpCardError> {
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. /// 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`. /// 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( copy_key_to_card(
card_app.card(), card_app,
key_type, key_type,
key.get_ts(), key.get_ts(),
key.get_fp(), key.get_fp(),
@ -385,28 +485,17 @@ fn ecc_algo_attrs_cmd(
} }
fn copy_key_to_card( fn copy_key_to_card(
card_client: &mut CardClientBox, card_app: &mut CardApp,
key_type: KeyType, key_type: KeyType,
ts: u64, ts: u32,
fp: Vec<u8>, fp: Vec<u8>,
algo_cmd: Command, algo_cmd: Command,
key_cmd: Command, key_cmd: Command,
) -> Result<(), OpenpgpCardError> { ) -> Result<(), OpenpgpCardError> {
let fp_cmd = commands::put_data(&[key_type.get_fingerprint_put_tag()], fp); let fp_cmd = commands::put_data(&[key_type.get_fingerprint_put_tag()], fp);
// Timestamp update
let time_value: Vec<u8> = 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 // Send all the commands
let card_client = card_app.card();
// FIXME: Only write algo attributes to the card if "extended // FIXME: Only write algo attributes to the card if "extended
// capabilities" show that they are changeable! // 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, key_cmd, false)?.check_ok()?;
apdu::send_command(card_client, fp_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(()) Ok(())
} }

View file

@ -21,7 +21,7 @@ pub mod apdu;
mod card; mod card;
pub mod card_app; pub mod card_app;
pub mod errors; pub mod errors;
mod key_upload; mod keys;
mod parse; mod parse;
mod tlv; 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. /// Container for a hash value.
/// These hash values can be signed by the card. /// These hash values can be signed by the card.
pub enum Hash<'a> { pub enum Hash<'a> {
@ -111,12 +121,37 @@ pub trait CardUploadableKey {
fn get_key(&self) -> Result<PrivateKeyMaterial>; fn get_key(&self) -> Result<PrivateKeyMaterial>;
/// timestamp of (sub)key creation /// timestamp of (sub)key creation
fn get_ts(&self) -> u64; fn get_ts(&self) -> u32;
/// fingerprint /// fingerprint
fn get_fp(&self) -> Vec<u8>; fn get_fp(&self) -> Vec<u8>;
} }
/// 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<u8>,
/// Public exponent (a number denoted as v, e.g. 65537 dec.)
pub v: Vec<u8>,
}
/// ECC-specific container for public key material from an OpenPGP card.
#[derive(Debug)]
pub struct EccPub {
pub x: Vec<u8>,
pub y: Vec<u8>,
}
/// Algorithm-independent container for private key material to upload to /// Algorithm-independent container for private key material to upload to
/// an OpenPGP card /// an OpenPGP card
pub enum PrivateKeyMaterial { pub enum PrivateKeyMaterial {
@ -678,7 +713,7 @@ impl CardAdmin {
) -> Result<(), OpenpgpCardError> { ) -> Result<(), OpenpgpCardError> {
let algo_list = self.list_supported_algo()?; 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)
} }
} }

View file

@ -7,9 +7,7 @@ use nom::{combinator, number::complete as number, sequence};
use crate::errors::OpenpgpCardError; use crate::errors::OpenpgpCardError;
use crate::parse::KeySet; use crate::parse::KeySet;
use crate::KeyGeneration;
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct KeyGeneration(u32);
impl From<KeyGeneration> for DateTime<Utc> { impl From<KeyGeneration> for DateTime<Utc> {
fn from(kg: KeyGeneration) -> Self { fn from(kg: KeyGeneration) -> Self {