1use std::sync::{Arc, Mutex};
26
27use anyhow::Context;
28use openpgp_x509_sequoia::types::{AlgorithmId, PublicKeyInfo};
29use sequoia_openpgp::crypto::mpi;
30use sequoia_openpgp::packet::key::{PublicParts, SecretKeyMaterial, SecretParts, UnspecifiedRole};
31use sequoia_openpgp::packet::Key;
32use sequoia_openpgp::parse::stream::DecryptorBuilder;
33use sequoia_openpgp::parse::Parse;
34use sequoia_openpgp::policy::NullPolicy;
35use sequoia_openpgp::types::Curve;
36use sequoia_openpgp::Cert;
37use x509_certificate::X509Certificate;
38use yubikey::certificate::CertInfo;
39use yubikey::piv::SlotId;
40use yubikey::{MgmKey, PinPolicy, TouchPolicy, YubiKey};
41
42use crate::decryptor::Helper;
43
44mod decryptor;
45mod signer;
46mod util;
47
48pub struct Opiv {
50 yk: YubiKey,
51}
52
53impl From<YubiKey> for Opiv {
54 fn from(yk: YubiKey) -> Self {
55 Self { yk }
56 }
57}
58
59impl Opiv {
60 pub fn open(serial: u32) -> anyhow::Result<Self> {
62 let yk =
63 YubiKey::open_by_serial(yubikey::Serial::from(serial)).context("card not found")?;
64
65 Ok(Opiv { yk })
66 }
67
68 pub fn verify_pin(&mut self, pin: &[u8]) -> anyhow::Result<()> {
70 Ok(self.yk.verify_pin(pin)?)
71 }
72
73 pub fn key(
78 &mut self,
79 slot: SlotId,
80 cert: Cert,
81 ) -> anyhow::Result<Key<PublicParts, UnspecifiedRole>> {
82 if let Ok(ykcert) = yubikey::Certificate::read(&mut self.yk, slot) {
83 let x509 = ykcert.into_buffer();
84 let x509cert: X509Certificate = X509Certificate::from_der(x509.as_slice())?;
85
86 openpgp_x509_sequoia::find_key_by_x509cert(&x509cert, &cert)
87 } else {
88 Err(anyhow::anyhow!(
89 "Couldn't read certificate for slot {:?}",
90 slot
91 ))
92 }
93 }
94
95 pub fn keypair(mut self, slot: SlotId, cert: Cert) -> anyhow::Result<PivKeyPair> {
101 let key = self.key(slot, cert)?;
102
103 let yk = Arc::new(Mutex::new(self.yk));
104 Ok(PivKeyPair::new(key, slot, yk))
105 }
106
107 pub fn sign(
113 self,
114 slot: SlotId,
115 input: &mut (dyn std::io::Read + Send + Sync),
116 output: &mut (dyn std::io::Write + Send + Sync),
117 cert: Cert,
118 ) -> anyhow::Result<()> {
119 let keypair = self.keypair(slot, cert)?;
120
121 signer::sign_on_card(keypair, input, output)
122 }
123
124 pub fn decrypt(
130 mut self,
131 slot: SlotId,
132 input: &mut (dyn std::io::Read + Send + Sync),
133 output: &mut (dyn std::io::Write + Send + Sync),
134 cert: Cert,
135 ) -> anyhow::Result<()> {
136 let public = self.key(slot, cert)?;
138
139 let helper = Helper {
140 yk: Arc::new(Mutex::new(self.yk)),
141 slot,
142 public: &public,
143 };
144
145 let policy = &NullPolicy::new();
147 let mut decryptor =
148 DecryptorBuilder::from_reader(input)?.with_policy(policy, None, helper)?;
149
150 std::io::copy(&mut decryptor, output)?;
152
153 Ok(())
154 }
155
156 pub fn upload_key(
161 &mut self,
162 key: &Key<SecretParts, UnspecifiedRole>,
163 slot: SlotId,
164 common_name: &str,
165 pin: &[u8],
166 mgm_key: MgmKey,
167 ) -> anyhow::Result<()> {
168 let unenc = if let SecretKeyMaterial::Unencrypted(ref u) = key.secret() {
172 u
173 } else {
174 return Err(anyhow::anyhow!("Can't access private key material"));
175 };
176
177 enum PrivateKey {
179 R(yubikey::piv::RsaKeyData),
180 E(mpi::ProtectedMPI),
181 }
182
183 let secret_key_material = unenc.map(|mpis| mpis.clone());
184 let privkey = match secret_key_material {
185 mpi::SecretKeyMaterial::RSA { p, q, .. } => {
186 PrivateKey::R(yubikey::piv::RsaKeyData::new(p.value(), q.value()))
187 }
188 mpi::SecretKeyMaterial::ECDSA { scalar } | mpi::SecretKeyMaterial::ECDH { scalar } => {
189 PrivateKey::E(scalar)
190 }
191 s => {
192 return Err(anyhow::anyhow!(
193 "Unsupported type of SecretKeyMaterial: {:?}",
194 s.algo()
195 ))
196 }
197 };
198
199 let pub_key_info = match key.parts_as_public().mpis() {
201 mpi::PublicKey::RSA { e, n } => {
202 match n.value().len() {
203 248..=256 => {
205 let rsa_pub = rsa::RsaPublicKey::new(
206 rsa::BigUint::from_bytes_be(n.value()),
207 rsa::BigUint::from_bytes_be(e.value()),
208 )?;
209
210 PublicKeyInfo::Rsa {
211 algorithm: AlgorithmId::Rsa2048,
212 pubkey: rsa_pub,
213 }
214 }
215 len => {
216 return Err(anyhow::anyhow!("Unexpected RSA modulus length {}", len * 8))
217 }
218 }
219 }
220 mpi::PublicKey::ECDH { curve, q, .. } | mpi::PublicKey::ECDSA { curve, q, .. } => {
221 match curve {
222 Curve::NistP256 => {
223 let p256 = p256::EncodedPoint::from_bytes(q.value()).map_err(|e| {
224 anyhow::anyhow!("Error while creating EncodedPoint: {e:?}")
225 })?;
226
227 PublicKeyInfo::EcP256(p256)
228 }
229 Curve::NistP384 => {
230 let p384 = p384::EncodedPoint::from_bytes(q.value()).map_err(|e| {
231 anyhow::anyhow!("Error while creating EncodedPoint: {e:?}")
232 })?;
233
234 PublicKeyInfo::EcP384(p384)
235 }
236 _ => return Err(anyhow::anyhow!("Unsupported curve {curve:?}")),
237 }
238 }
239
240 pk => return Err(anyhow::anyhow!("Unexpected public key {:?}", pk)),
241 };
242
243 let reader_name = self.yk.name();
244 log::trace!("Using '{reader_name}' [{}]", self.yk.version());
245
246 self.yk.authenticate(mgm_key)?;
247 log::trace!("MgmKey authenticated");
248
249 log::trace!("Upload private key");
251 match privkey {
252 PrivateKey::R(rsa) => yubikey::piv::import_rsa_key(
253 &mut self.yk,
254 slot,
255 yubikey::piv::AlgorithmId::Rsa2048,
257 rsa,
258 TouchPolicy::Default,
259 PinPolicy::Default,
260 )?,
261 PrivateKey::E(scalar) => {
262 let algo = match pub_key_info {
263 PublicKeyInfo::EcP256(_) => yubikey::piv::AlgorithmId::EccP256,
264 PublicKeyInfo::EcP384(_) => yubikey::piv::AlgorithmId::EccP384,
265 _ => {
266 return Err(anyhow::anyhow!(
267 "Unexpected ECC algorithm {:?}",
268 pub_key_info.algorithm()
269 ))
270 }
271 };
272
273 yubikey::piv::import_ecc_key(
274 &mut self.yk,
275 slot,
276 algo,
277 scalar.value(),
278 TouchPolicy::Default,
279 PinPolicy::Default,
280 )?
281 }
282 }
283
284 let tbs_cert = openpgp_x509_sequoia::generate_x509(&pub_key_info, key, common_name, &[]);
286
287 self.yk.verify_pin(pin)?;
288 log::trace!("PIN verified\n");
289
290 let mut signer = |data: &[u8], algo: AlgorithmId| {
292 let data = match algo {
293 AlgorithmId::Rsa2048 => {
294 let em_len = 2048 / 8;
295 util::pad_pkcs1_5(data, em_len)?
296 }
297 _ => data.to_vec(),
298 };
299
300 let algo: yubikey::piv::AlgorithmId = match algo {
301 AlgorithmId::Rsa2048 => Ok(yubikey::piv::AlgorithmId::Rsa2048),
302 AlgorithmId::EccP256 => Ok(yubikey::piv::AlgorithmId::EccP256),
303 AlgorithmId::EccP384 => Ok(yubikey::piv::AlgorithmId::EccP384),
304 _ => Err(anyhow::anyhow!("Unsupported algo {algo:?}")),
305 }?;
306
307 yubikey::piv::sign_data(&mut self.yk, &data, algo, slot)
308 .map(|b| b.to_vec())
309 .map_err(|e| e.into())
310 };
311
312 let cert =
314 openpgp_x509_sequoia::self_sign_x509(tbs_cert, pub_key_info.algorithm(), &mut signer)?;
315
316 let cert = yubikey::Buffer::from(cert);
318 let cert = yubikey::certificate::Certificate::from_bytes(cert)?;
319
320 cert.write(&mut self.yk, slot, CertInfo::Uncompressed)?;
321 log::debug!("created certificate object");
322
323 Ok(())
324 }
325
326 pub fn reset(&mut self) -> anyhow::Result<()> {
328 const BAD_PIV_PIN: &[u8] = &[1, 1, 1]; for _ in 1..5 {
332 let _ = self.yk.verify_pin(BAD_PIV_PIN); }
334 self.yk.block_puk()?;
335
336 self.yk.reset_device().context("Reset failed")
337 }
338
339 pub fn as_yk(&self) -> &YubiKey {
341 &self.yk
342 }
343
344 pub fn as_yk_mut(&mut self) -> &mut YubiKey {
346 &mut self.yk
347 }
348
349 pub fn into_yk(self) -> YubiKey {
351 self.yk
352 }
353}
354
355pub struct PivKeyPair {
358 public: Key<PublicParts, UnspecifiedRole>,
359 slot: SlotId,
360 yk: Arc<Mutex<YubiKey>>,
361}
362
363impl PivKeyPair {
364 pub fn new(
365 public: Key<PublicParts, UnspecifiedRole>,
366 slot: SlotId,
367 yk: Arc<Mutex<YubiKey>>,
368 ) -> Self {
369 Self { public, slot, yk }
370 }
371}