Merge branch 'heiko/openpgp-card-sequoia-api' into 'main'
Reorganize openpgp-card-sequoia API See merge request openpgp-card/openpgp-card!30
This commit is contained in:
commit
04b875754d
24 changed files with 886 additions and 845 deletions
|
@ -4,7 +4,7 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use openpgp_card_pcsc::PcscBackend;
|
use openpgp_card_pcsc::PcscBackend;
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
println!("The following OpenPGP cards are connected to your system:");
|
println!("The following OpenPGP cards are connected to your system:");
|
||||||
|
|
|
@ -16,10 +16,11 @@ use sequoia_openpgp::Cert;
|
||||||
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::{Error, KeyType, OpenPgp, OpenPgpTransaction, StatusBytes};
|
use openpgp_card::{Error, KeyType, OpenPgp, OpenPgpTransaction, StatusBytes};
|
||||||
use openpgp_card_sequoia::card::{Card, Transaction};
|
use openpgp_card_sequoia::sq_util;
|
||||||
use openpgp_card_sequoia::util::{
|
use openpgp_card_sequoia::util::{
|
||||||
make_cert, public_key_material_and_fp_to_key, public_key_material_to_key,
|
make_cert, public_key_material_and_fp_to_key, public_key_material_to_key,
|
||||||
};
|
};
|
||||||
|
use openpgp_card_sequoia::{state::Transaction, Card};
|
||||||
|
|
||||||
use crate::cards::TestCardData;
|
use crate::cards::TestCardData;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
@ -72,7 +73,7 @@ pub fn test_decrypt(pgp: &mut OpenPgp, param: &[&str]) -> Result<TestOutput, Tes
|
||||||
let mut user = transaction.user_card().unwrap();
|
let mut user = transaction.user_card().unwrap();
|
||||||
let d = user.decryptor(&|| {})?;
|
let d = user.decryptor(&|| {})?;
|
||||||
|
|
||||||
let res = openpgp_card_sequoia::util::decrypt(d, msg.into_bytes(), &p)?;
|
let res = sq_util::decrypt(d, msg.into_bytes(), &p)?;
|
||||||
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");
|
||||||
|
@ -96,7 +97,7 @@ pub fn test_sign(pgp: &mut OpenPgp, param: &[&str]) -> Result<TestOutput, TestEr
|
||||||
let s = sign.signer(&|| {})?;
|
let s = sign.signer(&|| {})?;
|
||||||
|
|
||||||
let msg = "Hello world, I am signed.";
|
let msg = "Hello world, I am signed.";
|
||||||
let sig = openpgp_card_sequoia::util::sign(s, &mut msg.as_bytes())?;
|
let sig = sq_util::sign(s, &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())?);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use openpgp_card_pcsc::PcscBackend;
|
use openpgp_card_pcsc::PcscBackend;
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
|
|
||||||
use openpgp::parse::{stream::DecryptorBuilder, Parse};
|
use openpgp::parse::{stream::DecryptorBuilder, Parse};
|
||||||
use openpgp::policy::StandardPolicy;
|
use openpgp::policy::StandardPolicy;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use openpgp_card_pcsc::PcscBackend;
|
use openpgp_card_pcsc::PcscBackend;
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
|
|
||||||
use openpgp::serialize::stream::{Armorer, Message, Signer};
|
use openpgp::serialize::stream::{Armorer, Message, Signer};
|
||||||
use sequoia_openpgp as openpgp;
|
use sequoia_openpgp as openpgp;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!--
|
<!--
|
||||||
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
|
SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ Note: the current API of this crate is an early draft, reflected by version numb
|
||||||
|
|
||||||
**Example code**
|
**Example code**
|
||||||
|
|
||||||
The program `main.rs` performs a number of functions on an OpenPGP card.
|
The program `examples/test.rs` performs a number of functions on an OpenPGP card.
|
||||||
To run it, you need to set an environment variable to the identifier of
|
To run it, you need to set an environment variable to the identifier of
|
||||||
the OpenPGP card you want to use.
|
the OpenPGP card you want to use.
|
||||||
|
|
||||||
|
@ -25,11 +25,11 @@ program!
|
||||||
|
|
||||||
```
|
```
|
||||||
$ export TEST_CARD_IDENT="0123:4567ABCD"
|
$ export TEST_CARD_IDENT="0123:4567ABCD"
|
||||||
$ cargo run
|
$ cargo run --example test
|
||||||
```
|
```
|
||||||
|
|
||||||
You can see more debugging output by increasing the log-level, like this:
|
You can see more debugging output by increasing the log-level, like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ RUST_LOG=trace cargo run
|
$ RUST_LOG=trace cargo run --example test
|
||||||
```
|
```
|
|
@ -13,8 +13,8 @@ use openpgp_card::card_do::Sex;
|
||||||
use openpgp_card::KeyType;
|
use openpgp_card::KeyType;
|
||||||
use openpgp_card_pcsc::PcscBackend;
|
use openpgp_card_pcsc::PcscBackend;
|
||||||
|
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
|
||||||
use openpgp_card_sequoia::sq_util;
|
use openpgp_card_sequoia::sq_util;
|
||||||
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
|
|
||||||
// Filename of test key and test message to use
|
// Filename of test key and test message to use
|
||||||
|
|
||||||
|
|
|
@ -1,702 +0,0 @@
|
||||||
// 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
|
|
||||||
//! different types, such as `Card`, `Open`, `User`, `Sign`, `Admin`.
|
|
||||||
|
|
||||||
use sequoia_openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation;
|
|
||||||
use sequoia_openpgp::packet::key::SecretParts;
|
|
||||||
use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm};
|
|
||||||
|
|
||||||
use openpgp_card::algorithm::{Algo, AlgoInfo, AlgoSimple};
|
|
||||||
use openpgp_card::card_do::{
|
|
||||||
ApplicationIdentifier, ApplicationRelatedData, CardholderRelatedData, ExtendedCapabilities,
|
|
||||||
ExtendedLengthInfo, Fingerprint, HistoricalBytes, KeyGenerationTime, KeyInformation, Lang,
|
|
||||||
PWStatusBytes, SecuritySupportTemplate, Sex, TouchPolicy, UIF,
|
|
||||||
};
|
|
||||||
use openpgp_card::crypto_data::PublicKeyMaterial;
|
|
||||||
use openpgp_card::{CardBackend, Error, KeySet, KeyType, OpenPgp, OpenPgpTransaction};
|
|
||||||
|
|
||||||
use crate::decryptor::CardDecryptor;
|
|
||||||
use crate::signer::CardSigner;
|
|
||||||
use crate::util::{public_to_fingerprint, vka_as_uploadable_key};
|
|
||||||
use crate::PublicKey;
|
|
||||||
|
|
||||||
pub trait State {}
|
|
||||||
impl State for Open {}
|
|
||||||
impl State for Transaction<'_> {}
|
|
||||||
impl State for User<'_, '_> {}
|
|
||||||
impl State for Sign<'_, '_> {}
|
|
||||||
impl State for Admin<'_, '_> {}
|
|
||||||
|
|
||||||
/// Representation of an opened OpenPGP card in its base state (i.e. no
|
|
||||||
/// passwords have been verified, default authorization applies).
|
|
||||||
/// No transaction has been started.
|
|
||||||
pub struct Open {
|
|
||||||
pgp: OpenPgp,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Representation of an opened OpenPGP card in its base state (i.e. no
|
|
||||||
/// passwords have been verified, default authorization applies).
|
|
||||||
/// A transaction has been started.
|
|
||||||
pub struct Transaction<'a> {
|
|
||||||
opt: OpenPgpTransaction<'a>,
|
|
||||||
|
|
||||||
// Cache of "application related data".
|
|
||||||
//
|
|
||||||
// FIXME: Should be invalidated when changing data on the card!
|
|
||||||
// (e.g. uploading keys, etc)
|
|
||||||
//
|
|
||||||
// This field should probably be an Option<> that gets invalidated when appropriate and
|
|
||||||
// re-fetched lazily.
|
|
||||||
ard: ApplicationRelatedData,
|
|
||||||
|
|
||||||
// verify status of pw1
|
|
||||||
pw1: bool,
|
|
||||||
|
|
||||||
// verify status of pw1 for signing
|
|
||||||
pw1_sign: bool,
|
|
||||||
|
|
||||||
// verify status of pw3
|
|
||||||
pw3: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An OpenPGP card after successfully verifying PW1 in mode 82
|
|
||||||
/// (verification for user operations other than signing)
|
|
||||||
pub struct User<'app, 'open> {
|
|
||||||
tx: &'open mut Card<Transaction<'app>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An OpenPGP card after successfully verifying PW1 in mode 81
|
|
||||||
/// (verification for signing)
|
|
||||||
pub struct Sign<'app, 'open> {
|
|
||||||
tx: &'open mut Card<Transaction<'app>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An OpenPGP card after successful verification of PW3 ("Admin privileges")
|
|
||||||
pub struct Admin<'app, 'open> {
|
|
||||||
tx: &'open mut Card<Transaction<'app>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Card<S>
|
|
||||||
where
|
|
||||||
S: State,
|
|
||||||
{
|
|
||||||
state: S,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<B> for Card<Open>
|
|
||||||
where
|
|
||||||
B: Into<Box<dyn CardBackend + Send + Sync>>,
|
|
||||||
{
|
|
||||||
fn from(backend: B) -> Self {
|
|
||||||
let pgp = OpenPgp::new(backend.into());
|
|
||||||
|
|
||||||
Card::<Open> {
|
|
||||||
state: Open { pgp },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Card<Open> {
|
|
||||||
pub fn transaction(&mut self) -> Result<Card<Transaction>, Error> {
|
|
||||||
let opt = self.state.pgp.transaction()?;
|
|
||||||
|
|
||||||
Card::<Transaction>::new(opt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Card<Transaction<'a>> {
|
|
||||||
/// Do not use!
|
|
||||||
///
|
|
||||||
/// FIXME: this interface is currently used in `card-functionality`, for testing.
|
|
||||||
/// It will be removed.
|
|
||||||
pub fn new(mut opt: OpenPgpTransaction<'a>) -> Result<Self, Error> {
|
|
||||||
let ard = opt.application_related_data()?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
state: Transaction {
|
|
||||||
opt,
|
|
||||||
ard,
|
|
||||||
pw1: false,
|
|
||||||
pw1_sign: false,
|
|
||||||
pw3: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replace cached "application related data" in this instance of Open
|
|
||||||
/// with the current data on the card.
|
|
||||||
///
|
|
||||||
/// This is needed e.g. after importing or generating keys on a card, to
|
|
||||||
/// see these changes reflected in `self.ard`.
|
|
||||||
pub fn reload_ard(&mut self) -> Result<(), Error> {
|
|
||||||
// FIXME: this should be implemented internally, transparent to users
|
|
||||||
self.state.ard = self.state.opt.application_related_data()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn feature_pinpad_verify(&mut self) -> bool {
|
|
||||||
self.state.opt.feature_pinpad_verify()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn feature_pinpad_modify(&mut self) -> bool {
|
|
||||||
self.state.opt.feature_pinpad_modify()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_user(&mut self, pin: &[u8]) -> Result<(), Error> {
|
|
||||||
self.state.opt.verify_pw1_user(pin)?;
|
|
||||||
self.state.pw1 = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_user_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> {
|
|
||||||
pinpad_prompt();
|
|
||||||
|
|
||||||
self.state.opt.verify_pw1_user_pinpad()?;
|
|
||||||
self.state.pw1 = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_user_for_signing(&mut self, pin: &[u8]) -> Result<(), Error> {
|
|
||||||
self.state.opt.verify_pw1_sign(pin)?;
|
|
||||||
|
|
||||||
// FIXME: depending on card mode, pw1_sign is only usable once
|
|
||||||
|
|
||||||
self.state.pw1_sign = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_user_for_signing_pinpad(
|
|
||||||
&mut self,
|
|
||||||
pinpad_prompt: &dyn Fn(),
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
pinpad_prompt();
|
|
||||||
|
|
||||||
self.state.opt.verify_pw1_sign_pinpad()?;
|
|
||||||
|
|
||||||
// FIXME: depending on card mode, pw1_sign is only usable once
|
|
||||||
|
|
||||||
self.state.pw1_sign = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_admin(&mut self, pin: &[u8]) -> Result<(), Error> {
|
|
||||||
self.state.opt.verify_pw3(pin)?;
|
|
||||||
self.state.pw3 = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_admin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> {
|
|
||||||
pinpad_prompt();
|
|
||||||
|
|
||||||
self.state.opt.verify_pw3_pinpad()?;
|
|
||||||
self.state.pw3 = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ask the card if the user password has been successfully verified.
|
|
||||||
///
|
|
||||||
/// NOTE: on some cards this functionality seems broken.
|
|
||||||
pub fn check_user_verified(&mut self) -> Result<(), Error> {
|
|
||||||
self.state.opt.check_pw1_user()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ask the card if the admin password has been successfully verified.
|
|
||||||
///
|
|
||||||
/// NOTE: on some cards this functionality seems broken.
|
|
||||||
pub fn check_admin_verified(&mut self) -> Result<(), Error> {
|
|
||||||
self.state.opt.check_pw3()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn change_user_pin(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> {
|
|
||||||
self.state.opt.change_pw1(old, new)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn change_user_pin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> {
|
|
||||||
pinpad_prompt();
|
|
||||||
self.state.opt.change_pw1_pinpad()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_user_pin(&mut self, rst: &[u8], new: &[u8]) -> Result<(), Error> {
|
|
||||||
self.state.opt.reset_retry_counter_pw1(new, Some(rst))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn change_admin_pin(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> {
|
|
||||||
self.state.opt.change_pw3(old, new)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn change_admin_pin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> {
|
|
||||||
pinpad_prompt();
|
|
||||||
self.state.opt.change_pw3_pinpad()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a view of the card authenticated for "User" commands.
|
|
||||||
pub fn user_card<'b>(&'b mut self) -> Option<Card<User<'a, 'b>>> {
|
|
||||||
Some(Card::<User> {
|
|
||||||
state: User { tx: self },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a view of the card authenticated for Signing.
|
|
||||||
pub fn signing_card<'b>(&'b mut self) -> Option<Card<Sign<'a, 'b>>> {
|
|
||||||
Some(Card::<Sign> {
|
|
||||||
state: Sign { tx: self },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a view of the card authenticated for "Admin" commands.
|
|
||||||
pub fn admin_card<'b>(&'b mut self) -> Option<Card<Admin<'a, 'b>>> {
|
|
||||||
Some(Card::<Admin> {
|
|
||||||
state: Admin { tx: self },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- application data ---
|
|
||||||
|
|
||||||
pub fn application_identifier(&self) -> Result<ApplicationIdentifier, Error> {
|
|
||||||
self.state.ard.application_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn historical_bytes(&self) -> Result<HistoricalBytes, Error> {
|
|
||||||
self.state.ard.historical_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extended_length_information(&self) -> Result<Option<ExtendedLengthInfo>, Error> {
|
|
||||||
self.state.ard.extended_length_information()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn general_feature_management() -> Option<bool> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn discretionary_data_objects() {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extended_capabilities(&self) -> Result<ExtendedCapabilities, Error> {
|
|
||||||
self.state.ard.extended_capabilities()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn algorithm_attributes(&self, key_type: KeyType) -> Result<Algo, Error> {
|
|
||||||
self.state.ard.algorithm_attributes(key_type)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// PW status Bytes
|
|
||||||
pub fn pw_status_bytes(&self) -> Result<PWStatusBytes, Error> {
|
|
||||||
self.state.ard.pw_status_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fingerprints(&self) -> Result<KeySet<Fingerprint>, Error> {
|
|
||||||
self.state.ard.fingerprints()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ca_fingerprints(&self) -> Result<[Option<Fingerprint>; 3], Error> {
|
|
||||||
self.state.ard.ca_fingerprints()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn key_generation_times(&self) -> Result<KeySet<KeyGenerationTime>, Error> {
|
|
||||||
self.state.ard.key_generation_times()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn key_information(&self) -> Result<Option<KeyInformation>, Error> {
|
|
||||||
self.state.ard.key_information()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uif_signing(&self) -> Result<Option<UIF>, Error> {
|
|
||||||
self.state.ard.uif_pso_cds()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uif_decryption(&self) -> Result<Option<UIF>, Error> {
|
|
||||||
self.state.ard.uif_pso_dec()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uif_authentication(&self) -> Result<Option<UIF>, Error> {
|
|
||||||
self.state.ard.uif_pso_aut()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uif_attestation(&self) -> Result<Option<UIF>, Error> {
|
|
||||||
self.state.ard.uif_attestation()
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- optional private DOs (0101 - 0104) ---
|
|
||||||
|
|
||||||
// --- login data (5e) ---
|
|
||||||
|
|
||||||
// --- URL (5f50) ---
|
|
||||||
|
|
||||||
pub fn url(&mut self) -> Result<String, Error> {
|
|
||||||
Ok(String::from_utf8_lossy(&self.state.opt.url()?).to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- cardholder related data (65) ---
|
|
||||||
pub fn cardholder_related_data(&mut self) -> Result<CardholderRelatedData, Error> {
|
|
||||||
self.state.opt.cardholder_related_data()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unicode codepoints are a superset of iso-8859-1 characters
|
|
||||||
fn latin1_to_string(s: &[u8]) -> String {
|
|
||||||
s.iter().map(|&c| c as char).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get cardholder name as a String (this also normalizes the "<" and "<<" filler chars)
|
|
||||||
pub fn cardholder_name(&mut self) -> Result<Option<String>, Error> {
|
|
||||||
let crd = self.state.opt.cardholder_related_data()?;
|
|
||||||
if let Some(name) = crd.name() {
|
|
||||||
let name = Self::latin1_to_string(name);
|
|
||||||
|
|
||||||
// re-format name ("last<<first")
|
|
||||||
let name: Vec<_> = name.split("<<").collect();
|
|
||||||
let name = name.iter().cloned().rev().collect::<Vec<_>>().join(" ");
|
|
||||||
|
|
||||||
// replace item separators with spaces
|
|
||||||
let name = name.replace('<', " ");
|
|
||||||
|
|
||||||
Ok(Some(name))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- security support template (7a) ---
|
|
||||||
pub fn security_support_template(&mut self) -> Result<SecuritySupportTemplate, Error> {
|
|
||||||
self.state.opt.security_support_template()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// SELECT DATA ("select a DO in the current template").
|
|
||||||
pub fn select_data(&mut self, num: u8, tag: &[u8], yk_workaround: bool) -> Result<(), Error> {
|
|
||||||
self.state.opt.select_data(num, tag, yk_workaround)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get cardholder certificate.
|
|
||||||
///
|
|
||||||
/// Call select_data() before calling this fn to select a particular
|
|
||||||
/// certificate (if the card supports multiple certificates).
|
|
||||||
pub fn cardholder_certificate(&mut self) -> Result<Vec<u8>, Error> {
|
|
||||||
self.state.opt.cardholder_certificate()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// "GET NEXT DATA" for the DO cardholder certificate.
|
|
||||||
///
|
|
||||||
/// Cardholder certificate data for multiple slots can be read from the card by first calling
|
|
||||||
/// cardholder_certificate(), followed by up to two calls to next_cardholder_certificate().
|
|
||||||
pub fn next_cardholder_certificate(&mut self) -> Result<Vec<u8>, Error> {
|
|
||||||
self.state.opt.next_cardholder_certificate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DO "Algorithm Information" (0xFA)
|
|
||||||
pub fn algorithm_information(&mut self) -> Result<Option<AlgoInfo>, Error> {
|
|
||||||
// The DO "Algorithm Information" (Tag FA) shall be present if
|
|
||||||
// Algorithm attributes can be changed
|
|
||||||
let ec = self.extended_capabilities()?;
|
|
||||||
if !ec.algo_attrs_changeable() {
|
|
||||||
// Algorithm attributes can not be changed,
|
|
||||||
// list_supported_algo is not supported
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.state.opt.algorithm_information()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// "MANAGE SECURITY ENVIRONMENT"
|
|
||||||
/// Make `key_ref` usable for the operation normally done by the key designated by `for_operation`
|
|
||||||
pub fn manage_security_environment(
|
|
||||||
&mut self,
|
|
||||||
for_operation: KeyType,
|
|
||||||
key_ref: KeyType,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.state
|
|
||||||
.opt
|
|
||||||
.manage_security_environment(for_operation, key_ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
/// Get "Attestation Certificate (Yubico)"
|
|
||||||
pub fn attestation_certificate(&mut self) -> Result<Vec<u8>, Error> {
|
|
||||||
self.state.opt.attestation_certificate()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn attestation_key_fingerprint(&mut self) -> Result<Option<Fingerprint>, Error> {
|
|
||||||
self.state.ard.attestation_key_fingerprint()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn attestation_key_algorithm_attributes(&mut self) -> Result<Option<Algo>, Error> {
|
|
||||||
self.state.ard.attestation_key_algorithm_attributes()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn attestation_key_generation_time(&mut self) -> Result<Option<KeyGenerationTime>, Error> {
|
|
||||||
self.state.ard.attestation_key_generation_time()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Firmware Version, YubiKey specific (?)
|
|
||||||
pub fn firmware_version(&mut self) -> Result<Vec<u8>, Error> {
|
|
||||||
self.state.opt.firmware_version()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set "identity", Nitrokey Start specific (possible values: 0, 1, 2).
|
|
||||||
/// <https://docs.nitrokey.com/start/windows/multiple-identities.html>
|
|
||||||
///
|
|
||||||
/// A Nitrokey Start can present as 3 different virtual OpenPGP cards.
|
|
||||||
/// This command enables one of those virtual cards.
|
|
||||||
///
|
|
||||||
/// Each virtual card identity behaves like a separate, independent OpenPGP card.
|
|
||||||
pub fn set_identity(&mut self, id: u8) -> Result<(), Error> {
|
|
||||||
// FIXME: what is in the returned data - is it ever useful?
|
|
||||||
let _ = self.state.opt.set_identity(id)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
pub fn public_key(&mut self, key_type: KeyType) -> Result<PublicKeyMaterial, Error> {
|
|
||||||
self.state.opt.public_key(key_type)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
/// Delete all state on this OpenPGP card
|
|
||||||
pub fn factory_reset(&mut self) -> Result<(), Error> {
|
|
||||||
self.state.opt.factory_reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'app, 'open> Card<User<'app, 'open>> {
|
|
||||||
/// Helper fn to easily access underlying openpgp_card object
|
|
||||||
fn card(&mut self) -> &mut OpenPgpTransaction<'app> {
|
|
||||||
&mut self.state.tx.state.opt
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decryptor(
|
|
||||||
&mut self,
|
|
||||||
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
|
||||||
) -> Result<CardDecryptor<'_, 'app>, Error> {
|
|
||||||
let pk = crate::util::key_slot(self.state.tx, KeyType::Decryption)?
|
|
||||||
.expect("Couldn't get decryption pubkey from card");
|
|
||||||
|
|
||||||
Ok(CardDecryptor::with_pubkey(self.card(), pk, touch_prompt))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decryptor_from_public(
|
|
||||||
&mut self,
|
|
||||||
pubkey: PublicKey,
|
|
||||||
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
|
||||||
) -> CardDecryptor<'_, 'app> {
|
|
||||||
CardDecryptor::with_pubkey(self.card(), pubkey, touch_prompt)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn authenticator(
|
|
||||||
&mut self,
|
|
||||||
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
|
||||||
) -> Result<CardSigner<'_, 'app>, Error> {
|
|
||||||
let pk = crate::util::key_slot(self.state.tx, KeyType::Authentication)?
|
|
||||||
.expect("Couldn't get authentication pubkey from card");
|
|
||||||
|
|
||||||
Ok(CardSigner::with_pubkey_for_auth(
|
|
||||||
self.card(),
|
|
||||||
pk,
|
|
||||||
touch_prompt,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
pub fn authenticator_from_public(
|
|
||||||
&mut self,
|
|
||||||
pubkey: PublicKey,
|
|
||||||
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
|
||||||
) -> CardSigner<'_, 'app> {
|
|
||||||
CardSigner::with_pubkey_for_auth(self.card(), pubkey, touch_prompt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'app, 'open> Card<Sign<'app, 'open>> {
|
|
||||||
/// Helper fn to easily access underlying openpgp_card object
|
|
||||||
fn card(&mut self) -> &mut OpenPgpTransaction<'app> {
|
|
||||||
&mut self.state.tx.state.opt
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn signer(
|
|
||||||
&mut self,
|
|
||||||
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
|
||||||
) -> Result<CardSigner<'_, 'app>, Error> {
|
|
||||||
// FIXME: depending on the setting in "PW1 Status byte", only one
|
|
||||||
// signature can be made after verification for signing
|
|
||||||
|
|
||||||
let pk = crate::util::key_slot(self.state.tx, KeyType::Signing)?
|
|
||||||
.expect("Couldn't get signing pubkey from card");
|
|
||||||
|
|
||||||
Ok(CardSigner::with_pubkey(self.card(), pk, touch_prompt))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn signer_from_public(
|
|
||||||
&mut self,
|
|
||||||
pubkey: PublicKey,
|
|
||||||
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
|
||||||
) -> CardSigner<'_, 'app> {
|
|
||||||
// FIXME: depending on the setting in "PW1 Status byte", only one
|
|
||||||
// signature can be made after verification for signing
|
|
||||||
|
|
||||||
CardSigner::with_pubkey(self.card(), pubkey, touch_prompt)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate Attestation (Yubico)
|
|
||||||
pub fn generate_attestation(
|
|
||||||
&mut self,
|
|
||||||
key_type: KeyType,
|
|
||||||
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Touch is required if:
|
|
||||||
// - the card supports the feature
|
|
||||||
// - and the policy is set to a value other than 'Off'
|
|
||||||
if let Some(uif) = self.state.tx.state.ard.uif_attestation()? {
|
|
||||||
if uif.touch_policy().touch_required() {
|
|
||||||
(touch_prompt)();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.card().generate_attestation(key_type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'app, 'open> Card<Admin<'app, 'open>> {
|
|
||||||
pub fn as_open(&'_ mut self) -> &mut Card<Transaction<'app>> {
|
|
||||||
self.state.tx
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper fn to easily access underlying openpgp_card object
|
|
||||||
fn card(&mut self) -> &mut OpenPgpTransaction<'app> {
|
|
||||||
&mut self.state.tx.state.opt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Card<Admin<'_, '_>> {
|
|
||||||
pub fn set_name(&mut self, name: &str) -> Result<(), Error> {
|
|
||||||
// All chars must be in ASCII7
|
|
||||||
if name.chars().any(|c| !c.is_ascii()) {
|
|
||||||
return Err(Error::InternalError("Invalid char in name".into()));
|
|
||||||
};
|
|
||||||
|
|
||||||
// FIXME: encode spaces and do ordering
|
|
||||||
|
|
||||||
if name.len() >= 40 {
|
|
||||||
return Err(Error::InternalError("name too long".into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.card().set_name(name.as_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> {
|
|
||||||
if lang.len() > 8 {
|
|
||||||
return Err(Error::InternalError("lang too long".into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.card().set_lang(lang)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> {
|
|
||||||
self.card().set_sex(sex)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_url(&mut self, url: &str) -> Result<(), Error> {
|
|
||||||
if url.chars().any(|c| !c.is_ascii()) {
|
|
||||||
return Err(Error::InternalError("Invalid char in url".into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for max len
|
|
||||||
let ec = self.state.tx.extended_capabilities()?;
|
|
||||||
|
|
||||||
if ec.max_len_special_do() == None || url.len() <= ec.max_len_special_do().unwrap() as usize
|
|
||||||
{
|
|
||||||
// If we don't know the max length for URL ("special DO"),
|
|
||||||
// or if it's within the acceptable length:
|
|
||||||
// send the url update to the card.
|
|
||||||
|
|
||||||
self.card().set_url(url.as_bytes())
|
|
||||||
} else {
|
|
||||||
Err(Error::InternalError("URL too long".into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_uif(&mut self, key: KeyType, policy: TouchPolicy) -> Result<(), Error> {
|
|
||||||
let uif = match key {
|
|
||||||
KeyType::Signing => self.state.tx.state.ard.uif_pso_cds()?,
|
|
||||||
KeyType::Decryption => self.state.tx.state.ard.uif_pso_dec()?,
|
|
||||||
KeyType::Authentication => self.state.tx.state.ard.uif_pso_aut()?,
|
|
||||||
KeyType::Attestation => self.state.tx.state.ard.uif_attestation()?,
|
|
||||||
_ => unimplemented!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(mut uif) = uif {
|
|
||||||
uif.set_touch_policy(policy);
|
|
||||||
|
|
||||||
match key {
|
|
||||||
KeyType::Signing => self.card().set_uif_pso_cds(&uif)?,
|
|
||||||
KeyType::Decryption => self.card().set_uif_pso_dec(&uif)?,
|
|
||||||
KeyType::Authentication => self.card().set_uif_pso_aut(&uif)?,
|
|
||||||
KeyType::Attestation => self.card().set_uif_attestation(&uif)?,
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::UnsupportedFeature(
|
|
||||||
"User Interaction Flag not available".into(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_resetting_code(&mut self, pin: &[u8]) -> Result<(), Error> {
|
|
||||||
self.card().set_resetting_code(pin)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_pso_enc_dec_key(&mut self, key: &[u8]) -> Result<(), Error> {
|
|
||||||
self.card().set_pso_enc_dec_key(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_user_pin(&mut self, new: &[u8]) -> Result<(), Error> {
|
|
||||||
self.card().reset_retry_counter_pw1(new, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Upload a ValidErasedKeyAmalgamation to the card as a specific KeyType.
|
|
||||||
///
|
|
||||||
/// (The caller needs to make sure that `vka` is suitable as `key_type`)
|
|
||||||
pub fn upload_key(
|
|
||||||
&mut self,
|
|
||||||
vka: ValidErasedKeyAmalgamation<SecretParts>,
|
|
||||||
key_type: KeyType,
|
|
||||||
password: Option<String>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let key = vka_as_uploadable_key(vka, password);
|
|
||||||
self.card().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.card().generate_key_simple(Self::ptf, key_type, algo),
|
|
||||||
None => self.card().generate_key(Self::ptf, key_type, None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,17 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
|
// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
//! A higher-level wrapper around the openpgp-card crate.
|
//! This crate offers ergonomic abstractions to use
|
||||||
//! It uses sequoia_openpgp for OpenPGP operations.
|
//! [OpenPGP cards](https://en.wikipedia.org/wiki/OpenPGP_card).
|
||||||
|
//! The central abstraction is the [Card] type, which offers access to all card operations.
|
||||||
|
//!
|
||||||
|
//! A [Card] object is always in one of the possible [State]s. The [State] determines which
|
||||||
|
//! operations can be performed on the card.
|
||||||
|
//!
|
||||||
|
//! This crate is a convenient higher-level wrapper around the
|
||||||
|
//! [openpgp-card](https://crates.io/crates/openpgp-card) crate (which exposes low level access
|
||||||
|
//! to OpenPGP card functionality).
|
||||||
|
//! [sequoia-openpgp](https://crates.io/crates/sequoia-openpgp) is used to perform OpenPGP operations.
|
||||||
//!
|
//!
|
||||||
//! # Backends
|
//! # Backends
|
||||||
//!
|
//!
|
||||||
|
@ -13,7 +22,7 @@
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use openpgp_card_pcsc::PcscBackend;
|
//! use openpgp_card_pcsc::PcscBackend;
|
||||||
//! use openpgp_card_sequoia::card::{Card, Open};
|
//! use openpgp_card_sequoia::{Card, state::Open};
|
||||||
//!
|
//!
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! for backend in PcscBackend::cards(None)? {
|
//! for backend in PcscBackend::cards(None)? {
|
||||||
|
@ -30,10 +39,10 @@
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use openpgp_card_pcsc::PcscBackend;
|
//! use openpgp_card_pcsc::PcscBackend;
|
||||||
//! use openpgp_card_sequoia::card::{Card, Open};
|
//! use openpgp_card_sequoia::{Card, state::Open};
|
||||||
//!
|
//!
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! let backend = PcscBackend::open_by_ident("abcd:12345678", None)?;
|
//! let backend = PcscBackend::open_by_ident("abcd:01234567", None)?;
|
||||||
//! let mut card: Card<Open> = backend.into();
|
//! let mut card: Card<Open> = backend.into();
|
||||||
//! let mut transaction = card.transaction()?;
|
//! let mut transaction = card.transaction()?;
|
||||||
//! # Ok(())
|
//! # Ok(())
|
||||||
|
@ -46,17 +55,16 @@
|
||||||
//!
|
//!
|
||||||
//! To use a card for decryption, it needs to be opened, user authorization
|
//! To use a card for decryption, it needs to be opened, user authorization
|
||||||
//! needs to be available. A `sequoia_openpgp::crypto::Decryptor`
|
//! needs to be available. A `sequoia_openpgp::crypto::Decryptor`
|
||||||
//! implementation can then be obtained by providing a Cert (public key)
|
//! implementation can then be obtained:
|
||||||
//! that corresponds to the private encryption key on the card:
|
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use openpgp_card_pcsc::PcscBackend;
|
//! use openpgp_card_pcsc::PcscBackend;
|
||||||
//! use openpgp_card_sequoia::card::{Card, Open};
|
//! use openpgp_card_sequoia::{Card, state::Open};
|
||||||
//!
|
//!
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! // Open card via PCSC
|
//! // Open card via PCSC
|
||||||
//! use sequoia_openpgp::policy::StandardPolicy;
|
//! use sequoia_openpgp::policy::StandardPolicy;
|
||||||
//! let backend = PcscBackend::open_by_ident("abcd:12345678", None)?;
|
//! let backend = PcscBackend::open_by_ident("abcd:01234567", None)?;
|
||||||
//! let mut card: Card<Open> = backend.into();
|
//! let mut card: Card<Open> = backend.into();
|
||||||
//! let mut transaction = card.transaction()?;
|
//! let mut transaction = card.transaction()?;
|
||||||
//!
|
//!
|
||||||
|
@ -78,21 +86,20 @@
|
||||||
//!
|
//!
|
||||||
//! To use a card for signing, it needs to be opened, signing authorization
|
//! To use a card for signing, it needs to be opened, signing authorization
|
||||||
//! needs to be available. A `sequoia_openpgp::crypto::Signer`
|
//! needs to be available. A `sequoia_openpgp::crypto::Signer`
|
||||||
//! implementation can then be obtained by providing a Cert (public key)
|
//! implementation can then be obtained.
|
||||||
//! that corresponds to the private signing key on the card.
|
|
||||||
//!
|
//!
|
||||||
//! (Note that by default, an OpenPGP Card will only allow one signing
|
//! (Note that by default, some OpenPGP Cards will only allow one signing
|
||||||
//! operation to be performed after the password has been presented for
|
//! operation to be performed after the password has been presented for
|
||||||
//! signing. Depending on the card's configuration you need to present the
|
//! signing. Depending on the card's configuration you need to present the
|
||||||
//! user password before each signing operation!)
|
//! user password before each signing operation!)
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use openpgp_card_pcsc::PcscBackend;
|
//! use openpgp_card_pcsc::PcscBackend;
|
||||||
//! use openpgp_card_sequoia::card::{Card, Open};
|
//! use openpgp_card_sequoia::{Card, state::Open};
|
||||||
//!
|
//!
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! // Open card via PCSC
|
//! // Open card via PCSC
|
||||||
//! let backend = PcscBackend::open_by_ident("abcd:12345678", None)?;
|
//! let backend = PcscBackend::open_by_ident("abcd:01234567", None)?;
|
||||||
//! let mut card: Card<Open> = backend.into();
|
//! let mut card: Card<Open> = backend.into();
|
||||||
//! let mut transaction = card.transaction()?;
|
//! let mut transaction = card.transaction()?;
|
||||||
//!
|
//!
|
||||||
|
@ -114,11 +121,11 @@
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use openpgp_card_pcsc::PcscBackend;
|
//! use openpgp_card_pcsc::PcscBackend;
|
||||||
//! use openpgp_card_sequoia::card::{Card, Open};
|
//! use openpgp_card_sequoia::{Card, state::Open};
|
||||||
//!
|
//!
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! // Open card via PCSC
|
//! // Open card via PCSC
|
||||||
//! let backend = PcscBackend::open_by_ident("abcd:12345678", None)?;
|
//! let backend = PcscBackend::open_by_ident("abcd:01234567", None)?;
|
||||||
//! let mut card: Card<Open> = backend.into();
|
//! let mut card: Card<Open> = backend.into();
|
||||||
//! let mut transaction = card.transaction()?;
|
//! let mut transaction = card.transaction()?;
|
||||||
//!
|
//!
|
||||||
|
@ -127,23 +134,733 @@
|
||||||
//! let mut admin = transaction.admin_card().expect("This should not fail");
|
//! let mut admin = transaction.admin_card().expect("This should not fail");
|
||||||
//!
|
//!
|
||||||
//! // Set the Name and URL fields on the card
|
//! // Set the Name and URL fields on the card
|
||||||
//! admin.set_name("Bar<<Foo")?;
|
//! admin.set_name("Alice Adams")?;
|
||||||
//! admin.set_url("https://example.org/openpgp.asc")?;
|
//! admin.set_url("https://example.org/openpgp.asc")?;
|
||||||
//!
|
//!
|
||||||
//! # Ok(())
|
//! # Ok(())
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
use crate::decryptor::CardDecryptor;
|
||||||
|
use crate::signer::CardSigner;
|
||||||
|
use crate::state::{Admin, Open, Sign, State, Transaction, User};
|
||||||
|
use crate::util::{
|
||||||
|
public_key_material_and_fp_to_key, public_to_fingerprint, vka_as_uploadable_key,
|
||||||
|
};
|
||||||
use openpgp::packet::{key, Key};
|
use openpgp::packet::{key, Key};
|
||||||
|
use openpgp_card::algorithm::{Algo, AlgoInfo, AlgoSimple};
|
||||||
|
use openpgp_card::card_do::{
|
||||||
|
ApplicationIdentifier, CardholderRelatedData, ExtendedCapabilities, ExtendedLengthInfo,
|
||||||
|
Fingerprint, HistoricalBytes, KeyGenerationTime, KeyInformation, Lang, PWStatusBytes,
|
||||||
|
SecuritySupportTemplate, Sex, TouchPolicy, UIF,
|
||||||
|
};
|
||||||
|
use openpgp_card::crypto_data::PublicKeyMaterial;
|
||||||
|
use openpgp_card::{CardBackend, Error, KeySet, KeyType, OpenPgp, OpenPgpTransaction};
|
||||||
use sequoia_openpgp as openpgp;
|
use sequoia_openpgp as openpgp;
|
||||||
|
use sequoia_openpgp::cert::prelude::ValidErasedKeyAmalgamation;
|
||||||
|
use sequoia_openpgp::packet::key::SecretParts;
|
||||||
|
use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm};
|
||||||
|
|
||||||
pub mod card;
|
|
||||||
mod decryptor;
|
mod decryptor;
|
||||||
mod privkey;
|
mod privkey;
|
||||||
mod signer;
|
mod signer;
|
||||||
pub mod sq_util;
|
pub mod sq_util;
|
||||||
|
pub mod state;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
/// Shorthand for Sequoia public key data (a single public (sub)key)
|
/// Shorthand for Sequoia public key data (a single public (sub)key)
|
||||||
pub type PublicKey = Key<key::PublicParts, key::UnspecifiedRole>;
|
pub type PublicKey = Key<key::PublicParts, key::UnspecifiedRole>;
|
||||||
|
|
||||||
|
/// Representation of an OpenPGP card.
|
||||||
|
///
|
||||||
|
/// A card transitions between `State`s by starting a transaction (that groups together a number
|
||||||
|
/// of operations into an atomic sequence) and via PIN presentation.
|
||||||
|
///
|
||||||
|
/// Depending on the `State` of the card, and the access privileges that are associated with that
|
||||||
|
/// state, different operations can be performed. In many cases, client software will want to
|
||||||
|
/// transition between states while performing one activity for the user.
|
||||||
|
pub struct Card<S>
|
||||||
|
where
|
||||||
|
S: State,
|
||||||
|
{
|
||||||
|
state: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<B> for Card<Open>
|
||||||
|
where
|
||||||
|
B: Into<Box<dyn CardBackend + Send + Sync>>,
|
||||||
|
{
|
||||||
|
fn from(backend: B) -> Self {
|
||||||
|
let pgp = OpenPgp::new(backend.into());
|
||||||
|
|
||||||
|
Card::<Open> {
|
||||||
|
state: Open { pgp },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Card<Open> {
|
||||||
|
pub fn transaction(&mut self) -> Result<Card<Transaction>, Error> {
|
||||||
|
let opt = self.state.pgp.transaction()?;
|
||||||
|
|
||||||
|
Card::<Transaction>::new(opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Card<Transaction<'a>> {
|
||||||
|
/// Do not use!
|
||||||
|
///
|
||||||
|
/// FIXME: this interface is currently used in `card-functionality`, for testing.
|
||||||
|
/// It will be removed.
|
||||||
|
pub fn new(mut opt: OpenPgpTransaction<'a>) -> Result<Self, Error> {
|
||||||
|
let ard = opt.application_related_data()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
state: Transaction {
|
||||||
|
opt,
|
||||||
|
ard,
|
||||||
|
pw1: false,
|
||||||
|
pw1_sign: false,
|
||||||
|
pw3: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replace cached "application related data" in this instance of Open
|
||||||
|
/// with the current data on the card.
|
||||||
|
///
|
||||||
|
/// This is needed e.g. after importing or generating keys on a card, to
|
||||||
|
/// see these changes reflected in `self.ard`.
|
||||||
|
pub fn reload_ard(&mut self) -> Result<(), Error> {
|
||||||
|
// FIXME: this should be implemented internally, transparent to users
|
||||||
|
self.state.ard = self.state.opt.application_related_data()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn feature_pinpad_verify(&mut self) -> bool {
|
||||||
|
self.state.opt.feature_pinpad_verify()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn feature_pinpad_modify(&mut self) -> bool {
|
||||||
|
self.state.opt.feature_pinpad_modify()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_user(&mut self, pin: &[u8]) -> Result<(), Error> {
|
||||||
|
self.state.opt.verify_pw1_user(pin)?;
|
||||||
|
self.state.pw1 = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_user_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> {
|
||||||
|
pinpad_prompt();
|
||||||
|
|
||||||
|
self.state.opt.verify_pw1_user_pinpad()?;
|
||||||
|
self.state.pw1 = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_user_for_signing(&mut self, pin: &[u8]) -> Result<(), Error> {
|
||||||
|
self.state.opt.verify_pw1_sign(pin)?;
|
||||||
|
|
||||||
|
// FIXME: depending on card mode, pw1_sign is only usable once
|
||||||
|
|
||||||
|
self.state.pw1_sign = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_user_for_signing_pinpad(
|
||||||
|
&mut self,
|
||||||
|
pinpad_prompt: &dyn Fn(),
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
pinpad_prompt();
|
||||||
|
|
||||||
|
self.state.opt.verify_pw1_sign_pinpad()?;
|
||||||
|
|
||||||
|
// FIXME: depending on card mode, pw1_sign is only usable once
|
||||||
|
|
||||||
|
self.state.pw1_sign = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_admin(&mut self, pin: &[u8]) -> Result<(), Error> {
|
||||||
|
self.state.opt.verify_pw3(pin)?;
|
||||||
|
self.state.pw3 = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_admin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> {
|
||||||
|
pinpad_prompt();
|
||||||
|
|
||||||
|
self.state.opt.verify_pw3_pinpad()?;
|
||||||
|
self.state.pw3 = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ask the card if the user password has been successfully verified.
|
||||||
|
///
|
||||||
|
/// NOTE: on some cards this functionality seems broken.
|
||||||
|
pub fn check_user_verified(&mut self) -> Result<(), Error> {
|
||||||
|
self.state.opt.check_pw1_user()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ask the card if the admin password has been successfully verified.
|
||||||
|
///
|
||||||
|
/// NOTE: on some cards this functionality seems broken.
|
||||||
|
pub fn check_admin_verified(&mut self) -> Result<(), Error> {
|
||||||
|
self.state.opt.check_pw3()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_user_pin(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> {
|
||||||
|
self.state.opt.change_pw1(old, new)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_user_pin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> {
|
||||||
|
pinpad_prompt();
|
||||||
|
self.state.opt.change_pw1_pinpad()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_user_pin(&mut self, rst: &[u8], new: &[u8]) -> Result<(), Error> {
|
||||||
|
self.state.opt.reset_retry_counter_pw1(new, Some(rst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_admin_pin(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> {
|
||||||
|
self.state.opt.change_pw3(old, new)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_admin_pin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> {
|
||||||
|
pinpad_prompt();
|
||||||
|
self.state.opt.change_pw3_pinpad()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a view of the card authenticated for "User" commands.
|
||||||
|
pub fn user_card<'b>(&'b mut self) -> Option<Card<User<'a, 'b>>> {
|
||||||
|
Some(Card::<User> {
|
||||||
|
state: User { tx: self },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a view of the card authenticated for Signing.
|
||||||
|
pub fn signing_card<'b>(&'b mut self) -> Option<Card<Sign<'a, 'b>>> {
|
||||||
|
Some(Card::<Sign> {
|
||||||
|
state: Sign { tx: self },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a view of the card authenticated for "Admin" commands.
|
||||||
|
pub fn admin_card<'b>(&'b mut self) -> Option<Card<Admin<'a, 'b>>> {
|
||||||
|
Some(Card::<Admin> {
|
||||||
|
state: Admin { tx: self },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- application data ---
|
||||||
|
|
||||||
|
pub fn application_identifier(&self) -> Result<ApplicationIdentifier, Error> {
|
||||||
|
self.state.ard.application_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn historical_bytes(&self) -> Result<HistoricalBytes, Error> {
|
||||||
|
self.state.ard.historical_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extended_length_information(&self) -> Result<Option<ExtendedLengthInfo>, Error> {
|
||||||
|
self.state.ard.extended_length_information()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn general_feature_management() -> Option<bool> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn discretionary_data_objects() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extended_capabilities(&self) -> Result<ExtendedCapabilities, Error> {
|
||||||
|
self.state.ard.extended_capabilities()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn algorithm_attributes(&self, key_type: KeyType) -> Result<Algo, Error> {
|
||||||
|
self.state.ard.algorithm_attributes(key_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PW status Bytes
|
||||||
|
pub fn pw_status_bytes(&self) -> Result<PWStatusBytes, Error> {
|
||||||
|
self.state.ard.pw_status_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fingerprints(&self) -> Result<KeySet<Fingerprint>, Error> {
|
||||||
|
self.state.ard.fingerprints()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ca_fingerprints(&self) -> Result<[Option<Fingerprint>; 3], Error> {
|
||||||
|
self.state.ard.ca_fingerprints()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key_generation_times(&self) -> Result<KeySet<KeyGenerationTime>, Error> {
|
||||||
|
self.state.ard.key_generation_times()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key_information(&self) -> Result<Option<KeyInformation>, Error> {
|
||||||
|
self.state.ard.key_information()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uif_signing(&self) -> Result<Option<UIF>, Error> {
|
||||||
|
self.state.ard.uif_pso_cds()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uif_decryption(&self) -> Result<Option<UIF>, Error> {
|
||||||
|
self.state.ard.uif_pso_dec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uif_authentication(&self) -> Result<Option<UIF>, Error> {
|
||||||
|
self.state.ard.uif_pso_aut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uif_attestation(&self) -> Result<Option<UIF>, Error> {
|
||||||
|
self.state.ard.uif_attestation()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- optional private DOs (0101 - 0104) ---
|
||||||
|
|
||||||
|
// --- login data (5e) ---
|
||||||
|
|
||||||
|
// --- URL (5f50) ---
|
||||||
|
|
||||||
|
pub fn url(&mut self) -> Result<String, Error> {
|
||||||
|
Ok(String::from_utf8_lossy(&self.state.opt.url()?).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- cardholder related data (65) ---
|
||||||
|
pub fn cardholder_related_data(&mut self) -> Result<CardholderRelatedData, Error> {
|
||||||
|
self.state.opt.cardholder_related_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unicode codepoints are a superset of iso-8859-1 characters
|
||||||
|
fn latin1_to_string(s: &[u8]) -> String {
|
||||||
|
s.iter().map(|&c| c as char).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get cardholder name as a String (this also normalizes the "<" and "<<" filler chars)
|
||||||
|
pub fn cardholder_name(&mut self) -> Result<Option<String>, Error> {
|
||||||
|
let crd = self.state.opt.cardholder_related_data()?;
|
||||||
|
if let Some(name) = crd.name() {
|
||||||
|
let name = Self::latin1_to_string(name);
|
||||||
|
|
||||||
|
// re-format name ("last<<first")
|
||||||
|
let name: Vec<_> = name.split("<<").collect();
|
||||||
|
let name = name.iter().cloned().rev().collect::<Vec<_>>().join(" ");
|
||||||
|
|
||||||
|
// replace item separators with spaces
|
||||||
|
let name = name.replace('<', " ");
|
||||||
|
|
||||||
|
Ok(Some(name))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- security support template (7a) ---
|
||||||
|
pub fn security_support_template(&mut self) -> Result<SecuritySupportTemplate, Error> {
|
||||||
|
self.state.opt.security_support_template()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SELECT DATA ("select a DO in the current template").
|
||||||
|
pub fn select_data(&mut self, num: u8, tag: &[u8], yk_workaround: bool) -> Result<(), Error> {
|
||||||
|
self.state.opt.select_data(num, tag, yk_workaround)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get cardholder certificate.
|
||||||
|
///
|
||||||
|
/// Call select_data() before calling this fn to select a particular
|
||||||
|
/// certificate (if the card supports multiple certificates).
|
||||||
|
pub fn cardholder_certificate(&mut self) -> Result<Vec<u8>, Error> {
|
||||||
|
self.state.opt.cardholder_certificate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "GET NEXT DATA" for the DO cardholder certificate.
|
||||||
|
///
|
||||||
|
/// Cardholder certificate data for multiple slots can be read from the card by first calling
|
||||||
|
/// cardholder_certificate(), followed by up to two calls to next_cardholder_certificate().
|
||||||
|
pub fn next_cardholder_certificate(&mut self) -> Result<Vec<u8>, Error> {
|
||||||
|
self.state.opt.next_cardholder_certificate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO "Algorithm Information" (0xFA)
|
||||||
|
pub fn algorithm_information(&mut self) -> Result<Option<AlgoInfo>, Error> {
|
||||||
|
// The DO "Algorithm Information" (Tag FA) shall be present if
|
||||||
|
// Algorithm attributes can be changed
|
||||||
|
let ec = self.extended_capabilities()?;
|
||||||
|
if !ec.algo_attrs_changeable() {
|
||||||
|
// Algorithm attributes can not be changed,
|
||||||
|
// list_supported_algo is not supported
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state.opt.algorithm_information()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "MANAGE SECURITY ENVIRONMENT"
|
||||||
|
/// Make `key_ref` usable for the operation normally done by the key designated by `for_operation`
|
||||||
|
pub fn manage_security_environment(
|
||||||
|
&mut self,
|
||||||
|
for_operation: KeyType,
|
||||||
|
key_ref: KeyType,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.state
|
||||||
|
.opt
|
||||||
|
.manage_security_environment(for_operation, key_ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
|
||||||
|
/// Get "Attestation Certificate (Yubico)"
|
||||||
|
pub fn attestation_certificate(&mut self) -> Result<Vec<u8>, Error> {
|
||||||
|
self.state.opt.attestation_certificate()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attestation_key_fingerprint(&mut self) -> Result<Option<Fingerprint>, Error> {
|
||||||
|
self.state.ard.attestation_key_fingerprint()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attestation_key_algorithm_attributes(&mut self) -> Result<Option<Algo>, Error> {
|
||||||
|
self.state.ard.attestation_key_algorithm_attributes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attestation_key_generation_time(&mut self) -> Result<Option<KeyGenerationTime>, Error> {
|
||||||
|
self.state.ard.attestation_key_generation_time()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Firmware Version, YubiKey specific (?)
|
||||||
|
pub fn firmware_version(&mut self) -> Result<Vec<u8>, Error> {
|
||||||
|
self.state.opt.firmware_version()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set "identity", Nitrokey Start specific (possible values: 0, 1, 2).
|
||||||
|
/// <https://docs.nitrokey.com/start/windows/multiple-identities.html>
|
||||||
|
///
|
||||||
|
/// A Nitrokey Start can present as 3 different virtual OpenPGP cards.
|
||||||
|
/// This command enables one of those virtual cards.
|
||||||
|
///
|
||||||
|
/// Each virtual card identity behaves like a separate, independent OpenPGP card.
|
||||||
|
pub fn set_identity(&mut self, id: u8) -> Result<(), Error> {
|
||||||
|
// FIXME: what is in the returned data - is it ever useful?
|
||||||
|
let _ = self.state.opt.set_identity(id)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
|
||||||
|
pub fn public_key_material(&mut self, key_type: KeyType) -> Result<PublicKeyMaterial, Error> {
|
||||||
|
self.state.opt.public_key(key_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
|
||||||
|
/// Delete all state on this OpenPGP card
|
||||||
|
pub fn factory_reset(&mut self) -> Result<(), Error> {
|
||||||
|
self.state.opt.factory_reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- higher level abstractions
|
||||||
|
|
||||||
|
/// Get PublicKey representation for a key slot on the card
|
||||||
|
pub fn public_key(&mut self, kt: KeyType) -> Result<Option<PublicKey>, Error> {
|
||||||
|
// FIXME: only read these once, if multiple subkeys are retrieved from the card
|
||||||
|
let times = self.key_generation_times()?;
|
||||||
|
let fps = self.fingerprints()?;
|
||||||
|
|
||||||
|
match kt {
|
||||||
|
KeyType::Signing => {
|
||||||
|
if let Ok(pkm) = self.public_key_material(KeyType::Signing) {
|
||||||
|
if let Some(ts) = times.signature() {
|
||||||
|
return Ok(Some(public_key_material_and_fp_to_key(
|
||||||
|
&pkm,
|
||||||
|
KeyType::Signing,
|
||||||
|
ts,
|
||||||
|
fps.signature().expect("Signature fingerprint is unset"),
|
||||||
|
)?));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
KeyType::Decryption => {
|
||||||
|
if let Ok(pkm) = self.public_key_material(KeyType::Decryption) {
|
||||||
|
if let Some(ts) = times.decryption() {
|
||||||
|
return Ok(Some(public_key_material_and_fp_to_key(
|
||||||
|
&pkm,
|
||||||
|
KeyType::Decryption,
|
||||||
|
ts,
|
||||||
|
fps.decryption().expect("Decryption fingerprint is unset"),
|
||||||
|
)?));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
KeyType::Authentication => {
|
||||||
|
if let Ok(pkm) = self.public_key_material(KeyType::Authentication) {
|
||||||
|
if let Some(ts) = times.authentication() {
|
||||||
|
return Ok(Some(public_key_material_and_fp_to_key(
|
||||||
|
&pkm,
|
||||||
|
KeyType::Authentication,
|
||||||
|
ts,
|
||||||
|
fps.authentication()
|
||||||
|
.expect("Authentication fingerprint is unset"),
|
||||||
|
)?));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'app, 'open> Card<User<'app, 'open>> {
|
||||||
|
/// Helper fn to easily access underlying openpgp_card object
|
||||||
|
fn card(&mut self) -> &mut OpenPgpTransaction<'app> {
|
||||||
|
&mut self.state.tx.state.opt
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decryptor(
|
||||||
|
&mut self,
|
||||||
|
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
||||||
|
) -> Result<CardDecryptor<'_, 'app>, Error> {
|
||||||
|
let pk = self
|
||||||
|
.state
|
||||||
|
.tx
|
||||||
|
.public_key(KeyType::Decryption)?
|
||||||
|
.expect("Couldn't get decryption pubkey from card");
|
||||||
|
|
||||||
|
Ok(CardDecryptor::with_pubkey(self.card(), pk, touch_prompt))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decryptor_from_public(
|
||||||
|
&mut self,
|
||||||
|
pubkey: PublicKey,
|
||||||
|
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
||||||
|
) -> CardDecryptor<'_, 'app> {
|
||||||
|
CardDecryptor::with_pubkey(self.card(), pubkey, touch_prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn authenticator(
|
||||||
|
&mut self,
|
||||||
|
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
||||||
|
) -> Result<CardSigner<'_, 'app>, Error> {
|
||||||
|
let pk = self
|
||||||
|
.state
|
||||||
|
.tx
|
||||||
|
.public_key(KeyType::Authentication)?
|
||||||
|
.expect("Couldn't get authentication pubkey from card");
|
||||||
|
|
||||||
|
Ok(CardSigner::with_pubkey_for_auth(
|
||||||
|
self.card(),
|
||||||
|
pk,
|
||||||
|
touch_prompt,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
pub fn authenticator_from_public(
|
||||||
|
&mut self,
|
||||||
|
pubkey: PublicKey,
|
||||||
|
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
||||||
|
) -> CardSigner<'_, 'app> {
|
||||||
|
CardSigner::with_pubkey_for_auth(self.card(), pubkey, touch_prompt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'app, 'open> Card<Sign<'app, 'open>> {
|
||||||
|
/// Helper fn to easily access underlying openpgp_card object
|
||||||
|
fn card(&mut self) -> &mut OpenPgpTransaction<'app> {
|
||||||
|
&mut self.state.tx.state.opt
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signer(
|
||||||
|
&mut self,
|
||||||
|
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
||||||
|
) -> Result<CardSigner<'_, 'app>, Error> {
|
||||||
|
// FIXME: depending on the setting in "PW1 Status byte", only one
|
||||||
|
// signature can be made after verification for signing
|
||||||
|
|
||||||
|
let pk = self
|
||||||
|
.state
|
||||||
|
.tx
|
||||||
|
.public_key(KeyType::Signing)?
|
||||||
|
.expect("Couldn't get signing pubkey from card");
|
||||||
|
|
||||||
|
Ok(CardSigner::with_pubkey(self.card(), pk, touch_prompt))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signer_from_public(
|
||||||
|
&mut self,
|
||||||
|
pubkey: PublicKey,
|
||||||
|
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
||||||
|
) -> CardSigner<'_, 'app> {
|
||||||
|
// FIXME: depending on the setting in "PW1 Status byte", only one
|
||||||
|
// signature can be made after verification for signing
|
||||||
|
|
||||||
|
CardSigner::with_pubkey(self.card(), pubkey, touch_prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate Attestation (Yubico)
|
||||||
|
pub fn generate_attestation(
|
||||||
|
&mut self,
|
||||||
|
key_type: KeyType,
|
||||||
|
touch_prompt: &'open (dyn Fn() + Send + Sync),
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Touch is required if:
|
||||||
|
// - the card supports the feature
|
||||||
|
// - and the policy is set to a value other than 'Off'
|
||||||
|
if let Some(uif) = self.state.tx.state.ard.uif_attestation()? {
|
||||||
|
if uif.touch_policy().touch_required() {
|
||||||
|
(touch_prompt)();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.card().generate_attestation(key_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'app, 'open> Card<Admin<'app, 'open>> {
|
||||||
|
pub fn as_open(&'_ mut self) -> &mut Card<Transaction<'app>> {
|
||||||
|
self.state.tx
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper fn to easily access underlying openpgp_card object
|
||||||
|
fn card(&mut self) -> &mut OpenPgpTransaction<'app> {
|
||||||
|
&mut self.state.tx.state.opt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Card<Admin<'_, '_>> {
|
||||||
|
pub fn set_name(&mut self, name: &str) -> Result<(), Error> {
|
||||||
|
// All chars must be in ASCII7
|
||||||
|
if name.chars().any(|c| !c.is_ascii()) {
|
||||||
|
return Err(Error::InternalError("Invalid char in name".into()));
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME: encode spaces and do ordering
|
||||||
|
|
||||||
|
if name.len() >= 40 {
|
||||||
|
return Err(Error::InternalError("name too long".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.card().set_name(name.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> {
|
||||||
|
if lang.len() > 8 {
|
||||||
|
return Err(Error::InternalError("lang too long".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.card().set_lang(lang)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> {
|
||||||
|
self.card().set_sex(sex)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_url(&mut self, url: &str) -> Result<(), Error> {
|
||||||
|
if url.chars().any(|c| !c.is_ascii()) {
|
||||||
|
return Err(Error::InternalError("Invalid char in url".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for max len
|
||||||
|
let ec = self.state.tx.extended_capabilities()?;
|
||||||
|
|
||||||
|
if ec.max_len_special_do() == None || url.len() <= ec.max_len_special_do().unwrap() as usize
|
||||||
|
{
|
||||||
|
// If we don't know the max length for URL ("special DO"),
|
||||||
|
// or if it's within the acceptable length:
|
||||||
|
// send the url update to the card.
|
||||||
|
|
||||||
|
self.card().set_url(url.as_bytes())
|
||||||
|
} else {
|
||||||
|
Err(Error::InternalError("URL too long".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_uif(&mut self, key: KeyType, policy: TouchPolicy) -> Result<(), Error> {
|
||||||
|
let uif = match key {
|
||||||
|
KeyType::Signing => self.state.tx.state.ard.uif_pso_cds()?,
|
||||||
|
KeyType::Decryption => self.state.tx.state.ard.uif_pso_dec()?,
|
||||||
|
KeyType::Authentication => self.state.tx.state.ard.uif_pso_aut()?,
|
||||||
|
KeyType::Attestation => self.state.tx.state.ard.uif_attestation()?,
|
||||||
|
_ => unimplemented!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(mut uif) = uif {
|
||||||
|
uif.set_touch_policy(policy);
|
||||||
|
|
||||||
|
match key {
|
||||||
|
KeyType::Signing => self.card().set_uif_pso_cds(&uif)?,
|
||||||
|
KeyType::Decryption => self.card().set_uif_pso_dec(&uif)?,
|
||||||
|
KeyType::Authentication => self.card().set_uif_pso_aut(&uif)?,
|
||||||
|
KeyType::Attestation => self.card().set_uif_attestation(&uif)?,
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::UnsupportedFeature(
|
||||||
|
"User Interaction Flag not available".into(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_resetting_code(&mut self, pin: &[u8]) -> Result<(), Error> {
|
||||||
|
self.card().set_resetting_code(pin)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pso_enc_dec_key(&mut self, key: &[u8]) -> Result<(), Error> {
|
||||||
|
self.card().set_pso_enc_dec_key(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_user_pin(&mut self, new: &[u8]) -> Result<(), Error> {
|
||||||
|
self.card().reset_retry_counter_pw1(new, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upload a ValidErasedKeyAmalgamation to the card as a specific KeyType.
|
||||||
|
///
|
||||||
|
/// (The caller needs to make sure that `vka` is suitable as `key_type`)
|
||||||
|
pub fn upload_key(
|
||||||
|
&mut self,
|
||||||
|
vka: ValidErasedKeyAmalgamation<SecretParts>,
|
||||||
|
key_type: KeyType,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let key = vka_as_uploadable_key(vka, password);
|
||||||
|
self.card().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.card().generate_key_simple(Self::ptf, key_type, algo),
|
||||||
|
None => self.card().generate_key(Self::ptf, key_type, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ use openpgp::serialize::stream::{Message, Signer};
|
||||||
use openpgp::{Cert, Fingerprint};
|
use openpgp::{Cert, Fingerprint};
|
||||||
use sequoia_openpgp as openpgp;
|
use sequoia_openpgp as openpgp;
|
||||||
|
|
||||||
|
use crate::{CardDecryptor, CardSigner};
|
||||||
use openpgp_card::{Error, KeyType};
|
use openpgp_card::{Error, KeyType};
|
||||||
|
|
||||||
/// Retrieve a (sub)key from a Cert, for a given KeyType.
|
/// Retrieve a (sub)key from a Cert, for a given KeyType.
|
||||||
|
@ -146,3 +147,37 @@ where
|
||||||
|
|
||||||
Ok(decrypted)
|
Ok(decrypted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wrapper to easily perform a sign operation
|
||||||
|
pub fn sign(s: CardSigner, input: &mut dyn io::Read) -> Result<String> {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper to easily perform a decrypt operation
|
||||||
|
pub fn decrypt(d: CardDecryptor, msg: Vec<u8>, p: &dyn Policy) -> Result<Vec<u8>> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
79
openpgp-card-sequoia/src/state.rs
Normal file
79
openpgp-card-sequoia/src/state.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
|
||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
//! States of a card are modeled by the types `Open`, `Transaction`, `User`, `Sign`, `Admin`.
|
||||||
|
|
||||||
|
use openpgp_card::card_do::ApplicationRelatedData;
|
||||||
|
use openpgp_card::{OpenPgp, OpenPgpTransaction};
|
||||||
|
|
||||||
|
use crate::Card;
|
||||||
|
|
||||||
|
/// States that a `Card` can be in.
|
||||||
|
///
|
||||||
|
/// See the implementations for more detail.
|
||||||
|
pub trait State {}
|
||||||
|
|
||||||
|
impl State for Open {}
|
||||||
|
impl State for Transaction<'_> {}
|
||||||
|
impl State for User<'_, '_> {}
|
||||||
|
impl State for Sign<'_, '_> {}
|
||||||
|
impl State for Admin<'_, '_> {}
|
||||||
|
|
||||||
|
/// State of an OpenPGP card in its base state, no transaction has been started.
|
||||||
|
///
|
||||||
|
/// A transaction can be started on the card, in this state.
|
||||||
|
pub struct Open {
|
||||||
|
pub(crate) pgp: OpenPgp,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State of an OpenPGP card once a transaction has been started.
|
||||||
|
///
|
||||||
|
/// The cards is in its base state, base authorization applies.
|
||||||
|
/// Card-Operations that don't require PIN validation can be performed in this state.
|
||||||
|
/// This includes many read-operations from the card.
|
||||||
|
///
|
||||||
|
/// (Note that a factory-reset can be performed in this base state.)
|
||||||
|
pub struct Transaction<'a> {
|
||||||
|
pub(crate) opt: OpenPgpTransaction<'a>,
|
||||||
|
|
||||||
|
// Cache of "application related data".
|
||||||
|
//
|
||||||
|
// FIXME: Should be invalidated when changing data on the card!
|
||||||
|
// (e.g. uploading keys, etc)
|
||||||
|
//
|
||||||
|
// This field should probably be an Option<> that gets invalidated when appropriate and
|
||||||
|
// re-fetched lazily.
|
||||||
|
pub(crate) ard: ApplicationRelatedData,
|
||||||
|
|
||||||
|
// verify status of pw1
|
||||||
|
pub(crate) pw1: bool,
|
||||||
|
|
||||||
|
// verify status of pw1 for signing
|
||||||
|
pub(crate) pw1_sign: bool,
|
||||||
|
|
||||||
|
// verify status of pw3
|
||||||
|
pub(crate) pw3: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State of an OpenPGP card after successfully verifying the User PIN
|
||||||
|
/// (this verification allow user operations other than signing).
|
||||||
|
///
|
||||||
|
/// In this state, e.g. decryption operations and authentication operations can be performed.
|
||||||
|
pub struct User<'app, 'open> {
|
||||||
|
pub(crate) tx: &'open mut Card<Transaction<'app>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State of an OpenPGP card after successfully verifying PW1 for signing.
|
||||||
|
///
|
||||||
|
/// In this state, signatures can be issued.
|
||||||
|
pub struct Sign<'app, 'open> {
|
||||||
|
pub(crate) tx: &'open mut Card<Transaction<'app>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State of an OpenPGP card after successful verification the Admin PIN.
|
||||||
|
///
|
||||||
|
/// In this state, the card can be configured, e.g.: importing key material onto the card,
|
||||||
|
/// or setting the cardholder name.
|
||||||
|
pub struct Admin<'app, 'open> {
|
||||||
|
pub(crate) tx: &'open mut Card<Transaction<'app>>,
|
||||||
|
}
|
|
@ -5,11 +5,9 @@
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
use openpgp::armor;
|
|
||||||
use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation;
|
use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation;
|
||||||
use openpgp::crypto::mpi;
|
use openpgp::crypto::mpi;
|
||||||
use openpgp::packet::Signature;
|
use openpgp::packet::Signature;
|
||||||
|
@ -18,9 +16,6 @@ use openpgp::packet::{
|
||||||
signature::SignatureBuilder,
|
signature::SignatureBuilder,
|
||||||
Key, UserID,
|
Key, UserID,
|
||||||
};
|
};
|
||||||
use openpgp::parse::{stream::DecryptorBuilder, Parse};
|
|
||||||
use openpgp::policy::Policy;
|
|
||||||
use openpgp::serialize::stream::{Message, Signer};
|
|
||||||
use openpgp::types::{
|
use openpgp::types::{
|
||||||
HashAlgorithm, KeyFlags, PublicKeyAlgorithm, SignatureType, SymmetricAlgorithm, Timestamp,
|
HashAlgorithm, KeyFlags, PublicKeyAlgorithm, SignatureType, SymmetricAlgorithm, Timestamp,
|
||||||
};
|
};
|
||||||
|
@ -32,11 +27,9 @@ use openpgp_card::card_do::{Fingerprint, KeyGenerationTime};
|
||||||
use openpgp_card::crypto_data::{CardUploadableKey, PublicKeyMaterial};
|
use openpgp_card::crypto_data::{CardUploadableKey, PublicKeyMaterial};
|
||||||
use openpgp_card::{Error, KeyType};
|
use openpgp_card::{Error, KeyType};
|
||||||
|
|
||||||
use crate::card::{Card, Transaction};
|
|
||||||
use crate::decryptor::CardDecryptor;
|
|
||||||
use crate::privkey::SequoiaKey;
|
use crate::privkey::SequoiaKey;
|
||||||
use crate::signer::CardSigner;
|
use crate::state::Transaction;
|
||||||
use crate::PublicKey;
|
use crate::{Card, PublicKey};
|
||||||
|
|
||||||
/// Create a Cert from the three subkeys on a card.
|
/// Create a Cert from the three subkeys on a card.
|
||||||
/// (Calling this multiple times will result in different Certs!)
|
/// (Calling this multiple times will result in different Certs!)
|
||||||
|
@ -225,57 +218,6 @@ pub fn public_key_material_and_fp_to_key(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a PublicKey representation for a key slot on the card
|
|
||||||
pub fn key_slot(open: &mut Card<Transaction>, kt: KeyType) -> Result<Option<PublicKey>, Error> {
|
|
||||||
// FIXME: only read these once, if multiple subkeys are retrieved from the card
|
|
||||||
let times = open.key_generation_times()?;
|
|
||||||
let fps = open.fingerprints()?;
|
|
||||||
|
|
||||||
match kt {
|
|
||||||
KeyType::Signing => {
|
|
||||||
if let Ok(pkm) = open.public_key(KeyType::Signing) {
|
|
||||||
if let Some(ts) = times.signature() {
|
|
||||||
return Ok(Some(public_key_material_and_fp_to_key(
|
|
||||||
&pkm,
|
|
||||||
KeyType::Signing,
|
|
||||||
ts,
|
|
||||||
fps.signature().expect("Signature fingerprint is unset"),
|
|
||||||
)?));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
KeyType::Decryption => {
|
|
||||||
if let Ok(pkm) = open.public_key(KeyType::Decryption) {
|
|
||||||
if let Some(ts) = times.decryption() {
|
|
||||||
return Ok(Some(public_key_material_and_fp_to_key(
|
|
||||||
&pkm,
|
|
||||||
KeyType::Decryption,
|
|
||||||
ts,
|
|
||||||
fps.decryption().expect("Decryption fingerprint is unset"),
|
|
||||||
)?));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
KeyType::Authentication => {
|
|
||||||
if let Ok(pkm) = open.public_key(KeyType::Authentication) {
|
|
||||||
if let Some(ts) = times.authentication() {
|
|
||||||
return Ok(Some(public_key_material_and_fp_to_key(
|
|
||||||
&pkm,
|
|
||||||
KeyType::Authentication,
|
|
||||||
ts,
|
|
||||||
fps.authentication()
|
|
||||||
.expect("Authentication fingerprint is unset"),
|
|
||||||
)?));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper fn: get a Sequoia PublicKey from an openpgp-card PublicKeyMaterial.
|
/// Helper fn: get a Sequoia PublicKey from an openpgp-card PublicKeyMaterial.
|
||||||
///
|
///
|
||||||
/// For ECC decryption keys, `hash` and `sym` can be optionally specified.
|
/// For ECC decryption keys, `hash` and `sym` can be optionally specified.
|
||||||
|
@ -412,35 +354,3 @@ pub fn vka_as_uploadable_key(
|
||||||
let sqk = SequoiaKey::new(vka, password);
|
let sqk = SequoiaKey::new(vka, password);
|
||||||
Box::new(sqk)
|
Box::new(sqk)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sign(s: CardSigner, input: &mut dyn io::Read) -> Result<String> {
|
|
||||||
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 decrypt(d: CardDecryptor, msg: Vec<u8>, p: &dyn Policy) -> Result<Vec<u8>> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use openpgp_card_sequoia::card::{Admin, Open, Transaction};
|
use openpgp_card_sequoia::state::{Admin, Open, Transaction};
|
||||||
use openpgp_card_sequoia::util::public_key_material_to_key;
|
use openpgp_card_sequoia::util::public_key_material_to_key;
|
||||||
use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm};
|
use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm};
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ use sequoia_openpgp::serialize::SerializeInto;
|
||||||
use sequoia_openpgp::Cert;
|
use sequoia_openpgp::Cert;
|
||||||
|
|
||||||
use openpgp_card_sequoia::types::AlgoSimple;
|
use openpgp_card_sequoia::types::AlgoSimple;
|
||||||
use openpgp_card_sequoia::{card::Card, types::KeyType};
|
use openpgp_card_sequoia::{types::KeyType, Card};
|
||||||
|
|
||||||
use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion};
|
use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion};
|
||||||
use crate::{output, util, ENTER_ADMIN_PIN, ENTER_USER_PIN};
|
use crate::{output, util, ENTER_ADMIN_PIN, ENTER_USER_PIN};
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
|
|
||||||
use openpgp_card_sequoia::types::KeyType;
|
use openpgp_card_sequoia::types::KeyType;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use clap::Parser;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
use sequoia_openpgp::{
|
use sequoia_openpgp::{
|
||||||
parse::{stream::DecryptorBuilder, Parse},
|
parse::{stream::DecryptorBuilder, Parse},
|
||||||
policy::StandardPolicy,
|
policy::StandardPolicy,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
|
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
|
|
||||||
use crate::output;
|
use crate::output;
|
||||||
use crate::pick_card_for_reading;
|
use crate::pick_card_for_reading;
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::path::PathBuf;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use openpgp_card_sequoia::card::{Card, Open, Transaction};
|
use openpgp_card_sequoia::{state::Open, state::Transaction, Card};
|
||||||
|
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use crate::util::{load_pin, print_gnuk_note};
|
use crate::util::{load_pin, print_gnuk_note};
|
||||||
|
|
|
@ -8,7 +8,7 @@ use clap::Parser;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
use sequoia_openpgp::serialize::SerializeInto;
|
use sequoia_openpgp::serialize::SerializeInto;
|
||||||
|
|
||||||
use openpgp_card_sequoia::types::KeyType;
|
use openpgp_card_sequoia::types::KeyType;
|
||||||
|
@ -58,7 +58,7 @@ pub fn print_pubkey(
|
||||||
|
|
||||||
let user_pin = util::get_pin(&mut card, command.user_pin, crate::ENTER_USER_PIN)?;
|
let user_pin = util::get_pin(&mut card, command.user_pin, crate::ENTER_USER_PIN)?;
|
||||||
|
|
||||||
let pkm = card.public_key(KeyType::Signing)?;
|
let pkm = card.public_key_material(KeyType::Signing)?;
|
||||||
let times = card.key_generation_times()?;
|
let times = card.key_generation_times()?;
|
||||||
let fps = card.fingerprints()?;
|
let fps = card.fingerprints()?;
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ pub fn print_pubkey(
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut key_dec = None;
|
let mut key_dec = None;
|
||||||
if let Ok(pkm) = card.public_key(KeyType::Decryption) {
|
if let Ok(pkm) = card.public_key_material(KeyType::Decryption) {
|
||||||
if let Some(ts) = times.decryption() {
|
if let Some(ts) = times.decryption() {
|
||||||
key_dec = Some(public_key_material_and_fp_to_key(
|
key_dec = Some(public_key_material_and_fp_to_key(
|
||||||
&pkm,
|
&pkm,
|
||||||
|
@ -82,7 +82,7 @@ pub fn print_pubkey(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut key_aut = None;
|
let mut key_aut = None;
|
||||||
if let Ok(pkm) = card.public_key(KeyType::Authentication) {
|
if let Ok(pkm) = card.public_key_material(KeyType::Authentication) {
|
||||||
if let Some(ts) = times.authentication() {
|
if let Some(ts) = times.authentication() {
|
||||||
key_aut = Some(public_key_material_and_fp_to_key(
|
key_aut = Some(public_key_material_and_fp_to_key(
|
||||||
&pkm,
|
&pkm,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
|
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use clap::Parser;
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
use sequoia_openpgp::serialize::stream::{Armorer, Message, Signer};
|
use sequoia_openpgp::serialize::stream::{Armorer, Message, Signer};
|
||||||
|
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
|
|
||||||
use openpgp_card_sequoia::types::KeyType;
|
use openpgp_card_sequoia::types::KeyType;
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ pub fn print_ssh(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show authentication subkey as openssh public key string
|
// Show authentication subkey as openssh public key string
|
||||||
if let Ok(pkm) = card.public_key(KeyType::Authentication) {
|
if let Ok(pkm) = card.public_key_material(KeyType::Authentication) {
|
||||||
if let Ok(ssh) = util::get_ssh_pubkey_string(&pkm, ident) {
|
if let Ok(ssh) = util::get_ssh_pubkey_string(&pkm, ident) {
|
||||||
output.ssh_public_key(ssh);
|
output.ssh_public_key(ssh);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use openpgp_card_sequoia::card::{Card, Open};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
|
|
||||||
use openpgp_card_sequoia::types::KeyType;
|
use openpgp_card_sequoia::types::KeyType;
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ pub fn print_status(
|
||||||
}
|
}
|
||||||
|
|
||||||
if command.pkm {
|
if command.pkm {
|
||||||
if let Ok(pkm) = card.public_key(KeyType::Signing) {
|
if let Ok(pkm) = card.public_key_material(KeyType::Signing) {
|
||||||
signature_key.public_key_material(pkm.to_string());
|
signature_key.public_key_material(pkm.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ pub fn print_status(
|
||||||
decryption_key.status(format!("{}", ks));
|
decryption_key.status(format!("{}", ks));
|
||||||
}
|
}
|
||||||
if command.pkm {
|
if command.pkm {
|
||||||
if let Ok(pkm) = card.public_key(KeyType::Decryption) {
|
if let Ok(pkm) = card.public_key_material(KeyType::Decryption) {
|
||||||
decryption_key.public_key_material(pkm.to_string());
|
decryption_key.public_key_material(pkm.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@ pub fn print_status(
|
||||||
authentication_key.status(format!("{}", ks));
|
authentication_key.status(format!("{}", ks));
|
||||||
}
|
}
|
||||||
if command.pkm {
|
if command.pkm {
|
||||||
if let Ok(pkm) = card.public_key(KeyType::Authentication) {
|
if let Ok(pkm) = card.public_key_material(KeyType::Authentication) {
|
||||||
authentication_key.public_key_material(pkm.to_string());
|
authentication_key.public_key_material(pkm.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,10 @@ use clap::Parser;
|
||||||
|
|
||||||
use sequoia_openpgp::Cert;
|
use sequoia_openpgp::Cert;
|
||||||
|
|
||||||
use openpgp_card_sequoia::card::{Card, Open, Transaction};
|
|
||||||
use openpgp_card_sequoia::types::CardBackend;
|
use openpgp_card_sequoia::types::CardBackend;
|
||||||
use openpgp_card_sequoia::util::make_cert;
|
use openpgp_card_sequoia::util::make_cert;
|
||||||
use openpgp_card_sequoia::PublicKey;
|
use openpgp_card_sequoia::PublicKey;
|
||||||
|
use openpgp_card_sequoia::{state::Open, state::Transaction, Card};
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod commands;
|
mod commands;
|
||||||
|
|
|
@ -5,10 +5,11 @@ use anyhow::{anyhow, Context, Result};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use openpgp_card_pcsc::PcscBackend;
|
use openpgp_card_pcsc::PcscBackend;
|
||||||
use openpgp_card_sequoia::card::{Admin, Card, Sign, Transaction, User};
|
use openpgp_card_sequoia::state::{Admin, Sign, Transaction, User};
|
||||||
use openpgp_card_sequoia::types::{
|
use openpgp_card_sequoia::types::{
|
||||||
Algo, CardBackend, Curve, EccType, Error, PublicKeyMaterial, StatusBytes,
|
Algo, CardBackend, Curve, EccType, Error, PublicKeyMaterial, StatusBytes,
|
||||||
};
|
};
|
||||||
|
use openpgp_card_sequoia::Card;
|
||||||
|
|
||||||
pub(crate) fn cards() -> Result<Vec<Box<dyn CardBackend + Send + Sync>>, Error> {
|
pub(crate) fn cards() -> Result<Vec<Box<dyn CardBackend + Send + Sync>>, Error> {
|
||||||
PcscBackend::cards(None).map(|cards| cards.into_iter().map(|c| c.into()).collect())
|
PcscBackend::cards(None).map(|cards| cards.into_iter().map(|c| c.into()).collect())
|
||||||
|
|
Loading…
Reference in a new issue