1use {
2 openpgp_card::{
3 algorithm::{Algo, Curve},
4 card_do::{
5 ApplicationIdentifier,
6 KeyStatus,
7 },
8 crypto_data::{EccType, PublicKeyMaterial, Hash},
9 Error as OpenpgpCardError,
10 KeyType,
11 OpenPgp,
12 StatusBytes,
13 },
14 openpgp_card_pcsc::PcscBackend,
15 serde::{Serialize, Deserialize},
16 std::cell::RefCell,
17 thiserror::Error,
18};
19
20pub struct OpenpgpCard {
21 pgp: RefCell<OpenPgp>,
22}
23
24impl From<PcscBackend> for OpenpgpCard {
25 fn from(backend: PcscBackend) -> Self {
26 let pgp = OpenPgp::new::<PcscBackend>(backend.into());
27 Self { pgp: RefCell::new(pgp) }
28 }
29}
30
31impl TryFrom<&String> for OpenpgpCard {
32 type Error = CardErrorWrapper;
33
34 fn try_from(ident: &String) -> Result<Self, Self::Error> {
35 if ident.len() != 32 {
36 return Err(Self::Error::AIDParseError("OpenPGP AID must be 32-digit hex string".to_string()));
37 }
38 let mut ident_bytes = Vec::<u8>::new();
39 for i in (0..ident.len()).step_by(2) {
40 ident_bytes.push(u8::from_str_radix(&ident[i..i + 2], 16).map_err(
41 |_| Self::Error::AIDParseError("non-hex character found in identifier".to_string())
42 )?);
43 }
44 let aid = ApplicationIdentifier::try_from(ident_bytes.as_slice()).map_err(
45 |e| Self::Error::AIDParseError(e.to_string())
46 )?;
47 let backend = PcscBackend::open_by_ident(aid.ident().as_str(), None).map_err(
48 |_| Self::Error::CardNotFound(ident.to_string())
49 )?;
50 Ok(backend.into())
51 }
52}
53
54#[derive(Serialize, Deserialize, Debug)]
55#[serde(rename_all = "camelCase")]
56pub struct OpenpgpCardInfo {
57 pub manufacturer: String,
58 pub serial_number: String,
59 pub aid: String,
60 pub signing_algo: String,
61 pub pubkey_bytes: Vec<u8>,
62}
63
64impl OpenpgpCard {
65 pub fn get_info(&self) -> Result<OpenpgpCardInfo, CardErrorWrapper> {
66 let mut pgp_mut = self.pgp.borrow_mut();
67 let opt = &mut pgp_mut.transaction()?;
68 let ard = opt.application_related_data()?;
69 let aid = ard.application_id()?;
70
71 let mut signing_key_exists = false;
72 if let Some(key_info) = ard.key_information()? {
73 match key_info.sig_status() {
74 KeyStatus::Generated | KeyStatus::Imported => signing_key_exists = true,
75 _ => (),
76 };
77 } else {
78 return Err(CardErrorWrapper::InternalError("could not get key information".to_string()));
79 }
80
81 let mut signing_algo: String = "null".to_string();
82 let mut pubkey = Vec::<u8>::new();
83 if signing_key_exists {
84 signing_algo = ard.algorithm_attributes(KeyType::Signing)?.to_string();
85 let pk_material = opt.public_key(KeyType::Signing)?;
86 pubkey = get_pubkey_from_pk_material(pk_material)?;
87 }
88
89 Ok(OpenpgpCardInfo {
90 manufacturer: aid.manufacturer_name().to_string(),
91 serial_number: format!("{:08x}", aid.serial()),
92 aid: aid.to_string().replace(" ", ""),
93 signing_algo: signing_algo,
94 pubkey_bytes: pubkey,
95 })
96 }
97
98 pub fn get_pubkey(&self) -> Result<Vec<u8>, CardErrorWrapper> {
99 let mut pgp_mut = self.pgp.borrow_mut();
100 let opt = &mut pgp_mut.transaction()?;
101 let ard = opt.application_related_data()?;
102
103 let mut signing_key_exists = false;
104 if let Some(key_info) = ard.key_information()? {
105 match key_info.sig_status() {
106 KeyStatus::Generated | KeyStatus::Imported => signing_key_exists = true,
107 _ => (),
108 };
109 } else {
110 return Err(CardErrorWrapper::InternalError("could not get key information".to_string()));
111 }
112
113 let mut pubkey = Vec::<u8>::new();
114 if signing_key_exists {
115 let pk_material = opt.public_key(KeyType::Signing)?;
116 pubkey = get_pubkey_from_pk_material(pk_material)?;
117 }
118
119 Ok(pubkey)
120 }
121
122 pub fn sign_message<T>(
123 &self,
124 message: &[u8],
125 pin: &[u8],
126 touch_confirm_callback: T,
127 ) -> Result<Vec<u8>, CardErrorWrapper>
128 where T: Fn() -> () {
129 let mut pgp_mut = self.pgp.borrow_mut();
130 let opt = &mut pgp_mut.transaction()?;
131
132 let ard = opt.application_related_data()?;
134 if let Some(key_info) = ard.key_information()? {
135 match key_info.sig_status() {
136 KeyStatus::NotPresent | KeyStatus::Unknown(_) => return Err(CardErrorWrapper::SigningKeyNotFound),
137 _ => (),
138 };
139 } else {
140 return Err(CardErrorWrapper::InternalError("could not get key information".to_string()));
141 }
142
143 opt.verify_pw1_sign(pin).map_err(
144 |e| match e {
145 OpenpgpCardError::CardStatus(StatusBytes::IncorrectParametersCommandDataField) => {
146 CardErrorWrapper::InvalidPin
147 },
148 _ => CardErrorWrapper::from(e),
149 }
150 )?;
151
152 let ard = opt.application_related_data()?;
156 if let Some(signing_uif) = ard.uif_pso_cds()? {
157 if signing_uif.touch_policy().touch_required() {
158 touch_confirm_callback();
159 }
160 }
161
162 let hash = Hash::EdDSA(message);
164 let sig = opt.signature_for_hash(hash).map_err(
165 |e| match e {
166 OpenpgpCardError::CardStatus(StatusBytes::SecurityRelatedIssues) => {
167 CardErrorWrapper::TouchConfirmationTimeout
168 },
169 _ => CardErrorWrapper::from(e),
170 }
171 )?;
172
173 Ok(sig)
174 }
175}
176
177#[derive(Serialize, Deserialize, Clone, Debug, Error, PartialEq, Eq)]
178pub enum CardErrorWrapper {
179 #[error("error parsing AID: {0}")]
180 AIDParseError(String),
181 #[error("could not find card with AID {0}")]
182 CardNotFound(String),
183 #[error("internal OpenPGP error: {0}")]
184 InternalError(String),
185 #[error("no signing key found on card")]
186 SigningKeyNotFound,
187 #[error("invalid PIN")]
188 InvalidPin,
189 #[error("touch confirmation timed out")]
190 TouchConfirmationTimeout,
191}
192
193impl From<OpenpgpCardError> for CardErrorWrapper {
194 fn from(e: OpenpgpCardError) -> Self {
195 CardErrorWrapper::InternalError(e.to_string())
196 }
197}
198
199fn get_pubkey_from_pk_material(pk_material: PublicKeyMaterial) -> Result<Vec<u8>, OpenpgpCardError> {
200 let pk_bytes: [u8; 32] = match pk_material {
201 PublicKeyMaterial::E(pk) => match pk.algo() {
202 Algo::Ecc(ecc_attrs) => {
203 if ecc_attrs.ecc_type() != EccType::EdDSA || ecc_attrs.curve() != Curve::Ed25519 {
204 return Err(OpenpgpCardError::UnsupportedAlgo(
205 format!("expected Ed25519 key, got {:?}", ecc_attrs.curve())
206 ));
207 }
208 pk.data().try_into().map_err(
209 |e| OpenpgpCardError::ParseError(format!("key on card is malformed: {}", e))
210 )?
211 },
212 _ => return Err(OpenpgpCardError::UnsupportedAlgo("expected ECC key, got RSA".to_string())),
213 }
214 _ => return Err(OpenpgpCardError::UnsupportedAlgo("expected ECC key, got RSA".to_string())),
215 };
216 Ok(pk_bytes.to_vec())
217}