|
7 | 7 | using Fido2NetLib.Exceptions;
|
8 | 8 | using Fido2NetLib.Objects;
|
9 | 9 |
|
10 |
| -namespace Fido2NetLib |
| 10 | +namespace Fido2NetLib; |
| 11 | + |
| 12 | +internal sealed class FidoU2f : AttestationVerifier |
11 | 13 | {
|
12 |
| - internal sealed class FidoU2f : AttestationVerifier |
| 14 | + public override (AttestationType, X509Certificate2[]) Verify() |
13 | 15 | {
|
14 |
| - public override (AttestationType, X509Certificate2[]) Verify() |
| 16 | + // verify that aaguid is 16 empty bytes (note: required by fido2 conformance testing, could not find this in spec?) |
| 17 | + if (AuthData.AttestedCredentialData!.AaGuid.CompareTo(Guid.Empty) != 0) |
| 18 | + throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Aaguid was not empty parsing fido-u2f attestation statement"); |
| 19 | + |
| 20 | + // https://www.w3.org/TR/webauthn/#fido-u2f-attestation |
| 21 | + // 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields. |
| 22 | + // (handled in base class) |
| 23 | + |
| 24 | + // 2a. Check that x5c has exactly one element and let attCert be that element. |
| 25 | + if (!(X5c is CborArray { Length: 1 } x5cArray && x5cArray[0] is CborByteString { Length: > 0 })) |
| 26 | + { |
| 27 | + throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, Fido2ErrorMessages.MalformedX5c_FidoU2fAttestation); |
| 28 | + } |
| 29 | + |
| 30 | + var attCert = new X509Certificate2((byte[])x5cArray[0]); |
| 31 | + |
| 32 | + // TODO : Check why this variable isn't used. Remove it or use it. |
| 33 | + var u2fTransports = U2FTransportsFromAttnCert(attCert.Extensions); |
| 34 | + |
| 35 | + // 2b. If certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve, terminate this algorithm and return an appropriate error |
| 36 | + var pubKey = attCert.GetECDsaPublicKey()!; |
| 37 | + var keyParams = pubKey.ExportParameters(false); |
| 38 | + |
| 39 | + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
| 40 | + { |
| 41 | + if (!keyParams.Curve.Oid.FriendlyName!.Equals(ECCurve.NamedCurves.nistP256.Oid.FriendlyName, StringComparison.Ordinal)) |
| 42 | + throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve"); |
| 43 | + } |
| 44 | + else |
15 | 45 | {
|
16 |
| - // verify that aaguid is 16 empty bytes (note: required by fido2 conformance testing, could not find this in spec?) |
17 |
| - if (AuthData.AttestedCredentialData!.AaGuid.CompareTo(Guid.Empty) != 0) |
18 |
| - throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Aaguid was not empty parsing fido-u2f atttestation statement"); |
19 |
| - |
20 |
| - // https://www.w3.org/TR/webauthn/#fido-u2f-attestation |
21 |
| - // 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields. |
22 |
| - // (handled in base class) |
23 |
| - |
24 |
| - // 2a. Check that x5c has exactly one element and let attCert be that element. |
25 |
| - if (!(X5c is CborArray { Length: 1 } x5cArray && x5cArray[0] is CborByteString { Length: > 0 })) |
26 |
| - { |
27 |
| - throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, Fido2ErrorMessages.MalformedX5c_FidoU2fAttestation); |
28 |
| - } |
29 |
| - |
30 |
| - var attCert = new X509Certificate2((byte[])x5cArray[0]); |
31 |
| - |
32 |
| - // TODO : Check why this variable isn't used. Remove it or use it. |
33 |
| - var u2ftransports = U2FTransportsFromAttnCert(attCert.Extensions); |
34 |
| - |
35 |
| - // 2b. If certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve, terminate this algorithm and return an appropriate error |
36 |
| - var pubKey = attCert.GetECDsaPublicKey()!; |
37 |
| - var keyParams = pubKey.ExportParameters(false); |
38 |
| - |
39 |
| - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
40 |
| - { |
41 |
| - if (!keyParams.Curve.Oid.FriendlyName!.Equals(ECCurve.NamedCurves.nistP256.Oid.FriendlyName, StringComparison.Ordinal)) |
42 |
| - throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve"); |
43 |
| - } |
44 |
| - else |
45 |
| - { |
46 |
| - if (!keyParams.Curve.Oid.Value!.Equals(ECCurve.NamedCurves.nistP256.Oid.Value, StringComparison.Ordinal)) |
47 |
| - throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve"); |
48 |
| - } |
49 |
| - |
50 |
| - // 3. Extract the claimed rpIdHash from authenticatorData, and the claimed credentialId and credentialPublicKey from authenticatorData |
51 |
| - // see rpIdHash, credentialId, and credentialPublicKey members of base class AuthenticatorData (AuthData) |
52 |
| - |
53 |
| - // 4. Convert the COSE_KEY formatted credentialPublicKey (see Section 7 of [RFC8152]) to CTAP1/U2F public Key format (Raw ANSI X9.62 public key format) |
54 |
| - // 4a. 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 |
55 |
| - var x = (byte[])CredentialPublicKey[COSE.KeyTypeParameter.X]; |
56 |
| - |
57 |
| - // 4b. 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 |
58 |
| - var y = (byte[])CredentialPublicKey[COSE.KeyTypeParameter.Y]; |
59 |
| - |
60 |
| - // 4c.Let publicKeyU2F be the concatenation 0x04 || x || y |
61 |
| - var publicKeyU2F = DataHelper.Concat(stackalloc byte[1] { 0x4 }, x, y); |
62 |
| - |
63 |
| - // 5. Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId || publicKeyU2F) |
64 |
| - byte[] verificationData = DataHelper.Concat( |
65 |
| - stackalloc byte[1] { 0x00 }, |
66 |
| - AuthData.RpIdHash, |
67 |
| - _clientDataHash, |
68 |
| - AuthData.AttestedCredentialData.CredentialID, |
69 |
| - publicKeyU2F |
70 |
| - ); |
71 |
| - |
72 |
| - // 6. Verify the sig using verificationData and certificate public key |
73 |
| - if (!TryGetSig(out byte[]? sig)) |
74 |
| - throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, Fido2ErrorMessages.InvalidFidoU2fAttestationSignature); |
75 |
| - |
76 |
| - byte[] ecsig; |
77 |
| - try |
78 |
| - { |
79 |
| - ecsig = CryptoUtils.SigFromEcDsaSig(sig, pubKey.KeySize); |
80 |
| - } |
81 |
| - catch (Exception ex) |
82 |
| - { |
83 |
| - throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Failed to decode fido-u2f attestation signature from ASN.1 encoded form", ex); |
84 |
| - } |
85 |
| - |
86 |
| - var coseAlg = (COSE.Algorithm)(int)CredentialPublicKey[COSE.KeyCommonParameter.Alg]; |
87 |
| - var hashAlg = CryptoUtils.HashAlgFromCOSEAlg(coseAlg); |
88 |
| - |
89 |
| - if (!pubKey.VerifyData(verificationData, ecsig, hashAlg)) |
90 |
| - throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Invalid fido-u2f attestation signature"); |
91 |
| - |
92 |
| - // 7. Optionally, inspect x5c and consult externally provided knowledge to determine whether attStmt conveys a Basic or AttCA attestation |
93 |
| - |
94 |
| - var trustPath = new X509Certificate2[1] { attCert }; |
95 |
| - |
96 |
| - return (AttestationType.AttCa, trustPath); |
| 46 | + if (!keyParams.Curve.Oid.Value!.Equals(ECCurve.NamedCurves.nistP256.Oid.Value, StringComparison.Ordinal)) |
| 47 | + throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve"); |
97 | 48 | }
|
| 49 | + |
| 50 | + // 3. Extract the claimed rpIdHash from authenticatorData, and the claimed credentialId and credentialPublicKey from authenticatorData |
| 51 | + // see rpIdHash, credentialId, and credentialPublicKey members of base class AuthenticatorData (AuthData) |
| 52 | + |
| 53 | + // 4. Convert the COSE_KEY formatted credentialPublicKey (see Section 7 of [RFC8152]) to CTAP1/U2F public Key format (Raw ANSI X9.62 public key format) |
| 54 | + // 4a. 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 |
| 55 | + var x = (byte[])CredentialPublicKey[COSE.KeyTypeParameter.X]; |
| 56 | + |
| 57 | + // 4b. 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 |
| 58 | + var y = (byte[])CredentialPublicKey[COSE.KeyTypeParameter.Y]; |
| 59 | + |
| 60 | + // 4c.Let publicKeyU2F be the concatenation 0x04 || x || y |
| 61 | + var publicKeyU2F = DataHelper.Concat(stackalloc byte[1] { 0x4 }, x, y); |
| 62 | + |
| 63 | + // 5. Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId || publicKeyU2F) |
| 64 | + byte[] verificationData = DataHelper.Concat( |
| 65 | + stackalloc byte[1] { 0x00 }, |
| 66 | + AuthData.RpIdHash, |
| 67 | + _clientDataHash, |
| 68 | + AuthData.AttestedCredentialData.CredentialID, |
| 69 | + publicKeyU2F |
| 70 | + ); |
| 71 | + |
| 72 | + // 6. Verify the sig using verificationData and certificate public key |
| 73 | + if (!TryGetSig(out byte[]? sig)) |
| 74 | + throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, Fido2ErrorMessages.InvalidFidoU2fAttestationSignature); |
| 75 | + |
| 76 | + byte[] ecsig; |
| 77 | + try |
| 78 | + { |
| 79 | + ecsig = CryptoUtils.SigFromEcDsaSig(sig, pubKey.KeySize); |
| 80 | + } |
| 81 | + catch (Exception ex) |
| 82 | + { |
| 83 | + throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Failed to decode fido-u2f attestation signature from ASN.1 encoded form", ex); |
| 84 | + } |
| 85 | + |
| 86 | + var coseAlg = (COSE.Algorithm)(int)CredentialPublicKey[COSE.KeyCommonParameter.Alg]; |
| 87 | + var hashAlg = CryptoUtils.HashAlgFromCOSEAlg(coseAlg); |
| 88 | + |
| 89 | + if (!pubKey.VerifyData(verificationData, ecsig, hashAlg)) |
| 90 | + throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Invalid fido-u2f attestation signature"); |
| 91 | + |
| 92 | + // 7. Optionally, inspect x5c and consult externally provided knowledge to determine whether attStmt conveys a Basic or AttCA attestation |
| 93 | + |
| 94 | + var trustPath = new X509Certificate2[1] { attCert }; |
| 95 | + |
| 96 | + return (AttestationType.AttCa, trustPath); |
98 | 97 | }
|
99 | 98 | }
|
0 commit comments