Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup #417

Merged
merged 8 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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