openpgp_card_rpgp/
cardslot.rs

1// SPDX-FileCopyrightText: Wiktor Kwapisiewicz <wiktor@metacode.biz>
2// SPDX-FileCopyrightText: Heiko Schaefer <heiko@schaefer.name>
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5use std::fmt::{Debug, Formatter};
6use std::sync::Mutex;
7
8use chrono::{DateTime, Utc};
9use openpgp_card::ocard::crypto::Hash;
10use openpgp_card::ocard::KeyType;
11use openpgp_card::state::Transaction;
12use openpgp_card::Card;
13use pgp::crypto::checksum;
14use pgp::crypto::ecc_curve::ECCCurve;
15use pgp::crypto::hash::HashAlgorithm;
16use pgp::crypto::public_key::PublicKeyAlgorithm;
17use pgp::crypto::sym::SymmetricKeyAlgorithm;
18use pgp::packet::PublicKey;
19use pgp::types::{
20    EcdhPublicParams, EcdsaPublicParams, Fingerprint, KeyId, KeyVersion, Mpi, PkeskBytes,
21    PublicKeyTrait, PublicParams, SecretKeyTrait, SignatureBytes,
22};
23use pgp::{Esk, Message, PlainSessionKey};
24use rand::{CryptoRng, Rng};
25
26use crate::rpgp::map_card_err;
27
28/// An individual OpenPGP card key slot, which can be used for private key operations.
29pub struct CardSlot<'cs, 't> {
30    tx: Mutex<&'cs mut Card<Transaction<'t>>>,
31
32    // Which key slot does this OpenPGP card operate on
33    key_type: KeyType,
34
35    // The public key material that corresponds to the key slot of this signer
36    //
37    // The distinction between primary and subkey is irrelevant here, but we have to use some type.
38    // So we model the key data as a public primary key packet.
39    public_key: PublicKey,
40
41    touch_prompt: &'cs (dyn Fn() + Send + Sync),
42}
43
44impl<'cs, 't> CardSlot<'cs, 't> {
45    /// Set up a CardSigner for the card behind `tx`, using the key slot for `key_type`.
46    ///
47    /// Initializes the CardSigner based on public key information obtained from `public_key`.
48    pub fn with_public_key(
49        tx: &'cs mut Card<Transaction<'t>>,
50        key_type: KeyType,
51        public_key: PublicKey,
52        touch_prompt: &'cs (dyn Fn() + Send + Sync),
53    ) -> Result<Self, pgp::errors::Error> {
54        // FIXME: compare the fingerprint between card slot and public_key?
55
56        Ok(Self {
57            tx: Mutex::new(tx),
58            public_key,
59            key_type,
60            touch_prompt,
61        })
62    }
63
64    /// Set up a CardSlot for the card behind `tx`, using the key slot for `key_type`.
65    ///
66    /// Initializes the CardSigner based on public key information obtained from the card.
67    pub fn init_from_card(
68        tx: &'cs mut Card<Transaction<'t>>,
69        key_type: KeyType,
70        touch_prompt: &'cs (dyn Fn() + Send + Sync),
71    ) -> Result<Self, pgp::errors::Error> {
72        let pk = crate::rpgp::pubkey_from_card(tx, key_type)?;
73
74        Self::with_public_key(tx, key_type, pk, touch_prompt)
75    }
76}
77
78impl CardSlot<'_, '_> {
79    /// The OpenPGP public key material that corresponds to the key in this CardSlot
80    pub fn public_key(&self) -> &PublicKey {
81        &self.public_key
82    }
83
84    /// The card slot that this CardSlot uses
85    pub fn key_type(&self) -> KeyType {
86        self.key_type
87    }
88
89    fn touch_required(&self, tx: &mut Card<Transaction<'_>>) -> bool {
90        // Touch is required if:
91        // - the card supports the feature
92        // - and the policy is set to a value other than 'Off'
93        if let Ok(Some(uif)) = tx.user_interaction_flag(self.key_type) {
94            uif.touch_policy().touch_required()
95        } else {
96            false
97        }
98    }
99
100    pub fn decrypt(
101        &self,
102        values: &PkeskBytes,
103    ) -> pgp::errors::Result<(Vec<u8>, SymmetricKeyAlgorithm)> {
104        #[allow(clippy::unwrap_used)]
105        let mut tx = self.tx.lock().unwrap();
106
107        let decrypted_key = match (self.public_key.public_params(), values) {
108            (PublicParams::RSA { n, .. }, PkeskBytes::Rsa { mpi }) => {
109                let mut ciphertext = mpi.to_vec();
110
111                // RSA modulus length. We use this length to pad the ciphertext, in case it was
112                // zero truncated.
113                //
114                // FIXME: There might be a problematic corner case when the modulus itself is
115                // zero-stripped, and as a consequence we don't pad enough?
116                // (We might want to use rounding to "round" to a typical RSA modulus size?)
117                let modulus_len = n.len();
118
119                // Left zero-pad `ciphertext` to the length of the RSA modulus
120                // (that is: if the ciphertext was zero-stripped, undo that stripping).
121                //
122                // This padding is not required in most cases. Typically, the ciphertext and
123                // modulus should be the same length. However, there is a 1:256 likelihood that
124                // the ciphertext happens to start with a 0x0 byte, which OpenPGP will truncate.
125                while modulus_len > ciphertext.len() {
126                    ciphertext.insert(0, 0u8);
127                }
128
129                let cryptogram = openpgp_card::ocard::crypto::Cryptogram::RSA(&ciphertext);
130
131                if self.touch_required(&mut tx) {
132                    (self.touch_prompt)();
133                }
134
135                tx.card().decipher(cryptogram).map_err(|e| {
136                    pgp::errors::Error::Message(format!(
137                        "RSA decipher operation on card failed: {}",
138                        e
139                    ))
140                })?
141            }
142
143            (
144                PublicParams::ECDH(EcdhPublicParams::Known {
145                    curve,
146                    alg_sym,
147                    hash,
148                    ..
149                }),
150                PkeskBytes::Ecdh {
151                    public_point,
152                    encrypted_session_key,
153                },
154            ) => {
155                let ciphertext = public_point.as_bytes();
156
157                let ciphertext = if *curve == ECCCurve::Curve25519 {
158                    assert_eq!(
159                        ciphertext[0], 0x40,
160                        "Unexpected shape of Cv25519 encrypted data"
161                    );
162
163                    // Strip trailing 0x40
164                    &ciphertext[1..]
165                } else {
166                    // For NIST and brainpool: we decrypt the ciphertext as is
167                    ciphertext
168                };
169
170                let cryptogram = openpgp_card::ocard::crypto::Cryptogram::ECDH(ciphertext);
171
172                if self.touch_required(&mut tx) {
173                    (self.touch_prompt)();
174                }
175
176                let shared_secret: Vec<u8> = tx.card().decipher(cryptogram).map_err(|e| {
177                    pgp::errors::Error::Message(format!(
178                        "ECDH decipher operation on card failed {}",
179                        e
180                    ))
181                })?;
182
183                let encrypted_key_len = encrypted_session_key.len();
184
185                let decrypted_key: Vec<u8> = pgp::crypto::ecdh::derive_session_key(
186                    &shared_secret,
187                    encrypted_session_key,
188                    encrypted_key_len,
189                    &(curve.clone(), *alg_sym, *hash),
190                    self.public_key.fingerprint().as_bytes(),
191                )?;
192
193                decrypted_key
194            }
195
196            pp => {
197                return Err(pgp::errors::Error::Message(format!(
198                    "decrypt: Unsupported key type  {:?}",
199                    pp
200                )));
201            }
202        };
203
204        // strip off the leading session key algorithm octet, and the two trailing checksum octets
205        let dec_len = decrypted_key.len();
206        let (sessionkey, checksum) = (
207            &decrypted_key[1..dec_len - 2],
208            &decrypted_key[dec_len - 2..],
209        );
210
211        // ... check the checksum, while we have it at hand
212        checksum::simple(checksum, sessionkey)?;
213
214        let session_key_algorithm = decrypted_key[0].into();
215        Ok((sessionkey.to_vec(), session_key_algorithm))
216    }
217
218    pub fn decrypt_message(&self, message: &Message) -> Result<Message, pgp::errors::Error> {
219        let Message::Encrypted { esk, edata } = message else {
220            return Err(pgp::errors::Error::Message(
221                "message must be Message::Encrypted".to_string(),
222            ));
223        };
224
225        let values = match &esk[0] {
226            Esk::PublicKeyEncryptedSessionKey(ref k) => k.values()?,
227            _ => {
228                return Err(pgp::errors::Error::Message(
229                    "Expected PublicKeyEncryptedSessionKey".to_string(),
230                ))
231            }
232        };
233
234        let (session_key, session_key_algorithm) =
235            self.unlock(String::new, |priv_key| priv_key.decrypt(values))?;
236
237        let plain_session_key = PlainSessionKey::V3_4 {
238            key: session_key,
239            sym_alg: session_key_algorithm,
240        };
241
242        let decrypted = edata.decrypt(plain_session_key)?;
243
244        Ok(decrypted)
245    }
246}
247
248impl Debug for CardSlot<'_, '_> {
249    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
250        // FIXME: also show card identifier
251        write!(f, "CardSlot for {:?}", self.public_key)?;
252
253        Ok(())
254    }
255}
256
257impl PublicKeyTrait for CardSlot<'_, '_> {
258    fn version(&self) -> KeyVersion {
259        KeyVersion::V4 // FIXME?
260    }
261
262    fn fingerprint(&self) -> Fingerprint {
263        self.public_key.fingerprint()
264    }
265
266    fn key_id(&self) -> KeyId {
267        self.public_key.key_id()
268    }
269
270    fn algorithm(&self) -> PublicKeyAlgorithm {
271        self.public_key.algorithm()
272    }
273
274    fn created_at(&self) -> &DateTime<Utc> {
275        self.public_key.created_at()
276    }
277
278    fn expiration(&self) -> Option<u16> {
279        None
280    }
281
282    fn verify_signature(
283        &self,
284        hash: HashAlgorithm,
285        data: &[u8],
286        sig: &SignatureBytes,
287    ) -> pgp::errors::Result<()> {
288        self.public_key.verify_signature(hash, data, sig)
289    }
290
291    fn encrypt<R: CryptoRng + Rng>(
292        &self,
293        rng: R,
294        plain: &[u8],
295        typ: pgp::types::EskType,
296    ) -> pgp::errors::Result<PkeskBytes> {
297        self.public_key.encrypt(rng, plain, typ)
298    }
299
300    fn serialize_for_hashing(&self, writer: &mut impl std::io::Write) -> pgp::errors::Result<()> {
301        self.public_key.serialize_for_hashing(writer)
302    }
303
304    fn public_params(&self) -> &PublicParams {
305        self.public_key.public_params()
306    }
307}
308
309impl SecretKeyTrait for CardSlot<'_, '_> {
310    // We model the key data as a public primary key packet for this type.
311    // FIXME: The choice of this type is a bit arbitrary.
312    type PublicKey = PublicKey;
313    type Unlocked = Self;
314
315    fn unlock<F, G, T>(&self, _pw: F, work: G) -> pgp::errors::Result<T>
316    where
317        F: FnOnce() -> String,
318        G: FnOnce(&Self::Unlocked) -> pgp::errors::Result<T>,
319    {
320        work(self)
321    }
322
323    fn create_signature<F>(
324        &self,
325        _key_pw: F,
326        hash: HashAlgorithm,
327        data: &[u8],
328    ) -> pgp::errors::Result<SignatureBytes>
329    where
330        F: FnOnce() -> String,
331    {
332        #[allow(clippy::unwrap_used)]
333        let mut tx = self.tx.lock().unwrap();
334
335        let hash = match self.public_key.algorithm() {
336            PublicKeyAlgorithm::RSA => to_hash_rsa(data, hash)?,
337            PublicKeyAlgorithm::ECDSA => Hash::ECDSA({
338                match self.public_key.public_params() {
339                    PublicParams::ECDSA(EcdsaPublicParams::P256 { .. }) => &data[..32],
340                    PublicParams::ECDSA(EcdsaPublicParams::P384 { .. }) => &data[..48],
341                    PublicParams::ECDSA(EcdsaPublicParams::P521 { .. }) => &data[..64],
342                    _ => data,
343                }
344            }),
345            PublicKeyAlgorithm::EdDSALegacy => Hash::EdDSA(data),
346
347            _ => {
348                return Err(pgp::errors::Error::Unimplemented(format!(
349                    "Unsupported PublicKeyAlgorithm for signature creation: {:?}",
350                    self.public_key.algorithm()
351                )))
352            }
353        };
354
355        if self.touch_required(&mut tx) {
356            (self.touch_prompt)();
357        }
358
359        let sig = match self.key_type {
360            KeyType::Signing => tx.card().signature_for_hash(hash).map_err(map_card_err)?,
361            KeyType::Authentication => tx
362                .card()
363                .authenticate_for_hash(hash)
364                .map_err(map_card_err)?,
365            _ => {
366                return Err(pgp::errors::Error::Unimplemented(format!(
367                    "Unsupported KeyType for signature creation: {:?}",
368                    self.key_type
369                )))
370            }
371        };
372
373        let mpis = match self.public_key.algorithm() {
374            PublicKeyAlgorithm::RSA => vec![Mpi::from_raw(sig)],
375
376            PublicKeyAlgorithm::ECDSA => {
377                let mid = sig.len() / 2;
378
379                vec![Mpi::from_slice(&sig[..mid]), Mpi::from_slice(&sig[mid..])]
380            }
381            PublicKeyAlgorithm::EdDSALegacy => {
382                assert_eq!(sig.len(), 64); // FIXME: check curve; add error handling
383
384                vec![Mpi::from_slice(&sig[..32]), Mpi::from_slice(&sig[32..])]
385            }
386
387            alg => {
388                return Err(pgp::errors::Error::Unimplemented(format!(
389                    "Unsupported algorithm for signature creation: {:?}",
390                    alg
391                )))
392            }
393        };
394
395        Ok(SignatureBytes::Mpis(mpis))
396    }
397
398    fn public_key(&self) -> Self::PublicKey {
399        self.public_key.clone()
400    }
401}
402
403fn to_hash_rsa(data: &[u8], hash: HashAlgorithm) -> pgp::errors::Result<Hash> {
404    match hash {
405        HashAlgorithm::SHA2_256 => {
406            if data.len() == 0x20 {
407                #[allow(clippy::unwrap_used)]
408                Ok(Hash::SHA256(data.try_into().unwrap()))
409            } else {
410                Err(pgp::errors::Error::Message(format!(
411                    "Illegal digest len for SHA256: {}",
412                    data.len()
413                )))
414            }
415        }
416        HashAlgorithm::SHA2_384 => {
417            if data.len() == 0x30 {
418                #[allow(clippy::unwrap_used)]
419                Ok(Hash::SHA384(data.try_into().unwrap()))
420            } else {
421                Err(pgp::errors::Error::Message(format!(
422                    "Illegal digest len for SHA384: {}",
423                    data.len()
424                )))
425            }
426        }
427        HashAlgorithm::SHA2_512 => {
428            if data.len() == 0x40 {
429                #[allow(clippy::unwrap_used)]
430                Ok(Hash::SHA512(data.try_into().unwrap()))
431            } else {
432                Err(pgp::errors::Error::Message(format!(
433                    "Illegal digest len for SHA512: {}",
434                    data.len()
435                )))
436            }
437        }
438        _ => Err(pgp::errors::Error::Message(format!(
439            "Unsupported HashAlgorithm for RSA: {:?}",
440            hash
441        ))),
442    }
443}