bloss_native/
card.rs

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        // Check if signing key exists
133        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        // Await user touch confirmation if and only if
153        //   * Card supports touch confirmation, and
154        //   * Touch policy set anything other than "off".
155        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        // Delegate message signing to card
163        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}