Adjusted/improved handling of public keys (especially to find the correct KDF parameters for ECC decryption keys):
- "Brute force" find the right KDF parameters in the new helper fn public_key_material_and_fp_to_key() [try possible parameters until a matching fingerprint is found, error if none]. - In `opgpcard pubkey`, use public_key_material_and_fp_to_key() to find the right parameters for the ECC decryption subkey (this subcommand now fails when the fingerprint on the card doesn't match the fingerprint of the public key data for that key slot) - When generating OpenPGP ECC decryption keys from public key material (including to compute fingerprints from the key material), use SHA256/AES128 as default parameters.
This commit is contained in:
parent
161ef5592b
commit
e6c40be8ad
6 changed files with 175 additions and 61 deletions
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use anyhow::Result;
|
||||
|
@ -10,13 +10,16 @@ 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::{CardBackend, Error, KeyType, OpenPgp, OpenPgpTransaction, StatusBytes};
|
||||
use openpgp_card_sequoia::card::Open;
|
||||
use openpgp_card_sequoia::util::{make_cert, public_key_material_to_key, public_to_fingerprint};
|
||||
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;
|
||||
|
@ -226,9 +229,11 @@ pub fn test_keygen(
|
|||
param: &[&str],
|
||||
) -> Result<TestOutput, TestError> {
|
||||
let mut pgp = OpenPgp::new(card);
|
||||
let mut pgpt = pgp.transaction()?;
|
||||
let pgpt = pgp.transaction()?;
|
||||
|
||||
pgpt.verify_pw3(b"12345678")?;
|
||||
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];
|
||||
|
@ -236,20 +241,24 @@ pub fn test_keygen(
|
|||
let alg = AlgoSimple::try_from(algo)?;
|
||||
|
||||
println!(" Generate subkey for Signing");
|
||||
let (pkm, ts) = pgpt.generate_key_simple(public_to_fingerprint, KeyType::Signing, alg)?;
|
||||
let key_sig = public_key_material_to_key(&pkm, KeyType::Signing, ts)?;
|
||||
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) = pgpt.generate_key_simple(public_to_fingerprint, KeyType::Decryption, alg)?;
|
||||
let key_dec = public_key_material_to_key(&pkm, KeyType::Decryption, ts)?;
|
||||
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) =
|
||||
pgpt.generate_key_simple(public_to_fingerprint, KeyType::Authentication, alg)?;
|
||||
let key_aut = public_key_material_to_key(&pkm, KeyType::Authentication, ts)?;
|
||||
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 mut open = Open::new(pgpt)?;
|
||||
let cert = make_cert(
|
||||
&mut open,
|
||||
key_sig,
|
||||
|
@ -274,37 +283,44 @@ pub fn test_get_pub(
|
|||
let mut pgpt = pgp.transaction()?;
|
||||
|
||||
let ard = pgpt.application_related_data()?;
|
||||
let key_gen = ard.key_generation_times()?;
|
||||
let times = ard.key_generation_times()?;
|
||||
let fps = ard.fingerprints()?;
|
||||
|
||||
// --
|
||||
|
||||
let sig = pgpt.public_key(KeyType::Signing)?;
|
||||
let ts = key_gen.signature().unwrap().get().into();
|
||||
let key = public_key_material_to_key(&sig, KeyType::Signing, ts)?;
|
||||
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 = key_gen.decryption().unwrap().get().into();
|
||||
let key = public_key_material_to_key(&dec, KeyType::Decryption, ts)?;
|
||||
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 = key_gen.authentication().unwrap().get().into();
|
||||
let key = public_key_material_to_key(&auth, KeyType::Authentication, ts)?;
|
||||
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);
|
||||
|
||||
// FIXME: assert that key FP is equal to FP from card
|
||||
|
||||
// ca.generate_key(fp, KeyType::Decryption)?;
|
||||
// ca.generate_key(fp, KeyType::Authentication)?;
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
name = "openpgp-card-sequoia"
|
||||
description = "Wrapper of openpgp-card for use with Sequoia PGP"
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.0.9"
|
||||
version = "0.0.10"
|
||||
authors = ["Heiko Schaefer <heiko@schaefer.name>"]
|
||||
edition = "2018"
|
||||
repository = "https://gitlab.com/hkos/openpgp-card"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
//! Perform operations on a card. Different states of a card are modeled by
|
||||
|
@ -6,6 +6,7 @@
|
|||
|
||||
use sequoia_openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation;
|
||||
use sequoia_openpgp::packet::key::SecretParts;
|
||||
use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm};
|
||||
use sequoia_openpgp::Cert;
|
||||
|
||||
use openpgp_card::algorithm::{Algo, AlgoInfo, AlgoSimple};
|
||||
|
@ -415,20 +416,32 @@ impl Admin<'_, '_> {
|
|||
self.oc.opt.key_import(key, key_type)
|
||||
}
|
||||
|
||||
/// Wrapper fn for `public_to_fingerprint` that uses SHA256/AES128 as default parameters.
|
||||
///
|
||||
/// FIXME: This is a hack.
|
||||
/// These parameters should probably be automatically determined based on the algorithm used?
|
||||
fn ptf(
|
||||
pkm: &PublicKeyMaterial,
|
||||
time: KeyGenerationTime,
|
||||
key_type: KeyType,
|
||||
) -> Result<Fingerprint, Error> {
|
||||
public_to_fingerprint(
|
||||
pkm,
|
||||
&time,
|
||||
key_type,
|
||||
Some(HashAlgorithm::SHA256),
|
||||
Some(SymmetricAlgorithm::AES128),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_key_simple(
|
||||
&mut self,
|
||||
key_type: KeyType,
|
||||
algo: Option<AlgoSimple>,
|
||||
) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> {
|
||||
match algo {
|
||||
Some(algo) => self
|
||||
.oc
|
||||
.opt
|
||||
.generate_key_simple(public_to_fingerprint, key_type, algo),
|
||||
None => self
|
||||
.oc
|
||||
.opt
|
||||
.generate_key(public_to_fingerprint, key_type, None),
|
||||
Some(algo) => self.oc.opt.generate_key_simple(Self::ptf, key_type, algo),
|
||||
None => self.oc.opt.generate_key(Self::ptf, key_type, None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
//! Odds and ends, will most likely be restructured.
|
||||
|
@ -24,6 +24,7 @@ use openpgp::serialize::stream::{Message, Signer};
|
|||
use openpgp::types::{KeyFlags, PublicKeyAlgorithm, SignatureType, Timestamp};
|
||||
use openpgp::{Cert, Packet};
|
||||
use sequoia_openpgp as openpgp;
|
||||
use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm};
|
||||
|
||||
use openpgp_card::algorithm::{Algo, Curve};
|
||||
use openpgp_card::card_do::{Fingerprint, KeyGenerationTime};
|
||||
|
@ -173,11 +174,77 @@ pub fn make_cert<'app>(
|
|||
Cert::try_from(pp)
|
||||
}
|
||||
|
||||
/// Helper fn: get a Sequoia PublicKey from an openpgp-card PublicKeyMaterial
|
||||
/// Meta-Helper fn: get a Sequoia PublicKey from an openpgp-card PublicKeyMaterial, timestamp and
|
||||
/// card-Fingerprint.
|
||||
///
|
||||
/// For ECC decryption keys, possible values for the parameters `hash` and `sym` will be tested.
|
||||
/// Once a key with matching fingerprint is found in this way, it is considered the correct key,
|
||||
/// and returned.
|
||||
///
|
||||
/// The Fingerprint of the retrieved PublicKey is always validated against the `Fingerprint` as
|
||||
/// stored on the card. If the fingerprints doesn't match, an Error is returned.
|
||||
pub fn public_key_material_and_fp_to_key(
|
||||
pkm: &PublicKeyMaterial,
|
||||
key_type: KeyType,
|
||||
time: &KeyGenerationTime,
|
||||
fingerprint: &Fingerprint,
|
||||
) -> Result<PublicKey, Error> {
|
||||
// Possible hash/sym parameters based on statistics over 2019-12 SKS dump:
|
||||
// https://gitlab.com/sequoia-pgp/sequoia/-/issues/838#note_909813463
|
||||
|
||||
// We try these parameters in descending order of occurrence and return the PublicKey
|
||||
// once the Fingerprint matches.
|
||||
|
||||
let param: &[_] = match (pkm, key_type) {
|
||||
(PublicKeyMaterial::E(_), KeyType::Decryption) => &[
|
||||
(
|
||||
Some(HashAlgorithm::SHA256),
|
||||
Some(SymmetricAlgorithm::AES128),
|
||||
),
|
||||
(
|
||||
Some(HashAlgorithm::SHA512),
|
||||
Some(SymmetricAlgorithm::AES256),
|
||||
),
|
||||
(
|
||||
Some(HashAlgorithm::SHA384),
|
||||
Some(SymmetricAlgorithm::AES256),
|
||||
),
|
||||
(
|
||||
Some(HashAlgorithm::SHA384),
|
||||
Some(SymmetricAlgorithm::AES192),
|
||||
),
|
||||
(
|
||||
Some(HashAlgorithm::SHA256),
|
||||
Some(SymmetricAlgorithm::AES256),
|
||||
),
|
||||
],
|
||||
_ => &[(None, None)],
|
||||
};
|
||||
|
||||
for (hash, sym) in param {
|
||||
if let Ok(key) = public_key_material_to_key(pkm, key_type, time, *hash, *sym) {
|
||||
// check FP
|
||||
if key.fingerprint().as_bytes() == fingerprint.as_bytes() {
|
||||
// return if match
|
||||
return Ok(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::InternalError(
|
||||
"Couldn't find key with matching fingerprint".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Helper fn: get a Sequoia PublicKey from an openpgp-card PublicKeyMaterial.
|
||||
///
|
||||
/// For ECC decryption keys, `hash` and `sym` can be optionally specified.
|
||||
pub fn public_key_material_to_key(
|
||||
pkm: &PublicKeyMaterial,
|
||||
key_type: KeyType,
|
||||
time: KeyGenerationTime,
|
||||
time: &KeyGenerationTime,
|
||||
hash: Option<HashAlgorithm>,
|
||||
sym: Option<SymmetricAlgorithm>,
|
||||
) -> Result<PublicKey, Error> {
|
||||
let time = Timestamp::from(time.get()).into();
|
||||
|
||||
|
@ -236,11 +303,8 @@ pub fn public_key_material_to_key(
|
|||
}
|
||||
KeyType::Decryption => {
|
||||
if algo_ecc.curve() == Curve::Cv25519 {
|
||||
// FIXME: not setting `hash` and `sym` is not
|
||||
// ok when a cert already exists
|
||||
|
||||
// EdDSA
|
||||
let k4 = Key4::import_public_cv25519(ecc.data(), None, None, time)
|
||||
let k4 = Key4::import_public_cv25519(ecc.data(), hash, sym, time)
|
||||
.map_err(|e| {
|
||||
Error::InternalError(format!(
|
||||
"sequoia Key4::import_public_cv25519 failed: {:?}",
|
||||
|
@ -250,9 +314,6 @@ pub fn public_key_material_to_key(
|
|||
|
||||
Ok(k4.into())
|
||||
} else {
|
||||
// FIXME: just defining `hash` and `sym` is not
|
||||
// ok when a cert already exists
|
||||
|
||||
// ECDH
|
||||
let k4 = Key4::new(
|
||||
time,
|
||||
|
@ -260,8 +321,8 @@ pub fn public_key_material_to_key(
|
|||
mpi::PublicKey::ECDH {
|
||||
curve,
|
||||
q: mpi::MPI::new(ecc.data()),
|
||||
hash: Default::default(),
|
||||
sym: Default::default(),
|
||||
hash: hash.unwrap_or_default(),
|
||||
sym: sym.unwrap_or_default(),
|
||||
},
|
||||
)
|
||||
.map_err(|e| {
|
||||
|
@ -286,13 +347,17 @@ pub fn public_key_material_to_key(
|
|||
|
||||
/// Mapping function to get a fingerprint from "PublicKeyMaterial +
|
||||
/// timestamp + KeyType" (intended for use with `CardApp.generate_key()`).
|
||||
pub fn public_to_fingerprint(
|
||||
///
|
||||
/// For ECC decryption keys, `hash` and `sym` can be optionally specified.
|
||||
pub(crate) fn public_to_fingerprint(
|
||||
pkm: &PublicKeyMaterial,
|
||||
time: KeyGenerationTime,
|
||||
time: &KeyGenerationTime,
|
||||
kt: KeyType,
|
||||
hash: Option<HashAlgorithm>,
|
||||
sym: Option<SymmetricAlgorithm>,
|
||||
) -> Result<Fingerprint, Error> {
|
||||
// Transform PublicKeyMaterial into a Sequoia Key
|
||||
let key = public_key_material_to_key(pkm, kt, time)?;
|
||||
let key = public_key_material_to_key(pkm, kt, time, hash, sym)?;
|
||||
|
||||
// Get fingerprint from the Sequoia Key
|
||||
let fp = key.fingerprint();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
name = "openpgp-card-tools"
|
||||
description = "CLI tools for OpenPGP cards"
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.0.6"
|
||||
version = "0.0.7"
|
||||
authors = ["Heiko Schaefer <heiko@schaefer.name>"]
|
||||
edition = "2018"
|
||||
repository = "https://gitlab.com/hkos/openpgp-card"
|
||||
|
@ -15,7 +15,7 @@ documentation = "https://docs.rs/crate/openpgp-card-tools"
|
|||
sequoia-openpgp = "1.3"
|
||||
openpgp-card = { path = "../openpgp-card", version = "0.2" }
|
||||
openpgp-card-pcsc = { path = "../pcsc", version = "0.2" }
|
||||
openpgp-card-sequoia = { path = "../openpgp-card-sequoia", version = "0.0.9" }
|
||||
openpgp-card-sequoia = { path = "../openpgp-card-sequoia", version = "0.0.10" }
|
||||
sshkeys = "0.3.2"
|
||||
rpassword = "5"
|
||||
anyhow = "1"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
|
@ -15,9 +15,12 @@ use openpgp_card::algorithm::AlgoSimple;
|
|||
use openpgp_card::card_do::Sex;
|
||||
use openpgp_card::{CardBackend, KeyType, OpenPgp};
|
||||
use openpgp_card_sequoia::card::{Admin, Open};
|
||||
use openpgp_card_sequoia::util::{make_cert, public_key_material_to_key};
|
||||
use openpgp_card_sequoia::util::{
|
||||
make_cert, public_key_material_and_fp_to_key, public_key_material_to_key,
|
||||
};
|
||||
use openpgp_card_sequoia::{sq_util, PublicKey};
|
||||
|
||||
use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm};
|
||||
use std::io::Write;
|
||||
|
||||
mod cli;
|
||||
|
@ -403,27 +406,36 @@ fn print_pubkey(ident: Option<String>, user_pin: Option<PathBuf>) -> Result<()>
|
|||
|
||||
let pkm = open.public_key(KeyType::Signing)?;
|
||||
let times = open.key_generation_times()?;
|
||||
let fps = open.fingerprints()?;
|
||||
|
||||
let key_sig = public_key_material_to_key(
|
||||
let key_sig = public_key_material_and_fp_to_key(
|
||||
&pkm,
|
||||
KeyType::Signing,
|
||||
*times.signature().expect("Signature time is unset"),
|
||||
times.signature().expect("Signature time is unset"),
|
||||
fps.signature().expect("Signature fingerprint is unset"),
|
||||
)?;
|
||||
|
||||
let mut key_dec = None;
|
||||
if let Ok(pkm) = open.public_key(KeyType::Decryption) {
|
||||
if let Some(ts) = times.decryption() {
|
||||
key_dec = Some(public_key_material_to_key(&pkm, KeyType::Decryption, *ts)?);
|
||||
key_dec = Some(public_key_material_and_fp_to_key(
|
||||
&pkm,
|
||||
KeyType::Decryption,
|
||||
ts,
|
||||
fps.decryption().expect("Decryption fingerprint is unset"),
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
||||
let mut key_aut = None;
|
||||
if let Ok(pkm) = open.public_key(KeyType::Authentication) {
|
||||
if let Some(ts) = times.authentication() {
|
||||
key_aut = Some(public_key_material_to_key(
|
||||
key_aut = Some(public_key_material_and_fp_to_key(
|
||||
&pkm,
|
||||
KeyType::Authentication,
|
||||
*ts,
|
||||
ts,
|
||||
fps.authentication()
|
||||
.expect("Authentication fingerprint is unset"),
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
@ -663,14 +675,20 @@ fn gen_subkeys(
|
|||
// We begin by generating the signing subkey, which is mandatory.
|
||||
println!(" Generate subkey for Signing");
|
||||
let (pkm, ts) = admin.generate_key_simple(KeyType::Signing, algo)?;
|
||||
let key_sig = public_key_material_to_key(&pkm, KeyType::Signing, ts)?;
|
||||
let key_sig = public_key_material_to_key(&pkm, KeyType::Signing, &ts, None, None)?;
|
||||
|
||||
// make decryption subkey (unless disabled), with the same algorithm as
|
||||
// the sig key
|
||||
let key_dec = if decrypt {
|
||||
println!(" Generate subkey for Decryption");
|
||||
let (pkm, ts) = admin.generate_key_simple(KeyType::Decryption, algo)?;
|
||||
Some(public_key_material_to_key(&pkm, KeyType::Decryption, ts)?)
|
||||
Some(public_key_material_to_key(
|
||||
&pkm,
|
||||
KeyType::Decryption,
|
||||
&ts,
|
||||
Some(HashAlgorithm::SHA256), // FIXME
|
||||
Some(SymmetricAlgorithm::AES128), // FIXME
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -684,7 +702,9 @@ fn gen_subkeys(
|
|||
Some(public_key_material_to_key(
|
||||
&pkm,
|
||||
KeyType::Authentication,
|
||||
ts,
|
||||
&ts,
|
||||
None,
|
||||
None,
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
|
|
Loading…
Reference in a new issue