Skip to content

Commit

Permalink
Cleanup (#417)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
iamcarbon authored Aug 11, 2023
1 parent 285a611 commit bf75254
Show file tree
Hide file tree
Showing 27 changed files with 717 additions and 812 deletions.
2 changes: 1 addition & 1 deletion Src/Fido2/AttestationFormat/AndroidKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static byte[] GetAttestationChallenge(byte[] attExtBytes)
public static bool FindAllApplicationsField(byte[] attExtBytes)
{
// https://developer.android.com/training/articles/security-key-attestation#certificate_schema
// check both software and tee enforced AuthorizationList objects for presense of "allApplications" tag, number 600
// check both software and tee enforced AuthorizationList objects for presence of "allApplications" tag, number 600

var keyDescription = Asn1Element.Decode(attExtBytes);

Expand Down
16 changes: 8 additions & 8 deletions Src/Fido2/AttestationFormat/AttestationVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,27 +102,27 @@ internal static bool IsAttnCertCACert(X509ExtensionCollection exts)

internal static byte U2FTransportsFromAttnCert(X509ExtensionCollection exts)
{
byte u2ftransports = 0;
byte u2fTransports = 0;
var ext = exts.FirstOrDefault(e => e.Oid?.Value is "1.3.6.1.4.1.45724.2.1.1");
if (ext != null)
{
var decodedU2Ftransports = Asn1Element.Decode(ext.RawData);
decodedU2Ftransports.CheckPrimitive();
var decodedU2fTransports = Asn1Element.Decode(ext.RawData);
decodedU2fTransports.CheckPrimitive();

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

decodedU2Ftransports.CheckTag(Asn1Tag.PrimitiveBitString);
decodedU2fTransports.CheckTag(Asn1Tag.PrimitiveBitString);

u2ftransports = decodedU2Ftransports.GetBitString()[0];
u2fTransports = decodedU2fTransports.GetBitString()[0];
}

return u2ftransports;
return u2fTransports;
}

#nullable disable
Expand Down
167 changes: 83 additions & 84 deletions Src/Fido2/AttestationFormat/FidoU2f.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,93 +7,92 @@
using Fido2NetLib.Exceptions;
using Fido2NetLib.Objects;

namespace Fido2NetLib
namespace Fido2NetLib;

internal sealed class FidoU2f : AttestationVerifier
{
internal sealed class FidoU2f : AttestationVerifier
public override (AttestationType, X509Certificate2[]) Verify()
{
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)
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Aaguid was not empty parsing fido-u2f attestation statement");

// https://www.w3.org/TR/webauthn/#fido-u2f-attestation
// 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields.
// (handled in base class)

// 2a. Check that x5c has exactly one element and let attCert be that element.
if (!(X5c is CborArray { Length: 1 } x5cArray && x5cArray[0] is CborByteString { Length: > 0 }))
{
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, Fido2ErrorMessages.MalformedX5c_FidoU2fAttestation);
}

var attCert = new X509Certificate2((byte[])x5cArray[0]);

// TODO : Check why this variable isn't used. Remove it or use it.
var u2fTransports = U2FTransportsFromAttnCert(attCert.Extensions);

// 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
var pubKey = attCert.GetECDsaPublicKey()!;
var keyParams = pubKey.ExportParameters(false);

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (!keyParams.Curve.Oid.FriendlyName!.Equals(ECCurve.NamedCurves.nistP256.Oid.FriendlyName, StringComparison.Ordinal))
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve");
}
else
{
// 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)
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Aaguid was not empty parsing fido-u2f atttestation statement");

// https://www.w3.org/TR/webauthn/#fido-u2f-attestation
// 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields.
// (handled in base class)

// 2a. Check that x5c has exactly one element and let attCert be that element.
if (!(X5c is CborArray { Length: 1 } x5cArray && x5cArray[0] is CborByteString { Length: > 0 }))
{
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, Fido2ErrorMessages.MalformedX5c_FidoU2fAttestation);
}

var attCert = new X509Certificate2((byte[])x5cArray[0]);

// TODO : Check why this variable isn't used. Remove it or use it.
var u2ftransports = U2FTransportsFromAttnCert(attCert.Extensions);

// 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
var pubKey = attCert.GetECDsaPublicKey()!;
var keyParams = pubKey.ExportParameters(false);

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (!keyParams.Curve.Oid.FriendlyName!.Equals(ECCurve.NamedCurves.nistP256.Oid.FriendlyName, StringComparison.Ordinal))
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve");
}
else
{
if (!keyParams.Curve.Oid.Value!.Equals(ECCurve.NamedCurves.nistP256.Oid.Value, StringComparison.Ordinal))
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve");
}

