Re-ordering the -sequoia crate, as a first step towards defining its API.
This commit is contained in:
parent
e261d4d041
commit
55e7a2c794
10 changed files with 1013 additions and 924 deletions
|
@ -14,7 +14,7 @@ use openpgp_card;
|
||||||
use openpgp_card::algorithm::AlgoSimple;
|
use openpgp_card::algorithm::AlgoSimple;
|
||||||
use openpgp_card::card_do::{KeyGenerationTime, Sex};
|
use openpgp_card::card_do::{KeyGenerationTime, Sex};
|
||||||
use openpgp_card::{CardApp, Error, KeyType, StatusBytes};
|
use openpgp_card::{CardApp, Error, KeyType, StatusBytes};
|
||||||
use openpgp_card_sequoia::{
|
use openpgp_card_sequoia::util::{
|
||||||
make_cert, public_key_material_to_key, public_to_fingerprint,
|
make_cert, public_key_material_to_key, public_to_fingerprint,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,7 +64,8 @@ pub fn test_decrypt(
|
||||||
|
|
||||||
ca.verify_pw1("123456")?;
|
ca.verify_pw1("123456")?;
|
||||||
|
|
||||||
let res = openpgp_card_sequoia::decrypt(&mut ca, &cert, msg.into_bytes())?;
|
let res =
|
||||||
|
openpgp_card_sequoia::util::decrypt(&mut ca, &cert, msg.into_bytes())?;
|
||||||
let plain = String::from_utf8_lossy(&res);
|
let plain = String::from_utf8_lossy(&res);
|
||||||
|
|
||||||
assert_eq!(plain, "Hello world!\n");
|
assert_eq!(plain, "Hello world!\n");
|
||||||
|
@ -84,7 +85,8 @@ pub fn test_sign(
|
||||||
let cert = Cert::from_str(param[0])?;
|
let cert = Cert::from_str(param[0])?;
|
||||||
|
|
||||||
let msg = "Hello world, I am signed.";
|
let msg = "Hello world, I am signed.";
|
||||||
let sig = openpgp_card_sequoia::sign(&mut ca, &cert, &mut msg.as_bytes())?;
|
let sig =
|
||||||
|
openpgp_card_sequoia::util::sign(&mut ca, &cert, &mut msg.as_bytes())?;
|
||||||
|
|
||||||
// validate sig
|
// validate sig
|
||||||
assert!(util::verify_sig(&cert, msg.as_bytes(), sig.as_bytes())?);
|
assert!(util::verify_sig(&cert, msg.as_bytes(), sig.as_bytes())?);
|
||||||
|
@ -257,11 +259,7 @@ pub fn test_get_pub(
|
||||||
|
|
||||||
let sig = ca.get_pub_key(KeyType::Signing)?;
|
let sig = ca.get_pub_key(KeyType::Signing)?;
|
||||||
let ts = key_gen.signature().unwrap().get().into();
|
let ts = key_gen.signature().unwrap().get().into();
|
||||||
let key = openpgp_card_sequoia::public_key_material_to_key(
|
let key = public_key_material_to_key(&sig, KeyType::Signing, ts)?;
|
||||||
&sig,
|
|
||||||
KeyType::Signing,
|
|
||||||
ts,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
println!(" sig key data from card -> {:x?}", key);
|
println!(" sig key data from card -> {:x?}", key);
|
||||||
|
|
||||||
|
@ -269,11 +267,7 @@ pub fn test_get_pub(
|
||||||
|
|
||||||
let dec = ca.get_pub_key(KeyType::Decryption)?;
|
let dec = ca.get_pub_key(KeyType::Decryption)?;
|
||||||
let ts = key_gen.decryption().unwrap().get().into();
|
let ts = key_gen.decryption().unwrap().get().into();
|
||||||
let key = openpgp_card_sequoia::public_key_material_to_key(
|
let key = public_key_material_to_key(&dec, KeyType::Decryption, ts)?;
|
||||||
&dec,
|
|
||||||
KeyType::Decryption,
|
|
||||||
ts,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
println!(" dec key data from card -> {:x?}", key);
|
println!(" dec key data from card -> {:x?}", key);
|
||||||
|
|
||||||
|
@ -281,11 +275,7 @@ pub fn test_get_pub(
|
||||||
|
|
||||||
let auth = ca.get_pub_key(KeyType::Authentication)?;
|
let auth = ca.get_pub_key(KeyType::Authentication)?;
|
||||||
let ts = key_gen.authentication().unwrap().get().into();
|
let ts = key_gen.authentication().unwrap().get().into();
|
||||||
let key = openpgp_card_sequoia::public_key_material_to_key(
|
let key = public_key_material_to_key(&auth, KeyType::Authentication, ts)?;
|
||||||
&auth,
|
|
||||||
KeyType::Authentication,
|
|
||||||
ts,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
println!(" auth key data from card -> {:x?}", key);
|
println!(" auth key data from card -> {:x?}", key);
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ use sequoia_openpgp::Cert;
|
||||||
|
|
||||||
use openpgp_card::card_do::KeyGenerationTime;
|
use openpgp_card::card_do::KeyGenerationTime;
|
||||||
use openpgp_card::{CardApp, KeyType};
|
use openpgp_card::{CardApp, KeyType};
|
||||||
use openpgp_card_sequoia::vka_as_uploadable_key;
|
use openpgp_card_sequoia::util::vka_as_uploadable_key;
|
||||||
|
|
||||||
pub const SP: &StandardPolicy = &StandardPolicy::new();
|
pub const SP: &StandardPolicy = &StandardPolicy::new();
|
||||||
|
|
||||||
|
|
350
openpgp-card-sequoia/src/card.rs
Normal file
350
openpgp-card-sequoia/src/card.rs
Normal file
|
@ -0,0 +1,350 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 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
|
||||||
|
//! different types, such as `Open`, `User`, `Sign`, `Admin`.
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use sequoia_openpgp::policy::Policy;
|
||||||
|
use sequoia_openpgp::Cert;
|
||||||
|
|
||||||
|
use openpgp_card::algorithm::{Algo, AlgoInfo};
|
||||||
|
use openpgp_card::card_do::{
|
||||||
|
ApplicationIdentifier, ApplicationRelatedData, CardholderRelatedData,
|
||||||
|
ExCapFeatures, ExtendedCapabilities, ExtendedLengthInfo, Fingerprint,
|
||||||
|
HistoricalBytes, PWStatusBytes, SecuritySupportTemplate, Sex,
|
||||||
|
};
|
||||||
|
use openpgp_card::crypto_data::CardUploadableKey;
|
||||||
|
use openpgp_card::{CardApp, CardClientBox, Error, KeySet, KeyType, Response};
|
||||||
|
|
||||||
|
use crate::decryptor::CardDecryptor;
|
||||||
|
use crate::signer::CardSigner;
|
||||||
|
|
||||||
|
/// Representation of an opened OpenPGP card in its base state (i.e. no
|
||||||
|
/// passwords have been verified, default authorization applies).
|
||||||
|
pub struct Open {
|
||||||
|
card_app: CardApp,
|
||||||
|
|
||||||
|
// Cache of "application related data".
|
||||||
|
//
|
||||||
|
// FIXME: Should be invalidated when changing data on the card!
|
||||||
|
// (e.g. uploading keys, etc)
|
||||||
|
ard: ApplicationRelatedData,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Open {
|
||||||
|
/// Set up connection to a CardClient (read and cache "application
|
||||||
|
/// related data").
|
||||||
|
///
|
||||||
|
/// The OpenPGP applet must already be opened in the CardClient.
|
||||||
|
pub fn open_card(ccb: CardClientBox) -> Result<Self, Error> {
|
||||||
|
// read and cache "application related data"
|
||||||
|
let mut card_app = CardApp::from(ccb);
|
||||||
|
|
||||||
|
let ard = card_app.get_application_related_data()?;
|
||||||
|
|
||||||
|
card_app.init_caps(&ard)?;
|
||||||
|
|
||||||
|
Ok(Self { card_app, ard })
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- application data ---
|
||||||
|
|
||||||
|
/// Load "application related data".
|
||||||
|
///
|
||||||
|
/// This is done once, after opening the OpenPGP card applet
|
||||||
|
/// (the data is stored in the OpenPGPCard object).
|
||||||
|
fn get_app_data(&mut self) -> Result<ApplicationRelatedData> {
|
||||||
|
self.card_app.get_application_related_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_application_id(&self) -> Result<ApplicationIdentifier, Error> {
|
||||||
|
self.ard.get_application_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_historical(&self) -> Result<HistoricalBytes, Error> {
|
||||||
|
self.ard.get_historical()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_extended_length_information(
|
||||||
|
&self,
|
||||||
|
) -> Result<Option<ExtendedLengthInfo>> {
|
||||||
|
self.ard.get_extended_length_information()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_general_feature_management() -> Option<bool> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_discretionary_data_objects() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_extended_capabilities(
|
||||||
|
&self,
|
||||||
|
) -> Result<ExtendedCapabilities, Error> {
|
||||||
|
self.ard.get_extended_capabilities()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_algorithm_attributes(&self, key_type: KeyType) -> Result<Algo> {
|
||||||
|
self.ard.get_algorithm_attributes(key_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PW status Bytes
|
||||||
|
pub fn get_pw_status_bytes(&self) -> Result<PWStatusBytes> {
|
||||||
|
self.ard.get_pw_status_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_fingerprints(&self) -> Result<KeySet<Fingerprint>, Error> {
|
||||||
|
self.ard.get_fingerprints()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ca_fingerprints(&self) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_key_generation_times() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_key_information() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_uif_pso_cds() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_uif_pso_dec() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_uif_pso_aut() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
pub fn get_uif_attestation() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- optional private DOs (0101 - 0104) ---
|
||||||
|
|
||||||
|
// --- login data (5e) ---
|
||||||
|
|
||||||
|
// --- URL (5f50) ---
|
||||||
|
|
||||||
|
pub fn get_url(&mut self) -> Result<String> {
|
||||||
|
self.card_app.get_url()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- cardholder related data (65) ---
|
||||||
|
pub fn get_cardholder_related_data(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<CardholderRelatedData> {
|
||||||
|
self.card_app.get_cardholder_related_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- security support template (7a) ---
|
||||||
|
pub fn get_security_support_template(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<SecuritySupportTemplate> {
|
||||||
|
self.card_app.get_security_support_template()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO "Algorithm Information" (0xFA)
|
||||||
|
pub fn list_supported_algo(&mut self) -> Result<Option<AlgoInfo>> {
|
||||||
|
// The DO "Algorithm Information" (Tag FA) shall be present if
|
||||||
|
// Algorithm attributes can be changed
|
||||||
|
let ec = self.get_extended_capabilities()?;
|
||||||
|
if !ec.features().contains(&ExCapFeatures::AlgoAttrsChangeable) {
|
||||||
|
// Algorithm attributes can not be changed,
|
||||||
|
// list_supported_algo is not supported
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.card_app.get_algo_info()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
|
||||||
|
/// Delete all state on this OpenPGP card
|
||||||
|
pub fn factory_reset(&mut self) -> Result<()> {
|
||||||
|
self.card_app.factory_reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_pw1_for_signing(mut self, pin: &str) -> Result<Sign, Open> {
|
||||||
|
assert!(pin.len() >= 6); // FIXME: Err
|
||||||
|
|
||||||
|
if self.card_app.verify_pw1_for_signing(pin).is_ok() {
|
||||||
|
Ok(Sign { oc: self })
|
||||||
|
} else {
|
||||||
|
Err(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_pw1(&mut self) -> Result<Response, Error> {
|
||||||
|
self.card_app.check_pw1()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_pw1(mut self, pin: &str) -> Result<User, Open> {
|
||||||
|
assert!(pin.len() >= 6); // FIXME: Err
|
||||||
|
|
||||||
|
if self.card_app.verify_pw1(pin).is_ok() {
|
||||||
|
Ok(User { oc: self })
|
||||||
|
} else {
|
||||||
|
Err(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_pw3(&mut self) -> Result<Response, Error> {
|
||||||
|
self.card_app.check_pw3()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_pw3(mut self, pin: &str) -> Result<Admin, Open> {
|
||||||
|
assert!(pin.len() >= 8); // FIXME: Err
|
||||||
|
|
||||||
|
if self.card_app.verify_pw3(pin).is_ok() {
|
||||||
|
Ok(Admin { oc: self })
|
||||||
|
} else {
|
||||||
|
Err(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An OpenPGP card after successful verification of PW1 in mode 82
|
||||||
|
/// (verification for operations other than signing)
|
||||||
|
pub struct User {
|
||||||
|
oc: Open,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow access to fn of OpenPGPCard, through OpenPGPCardUser.
|
||||||
|
impl Deref for User {
|
||||||
|
type Target = Open;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.oc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow access to fn of CardBase, through CardUser.
|
||||||
|
impl DerefMut for User {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.oc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
pub fn decryptor(
|
||||||
|
&mut self,
|
||||||
|
cert: &Cert,
|
||||||
|
policy: &dyn Policy,
|
||||||
|
) -> Result<CardDecryptor, Error> {
|
||||||
|
CardDecryptor::new(&mut self.card_app, cert, policy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An OpenPGP card after successful verification of PW1 in mode 81
|
||||||
|
/// (verification for signing)
|
||||||
|
pub struct Sign {
|
||||||
|
oc: Open,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow access to fn of CardBase, through CardSign.
|
||||||
|
impl Deref for Sign {
|
||||||
|
type Target = Open;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.oc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow access to fn of CardBase, through CardSign.
|
||||||
|
impl DerefMut for Sign {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.oc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: depending on the setting in "PW1 Status byte", only one
|
||||||
|
// signature can be made after verification for signing
|
||||||
|
impl Sign {
|
||||||
|
pub fn signer(
|
||||||
|
&mut self,
|
||||||
|
cert: &Cert,
|
||||||
|
policy: &dyn Policy,
|
||||||
|
) -> std::result::Result<CardSigner, Error> {
|
||||||
|
CardSigner::new(&mut self.card_app, cert, policy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An OpenPGP card after successful verification of PW3 ("Admin privileges")
|
||||||
|
pub struct Admin {
|
||||||
|
oc: Open,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin.
|
||||||
|
impl Deref for Admin {
|
||||||
|
type Target = Open;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.oc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin.
|
||||||
|
impl DerefMut for Admin {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.oc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Admin {
|
||||||
|
pub fn set_name(&mut self, name: &str) -> Result<Response, Error> {
|
||||||
|
if name.len() >= 40 {
|
||||||
|
return Err(anyhow!("name too long").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// All chars must be in ASCII7
|
||||||
|
if name.chars().any(|c| !c.is_ascii()) {
|
||||||
|
return Err(anyhow!("Invalid char in name").into());
|
||||||
|
};
|
||||||
|
|
||||||
|
self.card_app.set_name(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_lang(&mut self, lang: &str) -> Result<Response, Error> {
|
||||||
|
if lang.len() > 8 {
|
||||||
|
return Err(anyhow!("lang too long").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.card_app.set_lang(lang)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sex(&mut self, sex: Sex) -> Result<Response, Error> {
|
||||||
|
self.card_app.set_sex(sex)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_url(&mut self, url: &str) -> Result<Response, Error> {
|
||||||
|
if url.chars().any(|c| !c.is_ascii()) {
|
||||||
|
return Err(anyhow!("Invalid char in url").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for max len
|
||||||
|
let ec = self.get_extended_capabilities()?;
|
||||||
|
|
||||||
|
if url.len() < ec.max_len_special_do() as usize {
|
||||||
|
self.card_app.set_url(url)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("URL too long").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upload_key(
|
||||||
|
&mut self,
|
||||||
|
key: Box<dyn CardUploadableKey>,
|
||||||
|
key_type: KeyType,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.card_app.key_import(key, key_type)
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ use openpgp_card::{CardApp, Error};
|
||||||
|
|
||||||
use crate::PublicKey;
|
use crate::PublicKey;
|
||||||
|
|
||||||
pub(crate) struct CardDecryptor<'a> {
|
pub struct CardDecryptor<'a> {
|
||||||
/// The OpenPGP card (authenticated to allow decryption operations)
|
/// The OpenPGP card (authenticated to allow decryption operations)
|
||||||
ca: &'a mut CardApp,
|
ca: &'a mut CardApp,
|
||||||
|
|
||||||
|
|
|
@ -1,890 +1,18 @@
|
||||||
// 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
|
||||||
|
|
||||||
//! This is a higher-level wrapper around the openpgp-card crate.
|
//! A higher-level wrapper around the openpgp-card crate.
|
||||||
//! It uses sequoia_openpgp for OpenPGP operations.
|
//! It uses sequoia_openpgp for OpenPGP operations.
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::io;
|
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
use openpgp::armor;
|
|
||||||
use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation;
|
|
||||||
use openpgp::crypto::mpi;
|
|
||||||
use openpgp::crypto::mpi::{ProtectedMPI, MPI};
|
|
||||||
use openpgp::packet::key::KeyRole;
|
|
||||||
use openpgp::packet::key::{Key4, PublicParts};
|
|
||||||
use openpgp::packet::key::{PrimaryRole, SubordinateRole};
|
|
||||||
use openpgp::packet::key::{SecretParts, UnspecifiedRole};
|
|
||||||
use openpgp::packet::signature::SignatureBuilder;
|
|
||||||
use openpgp::packet::UserID;
|
|
||||||
use openpgp::packet::{key, Key};
|
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 openpgp::types::{KeyFlags, PublicKeyAlgorithm, SignatureType};
|
|
||||||
use openpgp::{Cert, Packet};
|
|
||||||
use sequoia_openpgp as openpgp;
|
use sequoia_openpgp as openpgp;
|
||||||
|
|
||||||
use openpgp_card::algorithm::{Algo, AlgoInfo, Curve};
|
pub mod card;
|
||||||
use openpgp_card::card_do::{
|
|
||||||
ApplicationIdentifier, ApplicationRelatedData, CardholderRelatedData,
|
|
||||||
ExCapFeatures, ExtendedCapabilities, ExtendedLengthInfo, Fingerprint,
|
|
||||||
HistoricalBytes, KeyGenerationTime, PWStatusBytes,
|
|
||||||
SecuritySupportTemplate, Sex,
|
|
||||||
};
|
|
||||||
use openpgp_card::crypto_data::{
|
|
||||||
CardUploadableKey, Cryptogram, EccKey, EccType, Hash, PrivateKeyMaterial,
|
|
||||||
PublicKeyMaterial, RSAKey,
|
|
||||||
};
|
|
||||||
use openpgp_card::{CardApp, CardClientBox, Error, KeySet, KeyType, Response};
|
|
||||||
|
|
||||||
use crate::signer::CardSigner;
|
|
||||||
|
|
||||||
mod decryptor;
|
mod decryptor;
|
||||||
pub mod signer;
|
mod privkey;
|
||||||
|
mod signer;
|
||||||
|
pub mod sq_util;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
/// Shorthand for public key data
|
/// Shorthand for Sequoia public key data (a single public (sub)key)
|
||||||
pub(crate) type PublicKey = Key<key::PublicParts, key::UnspecifiedRole>;
|
pub(crate) type PublicKey = Key<key::PublicParts, key::UnspecifiedRole>;
|
||||||
|
|
||||||
/// A SequoiaKey represents the private cryptographic key material of an
|
|
||||||
/// OpenPGP (sub)key to be uploaded to an OpenPGP card.
|
|
||||||
struct SequoiaKey {
|
|
||||||
key: openpgp::packet::Key<SecretParts, UnspecifiedRole>,
|
|
||||||
public: mpi::PublicKey,
|
|
||||||
password: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SequoiaKey {
|
|
||||||
/// A `SequoiaKey` wraps a Sequoia PGP private (sub)key data
|
|
||||||
/// (i.e. a ValidErasedKeyAmalgamation) in a form that can be uploaded
|
|
||||||
/// by the openpgp-card crate.
|
|
||||||
fn new(
|
|
||||||
vka: ValidErasedKeyAmalgamation<SecretParts>,
|
|
||||||
password: Option<String>,
|
|
||||||
) -> Self {
|
|
||||||
let public = vka.parts_as_public().mpis().clone();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
key: vka.key().clone(),
|
|
||||||
public,
|
|
||||||
password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper fn: get a CardUploadableKey for a ValidErasedKeyAmalgamation
|
|
||||||
pub fn vka_as_uploadable_key(
|
|
||||||
vka: ValidErasedKeyAmalgamation<SecretParts>,
|
|
||||||
password: Option<String>,
|
|
||||||
) -> Box<dyn CardUploadableKey> {
|
|
||||||
let sqk = SequoiaKey::new(vka, password);
|
|
||||||
Box::new(sqk)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper fn: get a Key<PublicParts, UnspecifiedRole> for a PublicKeyMaterial
|
|
||||||
pub fn public_key_material_to_key(
|
|
||||||
pkm: &PublicKeyMaterial,
|
|
||||||
key_type: KeyType,
|
|
||||||
time: KeyGenerationTime,
|
|
||||||
) -> Result<Key<PublicParts, UnspecifiedRole>> {
|
|
||||||
let time = Timestamp::from(time.get()).into();
|
|
||||||
|
|
||||||
match pkm {
|
|
||||||
PublicKeyMaterial::R(rsa) => {
|
|
||||||
let k4 = Key4::import_public_rsa(rsa.v(), rsa.n(), Some(time))?;
|
|
||||||
|
|
||||||
Ok(k4.into())
|
|
||||||
}
|
|
||||||
PublicKeyMaterial::E(ecc) => {
|
|
||||||
let algo = ecc.algo().clone(); // FIXME?
|
|
||||||
if let Algo::Ecc(algo_ecc) = algo {
|
|
||||||
let curve = match algo_ecc.curve() {
|
|
||||||
Curve::NistP256r1 => openpgp::types::Curve::NistP256,
|
|
||||||
Curve::NistP384r1 => openpgp::types::Curve::NistP384,
|
|
||||||
Curve::NistP521r1 => openpgp::types::Curve::NistP521,
|
|
||||||
Curve::Ed25519 => openpgp::types::Curve::Ed25519,
|
|
||||||
Curve::Cv25519 => openpgp::types::Curve::Cv25519,
|
|
||||||
c => unimplemented!("unhandled curve: {:?}", c),
|
|
||||||
};
|
|
||||||
|
|
||||||
match key_type {
|
|
||||||
KeyType::Authentication | KeyType::Signing => {
|
|
||||||
if algo_ecc.curve() == Curve::Ed25519 {
|
|
||||||
// EdDSA
|
|
||||||
let k4 =
|
|
||||||
Key4::import_public_ed25519(ecc.data(), time)?;
|
|
||||||
|
|
||||||
Ok(Key::from(k4))
|
|
||||||
} else {
|
|
||||||
// ECDSA
|
|
||||||
let k4 = Key4::new(
|
|
||||||
time,
|
|
||||||
PublicKeyAlgorithm::ECDSA,
|
|
||||||
mpi::PublicKey::ECDSA {
|
|
||||||
curve,
|
|
||||||
q: mpi::MPI::new(ecc.data()),
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(k4.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyType::Decryption => {
|
|
||||||
if algo_ecc.curve() == Curve::Cv25519 {
|
|
||||||
// EdDSA
|
|
||||||
let k4 = Key4::import_public_cv25519(
|
|
||||||
ecc.data(),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
time,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(k4.into())
|
|
||||||
} else {
|
|
||||||
// FIXME: just defining `hash` and `sym` is not
|
|
||||||
// ok when a cert already exists
|
|
||||||
|
|
||||||
// ECDH
|
|
||||||
let k4 = Key4::new(
|
|
||||||
time,
|
|
||||||
PublicKeyAlgorithm::ECDH,
|
|
||||||
mpi::PublicKey::ECDH {
|
|
||||||
curve,
|
|
||||||
q: mpi::MPI::new(ecc.data()),
|
|
||||||
hash: Default::default(),
|
|
||||||
sym: Default::default(),
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(k4.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unimplemented!("Unsupported KeyType"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("unexpected algo {:?}", algo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unimplemented!("Unexpected PublicKeyMaterial type"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a Cert from the three subkeys on a card.
|
|
||||||
/// (Calling this multiple times will result in different Certs!)
|
|
||||||
///
|
|
||||||
/// FIXME: make dec/auth keys optional
|
|
||||||
///
|
|
||||||
/// FIXME: accept optional metadata for user_id(s)?
|
|
||||||
pub fn make_cert(
|
|
||||||
ca: &mut CardApp,
|
|
||||||
key_sig: Key<PublicParts, UnspecifiedRole>,
|
|
||||||
key_dec: Key<PublicParts, UnspecifiedRole>,
|
|
||||||
key_aut: Key<PublicParts, UnspecifiedRole>,
|
|
||||||
) -> Result<Cert> {
|
|
||||||
let mut pp = vec![];
|
|
||||||
|
|
||||||
// 1) use the signing key as primary key
|
|
||||||
let pri = PrimaryRole::convert_key(key_sig.clone());
|
|
||||||
pp.push(Packet::from(pri));
|
|
||||||
|
|
||||||
// 2) add decryption key as subkey
|
|
||||||
let sub_dec = SubordinateRole::convert_key(key_dec);
|
|
||||||
pp.push(Packet::from(sub_dec.clone()));
|
|
||||||
|
|
||||||
// Temporary version of the cert
|
|
||||||
let cert = Cert::try_from(pp.clone())?;
|
|
||||||
|
|
||||||
// 3) make binding, sign with card -> add
|
|
||||||
{
|
|
||||||
let signing_builder =
|
|
||||||
SignatureBuilder::new(SignatureType::SubkeyBinding)
|
|
||||||
.set_signature_creation_time(SystemTime::now())?
|
|
||||||
.set_key_validity_period(std::time::Duration::new(0, 0))?
|
|
||||||
.set_key_flags(
|
|
||||||
KeyFlags::empty()
|
|
||||||
.set_storage_encryption()
|
|
||||||
.set_transport_encryption(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Allow signing on the card
|
|
||||||
ca.verify_pw1_for_signing("123456")?;
|
|
||||||
|
|
||||||
// Card-backed signer for bindings
|
|
||||||
let mut card_signer = CardSigner::with_pubkey(ca, key_sig.clone());
|
|
||||||
|
|
||||||
let signing_bsig: Packet = sub_dec
|
|
||||||
.bind(&mut card_signer, &cert, signing_builder)?
|
|
||||||
.into();
|
|
||||||
|
|
||||||
pp.push(signing_bsig);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4) add auth subkey
|
|
||||||
let sub_aut = SubordinateRole::convert_key(key_aut);
|
|
||||||
pp.push(Packet::from(sub_aut.clone()));
|
|
||||||
|
|
||||||
// 5) make, sign binding -> add
|
|
||||||
{
|
|
||||||
let signing_builder =
|
|
||||||
SignatureBuilder::new(SignatureType::SubkeyBinding)
|
|
||||||
.set_signature_creation_time(SystemTime::now())?
|
|
||||||
.set_key_validity_period(std::time::Duration::new(0, 0))?
|
|
||||||
.set_key_flags(KeyFlags::empty().set_authentication())?;
|
|
||||||
|
|
||||||
// Allow signing on the card
|
|
||||||
ca.verify_pw1_for_signing("123456")?;
|
|
||||||
|
|
||||||
// Card-backed signer for bindings
|
|
||||||
let mut card_signer = CardSigner::with_pubkey(ca, key_sig.clone());
|
|
||||||
|
|
||||||
let signing_bsig: Packet = sub_aut
|
|
||||||
.bind(&mut card_signer, &cert, signing_builder)?
|
|
||||||
.into();
|
|
||||||
|
|
||||||
pp.push(signing_bsig);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6) add user id from name / email
|
|
||||||
let cardholder = ca.get_cardholder_related_data()?;
|
|
||||||
|
|
||||||
// FIXME: process name field? accept email as argument?!
|
|
||||||
let uid: UserID =
|
|
||||||
cardholder.name().expect("expecting name on card").into();
|
|
||||||
|
|
||||||
pp.push(uid.clone().into());
|
|
||||||
|
|
||||||
// 7) make, sign binding -> add
|
|
||||||
{
|
|
||||||
let signing_builder =
|
|
||||||
SignatureBuilder::new(SignatureType::PositiveCertification)
|
|
||||||
.set_signature_creation_time(SystemTime::now())?
|
|
||||||
.set_key_validity_period(std::time::Duration::new(0, 0))?
|
|
||||||
.set_key_flags(
|
|
||||||
// Flags for primary key
|
|
||||||
KeyFlags::empty().set_signing().set_certification(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Allow signing on the card
|
|
||||||
ca.verify_pw1_for_signing("123456")?;
|
|
||||||
|
|
||||||
// Card-backed signer for bindings
|
|
||||||
let mut card_signer = CardSigner::with_pubkey(ca, key_sig);
|
|
||||||
|
|
||||||
let signing_bsig: Packet =
|
|
||||||
uid.bind(&mut card_signer, &cert, signing_builder)?.into();
|
|
||||||
|
|
||||||
pp.push(signing_bsig);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cert::try_from(pp)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement the `CardUploadableKey` trait that openpgp-card uses to
|
|
||||||
/// upload (sub)keys to a card.
|
|
||||||
impl CardUploadableKey for SequoiaKey {
|
|
||||||
fn get_key(&self) -> Result<PrivateKeyMaterial> {
|
|
||||||
// Decrypt key with password, if set
|
|
||||||
let key = match &self.password {
|
|
||||||
None => self.key.clone(),
|
|
||||||
Some(pw) => self.key.clone().decrypt_secret(
|
|
||||||
&openpgp::crypto::Password::from(pw.as_str()),
|
|
||||||
)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get private cryptographic material
|
|
||||||
let unenc = if let Some(
|
|
||||||
openpgp::packet::key::SecretKeyMaterial::Unencrypted(ref u),
|
|
||||||
) = key.optional_secret()
|
|
||||||
{
|
|
||||||
u
|
|
||||||
} else {
|
|
||||||
panic!("can't get private key material");
|
|
||||||
};
|
|
||||||
|
|
||||||
let secret_key_material = unenc.map(|mpis| mpis.clone());
|
|
||||||
|
|
||||||
match (self.public.clone(), secret_key_material) {
|
|
||||||
(
|
|
||||||
mpi::PublicKey::RSA { e, n },
|
|
||||||
mpi::SecretKeyMaterial::RSA { d: _, p, q, u: _ },
|
|
||||||
) => {
|
|
||||||
let sq_rsa = SqRSA::new(e, n, p, q);
|
|
||||||
|
|
||||||
Ok(PrivateKeyMaterial::R(Box::new(sq_rsa)))
|
|
||||||
}
|
|
||||||
(
|
|
||||||
mpi::PublicKey::ECDH { curve, .. },
|
|
||||||
mpi::SecretKeyMaterial::ECDH { scalar },
|
|
||||||
) => {
|
|
||||||
let sq_ecc =
|
|
||||||
SqEccKey::new(curve.oid().to_vec(), scalar, EccType::ECDH);
|
|
||||||
|
|
||||||
Ok(PrivateKeyMaterial::E(Box::new(sq_ecc)))
|
|
||||||
}
|
|
||||||
(
|
|
||||||
mpi::PublicKey::ECDSA { curve, .. },
|
|
||||||
mpi::SecretKeyMaterial::ECDSA { scalar },
|
|
||||||
) => {
|
|
||||||
let sq_ecc = SqEccKey::new(
|
|
||||||
curve.oid().to_vec(),
|
|
||||||
scalar,
|
|
||||||
EccType::ECDSA,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(PrivateKeyMaterial::E(Box::new(sq_ecc)))
|
|
||||||
}
|
|
||||||
(
|
|
||||||
mpi::PublicKey::EdDSA { curve, .. },
|
|
||||||
mpi::SecretKeyMaterial::EdDSA { scalar },
|
|
||||||
) => {
|
|
||||||
let sq_ecc = SqEccKey::new(
|
|
||||||
curve.oid().to_vec(),
|
|
||||||
scalar,
|
|
||||||
EccType::EdDSA,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(PrivateKeyMaterial::E(Box::new(sq_ecc)))
|
|
||||||
}
|
|
||||||
(p, s) => {
|
|
||||||
unimplemented!(
|
|
||||||
"Unexpected algorithms: public {:?}, \
|
|
||||||
secret {:?}",
|
|
||||||
p,
|
|
||||||
s
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Number of non-leap seconds since January 1, 1970 0:00:00 UTC
|
|
||||||
/// (aka "UNIX timestamp")
|
|
||||||
fn get_ts(&self) -> KeyGenerationTime {
|
|
||||||
let ts: Timestamp = Timestamp::try_from(self.key.creation_time())
|
|
||||||
.expect("Creation time cannot be converted into u32 timestamp");
|
|
||||||
let ts: u32 = ts.into();
|
|
||||||
|
|
||||||
ts.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_fp(&self) -> Result<Fingerprint, Error> {
|
|
||||||
let fp = self.key.fingerprint();
|
|
||||||
fp.as_bytes().try_into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RSA-specific data-structure to hold private (sub)key material for upload
|
|
||||||
/// with the `openpgp-card` crate.
|
|
||||||
struct SqRSA {
|
|
||||||
e: MPI,
|
|
||||||
n: MPI,
|
|
||||||
p: ProtectedMPI,
|
|
||||||
q: ProtectedMPI,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SqRSA {
|
|
||||||
fn new(e: MPI, n: MPI, p: ProtectedMPI, q: ProtectedMPI) -> Self {
|
|
||||||
Self { e, n, p, q }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RSAKey for SqRSA {
|
|
||||||
fn get_e(&self) -> &[u8] {
|
|
||||||
self.e.value()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_n(&self) -> &[u8] {
|
|
||||||
self.n.value()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_p(&self) -> &[u8] {
|
|
||||||
self.p.value()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_q(&self) -> &[u8] {
|
|
||||||
self.q.value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ECC-specific data-structure to hold private (sub)key material for upload
|
|
||||||
/// with the `openpgp-card` crate.
|
|
||||||
struct SqEccKey {
|
|
||||||
oid: Vec<u8>,
|
|
||||||
scalar: ProtectedMPI,
|
|
||||||
ecc_type: EccType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SqEccKey {
|
|
||||||
fn new(oid: Vec<u8>, scalar: ProtectedMPI, ecc_type: EccType) -> Self {
|
|
||||||
SqEccKey {
|
|
||||||
oid,
|
|
||||||
scalar,
|
|
||||||
ecc_type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EccKey for SqEccKey {
|
|
||||||
fn get_oid(&self) -> &[u8] {
|
|
||||||
&self.oid
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_scalar(&self) -> &[u8] {
|
|
||||||
self.scalar.value()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_type(&self) -> EccType {
|
|
||||||
self.ecc_type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convenience fn to select and upload a (sub)key from a Cert, as a given
|
|
||||||
/// KeyType. If multiple suitable (sub)keys are found, the first one is
|
|
||||||
/// used.
|
|
||||||
///
|
|
||||||
/// FIXME: picking the (sub)key to upload should probably done with
|
|
||||||
/// more intent.
|
|
||||||
pub fn upload_from_cert_yolo(
|
|
||||||
oca: &mut CardAdmin,
|
|
||||||
cert: &openpgp::Cert,
|
|
||||||
key_type: KeyType,
|
|
||||||
password: Option<String>,
|
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let policy = StandardPolicy::new();
|
|
||||||
|
|
||||||
// Find all suitable (sub)keys for key_type.
|
|
||||||
let mut valid_ka = cert
|
|
||||||
.keys()
|
|
||||||
.with_policy(&policy, None)
|
|
||||||
.secret()
|
|
||||||
.alive()
|
|
||||||
.revoked(false);
|
|
||||||
valid_ka = match key_type {
|
|
||||||
KeyType::Decryption => valid_ka.for_storage_encryption(),
|
|
||||||
KeyType::Signing => valid_ka.for_signing(),
|
|
||||||
KeyType::Authentication => valid_ka.for_authentication(),
|
|
||||||
_ => return Err(anyhow!("Unexpected KeyType").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// FIXME: for now, we just pick the first (sub)key from the list
|
|
||||||
if let Some(vka) = valid_ka.next() {
|
|
||||||
upload_key(oca, vka, key_type, password).map_err(|e| e.into())
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("No suitable (sub)key found").into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Upload a ValidErasedKeyAmalgamation to the card as a specific KeyType.
|
|
||||||
///
|
|
||||||
/// The caller needs to make sure that `vka` is suitable for `key_type`.
|
|
||||||
pub fn upload_key(
|
|
||||||
oca: &mut CardAdmin,
|
|
||||||
vka: ValidErasedKeyAmalgamation<SecretParts>,
|
|
||||||
key_type: KeyType,
|
|
||||||
password: Option<String>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let sqk = SequoiaKey::new(vka, password);
|
|
||||||
|
|
||||||
oca.upload_key(Box::new(sqk), key_type)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decrypt(
|
|
||||||
ca: &mut CardApp,
|
|
||||||
cert: &openpgp::Cert,
|
|
||||||
msg: Vec<u8>,
|
|
||||||
) -> Result<Vec<u8>> {
|
|
||||||
let mut decrypted = Vec::new();
|
|
||||||
{
|
|
||||||
let reader = io::BufReader::new(&msg[..]);
|
|
||||||
|
|
||||||
let p = StandardPolicy::new();
|
|
||||||
let d = decryptor::CardDecryptor::new(ca, cert, &p)?;
|
|
||||||
|
|
||||||
let db = DecryptorBuilder::from_reader(reader)?;
|
|
||||||
let mut decryptor = db.with_policy(&p, None, d)?;
|
|
||||||
|
|
||||||
// Read all data from decryptor and store in decrypted
|
|
||||||
io::copy(&mut decryptor, &mut decrypted)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(decrypted)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sign(
|
|
||||||
ca: &mut CardApp,
|
|
||||||
cert: &openpgp::Cert,
|
|
||||||
input: &mut dyn io::Read,
|
|
||||||
) -> Result<String> {
|
|
||||||
let mut armorer = armor::Writer::new(vec![], armor::Kind::Signature)?;
|
|
||||||
{
|
|
||||||
let p = StandardPolicy::new();
|
|
||||||
let s = signer::CardSigner::new(ca, cert, &p)?;
|
|
||||||
|
|
||||||
let message = Message::new(&mut armorer);
|
|
||||||
let mut message = Signer::new(message, s).detached().build()?;
|
|
||||||
|
|
||||||
// Process input data, via message
|
|
||||||
io::copy(input, &mut message)?;
|
|
||||||
|
|
||||||
message.finalize()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let buffer = armorer.finalize()?;
|
|
||||||
|
|
||||||
String::from_utf8(buffer).context("Failed to convert signature to utf8")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mapping function to get a fingerprint from "PublicKeyMaterial +
|
|
||||||
/// timestamp + KeyType" (intended for use with `CardApp.generate_key()`).
|
|
||||||
pub fn public_to_fingerprint(
|
|
||||||
pkm: &PublicKeyMaterial,
|
|
||||||
time: KeyGenerationTime,
|
|
||||||
kt: KeyType,
|
|
||||||
) -> Result<Fingerprint, Error> {
|
|
||||||
// Transform PublicKeyMaterial into a Sequoia Key
|
|
||||||
let key = public_key_material_to_key(pkm, kt, time)?;
|
|
||||||
|
|
||||||
// Get fingerprint from the Sequoia Key
|
|
||||||
let fp = key.fingerprint();
|
|
||||||
fp.as_bytes().try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------
|
|
||||||
|
|
||||||
/// Representation of an opened OpenPGP card in its basic, freshly opened,
|
|
||||||
/// state (i.e. no passwords have been verified, default privileges apply).
|
|
||||||
pub struct CardBase {
|
|
||||||
card_app: CardApp,
|
|
||||||
|
|
||||||
// Cache of "application related data".
|
|
||||||
//
|
|
||||||
// FIXME: Should be invalidated when changing data on the card!
|
|
||||||
// (e.g. uploading keys, etc)
|
|
||||||
ard: ApplicationRelatedData,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CardBase {
|
|
||||||
pub fn new(card_app: CardApp, ard: ApplicationRelatedData) -> Self {
|
|
||||||
Self { card_app, ard }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a reference to the internal CardApp object (for use in tests)
|
|
||||||
pub fn get_card_app(&mut self) -> &mut CardApp {
|
|
||||||
&mut self.card_app
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up connection (cache "application related data") to a
|
|
||||||
/// CardClient, on which the openpgp applet has already been opened.
|
|
||||||
pub fn open_card(ccb: CardClientBox) -> Result<Self, Error> {
|
|
||||||
// read and cache "application related data"
|
|
||||||
let mut card_app = CardApp::from(ccb);
|
|
||||||
|
|
||||||
let ard = card_app.get_application_related_data()?;
|
|
||||||
|
|
||||||
card_app.init_caps(&ard)?;
|
|
||||||
|
|
||||||
Ok(Self { card_app, ard })
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- application data ---
|
|
||||||
|
|
||||||
/// Load "application related data".
|
|
||||||
///
|
|
||||||
/// This is done once, after opening the OpenPGP card applet
|
|
||||||
/// (the data is stored in the OpenPGPCard object).
|
|
||||||
fn get_app_data(&mut self) -> Result<ApplicationRelatedData> {
|
|
||||||
self.card_app.get_application_related_data()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_application_id(&self) -> Result<ApplicationIdentifier, Error> {
|
|
||||||
self.ard.get_application_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_historical(&self) -> Result<HistoricalBytes, Error> {
|
|
||||||
self.ard.get_historical()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_extended_length_information(
|
|
||||||
&self,
|
|
||||||
) -> Result<Option<ExtendedLengthInfo>> {
|
|
||||||
self.ard.get_extended_length_information()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_general_feature_management() -> Option<bool> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_discretionary_data_objects() {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_extended_capabilities(
|
|
||||||
&self,
|
|
||||||
) -> Result<ExtendedCapabilities, Error> {
|
|
||||||
self.ard.get_extended_capabilities()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_algorithm_attributes(&self, key_type: KeyType) -> Result<Algo> {
|
|
||||||
self.ard.get_algorithm_attributes(key_type)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// PW status Bytes
|
|
||||||
pub fn get_pw_status_bytes(&self) -> Result<PWStatusBytes> {
|
|
||||||
self.ard.get_pw_status_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_fingerprints(&self) -> Result<KeySet<Fingerprint>, Error> {
|
|
||||||
self.ard.get_fingerprints()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_ca_fingerprints(&self) {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_key_generation_times() {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_key_information() {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_uif_pso_cds() {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_uif_pso_dec() {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_uif_pso_aut() {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
pub fn get_uif_attestation() {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- optional private DOs (0101 - 0104) ---
|
|
||||||
|
|
||||||
// --- login data (5e) ---
|
|
||||||
|
|
||||||
// --- URL (5f50) ---
|
|
||||||
|
|
||||||
pub fn get_url(&mut self) -> Result<String> {
|
|
||||||
self.card_app.get_url()
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- cardholder related data (65) ---
|
|
||||||
pub fn get_cardholder_related_data(
|
|
||||||
&mut self,
|
|
||||||
) -> Result<CardholderRelatedData> {
|
|
||||||
self.card_app.get_cardholder_related_data()
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- security support template (7a) ---
|
|
||||||
pub fn get_security_support_template(
|
|
||||||
&mut self,
|
|
||||||
) -> Result<SecuritySupportTemplate> {
|
|
||||||
self.card_app.get_security_support_template()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DO "Algorithm Information" (0xFA)
|
|
||||||
pub fn list_supported_algo(&mut self) -> Result<Option<AlgoInfo>> {
|
|
||||||
// The DO "Algorithm Information" (Tag FA) shall be present if
|
|
||||||
// Algorithm attributes can be changed
|
|
||||||
let ec = self.get_extended_capabilities()?;
|
|
||||||
if !ec.features().contains(&ExCapFeatures::AlgoAttrsChangeable) {
|
|
||||||
// Algorithm attributes can not be changed,
|
|
||||||
// list_supported_algo is not supported
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.card_app.get_algo_info()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
/// Delete all state on this OpenPGP card
|
|
||||||
pub fn factory_reset(&mut self) -> Result<()> {
|
|
||||||
self.card_app.factory_reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_pw1_for_signing(
|
|
||||||
mut self,
|
|
||||||
pin: &str,
|
|
||||||
) -> Result<CardSign, CardBase> {
|
|
||||||
assert!(pin.len() >= 6); // FIXME: Err
|
|
||||||
|
|
||||||
if self.card_app.verify_pw1_for_signing(pin).is_ok() {
|
|
||||||
Ok(CardSign { oc: self })
|
|
||||||
} else {
|
|
||||||
Err(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_pw1(&mut self) -> Result<Response, Error> {
|
|
||||||
self.card_app.check_pw1()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_pw1(mut self, pin: &str) -> Result<CardUser, CardBase> {
|
|
||||||
assert!(pin.len() >= 6); // FIXME: Err
|
|
||||||
|
|
||||||
if self.card_app.verify_pw1(pin).is_ok() {
|
|
||||||
Ok(CardUser { oc: self })
|
|
||||||
} else {
|
|
||||||
Err(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_pw3(&mut self) -> Result<Response, Error> {
|
|
||||||
self.card_app.check_pw3()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_pw3(mut self, pin: &str) -> Result<CardAdmin, CardBase> {
|
|
||||||
assert!(pin.len() >= 8); // FIXME: Err
|
|
||||||
|
|
||||||
if self.card_app.verify_pw3(pin).is_ok() {
|
|
||||||
Ok(CardAdmin { oc: self })
|
|
||||||
} else {
|
|
||||||
Err(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An OpenPGP card after successful verification of PW1 in mode 82
|
|
||||||
/// (verification for operations other than signing)
|
|
||||||
pub struct CardUser {
|
|
||||||
oc: CardBase,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allow access to fn of OpenPGPCard, through OpenPGPCardUser.
|
|
||||||
impl Deref for CardUser {
|
|
||||||
type Target = CardBase;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.oc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allow access to fn of CardBase, through CardUser.
|
|
||||||
impl DerefMut for CardUser {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.oc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CardUser {
|
|
||||||
/// Decrypt the ciphertext in `dm`, on the card.
|
|
||||||
pub fn decipher(&mut self, dm: Cryptogram) -> Result<Vec<u8>, Error> {
|
|
||||||
self.card_app.decipher(dm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An OpenPGP card after successful verification of PW1 in mode 81
|
|
||||||
/// (verification for signing)
|
|
||||||
pub struct CardSign {
|
|
||||||
oc: CardBase,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allow access to fn of CardBase, through CardSign.
|
|
||||||
impl Deref for CardSign {
|
|
||||||
type Target = CardBase;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.oc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allow access to fn of CardBase, through CardSign.
|
|
||||||
impl DerefMut for CardSign {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.oc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: depending on the setting in "PW1 Status byte", only one
|
|
||||||
// signature can be made after verification for signing
|
|
||||||
impl CardSign {
|
|
||||||
/// Sign the message in `hash`, on the card.
|
|
||||||
pub fn signature_for_hash(
|
|
||||||
&mut self,
|
|
||||||
hash: Hash,
|
|
||||||
) -> Result<Vec<u8>, Error> {
|
|
||||||
self.card_app.signature_for_hash(hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An OpenPGP card after successful verification of PW3 ("Admin privileges")
|
|
||||||
pub struct CardAdmin {
|
|
||||||
oc: CardBase,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin.
|
|
||||||
impl Deref for CardAdmin {
|
|
||||||
type Target = CardBase;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.oc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin.
|
|
||||||
impl DerefMut for CardAdmin {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.oc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CardAdmin {
|
|
||||||
pub fn set_name(&mut self, name: &str) -> Result<Response, Error> {
|
|
||||||
if name.len() >= 40 {
|
|
||||||
return Err(anyhow!("name too long").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// All chars must be in ASCII7
|
|
||||||
if name.chars().any(|c| !c.is_ascii()) {
|
|
||||||
return Err(anyhow!("Invalid char in name").into());
|
|
||||||
};
|
|
||||||
|
|
||||||
self.card_app.set_name(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_lang(&mut self, lang: &str) -> Result<Response, Error> {
|
|
||||||
if lang.len() > 8 {
|
|
||||||
return Err(anyhow!("lang too long").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.card_app.set_lang(lang)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_sex(&mut self, sex: Sex) -> Result<Response, Error> {
|
|
||||||
self.card_app.set_sex(sex)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_url(&mut self, url: &str) -> Result<Response, Error> {
|
|
||||||
if url.chars().any(|c| !c.is_ascii()) {
|
|
||||||
return Err(anyhow!("Invalid char in url").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for max len
|
|
||||||
let ec = self.get_extended_capabilities()?;
|
|
||||||
|
|
||||||
if url.len() < ec.max_len_special_do() as usize {
|
|
||||||
self.card_app.set_url(url)
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("URL too long").into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn upload_key(
|
|
||||||
&mut self,
|
|
||||||
key: Box<dyn CardUploadableKey>,
|
|
||||||
key_type: KeyType,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.card_app.key_import(key, key_type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::env;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
use sequoia_openpgp::parse::Parse;
|
use sequoia_openpgp::parse::Parse;
|
||||||
|
use sequoia_openpgp::policy::StandardPolicy;
|
||||||
use sequoia_openpgp::Cert;
|
use sequoia_openpgp::Cert;
|
||||||
|
|
||||||
use openpgp_card::card_do::Sex;
|
use openpgp_card::card_do::Sex;
|
||||||
|
@ -13,7 +14,8 @@ use openpgp_card::{CardApp, KeyType};
|
||||||
use openpgp_card_pcsc::PcscClient;
|
use openpgp_card_pcsc::PcscClient;
|
||||||
// use openpgp_card_scdc::ScdClient;
|
// use openpgp_card_scdc::ScdClient;
|
||||||
|
|
||||||
use openpgp_card_sequoia::CardBase;
|
use openpgp_card_sequoia::card::Open;
|
||||||
|
use openpgp_card_sequoia::sq_util::{decryption_helper, sign_helper};
|
||||||
|
|
||||||
// Filename of test key and test message to use:
|
// Filename of test key and test message to use:
|
||||||
|
|
||||||
|
@ -38,7 +40,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
if let Ok(test_card_ident) = test_card_ident {
|
if let Ok(test_card_ident) = test_card_ident {
|
||||||
println!("** get card");
|
println!("** get card");
|
||||||
let mut oc =
|
let mut oc =
|
||||||
CardBase::open_card(PcscClient::open_by_ident(&test_card_ident)?)?;
|
Open::open_card(PcscClient::open_by_ident(&test_card_ident)?)?;
|
||||||
|
|
||||||
// let mut oc = CardBase::open_card(ScdClient::open_by_serial(
|
// let mut oc = CardBase::open_card(ScdClient::open_by_serial(
|
||||||
// None,
|
// None,
|
||||||
|
@ -123,14 +125,14 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
let cert = Cert::from_file(TEST_KEY_PATH)?;
|
let cert = Cert::from_file(TEST_KEY_PATH)?;
|
||||||
|
|
||||||
openpgp_card_sequoia::upload_from_cert_yolo(
|
openpgp_card_sequoia::util::upload_from_cert_yolo(
|
||||||
&mut oc_admin,
|
&mut oc_admin,
|
||||||
&cert,
|
&cert,
|
||||||
KeyType::Decryption,
|
KeyType::Decryption,
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
openpgp_card_sequoia::upload_from_cert_yolo(
|
openpgp_card_sequoia::util::upload_from_cert_yolo(
|
||||||
&mut oc_admin,
|
&mut oc_admin,
|
||||||
&cert,
|
&cert,
|
||||||
KeyType::Signing,
|
KeyType::Signing,
|
||||||
|
@ -153,7 +155,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
||||||
let mut oc =
|
let mut oc =
|
||||||
CardBase::open_card(PcscClient::open_by_ident(&test_card_ident)?)?;
|
Open::open_card(PcscClient::open_by_ident(&test_card_ident)?)?;
|
||||||
|
|
||||||
// let mut oc = CardBase::open_card(ScdClient::open_by_serial(
|
// let mut oc = CardBase::open_card(ScdClient::open_by_serial(
|
||||||
// None,
|
// None,
|
||||||
|
@ -181,11 +183,11 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
println!("{:?}", msg);
|
println!("{:?}", msg);
|
||||||
|
|
||||||
let res = openpgp_card_sequoia::decrypt(
|
let sp = StandardPolicy::new();
|
||||||
&mut oc_user.get_card_app(),
|
|
||||||
&cert,
|
let d = oc_user.decryptor(&cert, &sp)?;
|
||||||
msg.into_bytes(),
|
|
||||||
)?;
|
let res = decryption_helper(d, msg.into_bytes(), &sp)?;
|
||||||
|
|
||||||
let plain = String::from_utf8_lossy(&res);
|
let plain = String::from_utf8_lossy(&res);
|
||||||
println!("decrypted plaintext: {}", plain);
|
println!("decrypted plaintext: {}", plain);
|
||||||
|
@ -199,7 +201,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
// Open fresh Card for signing
|
// Open fresh Card for signing
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
let oc =
|
let oc =
|
||||||
CardBase::open_card(PcscClient::open_by_ident(&test_card_ident)?)?;
|
Open::open_card(PcscClient::open_by_ident(&test_card_ident)?)?;
|
||||||
|
|
||||||
// let oc = CardBase::open_card(ScdClient::open_by_serial(
|
// let oc = CardBase::open_card(ScdClient::open_by_serial(
|
||||||
// None,
|
// None,
|
||||||
|
@ -214,11 +216,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let cert = Cert::from_file(TEST_KEY_PATH)?;
|
let cert = Cert::from_file(TEST_KEY_PATH)?;
|
||||||
|
|
||||||
let text = "Hello world, I am signed.";
|
let text = "Hello world, I am signed.";
|
||||||
let res = openpgp_card_sequoia::sign(
|
|
||||||
oc_user.get_card_app(),
|
let signer = oc_user.signer(&cert, &StandardPolicy::new())?;
|
||||||
&cert,
|
|
||||||
&mut text.as_bytes(),
|
let res = sign_helper(signer, &mut text.as_bytes());
|
||||||
);
|
|
||||||
|
|
||||||
println!("res sign {:?}", res);
|
println!("res sign {:?}", res);
|
||||||
|
|
||||||
|
|
205
openpgp-card-sequoia/src/privkey.rs
Normal file
205
openpgp-card-sequoia/src/privkey.rs
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation;
|
||||||
|
use openpgp::crypto::{mpi, mpi::ProtectedMPI, mpi::MPI};
|
||||||
|
use openpgp::packet::{
|
||||||
|
key,
|
||||||
|
key::{SecretParts, UnspecifiedRole},
|
||||||
|
Key,
|
||||||
|
};
|
||||||
|
use openpgp::types::Timestamp;
|
||||||
|
use sequoia_openpgp as openpgp;
|
||||||
|
|
||||||
|
use openpgp_card::card_do::{Fingerprint, KeyGenerationTime};
|
||||||
|
use openpgp_card::crypto_data::{
|
||||||
|
CardUploadableKey, EccKey, EccType, PrivateKeyMaterial, RSAKey,
|
||||||
|
};
|
||||||
|
use openpgp_card::Error;
|
||||||
|
|
||||||
|
/// A SequoiaKey represents the private cryptographic key material of an
|
||||||
|
/// OpenPGP (sub)key to be uploaded to an OpenPGP card.
|
||||||
|
pub(crate) struct SequoiaKey {
|
||||||
|
key: Key<SecretParts, UnspecifiedRole>,
|
||||||
|
public: mpi::PublicKey,
|
||||||
|
password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SequoiaKey {
|
||||||
|
/// A `SequoiaKey` wraps a Sequoia PGP private (sub)key data
|
||||||
|
/// (i.e. a ValidErasedKeyAmalgamation) in a form that can be uploaded
|
||||||
|
/// by the openpgp-card crate.
|
||||||
|
pub(crate) fn new(
|
||||||
|
vka: ValidErasedKeyAmalgamation<SecretParts>,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Self {
|
||||||
|
let public = vka.parts_as_public().mpis().clone();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
key: vka.key().clone(),
|
||||||
|
public,
|
||||||
|
password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement the `CardUploadableKey` trait that openpgp-card uses to
|
||||||
|
/// upload (sub)keys to a card.
|
||||||
|
impl CardUploadableKey for SequoiaKey {
|
||||||
|
fn get_key(&self) -> Result<PrivateKeyMaterial> {
|
||||||
|
// Decrypt key with password, if set
|
||||||
|
let key = match &self.password {
|
||||||
|
None => self.key.clone(),
|
||||||
|
Some(pw) => self.key.clone().decrypt_secret(
|
||||||
|
&openpgp::crypto::Password::from(pw.as_str()),
|
||||||
|
)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get private cryptographic material
|
||||||
|
let unenc = if let Some(key::SecretKeyMaterial::Unencrypted(ref u)) =
|
||||||
|
key.optional_secret()
|
||||||
|
{
|
||||||
|
u
|
||||||
|
} else {
|
||||||
|
panic!("can't get private key material");
|
||||||
|
};
|
||||||
|
|
||||||
|
let secret_key_material = unenc.map(|mpis| mpis.clone());
|
||||||
|
|
||||||
|
match (self.public.clone(), secret_key_material) {
|
||||||
|
(
|
||||||
|
mpi::PublicKey::RSA { e, n },
|
||||||
|
mpi::SecretKeyMaterial::RSA { d: _, p, q, u: _ },
|
||||||
|
) => {
|
||||||
|
let sq_rsa = SqRSA::new(e, n, p, q);
|
||||||
|
|
||||||
|
Ok(PrivateKeyMaterial::R(Box::new(sq_rsa)))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
mpi::PublicKey::ECDH { curve, .. },
|
||||||
|
mpi::SecretKeyMaterial::ECDH { scalar },
|
||||||
|
) => {
|
||||||
|
let sq_ecc =
|
||||||
|
SqEccKey::new(curve.oid().to_vec(), scalar, EccType::ECDH);
|
||||||
|
|
||||||
|
Ok(PrivateKeyMaterial::E(Box::new(sq_ecc)))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
mpi::PublicKey::ECDSA { curve, .. },
|
||||||
|
mpi::SecretKeyMaterial::ECDSA { scalar },
|
||||||
|
) => {
|
||||||
|
let sq_ecc = SqEccKey::new(
|
||||||
|
curve.oid().to_vec(),
|
||||||
|
scalar,
|
||||||
|
EccType::ECDSA,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(PrivateKeyMaterial::E(Box::new(sq_ecc)))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
mpi::PublicKey::EdDSA { curve, .. },
|
||||||
|
mpi::SecretKeyMaterial::EdDSA { scalar },
|
||||||
|
) => {
|
||||||
|
let sq_ecc = SqEccKey::new(
|
||||||
|
curve.oid().to_vec(),
|
||||||
|
scalar,
|
||||||
|
EccType::EdDSA,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(PrivateKeyMaterial::E(Box::new(sq_ecc)))
|
||||||
|
}
|
||||||
|
(p, s) => {
|
||||||
|
unimplemented!(
|
||||||
|
"Unexpected algorithms: public {:?}, \
|
||||||
|
secret {:?}",
|
||||||
|
p,
|
||||||
|
s
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of non-leap seconds since January 1, 1970 0:00:00 UTC
|
||||||
|
/// (aka "UNIX timestamp")
|
||||||
|
fn get_ts(&self) -> KeyGenerationTime {
|
||||||
|
let ts: Timestamp = Timestamp::try_from(self.key.creation_time())
|
||||||
|
.expect("Creation time cannot be converted into u32 timestamp");
|
||||||
|
let ts: u32 = ts.into();
|
||||||
|
|
||||||
|
ts.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fp(&self) -> Result<Fingerprint, Error> {
|
||||||
|
let fp = self.key.fingerprint();
|
||||||
|
fp.as_bytes().try_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RSA-specific data-structure to hold private (sub)key material for upload
|
||||||
|
/// with the `openpgp-card` crate.
|
||||||
|
struct SqRSA {
|
||||||
|
e: MPI,
|
||||||
|
n: MPI,
|
||||||
|
p: ProtectedMPI,
|
||||||
|
q: ProtectedMPI,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SqRSA {
|
||||||
|
fn new(e: MPI, n: MPI, p: ProtectedMPI, q: ProtectedMPI) -> Self {
|
||||||
|
Self { e, n, p, q }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RSAKey for SqRSA {
|
||||||
|
fn get_e(&self) -> &[u8] {
|
||||||
|
self.e.value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_n(&self) -> &[u8] {
|
||||||
|
self.n.value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_p(&self) -> &[u8] {
|
||||||
|
self.p.value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_q(&self) -> &[u8] {
|
||||||
|
self.q.value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ECC-specific data-structure to hold private (sub)key material for upload
|
||||||
|
/// with the `openpgp-card` crate.
|
||||||
|
struct SqEccKey {
|
||||||
|
oid: Vec<u8>,
|
||||||
|
scalar: ProtectedMPI,
|
||||||
|
ecc_type: EccType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SqEccKey {
|
||||||
|
fn new(oid: Vec<u8>, scalar: ProtectedMPI, ecc_type: EccType) -> Self {
|
||||||
|
SqEccKey {
|
||||||
|
oid,
|
||||||
|
scalar,
|
||||||
|
ecc_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EccKey for SqEccKey {
|
||||||
|
fn get_oid(&self) -> &[u8] {
|
||||||
|
&self.oid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_scalar(&self) -> &[u8] {
|
||||||
|
self.scalar.value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_type(&self) -> EccType {
|
||||||
|
self.ecc_type
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,13 +65,12 @@ impl<'a> CardSigner<'a> {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(Error::InternalError(anyhow!(
|
Err(Error::InternalError(anyhow!(
|
||||||
"Failed to get the signing key's Fingerprint \
|
"Failed to get the signing key's Fingerprint from the card"
|
||||||
from the card"
|
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_pubkey(
|
pub(crate) fn with_pubkey(
|
||||||
ca: &'a mut CardApp,
|
ca: &'a mut CardApp,
|
||||||
public: PublicKey,
|
public: PublicKey,
|
||||||
) -> CardSigner<'a> {
|
) -> CardSigner<'a> {
|
||||||
|
@ -84,17 +83,18 @@ impl<'a> crypto::Signer for CardSigner<'a> {
|
||||||
&self.public
|
&self.public
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delegate a signing operation to the OpenPGP card.
|
|
||||||
///
|
|
||||||
/// This fn prepares the data structures that openpgp-card needs to
|
|
||||||
/// perform the signing operation.
|
|
||||||
///
|
|
||||||
/// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE)
|
|
||||||
fn sign(
|
fn sign(
|
||||||
&mut self,
|
&mut self,
|
||||||
hash_algo: openpgp::types::HashAlgorithm,
|
hash_algo: openpgp::types::HashAlgorithm,
|
||||||
digest: &[u8],
|
digest: &[u8],
|
||||||
) -> openpgp::Result<mpi::Signature> {
|
) -> openpgp::Result<mpi::Signature> {
|
||||||
|
// Delegate a signing operation to the OpenPGP card.
|
||||||
|
//
|
||||||
|
// This fn prepares the data structures that openpgp-card needs to
|
||||||
|
// perform the signing operation.
|
||||||
|
//
|
||||||
|
// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE)
|
||||||
|
|
||||||
match (self.public.pk_algo(), self.public.mpis()) {
|
match (self.public.pk_algo(), self.public.mpis()) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
(PublicKeyAlgorithm::RSASign, mpi::PublicKey::RSA { .. })
|
(PublicKeyAlgorithm::RSASign, mpi::PublicKey::RSA { .. })
|
||||||
|
|
59
openpgp-card-sequoia/src/sq_util.rs
Normal file
59
openpgp-card-sequoia/src/sq_util.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
//! Simple wrappers for performing very specific tasks with Sequoia PGP
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use openpgp::armor;
|
||||||
|
use openpgp::crypto;
|
||||||
|
use openpgp::parse::{
|
||||||
|
stream::{DecryptionHelper, DecryptorBuilder, VerificationHelper},
|
||||||
|
Parse,
|
||||||
|
};
|
||||||
|
use openpgp::policy::Policy;
|
||||||
|
use openpgp::serialize::stream::{Message, Signer};
|
||||||
|
use sequoia_openpgp as openpgp;
|
||||||
|
|
||||||
|
pub fn sign_helper<S>(s: S, input: &mut dyn io::Read) -> Result<String>
|
||||||
|
where
|
||||||
|
S: crypto::Signer + Send + Sync,
|
||||||
|
{
|
||||||
|
let mut armorer = armor::Writer::new(vec![], armor::Kind::Signature)?;
|
||||||
|
{
|
||||||
|
let message = Message::new(&mut armorer);
|
||||||
|
let mut message = Signer::new(message, s).detached().build()?;
|
||||||
|
|
||||||
|
// Process input data, via message
|
||||||
|
io::copy(input, &mut message)?;
|
||||||
|
|
||||||
|
message.finalize()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = armorer.finalize()?;
|
||||||
|
|
||||||
|
String::from_utf8(buffer).context("Failed to convert signature to utf8")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decryption_helper<D>(
|
||||||
|
d: D,
|
||||||
|
msg: Vec<u8>,
|
||||||
|
p: &dyn Policy,
|
||||||
|
) -> Result<Vec<u8>>
|
||||||
|
where
|
||||||
|
D: crypto::Decryptor + VerificationHelper + DecryptionHelper,
|
||||||
|
{
|
||||||
|
let mut decrypted = Vec::new();
|
||||||
|
{
|
||||||
|
let reader = io::BufReader::new(&msg[..]);
|
||||||
|
|
||||||
|
let db = DecryptorBuilder::from_reader(reader)?;
|
||||||
|
let mut decryptor = db.with_policy(p, None, d)?;
|
||||||
|
|
||||||
|
// Read all data from decryptor and store in decrypted
|
||||||
|
io::copy(&mut decryptor, &mut decrypted)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(decrypted)
|
||||||
|
}
|
356
openpgp-card-sequoia/src/util.rs
Normal file
356
openpgp-card-sequoia/src/util.rs
Normal file
|
@ -0,0 +1,356 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
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::packet::{
|
||||||
|
key::{Key4, KeyRole, PrimaryRole, SecretParts, SubordinateRole},
|
||||||
|
signature::SignatureBuilder,
|
||||||
|
Key, UserID,
|
||||||
|
};
|
||||||
|
use openpgp::parse::{stream::DecryptorBuilder, Parse};
|
||||||
|
use openpgp::policy::StandardPolicy;
|
||||||
|
use openpgp::serialize::stream::{Message, Signer};
|
||||||
|
use openpgp::types::{KeyFlags, PublicKeyAlgorithm, SignatureType, Timestamp};
|
||||||
|
use openpgp::{Cert, Packet};
|
||||||
|
use sequoia_openpgp as openpgp;
|
||||||
|
|
||||||
|
use openpgp_card::algorithm::{Algo, Curve};
|
||||||
|
use openpgp_card::card_do::{Fingerprint, KeyGenerationTime};
|
||||||
|
use openpgp_card::crypto_data::{CardUploadableKey, PublicKeyMaterial};
|
||||||
|
use openpgp_card::{CardApp, Error, KeyType};
|
||||||
|
|
||||||
|
use crate::card::Admin;
|
||||||
|
use crate::privkey::SequoiaKey;
|
||||||
|
use crate::signer::CardSigner;
|
||||||
|
use crate::{decryptor, signer, PublicKey};
|
||||||
|
|
||||||
|
/// Create a Cert from the three subkeys on a card.
|
||||||
|
/// (Calling this multiple times will result in different Certs!)
|
||||||
|
///
|
||||||
|
/// FIXME: make dec/auth keys optional
|
||||||
|
///
|
||||||
|
/// FIXME: accept optional metadata for user_id(s)?
|
||||||
|
pub fn make_cert(
|
||||||
|
ca: &mut CardApp,
|
||||||
|
key_sig: PublicKey,
|
||||||
|
key_dec: PublicKey,
|
||||||
|
key_aut: PublicKey,
|
||||||
|
) -> Result<Cert> {
|
||||||
|
let mut pp = vec![];
|
||||||
|
|
||||||
|
// 1) use the signing key as primary key
|
||||||
|
let pri = PrimaryRole::convert_key(key_sig.clone());
|
||||||
|
pp.push(Packet::from(pri));
|
||||||
|
|
||||||
|
// 2) add decryption key as subkey
|
||||||
|
let sub_dec = SubordinateRole::convert_key(key_dec);
|
||||||
|
pp.push(Packet::from(sub_dec.clone()));
|
||||||
|
|
||||||
|
// Temporary version of the cert
|
||||||
|
let cert = Cert::try_from(pp.clone())?;
|
||||||
|
|
||||||
|
// 3) make binding, sign with card -> add
|
||||||
|
{
|
||||||
|
let signing_builder =
|
||||||
|
SignatureBuilder::new(SignatureType::SubkeyBinding)
|
||||||
|
.set_signature_creation_time(SystemTime::now())?
|
||||||
|
.set_key_validity_period(std::time::Duration::new(0, 0))?
|
||||||
|
.set_key_flags(
|
||||||
|
KeyFlags::empty()
|
||||||
|
.set_storage_encryption()
|
||||||
|
.set_transport_encryption(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Allow signing on the card
|
||||||
|
ca.verify_pw1_for_signing("123456")?;
|
||||||
|
|
||||||
|
// Card-backed signer for bindings
|
||||||
|
let mut card_signer = CardSigner::with_pubkey(ca, key_sig.clone());
|
||||||
|
|
||||||
|
let signing_bsig: Packet = sub_dec
|
||||||
|
.bind(&mut card_signer, &cert, signing_builder)?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
pp.push(signing_bsig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) add auth subkey
|
||||||
|
let sub_aut = SubordinateRole::convert_key(key_aut);
|
||||||
|
pp.push(Packet::from(sub_aut.clone()));
|
||||||
|
|
||||||
|
// 5) make, sign binding -> add
|
||||||
|
{
|
||||||
|
let signing_builder =
|
||||||
|
SignatureBuilder::new(SignatureType::SubkeyBinding)
|
||||||
|
.set_signature_creation_time(SystemTime::now())?
|
||||||
|
.set_key_validity_period(std::time::Duration::new(0, 0))?
|
||||||
|
.set_key_flags(KeyFlags::empty().set_authentication())?;
|
||||||
|
|
||||||
|
// Allow signing on the card
|
||||||
|
ca.verify_pw1_for_signing("123456")?;
|
||||||
|
|
||||||
|
// Card-backed signer for bindings
|
||||||
|
let mut card_signer = CardSigner::with_pubkey(ca, key_sig.clone());
|
||||||
|
|
||||||
|
let signing_bsig: Packet = sub_aut
|
||||||
|
.bind(&mut card_signer, &cert, signing_builder)?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
pp.push(signing_bsig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6) add user id from name / email
|
||||||
|
let cardholder = ca.get_cardholder_related_data()?;
|
||||||
|
|
||||||
|
// FIXME: process name field? accept email as argument?!
|
||||||
|
let uid: UserID =
|
||||||
|
cardholder.name().expect("expecting name on card").into();
|
||||||
|
|
||||||
|
pp.push(uid.clone().into());
|
||||||
|
|
||||||
|
// 7) make, sign binding -> add
|
||||||
|
{
|
||||||
|
let signing_builder =
|
||||||
|
SignatureBuilder::new(SignatureType::PositiveCertification)
|
||||||
|
.set_signature_creation_time(SystemTime::now())?
|
||||||
|
.set_key_validity_period(std::time::Duration::new(0, 0))?
|
||||||
|
.set_key_flags(
|
||||||
|
// Flags for primary key
|
||||||
|
KeyFlags::empty().set_signing().set_certification(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Allow signing on the card
|
||||||
|
ca.verify_pw1_for_signing("123456")?;
|
||||||
|
|
||||||
|
// Card-backed signer for bindings
|
||||||
|
let mut card_signer = CardSigner::with_pubkey(ca, key_sig);
|
||||||
|
|
||||||
|
let signing_bsig: Packet =
|
||||||
|
uid.bind(&mut card_signer, &cert, signing_builder)?.into();
|
||||||
|
|
||||||
|
pp.push(signing_bsig);
|
||||||
|
}
|
||||||
|
|
||||||
|
Cert::try_from(pp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper fn: get a CardUploadableKey for a ValidErasedKeyAmalgamation
|
||||||
|
pub fn vka_as_uploadable_key(
|
||||||
|
vka: ValidErasedKeyAmalgamation<SecretParts>,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Box<dyn CardUploadableKey> {
|
||||||
|
let sqk = SequoiaKey::new(vka, password);
|
||||||
|
Box::new(sqk)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper fn: get a Sequoia PublicKey from an openpgp-card PublicKeyMaterial
|
||||||
|
pub fn public_key_material_to_key(
|
||||||
|
pkm: &PublicKeyMaterial,
|
||||||
|
key_type: KeyType,
|
||||||
|
time: KeyGenerationTime,
|
||||||
|
) -> Result<PublicKey> {
|
||||||
|
let time = Timestamp::from(time.get()).into();
|
||||||
|
|
||||||
|
match pkm {
|
||||||
|
PublicKeyMaterial::R(rsa) => {
|
||||||
|
let k4 = Key4::import_public_rsa(rsa.v(), rsa.n(), Some(time))?;
|
||||||
|
|
||||||
|
Ok(k4.into())
|
||||||
|
}
|
||||||
|
PublicKeyMaterial::E(ecc) => {
|
||||||
|
let algo = ecc.algo().clone(); // FIXME?
|
||||||
|
if let Algo::Ecc(algo_ecc) = algo {
|
||||||
|
let curve = match algo_ecc.curve() {
|
||||||
|
Curve::NistP256r1 => openpgp::types::Curve::NistP256,
|
||||||
|
Curve::NistP384r1 => openpgp::types::Curve::NistP384,
|
||||||
|
Curve::NistP521r1 => openpgp::types::Curve::NistP521,
|
||||||
|
Curve::Ed25519 => openpgp::types::Curve::Ed25519,
|
||||||
|
Curve::Cv25519 => openpgp::types::Curve::Cv25519,
|
||||||
|
c => unimplemented!("unhandled curve: {:?}", c),
|
||||||
|
};
|
||||||
|
|
||||||
|
match key_type {
|
||||||
|
KeyType::Authentication | KeyType::Signing => {
|
||||||
|
if algo_ecc.curve() == Curve::Ed25519 {
|
||||||
|
// EdDSA
|
||||||
|
let k4 =
|
||||||
|
Key4::import_public_ed25519(ecc.data(), time)?;
|
||||||
|
|
||||||
|
Ok(Key::from(k4))
|
||||||
|
} else {
|
||||||
|
// ECDSA
|
||||||
|
let k4 = Key4::new(
|
||||||
|
time,
|
||||||
|
PublicKeyAlgorithm::ECDSA,
|
||||||
|
mpi::PublicKey::ECDSA {
|
||||||
|
curve,
|
||||||
|
q: mpi::MPI::new(ecc.data()),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(k4.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyType::Decryption => {
|
||||||
|
if algo_ecc.curve() == Curve::Cv25519 {
|
||||||
|
// EdDSA
|
||||||
|
let k4 = Key4::import_public_cv25519(
|
||||||
|
ecc.data(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
time,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(k4.into())
|
||||||
|
} else {
|
||||||
|
// FIXME: just defining `hash` and `sym` is not
|
||||||
|
// ok when a cert already exists
|
||||||
|
|
||||||
|
// ECDH
|
||||||
|
let k4 = Key4::new(
|
||||||
|
time,
|
||||||
|
PublicKeyAlgorithm::ECDH,
|
||||||
|
mpi::PublicKey::ECDH {
|
||||||
|
curve,
|
||||||
|
q: mpi::MPI::new(ecc.data()),
|
||||||
|
hash: Default::default(),
|
||||||
|
sym: Default::default(),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(k4.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!("Unsupported KeyType"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("unexpected algo {:?}", algo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!("Unexpected PublicKeyMaterial type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mapping function to get a fingerprint from "PublicKeyMaterial +
|
||||||
|
/// timestamp + KeyType" (intended for use with `CardApp.generate_key()`).
|
||||||
|
pub fn public_to_fingerprint(
|
||||||
|
pkm: &PublicKeyMaterial,
|
||||||
|
time: KeyGenerationTime,
|
||||||
|
kt: KeyType,
|
||||||
|
) -> Result<Fingerprint, Error> {
|
||||||
|
// Transform PublicKeyMaterial into a Sequoia Key
|
||||||
|
let key = public_key_material_to_key(pkm, kt, time)?;
|
||||||
|
|
||||||
|
// Get fingerprint from the Sequoia Key
|
||||||
|
let fp = key.fingerprint();
|
||||||
|
fp.as_bytes().try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: this is used in main.rs for testing, but should probably not exist.
|
||||||
|
///
|
||||||
|
/// Convenience fn to select and upload a (sub)key from a Cert, as a given
|
||||||
|
/// KeyType. If multiple suitable (sub)keys are found, the first one is
|
||||||
|
/// used.
|
||||||
|
///
|
||||||
|
/// (sub)keys for upload should probably always be explicitly picked by
|
||||||
|
/// client software.
|
||||||
|
pub fn upload_from_cert_yolo(
|
||||||
|
oca: &mut Admin,
|
||||||
|
cert: &Cert,
|
||||||
|
key_type: KeyType,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let policy = StandardPolicy::new();
|
||||||
|
|
||||||
|
// Find all suitable (sub)keys for key_type.
|
||||||
|
let mut valid_ka = cert
|
||||||
|
.keys()
|
||||||
|
.with_policy(&policy, None)
|
||||||
|
.secret()
|
||||||
|
.alive()
|
||||||
|
.revoked(false);
|
||||||
|
valid_ka = match key_type {
|
||||||
|
KeyType::Decryption => valid_ka.for_storage_encryption(),
|
||||||
|
KeyType::Signing => valid_ka.for_signing(),
|
||||||
|
KeyType::Authentication => valid_ka.for_authentication(),
|
||||||
|
_ => return Err(anyhow!("Unexpected KeyType").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME: for now, we just pick the first (sub)key from the list
|
||||||
|
if let Some(vka) = valid_ka.next() {
|
||||||
|
upload_key(oca, vka, key_type, password).map_err(|e| e.into())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("No suitable (sub)key found").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upload a ValidErasedKeyAmalgamation to the card as a specific KeyType.
|
||||||
|
///
|
||||||
|
/// The caller needs to make sure that `vka` is suitable for `key_type`.
|
||||||
|
pub fn upload_key(
|
||||||
|
oca: &mut Admin,
|
||||||
|
vka: ValidErasedKeyAmalgamation<SecretParts>,
|
||||||
|
key_type: KeyType,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let sqk = SequoiaKey::new(vka, password);
|
||||||
|
|
||||||
|
oca.upload_key(Box::new(sqk), key_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: this fn is used in card_functionality, but should be removed
|
||||||
|
pub fn sign(
|
||||||
|
ca: &mut CardApp,
|
||||||
|
cert: &Cert,
|
||||||
|
input: &mut dyn io::Read,
|
||||||
|
) -> Result<String> {
|
||||||
|
let mut armorer = armor::Writer::new(vec![], armor::Kind::Signature)?;
|
||||||
|
{
|
||||||
|
let p = StandardPolicy::new();
|
||||||
|
let s = signer::CardSigner::new(ca, cert, &p)?;
|
||||||
|
|
||||||
|
let message = Message::new(&mut armorer);
|
||||||
|
let mut message = Signer::new(message, s).detached().build()?;
|
||||||
|
|
||||||
|
// Process input data, via message
|
||||||
|
io::copy(input, &mut message)?;
|
||||||
|
|
||||||
|
message.finalize()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = armorer.finalize()?;
|
||||||
|
|
||||||
|
String::from_utf8(buffer).context("Failed to convert signature to utf8")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: this fn is used in card_functionality, but should be removed
|
||||||
|
pub fn decrypt(
|
||||||
|
ca: &mut CardApp,
|
||||||
|
cert: &Cert,
|
||||||
|
msg: Vec<u8>,
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
let mut decrypted = Vec::new();
|
||||||
|
{
|
||||||
|
let reader = io::BufReader::new(&msg[..]);
|
||||||
|
|
||||||
|
let p = StandardPolicy::new();
|
||||||
|
let d = decryptor::CardDecryptor::new(ca, cert, &p)?;
|
||||||
|
|
||||||
|
let db = DecryptorBuilder::from_reader(reader)?;
|
||||||
|
let mut decryptor = db.with_policy(&p, None, d)?;
|
||||||
|
|
||||||
|
// Read all data from decryptor and store in decrypted
|
||||||
|
io::copy(&mut decryptor, &mut decrypted)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(decrypted)
|
||||||
|
}
|
Loading…
Reference in a new issue