1 //! Cryptographic operation wrapper for Webauthn. This module exists to
2 //! allow ease of auditing, safe operation wrappers for the webauthn library,
3 //! and cryptographic provider abstraction. This module currently uses OpenSSL
4 //! as the cryptographic primitive provider.
5 
6 #![allow(non_camel_case_types)]
7 
8 use core::convert::TryFrom;
9 use openssl::{bn, ec, hash, nid, pkey, rsa, sha, sign, x509};
10 
11 // use super::constants::*;
12 use super::error::*;
13 use crate::proto::{
14     Aaguid, COSEAlgorithm, COSEEC2Key, COSEKey, COSEKeyType, COSEKeyTypeId, COSERSAKey, ECDSACurve,
15 };
16 // use super::proto::*;
17 
18 // Why OpenSSL over another rust crate?
19 // - The openssl crate allows us to reconstruct a public key from the
20 //   x/y group coords, where most others want a pkcs formatted structure. As
21 //   a result, it's easiest to use openssl as it gives us exactly what we need
22 //   for these operations, and despite it's many challenges as a library, it
23 //   has resources and investment into it's maintenance, so we can a least
24 //   assert a higher level of confidence in it that <backyard crypto here>.
25 
26 // Object({Integer(-3): Bytes([48, 185, 178, 204, 113, 186, 105, 138, 190, 33, 160, 46, 131, 253, 100, 177, 91, 243, 126, 128, 245, 119, 209, 59, 186, 41, 215, 196, 24, 222, 46, 102]), Integer(-2): Bytes([158, 212, 171, 234, 165, 197, 86, 55, 141, 122, 253, 6, 92, 242, 242, 114, 158, 221, 238, 163, 127, 214, 120, 157, 145, 226, 232, 250, 144, 150, 218, 138]), Integer(-1): U64(1), Integer(1): U64(2), Integer(3): I64(-7)})
27 //
28 
29 // Apple makes a webauthn root certificate public at
30 // https://www.apple.com/certificateauthority/private/.
31 // The certificate data itself (as linked in the cert listing linked above) can be found at
32 // https://www.apple.com/certificateauthority/Apple_WebAuthn_Root_CA.pem.
33 pub(crate) const APPLE_X509_PEM: &[u8] = b"-----BEGIN CERTIFICATE-----
34 MIICEjCCAZmgAwIBAgIQaB0BbHo84wIlpQGUKEdXcTAKBggqhkjOPQQDAzBLMR8w
35 HQYDVQQDDBZBcHBsZSBXZWJBdXRobiBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ
36 bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIwMDMxODE4MjEzMloXDTQ1MDMx
37 NTAwMDAwMFowSzEfMB0GA1UEAwwWQXBwbGUgV2ViQXV0aG4gUm9vdCBDQTETMBEG
38 A1UECgwKQXBwbGUgSW5jLjETMBEGA1UECAwKQ2FsaWZvcm5pYTB2MBAGByqGSM49
39 AgEGBSuBBAAiA2IABCJCQ2pTVhzjl4Wo6IhHtMSAzO2cv+H9DQKev3//fG59G11k
40 xu9eI0/7o6V5uShBpe1u6l6mS19S1FEh6yGljnZAJ+2GNP1mi/YK2kSXIuTHjxA/
41 pcoRf7XkOtO4o1qlcaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUJtdk
42 2cV4wlpn0afeaxLQG2PxxtcwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA
43 MGQCMFrZ+9DsJ1PW9hfNdBywZDsWDbWFp28it1d/5w2RPkRX3Bbn/UbDTNLx7Jr3
44 jAGGiQIwHFj+dJZYUJR786osByBelJYsVZd2GbHQu209b5RCmGQ21gpSAk9QZW4B
45 1bWeT0vT
46 -----END CERTIFICATE-----";
47 
verify_signature( pkey: &pkey::PKeyRef<pkey::Public>, stype: COSEAlgorithm, signature: &[u8], verification_data: &[u8], ) -> Result<bool, WebauthnError>48 fn verify_signature(
49     pkey: &pkey::PKeyRef<pkey::Public>,
50     stype: COSEAlgorithm,
51     signature: &[u8],
52     verification_data: &[u8],
53 ) -> Result<bool, WebauthnError> {
54     let mut verifier = match stype {
55         COSEAlgorithm::ES256 => sign::Verifier::new(hash::MessageDigest::sha256(), &pkey)
56             .map_err(WebauthnError::OpenSSLError),
57         COSEAlgorithm::RS256 => {
58             let mut verifier = sign::Verifier::new(hash::MessageDigest::sha256(), &pkey)
59                 .map_err(WebauthnError::OpenSSLError)?;
60             verifier
61                 .set_rsa_padding(rsa::Padding::PKCS1)
62                 .map_err(WebauthnError::OpenSSLError)?;
63             Ok(verifier)
64         }
65         COSEAlgorithm::INSECURE_RS1 => {
66             let mut verifier = sign::Verifier::new(hash::MessageDigest::sha1(), &pkey)
67                 .map_err(WebauthnError::OpenSSLError)?;
68             verifier
69                 .set_rsa_padding(rsa::Padding::PKCS1)
70                 .map_err(WebauthnError::OpenSSLError)?;
71             Ok(verifier)
72         }
73         _ => Err(WebauthnError::COSEKeyInvalidType),
74     }?;
75 
76     verifier
77         .update(verification_data)
78         .map_err(WebauthnError::OpenSSLError)?;
79     verifier
80         .verify(signature)
81         .map_err(WebauthnError::OpenSSLError)
82 }
83 
84 /// An X509PublicKey. This is what is otherwise known as a public certificate
85 /// which comprises a public key and other signed metadata related to the issuer
86 /// of the key.
87 pub struct X509PublicKey {
88     pubk: x509::X509,
89     t: COSEAlgorithm,
90 }
91 
92 impl std::fmt::Debug for X509PublicKey {
fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result93     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
94         write!(f, "X509PublicKey")
95     }
96 }
97 
98 impl TryFrom<(&[u8], COSEAlgorithm)> for X509PublicKey {
99     type Error = WebauthnError;
100 
101     // Must be DER bytes. If you have PEM, base64decode first!
try_from((d, t): (&[u8], COSEAlgorithm)) -> Result<Self, Self::Error>102     fn try_from((d, t): (&[u8], COSEAlgorithm)) -> Result<Self, Self::Error> {
103         let pubk = x509::X509::from_der(d).map_err(WebauthnError::OpenSSLError)?;
104 
105         #[allow(clippy::single_match)]
106         match &t {
107             COSEAlgorithm::ES256 => {
108                 let pk = pubk.public_key().map_err(WebauthnError::OpenSSLError)?;
109 
110                 let ec_key = pk.ec_key().map_err(WebauthnError::OpenSSLError)?;
111 
112                 ec_key.check_key().map_err(WebauthnError::OpenSSLError)?;
113 
114                 let ec_grpref = ec_key.group();
115 
116                 let ec_curve = ec_grpref
117                     .curve_name()
118                     .ok_or(WebauthnError::OpenSSLErrorNoCurveName)?;
119 
120                 if ec_curve != nid::Nid::X9_62_PRIME256V1 {
121                     return Err(WebauthnError::CertificatePublicKeyInvalid);
122                 }
123             }
124             _ => {}
125         }
126 
127         Ok(X509PublicKey { pubk, t })
128     }
129 }
130 
131 impl X509PublicKey {
verify_signature( &self, signature: &[u8], verification_data: &[u8], ) -> Result<bool, WebauthnError>132     pub(crate) fn verify_signature(
133         &self,
134         signature: &[u8],
135         verification_data: &[u8],
136     ) -> Result<bool, WebauthnError> {
137         let pkey = self
138             .pubk
139             .public_key()
140             .map_err(WebauthnError::OpenSSLError)?;
141 
142         verify_signature(&pkey, self.t, signature, verification_data)
143     }
144 
assert_tpm_attest_req(&self) -> Result<(), WebauthnError>145     pub(crate) fn assert_tpm_attest_req(&self) -> Result<(), WebauthnError> {
146         // TPM attestation certificate MUST have the following fields/extensions:
147 
148         // Version MUST be set to 3.
149         // version is not an attribute in openssl rust so I can't verify this
150 
151         // Subject field MUST be set to empty.
152         let subject_name_ref = self.pubk.subject_name();
153         if subject_name_ref.entries().count() != 0 {
154             return Err(WebauthnError::AttestationCertificateRequirementsNotMet);
155         }
156 
157         // The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9.
158         // https://www.trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf
159         //
160         // I actually have no idea how to parse or process this ...
161         // self.pubk.subject_alt_names
162 
163         // Today there is no way to view eku/bc from openssl rust
164 
165         // The Extended Key Usage extension MUST contain the "joint-iso-itu-t(2) internationalorganizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)" OID.
166 
167         // The Basic Constraints extension MUST have the CA component set to false.
168 
169         // An Authority Information Access (AIA) extension with entry id-ad-ocsp and a CRL Distribution
170         // Point extension [RFC5280] are both OPTIONAL as the status of many attestation certificates is
171         // available through metadata services. See, for example, the FIDO Metadata Service [FIDOMetadataService].
172 
173         Ok(())
174     }
175 
assert_packed_attest_req(&self) -> Result<(), WebauthnError>176     pub(crate) fn assert_packed_attest_req(&self) -> Result<(), WebauthnError> {
177         // Verify that attestnCert meets the requirements in § 8.2.1 Packed Attestation
178         // Statement Certificate Requirements.
179         // https://w3c.github.io/webauthn/#sctn-packed-attestation-cert-requirements
180 
181         // The attestation certificate MUST have the following fields/extensions:
182         // Version MUST be set to 3 (which is indicated by an ASN.1 INTEGER with value 2).
183 
184         // Subject field MUST be set to:
185         //
186         // Subject-C
187         //  ISO 3166 code specifying the country where the Authenticator vendor is incorporated (PrintableString)
188         // Subject-O
189         //  Legal name of the Authenticator vendor (UTF8String)
190         // Subject-OU
191         //  Literal string “Authenticator Attestation” (UTF8String)
192         // Subject-CN
193         //  A UTF8String of the vendor’s choosing
194         let subject_name_ref = self.pubk.subject_name();
195 
196         let subject_c = subject_name_ref
197             .entries_by_nid(nid::Nid::from_raw(14))
198             .take(1)
199             .next();
200         let subject_o = subject_name_ref
201             .entries_by_nid(nid::Nid::from_raw(17))
202             .take(1)
203             .next();
204         let subject_ou = subject_name_ref
205             .entries_by_nid(nid::Nid::from_raw(18))
206             .take(1)
207             .next();
208         let subject_cn = subject_name_ref
209             .entries_by_nid(nid::Nid::from_raw(13))
210             .take(1)
211             .next();
212 
213         if subject_c.is_none() || subject_o.is_none() || subject_cn.is_none() {
214             return Err(WebauthnError::AttestationCertificateRequirementsNotMet);
215         }
216 
217         match subject_ou {
218             Some(ou) => match ou.data().as_utf8() {
219                 Ok(ou_d) => {
220                     if ou_d.to_string() != "Authenticator Attestation" {
221                         return Err(WebauthnError::AttestationCertificateRequirementsNotMet);
222                     }
223                 }
224                 Err(_) => return Err(WebauthnError::AttestationCertificateRequirementsNotMet),
225             },
226             None => return Err(WebauthnError::AttestationCertificateRequirementsNotMet),
227         }
228 
229         // If the related attestation root certificate is used for multiple authenticator models,
230         // the Extension OID 1.3.6.1.4.1.45724.1.1.4 (id-fido-gen-ce-aaguid) MUST be present,
231         // containing the AAGUID as a 16-byte OCTET STRING. The extension MUST NOT be marked as critical.
232 
233         // The Basic Constraints extension MUST have the CA component set to false.
234 
235         // An Authority Information Access (AIA) extension with entry id-ad-ocsp and a CRL
236         // Distribution Point extension [RFC5280] are both OPTIONAL as the status of many
237         // attestation certificates is available through authenticator metadata services. See, for
238         // example, the FIDO Metadata Service [FIDOMetadataService].
239         Ok(())
240     }
241 
get_fido_gen_ce_aaguid(&self) -> Option<Aaguid>242     pub(crate) fn get_fido_gen_ce_aaguid(&self) -> Option<Aaguid> {
243         None
244     }
245 
apple_x509() -> X509PublicKey246     pub(crate) fn apple_x509() -> X509PublicKey {
247         Self {
248             pubk: x509::X509::from_pem(APPLE_X509_PEM).unwrap(),
249             // This content type was obtained statically (this certificate is static data)
250             t: COSEAlgorithm::ES384,
251         }
252     }
253 }
254 
255 impl TryFrom<nid::Nid> for ECDSACurve {
256     type Error = WebauthnError;
try_from(nid: nid::Nid) -> Result<Self, Self::Error>257     fn try_from(nid: nid::Nid) -> Result<Self, Self::Error> {
258         match nid {
259             nid::Nid::X9_62_PRIME256V1 => Ok(ECDSACurve::SECP256R1),
260             nid::Nid::SECP384R1 => Ok(ECDSACurve::SECP384R1),
261             nid::Nid::SECP521R1 => Ok(ECDSACurve::SECP521R1),
262             _ => Err(WebauthnError::ECDSACurveInvalidNid),
263         }
264     }
265 }
266 
267 impl ECDSACurve {
to_openssl_nid(&self) -> nid::Nid268     fn to_openssl_nid(&self) -> nid::Nid {
269         match self {
270             ECDSACurve::SECP256R1 => nid::Nid::X9_62_PRIME256V1,
271             ECDSACurve::SECP384R1 => nid::Nid::SECP384R1,
272             ECDSACurve::SECP521R1 => nid::Nid::SECP521R1,
273         }
274     }
275 }
276 
277 impl COSEAlgorithm {
only_hash_from_type(&self, input: &[u8]) -> Result<Vec<u8>, WebauthnError>278     pub(crate) fn only_hash_from_type(&self, input: &[u8]) -> Result<Vec<u8>, WebauthnError> {
279         match self {
280             COSEAlgorithm::INSECURE_RS1 => {
281                 // sha1
282                 hash::hash(hash::MessageDigest::sha1(), input)
283                     .map(|dbytes| Vec::from(dbytes.as_ref()))
284                     .map_err(WebauthnError::OpenSSLError)
285             }
286             _ => Err(WebauthnError::COSEKeyInvalidType),
287         }
288     }
289 }
290 
291 impl TryFrom<&serde_cbor::Value> for COSEKey {
292     type Error = WebauthnError;
try_from(d: &serde_cbor::Value) -> Result<COSEKey, Self::Error>293     fn try_from(d: &serde_cbor::Value) -> Result<COSEKey, Self::Error> {
294         let m = cbor_try_map!(d)?;
295 
296         // See also https://tools.ietf.org/html/rfc8152#section-3.1
297         // These values look like:
298         // Object({
299         //     // negative (-) values are per-algo specific
300         //     Integer(-3): Bytes([48, 185, 178, 204, 113, 186, 105, 138, 190, 33, 160, 46, 131, 253, 100, 177, 91, 243, 126, 128, 245, 119, 209, 59, 186, 41, 215, 196, 24, 222, 46, 102]),
301         //     Integer(-2): Bytes([158, 212, 171, 234, 165, 197, 86, 55, 141, 122, 253, 6, 92, 242, 242, 114, 158, 221, 238, 163, 127, 214, 120, 157, 145, 226, 232, 250, 144, 150, 218, 138]),
302         //     Integer(-1): U64(1),
303         //     Integer(1): U64(2), // algorithm identifier
304         //     Integer(3): I64(-7) // content type see https://tools.ietf.org/html/rfc8152#section-8.1 -7 being ES256 + SHA256
305         // })
306         // Now each of these integers has a specific meaning, and you need to parse them in order.
307         // First, value 1 for the key type.
308 
309         let key_type_value = m
310             .get(&serde_cbor::Value::Integer(1))
311             .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
312         let key_type = cbor_try_i128!(key_type_value)?;
313 
314         let content_type_value = m
315             .get(&serde_cbor::Value::Integer(3))
316             .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
317         let content_type = cbor_try_i128!(content_type_value)?;
318 
319         // https://www.iana.org/assignments/cose/cose.xhtml
320         // https://www.w3.org/TR/webauthn/#sctn-encoded-credPubKey-examples
321         // match key_type {
322         // 1 => {} OctetKey
323         if key_type == (COSEKeyTypeId::EC_EC2 as i128) {
324             // This indicates this is an EC2 key consisting of crv, x, y, which are stored in
325             // crv (-1), x (-2) and y (-3)
326             // Get these values now ....
327 
328             let curve_type_value = m
329                 .get(&serde_cbor::Value::Integer(-1))
330                 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
331             let curve_type = cbor_try_i128!(curve_type_value)?;
332 
333             // Let x be the value corresponding to the "-2" key (representing x coordinate) in credentialPublicKey, and confirm its size to be of 32 bytes. If size differs or "-2" key is not found, terminate this algorithm and return an appropriate error.
334 
335             // Let y be the value corresponding to the "-3" key (representing y coordinate) in credentialPublicKey, and confirm its size to be of 32 bytes. If size differs or "-3" key is not found, terminate this algorithm and return an appropriate error.
336 
337             let x_value = m
338                 .get(&serde_cbor::Value::Integer(-2))
339                 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
340             let x = cbor_try_bytes!(x_value)?;
341 
342             let y_value = m
343                 .get(&serde_cbor::Value::Integer(-3))
344                 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
345             let y = cbor_try_bytes!(y_value)?;
346 
347             if x.len() != 32 || y.len() != 32 {
348                 return Err(WebauthnError::COSEKeyECDSAXYInvalid);
349             }
350 
351             // Set the x and y, we know they are proper sizes.
352             let mut x_temp = [0; 32];
353             x_temp.copy_from_slice(x);
354             let mut y_temp = [0; 32];
355             y_temp.copy_from_slice(y);
356 
357             // Right, now build the struct.
358             let cose_key = COSEKey {
359                 type_: COSEAlgorithm::try_from(content_type)?,
360                 key: COSEKeyType::EC_EC2(COSEEC2Key {
361                     curve: ECDSACurve::try_from(curve_type)?,
362                     x: x_temp,
363                     y: y_temp,
364                 }),
365             };
366 
367             // The rfc additionally states:
368             //   "   Applications MUST check that the curve and the key type are
369             //     consistent and reject a key if they are not."
370             // this means feeding the values to openssl to validate them for us!
371 
372             cose_key.validate()?;
373             // return it
374             Ok(cose_key)
375         } else if key_type == (COSEKeyTypeId::EC_RSA as i128) {
376             // RSAKey
377 
378             // -37 -> PS256
379             // -257 -> RS256 aka RSASSA-PKCS1-v1_5 with SHA-256
380 
381             // -1 -> n 256 bytes
382             // -2 -> e 3 bytes
383 
384             let n_value = m
385                 .get(&serde_cbor::Value::Integer(-1))
386                 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
387             let n = cbor_try_bytes!(n_value)?;
388 
389             let e_value = m
390                 .get(&serde_cbor::Value::Integer(-2))
391                 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
392             let e = cbor_try_bytes!(e_value)?;
393 
394             if n.len() != 256 || e.len() != 3 {
395                 return Err(WebauthnError::COSEKeyRSANEInvalid);
396             }
397 
398             // Set the n and e, we know they are proper sizes.
399             let mut e_temp = [0; 3];
400             e_temp.copy_from_slice(e.as_slice());
401 
402             // Right, now build the struct.
403             let cose_key = COSEKey {
404                 type_: COSEAlgorithm::try_from(content_type)?,
405                 key: COSEKeyType::RSA(COSERSAKey {
406                     n: n.to_vec(),
407                     e: e_temp,
408                 }),
409             };
410 
411             cose_key.validate()?;
412             // return it
413             Ok(cose_key)
414         } else {
415             log::debug!("try from");
416             Err(WebauthnError::COSEKeyInvalidType)
417         }
418     }
419 }
420 
421 impl TryFrom<&X509PublicKey> for COSEKey {
422     type Error = WebauthnError;
try_from(cert: &X509PublicKey) -> Result<COSEKey, Self::Error>423     fn try_from(cert: &X509PublicKey) -> Result<COSEKey, Self::Error> {
424         let key = match cert.t {
425             COSEAlgorithm::ES256 | COSEAlgorithm::ES384 | COSEAlgorithm::ES512 => {
426                 let ec_key = cert
427                     .pubk
428                     .public_key()
429                     .and_then(|pk| pk.ec_key())
430                     .map_err(WebauthnError::OpenSSLError)?;
431 
432                 ec_key.check_key().map_err(WebauthnError::OpenSSLError)?;
433 
434                 let ec_grpref = ec_key.group();
435 
436                 let mut ctx =
437                     openssl::bn::BigNumContext::new().map_err(WebauthnError::OpenSSLError)?;
438                 let mut xbn = openssl::bn::BigNum::new().map_err(WebauthnError::OpenSSLError)?;
439                 let mut ybn = openssl::bn::BigNum::new().map_err(WebauthnError::OpenSSLError)?;
440 
441                 ec_key
442                     .public_key()
443                     .affine_coordinates_gfp(ec_grpref, &mut xbn, &mut ybn, &mut ctx)
444                     .map_err(WebauthnError::OpenSSLError)?;
445 
446                 let curve = ec_grpref
447                     .curve_name()
448                     .ok_or(WebauthnError::OpenSSLErrorNoCurveName)
449                     .and_then(ECDSACurve::try_from)?;
450 
451                 let mut x = [0; 32];
452                 x.copy_from_slice(xbn.to_vec().as_slice());
453 
454                 let mut y = [0; 32];
455                 y.copy_from_slice(ybn.to_vec().as_slice());
456 
457                 Ok(COSEKeyType::EC_EC2(COSEEC2Key { curve, x, y }))
458             }
459             COSEAlgorithm::RS256
460             | COSEAlgorithm::RS384
461             | COSEAlgorithm::RS512
462             | COSEAlgorithm::PS256
463             | COSEAlgorithm::PS384
464             | COSEAlgorithm::PS512
465             | COSEAlgorithm::EDDSA
466             | COSEAlgorithm::INSECURE_RS1 => {
467                 log::error!(
468                     "unsupported X509 to COSE conversion for COSE algorithm type {:?}",
469                     cert.t
470                 );
471                 Err(WebauthnError::COSEKeyInvalidType)
472             }
473         }?;
474 
475         Ok(COSEKey { type_: cert.t, key })
476     }
477 }
478 
479 impl COSEKey {
get_alg_key_ecc_x962_raw(&self) -> Result<Vec<u8>, WebauthnError>480     pub(crate) fn get_alg_key_ecc_x962_raw(&self) -> Result<Vec<u8>, WebauthnError> {
481         // Let publicKeyU2F be the concatenation 0x04 || x || y.
482         // Note: This signifies uncompressed ECC key format.
483         match &self.key {
484             COSEKeyType::EC_EC2(ecpk) => {
485                 let r: [u8; 1] = [0x04];
486                 Ok(r.iter()
487                     .chain(ecpk.x.iter())
488                     .chain(ecpk.y.iter())
489                     .copied()
490                     .collect())
491             }
492             _ => {
493                 log::debug!("get_alg_key_ecc_x962_raw");
494                 Err(WebauthnError::COSEKeyInvalidType)
495             }
496         }
497     }
498 
validate(&self) -> Result<(), WebauthnError>499     pub(crate) fn validate(&self) -> Result<(), WebauthnError> {
500         match &self.key {
501             COSEKeyType::EC_EC2(ec2k) => {
502                 // Get the curve type
503                 let curve = ec2k.curve.to_openssl_nid();
504                 let ec_group =
505                     ec::EcGroup::from_curve_name(curve).map_err(WebauthnError::OpenSSLError)?;
506 
507                 let xbn = bn::BigNum::from_slice(&ec2k.x).map_err(WebauthnError::OpenSSLError)?;
508                 let ybn = bn::BigNum::from_slice(&ec2k.y).map_err(WebauthnError::OpenSSLError)?;
509 
510                 let ec_key = ec::EcKey::from_public_key_affine_coordinates(&ec_group, &xbn, &ybn)
511                     .map_err(WebauthnError::OpenSSLError)?;
512 
513                 ec_key.check_key().map_err(WebauthnError::OpenSSLError)
514             }
515             COSEKeyType::RSA(rsak) => {
516                 let nbn = bn::BigNum::from_slice(&rsak.n).map_err(WebauthnError::OpenSSLError)?;
517                 let ebn = bn::BigNum::from_slice(&rsak.e).map_err(WebauthnError::OpenSSLError)?;
518 
519                 let _rsa_key = rsa::Rsa::from_public_components(nbn, ebn)
520                     .map_err(WebauthnError::OpenSSLError)?;
521                 /*
522                 // Only applies to keys with private components!
523                 rsa_key
524                     .check_key()
525                     .map_err(WebauthnError::OpenSSLError)
526                 */
527                 Ok(())
528             }
529             _ => Err(WebauthnError::COSEKeyInvalidType),
530         }
531     }
532 
get_openssl_pkey(&self) -> Result<pkey::PKey<pkey::Public>, WebauthnError>533     fn get_openssl_pkey(&self) -> Result<pkey::PKey<pkey::Public>, WebauthnError> {
534         match &self.key {
535             COSEKeyType::EC_EC2(ec2k) => {
536                 // Get the curve type
537                 let curve = ec2k.curve.to_openssl_nid();
538                 let ec_group =
539                     ec::EcGroup::from_curve_name(curve).map_err(WebauthnError::OpenSSLError)?;
540 
541                 let xbn = bn::BigNum::from_slice(&ec2k.x).map_err(WebauthnError::OpenSSLError)?;
542                 let ybn = bn::BigNum::from_slice(&ec2k.y).map_err(WebauthnError::OpenSSLError)?;
543 
544                 let ec_key = ec::EcKey::from_public_key_affine_coordinates(&ec_group, &xbn, &ybn)
545                     .map_err(WebauthnError::OpenSSLError)?;
546 
547                 // Validate the key is sound. IIRC this actually checks the values
548                 // are correctly on the curve as specified
549                 ec_key.check_key().map_err(WebauthnError::OpenSSLError)?;
550 
551                 let p = pkey::PKey::from_ec_key(ec_key).map_err(WebauthnError::OpenSSLError)?;
552                 Ok(p)
553             }
554             COSEKeyType::RSA(rsak) => {
555                 let nbn = bn::BigNum::from_slice(&rsak.n).map_err(WebauthnError::OpenSSLError)?;
556                 let ebn = bn::BigNum::from_slice(&rsak.e).map_err(WebauthnError::OpenSSLError)?;
557 
558                 let rsa_key = rsa::Rsa::from_public_components(nbn, ebn)
559                     .map_err(WebauthnError::OpenSSLError)?;
560 
561                 let p = pkey::PKey::from_rsa(rsa_key).map_err(WebauthnError::OpenSSLError)?;
562                 Ok(p)
563             }
564             _ => {
565                 log::debug!("get_openssl_pkey");
566                 Err(WebauthnError::COSEKeyInvalidType)
567             }
568         }
569     }
570 
verify_signature( &self, signature: &[u8], verification_data: &[u8], ) -> Result<bool, WebauthnError>571     pub(crate) fn verify_signature(
572         &self,
573         signature: &[u8],
574         verification_data: &[u8],
575     ) -> Result<bool, WebauthnError> {
576         let pkey = self.get_openssl_pkey()?;
577         verify_signature(&pkey, self.type_, signature, verification_data)
578     }
579 }
580 
581 /// Compute the sha256 of a slice of data.
compute_sha256(data: &[u8]) -> Vec<u8>582 pub fn compute_sha256(data: &[u8]) -> Vec<u8> {
583     let mut hasher = sha::Sha256::new();
584     hasher.update(data);
585     hasher.finish().iter().copied().collect()
586 }
587 
588 #[cfg(test)]
589 mod tests {
590     use super::*;
591     #[test]
nid_to_curve()592     fn nid_to_curve() {
593         assert_eq!(
594             ECDSACurve::try_from(nid::Nid::X9_62_PRIME256V1).unwrap(),
595             ECDSACurve::SECP256R1
596         );
597     }
598 }
599