Skip to content

Commit 2b0fe71

Browse files
authored
[WIP] Improve Code Quality (#422)
* Add implicit casting to CborByteString and CborTextString * Use constant size for stack allocation * Eliminate a few nullability suppressions * Remove workaround old .NET framework behavior .NET core populates Oid on named curves for Windows and Linux * Improve CredentialPublicKey test coverage * Skip new secP256k1 test on macOS * Prefer OperatingSystem to IsOSPlatform * Breakout CreateCertInfo helper from Fido2Tests * Prefer Assert.Equal to check for sequence equality * Breakout PubAreaHelper and improve variable names * Improve SafetyNet error messages * Skip test on macOS * Fix test regression * Breakout CreateCertInfo from Tpm * Breakout CreatePubArea from Tpm * Format Tpm tests * Revert const stackalloc change * Dispose MemoryStreams
1 parent 86b6c6d commit 2b0fe71

27 files changed

+705
-919
lines changed

Src/Fido2.Models/COSETypes.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ public enum EllipticCurve
186186
/// </summary>
187187
Ed448 = 7,
188188
/// <summary>
189-
/// secp256k1 (pending IANA - requested assignment 8)
189+
/// secp256k1
190190
/// </summary>
191191
P256K = 8
192192
}

Src/Fido2/AttestationFormat/AndroidKey.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ internal sealed class AndroidKey : AttestationVerifier
1515
{
1616
foreach (var ext in exts)
1717
{
18-
if (ext.Oid!.Value is "1.3.6.1.4.1.11129.2.1.17") // AttestationRecordOid
18+
if (ext.Oid?.Value is "1.3.6.1.4.1.11129.2.1.17") // AttestationRecordOid
1919
{
2020
return ext.RawData;
2121
}
2222
}
23+
2324
return null;
2425
}
2526

