openpgp_pkcs11_sequoia/
lib.rs

1//! Library for PKCS #11 HSM usage with Sequoia PGP.
2//!
3//! Example use, uploading an OpenPGP key to a PKCS #11 device:
4//!
5//! ```no_run
6//! use openpgp_pkcs11_sequoia::Op11;
7//!
8//! // PKCS #11 driver module
9//! let module = "/usr/lib64/pkcs11/yubihsm_pkcs11.so";
10//!
11//! // Serial of the PKCS #11 slot
12//! let serial = "07550916";
13//!
14//! // Open PKCS #11 context and slot
15//! let mut pkcs11 = Op11::open(module)?;
16//! let slot = pkcs11.slot(serial)?;
17//!
18//! // Open a read-write session, log in as user
19//! let session = slot.open_rw_session()?;
20//! session.login("0001password")?;
21//!
22//! // Upload an OpenPGP component key to the PKCS #11 device as id "3"
23//! # let common_name = String::new();
24//! # let pgp_key = sequoia_openpgp::packet::key::Key4::generate_ecc(true, sequoia_openpgp::types::Curve::NistP256)?.into();
25//! session.upload_key(&[3], &pgp_key, &common_name)?;
26//! # Ok::<(), anyhow::Error>(())
27//! ```
28
29use std::sync::{Arc, Mutex};
30
31use anyhow::Result;
32use cryptoki::context::Pkcs11;
33use cryptoki::error::RvError;
34use cryptoki::object::{Attribute, ObjectClass, ObjectHandle};
35use cryptoki::session::{Session, UserType};
36use cryptoki::slot::Slot;
37use openpgp_x509_sequoia::types::PgpKeyType;
38use sequoia_openpgp::packet::key::{PublicParts, SecretParts, UnspecifiedRole};
39use sequoia_openpgp::packet::Key;
40use sequoia_openpgp::parse::stream::DecryptorBuilder;
41use sequoia_openpgp::parse::Parse;
42use sequoia_openpgp::policy::NullPolicy;
43use sequoia_openpgp::types::Timestamp;
44use sequoia_openpgp::{Cert, Fingerprint};
45
46pub(crate) mod decryptor;
47pub(crate) mod signer;
48mod upload;
49mod util;
50
51/// OpenPGP PKCS #11 context
52pub struct Op11 {
53    pkcs11: Pkcs11,
54}
55
56impl Op11 {
57    /// Open and initialize PKCS #11 context
58    pub fn open(module: &str) -> Result<Self> {
59        let mut pkcs11 = Pkcs11::new(module)?;
60
61        let res = pkcs11.initialize(cryptoki::context::CInitializeArgs::OsThreads);
62        match res {
63            Err(cryptoki::error::Error::Pkcs11(RvError::CryptokiAlreadyInitialized)) => {
64                // Ignore multiple initializations
65
66                // If a program calls Op11::open more than once, each
67                // Pkcs11::new will start out with `is_initialized=false`.
68                // So we don't know if initialization is actually needed.
69
70                // Calling initialize() and ignoring this error is one
71                // way to resolve this.
72            }
73            Err(e) => return Err(e.into()),
74            Ok(()) => {}
75        }
76
77        Ok(Op11 { pkcs11 })
78    }
79
80    /// Get PKCS #11 `Slot` that matches `serial_number`
81    pub fn slot(&mut self, serial_number: &str) -> Result<Op11Slot> {
82        for slot in self.pkcs11.get_all_slots()? {
83            if let Ok(ti) = self.pkcs11.get_token_info(slot) {
84                if serial_number == ti.serial_number() {
85                    log::debug!("token info: {:#?}", ti);
86
87                    return Ok(Op11Slot {
88                        slot,
89                        pkcs11: &self.pkcs11,
90                    });
91                }
92            }
93        }
94
95        Err(anyhow::anyhow!("No slot found for '{serial_number}'"))
96    }
97
98    /// Get all (initialized) PKCS #11 `Slot`s
99    pub fn slots(&mut self) -> Result<Vec<Op11Slot>> {
100        Ok(self
101            .pkcs11
102            .get_slots_with_initialized_token()?
103            .into_iter()
104            .map(|slot| Op11Slot {
105                slot,
106                pkcs11: &self.pkcs11,
107            })
108            .collect())
109    }
110
111    /// XXX: escape hatch for direct PKCS #11 access (will be removed)
112    pub fn pkcs11(&self) -> &Pkcs11 {
113        &self.pkcs11
114    }
115}
116
117/// OpenPGP PKCS #11 Slot
118pub struct Op11Slot<'a> {
119    slot: Slot,
120    pkcs11: &'a Pkcs11,
121}
122
123impl Op11Slot<'_> {
124    pub fn open_rw_session(self) -> Result<Op11Session> {
125        let session = self.pkcs11.open_rw_session(self.slot)?;
126        Ok(Op11Session { session })
127    }
128
129    pub fn open_ro_session(self) -> Result<Op11Session> {
130        let session = self.pkcs11.open_ro_session(self.slot)?;
131        Ok(Op11Session { session })
132    }
133
134    pub fn serial(&self) -> Result<String> {
135        if let Ok(ti) = self.pkcs11.get_token_info(self.slot) {
136            return Ok(ti.serial_number().to_string());
137        }
138
139        Err(anyhow::anyhow!("Couldn't get serial number"))
140    }
141}
142
143/// OpenPGP PKCS #11 Session
144pub struct Op11Session {
145    session: Session,
146}
147
148impl Op11Session {
149    /// Log in as UserType::User
150    pub fn login(&self, pin: &str) -> Result<()> {
151        self.session.login(UserType::User, Some(pin))?;
152        Ok(())
153    }
154
155    /// Log in as UserType::So
156    pub fn login_so(&self, pin: &str) -> Result<()> {
157        self.session.login(UserType::So, Some(pin))?;
158        Ok(())
159    }
160
161    /// Log out
162    pub fn logout(&self) -> Result<()> {
163        self.session.logout()?;
164        Ok(())
165    }
166
167    /// Get OpenPGP [`sequoia_openpgp::packet::Key`] for `id`.
168    ///
169    /// The optional `cert` is used as source of OpenPGP metadata, if available.
170    pub fn key(
171        &self,
172        id: &[u8],
173        pkt: PgpKeyType,
174        cert: Option<Cert>,
175    ) -> Result<Key<PublicParts, UnspecifiedRole>> {
176        let x509cert = util::x509_cert(&self.session, id)?;
177
178        // If we have a Cert, we expect to find a matching key in it, and use that
179        if let Some(c) = cert {
180            return openpgp_x509_sequoia::find_key_by_x509cert(&x509cert, &c);
181        }
182
183        let x509_cert = x509_certificate::rfc5280::Certificate::from(x509cert.clone());
184
185        let x509_creation_time = if let x509_certificate::asn1time::Time::UtcTime(utc) =
186            x509_cert.tbs_certificate.validity.not_before.clone()
187        {
188            Timestamp::from(utc.timestamp() as u32).into()
189        } else {
190            return Err(anyhow::anyhow!(
191                "Unexpected enum variant for validity.not_before"
192            ));
193        };
194
195        // Get subkey fingerprint from x509 cert extension, if set
196        let extension_subkey_fp =
197            openpgp_x509_sequoia::experimental::extension_fingerprint(&x509_cert)?;
198
199        // Get kdf_kek params from x509 cert extension, if set
200        let extension_kdf_kek: Option<[u8; 4]> =
201            openpgp_x509_sequoia::experimental::extension_kdf_kek(&x509_cert)?;
202
203        // - If we have an extension_subkey_fp, we expect to match its FP,
204        // - Otherwise, we expect the serial to match the FP.
205        let fp = if let Some(fp) = extension_subkey_fp {
206            fp
207        } else {
208            let serial = x509_cert.tbs_certificate.serial_number.as_slice();
209            let serial = &serial[serial.len() - 20..]; // FIXME
210            Fingerprint::from_bytes(serial)
211        };
212
213        let k4 = if let Ok(rsa_pub) = x509cert.rsa_public_key_data() {
214            // -- RSA --
215
216            util::get_rsa_as_pgp(rsa_pub, x509_creation_time)?
217        } else {
218            // -- ECC --
219
220            // (this is not currently needed for gnupg-pkcs11-scd migration,
221            // because that project doesn't yet support ECC keys)
222            let pki = x509_cert.tbs_certificate.subject_public_key_info;
223
224            util::get_ecc_as_pgp(pkt, pki, x509_creation_time, &fp, extension_kdf_kek)?
225        };
226
227        // We expect a positive match, before using a key for OpenPGP.
228        if k4.fingerprint() != fp {
229            return Err(anyhow::anyhow!(
230                "Couldn't find matching key for Fingerprint {:?}",
231                fp
232            ));
233        }
234
235        Ok(k4.into())
236    }
237
238    /// Get an [`Op11KeyPair`] that can perform decryption and signing operations.
239    ///
240    /// The optional `cert` is used as source of OpenPGP metadata, if available.
241    pub fn keypair(self, id: &[u8], pkt: PgpKeyType, cert: Option<Cert>) -> Result<Op11KeyPair> {
242        // get public key for id
243        let key = self.key(id, pkt, cert)?;
244
245        let priv_key_template = match pkt {
246            PgpKeyType::Sign | PgpKeyType::Auth => {
247                vec![
248                    Attribute::Token(true),
249                    Attribute::Private(true),
250                    Attribute::Sign(true),
251                    Attribute::Id(id.to_vec()),
252                    Attribute::Class(ObjectClass::PRIVATE_KEY),
253                ]
254            }
255            PgpKeyType::Encrypt => {
256                vec![
257                    Attribute::Token(true),
258                    Attribute::Private(true),
259                    Attribute::Decrypt(true), // FIXME: or Derive for ECC?!
260                    Attribute::Id(id.to_vec()),
261                    Attribute::Class(ObjectClass::PRIVATE_KEY),
262                ]
263            }
264        };
265
266        let priv_key_handle = self.session.find_objects(&priv_key_template)?;
267
268        if priv_key_handle.len() == 1 {
269            Ok(Op11KeyPair::new(
270                key,
271                priv_key_handle[0],
272                Arc::new(Mutex::new(self.session)),
273            ))
274        } else {
275            Err(anyhow::anyhow!(
276                "Unexpected number of private keys found: {}",
277                priv_key_handle.len()
278            ))
279        }
280    }
281
282    /// Perform a decryption operation on a card.
283    ///
284    /// The optional `cert` is used as source of OpenPGP metadata, if available.
285    pub fn decrypt(
286        self,
287        id: &[u8],
288        input: &mut (dyn std::io::Read + Send + Sync),
289        output: &mut (dyn std::io::Write + Send + Sync),
290        cert: Option<Cert>,
291    ) -> Result<()> {
292        let op11kp = self.keypair(id, PgpKeyType::Encrypt, cert)?;
293
294        // Now, create a decryptor with a helper using the given Certs.
295        let policy = &NullPolicy::new();
296        let mut decryptor =
297            DecryptorBuilder::from_reader(input)?.with_policy(policy, None, op11kp)?;
298
299        // Decrypt the data.
300        std::io::copy(&mut decryptor, output)?;
301
302        Ok(())
303    }
304
305    /// Perform a signing operation on a card.
306    ///
307    /// The optional `cert` is used as source of OpenPGP metadata, if available.
308    pub fn sign(
309        self,
310        id: &[u8],
311        input: &mut (dyn std::io::Read + Send + Sync),
312        output: &mut (dyn std::io::Write + Send + Sync),
313        cert: Option<Cert>,
314    ) -> Result<()> {
315        let op11kp = self.keypair(id, PgpKeyType::Sign, cert)?;
316
317        signer::sign_on_card(op11kp, input, output)
318    }
319
320    /// Upload an OpenPGP component key to a card.
321    ///
322    /// - Uploads private key object
323    /// - Generates an X.509 certificate (with experimental OpenPGP metadata)
324    /// - Self-signs the certificate
325    /// - Uploads the X.509 certificate
326    ///
327    /// (NOTE: The OpenPGP metadata that gets generated by this function
328    /// is intended for testing purposes only!
329    /// More standardization work is required to define how OpenPGP
330    /// metadata gets stored in the generated X.509 certificate.)
331    ///
332    /// FIXME: split up private key and X.509 certificate upload
333    /// -> give the user more control over the generated certificate.
334    pub fn upload_key(
335        &self,
336        id: &[u8],
337        key: &Key<SecretParts, UnspecifiedRole>,
338        common_name: &str,
339    ) -> Result<()> {
340        let priv_key = self.upload_private(id, key)?;
341
342        let pub_key_info = Self::upload_gen_pki(key)?;
343        self.upload_pki(&pub_key_info)?;
344
345        // Generate x.509 certificate
346        let tbs_cert = openpgp_x509_sequoia::generate_x509(&pub_key_info, key, common_name, &[]);
347
348        // Self-sign x.509 certificate
349        let cert = self.upload_self_sign_x509(priv_key, tbs_cert, pub_key_info.algorithm())?;
350
351        // Upload x.509 certificate
352        let serial = key.fingerprint().as_bytes().to_vec();
353        self.upload_cert(cert, common_name, serial, id)?;
354
355        Ok(())
356    }
357
358    /// XXX: escape hatch for direct pkcs11 access (will be removed)
359    pub fn session(&self) -> &Session {
360        &self.session
361    }
362}
363
364/// PKCS #11 implementation of [`sequoia_openpgp::crypto::Signer`]
365/// and [`sequoia_openpgp::crypto::Decryptor`], as well as
366/// [`sequoia_openpgp::parse::stream::DecryptionHelper`] and
367/// [`sequoia_openpgp::parse::stream::VerificationHelper`].
368pub struct Op11KeyPair {
369    pub public: Key<PublicParts, UnspecifiedRole>,
370    pub private: ObjectHandle,
371    pub session: Arc<Mutex<Session>>,
372}
373
374impl Op11KeyPair {
375    pub fn new(
376        public: Key<PublicParts, UnspecifiedRole>,
377        private: ObjectHandle,
378        session: Arc<Mutex<Session>>,
379    ) -> Self {
380        Self {
381            public,
382            private,
383            session,
384        }
385    }
386}