Skip to content

Commit

Permalink
Merge branch 'master' into cq13
Browse files Browse the repository at this point in the history
  • Loading branch information
iamcarbon committed Aug 2, 2023
2 parents 4cce1d3 + e6567d3 commit e3b5ebd
Show file tree
Hide file tree
Showing 22 changed files with 322 additions and 341 deletions.
1 change: 0 additions & 1 deletion Src/Fido2.Models/Objects/AttestationVerificationSuccess.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json.Serialization;

namespace Fido2NetLib.Objects;
Expand Down
2 changes: 1 addition & 1 deletion Src/Fido2/AttestationFormat/AndroidKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ public override (AttestationType, X509Certificate2[]) Verify()
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Invalid android key attestation signature");

// 3. Verify that the public key in the first certificate in x5c matches the credentialPublicKey in the attestedCredentialData in authenticatorData.
if (!AuthData.AttestedCredentialData.CredentialPublicKey.Verify(Data, sig))
if (!AuthData.AttestedCredentialData!.CredentialPublicKey.Verify(Data, sig))
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Incorrect credentialPublicKey in android key attestation");

// 4. Verify that the attestationChallenge field in the attestation certificate extension data is identical to clientDataHash
Expand Down
2 changes: 1 addition & 1 deletion Src/Fido2/AttestationFormat/Apple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public override (AttestationType, X509Certificate2[]) Verify()
var cpk = new CredentialPublicKey(credCert, coseAlg);

// Finally, compare byte sequence of CredentialPublicKey built from credCert with byte sequence of CredentialPublicKey from AttestedCredentialData from authData
if (!cpk.GetBytes().AsSpan().SequenceEqual(AuthData.AttestedCredentialData.CredentialPublicKey.GetBytes()))
if (!cpk.GetBytes().AsSpan().SequenceEqual(AuthData.AttestedCredentialData!.CredentialPublicKey.GetBytes()))
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Credential public key in Apple attestation does not match subject public key of credCert");

// 7. If successful, return implementation-specific values representing attestation type Anonymous CA and attestation trust path x5c.
Expand Down
2 changes: 1 addition & 1 deletion Src/Fido2/AttestationFormat/AppleAppAttest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public override (AttestationType, X509Certificate2[]) Verify()
chain.ChainPolicy.ExtraStore.Add(intermediateCert);

