use std::io::Cursor;
use crate::error::{Error, Result};
use crate::pgp::composed::{Deserializable, SignedSecretKey};
use crate::pgp::types::{KeyDetails, Password, PlainSecretParams, PublicParams};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CardKeySlot {
Signing,
Decryption,
Authentication,
}
impl CardKeySlot {
fn crt_tag(&self) -> &[u8] {
match self {
CardKeySlot::Decryption => &[0xB8, 0x00],
CardKeySlot::Signing => &[0xB6, 0x00],
CardKeySlot::Authentication => &[0xA4, 0x00],
}
}
fn algo_p2(&self) -> u8 {
match self {
CardKeySlot::Decryption => 0xC2,
CardKeySlot::Signing => 0xC1,
CardKeySlot::Authentication => 0xC3,
}
}
fn fp_p2(&self) -> u8 {
match self {
CardKeySlot::Decryption => 0xC8,
CardKeySlot::Signing => 0xC7,
CardKeySlot::Authentication => 0xC9,
}
}
fn time_p2(&self) -> u8 {
match self {
CardKeySlot::Decryption => 0xCF,
CardKeySlot::Signing => 0xCE,
CardKeySlot::Authentication => 0xD0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeySelection {
Auto,
Primary,
ByFingerprint,
}
pub fn upload_key_to_card(
secret_key_data: &[u8],
key_password: &[u8],
slot: CardKeySlot,
admin_pin: &[u8],
) -> Result<()> {
let secret_key = parse_secret_key(secret_key_data)?;
let password = if key_password.is_empty() {
Password::empty()
} else {
Password::from(std::str::from_utf8(key_password)
.map_err(|_| Error::Parse("Password must be valid UTF-8".to_string()))?)
};
let key_info = find_key_for_slot(&secret_key, &password, slot)?;
upload_with_talktosc(&key_info, slot, admin_pin)
}
pub fn upload_primary_key_to_card(
secret_key_data: &[u8],
key_password: &[u8],
slot: CardKeySlot,
admin_pin: &[u8],
) -> Result<()> {
let secret_key = parse_secret_key(secret_key_data)?;
let password = if key_password.is_empty() {
Password::empty()
} else {
Password::from(std::str::from_utf8(key_password)
.map_err(|_| Error::Parse("Password must be valid UTF-8".to_string()))?)
};
let key_info = extract_primary_key_info(&secret_key, &password)?;
upload_with_talktosc(&key_info, slot, admin_pin)
}
pub fn upload_subkey_by_fingerprint(
secret_key_data: &[u8],
key_password: &[u8],
fingerprint: &str,
slot: CardKeySlot,
admin_pin: &[u8],
) -> Result<()> {
let secret_key = parse_secret_key(secret_key_data)?;
let password = if key_password.is_empty() {
Password::empty()
} else {
Password::from(std::str::from_utf8(key_password)
.map_err(|_| Error::Parse("Password must be valid UTF-8".to_string()))?)
};
let fp_normalized: String = fingerprint
.chars()
.filter(|c| !c.is_whitespace())
.collect::<String>()
.to_lowercase();
let primary_fp = hex::encode(secret_key.primary_key.fingerprint().as_bytes());
if primary_fp == fp_normalized {
let key_info = extract_primary_key_info(&secret_key, &password)?;
return upload_with_talktosc(&key_info, slot, admin_pin);
}
for subkey in &secret_key.secret_subkeys {
let subkey_fp = hex::encode(subkey.key.fingerprint().as_bytes());
if subkey_fp == fp_normalized {
let timestamp = subkey.key.created_at().as_secs() as u32;
let fp_bytes = subkey.key.fingerprint().as_bytes().to_vec();
let key_info = subkey.key.unlock(&password, |pub_p, priv_key| {
extract_key_info(pub_p, priv_key, timestamp, fp_bytes.clone())
}).map_err(|e| Error::Crypto(e.to_string()))??;
return upload_with_talktosc(&key_info, slot, admin_pin);
}
}
Err(Error::Crypto(format!(
"No key found with fingerprint: {}",
fingerprint
)))
}
fn extract_primary_key_info(
secret_key: &SignedSecretKey,
password: &Password,
) -> Result<KeyUploadInfo> {
let primary = &secret_key.primary_key;
let timestamp = primary.created_at().as_secs() as u32;
let fingerprint = primary.fingerprint().as_bytes().to_vec();
primary.unlock(password, |pub_p, priv_key| {
extract_key_info(pub_p, priv_key, timestamp, fingerprint.clone())
}).map_err(|e| Error::Crypto(e.to_string()))?
.map_err(|e| Error::Crypto(e.to_string()))
}
struct KeyUploadInfo {
key_type: KeyType,
scalar: Vec<u8>, fingerprint: Vec<u8>,
timestamp: u32,
n_bits: Option<u16>,
e_bits: Option<u16>,
e_value: Option<Vec<u8>>,
p_value: Option<Vec<u8>>,
q_value: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Copy)]
enum KeyType {
Ed25519,
Cv25519,
Rsa,
EcdsaP256,
EcdsaP384,
EcdsaP521,
EcdhP256,
EcdhP384,
EcdhP521,
}
fn parse_secret_key(data: &[u8]) -> Result<SignedSecretKey> {
match SignedSecretKey::from_armor_single(Cursor::new(data)) {
Ok((key, _headers)) => Ok(key),
Err(_) => {
SignedSecretKey::from_bytes(data)
.map_err(|e| Error::Parse(e.to_string()))
}
}
}
fn find_key_for_slot(
secret_key: &SignedSecretKey,
password: &Password,
slot: CardKeySlot,
) -> Result<KeyUploadInfo> {
match slot {
CardKeySlot::Signing | CardKeySlot::Authentication => find_signing_key(secret_key, password),
CardKeySlot::Decryption => find_encryption_key(secret_key, password),
}
}
fn is_signing_algorithm(params: &PublicParams) -> bool {
matches!(params,
PublicParams::RSA(_) |
PublicParams::EdDSALegacy(_) |
PublicParams::Ed25519(_) |
PublicParams::ECDSA(_)
)
}
fn is_encryption_algorithm(params: &PublicParams) -> bool {
matches!(params,
PublicParams::RSA(_) |
PublicParams::ECDH(_) |
PublicParams::X25519(_)
)
}
fn find_signing_key(
secret_key: &SignedSecretKey,
password: &Password,
) -> Result<KeyUploadInfo> {
for subkey in &secret_key.secret_subkeys {
let pub_params = subkey.key.public_params();
if !is_signing_algorithm(pub_params) {
continue;
}
let has_signing_flag = subkey.signatures.iter().any(|sig| {
sig.key_flags().sign()
});
if !has_signing_flag {
continue;
}
let timestamp = subkey.key.created_at().as_secs() as u32;
let fingerprint = subkey.key.fingerprint().as_bytes().to_vec();
let info = subkey.key.unlock(password, |pub_p, priv_key| {
extract_key_info(pub_p, priv_key, timestamp, fingerprint.clone())
}).map_err(|e| Error::Crypto(e.to_string()))??;
return Ok(info);
}
let primary = &secret_key.primary_key;
let pub_params = primary.public_params();
if is_signing_algorithm(pub_params) {
let timestamp = primary.created_at().as_secs() as u32;
let fingerprint = primary.fingerprint().as_bytes().to_vec();
let info = primary.unlock(password, |pub_p, priv_key| {
extract_key_info(pub_p, priv_key, timestamp, fingerprint.clone())
}).map_err(|e| Error::Crypto(e.to_string()))??;
return Ok(info);
}
Err(Error::Crypto("No signing-capable key found".to_string()))
}
fn find_encryption_key(
secret_key: &SignedSecretKey,
password: &Password,
) -> Result<KeyUploadInfo> {
for subkey in &secret_key.secret_subkeys {
let pub_params = subkey.key.public_params();
if !is_encryption_algorithm(pub_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 {
continue;
}
let timestamp = subkey.key.created_at().as_secs() as u32;
let fingerprint = subkey.key.fingerprint().as_bytes().to_vec();
let info = subkey.key.unlock(password, |pub_p, priv_key| {
extract_key_info(pub_p, priv_key, timestamp, fingerprint.clone())
}).map_err(|e| Error::Crypto(e.to_string()))??;
return Ok(info);
}
let primary = &secret_key.primary_key;
let pub_params = primary.public_params();
if is_encryption_algorithm(pub_params) {
let timestamp = primary.created_at().as_secs() as u32;
let fingerprint = primary.fingerprint().as_bytes().to_vec();
let info = primary.unlock(password, |pub_p, priv_key| {
extract_key_info(pub_p, priv_key, timestamp, fingerprint.clone())
}).map_err(|e| Error::Crypto(e.to_string()))??;
return Ok(info);
}
Err(Error::Crypto("No encryption-capable key found".to_string()))
}
fn extract_key_info(
pub_params: &PublicParams,
priv_params: &PlainSecretParams,
timestamp: u32,
fingerprint: Vec<u8>,
) -> crate::pgp::errors::Result<KeyUploadInfo> {
use rsa::traits::PublicKeyParts;
match (pub_params, priv_params) {
(PublicParams::EdDSALegacy(_), PlainSecretParams::Ed25519Legacy(ed_priv)) |
(PublicParams::Ed25519(_), PlainSecretParams::Ed25519(ed_priv)) => {
Ok(KeyUploadInfo {
key_type: KeyType::Ed25519,
scalar: ed_priv.to_bytes().to_vec(),
fingerprint,
timestamp,
n_bits: None,
e_bits: None,
e_value: None,
p_value: None,
q_value: None,
})
}
(PublicParams::ECDH(ecdh_pub), PlainSecretParams::ECDH(ecdh_priv)) => {
use crate::pgp::types::EcdhPublicParams;
match ecdh_pub {
EcdhPublicParams::Curve25519 { .. } => {
let scalar_le = ecdh_priv.to_bytes();
let scalar_be: Vec<u8> = scalar_le.iter().rev().copied().collect();
Ok(KeyUploadInfo {
key_type: KeyType::Cv25519,
scalar: scalar_be,
fingerprint,
timestamp,
n_bits: None,
e_bits: None,
e_value: None,
p_value: None,
q_value: None,
})
}
EcdhPublicParams::P256 { .. } => {
Ok(KeyUploadInfo {
key_type: KeyType::EcdhP256,
scalar: ecdh_priv.to_bytes(),
fingerprint,
timestamp,
n_bits: None,
e_bits: None,
e_value: None,
p_value: None,
q_value: None,
})
}
EcdhPublicParams::P384 { .. } => {
Ok(KeyUploadInfo {
key_type: KeyType::EcdhP384,
scalar: ecdh_priv.to_bytes(),
fingerprint,
timestamp,
n_bits: None,
e_bits: None,
e_value: None,
p_value: None,
q_value: None,
})
}
EcdhPublicParams::P521 { .. } => {
Ok(KeyUploadInfo {
key_type: KeyType::EcdhP521,
scalar: ecdh_priv.to_bytes(),
fingerprint,
timestamp,
n_bits: None,
e_bits: None,
e_value: None,
p_value: None,
q_value: None,
})
}
_ => Err(crate::pgp::errors::format_err!("Unsupported ECDH curve for card")),
}
}
(PublicParams::RSA(rsa_pub), PlainSecretParams::RSA(rsa_priv)) => {
let (d, p, q, _u) = rsa_priv.to_bytes();
let n = rsa_pub.key.n();
let e = rsa_pub.key.e();
Ok(KeyUploadInfo {
key_type: KeyType::Rsa,
scalar: d, fingerprint,
timestamp,
n_bits: Some(n.bits() as u16),
e_bits: Some(e.bits() as u16),
e_value: Some(e.to_bytes_be()),
p_value: Some(p),
q_value: Some(q),
})
}
(PublicParams::ECDSA(ecdsa_pub), PlainSecretParams::ECDSA(ecdsa_priv)) => {
use crate::pgp::types::EcdsaPublicParams;
let key_type = match ecdsa_pub {
EcdsaPublicParams::P256 { .. } => KeyType::EcdsaP256,
EcdsaPublicParams::P384 { .. } => KeyType::EcdsaP384,
EcdsaPublicParams::P521 { .. } => KeyType::EcdsaP521,
_ => return Err(crate::pgp::errors::format_err!("Unsupported ECDSA curve for card")),
};
Ok(KeyUploadInfo {
key_type,
scalar: ecdsa_priv.to_bytes(),
fingerprint,
timestamp,
n_bits: None,
e_bits: None,
e_value: None,
p_value: None,
q_value: None,
})
}
_ => Err(crate::pgp::errors::format_err!(
"Unsupported key type for card upload"
)),
}
}
fn upload_with_talktosc(
key_info: &KeyUploadInfo,
slot: CardKeySlot,
admin_pin: &[u8],
) -> Result<()> {
use talktosc::apdus::APDU;
use talktosc::{create_connection, send_and_parse, disconnect};
let card = create_connection().map_err(|e| {
Error::Card(super::types::CardError::CommunicationError(
format!("Failed to connect to card: {}", e)
))
})?;
let select_apdu = APDU::new(0x00, 0xA4, 0x04, 0x00, Some(vec![0xD2, 0x76, 0x00, 0x01, 0x24, 0x01]));
let resp = send_and_parse(&card, select_apdu);
if resp.is_err() {
disconnect(card);
return Err(Error::Card(super::types::CardError::CommunicationError(
"Failed to select OpenPGP applet".to_string()
)));
}
let pw3_apdu = talktosc::apdus::create_apdu_verify_pw3(admin_pin.to_vec());
let resp = send_and_parse(&card, pw3_apdu);
if resp.is_err() {
disconnect(card);
return Err(Error::Card(super::types::CardError::PinIncorrect {
retries_remaining: 3,
}));
}
let algo_attrs = build_algo_attributes(key_info);
let algo_apdu = APDU::create_big_apdu(0x00, 0xDA, 0x00, slot.algo_p2(), algo_attrs);
let resp = send_and_parse(&card, algo_apdu);
if resp.is_err() {
disconnect(card);
return Err(Error::Card(super::types::CardError::CommunicationError(
"Failed to set algorithm attributes".to_string()
)));
}
let pw3_apdu = talktosc::apdus::create_apdu_verify_pw3(admin_pin.to_vec());
let _ = send_and_parse(&card, pw3_apdu);
let key_data = build_key_import_data(key_info, slot)?;
let import_apdu = APDU::create_big_apdu(0x00, 0xDB, 0x3F, 0xFF, key_data);
let resp = send_and_parse(&card, import_apdu);
if resp.is_err() {
disconnect(card);
return Err(Error::Card(super::types::CardError::CommunicationError(
"Failed to import key".to_string()
)));
}
let fp_apdu = APDU::create_big_apdu(0x00, 0xDA, 0x00, slot.fp_p2(), key_info.fingerprint.clone());
let resp = send_and_parse(&card, fp_apdu);
if resp.is_err() {
disconnect(card);
return Err(Error::Card(super::types::CardError::CommunicationError(
"Failed to set fingerprint".to_string()
)));
}
let time_value: Vec<u8> = key_info.timestamp
.to_be_bytes()
.iter()
.skip_while(|&&e| e == 0)
.copied()
.collect();
let time_apdu = APDU::new(0x00, 0xDA, 0x00, slot.time_p2(), Some(time_value));
let resp = send_and_parse(&card, time_apdu);
if resp.is_err() {
disconnect(card);
return Err(Error::Card(super::types::CardError::CommunicationError(
"Failed to set timestamp".to_string()
)));
}
disconnect(card);
Ok(())
}
fn build_algo_attributes(key_info: &KeyUploadInfo) -> Vec<u8> {
match key_info.key_type {
KeyType::Ed25519 => {
vec![0x16, 0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01]
}
KeyType::Cv25519 => {
vec![0x12, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01]
}
KeyType::EcdsaP256 => {
vec![0x13, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]
}
KeyType::EcdsaP384 => {
vec![0x13, 0x2B, 0x81, 0x04, 0x00, 0x22]
}
KeyType::EcdsaP521 => {
vec![0x13, 0x2B, 0x81, 0x04, 0x00, 0x23]
}
KeyType::EcdhP256 => {
vec![0x12, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]
}
KeyType::EcdhP384 => {
vec![0x12, 0x2B, 0x81, 0x04, 0x00, 0x22]
}
KeyType::EcdhP521 => {
vec![0x12, 0x2B, 0x81, 0x04, 0x00, 0x23]
}
KeyType::Rsa => {
let mut attrs = vec![0x01];
if let Some(n_bits) = key_info.n_bits {
attrs.extend(n_bits.to_be_bytes());
}
if let Some(e_bits) = key_info.e_bits {
attrs.extend(e_bits.to_be_bytes());
}
attrs.push(0x00);
attrs
}
}
}
fn build_key_import_data(key_info: &KeyUploadInfo, slot: CardKeySlot) -> Result<Vec<u8>> {
let mut for4d: Vec<u8> = vec![0x4D];
match key_info.key_type {
KeyType::Ed25519 | KeyType::Cv25519 | KeyType::EcdsaP256 | KeyType::EcdsaP384 | KeyType::EcdsaP521 |
KeyType::EcdhP256 | KeyType::EcdhP384 | KeyType::EcdhP521 => {
let mut for5f48: Vec<u8> = vec![0x5F, 0x48];
let scalar_len = key_info.scalar.len();
if scalar_len > 0x7F {
for5f48.push(0x81);
for5f48.push(scalar_len as u8);
} else {
for5f48.push(scalar_len as u8);
}
for5f48.extend(&key_info.scalar);
let mut for7f48 = vec![0x7F, 0x48];
if scalar_len > 0x7F {
for7f48.extend([0x03, 0x92, 0x81, scalar_len as u8]);
} else {
for7f48.extend([0x02, 0x92, scalar_len as u8]);
}
let mut maindata: Vec<u8> = slot.crt_tag().to_vec();
maindata.extend(&for7f48);
maindata.extend(&for5f48);
let maindata_len = maindata.len();
if maindata_len > 0x7F {
for4d.push(0x81);
}
for4d.push(maindata_len as u8);
for4d.extend(maindata);
}
KeyType::Rsa => {
let mut result: Vec<u8> = Vec::new();
if let Some(ref e) = key_info.e_value {
result.extend(e);
}
if let Some(ref p) = key_info.p_value {
result.extend(p);
}
if let Some(ref q) = key_info.q_value {
result.extend(q);
}
let mut for5f48: Vec<u8> = vec![0x5F, 0x48];
let len = result.len() as u16;
if len > 0xFF {
for5f48.push(0x82);
} else {
for5f48.push(0x81);
}
let length = len.to_be_bytes();
for5f48.push(length[0]);
for5f48.push(length[1]);
for5f48.extend(result);
let for7f48 = vec![
0x7F, 0x48, 0x0A, 0x91, 0x03, 0x92, 0x82, 0x01, 0x00, 0x93, 0x82, 0x01, 0x00,
];
let mut maindata: Vec<u8> = slot.crt_tag().to_vec();
maindata.extend(&for7f48);
maindata.extend(&for5f48);
let len = maindata.len() as u16;
if len > 0xFF {
for4d.push(0x82);
} else {
for4d.push(0x81);
}
let length = len.to_be_bytes();
for4d.push(length[0]);
for4d.push(length[1]);
for4d.extend(maindata);
}
}
Ok(for4d)
}