use std::io::Cursor;
use openpgp_card::ocard::crypto::{Cryptogram, Hash};
use openpgp_card::Card;
use secrecy::SecretString;
use super::get_card_backend;
use super::types::CardError;
use crate::error::{Error, Result};
use crate::internal::{
can_primary_certify, can_primary_sign, is_subkey_valid, parse_public_key,
validate_primary_key_signing_usage, SigningKeyUsage,
};
use pgp::composed::{
DetachedSignature, Esk, Message, PlainSessionKey, RawSessionKey, SignedPublicKey,
};
use pgp::crypto::hash::HashAlgorithm;
use pgp::crypto::sym::SymmetricKeyAlgorithm;
use pgp::packet::{Signature, SignatureConfig, SignatureType, Subpacket, SubpacketData};
use pgp::types::{
EskType, Fingerprint, KeyDetails, Mpi, PkeskBytes, PkeskVersion, PublicParams, SignatureBytes,
Timestamp,
};
pub fn sign_bytes_detached_on_card(data: &[u8], public_cert: &[u8], pin: &[u8]) -> Result<String> {
let public_key = parse_public_key(public_cert)?;
let key_info = get_signing_key_info(&public_key, SigningKeyUsage::DataSignature)?;
let signature = create_card_signature(data, &key_info, pin)?;
let detached = DetachedSignature::new(signature);
detached
.to_armored_string(None.into())
.map_err(|e| Error::Crypto(e.to_string()))
}
fn pin_to_secret(pin: &[u8]) -> Result<SecretString> {
let pin_str = std::str::from_utf8(pin).map_err(|_| {
Error::Card(CardError::InvalidData(
"PIN must be valid UTF-8".to_string(),
))
})?;
let mut pin_owned = pin_str.to_string();
let secret = pin_owned.clone().into();
zeroize::Zeroize::zeroize(&mut pin_owned);
Ok(secret)
}
struct SigningKeyInfo {
algorithm: pgp::crypto::public_key::PublicKeyAlgorithm,
public_params: PublicParams,
fingerprint: Fingerprint,
key_id: pgp::types::KeyId,
hash_alg: HashAlgorithm,
}
fn get_signing_key_info(
public_key: &SignedPublicKey,
usage: SigningKeyUsage,
) -> Result<SigningKeyInfo> {
validate_primary_key_signing_usage(public_key, usage)?;
let card_fp = get_card_signing_fingerprint()?;
let primary = &public_key.primary_key;
let primary_fp = hex::encode(primary.fingerprint().as_bytes());
let primary_can_sign = can_primary_sign(public_key) || can_primary_certify(public_key);
if primary_fp == card_fp && can_sign(primary.public_params()) && primary_can_sign {
let params = primary.public_params();
let hash_alg = select_hash_for_params(params);
return Ok(SigningKeyInfo {
algorithm: primary.algorithm(),
public_params: params.clone(),
fingerprint: primary.fingerprint(),
key_id: primary.legacy_key_id(),
hash_alg,
});
}
for subkey in &public_key.public_subkeys {
let key = &subkey.key;
let subkey_fp = hex::encode(key.fingerprint().as_bytes());
let has_sign_flag = subkey.signatures.iter().any(|sig| sig.key_flags().sign());
let subkey_usable = match usage {
SigningKeyUsage::DataSignature => is_subkey_valid(subkey, false),
SigningKeyUsage::KeyMaintenance => is_subkey_valid(subkey, true),
};
if subkey_fp == card_fp && can_sign(key.public_params()) && has_sign_flag && subkey_usable {
let params = key.public_params();
let hash_alg = select_hash_for_params(params);
return Ok(SigningKeyInfo {
algorithm: key.algorithm(),
public_params: params.clone(),
fingerprint: key.fingerprint(),
key_id: key.legacy_key_id(),
hash_alg,
});
}
}
Err(Error::Crypto(format!(
"No key in certificate matches card signing slot fingerprint: {}",
card_fp
)))
}
fn get_card_signing_fingerprint() -> Result<String> {
let backend = get_card_backend(None)?;
let mut card = Card::new(backend).map_err(|e| Error::Card(CardError::from(e)))?;
let mut tx = card
.transaction()
.map_err(|e| Error::Card(CardError::from(e)))?;
let fps = tx
.fingerprints()
.map_err(|e| Error::Card(CardError::from(e)))?;
fps.signature()
.map(|fp| hex::encode(fp.as_bytes()))
.ok_or_else(|| {
Error::Card(CardError::InvalidData(
"No signing key fingerprint on card".to_string(),
))
})
}
fn can_sign(params: &PublicParams) -> bool {
matches!(
params,
PublicParams::RSA(_)
| PublicParams::DSA(_)
| PublicParams::ECDSA(_)
| PublicParams::EdDSALegacy(_)
| PublicParams::Ed25519(_)
| PublicParams::Ed448(_)
)
}
fn select_hash_for_params(params: &PublicParams) -> HashAlgorithm {
match params {
PublicParams::ECDSA(ecdsa) => {
use pgp::types::EcdsaPublicParams;
match ecdsa {
EcdsaPublicParams::P256 { .. } => HashAlgorithm::Sha256,
EcdsaPublicParams::P384 { .. } => HashAlgorithm::Sha384,
EcdsaPublicParams::P521 { .. } => HashAlgorithm::Sha512,
_ => HashAlgorithm::Sha256,
}
}
PublicParams::EdDSALegacy(_) | PublicParams::Ed25519(_) => HashAlgorithm::Sha256,
PublicParams::Ed448(_) => HashAlgorithm::Sha512,
PublicParams::RSA(_) => HashAlgorithm::Sha256,
_ => HashAlgorithm::Sha256,
}
}
fn create_card_signature(data: &[u8], key_info: &SigningKeyInfo, pin: &[u8]) -> Result<Signature> {
let mut config =
SignatureConfig::v4(SignatureType::Binary, key_info.algorithm, key_info.hash_alg);
let now = Timestamp::now();
config.hashed_subpackets.push(
Subpacket::regular(SubpacketData::SignatureCreationTime(now))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
config.hashed_subpackets.push(
Subpacket::regular(SubpacketData::IssuerFingerprint(
key_info.fingerprint.clone(),
))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
config.unhashed_subpackets.push(
Subpacket::regular(SubpacketData::IssuerKeyId(key_info.key_id))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
let hash_data = compute_signature_hash(data, &config, key_info.hash_alg)?;
let signed_hash_value = [hash_data[0], hash_data[1]];
let raw_signature = sign_on_card(&hash_data, key_info.hash_alg, &key_info.public_params, pin)?;
let signature_bytes = create_signature_bytes(&raw_signature, &key_info.public_params)?;
Signature::from_config(config, signed_hash_value, signature_bytes)
.map_err(|e| Error::Crypto(e.to_string()))
}
fn compute_signature_hash(
data: &[u8],
config: &SignatureConfig,
hash_alg: HashAlgorithm,
) -> Result<Vec<u8>> {
use digest::DynDigest;
let mut hasher: Box<dyn DynDigest + Send> = hash_alg
.new_hasher()
.map_err(|e| Error::Crypto(e.to_string()))?;
config
.hash_data_to_sign(&mut hasher, Cursor::new(data))
.map_err(|e| Error::Crypto(e.to_string()))?;
let sig_len = config
.hash_signature_data(&mut hasher)
.map_err(|e| Error::Crypto(e.to_string()))?;
let trailer = config
.trailer(sig_len)
.map_err(|e| Error::Crypto(e.to_string()))?;
hasher.update(&trailer);
Ok(hasher.finalize_reset().to_vec())
}
fn sign_on_card(
hash: &[u8],
hash_alg: HashAlgorithm,
public_params: &PublicParams,
pin: &[u8],
) -> Result<Vec<u8>> {
let backend = get_card_backend(None)?;
let mut card = Card::new(backend).map_err(|e| Error::Card(CardError::from(e)))?;
let mut tx = card
.transaction()
.map_err(|e| Error::Card(CardError::from(e)))?;
let secret_pin = pin_to_secret(pin)?;
tx.verify_user_signing_pin(secret_pin)
.map_err(|e| Error::Card(CardError::from(e)))?;
let low_tx = tx.card();
let card_hash =
match public_params {
PublicParams::EdDSALegacy(_) | PublicParams::Ed25519(_) => {
Hash::EdDSA(hash)
}
PublicParams::ECDSA(_) => {
Hash::ECDSA(hash)
}
_ => {
match hash_alg {
HashAlgorithm::Sha256 => Hash::SHA256(hash.try_into().map_err(|_| {
Error::Crypto("Invalid hash length for SHA256".to_string())
})?),
HashAlgorithm::Sha384 => Hash::SHA384(hash.try_into().map_err(|_| {
Error::Crypto("Invalid hash length for SHA384".to_string())
})?),
HashAlgorithm::Sha512 => Hash::SHA512(hash.try_into().map_err(|_| {
Error::Crypto("Invalid hash length for SHA512".to_string())
})?),
_ => {
return Err(Error::Crypto(format!(
"Unsupported hash algorithm: {:?}",
hash_alg
)))
}
}
}
};
let signature = low_tx
.signature_for_hash(card_hash)
.map_err(|e| Error::Card(CardError::from(e)))?;
Ok(signature)
}
fn create_signature_bytes(raw_sig: &[u8], public_params: &PublicParams) -> Result<SignatureBytes> {
use pgp::bytes::Bytes;
match public_params {
PublicParams::RSA(_) => {
let mpi = Mpi::from_slice(raw_sig);
Ok(SignatureBytes::Mpis(vec![mpi]))
}
PublicParams::EdDSALegacy(_) => {
if raw_sig.len() != 64 {
return Err(Error::Crypto(format!(
"Invalid EdDSA signature length: {} (expected 64)",
raw_sig.len()
)));
}
let r = Mpi::from_slice(&raw_sig[..32]);
let s = Mpi::from_slice(&raw_sig[32..]);
Ok(SignatureBytes::Mpis(vec![r, s]))
}
PublicParams::Ed25519(_) => {
Ok(SignatureBytes::Native(Bytes::copy_from_slice(raw_sig)))
}
PublicParams::ECDSA(_) => {
let half = raw_sig.len() / 2;
let r = Mpi::from_slice(&raw_sig[..half]);
let s = Mpi::from_slice(&raw_sig[half..]);
Ok(SignatureBytes::Mpis(vec![r, s]))
}
_ => Err(Error::Crypto(format!(
"Unsupported algorithm: {:?}",
public_params
))),
}
}
pub fn decrypt_bytes_on_card(data: &[u8], public_cert: &[u8], pin: &[u8]) -> Result<Vec<u8>> {
let public_key = parse_public_key(public_cert)?;
let message = match Message::from_armor(Cursor::new(data)) {
Ok((msg, _headers)) => msg,
Err(_) => Message::from_bytes(data).map_err(|e| Error::Parse(e.to_string()))?,
};
let (esk_packets, _edata) = match &message {
Message::Encrypted { esk, edata, .. } => (esk.clone(), edata),
_ => return Err(Error::Crypto("Message is not encrypted".to_string())),
};
let enc_key_info = get_encryption_key_info(&public_key)?;
let mut session_key: Option<PlainSessionKey> = None;
for esk in &esk_packets {
if let Esk::PublicKeyEncryptedSessionKey(pkesk) = esk {
if pkesk.match_identity(&enc_key_info) {
let values = pkesk.values().map_err(|e| Error::Crypto(e.to_string()))?;
let esk_type = match pkesk.version() {
PkeskVersion::V3 => EskType::V3_4,
PkeskVersion::V6 => EskType::V6,
_ => continue,
};
let decrypted = decrypt_session_key_on_card(values, &enc_key_info, pin, esk_type)?;
session_key = Some(decrypted);
break;
}
}
}
let session_key = session_key
.ok_or_else(|| Error::Crypto("No matching PKESK found for card key".to_string()))?;
let decrypted = message
.decrypt_with_session_key(session_key)
.map_err(|e| Error::Crypto(e.to_string()))?;
let mut decompressed = if decrypted.is_compressed() {
decrypted
.decompress()
.map_err(|e| Error::Crypto(e.to_string()))?
} else {
decrypted
};
decompressed
.as_data_vec()
.map_err(|e| Error::Crypto(e.to_string()))
}
#[derive(Debug)]
struct EncryptionKeyInfo {
public_params: PublicParams,
fingerprint: Fingerprint,
key_id: pgp::types::KeyId,
}
impl KeyDetails for EncryptionKeyInfo {
fn version(&self) -> pgp::types::KeyVersion {
self.fingerprint
.version()
.unwrap_or(pgp::types::KeyVersion::V4)
}
fn legacy_key_id(&self) -> pgp::types::KeyId {
self.key_id
}
fn fingerprint(&self) -> Fingerprint {
self.fingerprint.clone()
}
fn algorithm(&self) -> pgp::crypto::public_key::PublicKeyAlgorithm {
match &self.public_params {
PublicParams::RSA(_) => pgp::crypto::public_key::PublicKeyAlgorithm::RSA,
PublicParams::ECDH(_) => pgp::crypto::public_key::PublicKeyAlgorithm::ECDH,
PublicParams::X25519(_) => pgp::crypto::public_key::PublicKeyAlgorithm::X25519,
PublicParams::X448(_) => pgp::crypto::public_key::PublicKeyAlgorithm::X448,
_ => pgp::crypto::public_key::PublicKeyAlgorithm::RSA,
}
}
fn created_at(&self) -> Timestamp {
Timestamp::now()
}
fn legacy_v3_expiration_days(&self) -> Option<u16> {
None
}
fn public_params(&self) -> &PublicParams {
&self.public_params
}
}
fn get_encryption_key_info(public_key: &SignedPublicKey) -> Result<EncryptionKeyInfo> {
for subkey in &public_key.public_subkeys {
let key = &subkey.key;
if !can_encrypt_algorithm(key.public_params()) {
continue;
}
let has_encryption_flags = subkey.signatures.iter().any(|sig| {
let flags = sig.key_flags();
flags.encrypt_comms() || flags.encrypt_storage()
});
if has_encryption_flags {
return Ok(EncryptionKeyInfo {
public_params: key.public_params().clone(),
fingerprint: key.fingerprint(),
key_id: key.legacy_key_id(),
});
}
}
let primary = &public_key.primary_key;
if can_encrypt_algorithm(primary.public_params()) {
return Ok(EncryptionKeyInfo {
public_params: primary.public_params().clone(),
fingerprint: primary.fingerprint(),
key_id: primary.legacy_key_id(),
});
}
Err(Error::Crypto("No encryption-capable key found".to_string()))
}
fn can_encrypt_algorithm(params: &PublicParams) -> bool {
matches!(
params,
PublicParams::RSA(_)
| PublicParams::ECDH(_)
| PublicParams::X25519(_)
| PublicParams::X448(_)
)
}
fn decrypt_session_key_on_card(
values: &PkeskBytes,
key_info: &EncryptionKeyInfo,
pin: &[u8],
esk_type: EskType,
) -> Result<PlainSessionKey> {
let backend = get_card_backend(None)?;
let mut card = Card::new(backend).map_err(|e| Error::Card(CardError::from(e)))?;
let mut tx = card
.transaction()
.map_err(|e| Error::Card(CardError::from(e)))?;
let secret_pin = pin_to_secret(pin)?;
tx.verify_user_pin(secret_pin)
.map_err(|e| Error::Card(CardError::from(e)))?;
let low_tx = tx.card();
let decrypted = match (values, &key_info.public_params) {
(PkeskBytes::Rsa { mpi }, PublicParams::RSA(_)) => {
let ciphertext: &[u8] = mpi.as_ref();
let cryptogram = Cryptogram::RSA(ciphertext);
low_tx
.decipher(cryptogram)
.map_err(|e| Error::Card(CardError::from(e)))?
}
(
PkeskBytes::Ecdh {
public_point,
encrypted_session_key,
},
PublicParams::ECDH(ecdh_params),
) => {
let point_bytes: &[u8] = public_point.as_ref();
let card_point = if is_cv25519_key(ecdh_params) {
strip_cv25519_prefix(point_bytes)?
} else {
point_bytes
};
let cryptogram = Cryptogram::ECDH(card_point);
let shared_secret = low_tx
.decipher(cryptogram)
.map_err(|e| Error::Card(CardError::from(e)))?;
ecdh_unwrap_session_key(
&shared_secret,
encrypted_session_key.as_ref(),
ecdh_params,
&key_info.fingerprint,
)?
}
(
PkeskBytes::X25519 {
ephemeral,
session_key,
..
},
PublicParams::X25519(_),
) => {
let cryptogram = Cryptogram::ECDH(ephemeral.as_ref());
let shared_secret = low_tx
.decipher(cryptogram)
.map_err(|e| Error::Card(CardError::from(e)))?;
x25519_unwrap_session_key(&shared_secret, session_key.as_ref())?
}
(
PkeskBytes::Ecdh {
public_point,
encrypted_session_key,
},
PublicParams::X25519(_),
) => {
let point_bytes: &[u8] = public_point.as_ref();
let card_point = strip_cv25519_prefix(point_bytes)?;
let cryptogram = Cryptogram::ECDH(card_point);
let shared_secret = low_tx
.decipher(cryptogram)
.map_err(|e| Error::Card(CardError::from(e)))?;
x25519_unwrap_session_key(&shared_secret, encrypted_session_key.as_ref())?
}
_ => {
return Err(Error::Crypto(
"Mismatched PKESK values and key params".to_string(),
))
}
};
match esk_type {
EskType::V3_4 => {
if decrypted.is_empty() {
return Err(Error::Crypto("Empty decryption result".to_string()));
}
let sym_alg = SymmetricKeyAlgorithm::from(decrypted[0]);
if sym_alg == SymmetricKeyAlgorithm::Plaintext {
return Err(Error::Crypto(
"Session key algorithm cannot be plaintext".to_string(),
));
}
let key_size = sym_alg.key_size();
if decrypted.len() != key_size + 3 {
return Err(Error::Crypto(format!(
"Unexpected decrypted key length ({}) for sym_alg {:?}",
decrypted.len(),
sym_alg
)));
}
let key = RawSessionKey::from(&decrypted[1..=key_size]);
Ok(PlainSessionKey::V3_4 { sym_alg, key })
}
EskType::V6 => {
if decrypted.len() < 2 {
return Err(Error::Crypto(format!(
"Unexpected decrypted key length ({}) for V6 ESK",
decrypted.len()
)));
}
let len = decrypted.len();
let key = RawSessionKey::from(&decrypted[0..len - 2]);
Ok(PlainSessionKey::V6 { key })
}
}
}
fn is_cv25519_key(params: &pgp::types::EcdhPublicParams) -> bool {
use pgp::types::EcdhPublicParams;
matches!(params, EcdhPublicParams::Curve25519 { .. })
}
fn strip_cv25519_prefix(point: &[u8]) -> Result<&[u8]> {
if point.len() == 33 && point[0] == 0x40 {
Ok(&point[1..])
} else if point.len() == 32 {
Ok(point)
} else {
Err(Error::Crypto(format!(
"Invalid CV25519 point length: {} (expected 32 or 33)",
point.len()
)))
}
}
fn ecdh_unwrap_session_key(
shared_secret: &[u8],
encrypted_session_key: &[u8],
ecdh_params: &pgp::types::EcdhPublicParams,
fingerprint: &Fingerprint,
) -> Result<Vec<u8>> {
use pgp::crypto::ecdh::derive_session_key;
use pgp::crypto::hash::HashAlgorithm;
use pgp::crypto::sym::SymmetricKeyAlgorithm;
use pgp::types::EcdhPublicParams;
let (hash_algo, sym_algo): (HashAlgorithm, SymmetricKeyAlgorithm) = match ecdh_params {
EcdhPublicParams::Curve25519 { hash, alg_sym, .. } => (*hash, *alg_sym),
EcdhPublicParams::P256 { hash, alg_sym, .. } => (*hash, *alg_sym),
EcdhPublicParams::P384 { hash, alg_sym, .. } => (*hash, *alg_sym),
EcdhPublicParams::P521 { hash, alg_sym, .. } => (*hash, *alg_sym),
EcdhPublicParams::Brainpool256 { hash, alg_sym, .. } => (*hash, *alg_sym),
EcdhPublicParams::Brainpool384 { hash, alg_sym, .. } => (*hash, *alg_sym),
EcdhPublicParams::Brainpool512 { hash, alg_sym, .. } => (*hash, *alg_sym),
EcdhPublicParams::Unsupported { curve, .. } => {
return Err(Error::Crypto(format!(
"Unsupported ECDH curve: {:?}",
curve
)));
}
};
let curve = ecdh_params.curve();
derive_session_key(
shared_secret,
encrypted_session_key,
encrypted_session_key.len(),
curve,
hash_algo,
sym_algo,
fingerprint.as_bytes(),
)
.map(|zeroizing| zeroizing.to_vec())
.map_err(|e| Error::Crypto(e.to_string()))
}
fn x25519_unwrap_session_key(
shared_secret: &[u8],
encrypted_session_key: &[u8],
) -> Result<Vec<u8>> {
use aes::Aes256;
use aes_kw::Kek;
use hkdf::Hkdf;
use sha2::Sha256;
let hkdf = Hkdf::<Sha256>::new(None, shared_secret);
let mut kek = [0u8; 32];
hkdf.expand(b"OpenPGP X25519", &mut kek)
.map_err(|e| Error::Crypto(format!("HKDF failed: {:?}", e)))?;
let kek_key = Kek::<Aes256>::try_from(kek.as_slice())
.map_err(|e| Error::Crypto(format!("Invalid KEK: {:?}", e)))?;
let unwrapped = kek_key
.unwrap_vec(encrypted_session_key)
.map_err(|e| Error::Crypto(format!("AES unwrap failed: {:?}", e)))?;
Ok(unwrapped)
}
struct CardSigningKey<'a> {
public_params: PublicParams,
fingerprint: Fingerprint,
key_id: pgp::types::KeyId,
algorithm: pgp::crypto::public_key::PublicKeyAlgorithm,
version: pgp::types::KeyVersion,
created_at: Timestamp,
hash_alg: HashAlgorithm,
pin: &'a [u8],
}
impl std::fmt::Debug for CardSigningKey<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CardSigningKey")
.field("fingerprint", &hex::encode(self.fingerprint.as_bytes()))
.field("algorithm", &self.algorithm)
.finish()
}
}
impl pgp::types::KeyDetails for CardSigningKey<'_> {
fn version(&self) -> pgp::types::KeyVersion {
self.version
}
fn legacy_key_id(&self) -> pgp::types::KeyId {
self.key_id
}
fn fingerprint(&self) -> Fingerprint {
self.fingerprint.clone()
}
fn algorithm(&self) -> pgp::crypto::public_key::PublicKeyAlgorithm {
self.algorithm
}
fn created_at(&self) -> Timestamp {
self.created_at
}
fn legacy_v3_expiration_days(&self) -> Option<u16> {
None
}
fn public_params(&self) -> &PublicParams {
&self.public_params
}
}
impl pgp::types::SigningKey for CardSigningKey<'_> {
fn sign(
&self,
_key_pw: &pgp::types::Password,
hash: HashAlgorithm,
data: &[u8],
) -> pgp::errors::Result<pgp::types::SignatureBytes> {
let raw_signature = sign_on_card(data, hash, &self.public_params, self.pin)
.map_err(|e| -> String { e.to_string() })?;
create_signature_bytes(&raw_signature, &self.public_params)
.map_err(|e| -> pgp::errors::Error { e.to_string().into() })
}
fn hash_alg(&self) -> HashAlgorithm {
self.hash_alg
}
}
fn get_primary_key_for_card_signing<'a>(
public_key: &'a SignedPublicKey,
pin: &'a [u8],
) -> Result<CardSigningKey<'a>> {
validate_primary_key_signing_usage(public_key, SigningKeyUsage::KeyMaintenance)?;
let card_fp = get_card_signing_fingerprint()?;
let primary = &public_key.primary_key;
let primary_fp = hex::encode(primary.fingerprint().as_bytes());
if primary_fp != card_fp {
return Err(Error::Crypto(format!(
"Primary key fingerprint {} does not match card signing slot {}",
primary_fp, card_fp
)));
}
let params = primary.public_params();
let hash_alg = select_hash_for_params(params);
Ok(CardSigningKey {
public_params: params.clone(),
fingerprint: primary.fingerprint(),
key_id: primary.legacy_key_id(),
algorithm: primary.algorithm(),
version: primary.version(),
created_at: primary.created_at(),
hash_alg,
pin,
})
}
pub fn update_primary_expiry_on_card(
certdata: &[u8],
expirytime: u64,
pin: &[u8],
) -> Result<Vec<u8>> {
use pgp::types::{Duration as PgpDuration, KeyDetails, Tag};
let public_key = parse_public_key(certdata)?;
let card_signer = get_primary_key_for_card_signing(&public_key, pin)?;
let key_creation = public_key.primary_key.created_at();
let now_ts = Timestamp::now();
let creation_secs: u64 = key_creation.as_secs() as u64;
let now_secs: u64 = now_ts.as_secs() as u64;
let expiry_secs = now_secs.saturating_sub(creation_secs) + expirytime;
let expiry_duration = PgpDuration::from_secs(expiry_secs as u32);
let password = pgp::types::Password::from("");
let update_subpackets =
|existing_config: &SignatureConfig| -> Result<(Vec<Subpacket>, Vec<Subpacket>)> {
let mut new_hashed_subpackets: Vec<Subpacket> = Vec::new();
let mut has_creation_time = false;
let mut has_expiry_time = false;
for subpacket in existing_config.hashed_subpackets() {
match &subpacket.data {
SubpacketData::SignatureCreationTime(_) => {
new_hashed_subpackets.push(
Subpacket::regular(SubpacketData::SignatureCreationTime(
Timestamp::now(),
))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
has_creation_time = true;
}
SubpacketData::KeyExpirationTime(_) => {
new_hashed_subpackets.push(
Subpacket::regular(SubpacketData::KeyExpirationTime(expiry_duration))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
has_expiry_time = true;
}
_ => {
new_hashed_subpackets.push(subpacket.clone());
}
}
}
if !has_creation_time {
new_hashed_subpackets.push(
Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::now()))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
}
if !has_expiry_time {
new_hashed_subpackets.push(
Subpacket::regular(SubpacketData::KeyExpirationTime(expiry_duration))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
}
let new_unhashed_subpackets: Vec<Subpacket> =
existing_config.unhashed_subpackets().cloned().collect();
Ok((new_hashed_subpackets, new_unhashed_subpackets))
};
let mut new_direct_signatures: Vec<pgp::packet::Signature> = Vec::new();
for existing_sig in &public_key.details.direct_signatures {
if existing_sig.typ() == Some(SignatureType::Key) {
let existing_config = existing_sig.config().ok_or_else(|| {
Error::Crypto("Cannot read existing direct signature config".to_string())
})?;
let (new_hashed, new_unhashed) = update_subpackets(existing_config)?;
let mut config = SignatureConfig::v4(
SignatureType::Key,
card_signer.algorithm(),
card_signer.hash_alg,
);
config.hashed_subpackets = new_hashed;
config.unhashed_subpackets = new_unhashed;
let sig = config
.sign_key(&card_signer, &password, &public_key.primary_key)
.map_err(|e| Error::Crypto(e.to_string()))?;
new_direct_signatures.push(sig);
} else {
new_direct_signatures.push(existing_sig.clone());
}
}
let mut new_users: Vec<pgp::types::SignedUser> = Vec::new();
for signed_user in &public_key.details.users {
let existing_self_sig = signed_user
.signatures
.iter()
.find(|sig| sig.is_certification())
.ok_or_else(|| Error::Crypto("No self-certification signature found".to_string()))?;
let existing_config = existing_self_sig
.config()
.ok_or_else(|| Error::Crypto("Cannot read existing signature config".to_string()))?;
let (new_hashed, new_unhashed) = update_subpackets(existing_config)?;
let mut config = SignatureConfig::v4(
existing_config.typ(),
card_signer.algorithm(),
card_signer.hash_alg,
);
config.hashed_subpackets = new_hashed;
config.unhashed_subpackets = new_unhashed;
let sig = config
.sign_certification_third_party(
&card_signer,
&password,
&public_key.primary_key,
Tag::UserId,
&signed_user.id,
)
.map_err(|e| Error::Crypto(e.to_string()))?;
let mut combined_sigs = vec![sig];
for existing_sig in &signed_user.signatures {
if !existing_sig.is_certification() {
combined_sigs.push(existing_sig.clone());
}
}
new_users.push(pgp::types::SignedUser::new(
signed_user.id.clone(),
combined_sigs,
));
}
let updated_key = SignedPublicKey {
primary_key: public_key.primary_key.clone(),
details: pgp::composed::SignedKeyDetails::new(
public_key.details.revocation_signatures.clone(),
new_direct_signatures,
new_users,
public_key.details.user_attributes.clone(),
),
public_subkeys: public_key.public_subkeys.clone(),
};
let armored = crate::internal::public_key_to_armored(&updated_key)?;
Ok(armored.into_bytes())
}
pub fn update_subkeys_expiry_on_card(
certdata: &[u8],
fingerprints: &[&str],
expirytime: u64,
pin: &[u8],
) -> Result<Vec<u8>> {
use pgp::types::{Duration as PgpDuration, KeyDetails};
let public_key = parse_public_key(certdata)?;
let card_signer = get_primary_key_for_card_signing(&public_key, pin)?;
let password = pgp::types::Password::from("");
let normalized_fps: Vec<String> = fingerprints
.iter()
.map(|fp| fp.to_uppercase().replace(" ", ""))
.collect();
let now_ts = Timestamp::now();
let now_secs: u64 = now_ts.as_secs() as u64;
let mut new_public_subkeys = Vec::new();
for subkey in &public_key.public_subkeys {
let subkey_fp = hex::encode(subkey.key.fingerprint().as_bytes()).to_uppercase();
let should_update = normalized_fps
.iter()
.any(|fp| subkey_fp.contains(fp) || fp.contains(&subkey_fp));
if should_update {
let creation_secs: u64 = subkey.key.created_at().as_secs() as u64;
let expiry_secs = now_secs.saturating_sub(creation_secs) + expirytime;
let expiry_duration = PgpDuration::from_secs(expiry_secs as u32);
let existing_binding_sig = subkey
.signatures
.iter()
.find(|sig| sig.typ() == Some(SignatureType::SubkeyBinding))
.ok_or_else(|| {
Error::Crypto("No binding signature found for subkey".to_string())
})?;
let existing_config = existing_binding_sig.config().ok_or_else(|| {
Error::Crypto("Cannot read existing binding signature config".to_string())
})?;
let mut new_hashed_subpackets: Vec<Subpacket> = Vec::new();
let mut has_creation_time = false;
let mut has_expiry_time = false;
for subpacket in existing_config.hashed_subpackets() {
match &subpacket.data {
SubpacketData::SignatureCreationTime(_) => {
new_hashed_subpackets.push(
Subpacket::regular(SubpacketData::SignatureCreationTime(
Timestamp::now(),
))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
has_creation_time = true;
}
SubpacketData::KeyExpirationTime(_) => {
new_hashed_subpackets.push(
Subpacket::regular(SubpacketData::KeyExpirationTime(expiry_duration))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
has_expiry_time = true;
}
_ => {
new_hashed_subpackets.push(subpacket.clone());
}
}
}
if !has_creation_time {
new_hashed_subpackets.push(
Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::now()))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
}
if !has_expiry_time {
new_hashed_subpackets.push(
Subpacket::regular(SubpacketData::KeyExpirationTime(expiry_duration))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
}
let new_unhashed_subpackets: Vec<Subpacket> =
existing_config.unhashed_subpackets().cloned().collect();
let mut config = SignatureConfig::v4(
SignatureType::SubkeyBinding,
card_signer.algorithm(),
card_signer.hash_alg,
);
config.hashed_subpackets = new_hashed_subpackets;
config.unhashed_subpackets = new_unhashed_subpackets;
let sig = config
.sign_subkey_binding(
&card_signer,
&public_key.primary_key,
&password,
&subkey.key,
)
.map_err(|e| Error::Crypto(e.to_string()))?;
let mut new_sigs = vec![sig];
for existing_sig in &subkey.signatures {
if existing_sig.typ() != Some(SignatureType::SubkeyBinding) {
new_sigs.push(existing_sig.clone());
}
}
new_public_subkeys.push(pgp::composed::SignedPublicSubKey {
key: subkey.key.clone(),
signatures: new_sigs,
});
} else {
new_public_subkeys.push(subkey.clone());
}
}
let updated_key = SignedPublicKey {
primary_key: public_key.primary_key.clone(),
details: public_key.details.clone(),
public_subkeys: new_public_subkeys,
};
let armored = crate::internal::public_key_to_armored(&updated_key)?;
Ok(armored.into_bytes())
}
pub fn ssh_authenticate_on_card(data: &[u8], pin: &[u8], ident: Option<&str>) -> Result<Vec<u8>> {
let backend = get_card_backend(ident)?;
let mut card = Card::new(backend)
.map_err(|e| Error::Card(CardError::CommunicationError(e.to_string())))?;
let mut tx = card
.transaction()
.map_err(|e| Error::Card(CardError::CommunicationError(e.to_string())))?;
let pin_utf8 = std::str::from_utf8(pin)
.map_err(|_| Error::Card(CardError::InvalidData("PIN is not valid UTF-8".into())))?;
let mut pin_owned = pin_utf8.to_string();
let pin_secret: SecretString = pin_owned.clone().into();
zeroize::Zeroize::zeroize(&mut pin_owned);
tx.verify_user_pin(pin_secret)
.map_err(|e| Error::Card(CardError::from(e)))?;
let signature = tx
.card()
.internal_authenticate(data.to_vec())
.map_err(|e| {
Error::Card(CardError::CommunicationError(format!(
"Internal authenticate failed: {}",
e
)))
})?;
Ok(signature)
}
pub fn ssh_authenticate_for_hash_on_card(
hash: Hash,
pin: &[u8],
ident: Option<&str>,
) -> Result<Vec<u8>> {
let backend = get_card_backend(ident)?;
let mut card = Card::new(backend)
.map_err(|e| Error::Card(CardError::CommunicationError(e.to_string())))?;
let mut tx = card
.transaction()
.map_err(|e| Error::Card(CardError::CommunicationError(e.to_string())))?;
let pin_utf8 = std::str::from_utf8(pin)
.map_err(|_| Error::Card(CardError::InvalidData("PIN is not valid UTF-8".into())))?;
let mut pin_owned = pin_utf8.to_string();
let pin_secret: SecretString = pin_owned.clone().into();
zeroize::Zeroize::zeroize(&mut pin_owned);
tx.verify_user_pin(pin_secret)
.map_err(|e| Error::Card(CardError::from(e)))?;
let signature = tx.card().authenticate_for_hash(hash).map_err(|e| {
Error::Card(CardError::CommunicationError(format!(
"Authenticate for hash failed: {}",
e
)))
})?;
Ok(signature)
}
#[cfg(test)]
mod tests {
}