WIP: Handling of public key material from cards
This commit is contained in:
parent
3edadb8607
commit
44d5abd7ed
4 changed files with 146 additions and 69 deletions
|
@ -4,31 +4,32 @@
|
|||
//! This library supports using openpgp-card functionality with
|
||||
//! sequoia_openpgp data structures.
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use openpgp::armor;
|
||||
use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation;
|
||||
use openpgp::crypto::mpi;
|
||||
use openpgp::crypto::mpi::{ProtectedMPI, MPI};
|
||||
use openpgp::packet::key::{Key4, PublicParts};
|
||||
use openpgp::packet::key::{SecretParts, UnspecifiedRole};
|
||||
use openpgp::packet::{key, Key};
|
||||
use openpgp::parse::{stream::DecryptorBuilder, Parse};
|
||||
use openpgp::policy::StandardPolicy;
|
||||
use openpgp::serialize::stream::{Message, Signer};
|
||||
use openpgp::types::Timestamp;
|
||||
use sequoia_openpgp as openpgp;
|
||||
|
||||
use openpgp_card::card_app::CardApp;
|
||||
use openpgp_card::{
|
||||
errors::OpenpgpCardError, CardAdmin, CardUploadableKey, EccKey, EccType,
|
||||
KeyType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey,
|
||||
errors::OpenpgpCardError, Algo, CardAdmin, CardUploadableKey, Curve,
|
||||
EccKey, EccType, KeyType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey,
|
||||
};
|
||||
use sequoia_openpgp::packet::key::{Key4, PublicParts};
|
||||
use sequoia_openpgp::types::Timestamp;
|
||||
use sequoia_openpgp::types::PublicKeyAlgorithm;
|
||||
|
||||
mod decryptor;
|
||||
mod signer;
|
||||
|
@ -74,6 +75,7 @@ pub fn vka_as_uploadable_key(
|
|||
/// Helper fn: get a Key<PublicParts, UnspecifiedRole> for a PublicKeyMaterial
|
||||
pub fn public_key_material_to_key(
|
||||
pkm: &PublicKeyMaterial,
|
||||
key_type: KeyType,
|
||||
time: SystemTime,
|
||||
) -> Result<Key<PublicParts, UnspecifiedRole>> {
|
||||
match pkm {
|
||||
|
@ -83,7 +85,73 @@ pub fn public_key_material_to_key(
|
|||
|
||||
Ok(Key::from(k4))
|
||||
}
|
||||
_ => unimplemented!("ECC not implemented yet"),
|
||||
PublicKeyMaterial::E(ecc) => {
|
||||
let algo = ecc.algo.clone(); // FIXME?
|
||||
if let Algo::Ecc(algo_ecc) = algo {
|
||||
match key_type {
|
||||
KeyType::Authentication | KeyType::Signing => {
|
||||
if algo_ecc.curve == Curve::Ed25519 {
|
||||
// EdDSA
|
||||
let k4: Key4<
|
||||
key::PublicParts,
|
||||
key::UnspecifiedRole,
|
||||
> = Key4::import_public_ed25519(&ecc.data, time)?;
|
||||
|
||||
println!("k4 {:?}", k4);
|
||||
|
||||
Ok(Key::from(k4))
|
||||
} else {
|
||||
// ECDSA
|
||||
|
||||
// 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.
|
||||
|
||||
let ec = &ecc.data;
|
||||
|
||||
assert_eq!(ec[0], 0x4);
|
||||
|
||||
let len = ec.len();
|
||||
assert_eq!(len % 2, 1); // odd number of bytes
|
||||
|
||||
// ---
|
||||
|
||||
let curve = match algo_ecc.curve {
|
||||
Curve::NistP256r1 => {
|
||||
openpgp::types::Curve::NistP256
|
||||
}
|
||||
Curve::NistP384r1 => {
|
||||
openpgp::types::Curve::NistP384
|
||||
}
|
||||
Curve::NistP521r1 => {
|
||||
openpgp::types::Curve::NistP521
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
// NIST
|
||||
let k4 = Key4::new(
|
||||
time,
|
||||
PublicKeyAlgorithm::ECDSA,
|
||||
mpi::PublicKey::ECDSA {
|
||||
curve,
|
||||
q: mpi::MPI::new(&ec),
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(Key::from(k4))
|
||||
}
|
||||
}
|
||||
KeyType::Decryption => {
|
||||
unimplemented!("Decryption keys not implemented yet")
|
||||
}
|
||||
_ => unimplemented!("Unsupported KeyType"),
|
||||
}
|
||||
} else {
|
||||
panic!("unexpected algo {:?}", algo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,16 +18,15 @@ use anyhow::{anyhow, Result};
|
|||
use crate::apdu::{commands, response::Response};
|
||||
use crate::errors::OpenpgpCardError;
|
||||
use crate::parse::{
|
||||
algo_attrs::Algo, algo_attrs::RsaAttrs, algo_info::AlgoInfo,
|
||||
application_id::ApplicationId, cardholder::CardHolder,
|
||||
extended_cap::ExtendedCap, extended_length_info::ExtendedLengthInfo,
|
||||
fingerprint, historical::Historical, key_generation_times,
|
||||
pw_status::PWStatus, KeySet,
|
||||
algo_info::AlgoInfo, application_id::ApplicationId,
|
||||
cardholder::CardHolder, extended_cap::ExtendedCap,
|
||||
extended_length_info::ExtendedLengthInfo, fingerprint,
|
||||
historical::Historical, key_generation_times, pw_status::PWStatus, KeySet,
|
||||
};
|
||||
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
|
||||
use crate::{
|
||||
apdu, keys, CardCaps, CardClientBox, CardUploadableKey, DecryptMe,
|
||||
EccType, Hash, KeyGeneration, KeyType, PublicKeyMaterial, Sex,
|
||||
apdu, keys, Algo, CardCaps, CardClientBox, CardUploadableKey, DecryptMe,
|
||||
EccType, Hash, KeyGeneration, KeyType, PublicKeyMaterial, RsaAttrs, Sex,
|
||||
};
|
||||
|
||||
pub struct CardApp {
|
||||
|
@ -573,7 +572,7 @@ impl CardApp {
|
|||
|
||||
let data = match algo {
|
||||
Algo::Rsa(rsa) => Self::rsa_algo_attrs(rsa)?,
|
||||
Algo::Ecc(ecc) => Self::ecc_algo_attrs(&ecc.oid, ecc.ecc_type),
|
||||
Algo::Ecc(ecc) => Self::ecc_algo_attrs(ecc.oid(), ecc.ecc_type),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
|
@ -635,14 +634,22 @@ impl CardApp {
|
|||
keys::upload_key(self, key, key_type, algo_list)
|
||||
}
|
||||
|
||||
// FIXME: use subset of CardUploadableKey to specify algo?
|
||||
/// Generate a key on the card.
|
||||
/// If the `algo` parameter is Some, then this algorithm will be set on
|
||||
/// the card for "key_type".
|
||||
pub fn generate_key(
|
||||
&mut self,
|
||||
fp_from_pub: fn(&PublicKeyMaterial, SystemTime) -> Result<[u8; 20]>,
|
||||
fp_from_pub: fn(
|
||||
&PublicKeyMaterial,
|
||||
SystemTime,
|
||||
KeyType,
|
||||
&Algo,
|
||||
) -> Result<[u8; 20]>,
|
||||
key_type: KeyType,
|
||||
) -> Result<(), OpenpgpCardError> {
|
||||
algo: Option<&Algo>,
|
||||
) -> Result<(PublicKeyMaterial, u32), OpenpgpCardError> {
|
||||
// FIXME: specify algo; pass in algo list?
|
||||
keys::gen_key_with_metadata(self, fp_from_pub, key_type)
|
||||
keys::gen_key_with_metadata(self, fp_from_pub, key_type, algo)
|
||||
}
|
||||
|
||||
pub fn get_pub_key(
|
||||
|
|
|
@ -10,22 +10,47 @@ use crate::apdu::command::Command;
|
|||
use crate::apdu::commands;
|
||||
use crate::card_app::CardApp;
|
||||
use crate::errors::OpenpgpCardError;
|
||||
use crate::parse::algo_attrs::{Algo, EccAttrs, RsaAttrs};
|
||||
use crate::parse::algo_info::AlgoInfo;
|
||||
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
|
||||
use crate::{apdu, EccPub, PublicKeyMaterial, RSAPub};
|
||||
use crate::{apdu, Curve, EccPub, PublicKeyMaterial, RSAPub};
|
||||
use crate::{
|
||||
tlv, CardUploadableKey, EccKey, KeyType, PrivateKeyMaterial, RSAKey,
|
||||
tlv, Algo, CardUploadableKey, EccAttrs, EccKey, KeyType,
|
||||
PrivateKeyMaterial, RSAKey, RsaAttrs,
|
||||
};
|
||||
|
||||
/// `gen_key_with_metadata` 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]>,
|
||||
fp_from_pub: fn(
|
||||
&PublicKeyMaterial,
|
||||
SystemTime,
|
||||
KeyType,
|
||||
&Algo,
|
||||
) -> Result<[u8; 20]>,
|
||||
key_type: KeyType,
|
||||
) -> Result<(), OpenpgpCardError> {
|
||||
let pubkey = gen_key(card_app, key_type)?;
|
||||
algo: Option<&Algo>,
|
||||
) -> Result<(PublicKeyMaterial, u32), OpenpgpCardError> {
|
||||
// set algo on card if it's Some
|
||||
if let Some(algo) = algo {
|
||||
println!("set algo {:?}", algo);
|
||||
card_app
|
||||
.set_algorithm_attributes(key_type, algo)?
|
||||
.check_ok()?;
|
||||
println!("set algo done");
|
||||
}
|
||||
|
||||
// algo
|
||||
let ard = card_app.get_app_data()?; // no caching, here!
|
||||
let algo = CardApp::get_algorithm_attributes(&ard, key_type)?;
|
||||
|
||||
// generate key
|
||||
let tlv = gen_key(card_app, key_type)?;
|
||||
|
||||
// derive pubkey
|
||||
let pubkey = tlv_to_pubkey(&tlv, &algo)?;
|
||||
|
||||
log::trace!("public {:x?}", pubkey);
|
||||
|
||||
// set creation time
|
||||
let time = SystemTime::now();
|
||||
|
@ -39,13 +64,13 @@ pub(crate) fn gen_key_with_metadata(
|
|||
card_app.set_creation_time(ts, key_type)?.check_ok()?;
|
||||
|
||||
// calculate/store fingerprint
|
||||
let fp = fp_from_pub(&pubkey, time)?;
|
||||
let fp = fp_from_pub(&pubkey, time, key_type, &algo)?;
|
||||
card_app.set_fingerprint(fp, key_type)?.check_ok()?;
|
||||
|
||||
Ok(())
|
||||
Ok((pubkey, ts))
|
||||
}
|
||||
|
||||
fn tlv_to_pubkey(tlv: &Tlv) -> Result<PublicKeyMaterial> {
|
||||
fn tlv_to_pubkey(tlv: &Tlv, algo: &Algo) -> Result<PublicKeyMaterial> {
|
||||
let n = tlv.find(&Tag::new(vec![0x81]));
|
||||
let v = tlv.find(&Tag::new(vec![0x82]));
|
||||
|
||||
|
@ -61,26 +86,13 @@ fn tlv_to_pubkey(tlv: &Tlv) -> Result<PublicKeyMaterial> {
|
|||
Ok(PublicKeyMaterial::R(rsa))
|
||||
}
|
||||
(None, None, Some(ec)) => {
|
||||
let ec = ec.serialize();
|
||||
let data = ec.serialize();
|
||||
println!("EC --- len {}, data {:x?}", data.len(), data);
|
||||
|
||||
// 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))
|
||||
Ok(PublicKeyMaterial::E(EccPub {
|
||||
data,
|
||||
algo: algo.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
(_, _, _) => {
|
||||
|
@ -92,7 +104,7 @@ fn tlv_to_pubkey(tlv: &Tlv) -> Result<PublicKeyMaterial> {
|
|||
pub(crate) fn gen_key(
|
||||
card_app: &mut CardApp,
|
||||
key_type: KeyType,
|
||||
) -> Result<PublicKeyMaterial, OpenpgpCardError> {
|
||||
) -> Result<Tlv, OpenpgpCardError> {
|
||||
println!("gen key for {:?}", key_type);
|
||||
|
||||
// generate key
|
||||
|
@ -106,11 +118,7 @@ pub(crate) fn gen_key(
|
|||
|
||||
let tlv = Tlv::try_from(resp.data()?)?;
|
||||
|
||||
let pubkey = tlv_to_pubkey(&tlv)?;
|
||||
|
||||
log::trace!("public {:x?}", pubkey);
|
||||
|
||||
Ok(pubkey)
|
||||
Ok(tlv)
|
||||
}
|
||||
|
||||
pub(crate) fn get_pub_key(
|
||||
|
@ -119,17 +127,19 @@ pub(crate) fn get_pub_key(
|
|||
) -> Result<PublicKeyMaterial, OpenpgpCardError> {
|
||||
println!("get pub key for {:?}", key_type);
|
||||
|
||||
let card_client = card_app.card();
|
||||
// algo
|
||||
let ard = card_app.get_app_data()?; // FIXME: caching
|
||||
let algo = CardApp::get_algorithm_attributes(&ard, key_type)?;
|
||||
|
||||
// get public key
|
||||
let crt = get_crt(key_type)?;
|
||||
let get_pub_key_cmd = commands::get_pub_key(crt.serialize().to_vec());
|
||||
|
||||
let resp = apdu::send_command(card_client, get_pub_key_cmd, true)?;
|
||||
let resp = apdu::send_command(card_app.card(), get_pub_key_cmd, true)?;
|
||||
resp.check_ok()?;
|
||||
|
||||
let tlv = Tlv::try_from(resp.data()?)?;
|
||||
let pubkey = tlv_to_pubkey(&tlv)?;
|
||||
let pubkey = tlv_to_pubkey(&tlv, &algo)?;
|
||||
|
||||
Ok(pubkey)
|
||||
}
|
||||
|
@ -192,7 +202,7 @@ pub(crate) fn upload_key(
|
|||
|
||||
let algo = Algo::Ecc(EccAttrs {
|
||||
ecc_type: ecc_key.get_type(),
|
||||
oid: ecc_key.get_oid().to_vec(),
|
||||
curve: Curve::from(ecc_key.get_oid()).expect("unepected oid"),
|
||||
import_format: None,
|
||||
});
|
||||
|
||||
|
@ -264,7 +274,7 @@ fn check_card_algo_ecdh(
|
|||
.collect();
|
||||
|
||||
// Check if this OID exists in the supported algorithms
|
||||
ecdh_algos.iter().any(|e| e.oid == oid)
|
||||
ecdh_algos.iter().any(|e| e.oid() == oid)
|
||||
}
|
||||
|
||||
// FIXME: refactor, these checks currently pointlessly duplicate code
|
||||
|
@ -288,7 +298,7 @@ fn check_card_algo_ecdsa(
|
|||
.collect();
|
||||
|
||||
// Check if this OID exists in the supported algorithms
|
||||
ecdsa_algos.iter().any(|e| e.oid == oid)
|
||||
ecdsa_algos.iter().any(|e| e.oid() == oid)
|
||||
}
|
||||
|
||||
// FIXME: refactor, these checks currently pointlessly duplicate code
|
||||
|
@ -312,7 +322,7 @@ fn check_card_algo_eddsa(
|
|||
.collect();
|
||||
|
||||
// Check if this OID exists in the supported algorithms
|
||||
eddsa_algos.iter().any(|e| e.oid == oid)
|
||||
eddsa_algos.iter().any(|e| e.oid() == oid)
|
||||
}
|
||||
|
||||
fn ecc_key_cmd(
|
||||
|
|
|
@ -265,14 +265,6 @@ pub trait CardUploadableKey {
|
|||
pub enum PublicKeyMaterial {
|
||||
R(RSAPub),
|
||||
E(EccPub),
|
||||
T(Tffon), // 25519
|
||||
}
|
||||
|
||||
/// ed25519/cv25519
|
||||
#[derive(Debug)]
|
||||
pub struct Tffon {
|
||||
/// Public key
|
||||
pub pk: Vec<u8>,
|
||||
}
|
||||
|
||||
/// RSA-specific container for public key material from an OpenPGP card.
|
||||
|
@ -288,8 +280,8 @@ pub struct RSAPub {
|
|||
/// ECC-specific container for public key material from an OpenPGP card.
|
||||
#[derive(Debug)]
|
||||
pub struct EccPub {
|
||||
pub x: Vec<u8>,
|
||||
pub y: Vec<u8>,
|
||||
pub data: Vec<u8>,
|
||||
pub algo: Algo,
|
||||
}
|
||||
|
||||
/// Algorithm-independent container for private key material to upload to
|
||||
|
|
Loading…
Reference in a new issue