X509Certificate2 credCert = new((byte[])x5cArray[0]);
if (AuthData.AttestedCredentialData.AaGuid.Equals(devAaguid))
if (AuthData.AttestedCredentialData!.AaGuid.Equals(devAaguid))
{
// Allow expired leaf cert in development environment
chain.ChainPolicy.VerificationTime = credCert.NotBefore.AddSeconds(1);
Expand Down
10 changes: 5 additions & 5 deletions Src/Fido2/AttestationFormat/AttestationVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Fido2NetLib;
public abstract class AttestationVerifier
{
private protected CborMap _attStmt;
private protected byte[] _authenticatorData;
private protected AuthenticatorData _authenticatorData;
private protected byte[] _clientDataHash;

#nullable enable
Expand All @@ -23,11 +23,11 @@ public abstract class AttestationVerifier

internal CborObject? EcdaaKeyId => _attStmt["ecdaaKeyId"];

internal AuthenticatorData AuthData => new (_authenticatorData);
internal AuthenticatorData AuthData => _authenticatorData;

internal CborMap CredentialPublicKey => AuthData.AttestedCredentialData.CredentialPublicKey.GetCborObject();
internal CborMap CredentialPublicKey => AuthData.AttestedCredentialData!.CredentialPublicKey.GetCborObject();

internal byte[] Data => DataHelper.Concat(_authenticatorData, _clientDataHash);
internal byte[] Data => DataHelper.Concat(_authenticatorData.ToByteArray(), _clientDataHash);

internal bool TryGetVer([NotNullWhen(true)] out string? ver)
{
Expand Down Expand Up @@ -127,7 +127,7 @@ internal static byte U2FTransportsFromAttnCert(X509ExtensionCollection exts)

#nullable disable

public virtual (AttestationType, X509Certificate2[]) Verify(CborMap attStmt, byte[] authenticatorData, byte[] clientDataHash)
public virtual (AttestationType, X509Certificate2[]) Verify(CborMap attStmt, AuthenticatorData authenticatorData, byte[] clientDataHash)
{
_attStmt = attStmt;
_authenticatorData = authenticatorData;
Expand Down
2 changes: 1 addition & 1 deletion Src/Fido2/AttestationFormat/FidoU2f.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal sealed class FidoU2f : AttestationVerifier
public override (AttestationType, X509Certificate2[]) Verify()
{
// verify that aaguid is 16 empty bytes (note: required by fido2 conformance testing, could not find this in spec?)
if (AuthData.AttestedCredentialData.AaGuid.CompareTo(Guid.Empty) != 0)
if (AuthData.AttestedCredentialData!.AaGuid.CompareTo(Guid.Empty) != 0)
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Aaguid was not empty parsing fido-u2f atttestation statement");

// https://www.w3.org/TR/webauthn/#fido-u2f-attestation
Expand Down
4 changes: 2 additions & 2 deletions Src/Fido2/AttestationFormat/Packed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public override (AttestationType, X509Certificate2[]?) Verify()
// 2c. If attestnCert contains an extension with OID 1.3.6.1.4.1.45724.1.1.4 (id-fido-gen-ce-aaguid) verify that the value of this extension matches the aaguid in authenticatorData
if (aaguid != null)
{
if (AttestedCredentialData.FromBigEndian(aaguid).CompareTo(AuthData.AttestedCredentialData.AaGuid) != 0)
if (AttestedCredentialData.FromBigEndian(aaguid).CompareTo(AuthData.AttestedCredentialData!.AaGuid) != 0)
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "aaguid present in packed attestation cert exts but does not match aaguid from authData");
}

Expand Down Expand Up @@ -137,7 +137,7 @@ public override (AttestationType, X509Certificate2[]?) Verify()
else
{
// 4a. Validate that alg matches the algorithm of the credentialPublicKey in authenticatorData
if (!AuthData.AttestedCredentialData.CredentialPublicKey.IsSameAlg(alg))
if (!AuthData.AttestedCredentialData!.CredentialPublicKey.IsSameAlg(alg))
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Algorithm mismatch between credential public key and authenticator data in self attestation statement");

// 4b. Verify that sig is a valid signature over the concatenation of authenticatorData and
Expand Down
2 changes: 1 addition & 1 deletion Src/Fido2/AttestationFormat/Tpm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public override (AttestationType, X509Certificate2[]) Verify()
// 5c. If aikCert contains an extension with OID 1.3.6.1.4.1.45724.1.1.4 (id-fido-gen-ce-aaguid) verify that the value of this extension matches the aaguid in authenticatorData
if (AaguidFromAttnCertExts(aikCert.Extensions) is byte[] aaguid &&
(!aaguid.AsSpan().SequenceEqual(Guid.Empty.ToByteArray())) &&
(AttestedCredentialData.FromBigEndian(aaguid).CompareTo(AuthData.AttestedCredentialData.AaGuid) != 0))
(AttestedCredentialData.FromBigEndian(aaguid).CompareTo(AuthData.AttestedCredentialData!.AaGuid) != 0))
{
throw new Fido2VerificationException($"aaguid malformed, expected {AuthData.AttestedCredentialData.AaGuid}, got {new Guid(aaguid)}");
}
Expand Down
16 changes: 8 additions & 8 deletions Src/Fido2/AuthenticatorAssertionResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ private AuthenticatorAssertionResponse(byte[] clientDataJson) : base(clientDataJ

public AuthenticatorAssertionRawResponse Raw { get; init; }

public byte[] AuthenticatorData { get; init; }
public AuthenticatorData AuthenticatorData { get; init; }

public byte[] Signature { get; init; }

Expand All @@ -40,7 +40,7 @@ public static AuthenticatorAssertionResponse Parse(AuthenticatorAssertionRawResp
var response = new AuthenticatorAssertionResponse(rawResponse.Response.ClientDataJson)
{
Raw = rawResponse, // accessed in Verify()
AuthenticatorData = rawResponse.Response.AuthenticatorData,
AuthenticatorData = AuthenticatorData.Parse(rawResponse.Response.AuthenticatorData),
Signature = rawResponse.Response.Signature,
UserHandle = rawResponse.Response.UserHandle,
AttestationObject = rawResponse.Response.AttestationObject
Expand Down Expand Up @@ -103,7 +103,7 @@ public static AuthenticatorAssertionResponse Parse(AuthenticatorAssertionRawResp

// 7. Let cData, authData and sig denote the value of credential’s response's clientDataJSON, authenticatorData, and signature respectively.
//var cData = Raw.Response.ClientDataJson;
var authData = new AuthenticatorData(AuthenticatorData);
var authData = AuthenticatorData;
//var sig = Raw.Response.Signature;

// 8. Let JSONtext be the result of running UTF-8 decode on the value of cData.
Expand Down Expand Up @@ -245,7 +245,7 @@ public static AuthenticatorAssertionResponse Parse(AuthenticatorAssertionRawResp
private static byte[] DevicePublicKeyAuthentication(
List<byte[]> storedDevicePublicKeys,
AuthenticationExtensionsClientOutputs clientExtensionResults,
byte[] authData,
AuthenticatorData authData,
byte[] hash
)
{
Expand All @@ -257,7 +257,7 @@ byte[] hash
DevicePublicKeyAuthenticatorOutput devicePublicKeyAuthenticatorOutput = new(attObjForDevicePublicKey.AuthenticatorOutput);

// 3. Verify that signature is a valid signature over the assertion signature input (i.e. authData and hash) by the device public key dpk.
if (!devicePublicKeyAuthenticatorOutput.DevicePublicKey.Verify(DataHelper.Concat(authData, hash), attObjForDevicePublicKey.Signature))
if (!devicePublicKeyAuthenticatorOutput.DevicePublicKey.Verify(DataHelper.Concat(authData.ToByteArray(), hash), attObjForDevicePublicKey.Signature))
throw new Fido2VerificationException(Fido2ErrorCode.InvalidSignature, Fido2ErrorMessages.InvalidSignature);

// 4. If the Relying Party's user account mapped to the credential.id in play (i.e., for the user being
Expand Down Expand Up @@ -310,7 +310,7 @@ byte[] hash
try
{
// This is a known device public key with a valid signature and valid attestation and thus a known device. Terminate these verification steps.
_ = verifier.Verify(devicePublicKeyAuthenticatorOutput.AttStmt, devicePublicKeyAuthenticatorOutput.AuthData, devicePublicKeyAuthenticatorOutput.Hash);
_ = verifier.Verify(devicePublicKeyAuthenticatorOutput.AttStmt, AuthenticatorData.Parse(devicePublicKeyAuthenticatorOutput.AuthData), devicePublicKeyAuthenticatorOutput.Hash);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -357,7 +357,7 @@ byte[] hash
try
{
// This is a known device public key with a valid signature and valid attestation and thus a known device. Terminate these verification steps.
_ = verifier.Verify(devicePublicKeyAuthenticatorOutput.AttStmt, devicePublicKeyAuthenticatorOutput.AuthData, devicePublicKeyAuthenticatorOutput.Hash);
_ = verifier.Verify(devicePublicKeyAuthenticatorOutput.AttStmt, AuthenticatorData.Parse(devicePublicKeyAuthenticatorOutput.AuthData), devicePublicKeyAuthenticatorOutput.Hash);
return devicePublicKeyAuthenticatorOutput.GetBytes();
}
catch (Exception ex)
Expand Down Expand Up @@ -394,7 +394,7 @@ byte[] hash
try
{
// This is a known device public key with a valid signature and valid attestation and thus a known device. Terminate these verification steps.
_ = verifier.Verify(devicePublicKeyAuthenticatorOutput.AttStmt, devicePublicKeyAuthenticatorOutput.AuthData, devicePublicKeyAuthenticatorOutput.Hash);
_ = verifier.Verify(devicePublicKeyAuthenticatorOutput.AttStmt, AuthenticatorData.Parse(devicePublicKeyAuthenticatorOutput.AuthData), devicePublicKeyAuthenticatorOutput.Hash);
return devicePublicKeyAuthenticatorOutput.GetBytes();
}
catch
Expand Down
47 changes: 27 additions & 20 deletions Src/Fido2/AuthenticatorAttestationResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

using System;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
Expand All @@ -26,7 +25,8 @@ public sealed class AuthenticatorAttestationResponse : AuthenticatorResponse
private IMetadataService _metadataService;
private CancellationToken _cancellationToken;
private Fido2Configuration _config;
private AuthenticatorAttestationResponse(byte[] clientDataJson)

private AuthenticatorAttestationResponse(byte[] clientDataJson)
: base(clientDataJson)
{
}
Expand Down Expand Up @@ -54,22 +54,12 @@ public static AuthenticatorAttestationResponse Parse(AuthenticatorAttestationRaw
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestationObject, Fido2ErrorMessages.InvalidAttestationObject, ex);
}

if (!(
cborAttestation["fmt"] is { Type: CborType.TextString } &&
cborAttestation["attStmt"] is { Type: CborType.Map } &&
cborAttestation["authData"] is { Type: CborType.ByteString }))
{
throw new Fido2VerificationException(Fido2ErrorCode.MalformedAttestationObject, Fido2ErrorMessages.MalformedAttestationObject);
}
var attestationObject = ParsedAttestationObject.FromCbor(cborAttestation);

return new AuthenticatorAttestationResponse(rawResponse.Response.ClientDataJson)
{
Raw = rawResponse,
AttestationObject = new ParsedAttestationObject(
fmt : (string)cborAttestation["fmt"],
attStmt : (CborMap)cborAttestation["attStmt"],
authData : (byte[])cborAttestation["authData"]
)
AttestationObject = attestationObject
};
}

Expand Down Expand Up @@ -103,7 +93,7 @@ public static AuthenticatorAttestationResponse Parse(AuthenticatorAttestationRaw
if (Raw.Type != PublicKeyCredentialType.PublicKey)
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestationResponse, "AttestationResponse type must be 'public-key'");

var authData = new AuthenticatorData(AttestationObject.AuthData);
var authData = AttestationObject.AuthData;

// 10. Let hash be the result of computing a hash over response.clientDataJSON using SHA-256.
byte[] clientDataHash = SHA256.HashData(Raw.Response.ClientDataJson);
Expand Down Expand Up @@ -235,7 +225,7 @@ public static AuthenticatorAttestationResponse Parse(AuthenticatorAttestationRaw
/// <see cref="https://w3c.github.io/webauthn/#sctn-device-publickey-extension-verification-create"/>
private async Task<byte[]> DevicePublicKeyRegistrationAsync(
AuthenticationExtensionsClientOutputs clientExtensionResults,
byte[] authData,
AuthenticatorData authData,
byte[] hash
)
{
Expand All @@ -247,7 +237,7 @@ byte[] hash
DevicePublicKeyAuthenticatorOutput devicePublicKeyAuthenticatorOutput = new(attObjForDevicePublicKey.AuthenticatorOutput);

// 3. Verify that signature is a valid signature over the assertion signature input (i.e. authData and hash) by the device public key dpk.
if (!devicePublicKeyAuthenticatorOutput.DevicePublicKey.Verify(DataHelper.Concat(authData, hash), attObjForDevicePublicKey.Signature))
if (!devicePublicKeyAuthenticatorOutput.DevicePublicKey.Verify(DataHelper.Concat(authData.ToByteArray(), hash), attObjForDevicePublicKey.Signature))
throw new Fido2VerificationException(Fido2ErrorCode.InvalidSignature, Fido2ErrorMessages.InvalidSignature);

// 4. Optionally, if attestation was requested and the Relying Party wishes to verify it, verify that attStmt is a correct attestation statement, conveying a valid attestation signature,
Expand All @@ -256,7 +246,7 @@ byte[] hash
var verifier = AttestationVerifier.Create(devicePublicKeyAuthenticatorOutput.Fmt);

// https://w3c.github.io/webauthn/#sctn-device-publickey-attestation-calculations
(var attType, var trustPath) = verifier.Verify(devicePublicKeyAuthenticatorOutput.AttStmt, devicePublicKeyAuthenticatorOutput.AuthData, devicePublicKeyAuthenticatorOutput.Hash);
(var attType, var trustPath) = verifier.Verify(devicePublicKeyAuthenticatorOutput.AttStmt, AuthenticatorData.Parse(devicePublicKeyAuthenticatorOutput.AuthData), devicePublicKeyAuthenticatorOutput.Hash);

// 5. Complete the steps from § 7.1 Registering a New Credential and, if those steps are successful,
// store the aaguid, dpk, scope, fmt, attStmt values indexed to the credential.id in the user account.
Expand Down Expand Up @@ -335,7 +325,7 @@ static bool ContainsAttestationType(MetadataBLOBPayloadEntry entry, MetadataAtte
/// </summary>
public sealed class ParsedAttestationObject
{
public ParsedAttestationObject(string fmt, CborMap attStmt, byte[] authData)
public ParsedAttestationObject(string fmt, CborMap attStmt, AuthenticatorData authData)
{
Fmt = fmt;
AttStmt = attStmt;
Expand All @@ -346,6 +336,23 @@ public ParsedAttestationObject(string fmt, CborMap attStmt, byte[] authData)

public CborMap AttStmt { get; }

public byte[] AuthData { get; }
public AuthenticatorData AuthData { get; }

internal static ParsedAttestationObject FromCbor(CborObject cbor)
{
if (!(
cbor["fmt"] is { Type: CborType.TextString } fmt &&
cbor["attStmt"] is { Type: CborType.Map } attStmt &&
cbor["authData"] is { Type: CborType.ByteString } authData))
{
throw new Fido2VerificationException(Fido2ErrorCode.MalformedAttestationObject, Fido2ErrorMessages.MalformedAttestationObject);
}

return new ParsedAttestationObject(
fmt : (string)fmt,
attStmt : (CborMap)attStmt,
authData : AuthenticatorData.Parse((byte[])authData)
);
}
}
}
Loading

0 comments on commit e3b5ebd

Please sign in to comment.