Skip to content

Commit bf75254

Browse files
authored
Cleanup (#417)
* Update remaining files to use file scoped-name spaces * Move AuthDataHelper from root -> /Extensions * Make PubArea and CertInfo immutable * Fix a few typos * Format tests * Remove private setters from AttestedCredentialData * Use Convert.FromHexString * Format a few more tests
1 parent 285a611 commit bf75254

27 files changed

+717
-812
lines changed

Src/Fido2/AttestationFormat/AndroidKey.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static byte[] GetAttestationChallenge(byte[] attExtBytes)
3535
public static bool FindAllApplicationsField(byte[] attExtBytes)
3636
{
3737
// https://developer.android.com/training/articles/security-key-attestation#certificate_schema
38-
// check both software and tee enforced AuthorizationList objects for presense of "allApplications" tag, number 600
38+
// check both software and tee enforced AuthorizationList objects for presence of "allApplications" tag, number 600
3939

4040
var keyDescription = Asn1Element.Decode(attExtBytes);
4141

Src/Fido2/AttestationFormat/AttestationVerifier.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,27 +102,27 @@ internal static bool IsAttnCertCACert(X509ExtensionCollection exts)
102102

103103
internal static byte U2FTransportsFromAttnCert(X509ExtensionCollection exts)
104104
{
105-
byte u2ftransports = 0;
105+
byte u2fTransports = 0;
106106
var ext = exts.FirstOrDefault(e => e.Oid?.Value is "1.3.6.1.4.1.45724.2.1.1");
107107
if (ext != null)
108108
{
109-
var decodedU2Ftransports = Asn1Element.Decode(ext.RawData);
110-
decodedU2Ftransports.CheckPrimitive();
109+
var decodedU2fTransports = Asn1Element.Decode(ext.RawData);
110+
decodedU2fTransports.CheckPrimitive();
111111

112112
// some certificates seem to have this encoded as an octet string
113113
// instead of a bit string, attempt to correct
114-
if (decodedU2Ftransports.Tag == Asn1Tag.PrimitiveOctetString)
114+
if (decodedU2fTransports.Tag == Asn1Tag.PrimitiveOctetString)
115115
{
116116
ext.RawData[0] = (byte)UniversalTagNumber.BitString;
117-
decodedU2Ftransports = Asn1Element.Decode(ext.RawData);
117+
decodedU2fTransports = Asn1Element.Decode(ext.RawData);
118118
}
119119

120-
decodedU2Ftransports.CheckTag(Asn1Tag.PrimitiveBitString);
120+
decodedU2fTransports.CheckTag(Asn1Tag.PrimitiveBitString);
121121

122-
u2ftransports = decodedU2Ftransports.GetBitString()[0];
122+
u2fTransports = decodedU2fTransports.GetBitString()[0];
123123
}
124124

125-
return u2ftransports;
125+
return u2fTransports;
126126
}
127127

128128
#nullable disable

Src/Fido2/AttestationFormat/FidoU2f.cs

Lines changed: 83 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -7,93 +7,92 @@
77
using Fido2NetLib.Exceptions;
88
using Fido2NetLib.Objects;
99

10-
namespace Fido2NetLib
10+
namespace Fido2NetLib;
11+
12+
internal sealed class FidoU2f : AttestationVerifier
1113
{
12-
internal sealed class FidoU2f : AttestationVerifier
14+
public override (AttestationType, X509Certificate2[]) Verify()
1315
{
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
1545
{
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");
9748
}
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);
9897
}
9998
}

Src/Fido2/AttestationFormat/Packed.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public override (AttestationType, X509Certificate2[]?) Verify()
113113
}
114114

115115
// id-fido-u2f-ce-transports
116-
byte u2ftransports = U2FTransportsFromAttnCert(attestnCert.Extensions);
116+
byte u2fTransports = U2FTransportsFromAttnCert(attestnCert.Extensions);
117117

118118
// 2d. Optionally, inspect x5c and consult externally provided knowledge to determine whether attStmt conveys a Basic or AttCA attestation
119119

Src/Fido2/AttestationFormat/Tpm.cs

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -229,15 +229,15 @@ public override (AttestationType, X509Certificate2[]) Verify()
229229
{ 3, TpmEccCurve.TPM_ECC_NIST_P521}
230230
};
231231