Src/Fido2/AttestationFormat/AndroidSafetyNet.cs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,31 @@ public override (AttestationType, X509Certificate2[]) Verify(VerifyAttestationRe
2929
// 2. Verify that response is a valid SafetyNet response of version ver
3030
if (!request.TryGetVer(out string? ver))
3131
{
32-
throw new Fido2VerificationException("Invalid version in SafetyNet data");
32+
throw new Fido2VerificationException(Fido2ErrorMessages.InvalidSafetyNetVersion);
3333
}
3434

35-
if (!(request.AttStmt["response"] is CborByteString { Length: > 0 }))
36-
throw new Fido2VerificationException("Invalid response in SafetyNet data");
35+
if (!(request.AttStmt["response"] is CborByteString { Length: > 0 } responseByteString))
36+
throw new Fido2VerificationException(Fido2ErrorMessages.InvalidSafetyNetResponse);
3737

38-
var response = (byte[])request.AttStmt["response"]!;
39-
var responseJWT = Encoding.UTF8.GetString(response);
38+
var responseJwt = Encoding.UTF8.GetString(responseByteString);
4039

41-
if (string.IsNullOrWhiteSpace(responseJWT))
42-
throw new Fido2VerificationException("SafetyNet response null or whitespace");
40+
var jwtComponents = responseJwt.Split('.');
4341

44-
var jwtParts = responseJWT.Split('.');
42+
if (jwtComponents.Length != 3)
43+
throw new Fido2VerificationException(Fido2ErrorMessages.MalformedSafetyNetJwt);
4544

46-
if (jwtParts.Length != 3)
47-
throw new Fido2VerificationException("SafetyNet response JWT does not have the 3 expected components");
45+
byte[] jwtHeaderBytes;
4846

49-
string jwtHeaderString = jwtParts[0];
47+
try
48+
{
49+
jwtHeaderBytes = Base64Url.Decode(jwtComponents[0]);
50+
}
51+
catch (FormatException)
52+
{
53+
throw new Fido2VerificationException(Fido2ErrorMessages.MalformedSafetyNetJwt);
54+
}
5055

51-
using var jwtHeaderJsonDoc = JsonDocument.Parse(Base64Url.Decode(jwtHeaderString));
56+
using var jwtHeaderJsonDoc = JsonDocument.Parse(jwtHeaderBytes);
5257
var jwtHeaderJson = jwtHeaderJsonDoc.RootElement;
5358

5459
if (!jwtHeaderJson.TryGetProperty("x5c", out var x5cEl))
@@ -97,7 +102,7 @@ public override (AttestationType, X509Certificate2[]) Verify(VerifyAttestationRe
97102
SecurityToken validatedToken;
98103
try
99104
{
100-
tokenHandler.ValidateToken(responseJWT, validationParameters, out validatedToken);
105+
tokenHandler.ValidateToken(responseJwt, validationParameters, out validatedToken);
101106
}
102107
catch (SecurityTokenException ex)
103108
{

Src/Fido2/AttestationFormat/Apple.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal sealed class Apple : AttestationVerifier
1414
{
1515
public static byte[] GetAppleAttestationExtensionValue(X509ExtensionCollection exts)
1616
{
17-
var appleExtension = exts.FirstOrDefault(static e => e.Oid!.Value is "1.2.840.113635.100.8.2");
17+
var appleExtension = exts.FirstOrDefault(static e => e.Oid?.Value is "1.2.840.113635.100.8.2");
1818

1919
if (appleExtension is null || appleExtension.RawData is null || appleExtension.RawData.Length < 0x26)
2020
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Extension with OID 1.2.840.113635.100.8.2 not found on Apple attestation credCert");

Src/Fido2/AttestationFormat/AppleAppAttest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal sealed class AppleAppAttest : AttestationVerifier
1313
{
1414
public static byte[] GetAppleAppIdFromCredCertExtValue(X509ExtensionCollection exts)
1515
{
16-
var appleExtension = exts.FirstOrDefault(static e => e.Oid!.Value is "1.2.840.113635.100.8.5");
16+
var appleExtension = exts.FirstOrDefault(static e => e.Oid?.Value is "1.2.840.113635.100.8.5");
1717

1818
if (appleExtension is null || appleExtension.RawData is null)
1919
throw new Fido2VerificationException("Extension with OID 1.2.840.113635.100.8.5 not found on Apple AppAttest credCert");

Src/Fido2/AttestationFormat/FidoU2f.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,9 @@ public override (AttestationType, X509Certificate2[]) Verify(VerifyAttestationRe
3636
var pubKey = attCert.GetECDsaPublicKey()!;
3737
var keyParams = pubKey.ExportParameters(false);
3838

39-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
39+
if (!keyParams.Curve.Oid.Value!.Equals(ECCurve.NamedCurves.nistP256.Oid.Value, StringComparison.Ordinal))
4040
{
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");
41+
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve");
4842
}
4943

5044
// 3. Extract the claimed rpIdHash from authenticatorData, and the claimed credentialId and credentialPublicKey from authenticatorData

Src/Fido2/AttestationFormat/Tpm.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ private static (string?, string?, string?) SANFromAttnCertExts(X509ExtensionColl
239239

240240
foreach (var extension in exts)
241241
{
242-
if (extension.Oid!.Value is "2.5.29.17") // subject alternative name
242+
if (extension.Oid?.Value is "2.5.29.17") // subject alternative name
243243
{
244244
if (extension.RawData.Length is 0)
245245
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "SAN missing from TPM attestation certificate");
@@ -362,7 +362,7 @@ private static bool EKUFromAttnCertExts(X509ExtensionCollection exts, string exp
362362
{
363363
foreach (var ext in exts)
364364
{
365-
if (ext.Oid!.Value is "2.5.29.37" && ext is X509EnhancedKeyUsageExtension enhancedKeyUsageExtension)
365+
if (ext.Oid?.Value is "2.5.29.37" && ext is X509EnhancedKeyUsageExtension enhancedKeyUsageExtension)
366366
{
367367
foreach (var oid in enhancedKeyUsageExtension.EnhancedKeyUsages)
368368
{

Src/Fido2/AuthenticatorAttestationResponse.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -280,20 +280,20 @@ public ParsedAttestationObject(string fmt, CborMap attStmt, AuthenticatorData au
280280

281281
public AuthenticatorData AuthData { get; }
282282

283-
internal static ParsedAttestationObject FromCbor(CborObject cbor)
283+
internal static ParsedAttestationObject FromCbor(CborMap cbor)
284284
{
285285
if (!(
286-
cbor["fmt"] is { Type: CborType.TextString } fmt &&
287-
cbor["attStmt"] is { Type: CborType.Map } attStmt &&
288-
cbor["authData"] is { Type: CborType.ByteString } authData))
286+
cbor["fmt"] is CborTextString fmt &&
287+
cbor["attStmt"] is CborMap attStmt &&
288+
cbor["authData"] is CborByteString authData))
289289
{
290290
throw new Fido2VerificationException(Fido2ErrorCode.MalformedAttestationObject, Fido2ErrorMessages.MalformedAttestationObject);
291291
}
292292

293293
return new ParsedAttestationObject(
294-
fmt : (string)fmt,
295-
attStmt : (CborMap)attStmt,
296-
authData : AuthenticatorData.Parse((byte[])authData)
294+
fmt : fmt,
295+
attStmt : attStmt,
296+
authData : AuthenticatorData.Parse(authData)
297297
);
298298
}
299299
}

Src/Fido2/Cbor/CborByteString.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
namespace Fido2NetLib.Cbor;
1+
using System;
2+
3+
namespace Fido2NetLib.Cbor;
24

35
public sealed class CborByteString : CborObject
46
{
57
public CborByteString(byte[] value)
68
{
9+
ArgumentNullException.ThrowIfNull(value);
10+
711
Value = value;
812
}
913

@@ -12,4 +16,6 @@ public CborByteString(byte[] value)
1216
public byte[] Value { get; }
1317

1418
public int Length => Value.Length;
19+
20+
public static implicit operator byte[](CborByteString value) => value.Value;
1521
}

Src/Fido2/Cbor/CborTextString.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public CborTextString(string value)
1515

1616
public string Value { get; }
1717

18+
public static implicit operator string(CborTextString value) => value.Value;
19+
1820
public override bool Equals(object? obj)
1921
{
2022
return obj is CborTextString other && other.Value.Equals(Value, StringComparison.Ordinal);

0 commit comments

Comments
 (0)