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