// 3. Extract the claimed rpIdHash from authenticatorData, and the claimed credentialId and credentialPublicKey from authenticatorData
// see rpIdHash, credentialId, and credentialPublicKey members of base class AuthenticatorData (AuthData)

// 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)
// 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
var x = (byte[])CredentialPublicKey[COSE.KeyTypeParameter.X];

// 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
var y = (byte[])CredentialPublicKey[COSE.KeyTypeParameter.Y];

// 4c.Let publicKeyU2F be the concatenation 0x04 || x || y
var publicKeyU2F = DataHelper.Concat(stackalloc byte[1] { 0x4 }, x, y);

// 5. Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId || publicKeyU2F)
byte[] verificationData = DataHelper.Concat(
stackalloc byte[1] { 0x00 },
AuthData.RpIdHash,
_clientDataHash,
AuthData.AttestedCredentialData.CredentialID,
publicKeyU2F
);

// 6. Verify the sig using verificationData and certificate public key
if (!TryGetSig(out byte[]? sig))
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, Fido2ErrorMessages.InvalidFidoU2fAttestationSignature);

byte[] ecsig;
try
{
ecsig = CryptoUtils.SigFromEcDsaSig(sig, pubKey.KeySize);
}
catch (Exception ex)
{
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Failed to decode fido-u2f attestation signature from ASN.1 encoded form", ex);
}

var coseAlg = (COSE.Algorithm)(int)CredentialPublicKey[COSE.KeyCommonParameter.Alg];
var hashAlg = CryptoUtils.HashAlgFromCOSEAlg(coseAlg);

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

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

var trustPath = new X509Certificate2[1] { attCert };

return (AttestationType.AttCa, trustPath);
if (!keyParams.Curve.Oid.Value!.Equals(ECCurve.NamedCurves.nistP256.Oid.Value, StringComparison.Ordinal))
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve");
}

// 3. Extract the claimed rpIdHash from authenticatorData, and the claimed credentialId and credentialPublicKey from authenticatorData
// see rpIdHash, credentialId, and credentialPublicKey members of base class AuthenticatorData (AuthData)

// 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)
// 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
var x = (byte[])CredentialPublicKey[COSE.KeyTypeParameter.X];

// 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
var y = (byte[])CredentialPublicKey[COSE.KeyTypeParameter.Y];

// 4c.Let publicKeyU2F be the concatenation 0x04 || x || y
var publicKeyU2F = DataHelper.Concat(stackalloc byte[1] { 0x4 }, x, y);

// 5. Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId || publicKeyU2F)
byte[] verificationData = DataHelper.Concat(
stackalloc byte[1] { 0x00 },
AuthData.RpIdHash,
_clientDataHash,
AuthData.AttestedCredentialData.CredentialID,
publicKeyU2F
);

// 6. Verify the sig using verificationData and certificate public key
if (!TryGetSig(out byte[]? sig))
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, Fido2ErrorMessages.InvalidFidoU2fAttestationSignature);

byte[] ecsig;
try
{
ecsig = CryptoUtils.SigFromEcDsaSig(sig, pubKey.KeySize);
}
catch (Exception ex)
{
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestation, "Failed to decode fido-u2f attestation signature from ASN.1 encoded form", ex);
}

var coseAlg = (COSE.Algorithm)(int)CredentialPublicKey[COSE.KeyCommonParameter.Alg];
var hashAlg = CryptoUtils.HashAlgFromCOSEAlg(coseAlg);

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

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

var trustPath = new X509Certificate2[1] { attCert };

return (AttestationType.AttCa, trustPath);
}
}
2 changes: 1 addition & 1 deletion Src/Fido2/AttestationFormat/Packed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public override (AttestationType, X509Certificate2[]?) Verify()
}

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

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

Expand Down
60 changes: 30 additions & 30 deletions Src/Fido2/AttestationFormat/Tpm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,15 +229,15 @@ public override (AttestationType, X509Certificate2[]) Verify()
{ 3, TpmEccCurve.TPM_ECC_NIST_P521}
};

