keyfork-shard: begin work on OpenPGP card support

This commit is contained in:
Ryan Heywood 2023-11-02 01:01:34 -05:00
parent 8afcae5447
commit adad3e5b6b
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
6 changed files with 569 additions and 26 deletions

301
Cargo.lock generated
View File

@ -32,6 +32,21 @@ dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.5.0"
@ -234,6 +249,12 @@ version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.4.0"
@ -261,6 +282,27 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "card-backend"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd3ee3a298842065dc489180c34a4fe4bbbb8643bb422009d79558a099fb42e5"
dependencies = [
"thiserror",
]
[[package]]
name = "card-backend-pcsc"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68bb0b707b1b6b058ed93abd70ef65703ed6fd4150d32a0d735b78cfa61cbb35"
dependencies = [
"card-backend",
"iso7816-tlv",
"log",
"pcsc",
]
[[package]]
name = "cc"
version = "1.0.83"
@ -291,9 +333,12 @@ version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets 0.48.5",
]
[[package]]
@ -372,6 +417,12 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "cpufeatures"
version = "0.2.9"
@ -446,6 +497,17 @@ dependencies = [
"syn 2.0.29",
]
[[package]]
name = "der"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
dependencies = [
"const-oid",
"pem-rfc7468",
"zeroize",
]
[[package]]
name = "der"
version = "0.7.8"
@ -478,6 +540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"const-oid",
"crypto-common",
"subtle",
]
@ -515,10 +578,10 @@ version = "0.16.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4"
dependencies = [
"der",
"der 0.7.8",
"elliptic-curve",
"signature 2.1.0",
"spki",
"spki 0.7.2",
]
[[package]]
@ -536,7 +599,7 @@ version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d"
dependencies = [
"pkcs8",
"pkcs8 0.10.2",
"signature 2.1.0",
]
@ -571,7 +634,7 @@ dependencies = [
"ff",
"generic-array",
"group",
"pkcs8",
"pkcs8 0.10.2",
"rand_core 0.6.4",
"sec1",
"subtle",
@ -787,6 +850,12 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
[[package]]
name = "hex-slice"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5491a308e0214554f07a81d8944abe45f552871c12e3c3c6e7e5d354039a6c4c"
[[package]]
name = "hmac"
version = "0.12.1"
@ -796,6 +865,29 @@ dependencies = [
"digest 0.10.7",
]
[[package]]
name = "iana-time-zone"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "idna"
version = "0.3.0"
@ -851,6 +943,15 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "iso7816-tlv"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d47365efc3b4c252f8a3384445c0f7e8a4e0ae5c22bf3bedd2dd16f9bb45016a"
dependencies = [
"untrusted",
]
[[package]]
name = "itertools"
version = "0.10.5"
@ -982,7 +1083,9 @@ version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"card-backend-pcsc",
"keyfork-derive-openpgp",
"openpgp-card-sequoia",
"sequoia-openpgp",
"serde",
"sharks",
@ -1063,6 +1166,9 @@ name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
dependencies = [
"spin",
]
[[package]]
name = "lazycell"
@ -1086,6 +1192,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "libm"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
@ -1220,6 +1332,44 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
dependencies = [
"byteorder",
"lazy_static",
"libm",
"num-integer",
"num-iter",
"num-traits",
"rand 0.8.5",
"smallvec",
"zeroize",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.16"
@ -1227,6 +1377,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@ -1254,6 +1405,36 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "openpgp-card"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ba6b39b46a9deba985be9cc960709e284806b550d7e1aff915f8be4b06c3640"
dependencies = [
"card-backend",
"chrono",
"hex-slice",
"log",
"nom",
"thiserror",
]
[[package]]
name = "openpgp-card-sequoia"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7637080b15379df16fef0f81fd2664d403366b7514c721f2231c8974778017c3"
dependencies = [
"anyhow",
"card-backend",
"chrono",
"log",
"openpgp-card",
"rsa",
"sequoia-openpgp",
"thiserror",
]
[[package]]
name = "overload"
version = "0.1.1"
@ -1293,12 +1474,40 @@ dependencies = [
"hmac",
]
[[package]]
name = "pcsc"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37cab0be9d04e808a8d8059fa54befcd71dc8b168f9f0c04bdb7e59832abbab4"
dependencies = [
"bitflags 1.3.2",
"pcsc-sys",
]
[[package]]
name = "pcsc-sys"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1b7bfecba2c0f1b5efb0e7caf7533ab1c295024165bcbb066231f60d33e23ea"
dependencies = [
"pkg-config",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "pem-rfc7468"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac"
dependencies = [
"base64ct",
]
[[package]]
name = "petgraph"
version = "0.6.4"
@ -1350,14 +1559,36 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs1"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719"
dependencies = [
"der 0.6.1",
"pkcs8 0.9.0",
"spki 0.6.0",
"zeroize",
]
[[package]]
name = "pkcs8"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba"
dependencies = [
"der 0.6.1",
"spki 0.6.0",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
"der 0.7.8",
"spki 0.7.2",
]
[[package]]
@ -1601,6 +1832,26 @@ dependencies = [
"digest 0.10.7",
]
[[package]]
name = "rsa"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55a77d189da1fee555ad95b7e50e7457d91c0e089ec68ca69ad2989413bbdab4"
dependencies = [
"byteorder",
"digest 0.10.7",
"num-bigint-dig",
"num-integer",
"num-iter",
"num-traits",
"pkcs1",
"pkcs8 0.9.0",
"rand_core 0.6.4",
"signature 2.1.0",
"subtle",
"zeroize",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@ -1674,9 +1925,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct",
"der",
"der 0.7.8",
"generic-array",
"pkcs8",
"pkcs8 0.10.2",
"subtle",
"zeroize",
]
@ -1817,6 +2068,7 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500"
dependencies = [
"digest 0.10.7",
"rand_core 0.6.4",
]
@ -1852,6 +2104,22 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spki"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
dependencies = [
"base64ct",
"der 0.6.1",
]
[[package]]
name = "spki"
version = "0.7.2"
@ -1859,7 +2127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a"
dependencies = [
"base64ct",
"der",
"der 0.7.8",
]
[[package]]
@ -2171,6 +2439,12 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "utf8parse"
version = "0.2.1"
@ -2283,6 +2557,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.51.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.45.0"

View File

@ -6,13 +6,16 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["openpgp"]
default = ["openpgp", "openpgp-card"]
openpgp = ["sequoia-openpgp"]
openpgp-card = ["openpgp-card-sequoia", "card-backend-pcsc"]
[dependencies]
anyhow = "1.0.75"
bincode = "1.3.3"
card-backend-pcsc = { version = "0.5.0", optional = true }
keyfork-derive-openpgp = { version = "0.1.0", path = "../keyfork-derive-openpgp" }
openpgp-card-sequoia = { version = "0.2.0", optional = true }
sequoia-openpgp = { version = "1.16.1", optional = true }
serde = "1.0.188"
sharks = "0.5.0"

View File

@ -1,16 +1,21 @@
use std::{
env,
fs::File,
io::{stdin, stdout},
path::PathBuf,
process::ExitCode,
str::FromStr,
};
use keyfork_shard::openpgp::{combine, discover_certs, parse_messages, openpgp::Cert};
use keyfork_shard::openpgp::{combine, discover_certs, openpgp::Cert, parse_messages};
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
fn validate(threshold: &str, key_discovery: &str) -> Result<(u8, Vec<Cert>)> {
fn validate(
threshold: &str,
key_discovery: &str,
recovery_file: &str,
) -> Result<(u8, Vec<Cert>, PathBuf)> {
let threshold = u8::from_str(threshold)?;
let key_discovery = PathBuf::from(key_discovery);
@ -20,19 +25,33 @@ fn validate(threshold: &str, key_discovery: &str) -> Result<(u8, Vec<Cert>)> {
// Load certs from path
let certs = discover_certs(key_discovery)?;
Ok((threshold, certs))
let recovery_file = PathBuf::from(if recovery_file == "=" {
eprintln!("loading certs from stdin; note that prompting smartcard devices will not work");
"/dev/stdin"
} else {
recovery_file
});
std::fs::metadata(&recovery_file)?;
Ok((threshold, certs, recovery_file))
}
fn run() -> Result<()> {
let mut args = env::args();
let program_name = args.next().expect("program name");
let args = args.collect::<Vec<_>>();
let (threshold, cert_list) = match args.as_slice() {
[threshold, key_discovery] => validate(threshold, key_discovery)?,
_ => panic!("Usage: {program_name} threshold key_discovery"),
let (threshold, cert_list, recovery_file) = match args.as_slice() {
[threshold, key_discovery, recovery_file] => {
validate(threshold, key_discovery, recovery_file)?
}
[threshold, key_discovery] => {
validate(threshold, key_discovery, "-")?
}
_ => panic!("Usage: {program_name} threshold key_discovery recovery_file"),
};
let mut encrypted_messages = parse_messages(stdin())?;
let mut encrypted_messages = parse_messages(File::open(recovery_file)?)?;
let encrypted_metadata = encrypted_messages
.pop_front()

View File

@ -13,7 +13,10 @@ use openpgp::{
armor::{Kind, Writer},
cert::{Cert, CertParser, ValidCert},
packet::{Packet, Tag, UserID, PKESK, SEIP},
parse::{stream::DecryptorBuilder, Parse},
parse::{
stream::{DecryptionHelper, DecryptorBuilder, VerificationHelper},
Parse,
},
policy::{NullPolicy, Policy, StandardPolicy},
serialize::{
stream::{ArbitraryWriter, Encryptor, LiteralWriter, Message, Recipient, Signer},
@ -28,6 +31,9 @@ use sharks::{Share, Sharks};
mod keyring;
use keyring::Keyring;
mod smartcard;
use smartcard::SmartcardManager;
// TODO: better error handling
#[derive(Debug, Clone)]
@ -57,7 +63,10 @@ impl EncryptedMessage {
}
}
pub fn decrypt_with(&self, policy: &'_ dyn Policy, keyring: &mut Keyring) -> Result<Vec<u8>> {
pub fn decrypt_with<H>(&self, policy: &'_ dyn Policy, decryptor: H) -> Result<Vec<u8>>
where
H: VerificationHelper + DecryptionHelper,
{
let mut packets = vec![];
for pkesk in &self.pkesks {
@ -76,7 +85,7 @@ impl EncryptedMessage {
message.finalize()?;
let mut decryptor =
DecryptorBuilder::from_bytes(&packets)?.with_policy(policy, None, keyring)?;
DecryptorBuilder::from_bytes(&packets)?.with_policy(policy, None, decryptor)?;
let mut content = vec![];
decryptor.read_to_end(&mut content)?;
@ -222,9 +231,42 @@ pub fn combine(
let left_from_threshold = threshold as usize - decrypted_messages.len();
if left_from_threshold > 0 {
eprintln!("remaining keys: {left_from_threshold}, prompting yubikeys");
}
for _ in 0..left_from_threshold {
todo!("prompt for Yubikeys")
// TODO: allow decrypt metadata with Yubikey, avoid require stage 1
let mut manager =
SmartcardManager::new(keyring.root_cert().expect("stage 1 decrypt").clone());
let mut remaining_usable_certs = certs
.iter()
.filter(|cert| messages.contains_key(&cert.keyid()))
.collect::<Vec<_>>();
while threshold as usize - decrypted_messages.len() > 0 {
remaining_usable_certs.retain(|cert| messages.contains_key(&cert.keyid()));
let mut fingerprints = HashMap::new();
for valid_cert in remaining_usable_certs
.iter()
.map(|cert| cert.with_policy(&policy, None))
{
let valid_cert = valid_cert?;
fingerprints.insert(
valid_cert.keyid(),
valid_cert
.keys()
.for_storage_encryption()
.map(|k| k.fingerprint())
.collect::<Vec<_>>(),
);
}
for (cert_id, fingerprints) in fingerprints {
if manager.load_any_fingerprint(fingerprints)?.is_some() {
// manager is loaded with a Card<Open>, utilize in tx
let message = messages.remove(&cert_id);
if let Some(message) = message {
let message = message.decrypt_with(&policy, &mut manager)?;
decrypted_messages.insert(cert_id, message);
}
}
}
}
}
let shares = decrypted_messages

View File

@ -110,7 +110,7 @@ impl DecryptionHelper for &mut Keyring {
where
D: FnMut(openpgp::types::SymmetricAlgorithm, &openpgp::crypto::SessionKey) -> bool,
{
// optimized route: use all locally stored certs
// unoptimized route: use all locally stored certs
for pkesk in pkesks {
for cert in self.get_certs_for_pkesk(pkesk) {
for key in cert.keys().secret() {
@ -129,8 +129,6 @@ impl DecryptionHelper for &mut Keyring {
}
}
// smartcard route: plug in smartcard, attempt decrypt, fail and bail
Err(KeyringFailure::SecretKeyNotFound.into())
}
}

View File

@ -0,0 +1,198 @@
use std::collections::HashSet;
use super::openpgp::{
self,
cert::Cert,
packet::{PKESK, SKESK},
parse::stream::{DecryptionHelper, MessageLayer, MessageStructure, VerificationHelper},
Fingerprint,
};
use card_backend_pcsc::PcscBackend;
use openpgp_card_sequoia::{state::Open, Card};
#[derive(Clone, Debug)]
pub enum SmartcardFailure {
#[allow(dead_code)]
SmartCardPromptFailed,
SmartCardNotFound,
}
impl std::fmt::Display for SmartcardFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SmartCardPromptFailed => f.write_str("Attempt to prompt for smart card failed"),
Self::SmartCardNotFound => f.write_str("No smart card backend was stored"),
}
}
}
impl std::error::Error for SmartcardFailure {}
pub struct SmartcardManager {
current_card: Option<Card<Open>>,
root: Cert,
}
impl SmartcardManager {
pub fn new(root: Cert) -> Self {
Self {
current_card: None,
root,
}
}
/// Utility function to prompt for a newline from standard input.
pub fn prompt(&self, prompt: impl std::fmt::Display) -> std::io::Result<()> {
eprint!("{prompt}: ");
std::io::stdin().read_line(&mut String::new()).map(|_| ())
}
/// Utility function to obtain a prompt from the command line.
pub fn prompt_pin(&self, prompt: impl std::fmt::Display) -> std::io::Result<String> {
eprint!("{prompt}: ");
let mut output = String::new();
std::io::stdin().read_line(&mut output)?;
Ok(output)
}
/// Return all [`Fingerprint`] for the currently accessible backends.
///
/// NOTE: Only implemented for decryption keys.
pub fn iter_fingerprints() -> impl Iterator<Item = Fingerprint> {
PcscBackend::cards(None).into_iter().flat_map(|iter| {
iter.filter_map(|backend| {
let backend = backend.ok()?;
let mut card = Card::<Open>::new(backend).ok()?;
let transaction = card.transaction().ok()?;
transaction
.fingerprints()
.ok()?
.decryption()
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()))
})
})
}
/// Load a backend if any [`Fingerprint`] has been matched by a currently active card.
///
/// NOTE: Only implemented for decryption keys.
pub fn load_any_fingerprint(
&mut self,
fingerprints: impl IntoIterator<Item = Fingerprint>,
) -> Result<Option<Fingerprint>, Box<dyn std::error::Error>> {
// NOTE: This can't be HashSet::from_iter() because from_iter() requires a passed-in state
// I do not want to provide.
let mut requested_fingerprints = HashSet::new();
requested_fingerprints.extend(fingerprints);
let mut had_any_backend = false;
while !had_any_backend {
// Load all backends, confirm if any have any fingerprints
for backend in PcscBackend::cards(None)? {
had_any_backend = true;
let backend = backend?;
let mut card = Card::<Open>::new(backend)?;
let transaction = card.transaction()?;
let mut fingerprint = None;
if let Some(fp) = transaction
.fingerprints()?
.decryption()
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()))
{
if requested_fingerprints.contains(&fp) {
fingerprint.replace(fp);
}
}
drop(transaction);
if fingerprint.is_some() {
self.current_card.replace(card);
return Ok(fingerprint);
}
}
eprintln!("No matching smartcard detected.");
self.prompt("Please plug in a smart card and press enter")?;
}
Ok(None)
}
}
impl VerificationHelper for &mut SmartcardManager {
fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<Cert>> {
Ok(ids
.iter()
.flat_map(|kh| {
if &self.root.key_handle() == kh {
Some(self.root.clone())
} else {
None
}
})
.collect())
}
fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> {
for layer in structure.into_iter() {
#[allow(unused_variables)]
match layer {
MessageLayer::Compression { algo } => {}
MessageLayer::Encryption {
sym_algo,
aead_algo,
} => {}
MessageLayer::SignatureGroup { results } => {
for result in results {
if let Err(e) = result {
// FIXME: anyhow leak
return Err(anyhow::anyhow!(e.to_string()));
}
}
}
}
}
Ok(())
}
}
impl DecryptionHelper for &mut SmartcardManager {
fn decrypt<D>(
&mut self,
pkesks: &[PKESK],
_skesks: &[SKESK],
sym_algo: Option<openpgp::types::SymmetricAlgorithm>,
mut decrypt: D,
) -> openpgp::Result<Option<Fingerprint>>
where
D: FnMut(openpgp::types::SymmetricAlgorithm, &openpgp::crypto::SessionKey) -> bool,
{
let mut card = self.current_card.take();
let Some(card) = card.as_mut() else {
return Err(SmartcardFailure::SmartCardNotFound.into());
};
let mut transaction = card.transaction()?;
let fp = transaction
.fingerprints()?
.decryption()
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()));
let pin = self.prompt_pin("Please enter PIN to unlock card")?;
let mut user = transaction.to_user_card(pin.as_str())?;
let mut decryptor =
user.decryptor(&|| println!("Touch confirmation needed for decryption"))?;
for pkesk in pkesks {
if pkesk
.decrypt(&mut decryptor, sym_algo)
.map(|(algo, sk)| decrypt(algo, &sk))
.unwrap_or(false)
{
return Ok(fp);
}
}
Err(SmartcardFailure::SmartCardNotFound.into())
}
}