232-
private static (string?, string?, string?) SANFromAttnCertExts(X509ExtensionCollection extensions)
232+
private static (string?, string?, string?) SANFromAttnCertExts(X509ExtensionCollection exts)
233233
{
234234
string? tpmManufacturer = null;
235235
string? tpmModel = null;
236236
string? tpmVersion = null;
237237

238238
var foundSAN = false;
239239

240-
foreach (var extension in extensions)
240+
foreach (var extension in exts)
241241
{
242242
if (extension.Oid!.Value is "2.5.29.17") // subject alternative name
243243
{
@@ -433,7 +433,7 @@ public enum TpmAlg : ushort
433433
// TPMS_ATTEST, TPMv2-Part2, section 10.12.8
434434
public class CertInfo
435435
{
436-
private static readonly Dictionary<TpmAlg, ushort> tpmAlgToDigestSizeMap = new()
436+
private static readonly Dictionary<TpmAlg, ushort> s_tpmAlgToDigestSizeMap = new()
437437
{
438438
{ TpmAlg.TPM_ALG_SHA1, (160/8) },
439439
{ TpmAlg.TPM_ALG_SHA256, (256/8) },
@@ -472,7 +472,7 @@ public static (ushort size, byte[] name) NameFromTPM2BName(ReadOnlySpan<byte> ab
472472
if (Enum.IsDefined(typeof(TpmAlg), size))
473473
{
474474
var tpmalg = (TpmAlg)size;
475-
if (tpmAlgToDigestSizeMap.TryGetValue(tpmalg, out ushort tplAlgDigestSize))
475+
if (s_tpmAlgToDigestSizeMap.TryGetValue(tpmalg, out ushort tplAlgDigestSize))
476476
{
477477
name = AuthDataHelper.GetSizedByteArray(ab, ref offset, tplAlgDigestSize);
478478
}
@@ -529,19 +529,19 @@ public CertInfo(byte[] certInfo)
529529
if (certInfo.Length != offset)
530530
throw new Fido2VerificationException("Leftover bits decoding certInfo");
531531
}
532-
public byte[] Raw { get; private set; }
533-
public byte[] Magic { get; private set; }
534-
public byte[] Type { get; private set; }
535-
public byte[] QualifiedSigner { get; private set; }
536-
public byte[] ExtraData { get; private set; }
537-
public byte[] Clock { get; private set; }
538-
public byte[] ResetCount { get; private set; }
539-
public byte[] RestartCount { get; private set; }
540-
public byte[] Safe { get; private set; }
541-
public byte[] FirmwareVersion { get; private set; }
542-
public ushort Alg { get; private set; }
543-
public byte[] AttestedName { get; private set; }
544-
public byte[] AttestedQualifiedNameBuffer { get; private set; }
532+
public byte[] Raw { get; }
533+
public byte[] Magic { get; }
534+
public byte[] Type { get; }
535+
public byte[] QualifiedSigner { get; }
536+
public byte[] ExtraData { get; }
537+
public byte[] Clock { get; }
538+
public byte[] ResetCount { get; }
539+
public byte[] RestartCount { get; }
540+
public byte[] Safe { get; }
541+
public byte[] FirmwareVersion { get; }
542+
public ushort Alg { get; }
543+
public byte[] AttestedName { get; }
544+
public byte[] AttestedQualifiedNameBuffer { get; }
545545
}
546546

547547
// TPMT_PUBLIC, TPMv2-Part2, section 12.2.4
@@ -635,18 +635,18 @@ public PubArea(byte[] pubArea)
635635
throw new Fido2VerificationException("Leftover bytes decoding pubArea");
636636
}
637637

638-
public byte[] Raw { get; private set; }
639-
public byte[] Type { get; private set; }
640-
public byte[] Alg { get; private set; }
641-
public byte[] Attributes { get; private set; }
642-
public byte[] Policy { get; private set; }
643-
public byte[]? Symmetric { get; private set; }
644-
public byte[]? Scheme { get; private set; }
645-
public byte[]? KeyBits { get; private set; }
646-
public uint Exponent { get; private set; }
647-
public byte[]? CurveID { get; private set; }
648-
public byte[]? KDF { get; private set; }
649-
public byte[]? Unique { get; private set; }
638+
public byte[] Raw { get; }
639+
public byte[] Type { get; }
640+
public byte[] Alg { get; }
641+
public byte[] Attributes { get; }
642+
public byte[] Policy { get; }
643+
public byte[]? Symmetric { get; }
644+
public byte[]? Scheme { get; }
645+
public byte[]? KeyBits { get; }
646+
public uint Exponent { get; }
647+
public byte[]? CurveID { get; }
648+
public byte[]? KDF { get;}
649+
public byte[]? Unique { get; }
650650
public TpmEccCurve EccCurve => (TpmEccCurve)Enum.ToObject(typeof(TpmEccCurve), BinaryPrimitives.ReadUInt16BigEndian(CurveID));
651-
public ECPoint ECPoint { get; private set; }
651+
public ECPoint ECPoint { get; }
652652
}

0 commit comments

Comments
 (0)