private static (string?, string?, string?) SANFromAttnCertExts(X509ExtensionCollection extensions)
private static (string?, string?, string?) SANFromAttnCertExts(X509ExtensionCollection exts)
{
string? tpmManufacturer = null;
string? tpmModel = null;
string? tpmVersion = null;

var foundSAN = false;

foreach (var extension in extensions)
foreach (var extension in exts)
{
if (extension.Oid!.Value is "2.5.29.17") // subject alternative name
{
Expand Down Expand Up @@ -433,7 +433,7 @@ public enum TpmAlg : ushort
// TPMS_ATTEST, TPMv2-Part2, section 10.12.8
public class CertInfo
{
private static readonly Dictionary<TpmAlg, ushort> tpmAlgToDigestSizeMap = new()
private static readonly Dictionary<TpmAlg, ushort> s_tpmAlgToDigestSizeMap = new()
{
{ TpmAlg.TPM_ALG_SHA1, (160/8) },
{ TpmAlg.TPM_ALG_SHA256, (256/8) },
Expand Down Expand Up @@ -472,7 +472,7 @@ public static (ushort size, byte[] name) NameFromTPM2BName(ReadOnlySpan<byte> ab
if (Enum.IsDefined(typeof(TpmAlg), size))
{
var tpmalg = (TpmAlg)size;
if (tpmAlgToDigestSizeMap.TryGetValue(tpmalg, out ushort tplAlgDigestSize))
if (s_tpmAlgToDigestSizeMap.TryGetValue(tpmalg, out ushort tplAlgDigestSize))
{
name = AuthDataHelper.GetSizedByteArray(ab, ref offset, tplAlgDigestSize);
}
Expand Down Expand Up @@ -529,19 +529,19 @@ public CertInfo(byte[] certInfo)
if (certInfo.Length != offset)
throw new Fido2VerificationException("Leftover bits decoding certInfo");
}
public byte[] Raw { get; private set; }
public byte[] Magic { get; private set; }
public byte[] Type { get; private set; }
public byte[] QualifiedSigner { get; private set; }
public byte[] ExtraData { get; private set; }
public byte[] Clock { get; private set; }
public byte[] ResetCount { get; private set; }
public byte[] RestartCount { get; private set; }
public byte[] Safe { get; private set; }
public byte[] FirmwareVersion { get; private set; }
public ushort Alg { get; private set; }
public byte[] AttestedName { get; private set; }
public byte[] AttestedQualifiedNameBuffer { get; private set; }
public byte[] Raw { get; }
public byte[] Magic { get; }
public byte[] Type { get; }
public byte[] QualifiedSigner { get; }
public byte[] ExtraData { get; }
public byte[] Clock { get; }
public byte[] ResetCount { get; }
public byte[] RestartCount { get; }
public byte[] Safe { get; }
public byte[] FirmwareVersion { get; }
public ushort Alg { get; }
public byte[] AttestedName { get; }
public byte[] AttestedQualifiedNameBuffer { get; }
}

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

public byte[] Raw { get; private set; }
public byte[] Type { get; private set; }
public byte[] Alg { get; private set; }
public byte[] Attributes { get; private set; }
public byte[] Policy { get; private set; }
public byte[]? Symmetric { get; private set; }
public byte[]? Scheme { get; private set; }
public byte[]? KeyBits { get; private set; }
public uint Exponent { get; private set; }
public byte[]? CurveID { get; private set; }
public byte[]? KDF { get; private set; }
public byte[]? Unique { get; private set; }
public byte[] Raw { get; }
public byte[] Type { get; }
public byte[] Alg { get; }
public byte[] Attributes { get; }
public byte[] Policy { get; }
public byte[]? Symmetric { get; }
public byte[]? Scheme { get; }
public byte[]? KeyBits { get; }
public uint Exponent { get; }
public byte[]? CurveID { get; }
public byte[]? KDF { get;}
public byte[]? Unique { get; }
public TpmEccCurve EccCurve => (TpmEccCurve)Enum.ToObject(typeof(TpmEccCurve), BinaryPrimitives.ReadUInt16BigEndian(CurveID));
public ECPoint ECPoint { get; private set; }
public ECPoint ECPoint { get; }
}
Loading

0 comments on commit bf75254

Please sign in to comment.