Skip to content

Commit 3d77492

Browse files
authored
Use System.Formats.Cbor (#257)
* Add Cbor models * Use Cbor models * Update CborMap to support integer keys * Rename CborArray.Count -> Length * Use the map's definite length when available * Implement CborObject.Encode() and add roundtrip test coverage * Remove PeterO.Cbor dependency from build * Rename CborObject.Parse -> Decode * [Tests] Format Tpm
1 parent 19e60e9 commit 3d77492

36 files changed

+2359
-1796
lines changed

Src/Fido2/AttestationFormat/AndroidKey.cs

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
using System.Linq;
33
using System.Security.Cryptography;
44
using System.Security.Cryptography.X509Certificates;
5+
6+
using Fido2NetLib.Cbor;
57
using Fido2NetLib.Objects;
6-
using PeterO.Cbor;
78

89
namespace Fido2NetLib
910
{
@@ -142,54 +143,48 @@ public override (AttestationType, X509Certificate2[]) Verify()
142143
{
143144
// 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields
144145
// (handled in base class)
145-
if (attStmt.Keys.Count is 0 || attStmt.Values.Count is 0)
146+
if (attStmt.Count is 0)
146147
throw new Fido2VerificationException("Attestation format android-key must have attestation statement");
147148

148-
if (Sig is null || Sig.Type != CBORType.ByteString || Sig.GetByteString().Length is 0)
149+
if (!(Sig is CborByteString { Length: > 0 }))
149150
throw new Fido2VerificationException("Invalid android-key attestation signature");
150151

151152
// 2. Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash
152153
// using the attestation public key in attestnCert with the algorithm specified in alg
153-
if (X5c is null || X5c.Type != CBORType.Array || X5c.Count is 0)
154+
if (!(X5c is CborArray { Length: > 0 } x5cArray && x5cArray[0] is CborByteString { Length: > 0 }))
154155
throw new Fido2VerificationException("Malformed x5c in android-key attestation");
155156

156-
if (X5c.Values is null || X5c.Values.Count is 0 ||
157-
X5c.Values.First().Type != CBORType.ByteString ||
158-
X5c.Values.First().GetByteString().Length is 0)
159-
{
160-
throw new Fido2VerificationException("Malformed x5c in android-key attestation");
161-
}
162157

163158
X509Certificate2 androidKeyCert;
164159
ECDsa androidKeyPubKey;
165160
try
166161
{
167-
androidKeyCert = new X509Certificate2(X5c.Values.First().GetByteString());
162+
androidKeyCert = new X509Certificate2((byte[])x5cArray[0]);
168163
androidKeyPubKey = androidKeyCert.GetECDsaPublicKey()!; // attestation public key
169164
}
170165
catch (Exception ex)
171166
{
172167
throw new Fido2VerificationException("Failed to extract public key from android key: " + ex.Message, ex);
173168
}
174169

175-
if (Alg is null || !Alg.IsNumber)
170+
if (!(Alg is CborInteger))
176171
throw new Fido2VerificationException("Invalid android key attestation algorithm");
177172

178173
byte[] ecsig;
179174
try
180175
{
181-
ecsig = CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), androidKeyPubKey.KeySize);
176+
ecsig = CryptoUtils.SigFromEcDsaSig((byte[])Sig, androidKeyPubKey.KeySize);
182177
}
183178
catch (Exception ex)
184179
{
185180
throw new Fido2VerificationException("Failed to decode android key attestation signature from ASN.1 encoded form", ex);
186181
}
187182

188-
if (!androidKeyPubKey.VerifyData(Data, ecsig, CryptoUtils.HashAlgFromCOSEAlg((COSE.Algorithm)Alg.AsInt32())))
183+
if (!androidKeyPubKey.VerifyData(Data, ecsig, CryptoUtils.HashAlgFromCOSEAlg((COSE.Algorithm)(int)Alg)))
189184
throw new Fido2VerificationException("Invalid android key attestation signature");
190185

191186
// 3. Verify that the public key in the first certificate in x5c matches the credentialPublicKey in the attestedCredentialData in authenticatorData.
192-
if (!AuthData.AttestedCredentialData.CredentialPublicKey.Verify(Data, Sig.GetByteString()))
187+
if (!AuthData.AttestedCredentialData.CredentialPublicKey.Verify(Data, (byte[])Sig))
193188
throw new Fido2VerificationException("Incorrect credentialPublicKey in android key attestation");
194189

195190
// 4. Verify that the attestationChallenge field in the attestation certificate extension data is identical to clientDataHash
@@ -222,8 +217,8 @@ public override (AttestationType, X509Certificate2[]) Verify()
222217
if (!IsPurposeSign(attExtBytes))
223218
throw new Fido2VerificationException("Found purpose field not set to KM_PURPOSE_SIGN in android key attestation certificate extension");
224219

225-
var trustPath = X5c.Values
226-
.Select(x => new X509Certificate2(x.GetByteString()))
220+
var trustPath = x5cArray.Values
221+
.Select(x => new X509Certificate2((byte[])x))
227222
.ToArray();
228223

229224
return (AttestationType.Basic, trustPath);

Src/Fido2/AttestationFormat/AndroidSafetyNet.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
using System.Text;
88
using System.Text.Json;
99

10+
using Fido2NetLib.Cbor;
1011
using Fido2NetLib.Objects;
12+
1113
using Microsoft.IdentityModel.Tokens;
12-
using PeterO.Cbor;
1314

1415
namespace Fido2NetLib
1516
{
@@ -35,20 +36,18 @@ public override (AttestationType, X509Certificate2[]) Verify()
3536
// 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform
3637
// CBOR decoding on it to extract the contained fields
3738
// (handled in base class)
38-
if (attStmt["ver"].Type != CBORType.TextString ||
39-
attStmt["ver"].AsString().Length is 0)
39+
if (!(attStmt["ver"] is CborTextString { Length: > 0 }))
4040
{
4141
throw new Fido2VerificationException("Invalid version in SafetyNet data");
4242
}
4343

4444
// 2. Verify that response is a valid SafetyNet response of version ver
45-
var ver = attStmt["ver"].AsString();
45+
var ver = (string)attStmt["ver"];
4646

47-
if (attStmt["response"].Type != CBORType.ByteString ||
48-
attStmt["response"].GetByteString().Length is 0)
47+
if (!(attStmt["response"] is CborByteString { Length: > 0}))
4948
throw new Fido2VerificationException("Invalid response in SafetyNet data");
5049

51-
var response = attStmt["response"].GetByteString();
50+
var response = (byte[])attStmt["response"];
5251
var responseJWT = Encoding.UTF8.GetString(response);
5352

5453
if (string.IsNullOrWhiteSpace(responseJWT))

Src/Fido2/AttestationFormat/Apple.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
using System.Linq;
44
using System.Security.Cryptography;
55
using System.Security.Cryptography.X509Certificates;
6+
7+
using Fido2NetLib.Cbor;
68
using Fido2NetLib.Objects;
7-
using PeterO.Cbor;
89

910
namespace Fido2NetLib
1011
{
@@ -41,19 +42,16 @@ public static byte[] GetAppleAttestationExtensionValue(X509ExtensionCollection e
4142
public override (AttestationType, X509Certificate2[]) Verify()
4243
{
4344
// 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields.
44-
if (X5c is null || X5c.Type != CBORType.Array || X5c.Count < 2 ||
45-
X5c.Values is null || X5c.Values.Count is 0 ||
46-
X5c.Values.First().Type != CBORType.ByteString ||
47-
X5c.Values.First().GetByteString().Length is 0)
45+
if (!(X5c is CborArray { Length: >= 2 } x5cArray && x5cArray[0] is CborByteString { Length: > 0 }))
4846
{
4947
throw new Fido2VerificationException("Malformed x5c in Apple attestation");
5048
}
5149

5250
// 2. Verify x5c is a valid certificate chain starting from the credCert to the Apple WebAuthn root certificate.
5351
// This happens in AuthenticatorAttestationResponse.VerifyAsync using metadata from MDS3
5452

55-
var trustPath = X5c.Values
56-
.Select(x => new X509Certificate2(x.GetByteString()))
53+
var trustPath = x5cArray.Values
54+
.Select(x => new X509Certificate2((byte[])x))
5755
.ToArray();
5856

5957
// credCert is the first certificate in the trust path
@@ -74,7 +72,7 @@ X5c.Values is null || X5c.Values.Count is 0 ||
7472

7573
// 6. Verify credential public key matches the Subject Public Key of credCert.
7674
// First, obtain COSE algorithm being used from credential public key
77-
var coseAlg = CredentialPublicKey[CBORObject.FromObject(COSE.KeyCommonParameter.Alg)].AsInt32();
75+
var coseAlg = (int)CredentialPublicKey[COSE.KeyCommonParameter.Alg];
7876

7977
// Next, build temporary CredentialPublicKey for comparison from credCert and COSE algorithm
8078
var cpk = new CredentialPublicKey(credCert, coseAlg);

Src/Fido2/AttestationFormat/AttestationFormat.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
11
#nullable disable
22

3-
using PeterO.Cbor;
4-
using System;
3+
using System.Formats.Asn1;
4+
using System.Linq;
55
using System.Security.Cryptography.X509Certificates;
6+
using Fido2NetLib.Cbor;
67
using Fido2NetLib.Objects;
7-
using System.Linq;
8-
using System.Formats.Asn1;
98

109
namespace Fido2NetLib
1110
{
1211
public abstract class AttestationVerifier
1312
{
14-
public CBORObject attStmt;
13+
public CborMap attStmt;
1514
public byte[] authenticatorData;
1615
public byte[] clientDataHash;
1716

18-
internal CBORObject Sig => attStmt["sig"];
19-
internal CBORObject X5c => attStmt["x5c"];
20-
internal CBORObject Alg => attStmt["alg"];
21-
internal CBORObject EcdaaKeyId => attStmt["ecdaaKeyId"];
17+
internal CborObject Sig => attStmt["sig"];
18+
internal CborObject X5c => attStmt["x5c"];
19+
internal CborObject Alg => attStmt["alg"];
20+
internal CborObject EcdaaKeyId => attStmt["ecdaaKeyId"];
2221
internal AuthenticatorData AuthData => new AuthenticatorData(authenticatorData);
23-
internal CBORObject CredentialPublicKey => AuthData.AttestedCredentialData.CredentialPublicKey.GetCBORObject();
22+
internal CborMap CredentialPublicKey => AuthData.AttestedCredentialData.CredentialPublicKey.GetCborObject();
2423
internal byte[] Data => DataHelper.Concat(authenticatorData, clientDataHash);
2524

2625
internal static byte[] AaguidFromAttnCertExts(X509ExtensionCollection exts)
@@ -74,7 +73,7 @@ internal static byte U2FTransportsFromAttnCert(X509ExtensionCollection exts)
7473

7574
return u2ftransports;
7675
}
77-
public virtual (AttestationType, X509Certificate2[]) Verify(CBORObject attStmt, byte[] authenticatorData, byte[] clientDataHash)
76+
public virtual (AttestationType, X509Certificate2[]) Verify(CborMap attStmt, byte[] authenticatorData, byte[] clientDataHash)
7877
{
7978
this.attStmt = attStmt;
8079
this.authenticatorData = authenticatorData;

Src/Fido2/AttestationFormat/FidoU2f.cs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
using System.Runtime.InteropServices;
44
using System.Security.Cryptography;
55
using System.Security.Cryptography.X509Certificates;
6+
7+
using Fido2NetLib.Cbor;
68
using Fido2NetLib.Objects;
7-
using PeterO.Cbor;
89

910
namespace Fido2NetLib
1011
{
@@ -19,19 +20,14 @@ public override (AttestationType, X509Certificate2[]) Verify()
1920
// https://www.w3.org/TR/webauthn/#fido-u2f-attestation
2021
// 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields.
2122
// (handled in base class)
22-
if (X5c is null || X5c.Type != CBORType.Array || X5c.Count != 1)
23-
throw new Fido2VerificationException("Malformed x5c in fido - u2f attestation");
2423

2524
// 2a. Check that x5c has exactly one element and let attCert be that element.
26-
if (X5c.Values is null ||
27-
X5c.Values.Count is 0 ||
28-
X5c.Values.First().Type != CBORType.ByteString ||
29-
X5c.Values.First().GetByteString().Length is 0)
25+
if (!(X5c is CborArray { Length: 1 } x5cArray && x5cArray[0] is CborByteString { Length: > 0 }))
3026
{
3127
throw new Fido2VerificationException("Malformed x5c in fido-u2f attestation");
3228
}
3329

34-
var attCert = new X509Certificate2(X5c.Values.First().GetByteString());
30+
var attCert = new X509Certificate2((byte[])X5c[0]);
3531

3632
// TODO : Check why this variable isn't used. Remove it or use it.
3733
var u2ftransports = U2FTransportsFromAttnCert(attCert.Extensions);
@@ -56,10 +52,10 @@ X5c.Values.Count is 0 ||
5652

5753
// 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)
5854
// 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
59-
var x = CredentialPublicKey[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString();
55+
var x = (byte[])CredentialPublicKey[COSE.KeyTypeParameter.X];
6056

6157
// 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
62-
var y = CredentialPublicKey[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString();
58+
var y = (byte[])CredentialPublicKey[COSE.KeyTypeParameter.Y];
6359

6460
// 4c.Let publicKeyU2F be the concatenation 0x04 || x || y
6561
var publicKeyU2F = DataHelper.Concat(stackalloc byte[1] { 0x4 }, x, y);
@@ -74,28 +70,28 @@ X5c.Values.Count is 0 ||
7470
);
7571

7672
// 6. Verify the sig using verificationData and certificate public key
77-
if (Sig is null || Sig.Type != CBORType.ByteString || Sig.GetByteString().Length is 0)
73+
if (!(Sig is CborByteString { Length: > 0 }))
7874
throw new Fido2VerificationException("Invalid fido-u2f attestation signature");
7975

8076
byte[] ecsig;
8177
try
8278
{
83-
ecsig = CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), pubKey.KeySize);
79+
ecsig = CryptoUtils.SigFromEcDsaSig((byte[])Sig, pubKey.KeySize);
8480
}
8581
catch (Exception ex)
8682
{
8783
throw new Fido2VerificationException("Failed to decode fido-u2f attestation signature from ASN.1 encoded form", ex);
8884
}
8985

90-
var coseAlg = (COSE.Algorithm)CredentialPublicKey[CBORObject.FromObject(COSE.KeyCommonParameter.Alg)].AsInt32();
86+
var coseAlg = (COSE.Algorithm)(int)CredentialPublicKey[COSE.KeyCommonParameter.Alg];
9187
var hashAlg = CryptoUtils.HashAlgFromCOSEAlg(coseAlg);
9288

9389
if (!pubKey.VerifyData(verificationData, ecsig, hashAlg))
9490
throw new Fido2VerificationException("Invalid fido-u2f attestation signature");
9591

9692
// 7. Optionally, inspect x5c and consult externally provided knowledge to determine whether attStmt conveys a Basic or AttCA attestation
97-
var trustPath = X5c.Values
98-
.Select(x => new X509Certificate2(x.GetByteString()))
93+
var trustPath = ((CborArray)X5c).Values
94+
.Select(x => new X509Certificate2((byte[])x))
9995
.ToArray();
10096

10197
return (AttestationType.AttCa, trustPath);

Src/Fido2/AttestationFormat/None.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public sealed class None : AttestationVerifier
77
{
88
public override (AttestationType, X509Certificate2[]?) Verify()
99
{
10-
if (0 != attStmt.Keys.Count && 0 != attStmt.Values.Count)
10+
if (attStmt.Count != 0)
1111
throw new Fido2VerificationException("Attestation format none should have no attestation statement");
1212

1313
return (AttestationType.None, null);

0 commit comments

Comments
 (0)