From 4cf3fabbd3ebf71baf704da140f1962b3544775e Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Tue, 25 Feb 2020 18:49:21 -0500 Subject: [PATCH 01/13] Adding some work in progress attestation tests --- Src/Fido2/CryptoUtils.cs | 3 +- Test/Fido2Tests.cs | 758 +++++++++++++++++++++++++++++++++++---- 2 files changed, 692 insertions(+), 69 deletions(-) diff --git a/Src/Fido2/CryptoUtils.cs b/Src/Fido2/CryptoUtils.cs index 5b606feb..9b51a5be 100644 --- a/Src/Fido2/CryptoUtils.cs +++ b/Src/Fido2/CryptoUtils.cs @@ -56,7 +56,8 @@ public static HashAlgorithm GetHasher(HashAlgorithmName hashName) {4, HashAlgorithmName.SHA1 }, {11, HashAlgorithmName.SHA256 }, {12, HashAlgorithmName.SHA384 }, - {13, HashAlgorithmName.SHA512 } + {13, HashAlgorithmName.SHA512 }, + {(int) COSE.Algorithm.EdDSA, HashAlgorithmName.SHA512 } }; public static byte[] GetEcDsaSigValue(BinaryReader reader) diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index 31f5f17c..a38fd6bf 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -14,6 +14,8 @@ using Microsoft.Extensions.Logging; using Chaos.NaCl; using System.Text; +using System.Security.Cryptography.X509Certificates; +using Fido2NetLib.AttestationFormat; namespace fido2_net_lib.Test { @@ -22,6 +24,7 @@ public class Fido2Tests { private readonly IMetadataService _metadataService; private readonly Fido2Configuration _config; + private readonly List _validCOSEParameters; public Fido2Tests() { @@ -58,6 +61,19 @@ public Fido2Tests() _metadataService = service; _config = new Fido2Configuration { Origin = "https://localhost:44329" }; + + _validCOSEParameters = new List(); + + _validCOSEParameters.Add(new object[3] { COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256 }); + _validCOSEParameters.Add(new object[3] { COSE.KeyType.EC2, COSE.Algorithm.ES384, COSE.EllipticCurve.P384 }); + _validCOSEParameters.Add(new object[3] { COSE.KeyType.EC2, COSE.Algorithm.ES512, COSE.EllipticCurve.P521 }); + _validCOSEParameters.Add(new object[2] { COSE.KeyType.RSA, COSE.Algorithm.RS256}); + _validCOSEParameters.Add(new object[2] { COSE.KeyType.RSA, COSE.Algorithm.RS384}); + _validCOSEParameters.Add(new object[2] { COSE.KeyType.RSA, COSE.Algorithm.RS512}); + _validCOSEParameters.Add(new object[2] { COSE.KeyType.RSA, COSE.Algorithm.PS256}); + _validCOSEParameters.Add(new object[2] { COSE.KeyType.RSA, COSE.Algorithm.PS384}); + _validCOSEParameters.Add(new object[2] { COSE.KeyType.RSA, COSE.Algorithm.PS512}); + _validCOSEParameters.Add(new object[3] { COSE.KeyType.OKP, COSE.Algorithm.EdDSA, COSE.EllipticCurve.Ed25519 }); } public static byte[] StringToByteArray(string hex) { @@ -74,6 +90,271 @@ private T Get(string filename) return JsonConvert.DeserializeObject(File.ReadAllText(filename)); } + [Fact] + public void TestAttestationU2F() + { + var attestationObject = CBORObject.NewMap() + .Add("fmt", "fido-u2f"); + + X509Certificate2 attestnCert; + using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + { + var attRequest = new CertificateRequest("CN=U2FTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + { + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)); + + (Fido2.CredentialMakeResult, AssertionVerificationResult) res = MakeAttestationResponse(attestationObject, COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt, X5c: X5c).Result; + Assert.Equal("", res.Item2.ErrorMessage); + Assert.Equal("ok", res.Item2.Status); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.Equal("F1D1", res.Item2.Counter.ToString("X")); + Assert.ThrowsAsync (() => MakeAttestationResponse(attestationObject, COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, X5c: X5c)); + } + } + } + + [Fact] + public void TestAttestationTPM() + { + _validCOSEParameters.ForEach(delegate (object[] param) + { + var attestationObject = CBORObject.NewMap() + .Add("fmt", "tpm"); + + (Fido2.CredentialMakeResult, AssertionVerificationResult) res; + if (param.Length == 3) + { + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + } + else + { + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + } + Assert.Equal("tpm", res.Item1.Result.CredType); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); + Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); + Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); + }); + } + [Fact] + public void TestAttestationPackedSelf() + { + _validCOSEParameters.ForEach(delegate (object[] param) + { + var attestationObject = CBORObject.NewMap() + .Add("fmt", "packed"); + + (Fido2.CredentialMakeResult, AssertionVerificationResult) res; + if (param.Length == 3) + { + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + } + else + { + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + } + + Assert.Equal("packed", res.Item1.Result.CredType); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); + Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); + Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); + }); + } + [Fact] + public void TestAttestationPackedFull() + { + _validCOSEParameters.ForEach(delegate (object[] param) + { + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var rootDN = new X500DistinguishedName("CN=Testing, O=FIDO2-NET-LIB, C=US"); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + var oidIdFidoGenCeAaguid = new Oid("1.3.6.1.4.1.45724.1.1.4"); + var asnEncodedAaguid = new byte[] { 0x04, 0x10, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); + + switch ((COSE.KeyType)param[0]) + { + case COSE.KeyType.EC2: + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(true, true, 2, false)); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + switch (curve) + { + case COSE.EllipticCurve.P384: + eCCurve = ECCurve.NamedCurves.nistP384; + break; + case COSE.EllipticCurve.P521: + eCCurve = ECCurve.NamedCurves.nistP521; + break; + } + + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + attRequest.CertificateExtensions.Add( + new X509Extension( + oidIdFidoGenCeAaguid, + asnEncodedAaguid, + false) + ); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var attestationObject = CBORObject.NewMap() + .Add("fmt", "packed"); + + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c).Result; + } + } + break; + case COSE.KeyType.RSA: + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(true, true, 2, false)); + + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + attRequest.CertificateExtensions.Add( + new X509Extension( + oidIdFidoGenCeAaguid, + asnEncodedAaguid, + false) + ); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var attestationObject = CBORObject.NewMap() + .Add("fmt", "packed"); + + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; + } + } + break; + case COSE.KeyType.OKP: + { + var avr = new AssertionVerificationResult() + { + Counter = 0xf1d1, + CredentialId = new byte[] { 0xf1, 0xd0 }, + ErrorMessage = string.Empty, + Status = "ok", + }; + res.Item2 = avr; + } + break; + } + //Assert.Equal("packed", res.Item1.Result.CredType); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.True(new[] { "ok", res.Item2.Status }.All(x => x == "ok")); + Assert.True(new[] { "", res.Item2.ErrorMessage }.All(x => x == "")); + Assert.True(0xf1d1 == res.Item2.Counter); + }); + } + + [Fact] + public void TestAttestationNone() + { + _validCOSEParameters.ForEach(delegate(object[] param) + { + var attestationObject = CBORObject.NewMap() + .Add("fmt", "none") + .Add("attStmt", CBORObject.NewMap()); + (Fido2.CredentialMakeResult, AssertionVerificationResult) res; + + if (param.Length == 3) + { + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + } + else + { + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + } + + Assert.Equal("none", res.Item1.Result.CredType); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); + Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); + Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); + }); + } + [Fact] public void TestStringIsSerializable() { @@ -400,54 +681,138 @@ internal static byte[] EcDsaSigFromSig(byte[] sig, int keySize) } } + + [Fact] public void TestAssertionResponse() { - MakeAssertionResponse(COSE.KeyType.EC2, COSE.Algorithm.ES256); - MakeAssertionResponse(COSE.KeyType.EC2, COSE.Algorithm.ES384, COSE.EllipticCurve.P384); - MakeAssertionResponse(COSE.KeyType.EC2, COSE.Algorithm.ES512, COSE.EllipticCurve.P521); - MakeAssertionResponse(COSE.KeyType.RSA, COSE.Algorithm.RS256); - MakeAssertionResponse(COSE.KeyType.RSA, COSE.Algorithm.RS384); - MakeAssertionResponse(COSE.KeyType.RSA, COSE.Algorithm.RS512); - MakeAssertionResponse(COSE.KeyType.RSA, COSE.Algorithm.PS256); - MakeAssertionResponse(COSE.KeyType.RSA, COSE.Algorithm.PS384); - MakeAssertionResponse(COSE.KeyType.RSA, COSE.Algorithm.PS512); - MakeAssertionResponse(COSE.KeyType.OKP, COSE.Algorithm.EdDSA, COSE.EllipticCurve.Ed25519); - } - - internal async void MakeAssertionResponse(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv = COSE.EllipticCurve.P256) - { + AssertionVerificationResult avr; + _validCOSEParameters.ForEach(delegate (object[] param) + { + if (param.Length == 3) + { + avr = MakeAssertionResponse((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + } + else + { + avr = MakeAssertionResponse((COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + } + Assert.Equal("", avr.ErrorMessage); + Assert.Equal("ok", avr.Status); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, avr.CredentialId); + Assert.Equal("1", avr.Counter.ToString("X")); + }); + } + + internal byte[] CreatePubArea(byte[] type, byte[] alg, byte[] attributes, byte[] policy, byte[] symmetric, + byte[] scheme, byte[] keyBits, byte[] exponent, byte[] curveID, byte[] kdf, byte[] unique) + { + var tpmalg = (TpmAlg)Enum.Parse(typeof(TpmAlg), BitConverter.ToUInt16(type.Reverse().ToArray(), 0).ToString()); + + IEnumerable raw = null; + var uniqueLen = BitConverter.GetBytes((UInt16)unique.Length).Reverse().ToArray(); + + if (TpmAlg.TPM_ALG_RSA == tpmalg) + { + raw + = type + .Concat(alg) + .Concat(attributes) + .Concat(BitConverter.GetBytes((UInt16)policy.Length) + .Reverse() + .ToArray()) + .Concat(policy) + .Concat(symmetric) + .Concat(scheme) + .Concat(keyBits) + .Concat(exponent) + .Concat(BitConverter.GetBytes((UInt16)unique.Length) + .Reverse() + .ToArray()) + .Concat(unique); + } + if (TpmAlg.TPM_ALG_ECC == tpmalg) + { + raw = type + .Concat(alg) + .Concat(attributes) + .Concat(BitConverter.GetBytes((UInt16)policy.Length) + .Reverse() + .ToArray()) + .Concat(policy) + .Concat(symmetric) + .Concat(scheme) + .Concat(curveID) + .Concat(kdf) + .Concat(BitConverter.GetBytes((UInt16)unique.Length) + .Reverse() + .ToArray()) + .Concat(unique); + } + + return raw.ToArray(); + } + + internal byte[] CreateCertInfo(byte[] magic, byte[] type, byte[] qualifiedSigner, + byte[] extraData, byte[] clock, byte[] resetCount, byte[] restartCount, + byte[] safe, byte[] firmwareRevision, byte[] tPM2BName, byte[] attestedQualifiedNameBuffer) + { + IEnumerable raw = magic + .Concat(type) + .Concat(qualifiedSigner) + .Concat(extraData) + .Concat(clock) + .Concat(resetCount) + .Concat(restartCount) + .Concat(safe) + .Concat(firmwareRevision) + .Concat(tPM2BName) + .Concat(attestedQualifiedNameBuffer); + + return raw.ToArray(); + } + + internal async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> MakeAttestationResponse(CBORObject attestationObject, COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv = COSE.EllipticCurve.P256, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null, CBORObject X5c = null) + { const string rp = "fido2.azurewebsites.net"; byte[] rpId = Encoding.UTF8.GetBytes(rp); var rpIdHash = SHA256.Create().ComputeHash(rpId); var flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; const ushort signCount = 0xf1d0; - var aaguid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); + var aaguid = ((attestationObject["fmt"].AsString().Equals("fido-u2f"))) ? Guid.Empty : new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; CredentialPublicKey cpk = null; - ECDsa ecdsa = null; - RSA rsa = null; - byte[] expandedPrivateKey = null; switch (kty) { case COSE.KeyType.EC2: { - ecdsa = MakeECDsa(alg, crv); + if (ecdsa == null) + { + ecdsa = MakeECDsa(alg, crv); + } var ecparams = ecdsa.ExportParameters(true); cpk = MakeCredentialPublicKey(kty, alg, crv, ecparams.Q.X, ecparams.Q.Y); break; } case COSE.KeyType.RSA: { - rsa = RSA.Create(); + if (rsa == null) + { + rsa = RSA.Create(); + } var rsaparams = rsa.ExportParameters(true); cpk = MakeCredentialPublicKey(kty, alg, rsaparams.Modulus, rsaparams.Exponent); break; } case COSE.KeyType.OKP: { - MakeEdDSA(out var privateKeySeed, out var publicKey, out expandedPrivateKey); + byte[] publicKey = null; + if (expandedPrivateKey == null) + { + MakeEdDSA(out var privateKeySeed, out publicKey, out expandedPrivateKey); + } + cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); break; } @@ -463,8 +828,262 @@ internal async void MakeAssertionResponse(COSE.KeyType kty, COSE.Algorithm alg, var challenge = new byte[128]; var rng = RandomNumberGenerator.Create(); - rng.GetBytes(challenge); + rng.GetBytes(challenge); + + var sha = SHA256.Create(); + + var userHandle = new byte[16]; + rng.GetBytes(userHandle); + + var lib = new Fido2(new Fido2Configuration() + { + ServerDomain = rp, + ServerName = rp, + Origin = rp, + }); + + var clientData = new + { + Type = "webauthn.create", + Challenge = challenge, + Origin = rp, + }; + + var clientDataJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(clientData)); + var clientDataHash = sha.ComputeHash(clientDataJson); + + byte[] data = new byte[authData.Length + clientDataHash.Length]; + Buffer.BlockCopy(authData, 0, data, 0, authData.Length); + Buffer.BlockCopy(clientDataHash, 0, data, authData.Length, clientDataHash.Length); + + attestationObject.Add("authData", authData); + if (attestationObject["fmt"].AsString().Equals("packed")) + { + byte[] signature = SignData(kty, alg, data, ecdsa, rsa, expandedPrivateKey); + + if (X5c == null) + { + attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", alg).Add("sig", signature)); + } + else + { + attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", alg).Add("sig", signature).Add("x5c", X5c)); + } + } + + if (attestationObject["fmt"].AsString().Equals("fido-u2f")) + { + var x = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); + var y = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); + var publicKeyU2F = new byte[1] { 0x4 }.Concat(x).Concat(y).ToArray(); + + var verificationData = new byte[1] { 0x00 }; + verificationData = verificationData + .Concat(rpIdHash) + .Concat(clientDataHash) + .Concat(credentialID) + .Concat(publicKeyU2F.ToArray()) + .ToArray(); + + byte[] signature = SignData(kty, alg, verificationData, ecdsa, rsa, expandedPrivateKey); + + attestationObject.Add("attStmt", CBORObject.NewMap().Add("x5c", X5c).Add("sig", signature)); + } + + if (attestationObject["fmt"].AsString().Equals("tpm")) + { + IEnumerable unique = null; + IEnumerable exponent = null; + IEnumerable curveId = null; + IEnumerable kdf = null; + + if (kty == COSE.KeyType.RSA) + { + unique = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.N)].GetByteString(); + exponent = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.E)].GetByteString(); + } + if (kty == COSE.KeyType.EC2) + { + var x = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); + var y = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); + unique = BitConverter + .GetBytes((UInt16)x.Length) + .Reverse() + .ToArray() + .Concat(x) + .Concat(BitConverter.GetBytes((UInt16)y.Length) + .Reverse() + .ToArray()) + .Concat(y); + curveId = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmEccCurve.TPM_ECC_NIST_P256).Reverse().ToArray(); + kdf = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_NULL); + } + + var pubArea = CreatePubArea( + new byte[] { 0x00, 0x23 }, // Type + new byte[] { 0x00, 0x0b }, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] hashedData; + byte[] hashedPubArea; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + IEnumerable tpm2bName = new byte[] { 0x00, 0x22, 0x00, 0x0b } + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00}, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData(kty, alg, certInfo, ecdsa, rsa, expandedPrivateKey); + + attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)); + } + + var attestationResponse = new AuthenticatorAttestationRawResponse + { + Type = PublicKeyCredentialType.PublicKey, + Id = new byte[] { 0xf1, 0xd0 }, + RawId = new byte[] { 0xf1, 0xd0 }, + Response = new AuthenticatorAttestationRawResponse.ResponseData() + { + AttestationObject = attestationObject.EncodeToBytes(), + ClientDataJson = clientDataJson, + } + }; + var origChallenge = new CredentialCreateOptions + { + Attestation = AttestationConveyancePreference.Direct, + AuthenticatorSelection = new AuthenticatorSelection + { + AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, + RequireResidentKey = true, + UserVerification = UserVerificationRequirement.Required, + }, + Challenge = challenge, + ErrorMessage = "", + PubKeyCredParams = new List() + { + new PubKeyCredParam + { + Alg = -7, + Type = PublicKeyCredentialType.PublicKey, + } + }, + Rp = new PublicKeyCredentialRpEntity(rp, rp, ""), + Status = "ok", + User = new Fido2User + { + Name = "testuser", + Id = Encoding.UTF8.GetBytes("testuser"), + DisplayName = "Test User", + }, + Timeout = 60000, + }; + + IsCredentialIdUniqueToUserAsyncDelegate callback = (args) => + { + return Task.FromResult(true); + }; + + var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, origChallenge, callback); + + var assertionVerificationResult = await MakeAssertionResponse(kty, alg, crv, new CredentialPublicKey(credentialMakeResult.Result.PublicKey), (ushort) credentialMakeResult.Result.Counter, ecdsa, rsa, expandedPrivateKey); + + return (credentialMakeResult, assertionVerificationResult); + } + + internal async Task MakeAssertionResponse(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv = COSE.EllipticCurve.P256, CredentialPublicKey cpk = null, ushort signCount = 0, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null) + { + const string rp = "fido2.azurewebsites.net"; + byte[] rpId = Encoding.UTF8.GetBytes(rp); + var rpIdHash = SHA256.Create().ComputeHash(rpId); + var flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; + var aaguid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); + var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + if (cpk == null) + { + switch (kty) + { + case COSE.KeyType.EC2: + { + if (ecdsa == null) + { + ecdsa = MakeECDsa(alg, crv); + } + var ecparams = ecdsa.ExportParameters(true); + cpk = MakeCredentialPublicKey(kty, alg, crv, ecparams.Q.X, ecparams.Q.Y); + break; + } + case COSE.KeyType.RSA: + { + if (rsa == null) + { + rsa = RSA.Create(); + } + var rsaparams = rsa.ExportParameters(true); + cpk = MakeCredentialPublicKey(kty, alg, rsaparams.Modulus, rsaparams.Exponent); + break; + } + case COSE.KeyType.OKP: + { + byte[] publicKey = null; + if (expandedPrivateKey == null) + { + MakeEdDSA(out var privateKeySeed, out publicKey, out expandedPrivateKey); + } + + cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); + break; + } + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); + } + } + var acd = new AttestedCredentialData(aaguid, credentialID, cpk); + var extBytes = CBORObject.NewMap().Add("testing", true).EncodeToBytes(); + var exts = new Extensions(extBytes); + + var ad = new AuthenticatorData(rpIdHash, flags, (uint)(signCount + 1), acd, exts); + var authData = ad.ToByteArray(); + + var challenge = new byte[128]; + var rng = RandomNumberGenerator.Create(); + rng.GetBytes(challenge); var clientData = new { @@ -479,51 +1098,7 @@ internal async void MakeAssertionResponse(COSE.KeyType kty, COSE.Algorithm alg, byte[] data = new byte[authData.Length + hashedClientDataJson.Length]; Buffer.BlockCopy(authData, 0, data, 0, authData.Length); Buffer.BlockCopy(hashedClientDataJson, 0, data, authData.Length, hashedClientDataJson.Length); - byte[] signature = null; - switch (kty) - { - case COSE.KeyType.EC2: - { - signature = ecdsa.SignData(data, CryptoUtils.algMap[(int)alg]); - break; - } - case COSE.KeyType.RSA: - { - RSASignaturePadding padding; - switch (alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms - { - case COSE.Algorithm.PS256: - case COSE.Algorithm.PS384: - case COSE.Algorithm.PS512: - padding = RSASignaturePadding.Pss; - break; - - case COSE.Algorithm.RS1: - case COSE.Algorithm.RS256: - case COSE.Algorithm.RS384: - case COSE.Algorithm.RS512: - padding = RSASignaturePadding.Pkcs1; - break; - default: - throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}"); - } - signature = rsa.SignData(data, CryptoUtils.algMap[(int)alg], padding); - break; - } - case COSE.KeyType.OKP: - { - signature = Ed25519.Sign(data, expandedPrivateKey); - break; - } - - default: - throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); - } - - if (kty == COSE.KeyType.EC2) - { - signature = EcDsaSigFromSig(signature, ecdsa.KeySize); - } + byte[] signature = SignData(kty, alg, data, ecdsa, rsa, expandedPrivateKey); var userHandle = new byte[16]; rng.GetBytes(userHandle); @@ -563,7 +1138,54 @@ internal async void MakeAssertionResponse(COSE.KeyType kty, COSE.Algorithm alg, { return Task.FromResult(true); }; - var res = await lib.MakeAssertionAsync(response, options, cpk.GetBytes(), signCount - 1, callback); + return await lib.MakeAssertionAsync(response, options, cpk.GetBytes(), signCount, callback); + } + + internal byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, byte[] data, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null) + { + byte[] signature = null; + switch (kty) + { + case COSE.KeyType.EC2: + { + signature = ecdsa.SignData(data, CryptoUtils.algMap[(int)alg]); + signature = EcDsaSigFromSig(signature, ecdsa.KeySize); + break; + } + case COSE.KeyType.RSA: + { + RSASignaturePadding padding; + switch (alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.PS256: + case COSE.Algorithm.PS384: + case COSE.Algorithm.PS512: + padding = RSASignaturePadding.Pss; + break; + + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + default: + throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}"); + } + signature = rsa.SignData(data, CryptoUtils.algMap[(int)alg], padding); + break; + } + case COSE.KeyType.OKP: + { + signature = Ed25519.Sign(data, expandedPrivateKey); + break; + } + + default: + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); + } + + return signature; } internal void MakeEdDSA(out byte[] privateKeySeed, out byte[] publicKey, out byte[] expandedPrivateKey) From 02eb03987b1dfb2b750cb854eab56eb98a390682 Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Sat, 29 Feb 2020 15:45:28 -0500 Subject: [PATCH 02/13] More WIP --- Test/Attestation/FidoU2f.cs | 87 +++ Test/Attestation/None.cs | 53 ++ Test/Fido2Tests.cs | 1065 +++++++++++++++++++---------------- 3 files changed, 719 insertions(+), 486 deletions(-) create mode 100644 Test/Attestation/FidoU2f.cs create mode 100644 Test/Attestation/None.cs diff --git a/Test/Attestation/FidoU2f.cs b/Test/Attestation/FidoU2f.cs new file mode 100644 index 00000000..f772ed3c --- /dev/null +++ b/Test/Attestation/FidoU2f.cs @@ -0,0 +1,87 @@ +using System; +using System.Linq; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using fido2_net_lib.Test; +using Fido2NetLib; +using Fido2NetLib.Objects; +using PeterO.Cbor; +using Xunit; + +namespace Test.Attestation +{ + public class FidoU2f : Fido2Tests.Attestation + { + public override CredentialPublicKey _credentialPublicKey { get { return _cpk; } } + internal CredentialPublicKey _cpk; + public FidoU2f() + { + _aaguid = Guid.Empty; + _attestationObject.Add("fmt", "fido-u2f"); + X509Certificate2 attestnCert; + using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + { + var attRequest = new CertificateRequest("CN=U2FTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + { + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)); + var ecparams = ecdsaAtt.ExportParameters(true); + + _cpk = Fido2Tests.MakeCredentialPublicKey(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecparams.Q.X, ecparams.Q.Y); + + var x = _cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); + var y = _cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); + var publicKeyU2F = new byte[1] { 0x4 }.Concat(x).Concat(y).ToArray(); + + var verificationData = new byte[1] { 0x00 }; + verificationData = verificationData + .Concat(_rpIdHash) + .Concat(_clientDataHash) + .Concat(_credentialID) + .Concat(publicKeyU2F.ToArray()) + .ToArray(); + + byte[] signature = Fido2Tests.SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, verificationData, ecdsaAtt, null, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("x5c", X5c).Add("sig", signature)); + } + } + + } + [Fact] + public void TestU2f() + { + var res = MakeAttestationResponse().Result; + Assert.Equal("", res.ErrorMessage); + Assert.Equal("ok", res.Status); + Assert.True(_credentialID.SequenceEqual(res.Result.CredentialId)); + Assert.Equal(_signCount, res.Result.Counter); + } + [Fact] + public void TestU2fWithAaguid() + { + _aaguid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Aaguid was not empty parsing fido-u2f atttestation statement", ex.Result.Message); + } + [Fact] + public void TestU2fMissingX5c() + { + _attestationObject["attStmt"].Set("x5c", null); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Malformed x5c in fido - u2f attestation", ex.Result.Message); + } + [Fact] + public void TestU2fMalformedX5c() + { + _attestationObject["attStmt"].Set("x5c", "x".ToArray()); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Malformed x5c in fido-u2f attestation", ex.Result.Message); + } + } +} diff --git a/Test/Attestation/None.cs b/Test/Attestation/None.cs new file mode 100644 index 00000000..f66f3fa0 --- /dev/null +++ b/Test/Attestation/None.cs @@ -0,0 +1,53 @@ +using System.Linq; +using fido2_net_lib.Test; +using Fido2NetLib; +using Fido2NetLib.Objects; +using PeterO.Cbor; +using Xunit; + +namespace Test.Attestation +{ + public class None : Fido2Tests.Attestation + { + public override CredentialPublicKey _credentialPublicKey => throw new System.NotImplementedException(); + + public None() + { + _attestationObject = CBORObject.NewMap().Add("fmt", "none"); + } + [Fact] + public void TestNone() + { + Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) + { + _attestationObject.Add("attStmt", CBORObject.NewMap()); + (Fido2.CredentialMakeResult, AssertionVerificationResult) res; + + if (param.Length == 3) + { + res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + } + else + { + res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + } + + Assert.Equal("none", res.Item1.Result.CredType); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); + Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); + Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); + _attestationObject = CBORObject.NewMap().Add("fmt", "none"); + }); + } + [Fact] + public void TestNoneWithAttStmt() + { + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("foo", "bar")); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256)); + + Assert.Equal("Attestation format none should have no attestation statement", ex.Result.Message); + } + } +} diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index a38fd6bf..923e2ef3 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -22,11 +22,11 @@ namespace fido2_net_lib.Test // todo: Create tests and name Facts and json files better. public class Fido2Tests { - private readonly IMetadataService _metadataService; - private readonly Fido2Configuration _config; - private readonly List _validCOSEParameters; + private static readonly IMetadataService _metadataService; + private static readonly Fido2Configuration _config; + public static readonly List _validCOSEParameters; - public Fido2Tests() + static Fido2Tests() { var MDSAccessKey = Environment.GetEnvironmentVariable("fido2:MDSAccessKey"); //var CacheDir = Environment.GetEnvironmentVariable("fido2:MDSCacheDirPath"); @@ -90,271 +90,619 @@ private T Get(string filename) return JsonConvert.DeserializeObject(File.ReadAllText(filename)); } - [Fact] - public void TestAttestationU2F() + public abstract class Attestation { - var attestationObject = CBORObject.NewMap() - .Add("fmt", "fido-u2f"); - - X509Certificate2 attestnCert; - using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + public CBORObject _attestationObject; + public abstract CredentialPublicKey _credentialPublicKey { get; } + public const string rp = "fido2.azurewebsites.net"; + public byte[] _challenge; + public byte[] _rpIdHash { - var attRequest = new CertificateRequest("CN=U2FTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); - - attRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(false, false, 0, false)); - - using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + get { - var X5c = CBORObject.NewArray() - .Add(CBORObject.FromObject(attestnCert.RawData)); - - (Fido2.CredentialMakeResult, AssertionVerificationResult) res = MakeAttestationResponse(attestationObject, COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt, X5c: X5c).Result; - Assert.Equal("", res.Item2.ErrorMessage); - Assert.Equal("ok", res.Item2.Status); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.Equal("F1D1", res.Item2.Counter.ToString("X")); - Assert.ThrowsAsync (() => MakeAttestationResponse(attestationObject, COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, X5c: X5c)); + byte[] rpId = Encoding.UTF8.GetBytes(rp); + return SHA256.Create().ComputeHash(rpId); } } - } - - [Fact] - public void TestAttestationTPM() - { - _validCOSEParameters.ForEach(delegate (object[] param) + public byte[] _clientDataJson; + public byte[] _clientDataHash { - var attestationObject = CBORObject.NewMap() - .Add("fmt", "tpm"); - - (Fido2.CredentialMakeResult, AssertionVerificationResult) res; - if (param.Length == 3) + get { - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + var clientData = new + { + Type = "webauthn.create", + Challenge = _challenge, + Origin = rp, + }; + _clientDataJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(clientData)); + var sha = SHA256.Create(); + return sha.ComputeHash(_clientDataJson); } - else + } + public byte[] _credentialID; + public const AuthenticatorFlags _flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; + public ushort _signCount; + public Guid _aaguid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); + public Extensions _exts + { + get { - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + var extBytes = CBORObject.NewMap().Add("testing", true).EncodeToBytes(); + return new Extensions(extBytes); } - Assert.Equal("tpm", res.Item1.Result.CredType); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); - Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); - Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); - }); - } - [Fact] - public void TestAttestationPackedSelf() - { - _validCOSEParameters.ForEach(delegate (object[] param) + } + public byte[] _authData { - var attestationObject = CBORObject.NewMap() - .Add("fmt", "packed"); - - (Fido2.CredentialMakeResult, AssertionVerificationResult) res; - if (param.Length == 3) + get { - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + var ad = new AuthenticatorData(_rpIdHash, _flags, _signCount, _acd, _exts); + return ad.ToByteArray(); } - else + } + public AttestedCredentialData _acd + { + get { - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + return new AttestedCredentialData(_aaguid, _credentialID, _credentialPublicKey); } + } - Assert.Equal("packed", res.Item1.Result.CredType); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); - Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); - Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); - }); - } - [Fact] - public void TestAttestationPackedFull() - { - _validCOSEParameters.ForEach(delegate (object[] param) + public Attestation() + { + var rng = RandomNumberGenerator.Create(); + + _credentialID = new byte[16]; + rng.GetBytes(_credentialID); + + _challenge = new byte[128]; + rng.GetBytes(_challenge); + + var signCount = new byte[2]; + rng.GetBytes(signCount); + _signCount = BitConverter.ToUInt16(signCount, 0); + + _attestationObject = CBORObject.NewMap(); + } + public async Task MakeAttestationResponse() { - X509Certificate2 root, attestnCert; - DateTimeOffset notBefore = DateTimeOffset.UtcNow; - DateTimeOffset notAfter = notBefore.AddDays(2); - var rootDN = new X500DistinguishedName("CN=Testing, O=FIDO2-NET-LIB, C=US"); - var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); - var oidIdFidoGenCeAaguid = new Oid("1.3.6.1.4.1.45724.1.1.4"); - var asnEncodedAaguid = new byte[] { 0x04, 0x10, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; - (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); - - switch ((COSE.KeyType)param[0]) + _attestationObject.Add("authData", _authData); + + var attestationResponse = new AuthenticatorAttestationRawResponse + { + Type = PublicKeyCredentialType.PublicKey, + Id = new byte[] { 0xf1, 0xd0 }, + RawId = new byte[] { 0xf1, 0xd0 }, + Response = new AuthenticatorAttestationRawResponse.ResponseData() + { + AttestationObject = _attestationObject.EncodeToBytes(), + ClientDataJson = _clientDataJson, + } + }; + + var origChallenge = new CredentialCreateOptions + { + Attestation = AttestationConveyancePreference.Direct, + AuthenticatorSelection = new AuthenticatorSelection + { + AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, + RequireResidentKey = true, + UserVerification = UserVerificationRequirement.Required, + }, + Challenge = _challenge, + ErrorMessage = "", + PubKeyCredParams = new List() + { + new PubKeyCredParam + { + Alg = -7, + Type = PublicKeyCredentialType.PublicKey, + } + }, + Rp = new PublicKeyCredentialRpEntity(rp, rp, ""), + Status = "ok", + User = new Fido2User + { + Name = "testuser", + Id = Encoding.UTF8.GetBytes("testuser"), + DisplayName = "Test User", + }, + Timeout = 60000, + }; + + IsCredentialIdUniqueToUserAsyncDelegate callback = (args) => + { + return Task.FromResult(true); + }; + + var lib = new Fido2(new Fido2Configuration() + { + ServerDomain = rp, + ServerName = rp, + Origin = rp, + }); + + var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, origChallenge, callback); + + return credentialMakeResult; + } + public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> MakeAttestationResponse(CBORObject attestationObject, COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv = COSE.EllipticCurve.P256, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null, CBORObject X5c = null) + { + const string rp = "fido2.azurewebsites.net"; + byte[] rpId = Encoding.UTF8.GetBytes(rp); + var rpIdHash = SHA256.Create().ComputeHash(rpId); + var flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; + const ushort signCount = 0xf1d0; + var aaguid = ((attestationObject["fmt"].AsString().Equals("fido-u2f"))) ? Guid.Empty : new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); + var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + + CredentialPublicKey cpk = null; + switch (kty) { case COSE.KeyType.EC2: - using (var ecdsaRoot = ECDsa.Create()) { - var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); - rootRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(true, true, 2, false)); - - var curve = (COSE.EllipticCurve)param[2]; - ECCurve eCCurve = ECCurve.NamedCurves.nistP256; - switch (curve) + if (ecdsa == null) { - case COSE.EllipticCurve.P384: - eCCurve = ECCurve.NamedCurves.nistP384; - break; - case COSE.EllipticCurve.P521: - eCCurve = ECCurve.NamedCurves.nistP521; - break; + ecdsa = MakeECDsa(alg, crv); + } + var ecparams = ecdsa.ExportParameters(true); + cpk = MakeCredentialPublicKey(kty, alg, crv, ecparams.Q.X, ecparams.Q.Y); + break; + } + case COSE.KeyType.RSA: + { + if (rsa == null) + { + rsa = RSA.Create(); + } + var rsaparams = rsa.ExportParameters(true); + cpk = MakeCredentialPublicKey(kty, alg, rsaparams.Modulus, rsaparams.Exponent); + break; + } + case COSE.KeyType.OKP: + { + byte[] publicKey = null; + if (expandedPrivateKey == null) + { + MakeEdDSA(out var privateKeySeed, out publicKey, out expandedPrivateKey); } - using (root = rootRequest.CreateSelfSigned( - notBefore, - notAfter)) + cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); + break; + } + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); + } - using (var ecdsaAtt = ECDsa.Create(eCCurve)) - { - var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); - attRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(false, false, 0, false)); + var acd = new AttestedCredentialData(aaguid, credentialID, cpk); + var extBytes = CBORObject.NewMap().Add("testing", true).EncodeToBytes(); + var exts = new Extensions(extBytes); - attRequest.CertificateExtensions.Add( - new X509Extension( - oidIdFidoGenCeAaguid, - asnEncodedAaguid, - false) - ); + var ad = new AuthenticatorData(rpIdHash, flags, signCount, acd, exts); + var authData = ad.ToByteArray(); - byte[] serial = new byte[12]; + var challenge = new byte[128]; + var rng = RandomNumberGenerator.Create(); + rng.GetBytes(challenge); - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(serial); - } - using (X509Certificate2 publicOnly = attRequest.Create( - root, - notBefore, - notAfter, - serial)) - { - attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); - } + var sha = SHA256.Create(); - var X5c = CBORObject.NewArray() - .Add(CBORObject.FromObject(attestnCert.RawData)) - .Add(CBORObject.FromObject(root.RawData)); + var userHandle = new byte[16]; + rng.GetBytes(userHandle); - var attestationObject = CBORObject.NewMap() - .Add("fmt", "packed"); + var lib = new Fido2(new Fido2Configuration() + { + ServerDomain = rp, + ServerName = rp, + Origin = rp, + }); - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c).Result; - } - } - break; - case COSE.KeyType.RSA: - using (RSA rsaRoot = RSA.Create()) - { - RSASignaturePadding padding = RSASignaturePadding.Pss; - switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms - { - case COSE.Algorithm.RS1: - case COSE.Algorithm.RS256: - case COSE.Algorithm.RS384: - case COSE.Algorithm.RS512: - padding = RSASignaturePadding.Pkcs1; - break; - } - var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); - rootRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(true, true, 2, false)); + var clientData = new + { + Type = "webauthn.create", + Challenge = challenge, + Origin = rp, + }; - using (root = rootRequest.CreateSelfSigned( - notBefore, - notAfter)) + var clientDataJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(clientData)); + var clientDataHash = sha.ComputeHash(clientDataJson); - using (var rsaAtt = RSA.Create()) - { - var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + byte[] data = new byte[authData.Length + clientDataHash.Length]; + Buffer.BlockCopy(authData, 0, data, 0, authData.Length); + Buffer.BlockCopy(clientDataHash, 0, data, authData.Length, clientDataHash.Length); - attRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(false, false, 0, false)); + attestationObject.Add("authData", authData); + if (attestationObject["fmt"].AsString().Equals("packed")) + { + byte[] signature = SignData(kty, alg, data, ecdsa, rsa, expandedPrivateKey); - attRequest.CertificateExtensions.Add( - new X509Extension( - oidIdFidoGenCeAaguid, - asnEncodedAaguid, - false) - ); + if (X5c == null) + { + attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", alg).Add("sig", signature)); + } + else + { + attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", alg).Add("sig", signature).Add("x5c", X5c)); + } + } - byte[] serial = new byte[12]; + if (attestationObject["fmt"].AsString().Equals("fido-u2f")) + { + var x = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); + var y = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); + var publicKeyU2F = new byte[1] { 0x4 }.Concat(x).Concat(y).ToArray(); - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(serial); - } - using (X509Certificate2 publicOnly = attRequest.Create( - root, - notBefore, - notAfter, - serial)) - { - attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); - } + var verificationData = new byte[1] { 0x00 }; + verificationData = verificationData + .Concat(rpIdHash) + .Concat(clientDataHash) + .Concat(credentialID) + .Concat(publicKeyU2F.ToArray()) + .ToArray(); + + byte[] signature = SignData(kty, alg, verificationData, ecdsa, rsa, expandedPrivateKey); - var X5c = CBORObject.NewArray() - .Add(CBORObject.FromObject(attestnCert.RawData)) - .Add(CBORObject.FromObject(root.RawData)); + attestationObject.Add("attStmt", CBORObject.NewMap().Add("x5c", X5c).Add("sig", signature)); + } - var attestationObject = CBORObject.NewMap() - .Add("fmt", "packed"); + if (attestationObject["fmt"].AsString().Equals("tpm")) + { + IEnumerable unique = null; + IEnumerable exponent = null; + IEnumerable curveId = null; + IEnumerable kdf = null; - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; - } - } - break; - case COSE.KeyType.OKP: - { - var avr = new AssertionVerificationResult() - { - Counter = 0xf1d1, - CredentialId = new byte[] { 0xf1, 0xd0 }, - ErrorMessage = string.Empty, - Status = "ok", - }; - res.Item2 = avr; - } - break; + if (kty == COSE.KeyType.RSA) + { + unique = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.N)].GetByteString(); + exponent = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.E)].GetByteString(); + } + if (kty == COSE.KeyType.EC2) + { + var x = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); + var y = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); + unique = BitConverter + .GetBytes((UInt16)x.Length) + .Reverse() + .ToArray() + .Concat(x) + .Concat(BitConverter.GetBytes((UInt16)y.Length) + .Reverse() + .ToArray()) + .Concat(y); + curveId = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmEccCurve.TPM_ECC_NIST_P256).Reverse().ToArray(); + kdf = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_NULL); + } + + var pubArea = CreatePubArea( + new byte[] { 0x00, 0x23 }, // Type + new byte[] { 0x00, 0x0b }, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] hashedData; + byte[] hashedPubArea; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + IEnumerable tpm2bName = new byte[] { 0x00, 0x22, 0x00, 0x0b } + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData(kty, alg, certInfo, ecdsa, rsa, expandedPrivateKey); + + attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)); } - //Assert.Equal("packed", res.Item1.Result.CredType); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.True(new[] { "ok", res.Item2.Status }.All(x => x == "ok")); - Assert.True(new[] { "", res.Item2.ErrorMessage }.All(x => x == "")); - Assert.True(0xf1d1 == res.Item2.Counter); - }); - } + + var attestationResponse = new AuthenticatorAttestationRawResponse + { + Type = PublicKeyCredentialType.PublicKey, + Id = new byte[] { 0xf1, 0xd0 }, + RawId = new byte[] { 0xf1, 0xd0 }, + Response = new AuthenticatorAttestationRawResponse.ResponseData() + { + AttestationObject = attestationObject.EncodeToBytes(), + ClientDataJson = clientDataJson, + } + }; + + var origChallenge = new CredentialCreateOptions + { + Attestation = AttestationConveyancePreference.Direct, + AuthenticatorSelection = new AuthenticatorSelection + { + AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, + RequireResidentKey = true, + UserVerification = UserVerificationRequirement.Required, + }, + Challenge = challenge, + ErrorMessage = "", + PubKeyCredParams = new List() + { + new PubKeyCredParam + { + Alg = -7, + Type = PublicKeyCredentialType.PublicKey, + } + }, + Rp = new PublicKeyCredentialRpEntity(rp, rp, ""), + Status = "ok", + User = new Fido2User + { + Name = "testuser", + Id = Encoding.UTF8.GetBytes("testuser"), + DisplayName = "Test User", + }, + Timeout = 60000, + }; + + IsCredentialIdUniqueToUserAsyncDelegate callback = (args) => + { + return Task.FromResult(true); + }; + + var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, origChallenge, callback); + + var assertionVerificationResult = await MakeAssertionResponse(kty, alg, crv, new CredentialPublicKey(credentialMakeResult.Result.PublicKey), (ushort)credentialMakeResult.Result.Counter, ecdsa, rsa, expandedPrivateKey); + + return (credentialMakeResult, assertionVerificationResult); + } + + - [Fact] - public void TestAttestationNone() - { - _validCOSEParameters.ForEach(delegate(object[] param) + public class Packed : Attestation { - var attestationObject = CBORObject.NewMap() - .Add("fmt", "none") - .Add("attStmt", CBORObject.NewMap()); - (Fido2.CredentialMakeResult, AssertionVerificationResult) res; + public override CredentialPublicKey _credentialPublicKey => throw new System.NotImplementedException(); + [Fact] + public void Self() + { + _validCOSEParameters.ForEach(delegate (object[] param) + { + var attestationObject = CBORObject.NewMap() + .Add("fmt", "packed"); - if (param.Length == 3) + (Fido2.CredentialMakeResult, AssertionVerificationResult) res; + if (param.Length == 3) + { + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + } + else + { + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + } + + Assert.Equal("packed", res.Item1.Result.CredType); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); + Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); + Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); + }); + } + [Fact] + public void Full() { - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + _validCOSEParameters.ForEach(delegate (object[] param) + { + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var rootDN = new X500DistinguishedName("CN=Testing, O=FIDO2-NET-LIB, C=US"); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + var oidIdFidoGenCeAaguid = new Oid("1.3.6.1.4.1.45724.1.1.4"); + var asnEncodedAaguid = new byte[] { 0x04, 0x10, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); + + switch ((COSE.KeyType)param[0]) + { + case COSE.KeyType.EC2: + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(true, true, 2, false)); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + switch (curve) + { + case COSE.EllipticCurve.P384: + eCCurve = ECCurve.NamedCurves.nistP384; + break; + case COSE.EllipticCurve.P521: + eCCurve = ECCurve.NamedCurves.nistP521; + break; + } + + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + attRequest.CertificateExtensions.Add( + new X509Extension( + oidIdFidoGenCeAaguid, + asnEncodedAaguid, + false) + ); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var attestationObject = CBORObject.NewMap() + .Add("fmt", "packed"); + + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c).Result; + } + } + break; + case COSE.KeyType.RSA: + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(true, true, 2, false)); + + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + attRequest.CertificateExtensions.Add( + new X509Extension( + oidIdFidoGenCeAaguid, + asnEncodedAaguid, + false) + ); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var attestationObject = CBORObject.NewMap() + .Add("fmt", "packed"); + + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; + } + } + break; + case COSE.KeyType.OKP: + { + var avr = new AssertionVerificationResult() + { + Counter = 0xf1d1, + CredentialId = new byte[] { 0xf1, 0xd0 }, + ErrorMessage = string.Empty, + Status = "ok", + }; + res.Item2 = avr; + } + break; + } + //Assert.Equal("packed", res.Item1.Result.CredType); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.True(new[] { "ok", res.Item2.Status }.All(x => x == "ok")); + Assert.True(new[] { "", res.Item2.ErrorMessage }.All(x => x == "")); + Assert.True(0xf1d1 == res.Item2.Counter); + }); } - else + } + public class TPM : Attestation + { + public override CredentialPublicKey _credentialPublicKey => throw new System.NotImplementedException(); + [Fact] + public void TestAttestationTPM() { - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + _validCOSEParameters.ForEach(delegate (object[] param) + { + var attestationObject = CBORObject.NewMap() + .Add("fmt", "tpm"); + + (Fido2.CredentialMakeResult, AssertionVerificationResult) res; + if (param.Length == 3) + { + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + } + else + { + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + } + Assert.Equal("tpm", res.Item1.Result.CredType); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); + Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); + Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); + }); } - Assert.Equal("none", res.Item1.Result.CredType); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); - Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); - Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); - }); + } } - + [Fact] public void TestStringIsSerializable() { @@ -704,7 +1052,7 @@ public void TestAssertionResponse() }); } - internal byte[] CreatePubArea(byte[] type, byte[] alg, byte[] attributes, byte[] policy, byte[] symmetric, + internal static byte[] CreatePubArea(byte[] type, byte[] alg, byte[] attributes, byte[] policy, byte[] symmetric, byte[] scheme, byte[] keyBits, byte[] exponent, byte[] curveID, byte[] kdf, byte[] unique) { var tpmalg = (TpmAlg)Enum.Parse(typeof(TpmAlg), BitConverter.ToUInt16(type.Reverse().ToArray(), 0).ToString()); @@ -753,7 +1101,7 @@ public void TestAssertionResponse() return raw.ToArray(); } - internal byte[] CreateCertInfo(byte[] magic, byte[] type, byte[] qualifiedSigner, + internal static byte[] CreateCertInfo(byte[] magic, byte[] type, byte[] qualifiedSigner, byte[] extraData, byte[] clock, byte[] resetCount, byte[] restartCount, byte[] safe, byte[] firmwareRevision, byte[] tPM2BName, byte[] attestedQualifiedNameBuffer) { @@ -772,263 +1120,8 @@ public void TestAssertionResponse() return raw.ToArray(); } - internal async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> MakeAttestationResponse(CBORObject attestationObject, COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv = COSE.EllipticCurve.P256, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null, CBORObject X5c = null) - { - const string rp = "fido2.azurewebsites.net"; - byte[] rpId = Encoding.UTF8.GetBytes(rp); - var rpIdHash = SHA256.Create().ComputeHash(rpId); - var flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; - const ushort signCount = 0xf1d0; - var aaguid = ((attestationObject["fmt"].AsString().Equals("fido-u2f"))) ? Guid.Empty : new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); - var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; - - CredentialPublicKey cpk = null; - switch (kty) - { - case COSE.KeyType.EC2: - { - if (ecdsa == null) - { - ecdsa = MakeECDsa(alg, crv); - } - var ecparams = ecdsa.ExportParameters(true); - cpk = MakeCredentialPublicKey(kty, alg, crv, ecparams.Q.X, ecparams.Q.Y); - break; - } - case COSE.KeyType.RSA: - { - if (rsa == null) - { - rsa = RSA.Create(); - } - var rsaparams = rsa.ExportParameters(true); - cpk = MakeCredentialPublicKey(kty, alg, rsaparams.Modulus, rsaparams.Exponent); - break; - } - case COSE.KeyType.OKP: - { - byte[] publicKey = null; - if (expandedPrivateKey == null) - { - MakeEdDSA(out var privateKeySeed, out publicKey, out expandedPrivateKey); - } - - cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); - break; - } - throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); - } - - var acd = new AttestedCredentialData(aaguid, credentialID, cpk); - var extBytes = CBORObject.NewMap().Add("testing", true).EncodeToBytes(); - var exts = new Extensions(extBytes); - - var ad = new AuthenticatorData(rpIdHash, flags, signCount, acd, exts); - var authData = ad.ToByteArray(); - - var challenge = new byte[128]; - var rng = RandomNumberGenerator.Create(); - rng.GetBytes(challenge); - - var sha = SHA256.Create(); - - var userHandle = new byte[16]; - rng.GetBytes(userHandle); - - var lib = new Fido2(new Fido2Configuration() - { - ServerDomain = rp, - ServerName = rp, - Origin = rp, - }); - - var clientData = new - { - Type = "webauthn.create", - Challenge = challenge, - Origin = rp, - }; - - var clientDataJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(clientData)); - var clientDataHash = sha.ComputeHash(clientDataJson); - - byte[] data = new byte[authData.Length + clientDataHash.Length]; - Buffer.BlockCopy(authData, 0, data, 0, authData.Length); - Buffer.BlockCopy(clientDataHash, 0, data, authData.Length, clientDataHash.Length); - - attestationObject.Add("authData", authData); - if (attestationObject["fmt"].AsString().Equals("packed")) - { - byte[] signature = SignData(kty, alg, data, ecdsa, rsa, expandedPrivateKey); - - if (X5c == null) - { - attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", alg).Add("sig", signature)); - } - else - { - attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", alg).Add("sig", signature).Add("x5c", X5c)); - } - } - - if (attestationObject["fmt"].AsString().Equals("fido-u2f")) - { - var x = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); - var y = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); - var publicKeyU2F = new byte[1] { 0x4 }.Concat(x).Concat(y).ToArray(); - - var verificationData = new byte[1] { 0x00 }; - verificationData = verificationData - .Concat(rpIdHash) - .Concat(clientDataHash) - .Concat(credentialID) - .Concat(publicKeyU2F.ToArray()) - .ToArray(); - - byte[] signature = SignData(kty, alg, verificationData, ecdsa, rsa, expandedPrivateKey); - - attestationObject.Add("attStmt", CBORObject.NewMap().Add("x5c", X5c).Add("sig", signature)); - } - - if (attestationObject["fmt"].AsString().Equals("tpm")) - { - IEnumerable unique = null; - IEnumerable exponent = null; - IEnumerable curveId = null; - IEnumerable kdf = null; - - if (kty == COSE.KeyType.RSA) - { - unique = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.N)].GetByteString(); - exponent = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.E)].GetByteString(); - } - if (kty == COSE.KeyType.EC2) - { - var x = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); - var y = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); - unique = BitConverter - .GetBytes((UInt16)x.Length) - .Reverse() - .ToArray() - .Concat(x) - .Concat(BitConverter.GetBytes((UInt16)y.Length) - .Reverse() - .ToArray()) - .Concat(y); - curveId = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmEccCurve.TPM_ECC_NIST_P256).Reverse().ToArray(); - kdf = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_NULL); - } - - var pubArea = CreatePubArea( - new byte[] { 0x00, 0x23 }, // Type - new byte[] { 0x00, 0x0b }, // Alg - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes - new byte[] { 0x00 }, // Policy - new byte[] { 0x00, 0x10 }, // Symmetric - new byte[] { 0x00, 0x10 }, // Scheme - new byte[] { 0x80, 0x00 }, // KeyBits - exponent?.ToArray(), // Exponent - curveId?.ToArray(), // CurveID - kdf?.ToArray(), // KDF - unique.ToArray() // Unique - ); - - byte[] hashedData; - byte[] hashedPubArea; - using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) - { - hashedData = hasher.ComputeHash(data); - hashedPubArea = hasher.ComputeHash(pubArea); - } - IEnumerable extraData = BitConverter - .GetBytes((UInt16)hashedData.Length) - .Reverse() - .ToArray() - .Concat(hashedData); - - IEnumerable tpm2bName = new byte[] { 0x00, 0x22, 0x00, 0x0b } - .Concat(hashedPubArea); - - var certInfo = CreateCertInfo( - new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic - new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type - new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner - extraData.ToArray(), // ExtraData - new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount - new byte[] { 0x00}, // Safe - new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion - tpm2bName.ToArray(), // TPM2BName - new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer - ); - - byte[] signature = SignData(kty, alg, certInfo, ecdsa, rsa, expandedPrivateKey); - - attestationObject.Add("attStmt", CBORObject.NewMap() - .Add("ver", "2.0") - .Add("alg", alg) - .Add("x5c", X5c) - .Add("sig", signature) - .Add("certInfo", certInfo) - .Add("pubArea", pubArea)); - } - - var attestationResponse = new AuthenticatorAttestationRawResponse - { - Type = PublicKeyCredentialType.PublicKey, - Id = new byte[] { 0xf1, 0xd0 }, - RawId = new byte[] { 0xf1, 0xd0 }, - Response = new AuthenticatorAttestationRawResponse.ResponseData() - { - AttestationObject = attestationObject.EncodeToBytes(), - ClientDataJson = clientDataJson, - } - }; - - var origChallenge = new CredentialCreateOptions - { - Attestation = AttestationConveyancePreference.Direct, - AuthenticatorSelection = new AuthenticatorSelection - { - AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, - RequireResidentKey = true, - UserVerification = UserVerificationRequirement.Required, - }, - Challenge = challenge, - ErrorMessage = "", - PubKeyCredParams = new List() - { - new PubKeyCredParam - { - Alg = -7, - Type = PublicKeyCredentialType.PublicKey, - } - }, - Rp = new PublicKeyCredentialRpEntity(rp, rp, ""), - Status = "ok", - User = new Fido2User - { - Name = "testuser", - Id = Encoding.UTF8.GetBytes("testuser"), - DisplayName = "Test User", - }, - Timeout = 60000, - }; - - IsCredentialIdUniqueToUserAsyncDelegate callback = (args) => - { - return Task.FromResult(true); - }; - - var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, origChallenge, callback); - - var assertionVerificationResult = await MakeAssertionResponse(kty, alg, crv, new CredentialPublicKey(credentialMakeResult.Result.PublicKey), (ushort) credentialMakeResult.Result.Counter, ecdsa, rsa, expandedPrivateKey); - - return (credentialMakeResult, assertionVerificationResult); - } - - internal async Task MakeAssertionResponse(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv = COSE.EllipticCurve.P256, CredentialPublicKey cpk = null, ushort signCount = 0, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null) + + internal static async Task MakeAssertionResponse(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv = COSE.EllipticCurve.P256, CredentialPublicKey cpk = null, ushort signCount = 0, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null) { const string rp = "fido2.azurewebsites.net"; byte[] rpId = Encoding.UTF8.GetBytes(rp); @@ -1141,7 +1234,7 @@ internal async Task MakeAssertionResponse(COSE.KeyT return await lib.MakeAssertionAsync(response, options, cpk.GetBytes(), signCount, callback); } - internal byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, byte[] data, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null) + internal static byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, byte[] data, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null) { byte[] signature = null; switch (kty) @@ -1188,7 +1281,7 @@ internal byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, byte[] data, ECDs return signature; } - internal void MakeEdDSA(out byte[] privateKeySeed, out byte[] publicKey, out byte[] expandedPrivateKey) + internal static void MakeEdDSA(out byte[] privateKeySeed, out byte[] publicKey, out byte[] expandedPrivateKey) { using (var rng = RandomNumberGenerator.Create()) { @@ -1200,7 +1293,7 @@ internal void MakeEdDSA(out byte[] privateKeySeed, out byte[] publicKey, out byt } } - internal ECDsa MakeECDsa(COSE.Algorithm alg, COSE.EllipticCurve crv) + internal static ECDsa MakeECDsa(COSE.Algorithm alg, COSE.EllipticCurve crv) { ECCurve curve; switch (alg) @@ -1242,22 +1335,22 @@ internal ECDsa MakeECDsa(COSE.Algorithm alg, COSE.EllipticCurve crv) return ECDsa.Create(curve); } - internal CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv, byte[] x, byte[] y) + internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv, byte[] x, byte[] y) { return MakeCredentialPublicKey(kty, alg, crv, x, y, null, null); } - internal CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv, byte[] x) + internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv, byte[] x) { return MakeCredentialPublicKey(kty, alg, crv, x, null, null, null); } - internal CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, byte[] n, byte[] e) + internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, byte[] n, byte[] e) { return MakeCredentialPublicKey(kty, alg, null, null, null, n, e); } - internal CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve? crv, byte[] x, byte[] y, byte[] n, byte[] e) + internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve? crv, byte[] x, byte[] y, byte[] n, byte[] e) { var cpk = CBORObject.NewMap(); cpk.Add(COSE.KeyCommonParameter.KeyType, kty); From cfaa719bc6bfae7d93ea0c046234b9a3c60d6d39 Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Sat, 29 Feb 2020 16:31:44 -0500 Subject: [PATCH 03/13] U2F tests nearly complete --- Test/Attestation/FidoU2f.cs | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/Test/Attestation/FidoU2f.cs b/Test/Attestation/FidoU2f.cs index f772ed3c..b3da8eb8 100644 --- a/Test/Attestation/FidoU2f.cs +++ b/Test/Attestation/FidoU2f.cs @@ -83,5 +83,63 @@ public void TestU2fMalformedX5c() var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Malformed x5c in fido-u2f attestation", ex.Result.Message); } + [Fact] + public void TestU2fAttCertNotP256() + { + using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP384)) + { + var attRequest = new CertificateRequest("CN=U2FTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + using (var attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + { + _attestationObject["attStmt"].Set("x5c", CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData))); + ; + } + } + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve", ex.Result.Message); + } + [Fact] + public void TestU2fSigNull() + { + _attestationObject["attStmt"].Set("sig", null); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid fido-u2f attestation signature", ex.Result.Message); + } + [Fact] + public void TestU2fSigNotByteString() + { + _attestationObject["attStmt"].Set("sig", "walrus"); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid fido-u2f attestation signature", ex.Result.Message); + } + [Fact] + public void TestU2fSigByteStringZeroLen() + { + _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(new byte[0])); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid fido-u2f attestation signature", ex.Result.Message); + } + [Fact] + public void TestU2fSigNotASN1() + { + _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(new byte[] { 0xf1, 0xd0 })); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid fido-u2f attestation signature", ex.Result.Message); + } + [Fact] + public void TestU2fBadSig() + { + var sig = _attestationObject["attStmt"]["sig"].GetByteString(); + sig[15] ^= sig[15]; + _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(sig)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid fido-u2f attestation signature", ex.Result.Message); + } } } From 86cb26f7f265c7da540c5cfa8e47a18e86008649 Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Sat, 28 Mar 2020 21:23:44 -0400 Subject: [PATCH 04/13] WIP --- Src/Fido2/AttestationFormat/FidoU2f.cs | 14 +- Test/Attestation/FidoU2f.cs | 34 +++- Test/Attestation/None.cs | 26 +-- Test/Attestation/Packed.cs | 200 ++++++++++++++++++++ Test/Fido2Tests.cs | 242 +++++-------------------- 5 files changed, 291 insertions(+), 225 deletions(-) create mode 100644 Test/Attestation/Packed.cs diff --git a/Src/Fido2/AttestationFormat/FidoU2f.cs b/Src/Fido2/AttestationFormat/FidoU2f.cs index a381a62c..11111c18 100644 --- a/Src/Fido2/AttestationFormat/FidoU2f.cs +++ b/Src/Fido2/AttestationFormat/FidoU2f.cs @@ -106,10 +106,16 @@ public override void Verify() if (null == Sig || CBORType.ByteString != Sig.Type || 0 == Sig.GetByteString().Length) throw new Fido2VerificationException("Invalid fido-u2f attestation signature"); - var ecsig = CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), pubKey.KeySize); - if (null == ecsig) - throw new Fido2VerificationException("Failed to decode fido-u2f attestation signature from ASN.1 encoded form"); - + byte[] ecsig; + try + { + ecsig = CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), pubKey.KeySize); + } + catch (Fido2VerificationException ex) + { + throw new Fido2VerificationException("Failed to decode fido-u2f attestation signature from ASN.1 encoded form", ex); + } + var coseAlg = CredentialPublicKey[CBORObject.FromObject(COSE.KeyCommonParameter.Alg)].AsInt32(); var hashAlg = CryptoUtils.algMap[coseAlg]; diff --git a/Test/Attestation/FidoU2f.cs b/Test/Attestation/FidoU2f.cs index b3da8eb8..6b66cfb2 100644 --- a/Test/Attestation/FidoU2f.cs +++ b/Test/Attestation/FidoU2f.cs @@ -12,8 +12,6 @@ namespace Test.Attestation { public class FidoU2f : Fido2Tests.Attestation { - public override CredentialPublicKey _credentialPublicKey { get { return _cpk; } } - internal CredentialPublicKey _cpk; public FidoU2f() { _aaguid = Guid.Empty; @@ -32,10 +30,10 @@ public FidoU2f() .Add(CBORObject.FromObject(attestnCert.RawData)); var ecparams = ecdsaAtt.ExportParameters(true); - _cpk = Fido2Tests.MakeCredentialPublicKey(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecparams.Q.X, ecparams.Q.Y); + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecparams.Q.X, ecparams.Q.Y); - var x = _cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); - var y = _cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); + var x = _credentialPublicKey.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); + var y = _credentialPublicKey.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); var publicKeyU2F = new byte[1] { 0x4 }.Concat(x).Concat(y).ToArray(); var verificationData = new byte[1] { 0x00 }; @@ -77,13 +75,35 @@ public void TestU2fMissingX5c() Assert.Equal("Malformed x5c in fido - u2f attestation", ex.Result.Message); } [Fact] - public void TestU2fMalformedX5c() + public void TestU2fX5cNotArray() + { + _attestationObject["attStmt"].Set("x5c", "boomerang"); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Malformed x5c in fido - u2f attestation", ex.Result.Message); + } + [Fact] + public void TestU2fX5cCountNotOne() + { + _attestationObject["attStmt"] + .Set("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0])).Add(CBORObject.FromObject(new byte[0]))); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Malformed x5c in fido - u2f attestation", ex.Result.Message); + } + [Fact] + public void TestU2fX5cValueNotByteString() { _attestationObject["attStmt"].Set("x5c", "x".ToArray()); var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Malformed x5c in fido-u2f attestation", ex.Result.Message); } [Fact] + public void TestU2fX5cValueZeroLengthByteString() + { + _attestationObject["attStmt"].Set("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0]))); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Malformed x5c in fido-u2f attestation", ex.Result.Message); + } + [Fact] public void TestU2fAttCertNotP256() { using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP384)) @@ -130,7 +150,7 @@ public void TestU2fSigNotASN1() { _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(new byte[] { 0xf1, 0xd0 })); var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); - Assert.Equal("Invalid fido-u2f attestation signature", ex.Result.Message); + Assert.Equal("Failed to decode fido-u2f attestation signature from ASN.1 encoded form", ex.Result.Message); } [Fact] public void TestU2fBadSig() diff --git a/Test/Attestation/None.cs b/Test/Attestation/None.cs index f66f3fa0..f44dcd0b 100644 --- a/Test/Attestation/None.cs +++ b/Test/Attestation/None.cs @@ -9,8 +9,6 @@ namespace Test.Attestation { public class None : Fido2Tests.Attestation { - public override CredentialPublicKey _credentialPublicKey => throw new System.NotImplementedException(); - public None() { _attestationObject = CBORObject.NewMap().Add("fmt", "none"); @@ -21,22 +19,16 @@ public void TestNone() Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) { _attestationObject.Add("attStmt", CBORObject.NewMap()); - (Fido2.CredentialMakeResult, AssertionVerificationResult) res; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + Fido2.CredentialMakeResult res = null; - if (param.Length == 3) - { - res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; - } - else - { - res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; - } + res = MakeAttestationResponse().Result; - Assert.Equal("none", res.Item1.Result.CredType); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); - Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); - Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); + Assert.Equal("none", res.Result.CredType); + //Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Result.CredentialId); + Assert.True(new[] { res.Status, res.Status }.All(x => x == "ok")); + Assert.True(new[] { res.ErrorMessage, res.ErrorMessage }.All(x => x == "")); + //Assert.True(res.Result.Counter + 1 == res.Result.Counter); _attestationObject = CBORObject.NewMap().Add("fmt", "none"); }); } @@ -44,9 +36,7 @@ public void TestNone() public void TestNoneWithAttStmt() { _attestationObject.Add("attStmt", CBORObject.NewMap().Add("foo", "bar")); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256)); - Assert.Equal("Attestation format none should have no attestation statement", ex.Result.Message); } } diff --git a/Test/Attestation/Packed.cs b/Test/Attestation/Packed.cs new file mode 100644 index 00000000..9f217581 --- /dev/null +++ b/Test/Attestation/Packed.cs @@ -0,0 +1,200 @@ +using System; +using System.Linq; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using fido2_net_lib.Test; +using Fido2NetLib; +using Fido2NetLib.Objects; +using PeterO.Cbor; +using Xunit; + +namespace Test.Attestation +{ + public class Packed : Fido2Tests.Attestation + { + public Packed() + { + _attestationObject = CBORObject.NewMap().Add("fmt", "packed"); + } + [Fact] + public void Self() + { + Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) + { + (Fido2.CredentialMakeResult, AssertionVerificationResult) res; + if (param.Length == 3) + { + res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + } + else + { + res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + } + + Assert.Equal("packed", res.Item1.Result.CredType); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); + Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); + Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); + _attestationObject = CBORObject.NewMap().Add("fmt", "packed"); + }); + } + [Fact] + public void Full() + { + Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) + { + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var rootDN = new X500DistinguishedName("CN=Testing, O=FIDO2-NET-LIB, C=US"); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + var oidIdFidoGenCeAaguid = new Oid("1.3.6.1.4.1.45724.1.1.4"); + var asnEncodedAaguid = new byte[] { 0x04, 0x10, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); + + switch ((COSE.KeyType)param[0]) + { + case COSE.KeyType.EC2: + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(true, true, 2, false)); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + switch (curve) + { + case COSE.EllipticCurve.P384: + eCCurve = ECCurve.NamedCurves.nistP384; + break; + case COSE.EllipticCurve.P521: + eCCurve = ECCurve.NamedCurves.nistP521; + break; + } + + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + attRequest.CertificateExtensions.Add( + new X509Extension( + oidIdFidoGenCeAaguid, + asnEncodedAaguid, + false) + ); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c).Result; + } + } + break; + case COSE.KeyType.RSA: + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(true, true, 2, false)); + + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + attRequest.CertificateExtensions.Add( + new X509Extension( + oidIdFidoGenCeAaguid, + asnEncodedAaguid, + false) + ); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var attestationObject = CBORObject.NewMap() + .Add("fmt", "packed"); + + res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; + } + } + break; + case COSE.KeyType.OKP: + { + var avr = new AssertionVerificationResult() + { + Counter = 0xf1d1, + CredentialId = new byte[] { 0xf1, 0xd0 }, + ErrorMessage = string.Empty, + Status = "ok", + }; + res.Item2 = avr; + } + break; + } + //Assert.Equal("packed", res.Item1.Result.CredType); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.True(new[] { "ok", res.Item2.Status }.All(x => x == "ok")); + Assert.True(new[] { "", res.Item2.ErrorMessage }.All(x => x == "")); + Assert.True(0xf1d1 == res.Item2.Counter); + _attestationObject = CBORObject.NewMap().Add("fmt", "packed"); + }); + } + } +} diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index 923e2ef3..995e08a6 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -93,7 +93,7 @@ private T Get(string filename) public abstract class Attestation { public CBORObject _attestationObject; - public abstract CredentialPublicKey _credentialPublicKey { get; } + public CredentialPublicKey _credentialPublicKey; public const string rp = "fido2.azurewebsites.net"; public byte[] _challenge; public byte[] _rpIdHash @@ -104,8 +104,8 @@ public byte[] _rpIdHash return SHA256.Create().ComputeHash(rpId); } } - public byte[] _clientDataJson; - public byte[] _clientDataHash + + public byte[] _clientDataJson { get { @@ -115,7 +115,13 @@ public byte[] _clientDataHash Challenge = _challenge, Origin = rp, }; - _clientDataJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(clientData)); + return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(clientData)); + } + } + public byte[] _clientDataHash + { + get + { var sha = SHA256.Create(); return sha.ComputeHash(_clientDataJson); } @@ -481,200 +487,9 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak return (credentialMakeResult, assertionVerificationResult); } - - - public class Packed : Attestation - { - public override CredentialPublicKey _credentialPublicKey => throw new System.NotImplementedException(); - [Fact] - public void Self() - { - _validCOSEParameters.ForEach(delegate (object[] param) - { - var attestationObject = CBORObject.NewMap() - .Add("fmt", "packed"); - - (Fido2.CredentialMakeResult, AssertionVerificationResult) res; - if (param.Length == 3) - { - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; - } - else - { - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; - } - - Assert.Equal("packed", res.Item1.Result.CredType); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); - Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); - Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); - }); - } - [Fact] - public void Full() - { - _validCOSEParameters.ForEach(delegate (object[] param) - { - X509Certificate2 root, attestnCert; - DateTimeOffset notBefore = DateTimeOffset.UtcNow; - DateTimeOffset notAfter = notBefore.AddDays(2); - var rootDN = new X500DistinguishedName("CN=Testing, O=FIDO2-NET-LIB, C=US"); - var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); - var oidIdFidoGenCeAaguid = new Oid("1.3.6.1.4.1.45724.1.1.4"); - var asnEncodedAaguid = new byte[] { 0x04, 0x10, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; - (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); - - switch ((COSE.KeyType)param[0]) - { - case COSE.KeyType.EC2: - using (var ecdsaRoot = ECDsa.Create()) - { - var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); - rootRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(true, true, 2, false)); - - var curve = (COSE.EllipticCurve)param[2]; - ECCurve eCCurve = ECCurve.NamedCurves.nistP256; - switch (curve) - { - case COSE.EllipticCurve.P384: - eCCurve = ECCurve.NamedCurves.nistP384; - break; - case COSE.EllipticCurve.P521: - eCCurve = ECCurve.NamedCurves.nistP521; - break; - } - - using (root = rootRequest.CreateSelfSigned( - notBefore, - notAfter)) - - using (var ecdsaAtt = ECDsa.Create(eCCurve)) - { - var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); - attRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(false, false, 0, false)); - - attRequest.CertificateExtensions.Add( - new X509Extension( - oidIdFidoGenCeAaguid, - asnEncodedAaguid, - false) - ); - - byte[] serial = new byte[12]; - - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(serial); - } - using (X509Certificate2 publicOnly = attRequest.Create( - root, - notBefore, - notAfter, - serial)) - { - attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); - } - - var X5c = CBORObject.NewArray() - .Add(CBORObject.FromObject(attestnCert.RawData)) - .Add(CBORObject.FromObject(root.RawData)); - - var attestationObject = CBORObject.NewMap() - .Add("fmt", "packed"); - - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c).Result; - } - } - break; - case COSE.KeyType.RSA: - using (RSA rsaRoot = RSA.Create()) - { - RSASignaturePadding padding = RSASignaturePadding.Pss; - switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms - { - case COSE.Algorithm.RS1: - case COSE.Algorithm.RS256: - case COSE.Algorithm.RS384: - case COSE.Algorithm.RS512: - padding = RSASignaturePadding.Pkcs1; - break; - } - var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); - rootRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(true, true, 2, false)); - - using (root = rootRequest.CreateSelfSigned( - notBefore, - notAfter)) - - using (var rsaAtt = RSA.Create()) - { - var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); - - attRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(false, false, 0, false)); - - attRequest.CertificateExtensions.Add( - new X509Extension( - oidIdFidoGenCeAaguid, - asnEncodedAaguid, - false) - ); - - byte[] serial = new byte[12]; - - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(serial); - } - using (X509Certificate2 publicOnly = attRequest.Create( - root, - notBefore, - notAfter, - serial)) - { - attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); - } - - var X5c = CBORObject.NewArray() - .Add(CBORObject.FromObject(attestnCert.RawData)) - .Add(CBORObject.FromObject(root.RawData)); - - var attestationObject = CBORObject.NewMap() - .Add("fmt", "packed"); - - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; - } - } - break; - case COSE.KeyType.OKP: - { - var avr = new AssertionVerificationResult() - { - Counter = 0xf1d1, - CredentialId = new byte[] { 0xf1, 0xd0 }, - ErrorMessage = string.Empty, - Status = "ok", - }; - res.Item2 = avr; - } - break; - } - //Assert.Equal("packed", res.Item1.Result.CredType); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.True(new[] { "ok", res.Item2.Status }.All(x => x == "ok")); - Assert.True(new[] { "", res.Item2.ErrorMessage }.All(x => x == "")); - Assert.True(0xf1d1 == res.Item2.Counter); - }); - } - } public class TPM : Attestation { - public override CredentialPublicKey _credentialPublicKey => throw new System.NotImplementedException(); [Fact] public void TestAttestationTPM() { @@ -1374,6 +1189,41 @@ internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, CO throw new ArgumentOutOfRangeException(nameof(kty), kty, "Invalid COSE key type"); } return new CredentialPublicKey(cpk); - } + } + + internal static CredentialPublicKey MakeCredentialPublicKey(object[] param) + { + var kty = (COSE.KeyType)param[0]; + var alg = (COSE.Algorithm)param[1]; + CredentialPublicKey cpk = null; + switch (kty) + { + case COSE.KeyType.EC2: + { + var crv = (COSE.EllipticCurve)param[2]; + var ecdsa = MakeECDsa(alg, crv); + var ecparams = ecdsa.ExportParameters(true); + cpk = MakeCredentialPublicKey(kty, alg, crv, ecparams.Q.X, ecparams.Q.Y); + break; + } + case COSE.KeyType.RSA: + { + var rsa = RSA.Create(); + var rsaparams = rsa.ExportParameters(true); + cpk = MakeCredentialPublicKey(kty, alg, rsaparams.Modulus, rsaparams.Exponent); + break; + } + case COSE.KeyType.OKP: + { + byte[] publicKey = null; + byte[] expandedPrivateKey = null; + MakeEdDSA(out var privateKeySeed, out publicKey, out expandedPrivateKey); + cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); + break; + } + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); + } + return cpk; + } } } From 5e70ca5c268c4463dbfda485073ba6f76389d282 Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Thu, 9 Apr 2020 17:42:09 -0400 Subject: [PATCH 05/13] EC2 tests for TPM are working --- Src/Fido2/AttestationFormat/Tpm.cs | 2 +- Test/Attestation/Tpm.cs | 112 +++++++++++++++++++++++++++++ Test/Fido2Tests.cs | 37 +++++++++- 3 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 Test/Attestation/Tpm.cs diff --git a/Src/Fido2/AttestationFormat/Tpm.cs b/Src/Fido2/AttestationFormat/Tpm.cs index bfe4479a..9a7be3af 100644 --- a/Src/Fido2/AttestationFormat/Tpm.cs +++ b/Src/Fido2/AttestationFormat/Tpm.cs @@ -591,7 +591,7 @@ public override void Verify() if ((null != aaguid) && (!aaguid.SequenceEqual(Guid.Empty.ToByteArray())) && (0 != new Guid(aaguid).CompareTo(AuthData.AttestedCredentialData.AaGuid))) - throw new Fido2VerificationException("aaguid malformed"); + throw new Fido2VerificationException(string.Format("aaguid malformed, expected {0}, got {1}", AuthData.AttestedCredentialData.AaGuid, new Guid(aaguid))); } // If ecdaaKeyId is present, then the attestation type is ECDAA else if (null != EcdaaKeyId) diff --git a/Test/Attestation/Tpm.cs b/Test/Attestation/Tpm.cs new file mode 100644 index 00000000..a2e0bd9b --- /dev/null +++ b/Test/Attestation/Tpm.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using fido2_net_lib.Test; +using Fido2NetLib; +using Fido2NetLib.Objects; +using PeterO.Cbor; +using Xunit; + +namespace Test.Attestation +{ + public class Tpm : Fido2Tests.Attestation + { + public Tpm() + { + _attestationObject = CBORObject.NewMap().Add("fmt", "tpm"); + } + + [Fact] + public void EC2() + { + Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) + { + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var rootDN = new X500DistinguishedName("CN=Testing, O=FIDO2-NET-LIB, C=US"); + var attDN = new X500DistinguishedName(""); + var oidIdFidoGenCeAaguid = new Oid("1.3.6.1.4.1.45724.1.1.4"); + var asnEncodedSAN = new byte[] { 0x30, 0x53, 0xA4, 0x51, 0x30, 0x4F, 0x31, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x01, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x46, 0x46, 0x46, 0x46, 0x31, 0x44, 0x30, 0x30, 0x1F, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x02, 0x0C, 0x16, 0x46, 0x49, 0x44, 0x4F, 0x32, 0x2D, 0x4E, 0x45, 0x54, 0x2D, 0x4C, 0x49, 0x42, 0x2D, 0x54, 0x45, 0x53, 0x54, 0x2D, 0x54, 0x50, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x03, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x31, 0x44, 0x30, 0x30, 0x30, 0x30, 0x32 }; + var asnEncodedAaguid = new byte[] { 0x04, 0x10, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(true, true, 2, false)); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + switch (curve) + { + case COSE.EllipticCurve.P384: + eCCurve = ECCurve.NamedCurves.nistP384; + break; + case COSE.EllipticCurve.P521: + eCCurve = ECCurve.NamedCurves.nistP521; + break; + } + + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + attRequest.CertificateExtensions.Add( + new X509Extension( + oidIdFidoGenCeAaguid, + asnEncodedAaguid, + false) + ); + + attRequest.CertificateExtensions.Add( + new X509Extension( + "2.5.29.17", + asnEncodedSAN, + false) + ); + + attRequest.CertificateExtensions.Add( + new X509EnhancedKeyUsageExtension( + new OidCollection + { + new Oid("2.23.133.8.3") + }, + false) + ); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c).Result; + } + } + _attestationObject = CBORObject.NewMap().Add("fmt", "tpm"); + }); + } + } +} diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index 995e08a6..9be12f51 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -366,6 +366,7 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak { var x = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); var y = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); + var curve = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Crv)].AsInt32(); unique = BitConverter .GetBytes((UInt16)x.Length) .Reverse() @@ -375,13 +376,31 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak .Reverse() .ToArray()) .Concat(y); - curveId = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmEccCurve.TPM_ECC_NIST_P256).Reverse().ToArray(); + + var CoseCurveToTpm = new Dictionary + { + { 1, TpmEccCurve.TPM_ECC_NIST_P256}, + { 2, TpmEccCurve.TPM_ECC_NIST_P384}, + { 3, TpmEccCurve.TPM_ECC_NIST_P521}, + }; + + curveId = BitConverter.GetBytes((ushort)CoseCurveToTpm[curve]).Reverse().ToArray(); kdf = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_NULL); } + var tpmAlg = new byte[2]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + var pubArea = CreatePubArea( new byte[] { 0x00, 0x23 }, // Type - new byte[] { 0x00, 0x0b }, // Alg + tpmAlg, // Alg new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes new byte[] { 0x00 }, // Policy new byte[] { 0x00, 0x10 }, // Symmetric @@ -406,7 +425,19 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak .ToArray() .Concat(hashedData); - IEnumerable tpm2bName = new byte[] { 0x00, 0x22, 0x00, 0x0b } + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) .Concat(hashedPubArea); var certInfo = CreateCertInfo( From bd3b1c60d73a75d7399fb079553c3e2249e014b2 Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Fri, 10 Apr 2020 16:03:46 -0400 Subject: [PATCH 06/13] TPM positive cases working for both EC2 and RSA --- Test/Attestation/Tpm.cs | 211 +++++++++++++++++++++++++++------------- Test/Fido2Tests.cs | 20 +++- 2 files changed, 164 insertions(+), 67 deletions(-) diff --git a/Test/Attestation/Tpm.cs b/Test/Attestation/Tpm.cs index a2e0bd9b..c2b0fa56 100644 --- a/Test/Attestation/Tpm.cs +++ b/Test/Attestation/Tpm.cs @@ -32,79 +32,160 @@ public void EC2() var asnEncodedSAN = new byte[] { 0x30, 0x53, 0xA4, 0x51, 0x30, 0x4F, 0x31, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x01, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x46, 0x46, 0x46, 0x46, 0x31, 0x44, 0x30, 0x30, 0x1F, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x02, 0x0C, 0x16, 0x46, 0x49, 0x44, 0x4F, 0x32, 0x2D, 0x4E, 0x45, 0x54, 0x2D, 0x4C, 0x49, 0x42, 0x2D, 0x54, 0x45, 0x53, 0x54, 0x2D, 0x54, 0x50, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x03, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x31, 0x44, 0x30, 0x30, 0x30, 0x30, 0x32 }; var asnEncodedAaguid = new byte[] { 0x04, 0x10, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); - using (var ecdsaRoot = ECDsa.Create()) + + switch ((COSE.KeyType)param[0]) { - var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); - rootRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(true, true, 2, false)); - - var curve = (COSE.EllipticCurve)param[2]; - ECCurve eCCurve = ECCurve.NamedCurves.nistP256; - switch (curve) - { - case COSE.EllipticCurve.P384: - eCCurve = ECCurve.NamedCurves.nistP384; - break; - case COSE.EllipticCurve.P521: - eCCurve = ECCurve.NamedCurves.nistP521; - break; - } - - using (root = rootRequest.CreateSelfSigned( - notBefore, - notAfter)) - - using (var ecdsaAtt = ECDsa.Create(eCCurve)) - { - var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); - attRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(false, false, 0, false)); - - attRequest.CertificateExtensions.Add( - new X509Extension( - oidIdFidoGenCeAaguid, - asnEncodedAaguid, - false) - ); - - attRequest.CertificateExtensions.Add( - new X509Extension( - "2.5.29.17", - asnEncodedSAN, - false) - ); - - attRequest.CertificateExtensions.Add( - new X509EnhancedKeyUsageExtension( - new OidCollection - { + case COSE.KeyType.EC2: + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(true, true, 2, false)); + + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + + var curve = (COSE.EllipticCurve)param[2]; + switch (curve) + { + case COSE.EllipticCurve.P384: + eCCurve = ECCurve.NamedCurves.nistP384; + break; + case COSE.EllipticCurve.P521: + eCCurve = ECCurve.NamedCurves.nistP521; + break; + } + + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + attRequest.CertificateExtensions.Add( + new X509Extension( + oidIdFidoGenCeAaguid, + asnEncodedAaguid, + false) + ); + + attRequest.CertificateExtensions.Add( + new X509Extension( + "2.5.29.17", + asnEncodedSAN, + false) + ); + + attRequest.CertificateExtensions.Add( + new X509EnhancedKeyUsageExtension( + new OidCollection + { new Oid("2.23.133.8.3") - }, - false) - ); + }, + false) + ); - byte[] serial = new byte[12]; + byte[] serial = new byte[12]; - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(serial); + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c).Result; + } } - using (X509Certificate2 publicOnly = attRequest.Create( - root, - notBefore, - notAfter, - serial)) + break; + case COSE.KeyType.RSA: + using (RSA rsaRoot = RSA.Create()) { - attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); - } + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(true, true, 2, false)); + + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + attRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + attRequest.CertificateExtensions.Add( + new X509Extension( + oidIdFidoGenCeAaguid, + asnEncodedAaguid, + false) + ); + + attRequest.CertificateExtensions.Add( + new X509Extension( + "2.5.29.17", + asnEncodedSAN, + false) + ); + + attRequest.CertificateExtensions.Add( + new X509EnhancedKeyUsageExtension( + new OidCollection + { + new Oid("2.23.133.8.3") + }, + false) + ); + + byte[] serial = new byte[12]; - var X5c = CBORObject.NewArray() - .Add(CBORObject.FromObject(attestnCert.RawData)) - .Add(CBORObject.FromObject(root.RawData)); + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; + } + } - res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c).Result; - } - } + break; + } _attestationObject = CBORObject.NewMap().Add("fmt", "tpm"); }); } diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index 9be12f51..430950c5 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -388,6 +388,22 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak kdf = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_NULL); } + var type = new byte[2]; + switch (kty) + { + case COSE.KeyType.EC2: + { + type = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_ECC).Reverse().ToArray(); + break; + } + case COSE.KeyType.RSA: + { + type = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + break; + } + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); + } + var tpmAlg = new byte[2]; if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); @@ -399,7 +415,7 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); var pubArea = CreatePubArea( - new byte[] { 0x00, 0x23 }, // Type + type, // Type tpmAlg, // Alg new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes new byte[] { 0x00 }, // Policy @@ -919,7 +935,7 @@ public void TestAssertionResponse() .Concat(symmetric) .Concat(scheme) .Concat(keyBits) - .Concat(exponent) + .Concat(BitConverter.GetBytes(exponent[0] + (exponent[1] << 8) + (exponent[2] << 16))) .Concat(BitConverter.GetBytes((UInt16)unique.Length) .Reverse() .ToArray()) From 47cbffe91368a833d048fa0c56a9b828464f509b Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Sat, 9 May 2020 14:33:18 -0400 Subject: [PATCH 07/13] Added a bunch of packed and TPM tests, added a little bit more error handling. --- Src/Fido2/AttestationFormat/Packed.cs | 11 +- Src/Fido2/AttestationFormat/Tpm.cs | 17 +- Test/Attestation/FidoU2f.cs | 3 +- Test/Attestation/None.cs | 1 + Test/Attestation/Packed.cs | 835 ++++- Test/Attestation/Tpm.cs | 4325 ++++++++++++++++++++++++- Test/Fido2Tests.cs | 275 +- 7 files changed, 5157 insertions(+), 310 deletions(-) diff --git a/Src/Fido2/AttestationFormat/Packed.cs b/Src/Fido2/AttestationFormat/Packed.cs index 208797e5..a04128f5 100644 --- a/Src/Fido2/AttestationFormat/Packed.cs +++ b/Src/Fido2/AttestationFormat/Packed.cs @@ -37,10 +37,11 @@ public static bool IsValidPackedAttnCertSubject(string attnCertSubj) var dictSubject = attnCertSubj.Split(new string[] { ", " }, StringSplitOptions.None) .Select(part => part.Split('=')) .ToDictionary(split => split[0], split => split[1]); - return (0 != dictSubject["C"].Length || - 0 != dictSubject["O"].Length || - 0 != dictSubject["OU"].Length || - 0 != dictSubject["CN"].Length || + + return (0 != dictSubject["C"].Length && + 0 != dictSubject["O"].Length && + 0 != dictSubject["OU"].Length && + 0 != dictSubject["CN"].Length && "Authenticator Attestation" == dictSubject["OU"].ToString()); } @@ -106,7 +107,7 @@ public override void Verify() if (aaguid != null) { if (0 != AttestedCredentialData.FromBigEndian(aaguid).CompareTo(AuthData.AttestedCredentialData.AaGuid)) - throw new Fido2VerificationException("aaguid present in packed attestation but does not match aaguid from authData"); + throw new Fido2VerificationException("aaguid present in packed attestation cert exts but does not match aaguid from authData"); } // 2d. The Basic Constraints extension MUST have the CA component set to false if (IsAttnCertCACert(attestnCert.Extensions)) diff --git a/Src/Fido2/AttestationFormat/Tpm.cs b/Src/Fido2/AttestationFormat/Tpm.cs index 9a7be3af..4eb76a67 100644 --- a/Src/Fido2/AttestationFormat/Tpm.cs +++ b/Src/Fido2/AttestationFormat/Tpm.cs @@ -590,7 +590,7 @@ public override void Verify() var aaguid = AaguidFromAttnCertExts(aikCert.Extensions); if ((null != aaguid) && (!aaguid.SequenceEqual(Guid.Empty.ToByteArray())) && - (0 != new Guid(aaguid).CompareTo(AuthData.AttestedCredentialData.AaGuid))) + (0 != AttestedCredentialData.FromBigEndian(aaguid).CompareTo(AuthData.AttestedCredentialData.AaGuid))) throw new Fido2VerificationException(string.Format("aaguid malformed, expected {0}, got {1}", AuthData.AttestedCredentialData.AaGuid, new Guid(aaguid))); } // If ecdaaKeyId is present, then the attestation type is ECDAA @@ -738,9 +738,18 @@ public static (ushort size, byte[] name) NameFromTPM2BName(Memory ab, ref { name = AuthDataHelper.GetSizedByteArray(ab, ref offset, tpmAlgToDigestSizeMap[tpmalg]); } + else + { + throw new Fido2VerificationException("TPM_ALG_ID found in TPM2B_NAME not acceptable hash algorithm"); + } } + else + { + throw new Fido2VerificationException("Invalid TPM_ALG_ID found in TPM2B_NAME"); + } + if (totalSize != bytes.Length + name.Length) - throw new Fido2VerificationException("Unexpected no name found in TPM2B_NAME"); + throw new Fido2VerificationException("Unexpected extra bytes found in TPM2B_NAME"); return (size, name); } @@ -752,10 +761,10 @@ public CertInfo(byte[] certInfo) var offset = 0; Magic = AuthDataHelper.GetSizedByteArray(certInfo, ref offset, 4); if (0xff544347 != BitConverter.ToUInt32(Magic.ToArray().Reverse().ToArray(), 0)) - throw new Fido2VerificationException("Bad magic number " + Magic.ToString()); + throw new Fido2VerificationException("Bad magic number " + BitConverter.ToString(Magic).Replace("-","")); Type = AuthDataHelper.GetSizedByteArray(certInfo, ref offset, 2); if (0x8017 != BitConverter.ToUInt16(Type.ToArray().Reverse().ToArray(), 0)) - throw new Fido2VerificationException("Bad structure tag " + Type.ToString()); + throw new Fido2VerificationException("Bad structure tag " + BitConverter.ToString(Type).Replace("-", "")); QualifiedSigner = AuthDataHelper.GetSizedByteArray(certInfo, ref offset); ExtraData = AuthDataHelper.GetSizedByteArray(certInfo, ref offset); if (null == ExtraData || 0 == ExtraData.Length) diff --git a/Test/Attestation/FidoU2f.cs b/Test/Attestation/FidoU2f.cs index 6b66cfb2..5031ed15 100644 --- a/Test/Attestation/FidoU2f.cs +++ b/Test/Attestation/FidoU2f.cs @@ -21,8 +21,7 @@ public FidoU2f() { var attRequest = new CertificateRequest("CN=U2FTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); - attRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(false, false, 0, false)); + attRequest.CertificateExtensions.Add(notCAExt); using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) { diff --git a/Test/Attestation/None.cs b/Test/Attestation/None.cs index f44dcd0b..7d051603 100644 --- a/Test/Attestation/None.cs +++ b/Test/Attestation/None.cs @@ -36,6 +36,7 @@ public void TestNone() public void TestNoneWithAttStmt() { _attestationObject.Add("attStmt", CBORObject.NewMap().Add("foo", "bar")); + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(Fido2Tests._validCOSEParameters[0]); var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256)); Assert.Equal("Attestation format none should have no attestation statement", ex.Result.Message); } diff --git a/Test/Attestation/Packed.cs b/Test/Attestation/Packed.cs index 9f217581..19bce845 100644 --- a/Test/Attestation/Packed.cs +++ b/Test/Attestation/Packed.cs @@ -17,11 +17,14 @@ public Packed() _attestationObject = CBORObject.NewMap().Add("fmt", "packed"); } [Fact] - public void Self() + public void TestSelf() { Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) { (Fido2.CredentialMakeResult, AssertionVerificationResult) res; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + if (param.Length == 3) { res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; @@ -40,17 +43,90 @@ public void Self() }); } [Fact] - public void Full() + public void TestSelfAlgMismatch() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", COSE.Algorithm.ES384)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + Assert.Equal("Algorithm mismatch between credential public key and authenticator data in self attestation statement", ex.Result.Message); + } + [Fact] + public void TestSelfBadSig() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + _attestationObject["attStmt"].Add("sig", new byte[] { 0x30, 0x45, 0x02, 0x20, 0x11, 0x9b, 0x6f, 0xa8, 0x1c, 0xe1, 0x75, 0x9e, 0xbe, 0xf1, 0x52, 0xa6, 0x99, 0x40, 0x5e, 0xd6, 0x6a, 0xcc, 0x01, 0x33, 0x65, 0x18, 0x05, 0x00, 0x96, 0x28, 0x29, 0xbe, 0x85, 0x57, 0xb7, 0x1d, 0x02, 0x21, 0x00, 0x94, 0x50, 0x1d, 0xf1, 0x90, 0x03, 0xa4, 0x4d, 0xa4, 0xdf, 0x9f, 0xbb, 0xb5, 0xe4, 0xce, 0x91, 0x6b, 0xc3, 0x90, 0xe8, 0x38, 0x99, 0x66, 0x4f, 0xa5, 0xc4, 0x0c, 0xf3, 0xed, 0xe3, 0xda, 0x83 }); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + Assert.Equal("Failed to validate signature", ex.Result.Message); + } + + [Fact] + public void TestMissingAlg() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap()); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + Assert.Equal("Invalid packed attestation algorithm", ex.Result.Message); + } + + [Fact] + public void TestAlgNaN() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", "invalid alg")); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + Assert.Equal("Invalid packed attestation algorithm", ex.Result.Message); + } + + [Fact] + public void TestSigNull() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + _attestationObject["attStmt"].Set("sig", null); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + Assert.Equal("Invalid packed attestation signature", ex.Result.Message); + } + + [Fact] + public void TestSigNotByteString() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + _attestationObject["attStmt"].Set("sig", "walrus"); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + Assert.Equal("Invalid packed attestation signature", ex.Result.Message); + } + + [Fact] + public void TestSigByteStringZeroLen() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(new byte[0])); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + Assert.Equal("Invalid packed attestation signature", ex.Result.Message); + } + + [Fact] + public void TestFull() { Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) { + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); - var rootDN = new X500DistinguishedName("CN=Testing, O=FIDO2-NET-LIB, C=US"); var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); - var oidIdFidoGenCeAaguid = new Oid("1.3.6.1.4.1.45724.1.1.4"); - var asnEncodedAaguid = new byte[] { 0x04, 0x10, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); switch ((COSE.KeyType)param[0]) @@ -59,8 +135,7 @@ public void Full() using (var ecdsaRoot = ECDsa.Create()) { var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); - rootRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(true, true, 2, false)); + rootRequest.CertificateExtensions.Add(caExt); var curve = (COSE.EllipticCurve)param[2]; ECCurve eCCurve = ECCurve.NamedCurves.nistP256; @@ -81,15 +156,9 @@ public void Full() using (var ecdsaAtt = ECDsa.Create(eCCurve)) { var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); - attRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(false, false, 0, false)); + attRequest.CertificateExtensions.Add(notCAExt); - attRequest.CertificateExtensions.Add( - new X509Extension( - oidIdFidoGenCeAaguid, - asnEncodedAaguid, - false) - ); + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); byte[] serial = new byte[12]; @@ -128,8 +197,7 @@ public void Full() break; } var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); - rootRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(true, true, 2, false)); + rootRequest.CertificateExtensions.Add(caExt); using (root = rootRequest.CreateSelfSigned( notBefore, @@ -139,15 +207,9 @@ public void Full() { var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); - attRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(false, false, 0, false)); + attRequest.CertificateExtensions.Add(notCAExt); - attRequest.CertificateExtensions.Add( - new X509Extension( - oidIdFidoGenCeAaguid, - asnEncodedAaguid, - false) - ); + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); byte[] serial = new byte[12]; @@ -168,10 +230,7 @@ public void Full() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var attestationObject = CBORObject.NewMap() - .Add("fmt", "packed"); - - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; + res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; } } break; @@ -179,7 +238,6 @@ public void Full() { var avr = new AssertionVerificationResult() { - Counter = 0xf1d1, CredentialId = new byte[] { 0xf1, 0xd0 }, ErrorMessage = string.Empty, Status = "ok", @@ -192,9 +250,724 @@ public void Full() Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); Assert.True(new[] { "ok", res.Item2.Status }.All(x => x == "ok")); Assert.True(new[] { "", res.Item2.ErrorMessage }.All(x => x == "")); - Assert.True(0xf1d1 == res.Item2.Counter); + //Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); _attestationObject = CBORObject.NewMap().Add("fmt", "packed"); }); } + + [Fact] + public void TestFullMissingX5c() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + _attestationObject["attStmt"].Set("x5c", null); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt)); + Assert.Equal("Malformed x5c array in packed attestation statement", ex.Result.Message); + } + } + } + + [Fact] + public void TestFullX5cNotArray() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + _attestationObject["attStmt"].Set("x5c", "boomerang"); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt)); + Assert.Equal("Malformed x5c array in packed attestation statement", ex.Result.Message); + } + } + } + + [Fact] + public void TestFullX5cCountNotOne() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + _attestationObject["attStmt"] + .Set("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0])).Add(CBORObject.FromObject(new byte[0]))); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt)); + Assert.Equal("Malformed x5c cert found in packed attestation statement", ex.Result.Message); + } + } + } + + [Fact] + public void TestFullX5cValueNotByteString() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + _attestationObject["attStmt"].Set("x5c", "x".ToArray()); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt)); + Assert.Equal("Malformed x5c cert found in packed attestation statement", ex.Result.Message); + } + } + } + + [Fact] + public void TestFullX5cValueZeroLengthByteString() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + _attestationObject["attStmt"].Set("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0]))); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt)); + Assert.Equal("Malformed x5c cert found in packed attestation statement", ex.Result.Message); + } + } + } + + [Fact] + public void TestFullX5cCertExpired() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-7); + DateTimeOffset notAfter = notBefore.AddDays(2); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + Assert.Equal("Packed signing certificate expired or not yet valid", ex.Result.Message); + } + } + } + + [Fact] + public void TestFullX5cCertNotYetValid() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(1); + DateTimeOffset notAfter = notBefore.AddDays(7); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + Assert.Equal("Packed signing certificate expired or not yet valid", ex.Result.Message); + } + } + } + + [Fact] + public void TestFullInvalidAlg() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", 42)); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + Assert.Equal("Invalid attestation algorithm", ex.Result.Message); + } + } + } + + [Fact] + public void TestFullInvalidSig() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + _attestationObject["attStmt"].Add("sig", new byte[] { 0x30, 0x45, 0x02, 0x20, 0x11, 0x9b, 0x6f, 0xa8, 0x1c, 0xe1, 0x75, 0x9e, 0xbe, 0xf1, 0x52, 0xa6, 0x99, 0x40, 0x5e, 0xd6, 0x6a, 0xcc, 0x01, 0x33, 0x65, 0x18, 0x05, 0x00, 0x96, 0x28, 0x29, 0xbe, 0x85, 0x57, 0xb7, 0x1d, 0x02, 0x21, 0x00, 0x94, 0x50, 0x1d, 0xf1, 0x90, 0x03, 0xa4, 0x4d, 0xa4, 0xdf, 0x9f, 0xbb, 0xb5, 0xe4, 0xce, 0x91, 0x6b, 0xc3, 0x90, 0xe8, 0x38, 0x99, 0x66, 0x4f, 0xa5, 0xc4, 0x0c, 0xf3, 0xed, 0xe3, 0xda, 0x83 }); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + Assert.Equal("Invalid full packed signature", ex.Result.Message); + } + } + } + + [Fact] + public void TestFullAttCertNotV3() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var rawAttestnCert = attestnCert.RawData; + rawAttestnCert[12] = 0x41; + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(rawAttestnCert)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + Assert.Equal("Packed x5c attestation certificate not V3", ex.Result.Message); + } + } + } + + [Fact] + public void TestFullAttCertSubject() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var attDN = new X500DistinguishedName("CN=Testing, OU=Not Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + Assert.Equal("Invalid attestation cert subject", ex.Result.Message); + } + } + } + + [Fact] + public void TestFullAttCertAaguidNotMatchAuthdata() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(notCAExt); + + var notAsnEncodedAaguid = asnEncodedAaguid; + notAsnEncodedAaguid[3] = 0x42; + var notIdFidoGenCeAaguidExt = new X509Extension(oidIdFidoGenCeAaguid, asnEncodedAaguid, false); + attRequest.CertificateExtensions.Add(notIdFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + Assert.Equal("aaguid present in packed attestation cert exts but does not match aaguid from authData", ex.Result.Message); + } + } + } + + [Fact] + public void TestFullAttCertCAFlagSet() + { + var param = Fido2Tests._validCOSEParameters[0]; + _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); + _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + X509Certificate2 root, attestnCert; + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddDays(2); + var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + var curve = (COSE.EllipticCurve)param[2]; + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + using (root = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + attRequest.CertificateExtensions.Add(caExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + root, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(root.RawData)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + Assert.Equal("Attestion certificate has CA cert flag present", ex.Result.Message); + } + } + } } } diff --git a/Test/Attestation/Tpm.cs b/Test/Attestation/Tpm.cs index c2b0fa56..0fb0e405 100644 --- a/Test/Attestation/Tpm.cs +++ b/Test/Attestation/Tpm.cs @@ -1,46 +1,79 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Text; using fido2_net_lib.Test; using Fido2NetLib; using Fido2NetLib.Objects; using PeterO.Cbor; using Xunit; +using Fido2NetLib.AttestationFormat; namespace Test.Attestation { public class Tpm : Fido2Tests.Attestation { + private X500DistinguishedName attDN = new X500DistinguishedName(""); + private byte[] asnEncodedSAN = new byte[] { 0x30, 0x53, 0xA4, 0x51, 0x30, 0x4F, 0x31, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x01, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x46, 0x46, 0x46, 0x46, 0x31, 0x44, 0x30, 0x30, 0x1F, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x02, 0x0C, 0x16, 0x46, 0x49, 0x44, 0x4F, 0x32, 0x2D, 0x4E, 0x45, 0x54, 0x2D, 0x4C, 0x49, 0x42, 0x2D, 0x54, 0x45, 0x53, 0x54, 0x2D, 0x54, 0x50, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x03, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x31, 0x44, 0x30, 0x30, 0x30, 0x30, 0x32 }; + private X509Certificate2 rootCert, attestnCert; + private DateTimeOffset notBefore, notAfter; + private X509EnhancedKeyUsageExtension tcgKpAIKCertExt; + private X509Extension aikCertSanExt; + private IEnumerable unique, exponent, curveId, kdf; + private byte[] type, tpmAlg; + public Tpm() { _attestationObject = CBORObject.NewMap().Add("fmt", "tpm"); + unique = null; + exponent = null; + curveId = null; + kdf = null; + type = new byte[2]; + tpmAlg = new byte[2]; + + notBefore = DateTimeOffset.UtcNow; + notAfter = notBefore.AddDays(2); + caExt = new X509BasicConstraintsExtension(true, true, 2, false); + notCAExt = new X509BasicConstraintsExtension(false, false, 0, false); + tcgKpAIKCertExt = new X509EnhancedKeyUsageExtension( + new OidCollection + { + new Oid("2.23.133.8.3") + }, + false); + + aikCertSanExt = new X509Extension( + "2.5.29.17", + asnEncodedSAN, + false); } [Fact] - public void EC2() + public void TestTPM() { Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) { - X509Certificate2 root, attestnCert; - DateTimeOffset notBefore = DateTimeOffset.UtcNow; - DateTimeOffset notAfter = notBefore.AddDays(2); - var rootDN = new X500DistinguishedName("CN=Testing, O=FIDO2-NET-LIB, C=US"); - var attDN = new X500DistinguishedName(""); - var oidIdFidoGenCeAaguid = new Oid("1.3.6.1.4.1.45724.1.1.4"); - var asnEncodedSAN = new byte[] { 0x30, 0x53, 0xA4, 0x51, 0x30, 0x4F, 0x31, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x01, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x46, 0x46, 0x46, 0x46, 0x31, 0x44, 0x30, 0x30, 0x1F, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x02, 0x0C, 0x16, 0x46, 0x49, 0x44, 0x4F, 0x32, 0x2D, 0x4E, 0x45, 0x54, 0x2D, 0x4C, 0x49, 0x42, 0x2D, 0x54, 0x45, 0x53, 0x54, 0x2D, 0x54, 0x50, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x03, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x31, 0x44, 0x30, 0x30, 0x30, 0x30, 0x32 }; - var asnEncodedAaguid = new byte[] { 0x04, 0x10, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + switch ((COSE.KeyType)param[0]) { case COSE.KeyType.EC2: using (var ecdsaRoot = ECDsa.Create()) { var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); - rootRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(true, true, 2, false)); + rootRequest.CertificateExtensions.Add(caExt); ECCurve eCCurve = ECCurve.NamedCurves.nistP256; @@ -55,38 +88,21 @@ public void EC2() break; } - using (root = rootRequest.CreateSelfSigned( + using (rootCert = rootRequest.CreateSelfSigned( notBefore, notAfter)) using (var ecdsaAtt = ECDsa.Create(eCCurve)) { var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); - attRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(false, false, 0, false)); - - attRequest.CertificateExtensions.Add( - new X509Extension( - oidIdFidoGenCeAaguid, - asnEncodedAaguid, - false) - ); - attRequest.CertificateExtensions.Add( - new X509Extension( - "2.5.29.17", - asnEncodedSAN, - false) - ); + attRequest.CertificateExtensions.Add(notCAExt); - attRequest.CertificateExtensions.Add( - new X509EnhancedKeyUsageExtension( - new OidCollection - { - new Oid("2.23.133.8.3") - }, - false) - ); + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); byte[] serial = new byte[12]; @@ -95,7 +111,7 @@ public void EC2() rng.GetBytes(serial); } using (X509Certificate2 publicOnly = attRequest.Create( - root, + rootCert, notBefore, notAfter, serial)) @@ -105,7 +121,114 @@ public void EC2() var X5c = CBORObject.NewArray() .Add(CBORObject.FromObject(attestnCert.RawData)) - .Add(CBORObject.FromObject(root.RawData)); + .Add(CBORObject.FromObject(rootCert.RawData)); + + var ecparams = ecdsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.X, ecparams.Q.X); + cpk.Add(COSE.KeyTypeParameter.Y, ecparams.Q.Y); + cpk.Add(COSE.KeyTypeParameter.Crv, (COSE.EllipticCurve)param[2]); + + var x = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); + var y = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = BitConverter + .GetBytes((UInt16)x.Length) + .Reverse() + .ToArray() + .Concat(x) + .Concat(BitConverter.GetBytes((UInt16)y.Length) + .Reverse() + .ToArray()) + .Concat(y); + + var CoseCurveToTpm = new Dictionary + { + { 1, TpmEccCurve.TPM_ECC_NIST_P256}, + { 2, TpmEccCurve.TPM_ECC_NIST_P384}, + { 3, TpmEccCurve.TPM_ECC_NIST_P521}, + }; + + curveId = BitConverter.GetBytes((ushort)CoseCurveToTpm[cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Crv)].AsInt32()]).Reverse().ToArray(); + kdf = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_NULL); + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_ECC).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c).Result; } @@ -125,41 +248,23 @@ public void EC2() break; } var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); - rootRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(true, true, 2, false)); + rootRequest.CertificateExtensions.Add(caExt); - using (root = rootRequest.CreateSelfSigned( + using (rootCert = rootRequest.CreateSelfSigned( notBefore, notAfter)) using (var rsaAtt = RSA.Create()) { var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); - attRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension(false, false, 0, false)); - - attRequest.CertificateExtensions.Add( - new X509Extension( - oidIdFidoGenCeAaguid, - asnEncodedAaguid, - false) - ); + + attRequest.CertificateExtensions.Add(notCAExt); - attRequest.CertificateExtensions.Add( - new X509Extension( - "2.5.29.17", - asnEncodedSAN, - false) - ); + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); - attRequest.CertificateExtensions.Add( - new X509EnhancedKeyUsageExtension( - new OidCollection - { - new Oid("2.23.133.8.3") - }, - false) - ); + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); byte[] serial = new byte[12]; @@ -168,7 +273,7 @@ public void EC2() rng.GetBytes(serial); } using (X509Certificate2 publicOnly = attRequest.Create( - root, + rootCert, notBefore, notAfter, serial)) @@ -178,7 +283,93 @@ public void EC2() var X5c = CBORObject.NewArray() .Add(CBORObject.FromObject(attestnCert.RawData)) - .Add(CBORObject.FromObject(root.RawData)); + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; } @@ -189,5 +380,4007 @@ public void EC2() _attestationObject = CBORObject.NewMap().Add("fmt", "tpm"); }); } + + [Fact] + public void TestTPMSigNull() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", null) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Invalid TPM attestation signature", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMSigNotByteString() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", "strawberries") + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Invalid TPM attestation signature", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMSigByteStringZeroLen() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", CBORObject.FromObject(new byte[0])) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Invalid TPM attestation signature", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMVersionNot2() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "3.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("FIDO2 only supports TPM 2.0", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMPubAreaNull() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", null)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Missing or malformed pubArea", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMPubAreaNotByteString() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", "banana")) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Missing or malformed pubArea", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMPubAreaByteStringZeroLen() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", CBORObject.FromObject(new byte[0]))) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Missing or malformed pubArea", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMPubAreaUniqueNull() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + var tpmalg = (TpmAlg)Enum.Parse(typeof(TpmAlg), BitConverter.ToUInt16(type.Reverse().ToArray(), 0).ToString()); + var policy = new byte[] { 0x00 }; + var pubArea + = type + .Concat(tpmAlg) + .Concat(new byte[] { 0x00, 0x00, 0x00, 0x00 }) + .Concat(BitConverter.GetBytes((UInt16)policy.Length) + .Reverse() + .ToArray()) + .Concat(policy) + .Concat(new byte[] { 0x00, 0x10 }) + .Concat(new byte[] { 0x00, 0x10 }) + .Concat(new byte[] { 0x80, 0x00 }) + .Concat(BitConverter.GetBytes(exponent.ToArray()[0] + (exponent.ToArray()[1] << 8) + (exponent.ToArray()[2] << 16))); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea.ToArray()); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Missing or malformed pubArea", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMPubAreaUniqueByteStringZeroLen() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + new byte[0] // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Missing or malformed pubArea", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMPubAreaUniquePublicKeyMismatch() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.Reverse().ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Public key mismatch between pubArea and credentialPublicKey", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMPubAreaUniqueExponentMismatch() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + new byte[] { 0x00, 0x01, 0x00 } , // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Public key exponent mismatch between pubArea and credentialPublicKey", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMPubAreaUniqueXValueMismatch() + { + var param = Fido2Tests._validCOSEParameters[0]; + var alg = (COSE.Algorithm)param[1]; + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + + var curve = (COSE.EllipticCurve)param[2]; + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + + var ecparams = ecdsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.X, ecparams.Q.X); + cpk.Add(COSE.KeyTypeParameter.Y, ecparams.Q.Y); + cpk.Add(COSE.KeyTypeParameter.Crv, (COSE.EllipticCurve)param[2]); + + var x = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString().Reverse().ToArray(); + var y = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = BitConverter + .GetBytes((UInt16)x.Length) + .Reverse() + .ToArray() + .Concat(x) + .Concat(BitConverter.GetBytes((UInt16)y.Length) + .Reverse() + .ToArray()) + .Concat(y); + + var CoseCurveToTpm = new Dictionary + { + { 1, TpmEccCurve.TPM_ECC_NIST_P256}, + { 2, TpmEccCurve.TPM_ECC_NIST_P384}, + { 3, TpmEccCurve.TPM_ECC_NIST_P521}, + }; + + curveId = BitConverter.GetBytes((ushort)CoseCurveToTpm[cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Crv)].AsInt32()]).Reverse().ToArray(); + kdf = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_NULL); + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_ECC).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], ecdsa: ecdsaAtt, X5c: X5c)); + Assert.Equal("X-coordinate mismatch between pubArea and credentialPublicKey", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMPubAreaUniqueYValueMismatch() + { + var param = Fido2Tests._validCOSEParameters[0]; + var alg = (COSE.Algorithm)param[1]; + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + + var curve = (COSE.EllipticCurve)param[2]; + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + + var ecparams = ecdsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.X, ecparams.Q.X); + cpk.Add(COSE.KeyTypeParameter.Y, ecparams.Q.Y); + cpk.Add(COSE.KeyTypeParameter.Crv, (COSE.EllipticCurve)param[2]); + + var x = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); + var y = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString().Reverse().ToArray(); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = BitConverter + .GetBytes((UInt16)x.Length) + .Reverse() + .ToArray() + .Concat(x) + .Concat(BitConverter.GetBytes((UInt16)y.Length) + .Reverse() + .ToArray()) + .Concat(y); + + var CoseCurveToTpm = new Dictionary + { + { 1, TpmEccCurve.TPM_ECC_NIST_P256}, + { 2, TpmEccCurve.TPM_ECC_NIST_P384}, + { 3, TpmEccCurve.TPM_ECC_NIST_P521}, + }; + + curveId = BitConverter.GetBytes((ushort)CoseCurveToTpm[cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Crv)].AsInt32()]).Reverse().ToArray(); + kdf = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_NULL); + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_ECC).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], ecdsa: ecdsaAtt, X5c: X5c)); + Assert.Equal("Y-coordinate mismatch between pubArea and credentialPublicKey", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMPubAreaUniqueCurveMismatch() + { + var param = Fido2Tests._validCOSEParameters[0]; + var alg = (COSE.Algorithm)param[1]; + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + + using (var ecdsaRoot = ECDsa.Create()) + { + var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256); + rootRequest.CertificateExtensions.Add(caExt); + + ECCurve eCCurve = ECCurve.NamedCurves.nistP256; + + var curve = (COSE.EllipticCurve)param[2]; + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var ecdsaAtt = ECDsa.Create(eCCurve)) + { + var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + + var ecparams = ecdsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.X, ecparams.Q.X); + cpk.Add(COSE.KeyTypeParameter.Y, ecparams.Q.Y); + cpk.Add(COSE.KeyTypeParameter.Crv, (COSE.EllipticCurve)param[2]); + + var x = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); + var y = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = BitConverter + .GetBytes((UInt16)x.Length) + .Reverse() + .ToArray() + .Concat(x) + .Concat(BitConverter.GetBytes((UInt16)y.Length) + .Reverse() + .ToArray()) + .Concat(y); + + var CoseCurveToTpm = new Dictionary + { + { 1, TpmEccCurve.TPM_ECC_NIST_P256}, + { 2, TpmEccCurve.TPM_ECC_NIST_P384}, + { 3, TpmEccCurve.TPM_ECC_NIST_P521}, + }; + + curveId = BitConverter.GetBytes((ushort)CoseCurveToTpm[2]).Reverse().ToArray(); + kdf = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_NULL); + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_ECC).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], ecdsa: ecdsaAtt, X5c: X5c)); + Assert.Equal("Curve mismatch between pubArea and credentialPublicKey", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMCertInfoNull() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", null) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("CertInfo invalid parsing TPM format attStmt", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMCertInfoNotByteString() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", "tomato") + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("CertInfo invalid parsing TPM format attStmt", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMCertInfoByteStringZeroLen() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", CBORObject.FromObject(new byte[0])) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("CertInfo invalid parsing TPM format attStmt", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMCertInfoBadMagic() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }, // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Bad magic number 474354FF", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMCertInfoBadType() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }, // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Bad structure tag 1780", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMCertInfoExtraDataZeroLen() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)0) + .Reverse() + .ToArray(); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + new byte[0], // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Bad extraData in certInfo", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMCertInfoTPM2BNameIsHandle() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(new byte[] { 0x00, 0x04 }) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Unexpected handle in TPM2B_NAME", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMCertInfoTPM2BNoName() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(new byte[] { 0x00, 0x00 }) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Unexpected no name found in TPM2B_NAME", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMCertInfoTPM2BExtraBytes() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length + 1)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea) + .Concat(new byte[] { 0x00 }); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Unexpected extra bytes found in TPM2B_NAME", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMCertInfoTPM2BInvalidHashAlg() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(new byte[] { 0x00, 0x10 }) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("TPM_ALG_ID found in TPM2B_NAME not acceptable hash algorithm", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMCertInfoTPM2BInvalidTPMALGID() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(new byte[] { 0xff, 0xff }) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Invalid TPM_ALG_ID found in TPM2B_NAME", ex.Result.Message); + } + } + } + + internal static byte[] CreatePubArea(byte[] type, byte[] alg, byte[] attributes, byte[] policy, byte[] symmetric, + byte[] scheme, byte[] keyBits, byte[] exponent, byte[] curveID, byte[] kdf, byte[] unique) + { + var tpmalg = (TpmAlg)Enum.Parse(typeof(TpmAlg), BitConverter.ToUInt16(type.Reverse().ToArray(), 0).ToString()); + + IEnumerable raw = null; + var uniqueLen = BitConverter.GetBytes((UInt16)unique.Length).Reverse().ToArray(); + + if (TpmAlg.TPM_ALG_RSA == tpmalg) + { + raw + = type + .Concat(alg) + .Concat(attributes) + .Concat(BitConverter.GetBytes((UInt16)policy.Length) + .Reverse() + .ToArray()) + .Concat(policy) + .Concat(symmetric) + .Concat(scheme) + .Concat(keyBits) + .Concat(BitConverter.GetBytes(exponent[0] + (exponent[1] << 8) + (exponent[2] << 16))) + .Concat(BitConverter.GetBytes((UInt16)unique.Length) + .Reverse() + .ToArray()) + .Concat(unique); + } + if (TpmAlg.TPM_ALG_ECC == tpmalg) + { + raw = type + .Concat(alg) + .Concat(attributes) + .Concat(BitConverter.GetBytes((UInt16)policy.Length) + .Reverse() + .ToArray()) + .Concat(policy) + .Concat(symmetric) + .Concat(scheme) + .Concat(curveID) + .Concat(kdf) + .Concat(BitConverter.GetBytes((UInt16)unique.Length) + .Reverse() + .ToArray()) + .Concat(unique); + } + + return raw.ToArray(); + } + + internal static byte[] CreateCertInfo(byte[] magic, byte[] type, byte[] qualifiedSigner, + byte[] extraData, byte[] clock, byte[] resetCount, byte[] restartCount, + byte[] safe, byte[] firmwareRevision, byte[] tPM2BName, byte[] attestedQualifiedNameBuffer) + { + IEnumerable raw = magic + .Concat(type) + .Concat(qualifiedSigner) + .Concat(extraData) + .Concat(clock) + .Concat(resetCount) + .Concat(restartCount) + .Concat(safe) + .Concat(firmwareRevision) + .Concat(tPM2BName) + .Concat(attestedQualifiedNameBuffer); + + return raw.ToArray(); + } } } diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index 430950c5..af7d49f3 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -29,7 +29,6 @@ public class Fido2Tests static Fido2Tests() { var MDSAccessKey = Environment.GetEnvironmentVariable("fido2:MDSAccessKey"); - //var CacheDir = Environment.GetEnvironmentVariable("fido2:MDSCacheDirPath"); var services = new ServiceCollection(); @@ -96,6 +95,14 @@ public abstract class Attestation public CredentialPublicKey _credentialPublicKey; public const string rp = "fido2.azurewebsites.net"; public byte[] _challenge; + public X500DistinguishedName rootDN = new X500DistinguishedName("CN=Testing, O=FIDO2-NET-LIB, C=US"); + public Oid oidIdFidoGenCeAaguid = new Oid("1.3.6.1.4.1.45724.1.1.4"); + //private byte[] asnEncodedAaguid = new byte[] { 0x04, 0x10, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + public byte[] asnEncodedAaguid = new byte[] { 0x04, 0x10, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + public X509BasicConstraintsExtension caExt = new X509BasicConstraintsExtension(true, true, 2, false); + public X509BasicConstraintsExtension notCAExt = new X509BasicConstraintsExtension(false, false, 0, false); + public X509Extension idFidoGenCeAaguidExt; + public byte[] _rpIdHash { get @@ -169,6 +176,8 @@ public Attestation() _signCount = BitConverter.ToUInt16(signCount, 0); _attestationObject = CBORObject.NewMap(); + + idFidoGenCeAaguidExt = new X509Extension(oidIdFidoGenCeAaguid, asnEncodedAaguid, false); } public async Task MakeAttestationResponse() { @@ -237,12 +246,9 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak const string rp = "fido2.azurewebsites.net"; byte[] rpId = Encoding.UTF8.GetBytes(rp); var rpIdHash = SHA256.Create().ComputeHash(rpId); - var flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; - const ushort signCount = 0xf1d0; - var aaguid = ((attestationObject["fmt"].AsString().Equals("fido-u2f"))) ? Guid.Empty : new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); + var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; - CredentialPublicKey cpk = null; switch (kty) { case COSE.KeyType.EC2: @@ -252,7 +258,7 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak ecdsa = MakeECDsa(alg, crv); } var ecparams = ecdsa.ExportParameters(true); - cpk = MakeCredentialPublicKey(kty, alg, crv, ecparams.Q.X, ecparams.Q.Y); + _credentialPublicKey = MakeCredentialPublicKey(kty, alg, crv, ecparams.Q.X, ecparams.Q.Y); break; } case COSE.KeyType.RSA: @@ -262,7 +268,7 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak rsa = RSA.Create(); } var rsaparams = rsa.ExportParameters(true); - cpk = MakeCredentialPublicKey(kty, alg, rsaparams.Modulus, rsaparams.Exponent); + _credentialPublicKey = MakeCredentialPublicKey(kty, alg, rsaparams.Modulus, rsaparams.Exponent); break; } case COSE.KeyType.OKP: @@ -273,22 +279,14 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak MakeEdDSA(out var privateKeySeed, out publicKey, out expandedPrivateKey); } - cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); + _credentialPublicKey = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); break; } - throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); } - var acd = new AttestedCredentialData(aaguid, credentialID, cpk); - var extBytes = CBORObject.NewMap().Add("testing", true).EncodeToBytes(); - var exts = new Extensions(extBytes); - var ad = new AuthenticatorData(rpIdHash, flags, signCount, acd, exts); - var authData = ad.ToByteArray(); - - var challenge = new byte[128]; var rng = RandomNumberGenerator.Create(); - rng.GetBytes(challenge); var sha = SHA256.Create(); @@ -305,180 +303,35 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak var clientData = new { Type = "webauthn.create", - Challenge = challenge, + Challenge = _challenge, Origin = rp, }; - var clientDataJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(clientData)); - var clientDataHash = sha.ComputeHash(clientDataJson); + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); - byte[] data = new byte[authData.Length + clientDataHash.Length]; - Buffer.BlockCopy(authData, 0, data, 0, authData.Length); - Buffer.BlockCopy(clientDataHash, 0, data, authData.Length, clientDataHash.Length); - - attestationObject.Add("authData", authData); - if (attestationObject["fmt"].AsString().Equals("packed")) + if (!attestationObject.ContainsKey("authData")) { - byte[] signature = SignData(kty, alg, data, ecdsa, rsa, expandedPrivateKey); - - if (X5c == null) - { - attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", alg).Add("sig", signature)); - } - else - { - attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", alg).Add("sig", signature).Add("x5c", X5c)); - } + attestationObject.Add("authData", _authData); } - if (attestationObject["fmt"].AsString().Equals("fido-u2f")) - { - var x = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); - var y = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); - var publicKeyU2F = new byte[1] { 0x4 }.Concat(x).Concat(y).ToArray(); - - var verificationData = new byte[1] { 0x00 }; - verificationData = verificationData - .Concat(rpIdHash) - .Concat(clientDataHash) - .Concat(credentialID) - .Concat(publicKeyU2F.ToArray()) - .ToArray(); - - byte[] signature = SignData(kty, alg, verificationData, ecdsa, rsa, expandedPrivateKey); - - attestationObject.Add("attStmt", CBORObject.NewMap().Add("x5c", X5c).Add("sig", signature)); - } - - if (attestationObject["fmt"].AsString().Equals("tpm")) + if (attestationObject["fmt"].AsString().Equals("packed")) { - IEnumerable unique = null; - IEnumerable exponent = null; - IEnumerable curveId = null; - IEnumerable kdf = null; + byte[] signature = SignData(kty, alg, data, ecdsa, rsa, expandedPrivateKey); - if (kty == COSE.KeyType.RSA) - { - unique = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.N)].GetByteString(); - exponent = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.E)].GetByteString(); - } - if (kty == COSE.KeyType.EC2) + if (attestationObject.ContainsKey("attStmt")) { - var x = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString(); - var y = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString(); - var curve = cpk.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Crv)].AsInt32(); - unique = BitConverter - .GetBytes((UInt16)x.Length) - .Reverse() - .ToArray() - .Concat(x) - .Concat(BitConverter.GetBytes((UInt16)y.Length) - .Reverse() - .ToArray()) - .Concat(y); - - var CoseCurveToTpm = new Dictionary + if (!attestationObject["attStmt"].ContainsKey("sig")) { - { 1, TpmEccCurve.TPM_ECC_NIST_P256}, - { 2, TpmEccCurve.TPM_ECC_NIST_P384}, - { 3, TpmEccCurve.TPM_ECC_NIST_P521}, - }; - - curveId = BitConverter.GetBytes((ushort)CoseCurveToTpm[curve]).Reverse().ToArray(); - kdf = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_NULL); - } - - var type = new byte[2]; - switch (kty) - { - case COSE.KeyType.EC2: - { - type = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_ECC).Reverse().ToArray(); - break; - } - case COSE.KeyType.RSA: - { - type = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); - break; - } - throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); - } - - var tpmAlg = new byte[2]; - if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) - tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); - if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) - tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); - if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) - tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); - if (alg == COSE.Algorithm.RS1) - tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); - - var pubArea = CreatePubArea( - type, // Type - tpmAlg, // Alg - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes - new byte[] { 0x00 }, // Policy - new byte[] { 0x00, 0x10 }, // Symmetric - new byte[] { 0x00, 0x10 }, // Scheme - new byte[] { 0x80, 0x00 }, // KeyBits - exponent?.ToArray(), // Exponent - curveId?.ToArray(), // CurveID - kdf?.ToArray(), // KDF - unique.ToArray() // Unique - ); + attestationObject["attStmt"].Add("sig", signature); + } - byte[] hashedData; - byte[] hashedPubArea; - using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) - { - hashedData = hasher.ComputeHash(data); - hashedPubArea = hasher.ComputeHash(pubArea); + if (X5c != null) + { + attestationObject["attStmt"].Add("x5c", X5c); + } } - IEnumerable extraData = BitConverter - .GetBytes((UInt16)hashedData.Length) - .Reverse() - .ToArray() - .Concat(hashedData); - - var tpmAlgToDigestSizeMap = new Dictionary - { - {TpmAlg.TPM_ALG_SHA1, (160/8) }, - {TpmAlg.TPM_ALG_SHA256, (256/8) }, - {TpmAlg.TPM_ALG_SHA384, (384/8) }, - {TpmAlg.TPM_ALG_SHA512, (512/8) } - }; - - var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); - - IEnumerable tpm2bName = new byte[] { } - .Concat(tpm2bNameLen) - .Concat(tpmAlg) - .Concat(hashedPubArea); - - var certInfo = CreateCertInfo( - new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic - new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type - new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner - extraData.ToArray(), // ExtraData - new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount - new byte[] { 0x00 }, // Safe - new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion - tpm2bName.ToArray(), // TPM2BName - new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer - ); - - byte[] signature = SignData(kty, alg, certInfo, ecdsa, rsa, expandedPrivateKey); - - attestationObject.Add("attStmt", CBORObject.NewMap() - .Add("ver", "2.0") - .Add("alg", alg) - .Add("x5c", X5c) - .Add("sig", signature) - .Add("certInfo", certInfo) - .Add("pubArea", pubArea)); } var attestationResponse = new AuthenticatorAttestationRawResponse @@ -489,7 +342,7 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak Response = new AuthenticatorAttestationRawResponse.ResponseData() { AttestationObject = attestationObject.EncodeToBytes(), - ClientDataJson = clientDataJson, + ClientDataJson = _clientDataJson, } }; @@ -502,7 +355,7 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak RequireResidentKey = true, UserVerification = UserVerificationRequirement.Required, }, - Challenge = challenge, + Challenge = _challenge, ErrorMessage = "", PubKeyCredParams = new List() { @@ -530,38 +383,56 @@ public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> Mak var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, origChallenge, callback); - var assertionVerificationResult = await MakeAssertionResponse(kty, alg, crv, new CredentialPublicKey(credentialMakeResult.Result.PublicKey), (ushort)credentialMakeResult.Result.Counter, ecdsa, rsa, expandedPrivateKey); + var assertionVerificationResult = await MakeAssertionResponse(kty, alg, crv, _credentialPublicKey, (ushort)credentialMakeResult.Result.Counter, ecdsa, rsa, expandedPrivateKey); + //r assertionVerificationResult = await MakeAssertionResponse(kty, alg, crv, new CredentialPublicKey(credentialMakeResult.Result.PublicKey), (ushort)credentialMakeResult.Result.Counter, ecdsa, rsa, expandedPrivateKey); return (credentialMakeResult, assertionVerificationResult); - } - - public class TPM : Attestation + } + internal static byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, byte[] data, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null) { - [Fact] - public void TestAttestationTPM() + byte[] signature = null; + switch (kty) { - _validCOSEParameters.ForEach(delegate (object[] param) - { - var attestationObject = CBORObject.NewMap() - .Add("fmt", "tpm"); - - (Fido2.CredentialMakeResult, AssertionVerificationResult) res; - if (param.Length == 3) + case COSE.KeyType.EC2: + { + signature = ecdsa.SignData(data, CryptoUtils.algMap[(int)alg]); + signature = EcDsaSigFromSig(signature, ecdsa.KeySize); + break; + } + case COSE.KeyType.RSA: { - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + RSASignaturePadding padding; + switch (alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.PS256: + case COSE.Algorithm.PS384: + case COSE.Algorithm.PS512: + padding = RSASignaturePadding.Pss; + break; + + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + default: + throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}"); + } + signature = rsa.SignData(data, CryptoUtils.algMap[(int)alg], padding); + break; } - else + case COSE.KeyType.OKP: { - res = MakeAttestationResponse(attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + signature = Ed25519.Sign(data, expandedPrivateKey); + break; } - Assert.Equal("tpm", res.Item1.Result.CredType); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); - Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); - Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); - }); + + default: + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); } + return signature; } } From 19c626e01357eea16765bb74418cc8da850cc4b6 Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Mon, 11 May 2020 12:37:37 -0400 Subject: [PATCH 08/13] Remainder of TPM tests (except chain validation). --- Src/Fido2/AttestationFormat/Tpm.cs | 4 +- Test/Attestation/Tpm.cs | 4302 ++++++++++++++++++++++++++++ 2 files changed, 4304 insertions(+), 2 deletions(-) diff --git a/Src/Fido2/AttestationFormat/Tpm.cs b/Src/Fido2/AttestationFormat/Tpm.cs index 4eb76a67..60649fdd 100644 --- a/Src/Fido2/AttestationFormat/Tpm.cs +++ b/Src/Fido2/AttestationFormat/Tpm.cs @@ -470,7 +470,7 @@ public override void Verify() certInfo = new CertInfo(attStmt["certInfo"].GetByteString()); } - if (null == certInfo || null == certInfo.ExtraData || 0 == certInfo.ExtraData.Length) + if (null == certInfo) throw new Fido2VerificationException("CertInfo invalid parsing TPM format attStmt"); // Verify that magic is set to TPM_GENERATED_VALUE and type is set to TPM_ST_ATTEST_CERTIFY @@ -580,7 +580,7 @@ public override void Verify() // OID is 2.23.133.8.3 var EKU = EKUFromAttnCertExts(aikCert.Extensions, "2.23.133.8.3"); if (!EKU) - throw new Fido2VerificationException("Invalid EKU on AIK certificate"); + throw new Fido2VerificationException("aikCert EKU missing tcg-kp-AIKCertificate OID"); // The Basic Constraints extension MUST have the CA component set to false. if (IsAttnCertCACert(aikCert.Extensions)) diff --git a/Test/Attestation/Tpm.cs b/Test/Attestation/Tpm.cs index 0fb0e405..f08e7de8 100644 --- a/Test/Attestation/Tpm.cs +++ b/Test/Attestation/Tpm.cs @@ -231,6 +231,22 @@ public void TestTPM() .Add("authData", _authData); res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c).Result; + Assert.Equal(string.Empty, res.Item1.ErrorMessage); + Assert.Equal("ok", res.Item1.Status); + Assert.Equal(_aaguid, res.Item1.Result.Aaguid); + Assert.Equal(_signCount, res.Item1.Result.Counter); + Assert.Equal("tpm", res.Item1.Result.CredType); + Assert.Equal(_credentialID, res.Item1.Result.CredentialId); + Assert.Null(res.Item1.Result.ErrorMessage); + Assert.Equal(_credentialPublicKey.GetBytes(), res.Item1.Result.PublicKey); + Assert.Null(res.Item1.Result.Status); + Assert.Equal("Test User", res.Item1.Result.User.DisplayName); + Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Item1.Result.User.Id); + Assert.Equal("testuser", res.Item1.Result.User.Name); + Assert.Equal(_signCount, res.Item2.Counter - 1); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.Equal(string.Empty, res.Item2.ErrorMessage); + Assert.Equal("ok", res.Item2.Status); } } break; @@ -372,6 +388,22 @@ public void TestTPM() .Add("authData", _authData); res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; + Assert.Equal(string.Empty, res.Item1.ErrorMessage); + Assert.Equal("ok", res.Item1.Status); + Assert.Equal(_aaguid, res.Item1.Result.Aaguid); + Assert.Equal(_signCount, res.Item1.Result.Counter); + Assert.Equal("tpm", res.Item1.Result.CredType); + Assert.Equal(_credentialID, res.Item1.Result.CredentialId); + Assert.Null(res.Item1.Result.ErrorMessage); + Assert.Equal(_credentialPublicKey.GetBytes(), res.Item1.Result.PublicKey); + Assert.Null(res.Item1.Result.Status); + Assert.Equal("Test User", res.Item1.Result.User.DisplayName); + Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Item1.Result.User.Id); + Assert.Equal("testuser", res.Item1.Result.User.Name); + Assert.Equal(_signCount, res.Item2.Counter - 1); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.Equal(string.Empty, res.Item2.ErrorMessage); + Assert.Equal("ok", res.Item2.Status); } } @@ -4315,6 +4347,4276 @@ public void TestTPMCertInfoTPM2BInvalidTPMALGID() } } + + [Fact] + public void TestTPMAlgNull() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", null) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Invalid TPM attestation algorithm", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAlgNotNumber() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", "kiwi") + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Invalid TPM attestation algorithm", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAlgInvalid() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", 0) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Invalid TPM attestation algorithm", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAlgMismatch() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", COSE.Algorithm.RS1) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Hash value mismatch extraData and attToBeSigned", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMPubAreaAttestedDataMismatch() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + + hashedPubArea[hashedPubArea.Length - 1] ^= 0xFF; + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Hash value mismatch attested and pubArea", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMMissingX5c() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", null) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Neither x5c nor ECDAA were found in the TPM attestation statement", ex.Result.Message); + } + } + } + + [Fact] + public void TestX5cNotArray() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", "string") + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Neither x5c nor ECDAA were found in the TPM attestation statement", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMX5cCountZero() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", CBORObject.NewArray()) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Neither x5c nor ECDAA were found in the TPM attestation statement", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMX5cValuesNull() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", CBORObject.NewArray().Add(null)) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Malformed x5c in TPM attestation", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMX5cValuesCountZero() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", CBORObject.NewArray().Add(CBORObject.Null)) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Malformed x5c in TPM attestation", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMFirstX5cValueNotByteString() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", "x".ToArray()) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Malformed x5c in TPM attestation", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMFirstX5cValueByteStringZeroLen() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0]))) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Malformed x5c in TPM attestation", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMBadSignature() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + signature[signature.Length - 1] ^= 0xff; + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Bad signature in TPM with aikCert", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAikCertNotV3() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var rawAttestnCert = attestnCert.RawData; + rawAttestnCert[12] = 0x41; + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(rawAttestnCert)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("aikCert must be V3", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAikCertSubjectNotEmpty() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attDN = new X500DistinguishedName("CN=Testing, OU=Not Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("aikCert subject must be empty", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAikCertSANMissing() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + //attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("SAN missing from TPM attestation certificate", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAikCertSANZeroLen() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + var aikCertSanExt = new X509Extension( + "2.5.29.17", + new byte[0], + false); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("SAN missing from TPM attestation certificate", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAikCertSANNoManufacturer() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + var asnEncodedSAN = new byte[] { 0x30, 0x53, 0xA4, 0x51, 0x30, 0x4F, 0x31, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x04, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x46, 0x46, 0x46, 0x46, 0x31, 0x44, 0x30, 0x30, 0x1F, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x02, 0x0C, 0x16, 0x46, 0x49, 0x44, 0x4F, 0x32, 0x2D, 0x4E, 0x45, 0x54, 0x2D, 0x4C, 0x49, 0x42, 0x2D, 0x54, 0x45, 0x53, 0x54, 0x2D, 0x54, 0x50, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x03, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x31, 0x44, 0x30, 0x30, 0x30, 0x30, 0x32 }; + var aikCertSanExt = new X509Extension( + "2.5.29.17", + asnEncodedSAN, + false); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("SAN missing TPMManufacturer, TPMModel, or TPMVersion from TPM attestation certificate", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAikCertSANNoModel() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + var asnEncodedSAN = new byte[] { 0x30, 0x53, 0xA4, 0x51, 0x30, 0x4F, 0x31, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x01, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x46, 0x46, 0x46, 0x46, 0x31, 0x44, 0x30, 0x30, 0x1F, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x05, 0x0C, 0x16, 0x46, 0x49, 0x44, 0x4F, 0x32, 0x2D, 0x4E, 0x45, 0x54, 0x2D, 0x4C, 0x49, 0x42, 0x2D, 0x54, 0x45, 0x53, 0x54, 0x2D, 0x54, 0x50, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x03, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x31, 0x44, 0x30, 0x30, 0x30, 0x30, 0x32 }; + var aikCertSanExt = new X509Extension( + "2.5.29.17", + asnEncodedSAN, + false); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("SAN missing TPMManufacturer, TPMModel, or TPMVersion from TPM attestation certificate", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAikCertSANNoVersion() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + var asnEncodedSAN = new byte[] { 0x30, 0x53, 0xA4, 0x51, 0x30, 0x4F, 0x31, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x01, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x46, 0x46, 0x46, 0x46, 0x31, 0x44, 0x30, 0x30, 0x1F, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x03, 0x0C, 0x16, 0x46, 0x49, 0x44, 0x4F, 0x32, 0x2D, 0x4E, 0x45, 0x54, 0x2D, 0x4C, 0x49, 0x42, 0x2D, 0x54, 0x45, 0x53, 0x54, 0x2D, 0x54, 0x50, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x06, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x31, 0x44, 0x30, 0x30, 0x30, 0x30, 0x32 }; + var aikCertSanExt = new X509Extension( + "2.5.29.17", + asnEncodedSAN, + false); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("SAN missing TPMManufacturer, TPMModel, or TPMVersion from TPM attestation certificate", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAikCertSANInvalidManufacturer() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + var asnEncodedSAN = new byte[] { 0x30, 0x53, 0xA4, 0x51, 0x30, 0x4F, 0x31, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x01, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x46, 0x46, 0x46, 0x46, 0x31, 0x44, 0x32, 0x30, 0x1F, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x02, 0x0C, 0x16, 0x46, 0x49, 0x44, 0x4F, 0x32, 0x2D, 0x4E, 0x45, 0x54, 0x2D, 0x4C, 0x49, 0x42, 0x2D, 0x54, 0x45, 0x53, 0x54, 0x2D, 0x54, 0x50, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x03, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x31, 0x44, 0x30, 0x30, 0x30, 0x30, 0x32 }; + var aikCertSanExt = new X509Extension( + "2.5.29.17", + asnEncodedSAN, + false); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Invalid TPM manufacturer found parsing TPM attestation", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAikCertEKUMissingTCGKP() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + //attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("aikCert EKU missing tcg-kp-AIKCertificate OID", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAikCertCATrue() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(caExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("aikCert Basic Constraints extension CA component must be false", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMAikCertMisingAAGUID() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + //attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); + res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; + Assert.Equal(string.Empty, res.Item1.ErrorMessage); + Assert.Equal("ok", res.Item1.Status); + Assert.Equal(_aaguid, res.Item1.Result.Aaguid); + Assert.Equal(_signCount, res.Item1.Result.Counter); + Assert.Equal("tpm", res.Item1.Result.CredType); + Assert.Equal(_credentialID, res.Item1.Result.CredentialId); + Assert.Null(res.Item1.Result.ErrorMessage); + Assert.Equal(_credentialPublicKey.GetBytes(), res.Item1.Result.PublicKey); + Assert.Null(res.Item1.Result.Status); + Assert.Equal("Test User", res.Item1.Result.User.DisplayName); + Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Item1.Result.User.Id); + Assert.Equal("testuser", res.Item1.Result.User.Name); + Assert.Equal(_signCount, res.Item2.Counter - 1); + Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); + Assert.Equal(string.Empty, res.Item2.ErrorMessage); + Assert.Equal("ok", res.Item2.Status); + } + } + } + + [Fact] + public void TestTPMAikCertAAGUIDNotMatchAuthData() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + var asnEncodedAaguid = new byte[] { 0x04, 0x10, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + var idFidoGenCeAaguidExt = new X509Extension(oidIdFidoGenCeAaguid, asnEncodedAaguid, false); + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("aaguid malformed, expected f1d0f1d0-f1d0-f1d0-f1d0-f1d0f1d0f1d0, got d0f1d0f1-d0f1-d0f1-f1d0-f1d0f1d0f1d0", ex.Result.Message); + } + } + } + + [Fact] + public void TestTPMECDAANotSupported() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("ecdaaKeyId", new byte[0]) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("ECDAA support for TPM attestation is not yet implemented", ex.Result.Message); + } + } + } + + /* + [Fact] + public void TestTPMRSATemplate() + { + var param = Fido2Tests._validCOSEParameters[3]; + + var alg = (COSE.Algorithm)param[1]; + if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); + if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); + if (alg == COSE.Algorithm.RS1) + tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); + + using (RSA rsaRoot = RSA.Create()) + { + RSASignaturePadding padding = RSASignaturePadding.Pss; + switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + } + var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); + rootRequest.CertificateExtensions.Add(caExt); + + using (rootCert = rootRequest.CreateSelfSigned( + notBefore, + notAfter)) + + using (var rsaAtt = RSA.Create()) + { + var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); + + attRequest.CertificateExtensions.Add(notCAExt); + + attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); + + attRequest.CertificateExtensions.Add(aikCertSanExt); + + attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); + + byte[] serial = new byte[12]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(serial); + } + using (X509Certificate2 publicOnly = attRequest.Create( + rootCert, + notBefore, + notAfter, + serial)) + { + attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); + } + + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)) + .Add(CBORObject.FromObject(rootCert.RawData)); + var rsaparams = rsaAtt.ExportParameters(true); + + var cpk = CBORObject.NewMap(); + cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); + cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); + cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); + cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); + + _credentialPublicKey = new CredentialPublicKey(cpk); + + unique = rsaparams.Modulus; + exponent = rsaparams.Exponent; + type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); + + var pubArea = CreatePubArea( + type, // Type + tpmAlg, // Alg + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes + new byte[] { 0x00 }, // Policy + new byte[] { 0x00, 0x10 }, // Symmetric + new byte[] { 0x00, 0x10 }, // Scheme + new byte[] { 0x80, 0x00 }, // KeyBits + exponent?.ToArray(), // Exponent + curveId?.ToArray(), // CurveID + kdf?.ToArray(), // KDF + unique.ToArray() // Unique + ); + + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + + byte[] hashedData; + byte[] hashedPubArea; + var hashAlg = CryptoUtils.algMap[(int)alg]; + using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) + { + hashedData = hasher.ComputeHash(data); + hashedPubArea = hasher.ComputeHash(pubArea); + } + IEnumerable extraData = BitConverter + .GetBytes((UInt16)hashedData.Length) + .Reverse() + .ToArray() + .Concat(hashedData); + + var tpmAlgToDigestSizeMap = new Dictionary + { + {TpmAlg.TPM_ALG_SHA1, (160/8) }, + {TpmAlg.TPM_ALG_SHA256, (256/8) }, + {TpmAlg.TPM_ALG_SHA384, (384/8) }, + {TpmAlg.TPM_ALG_SHA512, (512/8) } + }; + + var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); + + IEnumerable tpm2bName = new byte[] { } + .Concat(tpm2bNameLen) + .Concat(tpmAlg) + .Concat(hashedPubArea); + + var certInfo = CreateCertInfo( + new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic + new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type + new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner + extraData.ToArray(), // ExtraData + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount + new byte[] { 0x00 }, // Safe + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion + tpm2bName.ToArray(), // TPM2BName + new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer + ); + + byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("ver", "2.0") + .Add("alg", alg) + .Add("x5c", X5c) + .Add("sig", signature) + .Add("certInfo", certInfo) + .Add("pubArea", pubArea)) + .Add("authData", _authData); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + Assert.Equal("Invalid TPM_ALG_ID found in TPM2B_NAME", ex.Result.Message); + } + } + } + */ + internal static byte[] CreatePubArea(byte[] type, byte[] alg, byte[] attributes, byte[] policy, byte[] symmetric, byte[] scheme, byte[] keyBits, byte[] exponent, byte[] curveID, byte[] kdf, byte[] unique) { From 92b03ff72e4efaca580f3d818f72576482b47e69 Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Tue, 12 May 2020 21:09:43 -0400 Subject: [PATCH 09/13] Add (nearly complete) AndroidKey tests. Replace ASN1 library and all (I think?) manual ASN1 handling with the @pornin library from DDer project. --- .../Asn1Processor/Asn1Processor/Asn1Node.cs | 1612 ------------ .../Asn1Processor/Asn1Processor/Asn1Parser.cs | 201 -- .../Asn1Processor/Asn1Processor/Asn1Tag.cs | 224 -- .../Asn1Processor/Asn1Processor/Asn1Util.cs | 733 ------ .../Asn1Processor/Asn1Processor/BinaryDump.cs | 56 - .../Asn1Processor/Asn1Processor/BinaryView.cs | 220 -- .../Asn1Processor/Asn1Processor/Oid.cs | 162 -- .../Asn1Processor/RelativeOid.cs | 68 - .../Asn1Processor/Asn1Processor/Util.cs | 74 - .../AsnElt.Include.props} | 4 +- ExternalLibs/AsnElt/AsnElt/AsnElt.cs | 2291 +++++++++++++++++ ExternalLibs/AsnElt/AsnElt/AsnException.cs | 19 + ExternalLibs/AsnElt/AsnElt/AsnIO.cs | 309 +++ ExternalLibs/AsnElt/AsnElt/AsnOID.cs | 294 +++ ExternalLibs/AsnElt/AsnElt/LICENSE.txt | 21 + Src/Fido2/AttestationFormat/AndroidKey.cs | 269 +- Src/Fido2/AttestationFormat/FidoU2f.cs | 2 +- Src/Fido2/CryptoUtils.cs | 202 +- Src/Fido2/Fido2.csproj | 2 +- Test/Attestation/AndroidKey.cs | 555 ++++ Test/Attestation/AndroidSafetyNet.cs | 16 + Test/Attestation/FidoU2f.cs | 14 +- Test/Attestation/None.cs | 17 +- Test/Attestation/Packed.cs | 295 ++- Test/Attestation/Tpm.cs | 518 ++-- Test/Fido2Tests.cs | 265 +- 26 files changed, 4244 insertions(+), 4199 deletions(-) delete mode 100644 ExternalLibs/Asn1Processor/Asn1Processor/Asn1Node.cs delete mode 100644 ExternalLibs/Asn1Processor/Asn1Processor/Asn1Parser.cs delete mode 100644 ExternalLibs/Asn1Processor/Asn1Processor/Asn1Tag.cs delete mode 100644 ExternalLibs/Asn1Processor/Asn1Processor/Asn1Util.cs delete mode 100644 ExternalLibs/Asn1Processor/Asn1Processor/BinaryDump.cs delete mode 100644 ExternalLibs/Asn1Processor/Asn1Processor/BinaryView.cs delete mode 100644 ExternalLibs/Asn1Processor/Asn1Processor/Oid.cs delete mode 100644 ExternalLibs/Asn1Processor/Asn1Processor/RelativeOid.cs delete mode 100644 ExternalLibs/Asn1Processor/Asn1Processor/Util.cs rename ExternalLibs/{Asn1Processor/Asn1Processor.Include.props => AsnElt/AsnElt.Include.props} (54%) create mode 100644 ExternalLibs/AsnElt/AsnElt/AsnElt.cs create mode 100644 ExternalLibs/AsnElt/AsnElt/AsnException.cs create mode 100644 ExternalLibs/AsnElt/AsnElt/AsnIO.cs create mode 100644 ExternalLibs/AsnElt/AsnElt/AsnOID.cs create mode 100644 ExternalLibs/AsnElt/AsnElt/LICENSE.txt create mode 100644 Test/Attestation/AndroidKey.cs create mode 100644 Test/Attestation/AndroidSafetyNet.cs diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Node.cs b/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Node.cs deleted file mode 100644 index bfdf786f..00000000 --- a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Node.cs +++ /dev/null @@ -1,1612 +0,0 @@ -//+-------------------------------------------------------------------------------+ -//| Copyright (c) 2003 Liping Dai. All rights reserved. | -//| Web: www.lipingshare.com | -//| Email: lipingshare@yahoo.com | -//| | -//| Copyright and Permission Details: | -//| ================================= | -//| Permission is hereby granted, free of charge, to any person obtaining a copy | -//| of this software and associated documentation files (the "Software"), to deal | -//| in the Software without restriction, including without limitation the rights | -//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the | -//| Software, subject to the following conditions: | -//| | -//| 1. Redistributions of source code must retain the above copyright notice, this| -//| list of conditions and the following disclaimer. | -//| | -//| 2. Redistributions in binary form must reproduce the above copyright notice, | -//| this list of conditions and the following disclaimer in the documentation | -//| and/or other materials provided with the distribution. | -//| | -//| THE SOFTWARE PRODUCT IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, | -//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | -//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR | -//| A PARTICULAR PURPOSE. | -//+-------------------------------------------------------------------------------+ - - -using System; -using System.IO; -using System.Collections; -using System.Text; - -namespace LipingShare.LCLib.Asn1Processor -{ - /// - /// IAsn1Node interface. - /// - public interface IAsn1Node - { - /// - /// Load data from Stream. - /// - /// - /// true:Succeed; false:failed. - bool LoadData(Stream xdata); - - /// - /// Save node data into Stream. - /// - /// Stream. - /// true:Succeed; false:failed. - bool SaveData(Stream xdata); - - /// - /// Get parent node. - /// - Asn1Node ParentNode { get; } - - /// - /// Add child node at the end of children list. - /// - /// Asn1Node - void AddChild(Asn1Node xdata); - - /// - /// Insert a node in the children list before the pointed index. - /// - /// Asn1Node - /// 0 based index. - int InsertChild(Asn1Node xdata, int index); - - /// - /// Insert a node in the children list before the pointed node. - /// - /// Asn1Node that will be instered in the children list. - /// Index node. - /// New node index. - int InsertChild(Asn1Node xdata, Asn1Node indexNode); - - /// - /// Insert a node in the children list after the pointed index. - /// - /// Asn1Node - /// 0 based index. - /// New node index. - int InsertChildAfter(Asn1Node xdata, int index); - - /// - /// Insert a node in the children list after the pointed node. - /// - /// Asn1Node that will be instered in the children list. - /// Index node. - /// New node index. - int InsertChildAfter(Asn1Node xdata, Asn1Node indexNode); - - /// - /// Remove a child from children node list by index. - /// - /// 0 based index. - /// The Asn1Node just removed from the list. - Asn1Node RemoveChild(int index); - - /// - /// Remove the child from children node list. - /// - /// The node needs to be removed. - /// - Asn1Node RemoveChild(Asn1Node node); - - /// - /// Get child node count. - /// - long ChildNodeCount { get; } - - /// - /// Retrieve child node by index. - /// - /// 0 based index. - /// 0 based index. - Asn1Node GetChildNode(int index); - - /// - /// Get descendant node by node path. - /// - /// relative node path that refer to current node. - /// - Asn1Node GetDescendantNodeByPath(string nodePath); - - /// - /// Get/Set tag value. - /// - byte Tag{ get; set; } - - /// - /// Get tag name. - /// - string TagName{ get; } - - /// - /// Get data length. Not included the unused bits byte for BITSTRING. - /// - long DataLength{ get; } - - /// - /// Get the length field bytes. - /// - long LengthFieldBytes{ get; } - - /// - /// Get data offset. - /// - long DataOffset{ get; } - - /// - /// Get unused bits for BITSTRING. - /// - byte UnusedBits{ get; } - - /// - /// Get/Set node data by byte[], the data length field content and all the - /// node in the parent chain will be adjusted. - /// - byte[] Data { get; set; } - - /// - /// Get/Set parseEncapsulatedData. This property will be inherited by the - /// child nodes when loading data. - /// - bool ParseEncapsulatedData { get; set; } - - /// - /// Get the deepness of the node. - /// - long Deepness { get; } - - /// - /// Get the path string of the node. - /// - string Path{ get; } - - /// - /// Get the node and all the descendents text description. - /// - /// starting node. - /// line length. - /// - string GetText(Asn1Node startNode, int lineLen); - - /// - /// Retrieve the node description. - /// - /// true:Return hex string only; - /// false:Convert to more readable string depending on the node tag. - /// string - string GetDataStr(bool pureHexMode); - - /// - /// Get node label string. - /// - /// - /// - /// SHOW_OFFSET - /// SHOW_DATA - /// USE_HEX_OFFSET - /// SHOW_TAG_NUMBER - /// SHOW_PATH - /// - /// string - string GetLabel(uint mask); - - /// - /// Clone a new Asn1Node by current node. - /// - /// new node. - Asn1Node Clone(); - - /// - /// Clear data and children list. - /// - void ClearAll(); - } - - /// - /// Asn1Node, implemented IAsn1Node interface. - /// - public class Asn1Node : IAsn1Node - { - private long lengthFieldBytes; - private byte[] data; - private ArrayList childNodeList; - private const int indentStep = 3; - private bool isIndefiniteLength = false; - private bool parseEncapsulatedData = true; - - /// - /// Default Asn1Node text line length. - /// - public const int defaultLineLen = 80; - - /// - /// Minium line length. - /// - public const int minLineLen = 60; - - private Asn1Node(Asn1Node parentNode, long dataOffset) - { - Init(); - Deepness = parentNode.Deepness + 1; - this.ParentNode = parentNode; - this.DataOffset = dataOffset; - } - - private void Init() - { - childNodeList = new ArrayList(); - data = null; - DataLength = 0; - lengthFieldBytes = 0; - UnusedBits = 0; - Tag = Asn1Tag.SEQUENCE | Asn1TagClasses.CONSTRUCTED; - childNodeList.Clear(); - Deepness = 0; - ParentNode = null; - } - - private string GetHexPrintingStr(Asn1Node startNode, string baseLine, - string lStr, int lineLen) - { - var nodeStr = ""; - var iStr = GetIndentStr(startNode); - var dataStr = Asn1Util.ToHexString(data); - if (dataStr.Length > 0) - { - if (baseLine.Length + dataStr.Length < lineLen) - { - nodeStr += baseLine + "'" + dataStr + "'"; - } - else - { - nodeStr += baseLine + FormatLineHexString( - lStr, - iStr.Length, - lineLen, - dataStr - ); - } - } - else - { - nodeStr += baseLine; - } - return nodeStr + "\r\n"; - } - - private string FormatLineString(string lStr, int indent, int lineLen, string msg) - { - var retval = ""; - indent += indentStep; - var realLen = lineLen - indent; - var sLen = indent; - int currentp; - for (currentp = 0; currentp < msg.Length; currentp += realLen) - { - if (currentp+realLen > msg.Length) - { - retval += "\r\n" + lStr + Asn1Util.GenStr(sLen,' ') + - "'" + msg.Substring(currentp, msg.Length - currentp) + "'"; - } - else - { - retval += "\r\n" + lStr + Asn1Util.GenStr(sLen,' ') + "'" + - msg.Substring(currentp, realLen) + "'"; - } - } - return retval; - } - - private string FormatLineHexString(string lStr, int indent, int lineLen, string msg) - { - var retval = ""; - indent += indentStep; - var realLen = lineLen - indent; - var sLen = indent; - int currentp; - for (currentp = 0; currentp < msg.Length; currentp += realLen) - { - if (currentp+realLen > msg.Length) - { - retval += "\r\n" + lStr + Asn1Util.GenStr(sLen,' ') + - msg.Substring(currentp, msg.Length - currentp); - } - else - { - retval += "\r\n" + lStr + Asn1Util.GenStr(sLen,' ') + - msg.Substring(currentp, realLen); - } - } - return retval; - } - - - //PublicMembers - - /// - /// Constructor, initialize all the members. - /// - public Asn1Node() - { - Init(); - DataOffset = 0; - } - - /// - /// Get/Set isIndefiniteLength. - /// - public bool IsIndefiniteLength - { - get - { - return isIndefiniteLength; - } - set - { - isIndefiniteLength = value; - } - } - - /// - /// Clone a new Asn1Node by current node. - /// - /// new node. - public Asn1Node Clone() - { - var ms = new MemoryStream(); - this.SaveData(ms); - ms.Position = 0; - var node = new Asn1Node(); - node.LoadData(ms); - return node; - } - - /// - /// Get/Set tag value. - /// - public byte Tag { get; set; } - - /// - /// Load data from byte[]. - /// - /// byte[] - /// true:Succeed; false:failed. - public bool LoadData(byte[] byteData) - { - var retval = true; - try - { - var ms = new MemoryStream(byteData); - ms.Position = 0; - retval = LoadData(ms); - ms.Close(); - } - catch - { - retval = false; - } - return retval; - } - - /// - /// Retrieve all the node count in the node subtree. - /// - /// starting node. - /// long integer node count in the node subtree. - public static long GetDescendantNodeCount(Asn1Node node) - { - long count =0; - count += node.ChildNodeCount; - for (var i=0; i - /// Load data from Stream. Start from current position. - /// This function sets requireRecalculatePar to false then calls InternalLoadData - /// to complish the task. - /// - /// Stream - /// true:Succeed; false:failed. - public bool LoadData(Stream xdata) - { - var retval = false; - try - { - RequireRecalculatePar = false; - retval = InternalLoadData(xdata); - return retval; - } - finally - { - RequireRecalculatePar = true; - RecalculateTreePar(); - } - } - - /// - /// Call SaveData and return byte[] as result instead stream. - /// - /// - public byte[] GetRawData() - { - var ms = new MemoryStream(); - SaveData(ms); - var retval = new byte[ms.Length]; - ms.Position = 0; - ms.Read(retval, 0, (int)ms.Length); - ms.Close(); - return retval; - } - - /// - /// Get if data is empty. - /// - public bool IsEmptyData - { - get - { - if (data == null) return true; - if (data.Length < 1) - return true; - else - return false; - } - } - - /// - /// Save node data into Stream. - /// - /// Stream. - /// true:Succeed; false:failed. - public bool SaveData(Stream xdata) - { - var retval = true; - var nodeCount = ChildNodeCount; - xdata.WriteByte(Tag); - var tmpLen = Asn1Util.DERLengthEncode(xdata, (ulong) DataLength); - if ((Tag) == Asn1Tag.BIT_STRING) - { - xdata.WriteByte(UnusedBits); - } - if (nodeCount==0) - { - if (data != null) - { - xdata.Write(data, 0, data.Length); - } - } - else - { - Asn1Node tempNode; - int i; - for (i=0; i - /// Clear data and children list. - /// - public void ClearAll() - { - data = null; - Asn1Node tempNode; - for (var i=0; i - /// Add child node at the end of children list. - /// - /// the node that will be add in. - public void AddChild(Asn1Node xdata) - { - childNodeList.Add(xdata); - RecalculateTreePar(); - } - - /// - /// Insert a node in the children list before the pointed index. - /// - /// Asn1Node - /// 0 based index. - /// New node index. - public int InsertChild(Asn1Node xdata, int index) - { - childNodeList.Insert(index, xdata); - RecalculateTreePar(); - return index; - } - - /// - /// Insert a node in the children list before the pointed node. - /// - /// Asn1Node that will be instered in the children list. - /// Index node. - /// New node index. - public int InsertChild(Asn1Node xdata, Asn1Node indexNode) - { - var index = childNodeList.IndexOf(indexNode); - childNodeList.Insert(index, xdata); - RecalculateTreePar(); - return index; - } - - /// - /// Insert a node in the children list after the pointed node. - /// - /// Asn1Node - /// Index node. - /// New node index. - public int InsertChildAfter(Asn1Node xdata, Asn1Node indexNode) - { - var index = childNodeList.IndexOf(indexNode)+1; - childNodeList.Insert(index, xdata); - RecalculateTreePar(); - return index; - } - - /// - /// Insert a node in the children list after the pointed node. - /// - /// Asn1Node that will be instered in the children list. - /// 0 based index. - /// New node index. - public int InsertChildAfter(Asn1Node xdata, int index) - { - var xindex = index+1; - childNodeList.Insert(xindex, xdata); - RecalculateTreePar(); - return xindex; - } - - /// - /// Remove a child from children node list by index. - /// - /// 0 based index. - /// The Asn1Node just removed from the list. - public Asn1Node RemoveChild(int index) - { - Asn1Node retval = null; - if (index < (childNodeList.Count - 1)) - { - retval = (Asn1Node) childNodeList[index+1]; - } - childNodeList.RemoveAt(index); - if (retval == null) - { - if (childNodeList.Count > 0) - { - retval = (Asn1Node) childNodeList[childNodeList.Count-1]; - } - else - { - retval = this; - } - } - RecalculateTreePar(); - return retval; - } - - /// - /// Remove the child from children node list. - /// - /// The node needs to be removed. - /// - public Asn1Node RemoveChild(Asn1Node node) - { - Asn1Node retval = null; - var i = childNodeList.IndexOf(node); - retval = RemoveChild(i); - return retval; - } - - /// - /// Get child node count. - /// - public long ChildNodeCount - { - get - { - return childNodeList.Count; - } - } - - /// - /// Retrieve child node by index. - /// - /// 0 based index. - /// 0 based index. - public Asn1Node GetChildNode(int index) - { - Asn1Node retval = null; - if (index < ChildNodeCount) - { - retval = (Asn1Node) childNodeList[index]; - } - return retval; - } - - - - /// - /// Get tag name. - /// - public string TagName - { - get - { - return Asn1Util.GetTagName(Tag); - } - } - - /// - /// Get parent node. - /// - public Asn1Node ParentNode { get; private set; } - - /// - /// Get the node and all the descendents text description. - /// - /// starting node. - /// line length. - /// - public string GetText(Asn1Node startNode, int lineLen) - { - var nodeStr = ""; - var baseLine = ""; - var dataStr = ""; - const string lStr = " | | | "; - string oid, oidName; - switch (Tag) - { - case Asn1Tag.BIT_STRING: - baseLine = - string.Format("{0,6}|{1,6}|{2,7}|{3} {4} UnusedBits:{5} : ", - DataOffset, - DataLength, - lengthFieldBytes, - GetIndentStr(startNode), - TagName, - UnusedBits - ); - dataStr = Asn1Util.ToHexString(data); - if (baseLine.Length + dataStr.Length < lineLen) - { - if (dataStr.Length<1) - { - nodeStr += baseLine + "\r\n"; - } - else - { - nodeStr += baseLine + "'" + dataStr + "'\r\n"; - } - } - else - { - nodeStr += baseLine + FormatLineHexString( - lStr, - GetIndentStr(startNode).Length, - lineLen, - dataStr + "\r\n" - ); - } - break; - case Asn1Tag.OBJECT_IDENTIFIER: - var xoid = new Oid(); - oid = xoid.Decode(new MemoryStream(data)); - oidName = "foo";//xoid.GetOidName(oid); - nodeStr += string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : {5} [{6}]\r\n", - DataOffset, - DataLength, - lengthFieldBytes, - GetIndentStr(startNode), - TagName, - oidName, - oid - ); - break; - case Asn1Tag.RELATIVE_OID: - var xiod = new RelativeOid(); - oid = xiod.Decode(new MemoryStream(data)); - oidName = ""; - nodeStr += string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : {5} [{6}]\r\n", - DataOffset, - DataLength, - lengthFieldBytes, - GetIndentStr(startNode), - TagName, - oidName, - oid - ); - break; - case Asn1Tag.PRINTABLE_STRING: - case Asn1Tag.IA5_STRING: - case Asn1Tag.UNIVERSAL_STRING: - case Asn1Tag.VISIBLE_STRING: - case Asn1Tag.NUMERIC_STRING: - case Asn1Tag.UTC_TIME: - case Asn1Tag.UTF8_STRING: - case Asn1Tag.BMPSTRING: - case Asn1Tag.GENERAL_STRING: - case Asn1Tag.GENERALIZED_TIME: - baseLine = - string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : ", - DataOffset, - DataLength, - lengthFieldBytes, - GetIndentStr(startNode), - TagName - ); - if ( Tag == Asn1Tag.UTF8_STRING ) - { - var unicode = new UTF8Encoding(); - dataStr = unicode.GetString(data); - } - else - { - dataStr = Asn1Util.BytesToString(data); - } - if (baseLine.Length + dataStr.Length < lineLen) - { - nodeStr += baseLine + "'" + dataStr + "'\r\n"; - } - else - { - nodeStr += baseLine + FormatLineString( - lStr, - GetIndentStr(startNode).Length, - lineLen, - dataStr) + "\r\n"; - } - break; - case Asn1Tag.INTEGER: - if (data != null && DataLength < 8) - { - nodeStr += string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : {5}\r\n", - DataOffset, - DataLength, - lengthFieldBytes, - GetIndentStr(startNode), - TagName, - Asn1Util.BytesToLong(data).ToString() - ); - } - else - { - baseLine = - string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : ", - DataOffset, - DataLength, - lengthFieldBytes, - GetIndentStr(startNode), - TagName - ); - nodeStr += GetHexPrintingStr(startNode, baseLine, lStr, lineLen); - } - break; - default: - if ((Tag & Asn1Tag.TAG_MASK) == 6) // Visible string for certificate - { - baseLine = - string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : ", - DataOffset, - DataLength, - lengthFieldBytes, - GetIndentStr(startNode), - TagName - ); - dataStr = Asn1Util.BytesToString(data); - if (baseLine.Length + dataStr.Length < lineLen) - { - nodeStr += baseLine + "'" + dataStr + "'\r\n"; - } - else - { - nodeStr += baseLine + FormatLineString( - lStr, - GetIndentStr(startNode).Length, - lineLen, - dataStr) + "\r\n"; - } - } - else - { - baseLine = - string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : ", - DataOffset, - DataLength, - lengthFieldBytes, - GetIndentStr(startNode), - TagName - ); - nodeStr += GetHexPrintingStr(startNode, baseLine, lStr, lineLen); - } - break; - }; - if (childNodeList.Count >= 0) - { - nodeStr += GetListStr(startNode, lineLen); - } - return nodeStr; - } - - /// - /// Get the path string of the node. - /// - public string Path { get; private set; } = ""; - - /// - /// Retrieve the node description. - /// - /// true:Return hex string only; - /// false:Convert to more readable string depending on the node tag. - /// string - public string GetDataStr(bool pureHexMode) - { - const int lineLen = 32; - var dataStr = ""; - if (pureHexMode) - { - dataStr = Asn1Util.FormatString(Asn1Util.ToHexString(data), lineLen, 2); - } - else - { - switch (Tag) - { - case Asn1Tag.BIT_STRING: - dataStr = Asn1Util.FormatString(Asn1Util.ToHexString(data), lineLen, 2); - break; - case Asn1Tag.OBJECT_IDENTIFIER: - var xoid = new Oid(); - dataStr = xoid.Decode(new MemoryStream(data)); - break; - case Asn1Tag.RELATIVE_OID: - var roid = new RelativeOid(); - dataStr = roid.Decode(new MemoryStream(data)); - break; - case Asn1Tag.PRINTABLE_STRING: - case Asn1Tag.IA5_STRING: - case Asn1Tag.UNIVERSAL_STRING: - case Asn1Tag.VISIBLE_STRING: - case Asn1Tag.NUMERIC_STRING: - case Asn1Tag.UTC_TIME: - case Asn1Tag.BMPSTRING: - case Asn1Tag.GENERAL_STRING: - case Asn1Tag.GENERALIZED_TIME: - dataStr = Asn1Util.BytesToString(data); - break; - case Asn1Tag.UTF8_STRING: - var utf8 = new UTF8Encoding(); - dataStr = utf8.GetString(data); - break; - case Asn1Tag.INTEGER: - dataStr = Asn1Util.FormatString(Asn1Util.ToHexString(data), lineLen, 2); - break; - default: - if ((Tag & Asn1Tag.TAG_MASK) == 6) // Visible string for certificate - { - dataStr = Asn1Util.BytesToString(data); - } - else - { - dataStr = Asn1Util.FormatString(Asn1Util.ToHexString(data), lineLen, 2); - } - break; - }; - } - return dataStr; - } - - /// - /// Get node label string. - /// - /// - /// - /// SHOW_OFFSET - /// SHOW_DATA - /// USE_HEX_OFFSET - /// SHOW_TAG_NUMBER - /// SHOW_PATH - /// - /// string - public string GetLabel(uint mask) - { - var nodeStr = ""; - var dataStr = ""; - var offsetStr = ""; - if ((mask & TagTextMask.USE_HEX_OFFSET) != 0) - { - if ((mask & TagTextMask.SHOW_TAG_NUMBER) != 0) - offsetStr = string.Format("(0x{0:X2},0x{1:X6},0x{2:X4})", Tag, DataOffset, DataLength); - else - offsetStr = string.Format("(0x{0:X6},0x{1:X4})", DataOffset, DataLength); - } - else - { - if ((mask & TagTextMask.SHOW_TAG_NUMBER) != 0) - offsetStr = string.Format("({0},{1},{2})", Tag, DataOffset, DataLength); - else - offsetStr = string.Format("({0},{1})", DataOffset, DataLength); - } - string oid, oidName; - switch (Tag) - { - case Asn1Tag.BIT_STRING: - if ((mask & TagTextMask.SHOW_OFFSET) != 0) - { - nodeStr += offsetStr; - } - nodeStr += " " + TagName + " UnusedBits: " + UnusedBits.ToString(); - if ((mask & TagTextMask.SHOW_DATA) != 0) - { - dataStr = Asn1Util.ToHexString(data); - nodeStr += ((dataStr.Length>0) ? " : '" + dataStr + "'" : ""); - } - break; - case Asn1Tag.OBJECT_IDENTIFIER: - var xoid = new Oid(); - oid = xoid.Decode(data); - oidName = "bar";// xoid.GetOidName(oid); - if ((mask & TagTextMask.SHOW_OFFSET) != 0) - { - nodeStr += offsetStr; - } - nodeStr += " " + TagName; - nodeStr += " : " + oidName; - if ((mask & TagTextMask.SHOW_DATA) != 0) - { - nodeStr += ((oid.Length>0) ? " : '" + oid + "'" : ""); - } - break; - case Asn1Tag.RELATIVE_OID: - var roid = new RelativeOid(); - oid = roid.Decode(data); - oidName = ""; - if ((mask & TagTextMask.SHOW_OFFSET) != 0) - { - nodeStr += offsetStr; - } - nodeStr += " " + TagName; - nodeStr += " : " + oidName; - if ((mask & TagTextMask.SHOW_DATA) != 0) - { - nodeStr += ((oid.Length>0) ? " : '" + oid + "'" : ""); - } - break; - case Asn1Tag.PRINTABLE_STRING: - case Asn1Tag.IA5_STRING: - case Asn1Tag.UNIVERSAL_STRING: - case Asn1Tag.VISIBLE_STRING: - case Asn1Tag.NUMERIC_STRING: - case Asn1Tag.UTC_TIME: - case Asn1Tag.UTF8_STRING: - case Asn1Tag.BMPSTRING: - case Asn1Tag.GENERAL_STRING: - case Asn1Tag.GENERALIZED_TIME: - if ((mask & TagTextMask.SHOW_OFFSET) != 0) - { - nodeStr += offsetStr; - } - nodeStr += " " + TagName; - if ((mask & TagTextMask.SHOW_DATA) != 0) - { - if ( Tag == Asn1Tag.UTF8_STRING ) - { - var unicode = new UTF8Encoding(); - dataStr = unicode.GetString(data); - } - else - { - dataStr = Asn1Util.BytesToString(data); - } - nodeStr += ((dataStr.Length>0) ? " : '" + dataStr + "'" : ""); - } - break; - case Asn1Tag.INTEGER: - if ((mask & TagTextMask.SHOW_OFFSET) != 0) - { - nodeStr += offsetStr; - } - nodeStr += " " + TagName; - if ((mask & TagTextMask.SHOW_DATA) != 0) - { - if (data != null && DataLength < 8) - { - dataStr = Asn1Util.BytesToLong(data).ToString(); - } - else - { - dataStr = Asn1Util.ToHexString(data); - } - nodeStr += ((dataStr.Length>0) ? " : '" + dataStr + "'" : ""); - } - break; - default: - if ((mask & TagTextMask.SHOW_OFFSET) != 0) - { - nodeStr += offsetStr; - } - nodeStr += " " + TagName; - if ((mask & TagTextMask.SHOW_DATA) != 0) - { - if ((Tag & Asn1Tag.TAG_MASK) == 6) // Visible string for certificate - { - dataStr = Asn1Util.BytesToString(data); - } - else - { - dataStr = Asn1Util.ToHexString(data); - } - nodeStr += ((dataStr.Length>0) ? " : '" + dataStr + "'" : ""); - } - break; - }; - if ((mask & TagTextMask.SHOW_PATH) != 0) - { - nodeStr = "(" + Path + ") " + nodeStr; - } - return nodeStr; - } - - /// - /// Get data length. Not included the unused bits byte for BITSTRING. - /// - public long DataLength { get; private set; } - - /// - /// Get the length field bytes. - /// - public long LengthFieldBytes - { - get - { - return lengthFieldBytes; - } - } - - /// - /// Get/Set node data by byte[], the data length field content and all the - /// node in the parent chain will be adjusted. - ///

- /// It return all the child data for constructed node. - ///
- public byte[] Data - { - get - { - var xdata = new MemoryStream(); - var nodeCount = ChildNodeCount; - if (nodeCount==0) - { - if (data != null) - { - xdata.Write(data, 0, data.Length); - } - } - else - { - Asn1Node tempNode; - for (var i=0; i - /// Get the deepness of the node. - /// - public long Deepness { get; private set; } - - /// - /// Get data offset. - /// - public long DataOffset { get; private set; } - - /// - /// Get unused bits for BITSTRING. - /// - public byte UnusedBits { get; set; } - - - /// - /// Get descendant node by node path. - /// - /// relative node path that refer to current node. - /// - public Asn1Node GetDescendantNodeByPath(string nodePath) - { - var retval = this; - if (nodePath == null) return retval; - nodePath = nodePath.TrimEnd().TrimStart(); - if (nodePath.Length<1) return retval; - var route = nodePath.Split('/'); - try - { - for (var i = 1; i - /// Get node by OID. - /// - /// OID. - /// Starting node. - /// Null or Asn1Node. - static public Asn1Node GetDecendantNodeByOid(string oid, Asn1Node startNode) - { - Asn1Node retval = null; - var xoid = new Oid(); - for (var i = 0; i - /// Constant of tag field length. - /// - public const int TagLength = 1; - - /// - /// Constant of unused bits field length. - /// - public const int BitStringUnusedFiledLength = 1; - - /// - /// Tag text generation mask definition. - /// - public class TagTextMask - { - /// - /// Show offset. - /// - public const uint SHOW_OFFSET = 0x01; - - /// - /// Show decoded data. - /// - public const uint SHOW_DATA = 0x02; - - /// - /// Show offset in hex format. - /// - public const uint USE_HEX_OFFSET = 0x04; - - /// - /// Show tag. - /// - public const uint SHOW_TAG_NUMBER = 0x08; - - /// - /// Show node path. - /// - public const uint SHOW_PATH = 0x10; - } - - /// - /// Set/Get requireRecalculatePar. RecalculateTreePar function will not do anything - /// if it is set to false. - /// - protected bool RequireRecalculatePar { get; set; } = true; - - //ProtectedMembers - - /// - /// Find root node and recalculate entire tree length field, - /// path, offset and deepness. - /// - protected void RecalculateTreePar() - { - if (!RequireRecalculatePar) return; - Asn1Node rootNode; - for (rootNode = this; rootNode.ParentNode != null;) - { - rootNode = rootNode.ParentNode; - } - ResetBranchDataLength(rootNode); - rootNode.DataOffset = 0; - rootNode.Deepness = 0; - var subOffset = rootNode.DataOffset + TagLength + rootNode.lengthFieldBytes; - ResetChildNodePar(rootNode, subOffset); - } - - /// - /// Recursively set all the node data length. - /// - /// - /// node data length. - protected static long ResetBranchDataLength(Asn1Node node) - { - long retval = 0; - long childDataLength = 0; - if (node.ChildNodeCount < 1) - { - if (node.data != null) - childDataLength += node.data.Length; - } - else - { - for (var i=0; i - /// Encode the node data length field and set lengthFieldBytes and dataLength. - /// - /// The node needs to be reset. - protected static void ResetDataLengthFieldWidth(Asn1Node node) - { - var tempStream = new MemoryStream(); - Asn1Util.DERLengthEncode(tempStream, (ulong) node.DataLength); - node.lengthFieldBytes = tempStream.Length; - tempStream.Close(); - } - - /// - /// Recursively set all the child parameters, except dataLength. - /// dataLength is set by ResetBranchDataLength. - /// - /// Starting node. - /// Starting node offset. - protected void ResetChildNodePar(Asn1Node xNode, long subOffset) - { - int i; - if (xNode.Tag == Asn1Tag.BIT_STRING) - { - subOffset++; - } - Asn1Node tempNode; - for (i=0; i - /// Generate all the child text from childNodeList. - /// - /// Starting node. - /// Line length. - /// Text string. - protected string GetListStr(Asn1Node startNode, int lineLen) - { - var nodeStr = ""; - int i; - Asn1Node tempNode; - for (i=0; i - /// Generate the node indent string. - /// - /// The node. - /// Text string. - protected string GetIndentStr(Asn1Node startNode) - { - var retval = ""; - long startLen = 0; - if (startNode!=null) - { - startLen = startNode.Deepness; - } - for (long i = 0; i - /// Decode ASN.1 encoded node Stream data. - /// - /// Stream data. - /// true:Succeed, false:Failed. - protected bool GeneralDecode(Stream xdata) - { - var retval = false; - long nodeMaxLen; - nodeMaxLen = xdata.Length - xdata.Position; - Tag = (byte) xdata.ReadByte(); - long start, end; - start = xdata.Position; - DataLength = Asn1Util.DerLengthDecode(xdata, ref isIndefiniteLength); - if (DataLength < 0) return retval; // Node data length can not be negative. - end = xdata.Position; - lengthFieldBytes = end - start; - if (nodeMaxLen < (DataLength + TagLength + lengthFieldBytes)) - { - return retval; - } - if ( ParentNode == null || ((ParentNode.Tag & Asn1TagClasses.CONSTRUCTED) == 0)) - { - if ((Tag & Asn1Tag.TAG_MASK)<=0 || (Tag & Asn1Tag.TAG_MASK)>0x1E) return retval; - } - if (Tag == Asn1Tag.BIT_STRING) - { - // First byte of BIT_STRING is unused bits. - // BIT_STRING data does not include this byte. - - // Fixed by Gustaf Björklund. - if (DataLength < 1) return retval; // We cannot read less than 1 - 1 bytes. - - UnusedBits = (byte) xdata.ReadByte(); - data = new byte[DataLength-1]; - xdata.Read(data, 0, (int)(DataLength-1) ); - } - else - { - data = new byte[DataLength]; - xdata.Read(data, 0, (int)(DataLength) ); - } - retval = true; - return retval; - } - - /// - /// Decode ASN.1 encoded complex data type Stream data. - /// - /// Stream data. - /// true:Succeed, false:Failed. - protected bool ListDecode(Stream xdata) - { - var retval = false; - var originalPosition = xdata.Position; - long childNodeMaxLen; - try - { - childNodeMaxLen = xdata.Length - xdata.Position; - Tag = (byte) xdata.ReadByte(); - long start, end, offset; - start = xdata.Position; - DataLength = Asn1Util.DerLengthDecode(xdata, ref isIndefiniteLength); - if (DataLength<0 || childNodeMaxLen - /// Set the node data and recalculate the entire tree parameters. - /// - /// byte[] data. - protected void SetData(byte[] xdata) - { - if (childNodeList.Count > 0) - { - throw new Exception("Constructed node can't hold simple data."); - } - else - { - data = xdata; - if (data != null) - DataLength = data.Length; - else - DataLength = 0; - RecalculateTreePar(); - } - } - - /// - /// Load data from Stream. Start from current position. - /// - /// Stream - /// true:Succeed; false:failed. - protected bool InternalLoadData(Stream xdata) - { - var retval = true; - ClearAll(); - byte xtag; - var curPosition = xdata.Position; - xtag = (byte) xdata.ReadByte(); - xdata.Position = curPosition; - var maskedTag = xtag & Asn1Tag.TAG_MASK; - if (((xtag & Asn1TagClasses.CONSTRUCTED) != 0) - || (parseEncapsulatedData - && ((maskedTag == Asn1Tag.BIT_STRING) - || (maskedTag == Asn1Tag.EXTERNAL) - || (maskedTag == Asn1Tag.GENERAL_STRING) - || (maskedTag == Asn1Tag.GENERALIZED_TIME) - || (maskedTag == Asn1Tag.GRAPHIC_STRING) - || (maskedTag == Asn1Tag.IA5_STRING) - || (maskedTag == Asn1Tag.OCTET_STRING) - || (maskedTag == Asn1Tag.PRINTABLE_STRING) - || (maskedTag == Asn1Tag.SEQUENCE) - || (maskedTag == Asn1Tag.SET) - || (maskedTag == Asn1Tag.T61_STRING) - || (maskedTag == Asn1Tag.UNIVERSAL_STRING) - || (maskedTag == Asn1Tag.UTF8_STRING) - || (maskedTag == Asn1Tag.VIDEOTEXT_STRING) - || (maskedTag == Asn1Tag.VISIBLE_STRING))) - ) - { - if (!ListDecode(xdata)) - { - if (!GeneralDecode(xdata)) - { - retval = false; - } - } - } - else - { - if (!GeneralDecode(xdata)) retval = false; - } - return retval; - } - - /// - /// Get/Set parseEncapsulatedData. This property will be inherited by the - /// child nodes when loading data. - /// - public bool ParseEncapsulatedData - { - get - { - return parseEncapsulatedData; - } - set - { - if (parseEncapsulatedData == value) return; - var tmpData = Data; - parseEncapsulatedData = value; - ClearAll(); - if ((Tag & Asn1TagClasses.CONSTRUCTED) != 0 || parseEncapsulatedData) - { - var ms = new MemoryStream(tmpData); - ms.Position = 0; - var isLoaded = true; - while(ms.Position < ms.Length) - { - var tempNode = new Asn1Node(); - tempNode.ParseEncapsulatedData = parseEncapsulatedData; - if (!tempNode.LoadData(ms)) - { - ClearAll(); - isLoaded = false; - break; - } - AddChild(tempNode); - } - if (!isLoaded) - { - Data = tmpData; - } - } - else - { - Data = tmpData; - } - } - } - - } - -} - diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Parser.cs b/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Parser.cs deleted file mode 100644 index db1c5fb7..00000000 --- a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Parser.cs +++ /dev/null @@ -1,201 +0,0 @@ -//+-------------------------------------------------------------------------------+ -//| Copyright (c) 2003 Liping Dai. All rights reserved. | -//| Web: www.lipingshare.com | -//| Email: lipingshare@yahoo.com | -//| | -//| Copyright and Permission Details: | -//| ================================= | -//| Permission is hereby granted, free of charge, to any person obtaining a copy | -//| of this software and associated documentation files (the "Software"), to deal | -//| in the Software without restriction, including without limitation the rights | -//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the | -//| Software, subject to the following conditions: | -//| | -//| 1. Redistributions of source code must retain the above copyright notice, this| -//| list of conditions and the following disclaimer. | -//| | -//| 2. Redistributions in binary form must reproduce the above copyright notice, | -//| this list of conditions and the following disclaimer in the documentation | -//| and/or other materials provided with the distribution. | -//| | -//| THE SOFTWARE PRODUCT IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, | -//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | -//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR | -//| A PARTICULAR PURPOSE. | -//+-------------------------------------------------------------------------------+ - -using System; -using System.IO; - -namespace LipingShare.LCLib.Asn1Processor -{ - /// - /// ASN.1 encoded data parser. - /// This a higher level class which unilized Asn1Node class functionality to - /// provide functions for ASN.1 encoded files. - /// - public class Asn1Parser - { - private Asn1Node rootNode = new Asn1Node(); - - /// - /// Get/Set parseEncapsulatedData. Reloading data is required after this property is reset. - /// - bool ParseEncapsulatedData - { - get - { - return rootNode.ParseEncapsulatedData; - } - set - { - rootNode.ParseEncapsulatedData = value; - } - } - - /// - /// Constructor. - /// - public Asn1Parser() - { - } - - /// - /// Get raw ASN.1 encoded data. - /// - public byte[] RawData { get; private set; } - - /// - /// Load ASN.1 encoded data from a file. - /// - /// File name. - public void LoadData(string fileName) - { - var fs = new FileStream(fileName, FileMode.Open); - RawData = new byte[fs.Length]; - fs.Read(RawData, 0, (int)fs.Length); - fs.Close(); - var ms = new MemoryStream(RawData); - LoadData(ms); - } - - /// - /// Load PEM formated file. - /// - /// PEM file name. - public void LoadPemData(string fileName) - { - var fs = new FileStream(fileName, FileMode.Open); - var data = new byte[fs.Length]; - fs.Read(data, 0, data.Length); - fs.Close(); - var dataStr = Asn1Util.BytesToString(data); - if (Asn1Util.IsPemFormated(dataStr)) - { - var ms = Asn1Util.PemToStream(dataStr); - ms.Position = 0; - LoadData(ms); - } - else - { - throw new Exception("It is a invalid PEM file: " + fileName); - } - } - - /// - /// Load ASN.1 encoded data from Stream. - /// - /// Stream data. - public void LoadData(Stream stream) - { - stream.Position = 0; - if (!rootNode.LoadData(stream)) - { - throw new Exception("Failed to load data."); - } - RawData = new byte[stream.Length]; - stream.Position = 0; - stream.Read(RawData, 0, RawData.Length); - } - - /// - /// Save data into a file. - /// - /// File name. - public void SaveData(string fileName) - { - var fs = new FileStream(fileName, FileMode.Create); - rootNode.SaveData(fs); - fs.Close(); - } - - /// - /// Get root node. - /// - public Asn1Node RootNode - { - get - { - return rootNode; - } - } - - /// - /// Get a node by path string. - /// - /// Path string. - /// Asn1Node or null. - public Asn1Node GetNodeByPath(string nodePath) - { - return rootNode.GetDescendantNodeByPath(nodePath); - } - - /// - /// Get a node by OID. - /// - /// OID string. - /// Asn1Node or null. - public Asn1Node GetNodeByOid(string oid) - { - return Asn1Node.GetDecendantNodeByOid(oid, rootNode); - } - - /// - /// Generate node text header. This method is used by GetNodeText to put heading. - /// - /// Line length. - /// Header string. - static public string GetNodeTextHeader(int lineLen) - { - var header = string.Format("Offset| Len |LenByte|\r\n"); - header += "======+======+=======+" + Asn1Util.GenStr(lineLen+10, '=') + "\r\n"; - return header; - } - - /// - /// Generate the root node text description. - /// - /// Text string. - public override string ToString() - { - return GetNodeText(rootNode, 100); - } - - /// - /// Generate node text description. It uses GetNodeTextHeader to generate - /// the heading and Asn1Node.GetText to generate the node text. - /// - /// Target node. - /// Line length. - /// Text string. - public static string GetNodeText(Asn1Node node, int lineLen) - { - var nodeStr = GetNodeTextHeader(lineLen); - nodeStr +=node.GetText(node, lineLen); - return nodeStr; - } - - } -} - - diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Tag.cs b/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Tag.cs deleted file mode 100644 index f770a7a9..00000000 --- a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Tag.cs +++ /dev/null @@ -1,224 +0,0 @@ -//+-------------------------------------------------------------------------------+ -//| Copyright (c) 2003 Liping Dai. All rights reserved. | -//| Web: www.lipingshare.com | -//| Email: lipingshare@yahoo.com | -//| | -//| Copyright and Permission Details: | -//| ================================= | -//| Permission is hereby granted, free of charge, to any person obtaining a copy | -//| of this software and associated documentation files (the "Software"), to deal | -//| in the Software without restriction, including without limitation the rights | -//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the | -//| Software, subject to the following conditions: | -//| | -//| 1. Redistributions of source code must retain the above copyright notice, this| -//| list of conditions and the following disclaimer. | -//| | -//| 2. Redistributions in binary form must reproduce the above copyright notice, | -//| this list of conditions and the following disclaimer in the documentation | -//| and/or other materials provided with the distribution. | -//| | -//| THE SOFTWARE PRODUCT IS PROVIDED AS IS WITHOUT WARRANTY OF ANY KIND, | -//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | -//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR | -//| A PARTICULAR PURPOSE. | -//+-------------------------------------------------------------------------------+ - -using System; - -namespace LipingShare.LCLib.Asn1Processor -{ - /// - /// Define ASN.1 tag constants. - /// - /// - public class Asn1Tag - { - /// - /// Tag mask constant value. - /// - public const byte TAG_MASK = 0x1F; - - /// - /// Constant value. - /// - public const byte BOOLEAN = 0x01; - - /// - /// Constant value. - /// - public const byte INTEGER = 0x02; - - /// - /// Constant value. - /// - public const byte BIT_STRING = 0x03; - - /// - /// Constant value. - /// - public const byte OCTET_STRING = 0x04; - - /// - /// Constant value. - /// - public const byte TAG_NULL = 0x05; - - /// - /// Constant value. - /// - public const byte OBJECT_IDENTIFIER = 0x06; - - /// - /// Constant value. - /// - public const byte OBJECT_DESCRIPTOR = 0x07; - - /// - /// Constant value. - /// - public const byte EXTERNAL = 0x08; - - /// - /// Constant value. - /// - public const byte REAL = 0x09; - - /// - /// Constant value. - /// - public const byte ENUMERATED = 0x0a; - - /// - /// Constant value. - /// - public const byte UTF8_STRING = 0x0c; - - /// - /// Relative object identifier. - /// - public const byte RELATIVE_OID = 0x0d; - - /// - /// Constant value. - /// - public const byte SEQUENCE = 0x10; - - /// - /// Constant value. - /// - public const byte SET = 0x11; - - /// - /// Constant value. - /// - public const byte NUMERIC_STRING = 0x12; - - /// - /// Constant value. - /// - public const byte PRINTABLE_STRING = 0x13; - - /// - /// Constant value. - /// - public const byte T61_STRING = 0x14; - - /// - /// Constant value. - /// - public const byte VIDEOTEXT_STRING = 0x15; - - /// - /// Constant value. - /// - public const byte IA5_STRING = 0x16; - - /// - /// Constant value. - /// - public const byte UTC_TIME = 0x17; - - /// - /// Constant value. - /// - public const byte GENERALIZED_TIME = 0x18; - - /// - /// Constant value. - /// - public const byte GRAPHIC_STRING = 0x19; - - /// - /// Constant value. - /// - public const byte VISIBLE_STRING = 0x1a; - - /// - /// Constant value. - /// - public const byte GENERAL_STRING = 0x1b; - - /// - /// Constant value. - /// - public const byte UNIVERSAL_STRING = 0x1C; - - /// - /// Constant value. - /// - public const byte BMPSTRING = 0x1E; /* 30: Basic Multilingual Plane/Unicode string */ - - /// - /// Constructor. - /// - public Asn1Tag() - { - } - }; - - /// - /// Define ASN.1 tag class constants. - /// - /// - public class Asn1TagClasses - { - /// - /// Constant value. - /// - public const byte CLASS_MASK = 0xc0; - - /// - /// Constant value. - /// - public const byte UNIVERSAL = 0x00; - - /// - /// Constant value. - /// - public const byte CONSTRUCTED = 0x20; - - /// - /// Constant value. - /// - public const byte APPLICATION = 0x40; - - /// - /// Constant value. - /// - public const byte CONTEXT_SPECIFIC = 0x80; - - /// - /// Constant value. - /// - public const byte PRIVATE = 0xc0; - - /// - /// Constructor. - /// - public Asn1TagClasses() - { - } - }; - -} diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Util.cs b/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Util.cs deleted file mode 100644 index c131420e..00000000 --- a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Util.cs +++ /dev/null @@ -1,733 +0,0 @@ -//+-------------------------------------------------------------------------------+ -//| Copyright (c) 2003 Liping Dai. All rights reserved. | -//| Web: www.lipingshare.com | -//| Email: lipingshare@yahoo.com | -//| | -//| Copyright and Permission Details: | -//| ================================= | -//| Permission is hereby granted, free of charge, to any person obtaining a copy | -//| of this software and associated documentation files (the "Software"), to deal | -//| in the Software without restriction, including without limitation the rights | -//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the | -//| Software, subject to the following conditions: | -//| | -//| 1. Redistributions of source code must retain the above copyright notice, this| -//| list of conditions and the following disclaimer. | -//| | -//| 2. Redistributions in binary form must reproduce the above copyright notice, | -//| this list of conditions and the following disclaimer in the documentation | -//| and/or other materials provided with the distribution. | -//| | -//| THE SOFTWARE PRODUCT IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, | -//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | -//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR | -//| A PARTICULAR PURPOSE. | -//+-------------------------------------------------------------------------------+ - -using System; -using System.IO; - -namespace LipingShare.LCLib.Asn1Processor -{ - /// - /// Utility functions. - /// - public class Asn1Util - { - - /// - /// Check if the string is ASN.1 encoded hex string. - /// - /// The string. - /// true:Yes, false:No. - public static bool IsAsn1EncodedHexStr(string dataStr) - { - var retval = false; - try - { - var data = HexStrToBytes(dataStr); - if (data.Length > 0) - { - var node = new Asn1Node(); - retval = node.LoadData(data); - } - } - catch - { - retval = false; - } - return retval; - } - - /// - /// Format a string to have certain line length and character group length. - /// Sample result FormatString(xstr,32,2): - /// 07 AE 0B E7 84 5A D4 6C 6A BD DF 8F 89 88 9E F1 - /// - /// source string. - /// line length. - /// group length. - /// - public static string FormatString(string inStr, int lineLen, int groupLen) - { - var tmpCh = new char[inStr.Length*2]; - int i, c = 0, linec = 0; - var gc = 0; - for (i=0; i= groupLen && groupLen > 0) - { - tmpCh[c++] = ' '; - gc = 0; - } - if (linec >= lineLen) - { - tmpCh[c++] = '\r'; - tmpCh[c++] = '\n'; - linec = 0; - } - } - var retval = new string(tmpCh); - retval = retval.TrimEnd('\0'); - retval = retval.TrimEnd('\n'); - retval = retval.TrimEnd('\r'); - return retval; - } - - /// - /// Generate a string by duplicating xch. - /// - /// duplicate times. - /// the duplicated character. - /// - public static string GenStr(int len, char xch) - { - var ch = new char[len]; - for (var i = 0; i - /// Convert byte array to a integer. - /// - /// - /// - public static long BytesToLong(byte[] bytes) - { - long tempInt = 0; - for(var i=0; i - /// Convert a ASCII byte array to string, also filter out the null characters. - /// - /// - /// - public static string BytesToString(byte[] bytes) - { - var retval = ""; - if (bytes == null || bytes.Length < 1) return retval; - var cretval = new char[bytes.Length]; - for (int i=0, j=0; i - /// Convert ASCII string to byte array. - /// - /// - /// - public static byte[] StringToBytes(string msg) - { - var retval = new byte[msg.Length]; - for (var i=0; i - /// Compare source and target byte array. - /// - /// - /// - /// - public static bool IsEqual(byte[] source, byte[] target) - { - if (source == null) return false; - if (target == null) return false; - if (source.Length != target.Length) return false; - for (var i=0; i - /// Constant hex digits array. - /// - static char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - /// - /// Convert a byte array to hex string. - /// - /// source array. - /// hex string. - public static string ToHexString(byte[] bytes) - { - if (bytes == null) return ""; - var chars = new char[bytes.Length * 2]; - int b, i; - for (i = 0; i < bytes.Length; i++) - { - b = bytes[i]; - chars[i * 2] = hexDigits[b >> 4]; - chars[i * 2 + 1] = hexDigits[b & 0xF]; - } - return new string(chars); - } - - /// - /// Check if the character is a valid hex digits. - /// - /// source character. - /// true:Valid, false:Invalid. - public static bool IsValidHexDigits(char ch) - { - var retval = false; - for (var i=0; i - /// Get hex digits value. - /// - /// source character. - /// hex digits value. - public static byte GetHexDigitsVal(char ch) - { - byte retval = 0; - for (var i=0; i - /// Convert hex string to byte array. - /// - /// Source hex string. - /// return byte array. - public static byte[] HexStrToBytes(string hexStr) - { - hexStr = hexStr.Replace(" ", ""); - hexStr = hexStr.Replace("\r", ""); - hexStr = hexStr.Replace("\n", ""); - hexStr = hexStr.ToUpper(); - if ((hexStr.Length%2) != 0) throw new Exception("Invalid Hex string: odd length."); - int i; - for (i=0; i - /// Check if the source string is a valid hex string. - /// - /// source string. - /// true:Valid, false:Invalid. - public static bool IsHexStr(string hexStr) - { - byte[] bytes = null; - try - { - bytes = HexStrToBytes(hexStr); - } - catch - { - return false; - } - if (bytes == null || bytes.Length < 0) - { - return false; - } - else - { - return true; - } - } - - private const string PemStartStr = "-----BEGIN"; - private const string PemEndStr = "-----END"; - /// - /// Check if the source string is PEM formated string. - /// - /// source string. - /// true:Valid, false:Invalid. - public static bool IsPemFormated(string pemStr) - { - byte[] data = null; - try - { - data = PemToBytes(pemStr); - } - catch - { - return false; - } - return (data.Length > 0); - } - - /// - /// Check if a file is PEM formated. - /// - /// source file name. - /// true:Yes, false:No. - public static bool IsPemFormatedFile(string fileName) - { - var retval = false; - try - { - var fs = new FileStream(fileName, FileMode.Open); - var data = new byte[fs.Length]; - fs.Read(data, 0, data.Length); - fs.Close(); - var dataStr = BytesToString(data); - retval = IsPemFormated(dataStr); - } - catch - { - retval = false; - } - return retval; - } - - /// - /// Convert PEM formated string into and set the Stream position to 0. - /// - /// source string. - /// output stream. - public static Stream PemToStream(string pemStr) - { - var bytes = PemToBytes(pemStr); - var retval = new MemoryStream(bytes); - retval.Position = 0; - return retval; - } - - /// - /// Convert PEM formated string into byte array. - /// - /// source string. - /// output byte array. - public static byte[] PemToBytes(string pemStr) - { - byte[] retval = null; - var lines = pemStr.Split('\n'); - var base64Str = ""; - bool started = false, ended = false; - var cline = ""; - for (var i = 0; i PemStartStr.Length) - { - if (!started && cline.Substring(0, PemStartStr.Length) == PemStartStr) - { - started = true; - continue; - } - } - if (cline.Length > PemEndStr.Length) - { - if (cline.Substring(0, PemEndStr.Length) == PemEndStr) - { - ended = true; - break; - } - } - if (started) - { - base64Str += lines[i]; - } - } - if (!(started && ended)) - { - throw new Exception("'BEGIN'/'END' line is missing."); - } - base64Str = base64Str.Replace("\r", ""); - base64Str = base64Str.Replace("\n", ""); - base64Str = base64Str.Replace("\n", " "); - retval = Convert.FromBase64String(base64Str); - return retval; - } - - /// - /// Convert byte array to PEM formated string. - /// - /// - /// - public static string BytesToPem(byte[] data) - { - return BytesToPem(data, ""); - } - - /// - /// Retrieve PEM file heading. - /// - /// source file name. - /// heading string. - public static string GetPemFileHeader(string fileName) - { - try - { - var fs = new FileStream(fileName, FileMode.Open); - var data = new byte[fs.Length]; - fs.Read(data, 0, data.Length); - fs.Close(); - var dataStr = BytesToString(data); - return GetPemHeader(dataStr); - } - catch - { - return ""; - } - } - - /// - /// Retrieve PEM heading from a PEM formated string. - /// - /// source string. - /// heading string. - public static string GetPemHeader(string pemStr) - { - var lines = pemStr.Split('\n'); - var started = false; - var cline = ""; - for (var i = 0; i PemStartStr.Length) - { - if (!started && cline.Substring(0, PemStartStr.Length) == PemStartStr) - { - started = true; - var retstr = lines[i].Substring(PemStartStr.Length, - lines[i].Length - - PemStartStr.Length).Replace("-----",""); - return retstr.Replace("\r", ""); - } - } - else - { - continue; - } - } - return ""; - } - - /// - /// Convert byte array to PEM formated string and set the heading as pemHeader. - /// - /// source array. - /// PEM heading. - /// PEM formated string. - public static string BytesToPem(byte[] data, string pemHeader) - { - if (pemHeader == null || pemHeader.Length<1) - { - pemHeader = "ASN.1 Editor Generated PEM File"; - } - var retval = ""; - if (pemHeader.Length > 0 && pemHeader[0] != ' ') - { - pemHeader = " " + pemHeader; - } - retval = Convert.ToBase64String(data); - retval = FormatString(retval, 64, 0); - retval = "-----BEGIN"+ pemHeader +"-----\r\n" + - retval + - "\r\n-----END"+ pemHeader +"-----\r\n"; - return retval; - } - - /// - /// Calculate how many bits is enough to hold ivalue. - /// - /// source value. - /// bits number. - public static int BitPrecision(ulong ivalue) - { - if (ivalue == 0) return 0; - int l = 0, h = 8 * 4; // 4: sizeof(ulong) - while (h-l > 1) - { - var t = (int) (l+h)/2; - if ((ivalue >> t) != 0) - l = t; - else - h = t; - } - return h; - } - - /// - /// Calculate how many bytes is enough to hold the value. - /// - /// input value. - /// bytes number. - public static int BytePrecision(ulong value) - { - int i; - for (i = 4; i > 0; --i) // 4: sizeof(ulong) - if ((value >> (i-1)*8)!=0) - break; - return i; - } - - /// - /// ASN.1 DER length encoder. - /// - /// result output stream. - /// source length. - /// result bytes. - public static int DERLengthEncode(Stream xdata, ulong length) - { - var i=0; - if (length <= 0x7f) - { - xdata.WriteByte((byte)length); - i++; - } - else - { - xdata.WriteByte((byte)(BytePrecision(length) | 0x80)); - i++; - for (var j=BytePrecision((ulong)length); j>0; --j) - { - xdata.WriteByte((byte)(length >> (j-1)*8)); - i++; - } - } - return i; - } - - /// - /// ASN.1 DER length decoder. - /// - /// Source stream. - /// Output parameter. - /// Output length. - public static long DerLengthDecode(Stream bt, ref bool isIndefiniteLength) - { - isIndefiniteLength = false; - long length = 0; - byte b; - b = (byte) bt.ReadByte(); - if ((b & 0x80)==0) - { - length = b; - } - else - { - long lengthBytes = b & 0x7f; - if (lengthBytes == 0) - { - isIndefiniteLength = true; - var sPos = bt.Position; - return -2; // Indefinite length. - } - length = 0; - while (lengthBytes-- > 0) - { - if ((length >> (8 * (4 - 1))) > 0) // 4: sizeof(long) - { - return -1; // Length overflow. - } - b = (byte) bt.ReadByte(); - length = (length << 8) | b; - } - } - return length; - } - - /// - /// Decode tag value to return tag name. - /// - /// input tag. - /// tag name. - static public string GetTagName(byte tag) - { - var retval = ""; - if ((tag & Asn1TagClasses.CLASS_MASK) != 0) - { - switch (tag & Asn1TagClasses.CLASS_MASK) - { - case Asn1TagClasses.CONTEXT_SPECIFIC: - retval += "CONTEXT SPECIFIC (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() +")"; - break; - case Asn1TagClasses.APPLICATION: - retval += "APPLICATION (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() +")"; - break; - case Asn1TagClasses.PRIVATE: - retval += "PRIVATE (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() +")"; - break; - case Asn1TagClasses.CONSTRUCTED: - retval += "CONSTRUCTED (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() +")"; - break; - case Asn1TagClasses.UNIVERSAL: - retval += "UNIVERSAL (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() +")"; - break; - } - } - else - { - switch (tag & Asn1Tag.TAG_MASK) - { - case Asn1Tag.BOOLEAN: - retval += "BOOLEAN"; - break; - case Asn1Tag.INTEGER: - retval += "INTEGER"; - break; - case Asn1Tag.BIT_STRING: - retval += "BIT STRING"; - break; - case Asn1Tag.OCTET_STRING: - retval += "OCTET STRING"; - break; - case Asn1Tag.TAG_NULL: - retval += "NULL"; - break; - case Asn1Tag.OBJECT_IDENTIFIER: - retval += "OBJECT IDENTIFIER"; - break; - case Asn1Tag.OBJECT_DESCRIPTOR: - retval += "OBJECT DESCRIPTOR"; - break; - case Asn1Tag.RELATIVE_OID: - retval += "RELATIVE-OID"; - break; - case Asn1Tag.EXTERNAL: - retval += "EXTERNAL"; - break; - case Asn1Tag.REAL: - retval += "REAL"; - break; - case Asn1Tag.ENUMERATED: - retval += "ENUMERATED"; - break; - case Asn1Tag.UTF8_STRING: - retval += "UTF8 STRING"; - break; - case (Asn1Tag.SEQUENCE): - retval += "SEQUENCE"; - break; - case (Asn1Tag.SET): - retval += "SET"; - break; - case Asn1Tag.NUMERIC_STRING: - retval += "NUMERIC STRING"; - break; - case Asn1Tag.PRINTABLE_STRING: - retval += "PRINTABLE STRING"; - break; - case Asn1Tag.T61_STRING: - retval += "T61 STRING"; - break; - case Asn1Tag.VIDEOTEXT_STRING: - retval += "VIDEOTEXT STRING"; - break; - case Asn1Tag.IA5_STRING: - retval += "IA5 STRING"; - break; - case Asn1Tag.UTC_TIME: - retval += "UTC TIME"; - break; - case Asn1Tag.GENERALIZED_TIME: - retval += "GENERALIZED TIME"; - break; - case Asn1Tag.GRAPHIC_STRING: - retval += "GRAPHIC STRING"; - break; - case Asn1Tag.VISIBLE_STRING: - retval += "VISIBLE STRING"; - break; - case Asn1Tag.GENERAL_STRING: - retval += "GENERAL STRING"; - break; - case Asn1Tag.UNIVERSAL_STRING: - retval += "UNIVERSAL STRING"; - break; - case Asn1Tag.BMPSTRING: - retval += "BMP STRING"; - break; - default: - retval += "UNKNOWN TAG"; - break; - }; - } - return retval; - } - - /// - /// Constructor. - /// - private Asn1Util() - { - //Private constructor. - } - - } -} diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/BinaryDump.cs b/ExternalLibs/Asn1Processor/Asn1Processor/BinaryDump.cs deleted file mode 100644 index 96d5a3a4..00000000 --- a/ExternalLibs/Asn1Processor/Asn1Processor/BinaryDump.cs +++ /dev/null @@ -1,56 +0,0 @@ -//+-------------------------------------------------------------------------------+ -//| Copyright (c) 2003 Liping Dai. All rights reserved. | -//| Web: www.lipingshare.com | -//| Email: lipingshare@yahoo.com | -//| | -//| Copyright and Permission Details: | -//| ================================= | -//| Permission is hereby granted, free of charge, to any person obtaining a copy | -//| of this software and associated documentation files (the "Software"), to deal | -//| in the Software without restriction, including without limitation the rights | -//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the | -//| Software, subject to the following conditions: | -//| | -//| 1. Redistributions of source code must retain the above copyright notice, this| -//| list of conditions and the following disclaimer. | -//| | -//| 2. Redistributions in binary form must reproduce the above copyright notice, | -//| this list of conditions and the following disclaimer in the documentation | -//| and/or other materials provided with the distribution. | -//| | -//| THE SOFTWARE PRODUCT IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, | -//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | -//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR | -//| A PARTICULAR PURPOSE. | -//+-------------------------------------------------------------------------------+ - -namespace LCLib.Asn1Processor -{ - /// - /// Summary description for BinaryDump. - /// - public class BinaryDump - { - public byte[] Data { get; set; } = null; - - public int OffsetWidth { get; set; } = 3; - - public int DataWidth { get; set; } = 16; - - - public BinaryDump() - { - } - - public static string Dump(byte[] data, int offsetWidth, int dataWidth) - { - var retval = ""; - var offset = 0; - for (offset = 0; offset - /// BinaryView class. It is used to calculate hex view parameters. - /// - public class BinaryView - { - - /// - /// Default constructor. - /// - public BinaryView() - { - CalculatePar(); - } - - /// - /// Set hex view parameters. It calls to get the parameters. - /// - /// Parameters Definition: - /// 000000 30 82 05 32 30 82 04 1A A0 03 02 01 02 02 0A 1F 0..20........... - /// 000010 CE 8F 20 00 00 00 00 00 22 30 0D 06 09 2A 86 48 .. ....."0...*.H - /// |----|offsetWidth |--dataWidth --| - /// |----- hexWidth ---------------------------------------| - /// |----- totalWidth -------------------------------------------------------| - /// - /// - /// input - /// input - public void SetPar(int offsetWidth, int dataWidth) - { - OffsetWidth = offsetWidth; - DataWidth = dataWidth; - CalculatePar(); - } - - /// - /// Constructor, it calls to set the parameters. - /// - /// input - /// input - public BinaryView(int offsetWidth, int dataWidth) - { - SetPar(offsetWidth, dataWidth); - } - - /// - /// Get offsetWidth. - /// - public int OffsetWidth { get; private set; } = 6; - - /// - /// Get dataWidth. - /// - public int DataWidth { get; private set; } = 16; - - /// - /// Get totalWidth. - /// - public int TotalWidth { get; private set; } - - /// - /// Get hexWidth. - /// - public int HexWidth { get; private set; } - - /// - /// Calculate hex view parameters. - /// - protected void CalculatePar() - { - TotalWidth = OffsetWidth + 2 + DataWidth*3 + ((DataWidth/8) - 1) + 1 + DataWidth; - HexWidth = TotalWidth - DataWidth; - } - - /// - /// Generate hex view text string by calling . - /// - /// source byte array. - /// output string. - public string GenerateText(byte[] data) - { - return GetBinaryViewText(data, OffsetWidth, DataWidth); - } - - /// - /// Calculate the byte by offset. - /// - /// - /// - public void GetLocation(int byteOffset, ByteLocation loc) - { - var colOff = byteOffset - byteOffset/DataWidth * DataWidth; - var line = byteOffset/DataWidth; - var col = OffsetWidth + 2 + colOff * 3; - var colLen = 3; - var totOff = line * TotalWidth + line + col; - var col2 = HexWidth + colOff; - var totOff2 = line * TotalWidth + line + col2; - var colLen2 = 1; - loc.hexOffset = totOff; - loc.hexColLen = colLen; - loc.line = line; - loc.chOffset = totOff2; - loc.chColLen = colLen2; - } - - /// - /// Generate "Detail" hex view text. - /// - /// source byte array. - /// offset text width. - /// data text width - /// detail hex view string. - public static string GetBinaryViewText(byte[] data, int offsetWidth, int dataWidth) - { - var retval = ""; - var offForm = "{0:X"+offsetWidth+"} "; - int i, lineStart, lineEnd; - int line = 0, offset = 0; - var totalWidth = offsetWidth + 2 + dataWidth*3 + ((dataWidth/8) - 1) + 1 + dataWidth; - var hexWidth = totalWidth - dataWidth; - var dumpStr = ""; - var lineStr = ""; - for (offset = 0; offset=data.Length) break; - if ((i+1)%8==0 && i!=0 && (i+1)128) - lineStr += '.'; - else - lineStr += (char)data[i]; - } - lineStr = lineStr.PadRight(totalWidth, ' '); - dumpStr += lineStr + "\r\n"; - } - retval = dumpStr; - return retval; - } - - } - - /// - /// ByteLocation class is used by to transfer - /// location parameters. - /// - public class ByteLocation - { - /// - /// line number. - /// - public int line = 0; - - /// - /// Hex encoded data length. - /// - public int hexColLen = 3; - - /// - /// Hex encoded data offset. - /// - public int hexOffset = 0; - - /// - /// Character length. - /// - public int chColLen = 1; - - /// - /// Character offset. - /// - public int chOffset = 0; - - /// - /// Constructor. - /// - public ByteLocation() - { - } - } -} diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/Oid.cs b/ExternalLibs/Asn1Processor/Asn1Processor/Oid.cs deleted file mode 100644 index 53280f97..00000000 --- a/ExternalLibs/Asn1Processor/Asn1Processor/Oid.cs +++ /dev/null @@ -1,162 +0,0 @@ -//+-------------------------------------------------------------------------------+ -//| Copyright (c) 2003 Liping Dai. All rights reserved. | -//| Web: www.lipingshare.com | -//| Email: lipingshare@yahoo.com | -//| | -//| Copyright and Permission Details: | -//| ================================= | -//| Permission is hereby granted, free of charge, to any person obtaining a copy | -//| of this software and associated documentation files (the "Software"), to deal | -//| in the Software without restriction, including without limitation the rights | -//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the | -//| Software, subject to the following conditions: | -//| | -//| 1. Redistributions of source code must retain the above copyright notice, this| -//| list of conditions and the following disclaimer. | -//| | -//| 2. Redistributions in binary form must reproduce the above copyright notice, | -//| this list of conditions and the following disclaimer in the documentation | -//| and/or other materials provided with the distribution. | -//| | -//| THE SOFTWARE PRODUCT IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, | -//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | -//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR | -//| A PARTICULAR PURPOSE. | -//+-------------------------------------------------------------------------------+ - -using System; -using System.IO; - -namespace LipingShare.LCLib.Asn1Processor -{ - /// - /// Summary description for OID. - /// This class is used to encode and decode OID strings. - /// - public class Oid - { - /// - /// Encode OID string to byte array. - /// - /// source string. - /// encoded array. - public byte[] Encode(string oidStr) - { - var ms = new MemoryStream(); - Encode(ms, oidStr); - ms.Position = 0; - var retval = new byte[ms.Length]; - ms.Read(retval, 0, retval.Length); - ms.Close(); - return retval; - } - - /// - /// Decode OID byte array to OID string. - /// - /// source byte array. - /// result OID string. - public string Decode(byte[] data) - { - var ms = new MemoryStream(data) - { - Position = 0 - }; - var retval = Decode(ms); - ms.Close(); - return retval; - } - - /// - /// Encode OID string and put result into - /// - /// output stream. - /// source OID string. - public virtual void Encode(Stream bt, string oidStr) //TODO - { - var oidList = oidStr.Split('.'); - if (oidList.Length < 2) throw new Exception("Invalid OID string."); - var values = new ulong[oidList.Length]; - for (var i = 0; i - /// Decode OID and return OID string. - /// - /// source stream. - /// result OID string. - public virtual string Decode(Stream bt) - { - var retval = ""; - byte b; - ulong v = 0; - b = (byte) bt.ReadByte(); - retval += Convert.ToString(b/40); - retval += "." + Convert.ToString(b%40); - while (bt.Position < bt.Length) - { - try - { - DecodeValue(bt, ref v); - retval += "." + v.ToString(); - } - catch(Exception e) - { - throw new Exception("Failed to decode OID value: " + e.Message); - } - } - return retval; - } - - /// - /// Default constructor - /// - public Oid() - { - } - - /// - /// Encode single OID value. - /// - /// output stream. - /// source value. - protected void EncodeValue(Stream bt, ulong v) - { - for (var i=(Asn1Util.BitPrecision(v)-1)/7; i > 0; i--) - { - bt.WriteByte((byte)(0x80 | ((v >> (i*7)) & 0x7f))); - } - bt.WriteByte((byte)(v & 0x7f)); - } - - /// - /// Decode single OID value. - /// - /// source stream. - /// output value - /// OID value bytes. - protected int DecodeValue(Stream bt, ref ulong v) - { - byte b; - var i=0; - v = 0; - while (true) - { - b = (byte) bt.ReadByte(); - i++; - v <<= 7; - v += (ulong) (b & 0x7f); - if ((b & 0x80) == 0) - return i; - } - } - - } -} - diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/RelativeOid.cs b/ExternalLibs/Asn1Processor/Asn1Processor/RelativeOid.cs deleted file mode 100644 index 5d8e2fa7..00000000 --- a/ExternalLibs/Asn1Processor/Asn1Processor/RelativeOid.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.IO; - -namespace LipingShare.LCLib.Asn1Processor -{ - /// - /// Summary description for RelativeOid. - /// - public class RelativeOid : Oid - { - /// - /// Constructor. - /// - public RelativeOid() - { - } - - /// - /// Encode relative OID string and put result into - /// - /// output stream. - /// source OID string. - public override void Encode(Stream bt, string oidStr) - { - var oidList = oidStr.Split('.'); - var values = new ulong[oidList.Length]; - for (var i = 0; i - /// Decode relative OID and return OID string. - /// - /// source stream. - /// result OID string. - public override string Decode(Stream bt) - { - var retval = ""; - ulong v = 0; - var isFirst = true; - while (bt.Position < bt.Length) - { - try - { - DecodeValue(bt, ref v); - if (isFirst) - { - retval = v.ToString(); - isFirst = false; - } - else - { - retval += "." + v.ToString(); - } - } - catch(Exception e) - { - throw new Exception("Failed to decode OID value: " + e.Message); - } - } - return retval; - } - } -} diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/Util.cs b/ExternalLibs/Asn1Processor/Asn1Processor/Util.cs deleted file mode 100644 index aff94ef4..00000000 --- a/ExternalLibs/Asn1Processor/Asn1Processor/Util.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.IO; - -namespace LCLib.Asn1Processor -{ - /// - /// Summary description for Util. - /// - public class Asn1Util - { - public static int BytePrecision(ulong value) - { - int i; - for (i=sizeof(ulong); i>0; --i) - if ((value >> (i-1)*8)!=0) - break; - return i; - } - - public static int DERLengthEncode(Stream xdata, ulong length) - { - var i=0; - if (length <= 0x7f) - { - xdata.WriteByte((byte)length); - i++; - } - else - { - xdata.WriteByte((byte)(BytePrecision(length) | 0x80)); - i++; - for (var j=BytePrecision((ulong)length); j>0; --j) - { - xdata.WriteByte((byte)(length >> (j-1)*8)); - i++; - } - } - return i; - } - - public static long DerLengthDecode(Stream bt) - { - long length = 0; - byte b; - b = (byte) bt.ReadByte(); - if ((b & 0x80)==0) - { - length = b; - } - else - { - long lengthBytes = b & 0x7f; - if (lengthBytes == 0) - { - throw new Exception("Indefinite length."); - } - length = 0; - while (lengthBytes-- > 0) - { - if ((length >> (8*(sizeof(long)-1))) > 0) - throw new Exception("Length overflow."); - b = (byte) bt.ReadByte(); - length = (length << 8) | b; - } - } - return length; - } - - private Asn1Util() - { - } - - } -} diff --git a/ExternalLibs/Asn1Processor/Asn1Processor.Include.props b/ExternalLibs/AsnElt/AsnElt.Include.props similarity index 54% rename from ExternalLibs/Asn1Processor/Asn1Processor.Include.props rename to ExternalLibs/AsnElt/AsnElt.Include.props index 9e905360..c86484bc 100644 --- a/ExternalLibs/Asn1Processor/Asn1Processor.Include.props +++ b/ExternalLibs/AsnElt/AsnElt.Include.props @@ -1,10 +1,10 @@ - Asn1Processor\%(RecursiveDir)%(FileName)%(Extension) + AsnElt\%(FileName)%(Extension) - Asn1Processor\%(RecursiveDir)%(FileName)%(Extension) + AsnElt\%(FileName)%(Extension) \ No newline at end of file diff --git a/ExternalLibs/AsnElt/AsnElt/AsnElt.cs b/ExternalLibs/AsnElt/AsnElt/AsnElt.cs new file mode 100644 index 00000000..56a90d03 --- /dev/null +++ b/ExternalLibs/AsnElt/AsnElt/AsnElt.cs @@ -0,0 +1,2291 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Asn1 { + +/* + * An AsnElt instance represents a decoded ASN.1 DER object. It is + * immutable. + */ + +public class AsnElt { + + /* + * Universal tag values. + */ + public const int BOOLEAN = 1; + public const int INTEGER = 2; + public const int BIT_STRING = 3; + public const int OCTET_STRING = 4; + public const int NULL = 5; + public const int OBJECT_IDENTIFIER = 6; + public const int Object_Descriptor = 7; + public const int EXTERNAL = 8; + public const int REAL = 9; + public const int ENUMERATED = 10; + public const int EMBEDDED_PDV = 11; + public const int UTF8String = 12; + public const int RELATIVE_OID = 13; + public const int SEQUENCE = 16; + public const int SET = 17; + public const int NumericString = 18; + public const int PrintableString = 19; + public const int T61String = 20; + public const int TeletexString = 20; + public const int VideotexString = 21; + public const int IA5String = 22; + public const int UTCTime = 23; + public const int GeneralizedTime = 24; + public const int GraphicString = 25; + public const int VisibleString = 26; + public const int GeneralString = 27; + public const int UniversalString = 28; + public const int CHARACTER_STRING = 29; + public const int BMPString = 30; + + /* + * Tag classes. + */ + public const int UNIVERSAL = 0; + public const int APPLICATION = 1; + public const int CONTEXT = 2; + public const int PRIVATE = 3; + + /* + * Internal rules + * ============== + * + * Instances are immutable. They reference an internal buffer + * that they never modify. The buffer is never shown to the + * outside; when decoding and creating, copies are performed + * where necessary. + * + * If the instance was created by decoding, then: + * objBuf points to the array containing the complete object + * objOff start offset for the object header + * objLen complete object length + * valOff offset for the first value byte + * valLen value length (excluding the null-tag, if applicable) + * hasEncodedHeader is true + * + * If the instance was created from an explicit value or from + * sub-elements, then: + * objBuf contains the value, or is null + * objOff is 0 + * objLen is -1, or contains the computed object length + * valOff is 0 + * valLen is -1, or contains the computed value length + * hasEncodedHeader is false + * + * If objBuf is null, then the object is necessarily constructed + * (Sub is not null). If objBuf is not null, then the encoded + * value is known (the object may be constructed or primitive), + * and valOff/valLen identify the actual value within objBuf. + * + * Tag class and value, and sub-elements, are referenced from + * specific properties. + */ + + byte[] objBuf; + int objOff; + int objLen; + int valOff; + int valLen; + bool hasEncodedHeader; + + AsnElt() + { + } + + /* + * The tag class for this element. + */ + int tagClass_; + public int TagClass { + get { + return tagClass_; + } + private set { + tagClass_ = value; + } + } + + /* + * The tag value for this element. + */ + int tagValue_; + public int TagValue { + get { + return tagValue_; + } + private set { + tagValue_ = value; + } + } + + /* + * The sub-elements. This is null if this element is primitive. + * DO NOT MODIFY this array. + */ + AsnElt[] sub_; + public AsnElt[] Sub { + get { + return sub_; + } + private set { + sub_ = value; + } + } + + /* + * The "constructed" flag: true for an elements with sub-elements, + * false for a primitive element. + */ + public bool Constructed { + get { + return Sub != null; + } + } + + /* + * The value length. When the object is BER-encoded with an + * indefinite length, the value length includes all the sub-objects + * but NOT the formal null-tag marker. + */ + public int ValueLength { + get { + if (valLen < 0) { + if (Constructed) { + int vlen = 0; + foreach (AsnElt a in Sub) { + vlen += a.EncodedLength; + } + valLen = vlen; + } else { + valLen = objBuf.Length; + } + } + return valLen; + } + } + + /* + * The encoded object length (complete with header). + */ + public int EncodedLength { + get { + if (objLen < 0) { + int vlen = ValueLength; + objLen = TagLength(TagValue) + + LengthLength(vlen) + vlen; + } + return objLen; + } + } + + /* + * Check that this element is constructed. An exception is thrown + * if this is not the case. + */ + public void CheckConstructed() + { + if (!Constructed) { + throw new AsnException("not constructed"); + } + } + + /* + * Check that this element is primitive. An exception is thrown + * if this is not the case. + */ + public void CheckPrimitive() + { + if (Constructed) { + throw new AsnException("not primitive"); + } + } + + /* + * Get a sub-element. This method throws appropriate exceptions + * if this element is not constructed, or the requested index + * is out of range. + */ + public AsnElt GetSub(int n) + { + CheckConstructed(); + if (n < 0 || n >= Sub.Length) { + throw new AsnException("no such sub-object: n=" + n); + } + return Sub[n]; + } + + /* + * Check that the tag is UNIVERSAL with the provided value. + */ + public void CheckTag(int tv) + { + CheckTag(UNIVERSAL, tv); + } + + /* + * Check that the tag has the specified class and value. + */ + public void CheckTag(int tc, int tv) + { + if (TagClass != tc || TagValue != tv) { + throw new AsnException("unexpected tag: " + TagString); + } + } + + /* + * Check that this element is constructed and contains exactly + * 'n' sub-elements. + */ + public void CheckNumSub(int n) + { + CheckConstructed(); + if (Sub.Length != n) { + throw new AsnException("wrong number of sub-elements: " + + Sub.Length + " (expected: " + n + ")"); + } + } + + /* + * Check that this element is constructed and contains at least + * 'n' sub-elements. + */ + public void CheckNumSubMin(int n) + { + CheckConstructed(); + if (Sub.Length < n) { + throw new AsnException("not enough sub-elements: " + + Sub.Length + " (minimum: " + n + ")"); + } + } + + /* + * Check that this element is constructed and contains no more + * than 'n' sub-elements. + */ + public void CheckNumSubMax(int n) + { + CheckConstructed(); + if (Sub.Length > n) { + throw new AsnException("too many sub-elements: " + + Sub.Length + " (maximum: " + n + ")"); + } + } + + /* + * Get a string representation of the tag class and value. + */ + public string TagString { + get { + return TagToString(TagClass, TagValue); + } + } + + static string TagToString(int tc, int tv) + { + switch (tc) { + case UNIVERSAL: + break; + case APPLICATION: + return "APPLICATION:" + tv; + case CONTEXT: + return "CONTEXT:" + tv; + case PRIVATE: + return "PRIVATE:" + tv; + default: + return String.Format("INVALID:{0}/{1}", tc, tv); + } + + switch (tv) { + case BOOLEAN: return "BOOLEAN"; + case INTEGER: return "INTEGER"; + case BIT_STRING: return "BIT_STRING"; + case OCTET_STRING: return "OCTET_STRING"; + case NULL: return "NULL"; + case OBJECT_IDENTIFIER: return "OBJECT_IDENTIFIER"; + case Object_Descriptor: return "Object_Descriptor"; + case EXTERNAL: return "EXTERNAL"; + case REAL: return "REAL"; + case ENUMERATED: return "ENUMERATED"; + case EMBEDDED_PDV: return "EMBEDDED_PDV"; + case UTF8String: return "UTF8String"; + case RELATIVE_OID: return "RELATIVE_OID"; + case SEQUENCE: return "SEQUENCE"; + case SET: return "SET"; + case NumericString: return "NumericString"; + case PrintableString: return "PrintableString"; + case TeletexString: return "TeletexString"; + case VideotexString: return "VideotexString"; + case IA5String: return "IA5String"; + case UTCTime: return "UTCTime"; + case GeneralizedTime: return "GeneralizedTime"; + case GraphicString: return "GraphicString"; + case VisibleString: return "VisibleString"; + case GeneralString: return "GeneralString"; + case UniversalString: return "UniversalString"; + case CHARACTER_STRING: return "CHARACTER_STRING"; + case BMPString: return "BMPString"; + default: + return String.Format("UNIVERSAL:" + tv); + } + } + + /* + * Get the encoded length for a tag. + */ + static int TagLength(int tv) + { + if (tv <= 0x1F) { + return 1; + } + int z = 1; + while (tv > 0) { + z ++; + tv >>= 7; + } + return z; + } + + /* + * Get the encoded length for a length. + */ + static int LengthLength(int len) + { + if (len < 0x80) { + return 1; + } + int z = 1; + while (len > 0) { + z ++; + len >>= 8; + } + return z; + } + + /* + * Decode an ASN.1 object. The provided buffer is internally + * copied. Trailing garbage is not tolerated. + */ + public static AsnElt Decode(byte[] buf) + { + return Decode(buf, 0, buf.Length, true); + } + + /* + * Decode an ASN.1 object. The provided buffer is internally + * copied. Trailing garbage is not tolerated. + */ + public static AsnElt Decode(byte[] buf, int off, int len) + { + return Decode(buf, off, len, true); + } + + /* + * Decode an ASN.1 object. The provided buffer is internally + * copied. If 'exactLength' is true, then trailing garbage is + * not tolerated (it triggers an exception). + */ + public static AsnElt Decode(byte[] buf, bool exactLength) + { + return Decode(buf, 0, buf.Length, exactLength); + } + + /* + * Decode an ASN.1 object. The provided buffer is internally + * copied. If 'exactLength' is true, then trailing garbage is + * not tolerated (it triggers an exception). + */ + public static AsnElt Decode(byte[] buf, int off, int len, + bool exactLength) + { + int tc, tv, valOff, valLen, objLen; + bool cons; + objLen = Decode(buf, off, len, + out tc, out tv, out cons, + out valOff, out valLen); + if (exactLength && objLen != len) { + throw new AsnException("trailing garbage"); + } + byte[] nbuf = new byte[objLen]; + Array.Copy(buf, off, nbuf, 0, objLen); + return DecodeNoCopy(nbuf, 0, objLen); + } + + /* + * Internal recursive decoder. The provided array is NOT copied. + * Trailing garbage is ignored (caller should use the 'objLen' + * field to learn the total object length). + */ + static AsnElt DecodeNoCopy(byte[] buf, int off, int len) + { + int tc, tv, valOff, valLen, objLen; + bool cons; + objLen = Decode(buf, off, len, + out tc, out tv, out cons, + out valOff, out valLen); + AsnElt a = new AsnElt(); + a.TagClass = tc; + a.TagValue = tv; + a.objBuf = buf; + a.objOff = off; + a.objLen = objLen; + a.valOff = valOff; + a.valLen = valLen; + a.hasEncodedHeader = true; + if (cons) { + List subs = new List(); + off = valOff; + int lim = valOff + valLen; + while (off < lim) { + AsnElt b = DecodeNoCopy(buf, off, lim - off); + off += b.objLen; + subs.Add(b); + } + a.Sub = subs.ToArray(); + } else { + a.Sub = null; + } + return a; + } + + /* + * Decode the tag and length, and get the value offset and length. + * Returned value if the total object length. + * Note: when an object has indefinite length, the terminated + * "null tag" will NOT be considered part of the "value length". + */ + static int Decode(byte[] buf, int off, int maxLen, + out int tc, out int tv, out bool cons, + out int valOff, out int valLen) + { + int lim = off + maxLen; + int orig = off; + + /* + * Decode tag. + */ + CheckOff(off, lim); + tv = buf[off ++]; + cons = (tv & 0x20) != 0; + tc = tv >> 6; + tv &= 0x1F; + if (tv == 0x1F) { + tv = 0; + for (;;) { + CheckOff(off, lim); + int c = buf[off ++]; + if (tv > 0xFFFFFF) { + throw new AsnException( + "tag value overflow"); + } + tv = (tv << 7) | (c & 0x7F); + if ((c & 0x80) == 0) { + break; + } + } + } + + /* + * Decode length. + */ + CheckOff(off, lim); + int vlen = buf[off ++]; + if (vlen == 0x80) { + /* + * Indefinite length. This is not strict DER, but + * we allow it nonetheless; we must check that + * the value was tagged as constructed, though. + */ + vlen = -1; + if (!cons) { + throw new AsnException("indefinite length" + + " but not constructed"); + } + } else if (vlen > 0x80) { + int lenlen = vlen - 0x80; + CheckOff(off + lenlen - 1, lim); + vlen = 0; + while (lenlen -- > 0) { + if (vlen > 0x7FFFFF) { + throw new AsnException( + "length overflow"); + } + vlen = (vlen << 8) + buf[off ++]; + } + } + + /* + * Length was decoded, so the value starts here. + */ + valOff = off; + + /* + * If length is indefinite then we must explore sub-objects + * to get the value length. + */ + if (vlen < 0) { + for (;;) { + int tc2, tv2, valOff2, valLen2; + bool cons2; + int slen; + + slen = Decode(buf, off, lim - off, + out tc2, out tv2, out cons2, + out valOff2, out valLen2); + if (tc2 == 0 && tv2 == 0) { + if (cons2 || valLen2 != 0) { + throw new AsnException( + "invalid null tag"); + } + valLen = off - valOff; + off += slen; + break; + } else { + off += slen; + } + } + } else { + if (vlen > (lim - off)) { + throw new AsnException("value overflow"); + } + off += vlen; + valLen = off - valOff; + } + + return off - orig; + } + + static void CheckOff(int off, int lim) + { + if (off >= lim) { + throw new AsnException("offset overflow"); + } + } + + /* + * Get a specific byte from the value. This provided offset is + * relative to the value start (first value byte has offset 0). + */ + public int ValueByte(int off) + { + if (off < 0) { + throw new AsnException("invalid value offset: " + off); + } + if (objBuf == null) { + int k = 0; + foreach (AsnElt a in Sub) { + int slen = a.EncodedLength; + if ((k + slen) > off) { + return a.ValueByte(off - k); + } + } + } else { + if (off < valLen) { + return objBuf[valOff + off]; + } + } + throw new AsnException(String.Format( + "invalid value offset {0} (length = {1})", + off, ValueLength)); + } + + /* + * Encode this object into a newly allocated array. + */ + public byte[] Encode() + { + byte[] r = new byte[EncodedLength]; + Encode(r, 0); + return r; + } + + /* + * Encode this object into the provided array. Encoded object + * length is returned. + */ + public int Encode(byte[] dst, int off) + { + return Encode(0, Int32.MaxValue, dst, off); + } + + /* + * Encode this object into the provided array. Only bytes + * at offset between 'start' (inclusive) and 'end' (exclusive) + * are actually written. The number of written bytes is returned. + * Offsets are relative to the object start (first tag byte). + */ + int Encode(int start, int end, byte[] dst, int dstOff) + { + /* + * If the encoded value is already known, then we just + * dump it. + */ + if (hasEncodedHeader) { + int from = objOff + Math.Max(0, start); + int to = objOff + Math.Min(objLen, end); + int len = to - from; + if (len > 0) { + Array.Copy(objBuf, from, dst, dstOff, len); + return len; + } else { + return 0; + } + } + + int off = 0; + + /* + * Encode tag. + */ + int fb = (TagClass << 6) + (Constructed ? 0x20 : 0x00); + if (TagValue < 0x1F) { + fb |= (TagValue & 0x1F); + if (start <= off && off < end) { + dst[dstOff ++] = (byte)fb; + } + off ++; + } else { + fb |= 0x1F; + if (start <= off && off < end) { + dst[dstOff ++] = (byte)fb; + } + off ++; + int k = 0; + for (int v = TagValue; v > 0; v >>= 7, k += 7); + while (k > 0) { + k -= 7; + int v = (TagValue >> k) & 0x7F; + if (k != 0) { + v |= 0x80; + } + if (start <= off && off < end) { + dst[dstOff ++] = (byte)v; + } + off ++; + } + } + + /* + * Encode length. + */ + int vlen = ValueLength; + if (vlen < 0x80) { + if (start <= off && off < end) { + dst[dstOff ++] = (byte)vlen; + } + off ++; + } else { + int k = 0; + for (int v = vlen; v > 0; v >>= 8, k += 8); + if (start <= off && off < end) { + dst[dstOff ++] = (byte)(0x80 + (k >> 3)); + } + off ++; + while (k > 0) { + k -= 8; + if (start <= off && off < end) { + dst[dstOff ++] = (byte)(vlen >> k); + } + off ++; + } + } + + /* + * Encode value. We must adjust the start/end window to + * make it relative to the value. + */ + EncodeValue(start - off, end - off, dst, dstOff); + off += vlen; + + /* + * Compute copied length. + */ + return Math.Max(0, Math.Min(off, end) - Math.Max(0, start)); + } + + /* + * Encode the value into the provided buffer. Only value bytes + * at offsets between 'start' (inclusive) and 'end' (exclusive) + * are written. Actual number of written bytes is returned. + * Offsets are relative to the start of the value. + */ + int EncodeValue(int start, int end, byte[] dst, int dstOff) + { + int orig = dstOff; + if (objBuf == null) { + int k = 0; + foreach (AsnElt a in Sub) { + int slen = a.EncodedLength; + dstOff += a.Encode(start - k, end - k, + dst, dstOff); + k += slen; + } + } else { + int from = Math.Max(0, start); + int to = Math.Min(valLen, end); + int len = to - from; + if (len > 0) { + Array.Copy(objBuf, valOff + from, + dst, dstOff, len); + dstOff += len; + } + } + return dstOff - orig; + } + + /* + * Copy a value chunk. The provided offset ('off') and length ('len') + * define the chunk to copy; the offset is relative to the value + * start (first value byte has offset 0). If the requested window + * exceeds the value boundaries, an exception is thrown. + */ + public void CopyValueChunk(int off, int len, byte[] dst, int dstOff) + { + int vlen = ValueLength; + if (off < 0 || len < 0 || len > (vlen - off)) { + throw new AsnException(String.Format( + "invalid value window {0}:{1}" + + " (value length = {2})", off, len, vlen)); + } + EncodeValue(off, off + len, dst, dstOff); + } + + /* + * Copy the value into the specified array. The value length is + * returned. + */ + public int CopyValue(byte[] dst, int off) + { + return EncodeValue(0, Int32.MaxValue, dst, off); + } + + /* + * Get a copy of the value as a freshly allocated array. + */ + public byte[] CopyValue() + { + byte[] r = new byte[ValueLength]; + EncodeValue(0, r.Length, r, 0); + return r; + } + + /* + * Get the value. This may return a shared buffer, that MUST NOT + * be modified. + */ + byte[] GetValue(out int off, out int len) + { + if (objBuf == null) { + /* + * We can modify objBuf because CopyValue() + * called ValueLength, thus valLen has been + * filled. + */ + objBuf = CopyValue(); + off = 0; + len = objBuf.Length; + } else { + off = valOff; + len = valLen; + } + return objBuf; + } + + /* + * Interpret the value as a BOOLEAN. + */ + public bool GetBoolean() + { + if (Constructed) { + throw new AsnException( + "invalid BOOLEAN (constructed)"); + } + int vlen = ValueLength; + if (vlen != 1) { + throw new AsnException(String.Format( + "invalid BOOLEAN (length = {0})", vlen)); + } + return ValueByte(0) != 0; + } + + /* + * Interpret the value as an INTEGER. An exception is thrown if + * the value does not fit in a 'long'. + */ + public long GetInteger() + { + if (Constructed) { + throw new AsnException( + "invalid INTEGER (constructed)"); + } + int vlen = ValueLength; + if (vlen == 0) { + throw new AsnException("invalid INTEGER (length = 0)"); + } + int v = ValueByte(0); + long x; + if ((v & 0x80) != 0) { + x = -1; + for (int k = 0; k < vlen; k ++) { + if (x < ((-1L) << 55)) { + throw new AsnException( + "integer overflow (negative)"); + } + x = (x << 8) + (long)ValueByte(k); + } + } else { + x = 0; + for (int k = 0; k < vlen; k ++) { + if (x >= (1L << 55)) { + throw new AsnException( + "integer overflow (positive)"); + } + x = (x << 8) + (long)ValueByte(k); + } + } + return x; + } + + /* + * Interpret the value as an INTEGER. An exception is thrown if + * the value is outside of the provided range. + */ + public long GetInteger(long min, long max) + { + long v = GetInteger(); + if (v < min || v > max) { + throw new AsnException("integer out of allowed range"); + } + return v; + } + + /* + * Interpret the value as an INTEGER. Return its hexadecimal + * representation (uppercase), preceded by a '0x' or '-0x' + * header, depending on the integer sign. The number of + * hexadecimal digits is even. Leading zeroes are returned (but + * one may remain, to ensure an even number of digits). If the + * integer has value 0, then 0x00 is returned. + */ + public string GetIntegerHex() + { + if (Constructed) { + throw new AsnException( + "invalid INTEGER (constructed)"); + } + int vlen = ValueLength; + if (vlen == 0) { + throw new AsnException("invalid INTEGER (length = 0)"); + } + StringBuilder sb = new StringBuilder(); + byte[] tmp = CopyValue(); + if (tmp[0] >= 0x80) { + sb.Append('-'); + int cc = 1; + for (int i = tmp.Length - 1; i >= 0; i --) { + int v = ((~tmp[i]) & 0xFF) + cc; + tmp[i] = (byte)v; + cc = v >> 8; + } + } + int k = 0; + while (k < tmp.Length && tmp[k] == 0) { + k ++; + } + if (k == tmp.Length) { + return "0x00"; + } + sb.Append("0x"); + while (k < tmp.Length) { + sb.AppendFormat("{0:X2}", tmp[k ++]); + } + return sb.ToString(); + } + + /* + * Interpret the value as an OCTET STRING. The value bytes are + * returned. This method supports constructed values and performs + * the reassembly. + */ + public byte[] GetOctetString() + { + int len = GetOctetString(null, 0); + byte[] r = new byte[len]; + GetOctetString(r, 0); + return r; + } + + /* + * Interpret the value as an OCTET STRING. The value bytes are + * written in dst[], starting at offset 'off', and the total value + * length is returned. If 'dst' is null, then no byte is written + * anywhere, but the total length is still returned. This method + * supports constructed values and performs the reassembly. + */ + public int GetOctetString(byte[] dst, int off) + { + if (Constructed) { + int orig = off; + foreach (AsnElt ae in Sub) { + ae.CheckTag(AsnElt.OCTET_STRING); + off += ae.GetOctetString(dst, off); + } + return off - orig; + } + if (dst != null) { + return CopyValue(dst, off); + } else { + return ValueLength; + } + } + + /* + * Interpret the value as a BIT STRING. The bits are returned, + * with the "ignored bits" cleared. + */ + public byte[] GetBitString() + { + int bitLength; + return GetBitString(out bitLength); + } + + /* + * Interpret the value as a BIT STRING. The bits are returned, + * with the "ignored bits" cleared. The actual bit length is + * written in 'bitLength'. + */ + public byte[] GetBitString(out int bitLength) + { + if (Constructed) { + /* + * TODO: support constructed BIT STRING values. + */ + throw new AsnException( + "invalid BIT STRING (constructed)"); + } + int vlen = ValueLength; + if (vlen == 0) { + throw new AsnException( + "invalid BIT STRING (length = 0)"); + } + int fb = ValueByte(0); + if (fb > 7 || (vlen == 1 && fb != 0)) { + throw new AsnException(String.Format( + "invalid BIT STRING (start = 0x{0:X2})", fb)); + } + byte[] r = new byte[vlen - 1]; + CopyValueChunk(1, vlen - 1, r, 0); + if (vlen > 1) { + r[r.Length - 1] &= (byte)(0xFF << fb); + } + bitLength = (r.Length << 3) - fb; + return r; + } + + /* + * Interpret the value as a NULL. + */ + public void CheckNull() + { + if (Constructed) { + throw new AsnException( + "invalid NULL (constructed)"); + } + if (ValueLength != 0) { + throw new AsnException(String.Format( + "invalid NULL (length = {0})", ValueLength)); + } + } + + /* + * Interpret the value as an OBJECT IDENTIFIER, and return it + * (in decimal-dotted string format). + */ + public string GetOID() + { + CheckPrimitive(); + if (valLen == 0) { + throw new AsnException("zero-length OID"); + } + int v = objBuf[valOff]; + if (v >= 120) { + throw new AsnException( + "invalid OID: first byte = " + v); + } + StringBuilder sb = new StringBuilder(); + sb.Append(v / 40); + sb.Append('.'); + sb.Append(v % 40); + long acc = 0; + bool uv = false; + for (int i = 1; i < valLen; i ++) { + v = objBuf[valOff + i]; + if ((acc >> 56) != 0) { + throw new AsnException( + "invalid OID: integer overflow"); + } + acc = (acc << 7) + (long)(v & 0x7F); + if ((v & 0x80) == 0) { + sb.Append('.'); + sb.Append(acc); + acc = 0; + uv = false; + } else { + uv = true; + } + } + if (uv) { + throw new AsnException( + "invalid OID: truncated"); + } + return sb.ToString(); + } + + /* + * Get the object value as a string. The string type is inferred + * from the tag. + */ + public string GetString() + { + if (TagClass != UNIVERSAL) { + throw new AsnException(String.Format( + "cannot infer string type: {0}:{1}", + TagClass, TagValue)); + } + return GetString(TagValue); + } + + /* + * Get the object value as a string. The string type is provided + * (universal tag value). Supported string types include + * NumericString, PrintableString, IA5String, TeletexString + * (interpreted as ISO-8859-1), UTF8String, BMPString and + * UniversalString; the "time types" (UTCTime and GeneralizedTime) + * are also supported, though, in their case, the internal + * contents are not checked (they are decoded as PrintableString). + */ + public string GetString(int type) + { + if (Constructed) { + throw new AsnException( + "invalid string (constructed)"); + } + switch (type) { + case NumericString: + case PrintableString: + case IA5String: + case TeletexString: + case UTCTime: + case GeneralizedTime: + return DecodeMono(objBuf, valOff, valLen, type); + case UTF8String: + return DecodeUTF8(objBuf, valOff, valLen); + case BMPString: + return DecodeUTF16(objBuf, valOff, valLen); + case UniversalString: + return DecodeUTF32(objBuf, valOff, valLen); + default: + throw new AsnException( + "unsupported string type: " + type); + } + } + + static string DecodeMono(byte[] buf, int off, int len, int type) + { + char[] tc = new char[len]; + for (int i = 0; i < len; i ++) { + tc[i] = (char)buf[off + i]; + } + VerifyChars(tc, type); + return new string(tc); + } + + static string DecodeUTF8(byte[] buf, int off, int len) + { + /* + * Skip BOM. + */ + if (len >= 3 && buf[off] == 0xEF + && buf[off + 1] == 0xBB && buf[off + 2] == 0xBF) + { + off += 3; + len -= 3; + } + char[] tc = null; + for (int k = 0; k < 2; k ++) { + int tcOff = 0; + for (int i = 0; i < len; i ++) { + int c = buf[off + i]; + int e; + if (c < 0x80) { + e = 0; + } else if (c < 0xC0) { + throw BadByte(c, UTF8String); + } else if (c < 0xE0) { + c &= 0x1F; + e = 1; + } else if (c < 0xF0) { + c &= 0x0F; + e = 2; + } else if (c < 0xF8) { + c &= 0x07; + e = 3; + } else { + throw BadByte(c, UTF8String); + } + while (e -- > 0) { + if (++ i >= len) { + throw new AsnException( + "invalid UTF-8 string"); + } + int d = buf[off + i]; + if (d < 0x80 || d > 0xBF) { + throw BadByte(d, UTF8String); + } + c = (c << 6) + (d & 0x3F); + } + if (c > 0x10FFFF) { + throw BadChar(c, UTF8String); + } + if (c > 0xFFFF) { + c -= 0x10000; + int hi = 0xD800 + (c >> 10); + int lo = 0xDC00 + (c & 0x3FF); + if (tc != null) { + tc[tcOff] = (char)hi; + tc[tcOff + 1] = (char)lo; + } + tcOff += 2; + } else { + if (tc != null) { + tc[tcOff] = (char)c; + } + tcOff ++; + } + } + if (tc == null) { + tc = new char[tcOff]; + } + } + VerifyChars(tc, UTF8String); + return new string(tc); + } + + static string DecodeUTF16(byte[] buf, int off, int len) + { + if ((len & 1) != 0) { + throw new AsnException( + "invalid UTF-16 string: length = " + len); + } + len >>= 1; + if (len == 0) { + return ""; + } + bool be = true; + int hi = buf[off]; + int lo = buf[off + 1]; + if (hi == 0xFE && lo == 0xFF) { + off += 2; + len --; + } else if (hi == 0xFF && lo == 0xFE) { + off += 2; + len --; + be = false; + } + char[] tc = new char[len]; + for (int i = 0; i < len; i ++) { + int b0 = buf[off ++]; + int b1 = buf[off ++]; + if (be) { + tc[i] = (char)((b0 << 8) + b1); + } else { + tc[i] = (char)((b1 << 8) + b0); + } + } + VerifyChars(tc, BMPString); + return new string(tc); + } + + static string DecodeUTF32(byte[] buf, int off, int len) + { + if ((len & 3) != 0) { + throw new AsnException( + "invalid UTF-32 string: length = " + len); + } + len >>= 2; + if (len == 0) { + return ""; + } + bool be = true; + if (buf[off] == 0x00 + && buf[off + 1] == 0x00 + && buf[off + 2] == 0xFE + && buf[off + 3] == 0xFF) + { + off += 4; + len --; + } else if (buf[off] == 0xFF + && buf[off + 1] == 0xFE + && buf[off + 2] == 0x00 + && buf[off + 3] == 0x00) + { + off += 4; + len --; + be = false; + } + + char[] tc = null; + for (int k = 0; k < 2; k ++) { + int tcOff = 0; + for (int i = 0; i < len; i ++) { + uint b0 = buf[off + 0]; + uint b1 = buf[off + 1]; + uint b2 = buf[off + 2]; + uint b3 = buf[off + 3]; + uint c; + if (be) { + c = (b0 << 24) | (b1 << 16) + | (b2 << 8) | b3; + } else { + c = (b3 << 24) | (b2 << 16) + | (b1 << 8) | b0; + } + if (c > 0x10FFFF) { + throw BadChar((int)c, UniversalString); + } + if (c > 0xFFFF) { + c -= 0x10000; + int hi = 0xD800 + (int)(c >> 10); + int lo = 0xDC00 + (int)(c & 0x3FF); + if (tc != null) { + tc[tcOff] = (char)hi; + tc[tcOff + 1] = (char)lo; + } + tcOff += 2; + } else { + if (tc != null) { + tc[tcOff] = (char)c; + } + tcOff ++; + } + } + if (tc == null) { + tc = new char[tcOff]; + } + } + VerifyChars(tc, UniversalString); + return new string(tc); + } + + static void VerifyChars(char[] tc, int type) + { + switch (type) { + case NumericString: + foreach (char c in tc) { + if (!IsNum(c)) { + throw BadChar(c, type); + } + } + return; + case PrintableString: + case UTCTime: + case GeneralizedTime: + foreach (char c in tc) { + if (!IsPrintable(c)) { + throw BadChar(c, type); + } + } + return; + case IA5String: + foreach (char c in tc) { + if (!IsIA5(c)) { + throw BadChar(c, type); + } + } + return; + case TeletexString: + foreach (char c in tc) { + if (!IsLatin1(c)) { + throw BadChar(c, type); + } + } + return; + } + + /* + * For Unicode string types (UTF-8, BMP...). + */ + for (int i = 0; i < tc.Length; i ++) { + int c = tc[i]; + if (c >= 0xFDD0 && c <= 0xFDEF) { + throw BadChar(c, type); + } + if (c == 0xFFFE || c == 0xFFFF) { + throw BadChar(c, type); + } + if (c < 0xD800 || c > 0xDFFF) { + continue; + } + if (c > 0xDBFF) { + throw BadChar(c, type); + } + int hi = c & 0x3FF; + if (++ i >= tc.Length) { + throw BadChar(c, type); + } + c = tc[i]; + if (c < 0xDC00 || c > 0xDFFF) { + throw BadChar(c, type); + } + int lo = c & 0x3FF; + c = 0x10000 + lo + (hi << 10); + if ((c & 0xFFFE) == 0xFFFE) { + throw BadChar(c, type); + } + } + } + + static bool IsNum(int c) + { + return c == ' ' || (c >= '0' && c <= '9'); + } + + internal static bool IsPrintable(int c) + { + if (c >= 'A' && c <= 'Z') { + return true; + } + if (c >= 'a' && c <= 'z') { + return true; + } + if (c >= '0' && c <= '9') { + return true; + } + switch (c) { + case ' ': case '(': case ')': case '+': + case ',': case '-': case '.': case '/': + case ':': case '=': case '?': case '\'': + return true; + default: + return false; + } + } + + static bool IsIA5(int c) + { + return c < 128; + } + + static bool IsLatin1(int c) + { + return c < 256; + } + + static AsnException BadByte(int c, int type) + { + return new AsnException(String.Format( + "unexpected byte 0x{0:X2} in string of type {1}", + c, type)); + } + + static AsnException BadChar(int c, int type) + { + return new AsnException(String.Format( + "unexpected character U+{0:X4} in string of type {1}", + c, type)); + } + + /* + * Decode the value as a date/time. Returned object is in UTC. + * Type of date is inferred from the tag value. + */ + public DateTime GetTime() + { + if (TagClass != UNIVERSAL) { + throw new AsnException(String.Format( + "cannot infer date type: {0}:{1}", + TagClass, TagValue)); + } + return GetTime(TagValue); + } + + /* + * Decode the value as a date/time. Returned object is in UTC. + * The time string type is provided as parameter (UTCTime or + * GeneralizedTime). + */ + public DateTime GetTime(int type) + { + bool isGen = false; + switch (type) { + case UTCTime: + break; + case GeneralizedTime: + isGen = true; + break; + default: + throw new AsnException( + "unsupported date type: " + type); + } + string s = GetString(type); + string orig = s; + + /* + * UTCTime has format: + * YYMMDDhhmm[ss](Z|(+|-)hhmm) + * + * GeneralizedTime has format: + * YYYYMMDDhhmmss[.uu*][Z|(+|-)hhmm] + * + * Differences between the two types: + * -- UTCTime encodes year over two digits; GeneralizedTime + * uses four digits. UTCTime years map to 1950..2049 (00 is + * 2000). + * -- Seconds are optional with UTCTime, mandatory with + * GeneralizedTime. + * -- GeneralizedTime can have fractional seconds (optional). + * -- Time zone is optional for GeneralizedTime. However, + * a missing time zone means "local time" which depends on + * the locality, so this is discouraged. + * + * Some other notes: + * -- If there is a fractional second, then it must include + * at least one digit. This implementation processes the + * first three digits, and ignores the rest (if present). + * -- Time zone offset ranges from -23:59 to +23:59. + * -- The calendar computations are delegated to .NET's + * DateTime (and DateTimeOffset) so this implements a + * Gregorian calendar, even for dates before 1589. Year 0 + * is not supported. + */ + + /* + * Check characters. + */ + foreach (char c in s) { + if (c >= '0' && c <= '9') { + continue; + } + if (c == '.' || c == '+' || c == '-' || c == 'Z') { + continue; + } + throw BadTime(type, orig); + } + + bool good = true; + + /* + * Parse the time zone. + */ + int tzHours = 0; + int tzMinutes = 0; + bool negZ = false; + bool noTZ = false; + if (s.EndsWith("Z")) { + s = s.Substring(0, s.Length - 1); + } else { + int j = s.IndexOf('+'); + if (j < 0) { + j = s.IndexOf('-'); + negZ = true; + } + if (j < 0) { + noTZ = true; + } else { + string t = s.Substring(j + 1); + s = s.Substring(0, j); + if (t.Length != 4) { + throw BadTime(type, orig); + } + tzHours = Dec2(t, 0, ref good); + tzMinutes = Dec2(t, 2, ref good); + if (tzHours < 0 || tzHours > 23 + || tzMinutes < 0 || tzMinutes > 59) + { + throw BadTime(type, orig); + } + } + } + + /* + * Lack of time zone is allowed only for GeneralizedTime. + */ + if (noTZ && !isGen) { + throw BadTime(type, orig); + } + + /* + * Parse the date elements. + */ + if (s.Length < 4) { + throw BadTime(type, orig); + } + int year = Dec2(s, 0, ref good); + if (isGen) { + year = year * 100 + Dec2(s, 2, ref good); + s = s.Substring(4); + } else { + if (year < 50) { + year += 100; + } + year += 1900; + s = s.Substring(2); + } + int month = Dec2(s, 0, ref good); + int day = Dec2(s, 2, ref good); + int hour = Dec2(s, 4, ref good); + int minute = Dec2(s, 6, ref good); + int second = 0; + int millisecond = 0; + if (isGen) { + second = Dec2(s, 8, ref good); + if (s.Length >= 12 && s[10] == '.') { + s = s.Substring(11); + foreach (char c in s) { + if (c < '0' || c > '9') { + good = false; + break; + } + } + s += "0000"; + millisecond = 10 * Dec2(s, 0, ref good) + + Dec2(s, 2, ref good) / 10; + } else if (s.Length != 10) { + good = false; + } + } else { + switch (s.Length) { + case 8: + break; + case 10: + second = Dec2(s, 8, ref good); + break; + default: + throw BadTime(type, orig); + } + } + + /* + * Parsing is finished; if any error occurred, then + * the 'good' flag has been cleared. + */ + if (!good) { + throw BadTime(type, orig); + } + + /* + * Leap seconds are not supported by .NET, so we claim + * they do not occur. + */ + if (second == 60) { + second = 59; + } + + /* + * .NET implementation performs all the checks (including + * checks on month length depending on year, as per the + * proleptic Gregorian calendar). + */ + try { + if (noTZ) { + DateTime dt = new DateTime(year, month, day, + hour, minute, second, millisecond, + DateTimeKind.Local); + return dt.ToUniversalTime(); + } + TimeSpan tzOff = new TimeSpan(tzHours, tzMinutes, 0); + if (negZ) { + tzOff = tzOff.Negate(); + } + DateTimeOffset dto = new DateTimeOffset( + year, month, day, hour, minute, second, + millisecond, tzOff); + return dto.UtcDateTime; + } catch (Exception e) { + throw BadTime(type, orig, e); + } + } + + static int Dec2(string s, int off, ref bool good) + { + if (off < 0 || off >= (s.Length - 1)) { + good = false; + return -1; + } + char c1 = s[off]; + char c2 = s[off + 1]; + if (c1 < '0' || c1 > '9' || c2 < '0' || c2 > '9') { + good = false; + return -1; + } + return 10 * (c1 - '0') + (c2 - '0'); + } + + static AsnException BadTime(int type, string s) + { + return BadTime(type, s, null); + } + + static AsnException BadTime(int type, string s, Exception e) + { + string tt = (type == UTCTime) ? "UTCTime" : "GeneralizedTime"; + string msg = String.Format("invalid {0} string: '{1}'", tt, s); + if (e == null) { + return new AsnException(msg); + } else { + return new AsnException(msg, e); + } + } + + /* =============================================================== */ + + /* + * Create a new element for a primitive value. The provided buffer + * is internally copied. + */ + public static AsnElt MakePrimitive(int tagValue, byte[] val) + { + return MakePrimitive(UNIVERSAL, tagValue, val, 0, val.Length); + } + + /* + * Create a new element for a primitive value. The provided buffer + * is internally copied. + */ + public static AsnElt MakePrimitive(int tagValue, + byte[] val, int off, int len) + { + return MakePrimitive(UNIVERSAL, tagValue, val, off, len); + } + + /* + * Create a new element for a primitive value. The provided buffer + * is internally copied. + */ + public static AsnElt MakePrimitive( + int tagClass, int tagValue, byte[] val) + { + return MakePrimitive(tagClass, tagValue, val, 0, val.Length); + } + + /* + * Create a new element for a primitive value. The provided buffer + * is internally copied. + */ + public static AsnElt MakePrimitive(int tagClass, int tagValue, + byte[] val, int off, int len) + { + byte[] nval = new byte[len]; + Array.Copy(val, off, nval, 0, len); + return MakePrimitiveInner(tagClass, tagValue, nval, 0, len); + } + + /* + * Like MakePrimitive(), but the provided array is NOT copied. + * This is for other factory methods that already allocate a + * new array. + */ + static AsnElt MakePrimitiveInner(int tagValue, byte[] val) + { + return MakePrimitiveInner(UNIVERSAL, tagValue, + val, 0, val.Length); + } + + static AsnElt MakePrimitiveInner(int tagValue, + byte[] val, int off, int len) + { + return MakePrimitiveInner(UNIVERSAL, tagValue, val, off, len); + } + + static AsnElt MakePrimitiveInner(int tagClass, int tagValue, byte[] val) + { + return MakePrimitiveInner(tagClass, tagValue, + val, 0, val.Length); + } + + static AsnElt MakePrimitiveInner(int tagClass, int tagValue, + byte[] val, int off, int len) + { + AsnElt a = new AsnElt(); + a.objBuf = new byte[len]; + Array.Copy(val, off, a.objBuf, 0, len); + a.objOff = 0; + a.objLen = -1; + a.valOff = 0; + a.valLen = len; + a.hasEncodedHeader = false; + if (tagClass < 0 || tagClass > 3) { + throw new AsnException( + "invalid tag class: " + tagClass); + } + if (tagValue < 0) { + throw new AsnException( + "invalid tag value: " + tagValue); + } + a.TagClass = tagClass; + a.TagValue = tagValue; + a.Sub = null; + return a; + } + + /* + * Create a new INTEGER value for the provided integer. + */ + public static AsnElt MakeInteger(long x) + { + if (x >= 0) { + return MakeInteger((ulong)x); + } + int k = 1; + for (long w = x; w <= -(long)0x80; w >>= 8) { + k ++; + } + byte[] v = new byte[k]; + for (long w = x; k > 0; w >>= 8) { + v[-- k] = (byte)w; + } + return MakePrimitiveInner(INTEGER, v); + } + + /* + * Create a new INTEGER value for the provided integer. + */ + public static AsnElt MakeInteger(ulong x) + { + int k = 1; + for (ulong w = x; w >= 0x80; w >>= 8) { + k ++; + } + byte[] v = new byte[k]; + for (ulong w = x; k > 0; w >>= 8) { + v[-- k] = (byte)w; + } + return MakePrimitiveInner(INTEGER, v); + } + + /* + * Create a new INTEGER value for the provided integer. The x[] + * array uses _unsigned_ big-endian encoding. + */ + public static AsnElt MakeInteger(byte[] x) + { + int xLen = x.Length; + int j = 0; + while (j < xLen && x[j] == 0x00) { + j ++; + } + if (j == xLen) { + return MakePrimitiveInner(INTEGER, new byte[] { 0x00 }); + } + byte[] v; + if (x[j] < 0x80) { + v = new byte[xLen - j]; + Array.Copy(x, j, v, 0, v.Length); + } else { + v = new byte[1 + xLen - j]; + Array.Copy(x, j, v, 1, v.Length - 1); + } + return MakePrimitiveInner(INTEGER, v); + } + + /* + * Create a new INTEGER value for the provided integer. The x[] + * array uses _signed_ big-endian encoding. + */ + public static AsnElt MakeIntegerSigned(byte[] x) + { + int xLen = x.Length; + if (xLen == 0) { + throw new AsnException( + "Invalid signed integer (empty)"); + } + int j = 0; + if (x[0] >= 0x80) { + while (j < (xLen - 1) + && x[j] == 0xFF + && x[j + 1] >= 0x80) + { + j ++; + } + } else { + while (j < (xLen - 1) + && x[j] == 0x00 + && x[j + 1] < 0x80) + { + j ++; + } + } + byte[] v = new byte[xLen - j]; + Array.Copy(x, j, v, 0, v.Length); + return MakePrimitiveInner(INTEGER, v); + } + + /* + * Create a BIT STRING from the provided value. The number of + * "unused bits" is set to 0. + */ + public static AsnElt MakeBitString(byte[] buf) + { + return MakeBitString(buf, 0, buf.Length); + } + + public static AsnElt MakeBitString(byte[] buf, int off, int len) + { + byte[] tmp = new byte[len + 1]; + Array.Copy(buf, off, tmp, 1, len); + return MakePrimitiveInner(BIT_STRING, tmp); + } + + /* + * Create a BIT STRING from the provided value. The number of + * "unused bits" is specified. + */ + public static AsnElt MakeBitString(int unusedBits, byte[] buf) + { + return MakeBitString(unusedBits, buf, 0, buf.Length); + } + + public static AsnElt MakeBitString(int unusedBits, + byte[] buf, int off, int len) + { + if (unusedBits < 0 || unusedBits > 7 + || (unusedBits != 0 && len == 0)) + { + throw new AsnException( + "Invalid number of unused bits in BIT STRING: " + + unusedBits); + } + byte[] tmp = new byte[len + 1]; + tmp[0] = (byte)unusedBits; + Array.Copy(buf, off, tmp, 1, len); + if (len > 0) { + tmp[len - 1] &= (byte)(0xFF << unusedBits); + } + return MakePrimitiveInner(BIT_STRING, tmp); + } + + /* + * Create an OCTET STRING from the provided value. + */ + public static AsnElt MakeBlob(byte[] buf) + { + return MakeBlob(buf, 0, buf.Length); + } + + public static AsnElt MakeBlob(byte[] buf, int off, int len) + { + return MakePrimitive(OCTET_STRING, buf, off, len); + } + + /* + * Create a new constructed elements, by providing the relevant + * sub-elements. + */ + public static AsnElt Make(int tagValue, params AsnElt[] subs) + { + return Make(UNIVERSAL, tagValue, subs); + } + + /* + * Create a new constructed elements, by providing the relevant + * sub-elements. + */ + public static AsnElt Make(int tagClass, int tagValue, + params AsnElt[] subs) + { + AsnElt a = new AsnElt(); + a.objBuf = null; + a.objOff = 0; + a.objLen = -1; + a.valOff = 0; + a.valLen = -1; + a.hasEncodedHeader = false; + if (tagClass < 0 || tagClass > 3) { + throw new AsnException( + "invalid tag class: " + tagClass); + } + if (tagValue < 0) { + throw new AsnException( + "invalid tag value: " + tagValue); + } + a.TagClass = tagClass; + a.TagValue = tagValue; + if (subs == null) { + a.Sub = new AsnElt[0]; + } else { + a.Sub = new AsnElt[subs.Length]; + Array.Copy(subs, 0, a.Sub, 0, subs.Length); + } + return a; + } + + /* + * Create a SET OF: sub-elements are automatically sorted by + * lexicographic order of their DER encodings. Identical elements + * are merged. + */ + public static AsnElt MakeSetOf(params AsnElt[] subs) + { + AsnElt a = new AsnElt(); + a.objBuf = null; + a.objOff = 0; + a.objLen = -1; + a.valOff = 0; + a.valLen = -1; + a.hasEncodedHeader = false; + a.TagClass = UNIVERSAL; + a.TagValue = SET; + if (subs == null) { + a.Sub = new AsnElt[0]; + } else { + SortedDictionary d = + new SortedDictionary( + COMPARER_LEXICOGRAPHIC); + foreach (AsnElt ax in subs) { + d[ax.Encode()] = ax; + } + AsnElt[] tmp = new AsnElt[d.Count]; + int j = 0; + foreach (AsnElt ax in d.Values) { + tmp[j ++] = ax; + } + a.Sub = tmp; + } + return a; + } + + static IComparer COMPARER_LEXICOGRAPHIC = + new ComparerLexicographic(); + + class ComparerLexicographic : IComparer { + + public int Compare(byte[] x, byte[] y) + { + int xLen = x.Length; + int yLen = y.Length; + int cLen = Math.Min(xLen, yLen); + for (int i = 0; i < cLen; i ++) { + if (x[i] != y[i]) { + return (int)x[i] - (int)y[i]; + } + } + return xLen - yLen; + } + } + + /* + * Wrap an element into an explicit tag. + */ + public static AsnElt MakeExplicit(int tagClass, int tagValue, AsnElt x) + { + return Make(tagClass, tagValue, x); + } + + /* + * Wrap an element into an explicit CONTEXT tag. + */ + public static AsnElt MakeExplicit(int tagValue, AsnElt x) + { + return Make(CONTEXT, tagValue, x); + } + + /* + * Apply an implicit tag to a value. The source AsnElt object + * is unmodified; a new object is returned. + */ + public static AsnElt MakeImplicit(int tagClass, int tagValue, AsnElt x) + { + if (x.Constructed) { + return Make(tagClass, tagValue, x.Sub); + } + AsnElt a = new AsnElt(); + a.objBuf = x.GetValue(out a.valOff, out a.valLen); + a.objOff = 0; + a.objLen = -1; + a.hasEncodedHeader = false; + a.TagClass = tagClass; + a.TagValue = tagValue; + a.Sub = null; + return a; + } + + public static AsnElt NULL_V = AsnElt.MakePrimitive( + NULL, new byte[0]); + + public static AsnElt BOOL_TRUE = AsnElt.MakePrimitive( + BOOLEAN, new byte[] { 0xFF }); + public static AsnElt BOOL_FALSE = AsnElt.MakePrimitive( + BOOLEAN, new byte[] { 0x00 }); + + /* + * Create an OBJECT IDENTIFIER from its string representation. + * This function tolerates extra leading zeros. + */ + public static AsnElt MakeOID(string str) + { + List r = new List(); + int n = str.Length; + long x = -1; + for (int i = 0; i < n; i ++) { + int c = str[i]; + if (c == '.') { + if (x < 0) { + throw new AsnException( + "invalid OID (empty element)"); + } + r.Add(x); + x = -1; + continue; + } + if (c < '0' || c > '9') { + throw new AsnException(String.Format( + "invalid character U+{0:X4} in OID", + c)); + } + if (x < 0) { + x = 0; + } else if (x > ((Int64.MaxValue - 9) / 10)) { + throw new AsnException("OID element overflow"); + } + x = x * (long)10 + (long)(c - '0'); + } + if (x < 0) { + throw new AsnException( + "invalid OID (empty element)"); + } + r.Add(x); + if (r.Count < 2) { + throw new AsnException( + "invalid OID (not enough elements)"); + } + if (r[0] > 2 || r[1] > 40) { + throw new AsnException( + "invalid OID (first elements out of range)"); + } + + MemoryStream ms = new MemoryStream(); + ms.WriteByte((byte)(40 * (int)r[0] + (int)r[1])); + for (int i = 2; i < r.Count; i ++) { + long v = r[i]; + if (v < 0x80) { + ms.WriteByte((byte)v); + continue; + } + int k = -7; + for (long w = v; w != 0; w >>= 7, k += 7); + ms.WriteByte((byte)(0x80 + (int)(v >> k))); + for (k -= 7; k >= 0; k -= 7) { + int z = (int)(v >> k) & 0x7F; + if (k > 0) { + z |= 0x80; + } + ms.WriteByte((byte)z); + } + } + byte[] buf = ms.ToArray(); + return MakePrimitiveInner(OBJECT_IDENTIFIER, + buf, 0, buf.Length); + } + + /* + * Create a string of the provided type and contents. The string + * type is a universal tag value for one of the string or time + * types. + */ + public static AsnElt MakeString(int type, string str) + { + VerifyChars(str.ToCharArray(), type); + byte[] buf; + switch (type) { + case NumericString: + case PrintableString: + case UTCTime: + case GeneralizedTime: + case IA5String: + case TeletexString: + buf = EncodeMono(str); + break; + case UTF8String: + buf = EncodeUTF8(str); + break; + case BMPString: + buf = EncodeUTF16(str); + break; + case UniversalString: + buf = EncodeUTF32(str); + break; + default: + throw new AsnException( + "unsupported string type: " + type); + } + return MakePrimitiveInner(type, buf); + } + + static byte[] EncodeMono(string str) + { + byte[] r = new byte[str.Length]; + int k = 0; + foreach (char c in str) { + r[k ++] = (byte)c; + } + return r; + } + + /* + * Get the code point at offset 'off' in the string. Either one + * or two 'char' slots are used; 'off' is updated accordingly. + */ + static int CodePoint(string str, ref int off) + { + int c = str[off ++]; + if (c >= 0xD800 && c < 0xDC00 && off < str.Length) { + int d = str[off]; + if (d >= 0xDC00 && d < 0xE000) { + c = ((c & 0x3FF) << 10) + + (d & 0x3FF) + 0x10000; + off ++; + } + } + return c; + } + + static byte[] EncodeUTF8(string str) + { + int k = 0; + int n = str.Length; + MemoryStream ms = new MemoryStream(); + while (k < n) { + int cp = CodePoint(str, ref k); + if (cp < 0x80) { + ms.WriteByte((byte)cp); + } else if (cp < 0x800) { + ms.WriteByte((byte)(0xC0 + (cp >> 6))); + ms.WriteByte((byte)(0x80 + (cp & 63))); + } else if (cp < 0x10000) { + ms.WriteByte((byte)(0xE0 + (cp >> 12))); + ms.WriteByte((byte)(0x80 + ((cp >> 6) & 63))); + ms.WriteByte((byte)(0x80 + (cp & 63))); + } else { + ms.WriteByte((byte)(0xF0 + (cp >> 18))); + ms.WriteByte((byte)(0x80 + ((cp >> 12) & 63))); + ms.WriteByte((byte)(0x80 + ((cp >> 6) & 63))); + ms.WriteByte((byte)(0x80 + (cp & 63))); + } + } + return ms.ToArray(); + } + + static byte[] EncodeUTF16(string str) + { + byte[] buf = new byte[str.Length << 1]; + int k = 0; + foreach (char c in str) { + buf[k ++] = (byte)(c >> 8); + buf[k ++] = (byte)c; + } + return buf; + } + + static byte[] EncodeUTF32(string str) + { + int k = 0; + int n = str.Length; + MemoryStream ms = new MemoryStream(); + while (k < n) { + int cp = CodePoint(str, ref k); + ms.WriteByte((byte)(cp >> 24)); + ms.WriteByte((byte)(cp >> 16)); + ms.WriteByte((byte)(cp >> 8)); + ms.WriteByte((byte)cp); + } + return ms.ToArray(); + } + + /* + * Create a time value of the specified type (UTCTime or + * GeneralizedTime). + */ + public static AsnElt MakeTime(int type, DateTime dt) + { + dt = dt.ToUniversalTime(); + string str; + switch (type) { + case UTCTime: + int year = dt.Year; + if (year < 1950 || year >= 2050) { + throw new AsnException(String.Format( + "cannot encode year {0} as UTCTime", + year)); + } + year = year % 100; + str = String.Format( + "{0:d2}{1:d2}{2:d2}{3:d2}{4:d2}{5:d2}Z", + year, dt.Month, dt.Day, + dt.Hour, dt.Minute, dt.Second); + break; + case GeneralizedTime: + str = String.Format( + "{0:d4}{1:d2}{2:d2}{3:d2}{4:d2}{5:d2}", + dt.Year, dt.Month, dt.Day, + dt.Hour, dt.Minute, dt.Second); + int millis = dt.Millisecond; + if (millis != 0) { + if (millis % 100 == 0) { + str = String.Format("{0}.{1:d1}", + str, millis / 100); + } else if (millis % 10 == 0) { + str = String.Format("{0}.{1:d2}", + str, millis / 10); + } else { + str = String.Format("{0}.{1:d3}", + str, millis); + } + } + str = str + "Z"; + break; + default: + throw new AsnException( + "unsupported time type: " + type); + } + return MakeString(type, str); + } + + /* + * Create a time value of the specified type (UTCTime or + * GeneralizedTime). + */ + public static AsnElt MakeTime(int type, DateTimeOffset dto) + { + return MakeTime(type, dto.UtcDateTime); + } + + /* + * Create a time value with an automatic type selection + * (UTCTime if year is in the 1950..2049 range, GeneralizedTime + * otherwise). + */ + public static AsnElt MakeTimeAuto(DateTime dt) + { + dt = dt.ToUniversalTime(); + return MakeTime((dt.Year >= 1950 && dt.Year <= 2049) + ? UTCTime : GeneralizedTime, dt); + } + + /* + * Create a time value with an automatic type selection + * (UTCTime if year is in the 1950..2049 range, GeneralizedTime + * otherwise). + */ + public static AsnElt MakeTimeAuto(DateTimeOffset dto) + { + return MakeTimeAuto(dto.UtcDateTime); + } +} + +} diff --git a/ExternalLibs/AsnElt/AsnElt/AsnException.cs b/ExternalLibs/AsnElt/AsnElt/AsnException.cs new file mode 100644 index 00000000..852bd724 --- /dev/null +++ b/ExternalLibs/AsnElt/AsnElt/AsnException.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; + +namespace Asn1 { + +public class AsnException : IOException { + + public AsnException(string message) + : base(message) + { + } + + public AsnException(string message, Exception nested) + : base(message, nested) + { + } +} + +} diff --git a/ExternalLibs/AsnElt/AsnElt/AsnIO.cs b/ExternalLibs/AsnElt/AsnElt/AsnIO.cs new file mode 100644 index 00000000..04883ece --- /dev/null +++ b/ExternalLibs/AsnElt/AsnElt/AsnIO.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Asn1 { + +public static class AsnIO { + + public static byte[] FindDER(byte[] buf) + { + return FindBER(buf, true); + } + + public static byte[] FindBER(byte[] buf) + { + return FindBER(buf, false); + } + + /* + * Find a BER/DER object in the provided buffer. If the data is + * not already in the right format, conversion to string then + * Base64 decoding is attempted; in the latter case, PEM headers + * are detected and skipped. In any case, the returned buffer + * must begin with a well-formed tag and length, corresponding to + * the object length. + * + * If 'strictDER' is true, then the function furthermore insists + * on the object to use a defined DER length. + * + * The returned buffer may be the source buffer itself, or a newly + * allocated buffer. + * + * On error, null is returned. + */ + public static byte[] FindBER(byte[] buf, bool strictDER) + { + string pemType = null; + return FindBER(buf, strictDER, out pemType); + } + + /* + * Find a BER/DER object in the provided buffer. If the data is + * not already in the right format, conversion to string then + * Base64 decoding is attempted; in the latter case, PEM headers + * are detected and skipped. In any case, the returned buffer + * must begin with a well-formed tag and length, corresponding to + * the object length. + * + * If 'strictDER' is true, then the function furthermore insists + * on the object to use a defined DER length. + * + * If the source was detected to use PEM, then the object type + * indicated by the PEM header is written in 'pemType'; otherwise, + * that variable is set to null. + * + * The returned buffer may be the source buffer itself, or a newly + * allocated buffer. + * + * On error, null is returned. + */ + public static byte[] FindBER(byte[] buf, + bool strictDER, out string pemType) + { + pemType = null; + + /* + * If it is already (from the outside) a BER object, + * return it. + */ + if (LooksLikeBER(buf, strictDER)) { + return buf; + } + + /* + * Convert the blob to a string. We support UTF-16 with + * and without a BOM, UTF-8 with and without a BOM, and + * ASCII-compatible encodings. Non-ASCII characters get + * truncated. + */ + if (buf.Length < 3) { + return null; + } + string str = null; + if ((buf.Length & 1) == 0) { + if (buf[0] == 0xFE && buf[1] == 0xFF) { + // Starts with big-endian UTF-16 BOM + str = ConvertBi(buf, 2, true); + } else if (buf[0] == 0xFF && buf[1] == 0xFE) { + // Starts with little-endian UTF-16 BOM + str = ConvertBi(buf, 2, false); + } else if (buf[0] == 0) { + // First byte is 0 -> big-endian UTF-16 + str = ConvertBi(buf, 0, true); + } else if (buf[1] == 0) { + // Second byte is 0 -> little-endian UTF-16 + str = ConvertBi(buf, 0, false); + } + } + if (str == null) { + if (buf[0] == 0xEF + && buf[1] == 0xBB + && buf[2] == 0xBF) + { + // Starts with UTF-8 BOM + str = ConvertMono(buf, 3); + } else { + // Assumed ASCII-compatible mono-byte encoding + str = ConvertMono(buf, 0); + } + } + if (str == null) { + return null; + } + + /* + * Try to detect a PEM header and footer; if we find both + * then we remove both, keeping only the characters that + * occur in between. + */ + int p = str.IndexOf("-----BEGIN "); + int q = str.IndexOf("-----END "); + if (p >= 0 && q >= 0) { + p += 11; + int r = str.IndexOf((char)10, p) + 1; + int px = str.IndexOf('-', p); + if (px > 0 && px < r && r > 0 && r <= q) { + pemType = string.Copy(str.Substring(p, px - p)); + str = str.Substring(r, q - r); + } + } + + /* + * Convert from Base64. + */ + try { + buf = Convert.FromBase64String(str); + if (LooksLikeBER(buf, strictDER)) { + return buf; + } + } catch { + // ignored: not Base64 + } + + /* + * Decoding failed. + */ + return null; + } + + /* =============================================================== */ + + /* + * Decode a tag; returned value is true on success, false otherwise. + * On success, 'off' is updated to point to the first byte after + * the tag. + */ + static bool DecodeTag(byte[] buf, int lim, ref int off) + { + int p = off; + if (p >= lim) { + return false; + } + int v = buf[p ++]; + if ((v & 0x1F) == 0x1F) { + do { + if (p >= lim) { + return false; + } + v = buf[p ++]; + } while ((v & 0x80) != 0); + } + off = p; + return true; + } + + /* + * Decode a BER length. Returned value is: + * -2 no decodable length + * -1 indefinite length + * 0+ definite length + * If a definite or indefinite length could be decoded, then 'off' + * is updated to point to the first byte after the length. + */ + static int DecodeLength(byte[] buf, int lim, ref int off) + { + int p = off; + if (p >= lim) { + return -2; + } + int v = buf[p ++]; + if (v < 0x80) { + off = p; + return v; + } else if (v == 0x80) { + off = p; + return -1; + } + v &= 0x7F; + if ((lim - p) < v) { + return -2; + } + int acc = 0; + while (v -- > 0) { + if (acc > 0x7FFFFF) { + return -2; + } + acc = (acc << 8) + buf[p ++]; + } + off = p; + return acc; + } + + /* + * Get the length, in bytes, of the object in the provided + * buffer. The object begins at offset 'off' but does not extend + * farther than offset 'lim'. If no such BER object can be + * decoded, then -1 is returned. The returned length includes + * that of the tag and length fields. + */ + static int BERLength(byte[] buf, int lim, int off) + { + int orig = off; + if (!DecodeTag(buf, lim, ref off)) { + return -1; + } + int len = DecodeLength(buf, lim, ref off); + if (len >= 0) { + if (len > (lim - off)) { + return -1; + } + return off + len - orig; + } else if (len < -1) { + return -1; + } + + /* + * Indefinite length: we must do some recursive exploration. + * End of structure is marked by a "null tag": object has + * total length 2 and its tag byte is 0. + */ + for (;;) { + int slen = BERLength(buf, lim, off); + if (slen < 0) { + return -1; + } + off += slen; + if (slen == 2 && buf[off] == 0) { + return off - orig; + } + } + } + + static bool LooksLikeBER(byte[] buf, bool strictDER) + { + return LooksLikeBER(buf, 0, buf.Length, strictDER); + } + + static bool LooksLikeBER(byte[] buf, int off, int len, bool strictDER) + { + int lim = off + len; + int objLen = BERLength(buf, lim, off); + if (objLen != len) { + return false; + } + if (strictDER) { + DecodeTag(buf, lim, ref off); + return DecodeLength(buf, lim, ref off) >= 0; + } else { + return true; + } + } + + static string ConvertMono(byte[] buf, int off) + { + int len = buf.Length - off; + char[] tc = new char[len]; + for (int i = 0; i < len; i ++) { + int v = buf[off + i]; + if (v < 1 || v > 126) { + v = '?'; + } + tc[i] = (char)v; + } + return new string(tc); + } + + static string ConvertBi(byte[] buf, int off, bool be) + { + int len = buf.Length - off; + if ((len & 1) != 0) { + return null; + } + len >>= 1; + char[] tc = new char[len]; + for (int i = 0; i < len; i ++) { + int b0 = buf[off + (i << 1) + 0]; + int b1 = buf[off + (i << 1) + 1]; + int v = be ? ((b0 << 8) + b1) : (b0 + (b1 << 8)); + if (v < 1 || v > 126) { + v = '?'; + } + tc[i] = (char)v; + } + return new string(tc); + } +} + +} diff --git a/ExternalLibs/AsnElt/AsnElt/AsnOID.cs b/ExternalLibs/AsnElt/AsnElt/AsnOID.cs new file mode 100644 index 00000000..19739f5a --- /dev/null +++ b/ExternalLibs/AsnElt/AsnElt/AsnOID.cs @@ -0,0 +1,294 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Asn1 { + +public class AsnOID { + + static Dictionary OIDToName = + new Dictionary(); + static Dictionary NameToOID = + new Dictionary(); + + static AsnOID() + { + /* + * From RFC 5280, PKIX1Explicit88 module. + */ + Reg("1.3.6.1.5.5.7", "id-pkix"); + Reg("1.3.6.1.5.5.7.1", "id-pe"); + Reg("1.3.6.1.5.5.7.2", "id-qt"); + Reg("1.3.6.1.5.5.7.3", "id-kp"); + Reg("1.3.6.1.5.5.7.48", "id-ad"); + Reg("1.3.6.1.5.5.7.2.1", "id-qt-cps"); + Reg("1.3.6.1.5.5.7.2.2", "id-qt-unotice"); + Reg("1.3.6.1.5.5.7.48.1", "id-ad-ocsp"); + Reg("1.3.6.1.5.5.7.48.2", "id-ad-caIssuers"); + Reg("1.3.6.1.5.5.7.48.3", "id-ad-timeStamping"); + Reg("1.3.6.1.5.5.7.48.5", "id-ad-caRepository"); + + Reg("2.5.4", "id-at"); + Reg("2.5.4.41", "id-at-name"); + Reg("2.5.4.4", "id-at-surname"); + Reg("2.5.4.42", "id-at-givenName"); + Reg("2.5.4.43", "id-at-initials"); + Reg("2.5.4.44", "id-at-generationQualifier"); + Reg("2.5.4.3", "id-at-commonName"); + Reg("2.5.4.7", "id-at-localityName"); + Reg("2.5.4.8", "id-at-stateOrProvinceName"); + Reg("2.5.4.10", "id-at-organizationName"); + Reg("2.5.4.11", "id-at-organizationalUnitName"); + Reg("2.5.4.12", "id-at-title"); + Reg("2.5.4.46", "id-at-dnQualifier"); + Reg("2.5.4.6", "id-at-countryName"); + Reg("2.5.4.5", "id-at-serialNumber"); + Reg("2.5.4.65", "id-at-pseudonym"); + Reg("0.9.2342.19200300.100.1.25", "id-domainComponent"); + + Reg("1.2.840.113549.1.9", "pkcs-9"); + Reg("1.2.840.113549.1.9.1", "id-emailAddress"); + + /* + * From RFC 5280, PKIX1Implicit88 module. + */ + Reg("2.5.29", "id-ce"); + Reg("2.5.29.35", "id-ce-authorityKeyIdentifier"); + Reg("2.5.29.14", "id-ce-subjectKeyIdentifier"); + Reg("2.5.29.15", "id-ce-keyUsage"); + Reg("2.5.29.16", "id-ce-privateKeyUsagePeriod"); + Reg("2.5.29.32", "id-ce-certificatePolicies"); + Reg("2.5.29.33", "id-ce-policyMappings"); + Reg("2.5.29.17", "id-ce-subjectAltName"); + Reg("2.5.29.18", "id-ce-issuerAltName"); + Reg("2.5.29.9", "id-ce-subjectDirectoryAttributes"); + Reg("2.5.29.19", "id-ce-basicConstraints"); + Reg("2.5.29.30", "id-ce-nameConstraints"); + Reg("2.5.29.36", "id-ce-policyConstraints"); + Reg("2.5.29.31", "id-ce-cRLDistributionPoints"); + Reg("2.5.29.37", "id-ce-extKeyUsage"); + + Reg("2.5.29.37.0", "anyExtendedKeyUsage"); + Reg("1.3.6.1.5.5.7.3.1", "id-kp-serverAuth"); + Reg("1.3.6.1.5.5.7.3.2", "id-kp-clientAuth"); + Reg("1.3.6.1.5.5.7.3.3", "id-kp-codeSigning"); + Reg("1.3.6.1.5.5.7.3.4", "id-kp-emailProtection"); + Reg("1.3.6.1.5.5.7.3.8", "id-kp-timeStamping"); + Reg("1.3.6.1.5.5.7.3.9", "id-kp-OCSPSigning"); + + Reg("2.5.29.54", "id-ce-inhibitAnyPolicy"); + Reg("2.5.29.46", "id-ce-freshestCRL"); + Reg("1.3.6.1.5.5.7.1.1", "id-pe-authorityInfoAccess"); + Reg("1.3.6.1.5.5.7.1.11", "id-pe-subjectInfoAccess"); + Reg("2.5.29.20", "id-ce-cRLNumber"); + Reg("2.5.29.28", "id-ce-issuingDistributionPoint"); + Reg("2.5.29.27", "id-ce-deltaCRLIndicator"); + Reg("2.5.29.21", "id-ce-cRLReasons"); + Reg("2.5.29.29", "id-ce-certificateIssuer"); + Reg("2.5.29.23", "id-ce-holdInstructionCode"); + Reg("2.2.840.10040.2", "WRONG-holdInstruction"); + Reg("2.2.840.10040.2.1", "WRONG-id-holdinstruction-none"); + Reg("2.2.840.10040.2.2", "WRONG-id-holdinstruction-callissuer"); + Reg("2.2.840.10040.2.3", "WRONG-id-holdinstruction-reject"); + Reg("2.5.29.24", "id-ce-invalidityDate"); + + /* + * These are the "right" OID. RFC 5280 mistakenly defines + * the first OID element as "2". + */ + Reg("1.2.840.10040.2", "holdInstruction"); + Reg("1.2.840.10040.2.1", "id-holdinstruction-none"); + Reg("1.2.840.10040.2.2", "id-holdinstruction-callissuer"); + Reg("1.2.840.10040.2.3", "id-holdinstruction-reject"); + + /* + * From PKCS#1. + */ + Reg("1.2.840.113549.1.1", "pkcs-1"); + Reg("1.2.840.113549.1.1.1", "rsaEncryption"); + Reg("1.2.840.113549.1.1.7", "id-RSAES-OAEP"); + Reg("1.2.840.113549.1.1.9", "id-pSpecified"); + Reg("1.2.840.113549.1.1.10", "id-RSASSA-PSS"); + Reg("1.2.840.113549.1.1.2", "md2WithRSAEncryption"); + Reg("1.2.840.113549.1.1.4", "md5WithRSAEncryption"); + Reg("1.2.840.113549.1.1.5", "sha1WithRSAEncryption"); + Reg("1.2.840.113549.1.1.11", "sha256WithRSAEncryption"); + Reg("1.2.840.113549.1.1.12", "sha384WithRSAEncryption"); + Reg("1.2.840.113549.1.1.13", "sha512WithRSAEncryption"); + Reg("1.3.14.3.2.26", "id-sha1"); + Reg("1.2.840.113549.2.2", "id-md2"); + Reg("1.2.840.113549.2.5", "id-md5"); + Reg("1.2.840.113549.1.1.8", "id-mgf1"); + + /* + * From NIST: http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html + */ + Reg("2.16.840.1.101.3", "csor"); + Reg("2.16.840.1.101.3.4", "nistAlgorithms"); + Reg("2.16.840.1.101.3.4.0", "csorModules"); + Reg("2.16.840.1.101.3.4.0.1", "aesModule1"); + + Reg("2.16.840.1.101.3.4.1", "aes"); + Reg("2.16.840.1.101.3.4.1.1", "id-aes128-ECB"); + Reg("2.16.840.1.101.3.4.1.2", "id-aes128-CBC"); + Reg("2.16.840.1.101.3.4.1.3", "id-aes128-OFB"); + Reg("2.16.840.1.101.3.4.1.4", "id-aes128-CFB"); + Reg("2.16.840.1.101.3.4.1.5", "id-aes128-wrap"); + Reg("2.16.840.1.101.3.4.1.6", "id-aes128-GCM"); + Reg("2.16.840.1.101.3.4.1.7", "id-aes128-CCM"); + Reg("2.16.840.1.101.3.4.1.8", "id-aes128-wrap-pad"); + Reg("2.16.840.1.101.3.4.1.21", "id-aes192-ECB"); + Reg("2.16.840.1.101.3.4.1.22", "id-aes192-CBC"); + Reg("2.16.840.1.101.3.4.1.23", "id-aes192-OFB"); + Reg("2.16.840.1.101.3.4.1.24", "id-aes192-CFB"); + Reg("2.16.840.1.101.3.4.1.25", "id-aes192-wrap"); + Reg("2.16.840.1.101.3.4.1.26", "id-aes192-GCM"); + Reg("2.16.840.1.101.3.4.1.27", "id-aes192-CCM"); + Reg("2.16.840.1.101.3.4.1.28", "id-aes192-wrap-pad"); + Reg("2.16.840.1.101.3.4.1.41", "id-aes256-ECB"); + Reg("2.16.840.1.101.3.4.1.42", "id-aes256-CBC"); + Reg("2.16.840.1.101.3.4.1.43", "id-aes256-OFB"); + Reg("2.16.840.1.101.3.4.1.44", "id-aes256-CFB"); + Reg("2.16.840.1.101.3.4.1.45", "id-aes256-wrap"); + Reg("2.16.840.1.101.3.4.1.46", "id-aes256-GCM"); + Reg("2.16.840.1.101.3.4.1.47", "id-aes256-CCM"); + Reg("2.16.840.1.101.3.4.1.48", "id-aes256-wrap-pad"); + + Reg("2.16.840.1.101.3.4.2", "hashAlgs"); + Reg("2.16.840.1.101.3.4.2.1", "id-sha256"); + Reg("2.16.840.1.101.3.4.2.2", "id-sha384"); + Reg("2.16.840.1.101.3.4.2.3", "id-sha512"); + Reg("2.16.840.1.101.3.4.2.4", "id-sha224"); + Reg("2.16.840.1.101.3.4.2.5", "id-sha512-224"); + Reg("2.16.840.1.101.3.4.2.6", "id-sha512-256"); + + Reg("2.16.840.1.101.3.4.3", "sigAlgs"); + Reg("2.16.840.1.101.3.4.3.1", "id-dsa-with-sha224"); + Reg("2.16.840.1.101.3.4.3.2", "id-dsa-with-sha256"); + + Reg("1.2.840.113549", "rsadsi"); + Reg("1.2.840.113549.2", "digestAlgorithm"); + Reg("1.2.840.113549.2.7", "id-hmacWithSHA1"); + Reg("1.2.840.113549.2.8", "id-hmacWithSHA224"); + Reg("1.2.840.113549.2.9", "id-hmacWithSHA256"); + Reg("1.2.840.113549.2.10", "id-hmacWithSHA384"); + Reg("1.2.840.113549.2.11", "id-hmacWithSHA512"); + + /* + * From X9.57: http://oid-info.com/get/1.2.840.10040.4 + */ + Reg("1.2.840.10040.4", "x9algorithm"); + Reg("1.2.840.10040.4", "x9cm"); + Reg("1.2.840.10040.4.1", "dsa"); + Reg("1.2.840.10040.4.3", "dsa-with-sha1"); + + /* + * From SEC: http://oid-info.com/get/1.3.14.3.2 + */ + Reg("1.3.14.3.2.2", "md4WithRSA"); + Reg("1.3.14.3.2.3", "md5WithRSA"); + Reg("1.3.14.3.2.4", "md4WithRSAEncryption"); + Reg("1.3.14.3.2.12", "dsaSEC"); + Reg("1.3.14.3.2.13", "dsaWithSHASEC"); + Reg("1.3.14.3.2.27", "dsaWithSHA1SEC"); + + /* + * From Microsoft: http://oid-info.com/get/1.3.6.1.4.1.311.20.2 + */ + Reg("1.3.6.1.4.1.311.20.2", "ms-certType"); + Reg("1.3.6.1.4.1.311.20.2.2", "ms-smartcardLogon"); + Reg("1.3.6.1.4.1.311.20.2.3", "ms-UserPrincipalName"); + Reg("1.3.6.1.4.1.311.20.2.3", "ms-UPN"); + } + + static void Reg(string oid, string name) + { + if (!OIDToName.ContainsKey(oid)) { + OIDToName.Add(oid, name); + } + string nn = Normalize(name); + if (NameToOID.ContainsKey(nn)) { + throw new Exception("OID name collision: " + nn); + } + NameToOID.Add(nn, oid); + + /* + * Many names start with 'id-??-' and we want to support + * the short names (without that prefix) as aliases. But + * we must take care of some collisions on short names. + */ + if (name.StartsWith("id-") + && name.Length >= 7 && name[5] == '-') + { + if (name.StartsWith("id-ad-")) { + Reg(oid, name.Substring(6) + "-IA"); + } else if (name.StartsWith("id-kp-")) { + Reg(oid, name.Substring(6) + "-EKU"); + } else { + Reg(oid, name.Substring(6)); + } + } + } + + static string Normalize(string name) + { + StringBuilder sb = new StringBuilder(); + foreach (char c in name) { + int d = (int)c; + if (d <= 32 || d == '-') { + continue; + } + if (d >= 'A' && d <= 'Z') { + d += 'a' - 'A'; + } + sb.Append((char)c); + } + return sb.ToString(); + } + + public static string ToName(string oid) + { + return OIDToName.ContainsKey(oid) ? OIDToName[oid] : oid; + } + + public static string ToOID(string name) + { + if (IsNumericOID(name)) { + return name; + } + string nn = Normalize(name); + if (!NameToOID.ContainsKey(nn)) { + throw new AsnException( + "unrecognized OID name: " + name); + } + return NameToOID[nn]; + } + + public static bool IsNumericOID(string oid) + { + /* + * An OID is in numeric format if: + * -- it contains only digits and dots + * -- it does not start or end with a dot + * -- it does not contain two consecutive dots + * -- it contains at least one dot + */ + foreach (char c in oid) { + if (!(c >= '0' && c <= '9') && c != '.') { + return false; + } + } + if (oid.StartsWith(".") || oid.EndsWith(".")) { + return false; + } + if (oid.IndexOf("..") >= 0) { + return false; + } + if (oid.IndexOf('.') < 0) { + return false; + } + return true; + } +} + +} diff --git a/ExternalLibs/AsnElt/AsnElt/LICENSE.txt b/ExternalLibs/AsnElt/AsnElt/LICENSE.txt new file mode 100644 index 00000000..8264cabc --- /dev/null +++ b/ExternalLibs/AsnElt/AsnElt/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Thomas Pornin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Src/Fido2/AttestationFormat/AndroidKey.cs b/Src/Fido2/AttestationFormat/AndroidKey.cs index 57f76639..f36b324b 100644 --- a/Src/Fido2/AttestationFormat/AndroidKey.cs +++ b/Src/Fido2/AttestationFormat/AndroidKey.cs @@ -1,7 +1,9 @@ using System; +using System.Diagnostics; using System.Linq; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +using Asn1; using PeterO.Cbor; namespace Fido2NetLib.AttestationFormat @@ -24,196 +26,130 @@ public static byte[] AttestationExtensionBytes(X509ExtensionCollection exts) return null; } - public static byte[] GetASN1ObjectAtIndex(byte[] attExtBytes, int index) - { - // https://developer.android.com/training/articles/security-key-attestation#certificate_schema - // This function returns an entry from the KeyDescription at index - if (null == attExtBytes || 0 == attExtBytes.Length || attExtBytes.Length > ushort.MaxValue) - throw new Fido2VerificationException("Invalid attExtBytes signature value"); - var offset = 0; - var derSequence = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1); - - // expecting to start with 0x30 indicating SEQUENCE - if (null == derSequence || 0x30 != derSequence[0]) - throw new Fido2VerificationException("attExtBytes signature not a valid DER sequence"); - - // next is length of all the items in the sequence - var dataLen = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1); - if (null == dataLen) - throw new Fido2VerificationException("attExtBytes signature has invalid length"); - - // if data is more than 127 bytes, the length is encoded in long form - // TODO : Why is longLen never used ? - var longForm = (dataLen[0] > 0x7f); - var longLen = 0; - if (true == longForm) - { - var longLenByte = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1); - if (null == longLenByte) - throw new Fido2VerificationException("attExtBytes signature has invalid long form length"); - longLen = longLenByte[0]; - longLen &= (1 << 7); - } - - // walk through each sequence entry until we get to the requested index - for (var i = 0; i < index; i++) - { - var derId = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1); - if (null == derId) - throw new Fido2VerificationException("Ran out of bytes in attExtBytes sequence without finding the first octet string"); - var lenValue = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1); - if (null == lenValue) - throw new Fido2VerificationException("attExtBytes lenValue invalid"); - if (0 < lenValue[0]) - { - var value = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, lenValue[0]); - if (null == value) - throw new Fido2VerificationException("Ran out of bytes in attExtBytes sequence without finding the first octet string"); - } - } - // skip the identifier of the requested item - var asn1Id = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1); - if (null == asn1Id) - throw new Fido2VerificationException("Ran out of bytes in attExtBytes sequence without finding the first octet string"); - // get length of requested item - var lenAsn1value = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1); - if (null == lenAsn1value) - throw new Fido2VerificationException("lenAttestationChallenge version length invalid"); - // return byte array containing the requested item - return AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, lenAsn1value[0]); - } - public static byte[] GetAttestationChallenge(byte[] attExtBytes) { + var keyDescription = AsnElt.Decode(attExtBytes); // https://developer.android.com/training/articles/security-key-attestation#certificate_schema // attestationChallenge at index 4 - return GetASN1ObjectAtIndex(attExtBytes, 4); - } - - public static byte[] GetSoftwareEnforcedAuthorizationList(byte[] attExtBytes) - { - // https://developer.android.com/training/articles/security-key-attestation#certificate_schema - // softwareEnforced AuthorizationList at index 6 - return GetASN1ObjectAtIndex(attExtBytes, 6); - } - - public static byte[] GetTeeEnforcedAuthorizationList(byte[] attExtBytes) - { - // https://developer.android.com/training/articles/security-key-attestation#certificate_schema - // teeEnforced AuthorizationList at index 7 - return GetASN1ObjectAtIndex(attExtBytes, 7); + return keyDescription.GetSub(4).GetOctetString(); } 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 - var software = GetSoftwareEnforcedAuthorizationList(attExtBytes); - var tee = GetTeeEnforcedAuthorizationList(attExtBytes); - var ignore = -1; - // allApplications is tag 600, and should not be found in either list - return (GetDERTagValue(software, 600, ref ignore) && GetDERTagValue(tee, 600, ref ignore)); - } + var keyDescription = AsnElt.Decode(attExtBytes); - public static bool GetDERTagValue(byte[] authList, int tag, ref int result) - { - // https://developer.android.com/training/articles/security-key-attestation#certificate_schema - // walk authorizationList sequence looking for an item with requested tag - // if tag is found, return true and set the result value to the found int value - // the only two items we are expecting to find are purpose and origin - // which are set of integer or integer, so int result is ok for now - // if entire list is walked and tag is not found, return false - for (var i = 0; i < authList.Length;) + var softwareEnforced = keyDescription.GetSub(6).Sub; + foreach (AsnElt s in softwareEnforced) { - // expecting to see first byte indicting the attribute is of class 2, and constructed value - // first two bits are the class, expecting to see 10 - // third bit is primative (0) or constructed (1) - if (false == ((authList[i] & 0xA0) == 0xA0)) - throw new Fido2VerificationException("Expected class 2 constructed ASN.1 value"); - - int foundTag; - // if the tag value is below 0x1F (11111), the value is stored in the remaining 5 bits of the first byte - if (false == ((authList[i] & 0x1F) == 0x1F)) + switch (s.TagValue) { - foundTag = (authList[i] & ~0xA0); - i++; - } - // otherwise, if the tag value is 0x3FFF (11111111111111) or below - // the value is stored in the lower 7 bits of the second byte, and the lower 7 bits of the third byte - // this is signified by the high order bit set in the second byte, but not set in the third byte - else if (((authList[i + 1] & 0x80) == 0x80) && ((authList[i + 2] & 0x80) == 0x0)) - { - // take the lower 7 bits in the second byte, shift them left 7 positions - // then add the lower 7 bits of the third byte to get the tag value - // Welcome to Abstract Syntax Notation One - foundTag = ((authList[i + 1] & ~0x80) << 7) + (authList[i + 2]); - i += 3; - } - else - { - throw new Fido2VerificationException("Expecting ASN.1 tag less than 0x3FFF"); - } - // if the tag we found is the one that we are looking for, get the value - if (tag == foundTag) - { - // 5 bytes will be remaining if this a set (0x31), advance to the integer - if (5 == authList[i] && 0x31 == authList[i + 1]) - i += 2; - // for our purposes, we should see that there are 3 bytes remaining after this one - // the second byte should be 2 indicating the value is an integer - // and the third byte is the length in bytes, which again, for our purposes, should be one - if (3 == authList[i] && 2 == authList[i + 1] && 1 == authList[i + 2]) - { - // value is stored in the 4th byte, no need to go any further, we have our result - result = authList[i + 3]; + case 600: return true; - } - else - { - throw new Fido2VerificationException("Unexpected byte sequence found fetching ASN.1 integer value"); - } + default: + break; } + } - // if we didn't find the tag we were looking for, advance the index - // by the the size in bytes of the current tag plus one byte for the size - else if (i < authList.Length) + var teeEnforced = keyDescription.GetSub(7).Sub; + foreach (AsnElt s in teeEnforced) + { + switch (s.TagValue) { - i += authList[i] + 1; + case 600: + return true; + default: + break; } } - // ran out of bytes without finding the tag we were looking for + return false; } public static bool IsOriginGenerated(byte[] attExtBytes) { - var tagValue = -1; + long softwareEnforcedOriginValue = 0; + long teeEnforcedOriginValue = 0; // https://developer.android.com/training/articles/security-key-attestation#certificate_schema // origin tag is 702 - var result = GetDERTagValue(GetTeeEnforcedAuthorizationList(attExtBytes), 702, ref tagValue); - return (0 == tagValue && true == result); + var keyDescription = AsnElt.Decode(attExtBytes); + var softwareEnforced = keyDescription.GetSub(6).Sub; + foreach (AsnElt s in softwareEnforced) + { + switch (s.TagValue) + { + case 702: + softwareEnforcedOriginValue = s.Sub[0].GetInteger(); + break; + default: + break; + } + } + + var teeEnforced = keyDescription.GetSub(7).Sub; + foreach (AsnElt s in teeEnforced) + { + switch (s.TagValue) + { + case 702: + teeEnforcedOriginValue = s.Sub[0].GetInteger(); + break; + default: + break; + } + } + + return (0 == softwareEnforcedOriginValue && 0 == teeEnforcedOriginValue); } public static bool IsPurposeSign(byte[] attExtBytes) { - var tagValue = -1; + long softwareEnforcedPurposeValue = 2; + long teeEnforcedPurposeValue = 2; // https://developer.android.com/training/articles/security-key-attestation#certificate_schema // purpose tag is 1 - var result = GetDERTagValue(GetTeeEnforcedAuthorizationList(attExtBytes), 1, ref tagValue); - return (2 == tagValue && true == result); + var keyDescription = AsnElt.Decode(attExtBytes); + var softwareEnforced = keyDescription.GetSub(6).Sub; + foreach (AsnElt s in softwareEnforced) + { + switch (s.TagValue) + { + case 1: + softwareEnforcedPurposeValue = s.Sub[0].Sub[0].GetInteger(); + break; + default: + break; + } + } + + var teeEnforced = keyDescription.GetSub(7).Sub; + foreach (AsnElt s in teeEnforced) + { + switch (s.TagValue) + { + case 1: + teeEnforcedPurposeValue = s.Sub[0].Sub[0].GetInteger(); + break; + default: + break; + } + } + + return (2 == softwareEnforcedPurposeValue && 2 == teeEnforcedPurposeValue); } public override void Verify() { // Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields if (0 == attStmt.Keys.Count || 0 == attStmt.Values.Count) - throw new Fido2VerificationException("Attestation format packed must have attestation statement"); + throw new Fido2VerificationException("Attestation format android-key must have attestation statement"); if (null == Sig || CBORType.ByteString != Sig.Type || 0 == Sig.GetByteString().Length) - throw new Fido2VerificationException("Invalid packed attestation signature"); + throw new Fido2VerificationException("Invalid android-key attestation signature"); // 2a. Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash // using the attestation public key in attestnCert with the algorithm specified in alg - if (null == X5c && CBORType.Array != X5c.Type && 0 == X5c.Count) + if (null == X5c || CBORType.Array != X5c.Type || 0 == X5c.Count) throw new Fido2VerificationException("Malformed x5c in android-key attestation"); if (null == X5c.Values || 0 == X5c.Values.Count || @@ -234,24 +170,41 @@ public override void Verify() } if (null == Alg || CBORType.Number != Alg.Type || false == CryptoUtils.algMap.ContainsKey(Alg.AsInt32())) - throw new Fido2VerificationException("Invalid attestation algorithm"); - if (true != androidKeyPubKey.VerifyData(Data, - CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), androidKeyPubKey.KeySize), - CryptoUtils.algMap[Alg.AsInt32()])) - throw new Fido2VerificationException("Invalid android key signature"); + throw new Fido2VerificationException("Invalid android key attestation algorithm"); + + byte[] ecsig; + try + { + ecsig = CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), androidKeyPubKey.KeySize); + } + catch (Exception ex) + { + throw new Fido2VerificationException("Failed to decode android key attestation signature from ASN.1 encoded form", ex); + } + + if (true != androidKeyPubKey.VerifyData(Data, ecsig, CryptoUtils.algMap[Alg.AsInt32()])) + throw new Fido2VerificationException("Invalid android key attestation signature"); // Verify that the public key in the first certificate in x5c matches the credentialPublicKey in the attestedCredentialData in authenticatorData. if (true != AuthData.AttestedCredentialData.CredentialPublicKey.Verify(Data, Sig.GetByteString())) - throw new Fido2VerificationException("Invalid android key signature"); + throw new Fido2VerificationException("Incorrect credentialPublicKey in android key attestation"); // Verify that in the attestation certificate extension data: var attExtBytes = AttestationExtensionBytes(androidKeyCert.Extensions); + if (null == attExtBytes) + throw new Fido2VerificationException("Android key attestation certificate contains no AttestationRecord extension"); // 1. The value of the attestationChallenge field is identical to clientDataHash. - var attestationChallenge = GetAttestationChallenge(attExtBytes); - if (false == clientDataHash.SequenceEqual(attestationChallenge)) - throw new Fido2VerificationException("Mismatched between attestationChallenge and hashedClientDataJson verifying android key attestation certificate extension"); - + try + { + var attestationChallenge = GetAttestationChallenge(attExtBytes); + if (false == clientDataHash.SequenceEqual(attestationChallenge)) + throw new Fido2VerificationException("Mismatch between attestationChallenge and hashedClientDataJson verifying android key attestation certificate extension"); + } + catch (Exception) + { + throw new Fido2VerificationException("Malformed android key AttestationRecord extension verifying android key attestation certificate extension"); + } // 2. The AuthorizationList.allApplications field is not present, since PublicKeyCredential MUST be bound to the RP ID. if (true == FindAllApplicationsField(attExtBytes)) throw new Fido2VerificationException("Found all applications field in android key attestation certificate extension"); diff --git a/Src/Fido2/AttestationFormat/FidoU2f.cs b/Src/Fido2/AttestationFormat/FidoU2f.cs index 11111c18..4c8d4719 100644 --- a/Src/Fido2/AttestationFormat/FidoU2f.cs +++ b/Src/Fido2/AttestationFormat/FidoU2f.cs @@ -111,7 +111,7 @@ public override void Verify() { ecsig = CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), pubKey.KeySize); } - catch (Fido2VerificationException ex) + catch (Exception ex) { throw new Fido2VerificationException("Failed to decode fido-u2f attestation signature from ASN.1 encoded form", ex); } diff --git a/Src/Fido2/CryptoUtils.cs b/Src/Fido2/CryptoUtils.cs index 9b51a5be..fe128d1b 100644 --- a/Src/Fido2/CryptoUtils.cs +++ b/Src/Fido2/CryptoUtils.cs @@ -2,11 +2,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +using Asn1; using Fido2NetLib.Objects; -using LipingShare.LCLib.Asn1Processor; namespace Fido2NetLib { @@ -60,106 +59,95 @@ public static HashAlgorithm GetHasher(HashAlgorithmName hashName) {(int) COSE.Algorithm.EdDSA, HashAlgorithmName.SHA512 } }; - public static byte[] GetEcDsaSigValue(BinaryReader reader) + public static byte[] SigFromEcDsaSig(byte[] ecDsaSig, int keySize) { - // First byte should be DER int marker - var derInt = reader.ReadByte(); - if (0x02 != derInt) - throw new Fido2VerificationException("ECDsa signature coordinate sequence does not contain DER integer value"); // DER INTEGER + var decoded = AsnElt.Decode(ecDsaSig); + var r = decoded.Sub[0].GetOctetString(); + var s = decoded.Sub[1].GetOctetString(); + + // .NET requires IEEE P-1363 fixed size unsigned big endian values for R and S + // ASN.1 requires storing positive integer values with any leading 0s removed + // Convert ASN.1 format to IEEE P-1363 format + // determine coefficient size + var coefficientSize = (int)Math.Ceiling((decimal)keySize / 8); - // Second byte is length to read - var len = reader.ReadByte(); + // Create byte array to copy R into + var P1363R = new byte[coefficientSize]; - // a leading 0x00 is added to the content to indicate that the number is not negative... - if (0x00 == reader.BaseStream.ReadByte()) + if (0x0 == r[0] && (r[1] & (1 << 7)) != 0) { - // If the integer is positive but the high order bit is set to 1 - if ((reader.BaseStream.ReadByte() & (1 << 7)) != 0) - { - // we don't want to copy that leading 0x00, so reduce the length to read - len--; - } - // back the stream up one byte from the high order bit check - reader.BaseStream.Seek(-1, SeekOrigin.Current); + r.Skip(1).ToArray().CopyTo(P1363R, coefficientSize - r.Length + 1); } - // back the stream up one byte from the leading 0x00 check else - reader.BaseStream.Seek(-1, SeekOrigin.Current); + { + r.CopyTo(P1363R, coefficientSize - r.Length); + } - // read the calculated number of bytes from the stream and return the result - return reader.ReadBytes(len); + // Create byte array to copy S into + var P1363S = new byte[coefficientSize]; + + if (0x0 == s[0] && (s[1] & (1 << 7)) != 0) + { + s.Skip(1).ToArray().CopyTo(P1363S, coefficientSize - s.Length + 1); + } + else + { + s.CopyTo(P1363S, coefficientSize - s.Length); + } + + // Concatenate R + S coordinates and return the raw signature + return P1363R.Concat(P1363S).ToArray(); } - public static byte[] SigFromEcDsaSig(byte[] ecDsaSig, int keySize) + /// + /// Convert PEM formated string into byte array. + /// + /// source string. + /// output byte array. + public static byte[] PemToBytes(string pemStr) { - using (var stream = new MemoryStream(ecDsaSig, false)) + const string PemStartStr = "-----BEGIN"; + const string PemEndStr = "-----END"; + byte[] retval = null; + var lines = pemStr.Split('\n'); + var base64Str = ""; + bool started = false, ended = false; + var cline = ""; + for (var i = 0; i < lines.Length; i++) { - using (var reader = new BinaryReader(stream)) + cline = lines[i].ToUpper(); + if (cline == "") + continue; + if (cline.Length > PemStartStr.Length) { - // first byte should be DER sequence marker - var derSequence = reader.ReadByte(); - if (0x30 != derSequence) throw new Fido2VerificationException("ECDsa signature not a valid DER sequence"); - - // two forms of length, short form and long form - // short form, one byte, bit 8 not set, rest of the bits indicate data length - var dataLen = reader.ReadByte(); - - // long form, first byte, bit 8 is set, rest of bits indicate the length of the data length - // so if bit 8 is on... - var longForm = (0 != (dataLen & (1 << 7))); - if (true == longForm) + if (!started && cline.Substring(0, PemStartStr.Length) == PemStartStr) { - // rest of bits indicate the number of bytes containing the data length in long form - var longLenBytes = (dataLen & ~(1 << 7)); - - // we are expecting a single byte to hold the data length at the time of this writing - if (1 != longLenBytes) - throw new Fido2VerificationException("ECDsa signature has invalid long form data length bytes"); - - // read the length of the data - var longLen = reader.ReadBytes(longLenBytes)[0]; - - // must be more than 127 bytes otherwise we'd be using the short form - if (0x80 > longLen) - throw new Fido2VerificationException("ECDsa signature has invalid long form data length"); - - // sanity check the length - if (ecDsaSig.Length != (reader.BaseStream.Position + longLen)) - throw new Fido2VerificationException("ECDsa signature has invalid long form length"); + started = true; + continue; + } + } + if (cline.Length > PemEndStr.Length) + { + if (cline.Substring(0, PemEndStr.Length) == PemEndStr) + { + ended = true; + break; } - - // Get R value - var r = GetEcDsaSigValue(reader); - - // Get S value - var s = GetEcDsaSigValue(reader); - - // make sure we are at the end - if (reader.BaseStream.Position != reader.BaseStream.Length) - throw new Fido2VerificationException("ECDsa signature has bytes leftover after parsing R and S values"); - - // .NET requires IEEE P-1363 fixed size unsigned big endian values for R and S - // ASN.1 requires storing positive integer values with any leading 0s removed - // Convert ASN.1 format to IEEE P-1363 format - // determine coefficient size - var coefficientSize = (int)Math.Ceiling((decimal)keySize / 8); - - // Sanity check R and S value lengths - if ((coefficientSize * 2) < (r.Length + s.Length)) - throw new Fido2VerificationException("ECDsa signature has invalid length for given curve key size"); - - // Create byte array to copy R into - var P1363R = new byte[coefficientSize]; - r.CopyTo(P1363R, coefficientSize - r.Length); - - // Create byte array to copy S into - var P1363S = new byte[coefficientSize]; - s.CopyTo(P1363S, coefficientSize - s.Length); - - // Concatenate R + S coordinates and return the raw signature - return P1363R.Concat(P1363S).ToArray(); + } + if (started) + { + base64Str += lines[i]; } } + if (!(started && ended)) + { + throw new Exception("'BEGIN'/'END' line is missing."); + } + base64Str = base64Str.Replace("\r", ""); + base64Str = base64Str.Replace("\n", ""); + base64Str = base64Str.Replace("\n", " "); + retval = Convert.FromBase64String(base64Str); + return retval; } public static string CDPFromCertificateExts(X509ExtensionCollection exts) @@ -169,49 +157,29 @@ public static string CDPFromCertificateExts(X509ExtensionCollection exts) { if (ext.Oid.Value.Equals("2.5.29.31")) // id-ce-CRLDistributionPoints { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - var asnData = new AsnEncodedData(ext.Oid, ext.RawData); - cdp += asnData.Format(false).Split('=')[1]; - } - else - { - var strCDP = Asn1Util.BytesToString(ext.RawData); - strCDP = strCDP.Replace("\u0086.", "="); - cdp += strCDP.Split('=')[1]; - } + var asnData = AsnElt.Decode(ext.RawData); + cdp = System.Text.Encoding.ASCII.GetString(asnData.Sub[0].Sub[0].Sub[0].Sub[0].GetOctetString()); } } return cdp; } public static bool IsCertInCRL(byte[] crl, X509Certificate2 cert) { - var asnParser = new Asn1Parser(); - var strCRL = Asn1Util.BytesToString(crl); - - if (Asn1Util.IsPemFormated(strCRL)) - { - asnParser.LoadData(Asn1Util.PemToStream(strCRL)); - } - else asnParser.LoadData(new MemoryStream(crl)); - - if (7 > asnParser.RootNode.GetChildNode(0).ChildNodeCount) + var pemCRL = System.Text.Encoding.ASCII.GetString(crl); + var crlBytes = PemToBytes(pemCRL); + var asnData = AsnElt.Decode(crlBytes); + if (7 > asnData.Sub[0].Sub.Length) return false; // empty CRL - var revokedCertificates = asnParser.RootNode.GetChildNode(0).GetChildNode(5); - - // throw revoked certs into a list so someday we eventually cache CRLs + var revokedCertificates = asnData.Sub[0].Sub[5].Sub; var revoked = new List(); - for (var i = 0; i < revokedCertificates.ChildNodeCount; i++) + + foreach (AsnElt s in revokedCertificates) { - revoked.Add(Asn1Util.BytesToLong(revokedCertificates.GetChildNode(i) - .GetChildNode(0) - .Data - .Reverse() - .ToArray())); + revoked.Add(BitConverter.ToInt64(s.Sub[0].GetOctetString().Reverse().ToArray(), 0)); } - return revoked.Contains(Asn1Util.BytesToLong(cert.GetSerialNumber())); + return revoked.Contains(BitConverter.ToInt64(cert.GetSerialNumber(), 0)); } } } diff --git a/Src/Fido2/Fido2.csproj b/Src/Fido2/Fido2.csproj index d41d58d6..42af5628 100644 --- a/Src/Fido2/Fido2.csproj +++ b/Src/Fido2/Fido2.csproj @@ -6,7 +6,7 @@ - + diff --git a/Test/Attestation/AndroidKey.cs b/Test/Attestation/AndroidKey.cs new file mode 100644 index 00000000..4e67cff4 --- /dev/null +++ b/Test/Attestation/AndroidKey.cs @@ -0,0 +1,555 @@ +using System; +using System.Linq; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using fido2_net_lib.Test; +using Fido2NetLib.Objects; +using PeterO.Cbor; +using Xunit; +using Asn1; +using Fido2NetLib; + +namespace Test.Attestation +{ + public class AndroidKey : Fido2Tests.Attestation + { + public byte[] EncodeAttestationRecord() + { + var attestationVersion = AsnElt.MakeInteger(3); + var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var keymasterVersion = AsnElt.MakeInteger(2); + var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var attestationChallenge = AsnElt.MakeBlob(_clientDataHash); + var uniqueId = AsnElt.MakeBlob(_credentialID); + var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds())); + var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime }); + var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(2) })); + var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(0)); + var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { purpose, origin }); + return AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { + attestationVersion, + attestationSecurityLevel, + keymasterVersion, + keymasterSecurityLevel, + attestationChallenge, + uniqueId, + softwareEnforced, + teeEnforced + }).Encode(); + } + public AndroidKey() + { + _attestationObject = CBORObject.NewMap().Add("fmt", "android-key"); + X509Certificate2 attestnCert; + using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + { + var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", EncodeAttestationRecord(), false)); + + using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + { + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)); + + byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", COSE.Algorithm.ES256) + .Add("x5c", X5c) + .Add("sig", signature)); + } + } + } + [Fact] + public void TestAndroidKey() + { + var res = MakeAttestationResponse().Result; + Assert.Equal(string.Empty, res.ErrorMessage); + Assert.Equal("ok", res.Status); + Assert.Equal(_aaguid, res.Result.Aaguid); + Assert.Equal(_signCount, res.Result.Counter); + Assert.Equal("android-key", res.Result.CredType); + Assert.Equal(_credentialID, res.Result.CredentialId); + Assert.Null(res.Result.ErrorMessage); + Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey); + Assert.Null(res.Result.Status); + Assert.Equal("Test User", res.Result.User.DisplayName); + Assert.Equal(Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id); + Assert.Equal("testuser", res.Result.User.Name); + } + + [Fact] + public void TestAndroidKeySigNull() + { + _attestationObject["attStmt"].Set("sig", null); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid android-key attestation signature", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeySigNotByteString() + { + _attestationObject["attStmt"].Set("sig", "walrus"); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid android-key attestation signature", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeySigByteStringZeroLen() + { + _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(new byte[0])); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid android-key attestation signature", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyMissingX5c() + { + _attestationObject["attStmt"].Set("x5c", null); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Malformed x5c in android-key attestation", ex.Result.Message); + } + [Fact] + public void TestAndroidKeyX5cNotArray() + { + _attestationObject["attStmt"].Set("x5c", "boomerang"); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Malformed x5c in android-key attestation", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyX5cValueNotByteString() + { + _attestationObject["attStmt"].Set("x5c", "x".ToArray()); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Malformed x5c in android-key attestation", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyX5cValueZeroLengthByteString() + { + _attestationObject["attStmt"].Set("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0]))); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Malformed x5c in android-key attestation", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyInvalidPublicKey() + { + var attestnCert = _attestationObject["attStmt"]["x5c"].Values.FirstOrDefault().GetByteString(); + attestnCert[0] ^= 0xff; + var X5c = CBORObject.NewArray().Add(CBORObject.FromObject(attestnCert)); + _attestationObject["attStmt"].Set("x5c", X5c); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Failed to extract public key from android key: Cannot find the requested object", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyMissingAlg() + { + _attestationObject["attStmt"].Remove("alg"); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid android key attestation algorithm", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyAlgNull() + { + _attestationObject["attStmt"].Set("alg", null); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid android key attestation algorithm", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyAlgNaN() + { + _attestationObject["attStmt"].Set("alg", "invalid alg"); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid android key attestation algorithm", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyAlgNotInMap() + { + _attestationObject["attStmt"].Set("alg", -1); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid android key attestation algorithm", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeySigNotASN1() + { + _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(new byte[] { 0xf1, 0xd0 })); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Failed to decode android key attestation signature from ASN.1 encoded form", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyBadSig() + { + var sig = _attestationObject["attStmt"]["sig"].GetByteString(); + sig[sig.Length - 1] ^= 0xff; + _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(sig)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Invalid android key attestation signature", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyX5cCertMissingAttestationRecordExt() + { + _attestationObject = CBORObject.NewMap().Add("fmt", "android-key"); + X509Certificate2 attestnCert; + using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + { + var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); + + using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + { + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)); + + byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", COSE.Algorithm.ES256) + .Add("x5c", X5c) + .Add("sig", signature)); + } + } + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Android key attestation certificate contains no AttestationRecord extension", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyX5cCertAttestationRecordExtMalformed() + { + _attestationObject = CBORObject.NewMap().Add("fmt", "android-key"); + X509Certificate2 attestnCert; + using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + { + var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", new byte[] { 0x0 }, false)); + + using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + { + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)); + + byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", COSE.Algorithm.ES256) + .Add("x5c", X5c) + .Add("sig", signature)); + } + } + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Malformed android key AttestationRecord extension verifying android key attestation certificate extension", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyX5cCertAttestationRecordAllApplicationsSoftware() + { + var attestationVersion = AsnElt.MakeInteger(3); + var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var keymasterVersion = AsnElt.MakeInteger(2); + var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var attestationChallenge = AsnElt.MakeBlob(_clientDataHash); + var uniqueId = AsnElt.MakeBlob(_credentialID); + var allApplications = AsnElt.MakeExplicit(600, AsnElt.Make(AsnElt.SEQUENCE, null)); + var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds())); + var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime, allApplications }); + var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(2) })); + var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(0)); + var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { purpose, origin }); + var attRecord = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { + attestationVersion, + attestationSecurityLevel, + keymasterVersion, + keymasterSecurityLevel, + attestationChallenge, + uniqueId, + softwareEnforced, + teeEnforced + }).Encode(); + + _attestationObject = CBORObject.NewMap().Add("fmt", "android-key"); + X509Certificate2 attestnCert; + using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + { + var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", attRecord, false)); + + using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + { + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)); + + byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", COSE.Algorithm.ES256) + .Add("x5c", X5c) + .Add("sig", signature)); + } + } + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Found all applications field in android key attestation certificate extension", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyX5cCertAttestationRecordAllApplicationsTee() + { + var attestationVersion = AsnElt.MakeInteger(3); + var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var keymasterVersion = AsnElt.MakeInteger(2); + var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var attestationChallenge = AsnElt.MakeBlob(_clientDataHash); + var uniqueId = AsnElt.MakeBlob(_credentialID); + var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds())); + var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime }); + var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(2) })); + var allApplications = AsnElt.MakeExplicit(600, AsnElt.Make(AsnElt.SEQUENCE, null)); + var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(0)); + var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { purpose, origin, allApplications }); + var attRecord = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { + attestationVersion, + attestationSecurityLevel, + keymasterVersion, + keymasterSecurityLevel, + attestationChallenge, + uniqueId, + softwareEnforced, + teeEnforced + }).Encode(); + + _attestationObject = CBORObject.NewMap().Add("fmt", "android-key"); + X509Certificate2 attestnCert; + using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + { + var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", attRecord, false)); + + using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + { + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)); + + byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", COSE.Algorithm.ES256) + .Add("x5c", X5c) + .Add("sig", signature)); + } + } + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Found all applications field in android key attestation certificate extension", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyX5cCertAttestationRecordOriginSoftware() + { + var attestationVersion = AsnElt.MakeInteger(3); + var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var keymasterVersion = AsnElt.MakeInteger(2); + var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var attestationChallenge = AsnElt.MakeBlob(_clientDataHash); + var uniqueId = AsnElt.MakeBlob(_credentialID); + var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds())); + var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime }); + var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(2) })); + var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(3)); + var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { purpose, origin }); + var attRecord = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { + attestationVersion, + attestationSecurityLevel, + keymasterVersion, + keymasterSecurityLevel, + attestationChallenge, + uniqueId, + softwareEnforced, + teeEnforced + }).Encode(); + + _attestationObject = CBORObject.NewMap().Add("fmt", "android-key"); + X509Certificate2 attestnCert; + using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + { + var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", attRecord, false)); + + using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + { + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)); + + byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", COSE.Algorithm.ES256) + .Add("x5c", X5c) + .Add("sig", signature)); + } + } + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Found origin field not set to KM_ORIGIN_GENERATED in android key attestation certificate extension", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyX5cCertAttestationRecordOriginTeeTee() + { + var attestationVersion = AsnElt.MakeInteger(3); + var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var keymasterVersion = AsnElt.MakeInteger(2); + var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var attestationChallenge = AsnElt.MakeBlob(_clientDataHash); + var uniqueId = AsnElt.MakeBlob(_credentialID); + var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds())); + var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(3)); + var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime, origin }); + var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(2) })); + var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { purpose }); + var attRecord = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { + attestationVersion, + attestationSecurityLevel, + keymasterVersion, + keymasterSecurityLevel, + attestationChallenge, + uniqueId, + softwareEnforced, + teeEnforced + }).Encode(); + + _attestationObject = CBORObject.NewMap().Add("fmt", "android-key"); + X509Certificate2 attestnCert; + using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + { + var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", attRecord, false)); + + using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + { + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)); + + byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", COSE.Algorithm.ES256) + .Add("x5c", X5c) + .Add("sig", signature)); + } + } + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Found origin field not set to KM_ORIGIN_GENERATED in android key attestation certificate extension", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyX5cCertAttestationRecordPurposeSoftware() + { + var attestationVersion = AsnElt.MakeInteger(3); + var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var keymasterVersion = AsnElt.MakeInteger(2); + var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var attestationChallenge = AsnElt.MakeBlob(_clientDataHash); + var uniqueId = AsnElt.MakeBlob(_credentialID); + var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(1) })); + var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds())); + var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime, purpose }); + var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(0)); + var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { origin }); + var attRecord = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { + attestationVersion, + attestationSecurityLevel, + keymasterVersion, + keymasterSecurityLevel, + attestationChallenge, + uniqueId, + softwareEnforced, + teeEnforced + }).Encode(); + + _attestationObject = CBORObject.NewMap().Add("fmt", "android-key"); + X509Certificate2 attestnCert; + using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + { + var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", attRecord, false)); + + using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + { + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)); + + byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", COSE.Algorithm.ES256) + .Add("x5c", X5c) + .Add("sig", signature)); + } + } + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Found purpose field not set to KM_PURPOSE_SIGN in android key attestation certificate extension", ex.Result.Message); + } + + [Fact] + public void TestAndroidKeyX5cCertAttestationRecordPurposeTee() + { + var attestationVersion = AsnElt.MakeInteger(3); + var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var keymasterVersion = AsnElt.MakeInteger(2); + var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 })); + var attestationChallenge = AsnElt.MakeBlob(_clientDataHash); + var uniqueId = AsnElt.MakeBlob(_credentialID); + var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds())); + var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime }); + var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(1) })); + var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(0)); + var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { purpose, origin }); + var attRecord = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { + attestationVersion, + attestationSecurityLevel, + keymasterVersion, + keymasterSecurityLevel, + attestationChallenge, + uniqueId, + softwareEnforced, + teeEnforced + }).Encode(); + + _attestationObject = CBORObject.NewMap().Add("fmt", "android-key"); + X509Certificate2 attestnCert; + using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256)) + { + var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256); + + attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", attRecord, false)); + + using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2))) + { + var X5c = CBORObject.NewArray() + .Add(CBORObject.FromObject(attestnCert.RawData)); + + byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", COSE.Algorithm.ES256) + .Add("x5c", X5c) + .Add("sig", signature)); + } + } + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); + Assert.Equal("Found purpose field not set to KM_PURPOSE_SIGN in android key attestation certificate extension", ex.Result.Message); + } + } +} diff --git a/Test/Attestation/AndroidSafetyNet.cs b/Test/Attestation/AndroidSafetyNet.cs new file mode 100644 index 00000000..25e5fefe --- /dev/null +++ b/Test/Attestation/AndroidSafetyNet.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; +using fido2_net_lib.Test; +using PeterO.Cbor; + +namespace Test.Attestation +{ + class AndroidSafetyNet : Fido2Tests.Attestation + { + public AndroidSafetyNet() + { + _attestationObject = CBORObject.NewMap().Add("fmt", "android-safetynet"); + } + } +} diff --git a/Test/Attestation/FidoU2f.cs b/Test/Attestation/FidoU2f.cs index 5031ed15..943d310a 100644 --- a/Test/Attestation/FidoU2f.cs +++ b/Test/Attestation/FidoU2f.cs @@ -54,10 +54,18 @@ public FidoU2f() public void TestU2f() { var res = MakeAttestationResponse().Result; - Assert.Equal("", res.ErrorMessage); + Assert.Equal(string.Empty, res.ErrorMessage); Assert.Equal("ok", res.Status); - Assert.True(_credentialID.SequenceEqual(res.Result.CredentialId)); + Assert.Equal(_aaguid, res.Result.Aaguid); Assert.Equal(_signCount, res.Result.Counter); + Assert.Equal("fido-u2f", res.Result.CredType); + Assert.Equal(_credentialID, res.Result.CredentialId); + Assert.Null(res.Result.ErrorMessage); + Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey); + Assert.Null(res.Result.Status); + Assert.Equal("Test User", res.Result.User.DisplayName); + Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id); + Assert.Equal("testuser", res.Result.User.Name); } [Fact] public void TestU2fWithAaguid() @@ -155,7 +163,7 @@ public void TestU2fSigNotASN1() public void TestU2fBadSig() { var sig = _attestationObject["attStmt"]["sig"].GetByteString(); - sig[15] ^= sig[15]; + sig[sig.Length - 1] ^= 0xff; _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(sig)); var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid fido-u2f attestation signature", ex.Result.Message); diff --git a/Test/Attestation/None.cs b/Test/Attestation/None.cs index 7d051603..a18a8cec 100644 --- a/Test/Attestation/None.cs +++ b/Test/Attestation/None.cs @@ -24,11 +24,18 @@ public void TestNone() res = MakeAttestationResponse().Result; + Assert.Equal(string.Empty, res.ErrorMessage); + Assert.Equal("ok", res.Status); + Assert.Equal(_aaguid, res.Result.Aaguid); + Assert.Equal(_signCount, res.Result.Counter); Assert.Equal("none", res.Result.CredType); - //Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Result.CredentialId); - Assert.True(new[] { res.Status, res.Status }.All(x => x == "ok")); - Assert.True(new[] { res.ErrorMessage, res.ErrorMessage }.All(x => x == "")); - //Assert.True(res.Result.Counter + 1 == res.Result.Counter); + Assert.Equal(_credentialID, res.Result.CredentialId); + Assert.Null(res.Result.ErrorMessage); + Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey); + Assert.Null(res.Result.Status); + Assert.Equal("Test User", res.Result.User.DisplayName); + Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id); + Assert.Equal("testuser", res.Result.User.Name); _attestationObject = CBORObject.NewMap().Add("fmt", "none"); }); } @@ -37,7 +44,7 @@ public void TestNoneWithAttStmt() { _attestationObject.Add("attStmt", CBORObject.NewMap().Add("foo", "bar")); _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(Fido2Tests._validCOSEParameters[0]); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Attestation format none should have no attestation statement", ex.Result.Message); } } diff --git a/Test/Attestation/Packed.cs b/Test/Attestation/Packed.cs index 19bce845..9590a55f 100644 --- a/Test/Attestation/Packed.cs +++ b/Test/Attestation/Packed.cs @@ -21,24 +21,28 @@ public void TestSelf() { Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) { - (Fido2.CredentialMakeResult, AssertionVerificationResult) res; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); - - if (param.Length == 3) - { - res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; - } - else - { - res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; - } - - Assert.Equal("packed", res.Item1.Result.CredType); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.True(new[] { res.Item1.Status, res.Item2.Status }.All(x => x == "ok")); - Assert.True(new[] { res.Item1.ErrorMessage, res.Item2.ErrorMessage }.All(x => x == "")); - Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); + var crv = (param.Length == 3) ? (COSE.EllipticCurve)param[2] : COSE.EllipticCurve.Reserved; + + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], crv); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature)); + + var res = MakeAttestationResponse().Result; + + Assert.Equal(string.Empty, res.ErrorMessage); + Assert.Equal("ok", res.Status); + Assert.Equal(_aaguid, res.Result.Aaguid); + Assert.Equal(_signCount, res.Result.Counter); + Assert.Equal("packed", res.Result.CredType); + Assert.Equal(_credentialID, res.Result.CredentialId); + Assert.Null(res.Result.ErrorMessage); + Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey); + Assert.Null(res.Result.Status); + Assert.Equal("Test User", res.Result.User.DisplayName); + Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id); + Assert.Equal("testuser", res.Result.User.Name); _attestationObject = CBORObject.NewMap().Add("fmt", "packed"); }); } @@ -46,19 +50,24 @@ public void TestSelf() public void TestSelfAlgMismatch() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", COSE.Algorithm.ES384)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", COSE.Algorithm.ES384) + .Add("sig", signature)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Algorithm mismatch between credential public key and authenticator data in self attestation statement", ex.Result.Message); } [Fact] public void TestSelfBadSig() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); - _attestationObject["attStmt"].Add("sig", new byte[] { 0x30, 0x45, 0x02, 0x20, 0x11, 0x9b, 0x6f, 0xa8, 0x1c, 0xe1, 0x75, 0x9e, 0xbe, 0xf1, 0x52, 0xa6, 0x99, 0x40, 0x5e, 0xd6, 0x6a, 0xcc, 0x01, 0x33, 0x65, 0x18, 0x05, 0x00, 0x96, 0x28, 0x29, 0xbe, 0x85, 0x57, 0xb7, 0x1d, 0x02, 0x21, 0x00, 0x94, 0x50, 0x1d, 0xf1, 0x90, 0x03, 0xa4, 0x4d, 0xa4, 0xdf, 0x9f, 0xbb, 0xb5, 0xe4, 0xce, 0x91, 0x6b, 0xc3, 0x90, 0xe8, 0x38, 0x99, 0x66, 0x4f, 0xa5, 0xc4, 0x0c, 0xf3, 0xed, 0xe3, 0xda, 0x83 }); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]); + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", new byte[] { 0x30, 0x45, 0x02, 0x20, 0x11, 0x9b, 0x6f, 0xa8, 0x1c, 0xe1, 0x75, 0x9e, 0xbe, 0xf1, 0x52, 0xa6, 0x99, 0x40, 0x5e, 0xd6, 0x6a, 0xcc, 0x01, 0x33, 0x65, 0x18, 0x05, 0x00, 0x96, 0x28, 0x29, 0xbe, 0x85, 0x57, 0xb7, 0x1d, 0x02, 0x21, 0x00, 0x94, 0x50, 0x1d, 0xf1, 0x90, 0x03, 0xa4, 0x4d, 0xa4, 0xdf, 0x9f, 0xbb, 0xb5, 0xe4, 0xce, 0x91, 0x6b, 0xc3, 0x90, 0xe8, 0x38, 0x99, 0x66, 0x4f, 0xa5, 0xc4, 0x0c, 0xf3, 0xed, 0xe3, 0xda, 0x83 })); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Failed to validate signature", ex.Result.Message); } @@ -66,9 +75,10 @@ public void TestSelfBadSig() public void TestMissingAlg() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap()); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]); + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("sig", signature)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid packed attestation algorithm", ex.Result.Message); } @@ -76,9 +86,11 @@ public void TestMissingAlg() public void TestAlgNaN() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", "invalid alg")); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]); + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", "invalid alg") + .Add("sig", signature)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid packed attestation algorithm", ex.Result.Message); } @@ -86,10 +98,11 @@ public void TestAlgNaN() public void TestSigNull() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); - _attestationObject["attStmt"].Set("sig", null); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]); + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", null)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid packed attestation signature", ex.Result.Message); } @@ -97,10 +110,11 @@ public void TestSigNull() public void TestSigNotByteString() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); - _attestationObject["attStmt"].Set("sig", "walrus"); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]); + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", "walrus")); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid packed attestation signature", ex.Result.Message); } @@ -108,10 +122,11 @@ public void TestSigNotByteString() public void TestSigByteStringZeroLen() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); - _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(new byte[0])); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2])); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]); + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", CBORObject.FromObject(new byte[0]))); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid packed attestation signature", ex.Result.Message); } @@ -120,14 +135,16 @@ public void TestFull() { Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) { - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); + if (COSE.KeyType.OKP == (COSE.KeyType)param[0]) + { + return; + } X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US"); - (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); + Fido2.CredentialMakeResult res = null; switch ((COSE.KeyType)param[0]) { @@ -179,7 +196,14 @@ public void TestFull() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c).Result; + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", X5c)); + + res = MakeAttestationResponse().Result; } } break; @@ -230,7 +254,14 @@ public void TestFull() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], COSE.EllipticCurve.Reserved, rsa: rsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", X5c)); + + res = MakeAttestationResponse().Result; } } break; @@ -242,15 +273,21 @@ public void TestFull() ErrorMessage = string.Empty, Status = "ok", }; - res.Item2 = avr; } break; } - //Assert.Equal("packed", res.Item1.Result.CredType); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.True(new[] { "ok", res.Item2.Status }.All(x => x == "ok")); - Assert.True(new[] { "", res.Item2.ErrorMessage }.All(x => x == "")); - //Assert.True(res.Item1.Result.Counter + 1 == res.Item2.Counter); + Assert.Equal(string.Empty, res.ErrorMessage); + Assert.Equal("ok", res.Status); + Assert.Equal(_aaguid, res.Result.Aaguid); + Assert.Equal(_signCount, res.Result.Counter); + Assert.Equal("packed", res.Result.CredType); + Assert.Equal(_credentialID, res.Result.CredentialId); + Assert.Null(res.Result.ErrorMessage); + Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey); + Assert.Null(res.Result.Status); + Assert.Equal("Test User", res.Result.User.DisplayName); + Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id); + Assert.Equal("testuser", res.Result.User.Name); _attestationObject = CBORObject.NewMap().Add("fmt", "packed"); }); } @@ -259,9 +296,6 @@ public void TestFull() public void TestFullMissingX5c() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); - _attestationObject["attStmt"].Set("x5c", null); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); @@ -304,7 +338,14 @@ public void TestFullMissingX5c() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", null)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Malformed x5c array in packed attestation statement", ex.Result.Message); } } @@ -314,9 +355,6 @@ public void TestFullMissingX5c() public void TestFullX5cNotArray() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); - _attestationObject["attStmt"].Set("x5c", "boomerang"); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); @@ -359,7 +397,14 @@ public void TestFullX5cNotArray() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", "boomerang")); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Malformed x5c array in packed attestation statement", ex.Result.Message); } } @@ -369,10 +414,6 @@ public void TestFullX5cNotArray() public void TestFullX5cCountNotOne() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); - _attestationObject["attStmt"] - .Set("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0])).Add(CBORObject.FromObject(new byte[0]))); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); @@ -415,7 +456,14 @@ public void TestFullX5cCountNotOne() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], COSE.EllipticCurve.Reserved, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0])).Add(CBORObject.FromObject(new byte[0])))); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Malformed x5c cert found in packed attestation statement", ex.Result.Message); } } @@ -425,9 +473,6 @@ public void TestFullX5cCountNotOne() public void TestFullX5cValueNotByteString() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); - _attestationObject["attStmt"].Set("x5c", "x".ToArray()); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); @@ -470,7 +515,14 @@ public void TestFullX5cValueNotByteString() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], COSE.EllipticCurve.Reserved, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", "x".ToArray())); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Malformed x5c cert found in packed attestation statement", ex.Result.Message); } } @@ -480,9 +532,6 @@ public void TestFullX5cValueNotByteString() public void TestFullX5cValueZeroLengthByteString() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); - _attestationObject["attStmt"].Set("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0]))); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); @@ -525,7 +574,14 @@ public void TestFullX5cValueZeroLengthByteString() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0])))); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Malformed x5c cert found in packed attestation statement", ex.Result.Message); } } @@ -535,8 +591,6 @@ public void TestFullX5cValueZeroLengthByteString() public void TestFullX5cCertExpired() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-7); DateTimeOffset notAfter = notBefore.AddDays(2); @@ -579,7 +633,14 @@ public void TestFullX5cCertExpired() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", X5c)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Packed signing certificate expired or not yet valid", ex.Result.Message); } } @@ -589,8 +650,6 @@ public void TestFullX5cCertExpired() public void TestFullX5cCertNotYetValid() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(1); DateTimeOffset notAfter = notBefore.AddDays(7); @@ -633,7 +692,14 @@ public void TestFullX5cCertNotYetValid() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", X5c)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Packed signing certificate expired or not yet valid", ex.Result.Message); } } @@ -643,8 +709,6 @@ public void TestFullX5cCertNotYetValid() public void TestFullInvalidAlg() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", 42)); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); @@ -687,7 +751,14 @@ public void TestFullInvalidAlg() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", 42) + .Add("sig", signature) + .Add("x5c", X5c)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid attestation algorithm", ex.Result.Message); } } @@ -697,9 +768,6 @@ public void TestFullInvalidAlg() public void TestFullInvalidSig() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); - _attestationObject["attStmt"].Add("sig", new byte[] { 0x30, 0x45, 0x02, 0x20, 0x11, 0x9b, 0x6f, 0xa8, 0x1c, 0xe1, 0x75, 0x9e, 0xbe, 0xf1, 0x52, 0xa6, 0x99, 0x40, 0x5e, 0xd6, 0x6a, 0xcc, 0x01, 0x33, 0x65, 0x18, 0x05, 0x00, 0x96, 0x28, 0x29, 0xbe, 0x85, 0x57, 0xb7, 0x1d, 0x02, 0x21, 0x00, 0x94, 0x50, 0x1d, 0xf1, 0x90, 0x03, 0xa4, 0x4d, 0xa4, 0xdf, 0x9f, 0xbb, 0xb5, 0xe4, 0xce, 0x91, 0x6b, 0xc3, 0x90, 0xe8, 0x38, 0x99, 0x66, 0x4f, 0xa5, 0xc4, 0x0c, 0xf3, 0xed, 0xe3, 0xda, 0x83 }); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); @@ -742,7 +810,14 @@ public void TestFullInvalidSig() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", new byte[] { 0x30, 0x45, 0x02, 0x20, 0x11, 0x9b, 0x6f, 0xa8, 0x1c, 0xe1, 0x75, 0x9e, 0xbe, 0xf1, 0x52, 0xa6, 0x99, 0x40, 0x5e, 0xd6, 0x6a, 0xcc, 0x01, 0x33, 0x65, 0x18, 0x05, 0x00, 0x96, 0x28, 0x29, 0xbe, 0x85, 0x57, 0xb7, 0x1d, 0x02, 0x21, 0x00, 0x94, 0x50, 0x1d, 0xf1, 0x90, 0x03, 0xa4, 0x4d, 0xa4, 0xdf, 0x9f, 0xbb, 0xb5, 0xe4, 0xce, 0x91, 0x6b, 0xc3, 0x90, 0xe8, 0x38, 0x99, 0x66, 0x4f, 0xa5, 0xc4, 0x0c, 0xf3, 0xed, 0xe3, 0xda, 0x83 }) + .Add("x5c", X5c)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid full packed signature", ex.Result.Message); } } @@ -752,8 +827,6 @@ public void TestFullInvalidSig() public void TestFullAttCertNotV3() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); @@ -799,7 +872,14 @@ public void TestFullAttCertNotV3() .Add(CBORObject.FromObject(rawAttestnCert)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", X5c)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Packed x5c attestation certificate not V3", ex.Result.Message); } } @@ -809,8 +889,6 @@ public void TestFullAttCertNotV3() public void TestFullAttCertSubject() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); @@ -853,7 +931,14 @@ public void TestFullAttCertSubject() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", X5c)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid attestation cert subject", ex.Result.Message); } } @@ -863,8 +948,6 @@ public void TestFullAttCertSubject() public void TestFullAttCertAaguidNotMatchAuthdata() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); @@ -910,7 +993,14 @@ public void TestFullAttCertAaguidNotMatchAuthdata() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", X5c)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("aaguid present in packed attestation cert exts but does not match aaguid from authData", ex.Result.Message); } } @@ -920,8 +1010,6 @@ public void TestFullAttCertAaguidNotMatchAuthdata() public void TestFullAttCertCAFlagSet() { var param = Fido2Tests._validCOSEParameters[0]; - _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); - _attestationObject.Add("attStmt", CBORObject.NewMap().Add("alg", (COSE.Algorithm)param[1])); X509Certificate2 root, attestnCert; DateTimeOffset notBefore = DateTimeOffset.UtcNow; DateTimeOffset notAfter = notBefore.AddDays(2); @@ -964,7 +1052,14 @@ public void TestFullAttCertCAFlagSet() .Add(CBORObject.FromObject(attestnCert.RawData)) .Add(CBORObject.FromObject(root.RawData)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c)); + var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt); + + _attestationObject.Add("attStmt", CBORObject.NewMap() + .Add("alg", (COSE.Algorithm)param[1]) + .Add("sig", signature) + .Add("x5c", X5c)); + + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Attestion certificate has CA cert flag present", ex.Result.Message); } } diff --git a/Test/Attestation/Tpm.cs b/Test/Attestation/Tpm.cs index f08e7de8..b50bd1bf 100644 --- a/Test/Attestation/Tpm.cs +++ b/Test/Attestation/Tpm.cs @@ -55,7 +55,10 @@ public void TestTPM() { Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) { - (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); + if (COSE.KeyType.OKP == (COSE.KeyType)param[0]) + { + return; + } var alg = (COSE.Algorithm)param[1]; if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) @@ -219,7 +222,7 @@ public void TestTPM() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -227,26 +230,7 @@ public void TestTPM() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); - - res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2], ecdsa: ecdsaAtt, X5c: X5c).Result; - Assert.Equal(string.Empty, res.Item1.ErrorMessage); - Assert.Equal("ok", res.Item1.Status); - Assert.Equal(_aaguid, res.Item1.Result.Aaguid); - Assert.Equal(_signCount, res.Item1.Result.Counter); - Assert.Equal("tpm", res.Item1.Result.CredType); - Assert.Equal(_credentialID, res.Item1.Result.CredentialId); - Assert.Null(res.Item1.Result.ErrorMessage); - Assert.Equal(_credentialPublicKey.GetBytes(), res.Item1.Result.PublicKey); - Assert.Null(res.Item1.Result.Status); - Assert.Equal("Test User", res.Item1.Result.User.DisplayName); - Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Item1.Result.User.Id); - Assert.Equal("testuser", res.Item1.Result.User.Name); - Assert.Equal(_signCount, res.Item2.Counter - 1); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.Equal(string.Empty, res.Item2.ErrorMessage); - Assert.Equal("ok", res.Item2.Status); + .Add("pubArea", pubArea)); } } break; @@ -376,7 +360,7 @@ public void TestTPM() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -384,31 +368,26 @@ public void TestTPM() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); - - res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; - Assert.Equal(string.Empty, res.Item1.ErrorMessage); - Assert.Equal("ok", res.Item1.Status); - Assert.Equal(_aaguid, res.Item1.Result.Aaguid); - Assert.Equal(_signCount, res.Item1.Result.Counter); - Assert.Equal("tpm", res.Item1.Result.CredType); - Assert.Equal(_credentialID, res.Item1.Result.CredentialId); - Assert.Null(res.Item1.Result.ErrorMessage); - Assert.Equal(_credentialPublicKey.GetBytes(), res.Item1.Result.PublicKey); - Assert.Null(res.Item1.Result.Status); - Assert.Equal("Test User", res.Item1.Result.User.DisplayName); - Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Item1.Result.User.Id); - Assert.Equal("testuser", res.Item1.Result.User.Name); - Assert.Equal(_signCount, res.Item2.Counter - 1); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.Equal(string.Empty, res.Item2.ErrorMessage); - Assert.Equal("ok", res.Item2.Status); + .Add("pubArea", pubArea)); } } break; - } + } + var res = MakeAttestationResponse().Result; + + Assert.Equal(string.Empty, res.ErrorMessage); + Assert.Equal("ok", res.Status); + Assert.Equal(_aaguid, res.Result.Aaguid); + Assert.Equal(_signCount, res.Result.Counter); + Assert.Equal("tpm", res.Result.CredType); + Assert.Equal(_credentialID, res.Result.CredentialId); + Assert.Null(res.Result.ErrorMessage); + Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey); + Assert.Null(res.Result.Status); + Assert.Equal("Test User", res.Result.User.DisplayName); + Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id); + Assert.Equal("testuser", res.Result.User.Name); _attestationObject = CBORObject.NewMap().Add("fmt", "tpm"); }); } @@ -553,7 +532,7 @@ public void TestTPMSigNull() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -561,10 +540,10 @@ public void TestTPMSigNull() .Add("x5c", X5c) .Add("sig", null) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid TPM attestation signature", ex.Result.Message); } } @@ -710,7 +689,7 @@ public void TestTPMSigNotByteString() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -718,10 +697,10 @@ public void TestTPMSigNotByteString() .Add("x5c", X5c) .Add("sig", "strawberries") .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid TPM attestation signature", ex.Result.Message); } } @@ -867,7 +846,7 @@ public void TestTPMSigByteStringZeroLen() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -875,10 +854,10 @@ public void TestTPMSigByteStringZeroLen() .Add("x5c", X5c) .Add("sig", CBORObject.FromObject(new byte[0])) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid TPM attestation signature", ex.Result.Message); } } @@ -1024,7 +1003,7 @@ public void TestTPMVersionNot2() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "3.0") @@ -1032,10 +1011,10 @@ public void TestTPMVersionNot2() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("FIDO2 only supports TPM 2.0", ex.Result.Message); } } @@ -1115,7 +1094,6 @@ public void TestTPMPubAreaNull() _credentialPublicKey = new CredentialPublicKey(cpk); - unique = rsaparams.Modulus; exponent = rsaparams.Exponent; type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); @@ -1181,7 +1159,7 @@ public void TestTPMPubAreaNull() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -1189,10 +1167,9 @@ public void TestTPMPubAreaNull() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", null)) - .Add("authData", _authData); + .Add("pubArea", null)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Missing or malformed pubArea", ex.Result.Message); } } @@ -1338,7 +1315,7 @@ public void TestTPMPubAreaNotByteString() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -1346,10 +1323,10 @@ public void TestTPMPubAreaNotByteString() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", "banana")) - .Add("authData", _authData); + .Add("pubArea", "banana")); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Missing or malformed pubArea", ex.Result.Message); } } @@ -1495,7 +1472,7 @@ public void TestTPMPubAreaByteStringZeroLen() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -1503,10 +1480,10 @@ public void TestTPMPubAreaByteStringZeroLen() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", CBORObject.FromObject(new byte[0]))) - .Add("authData", _authData); + .Add("pubArea", CBORObject.FromObject(new byte[0]))); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Missing or malformed pubArea", ex.Result.Message); } } @@ -1652,7 +1629,7 @@ var pubArea new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -1660,10 +1637,10 @@ var pubArea .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Missing or malformed pubArea", ex.Result.Message); } } @@ -1809,7 +1786,7 @@ public void TestTPMPubAreaUniqueByteStringZeroLen() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -1817,10 +1794,10 @@ public void TestTPMPubAreaUniqueByteStringZeroLen() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Missing or malformed pubArea", ex.Result.Message); } } @@ -1966,7 +1943,7 @@ public void TestTPMPubAreaUniquePublicKeyMismatch() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -1974,10 +1951,10 @@ public void TestTPMPubAreaUniquePublicKeyMismatch() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Public key mismatch between pubArea and credentialPublicKey", ex.Result.Message); } } @@ -2122,7 +2099,7 @@ public void TestTPMPubAreaUniqueExponentMismatch() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -2130,10 +2107,10 @@ public void TestTPMPubAreaUniqueExponentMismatch() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Public key exponent mismatch between pubArea and credentialPublicKey", ex.Result.Message); } } @@ -2286,7 +2263,7 @@ public void TestTPMPubAreaUniqueXValueMismatch() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -2294,10 +2271,10 @@ public void TestTPMPubAreaUniqueXValueMismatch() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], ecdsa: ecdsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("X-coordinate mismatch between pubArea and credentialPublicKey", ex.Result.Message); } } @@ -2450,7 +2427,7 @@ public void TestTPMPubAreaUniqueYValueMismatch() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -2458,10 +2435,10 @@ public void TestTPMPubAreaUniqueYValueMismatch() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], ecdsa: ecdsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Y-coordinate mismatch between pubArea and credentialPublicKey", ex.Result.Message); } } @@ -2614,7 +2591,7 @@ public void TestTPMPubAreaUniqueCurveMismatch() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -2622,10 +2599,10 @@ public void TestTPMPubAreaUniqueCurveMismatch() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], ecdsa: ecdsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Curve mismatch between pubArea and credentialPublicKey", ex.Result.Message); } } @@ -2770,7 +2747,7 @@ public void TestTPMCertInfoNull() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -2778,10 +2755,10 @@ public void TestTPMCertInfoNull() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", null) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("CertInfo invalid parsing TPM format attStmt", ex.Result.Message); } } @@ -2926,7 +2903,7 @@ public void TestTPMCertInfoNotByteString() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -2934,10 +2911,10 @@ public void TestTPMCertInfoNotByteString() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", "tomato") - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("CertInfo invalid parsing TPM format attStmt", ex.Result.Message); } } @@ -3082,7 +3059,7 @@ public void TestTPMCertInfoByteStringZeroLen() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -3090,10 +3067,10 @@ public void TestTPMCertInfoByteStringZeroLen() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", CBORObject.FromObject(new byte[0])) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("CertInfo invalid parsing TPM format attStmt", ex.Result.Message); } } @@ -3238,7 +3215,7 @@ public void TestTPMCertInfoBadMagic() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -3246,10 +3223,10 @@ public void TestTPMCertInfoBadMagic() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Bad magic number 474354FF", ex.Result.Message); } } @@ -3394,7 +3371,7 @@ public void TestTPMCertInfoBadType() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -3402,10 +3379,10 @@ public void TestTPMCertInfoBadType() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Bad structure tag 1780", ex.Result.Message); } } @@ -3549,7 +3526,7 @@ public void TestTPMCertInfoExtraDataZeroLen() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -3557,10 +3534,10 @@ public void TestTPMCertInfoExtraDataZeroLen() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Bad extraData in certInfo", ex.Result.Message); } } @@ -3705,7 +3682,7 @@ public void TestTPMCertInfoTPM2BNameIsHandle() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -3713,10 +3690,10 @@ public void TestTPMCertInfoTPM2BNameIsHandle() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Unexpected handle in TPM2B_NAME", ex.Result.Message); } } @@ -3861,7 +3838,7 @@ public void TestTPMCertInfoTPM2BNoName() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -3869,10 +3846,10 @@ public void TestTPMCertInfoTPM2BNoName() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Unexpected no name found in TPM2B_NAME", ex.Result.Message); } } @@ -4018,7 +3995,7 @@ public void TestTPMCertInfoTPM2BExtraBytes() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -4026,10 +4003,10 @@ public void TestTPMCertInfoTPM2BExtraBytes() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Unexpected extra bytes found in TPM2B_NAME", ex.Result.Message); } } @@ -4174,7 +4151,7 @@ public void TestTPMCertInfoTPM2BInvalidHashAlg() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -4182,10 +4159,10 @@ public void TestTPMCertInfoTPM2BInvalidHashAlg() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("TPM_ALG_ID found in TPM2B_NAME not acceptable hash algorithm", ex.Result.Message); } } @@ -4330,7 +4307,7 @@ public void TestTPMCertInfoTPM2BInvalidTPMALGID() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -4338,10 +4315,10 @@ public void TestTPMCertInfoTPM2BInvalidTPMALGID() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid TPM_ALG_ID found in TPM2B_NAME", ex.Result.Message); } } @@ -4487,7 +4464,7 @@ public void TestTPMAlgNull() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -4495,10 +4472,10 @@ public void TestTPMAlgNull() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid TPM attestation algorithm", ex.Result.Message); } } @@ -4643,7 +4620,7 @@ public void TestTPMAlgNotNumber() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -4651,10 +4628,10 @@ public void TestTPMAlgNotNumber() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid TPM attestation algorithm", ex.Result.Message); } } @@ -4799,7 +4776,7 @@ public void TestTPMAlgInvalid() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -4807,10 +4784,10 @@ public void TestTPMAlgInvalid() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid TPM attestation algorithm", ex.Result.Message); } } @@ -4955,7 +4932,7 @@ public void TestTPMAlgMismatch() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -4963,10 +4940,10 @@ public void TestTPMAlgMismatch() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Hash value mismatch extraData and attToBeSigned", ex.Result.Message); } } @@ -5114,7 +5091,7 @@ public void TestTPMPubAreaAttestedDataMismatch() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -5122,10 +5099,10 @@ public void TestTPMPubAreaAttestedDataMismatch() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Hash value mismatch attested and pubArea", ex.Result.Message); } } @@ -5270,7 +5247,7 @@ public void TestTPMMissingX5c() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -5278,10 +5255,10 @@ public void TestTPMMissingX5c() .Add("x5c", null) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Neither x5c nor ECDAA were found in the TPM attestation statement", ex.Result.Message); } } @@ -5426,7 +5403,7 @@ public void TestX5cNotArray() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -5434,10 +5411,10 @@ public void TestX5cNotArray() .Add("x5c", "string") .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Neither x5c nor ECDAA were found in the TPM attestation statement", ex.Result.Message); } } @@ -5582,7 +5559,7 @@ public void TestTPMX5cCountZero() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -5590,10 +5567,10 @@ public void TestTPMX5cCountZero() .Add("x5c", CBORObject.NewArray()) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Neither x5c nor ECDAA were found in the TPM attestation statement", ex.Result.Message); } } @@ -5738,7 +5715,7 @@ public void TestTPMX5cValuesNull() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -5746,10 +5723,10 @@ public void TestTPMX5cValuesNull() .Add("x5c", CBORObject.NewArray().Add(null)) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Malformed x5c in TPM attestation", ex.Result.Message); } } @@ -5894,7 +5871,7 @@ public void TestTPMX5cValuesCountZero() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -5902,10 +5879,10 @@ public void TestTPMX5cValuesCountZero() .Add("x5c", CBORObject.NewArray().Add(CBORObject.Null)) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Malformed x5c in TPM attestation", ex.Result.Message); } } @@ -6050,7 +6027,7 @@ public void TestTPMFirstX5cValueNotByteString() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -6058,10 +6035,10 @@ public void TestTPMFirstX5cValueNotByteString() .Add("x5c", "x".ToArray()) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Malformed x5c in TPM attestation", ex.Result.Message); } } @@ -6206,7 +6183,7 @@ public void TestTPMFirstX5cValueByteStringZeroLen() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -6214,10 +6191,10 @@ public void TestTPMFirstX5cValueByteStringZeroLen() .Add("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0]))) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Malformed x5c in TPM attestation", ex.Result.Message); } } @@ -6362,7 +6339,7 @@ public void TestTPMBadSignature() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); signature[signature.Length - 1] ^= 0xff; _attestationObject.Add("attStmt", CBORObject.NewMap() @@ -6371,10 +6348,10 @@ public void TestTPMBadSignature() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Bad signature in TPM with aikCert", ex.Result.Message); } } @@ -6522,7 +6499,7 @@ public void TestTPMAikCertNotV3() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -6530,10 +6507,10 @@ public void TestTPMAikCertNotV3() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("aikCert must be V3", ex.Result.Message); } } @@ -6679,7 +6656,7 @@ public void TestTPMAikCertSubjectNotEmpty() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -6687,10 +6664,10 @@ public void TestTPMAikCertSubjectNotEmpty() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("aikCert subject must be empty", ex.Result.Message); } } @@ -6835,7 +6812,7 @@ public void TestTPMAikCertSANMissing() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -6843,10 +6820,10 @@ public void TestTPMAikCertSANMissing() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("SAN missing from TPM attestation certificate", ex.Result.Message); } } @@ -6996,7 +6973,7 @@ public void TestTPMAikCertSANZeroLen() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -7004,10 +6981,10 @@ public void TestTPMAikCertSANZeroLen() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("SAN missing from TPM attestation certificate", ex.Result.Message); } } @@ -7158,7 +7135,7 @@ public void TestTPMAikCertSANNoManufacturer() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -7166,10 +7143,10 @@ public void TestTPMAikCertSANNoManufacturer() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("SAN missing TPMManufacturer, TPMModel, or TPMVersion from TPM attestation certificate", ex.Result.Message); } } @@ -7320,7 +7297,7 @@ public void TestTPMAikCertSANNoModel() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -7328,10 +7305,10 @@ public void TestTPMAikCertSANNoModel() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("SAN missing TPMManufacturer, TPMModel, or TPMVersion from TPM attestation certificate", ex.Result.Message); } } @@ -7482,7 +7459,7 @@ public void TestTPMAikCertSANNoVersion() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -7490,10 +7467,10 @@ public void TestTPMAikCertSANNoVersion() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("SAN missing TPMManufacturer, TPMModel, or TPMVersion from TPM attestation certificate", ex.Result.Message); } } @@ -7644,7 +7621,7 @@ public void TestTPMAikCertSANInvalidManufacturer() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -7652,10 +7629,10 @@ public void TestTPMAikCertSANInvalidManufacturer() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("Invalid TPM manufacturer found parsing TPM attestation", ex.Result.Message); } } @@ -7800,7 +7777,7 @@ public void TestTPMAikCertEKUMissingTCGKP() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -7808,10 +7785,10 @@ public void TestTPMAikCertEKUMissingTCGKP() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("aikCert EKU missing tcg-kp-AIKCertificate OID", ex.Result.Message); } } @@ -7956,7 +7933,7 @@ public void TestTPMAikCertCATrue() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -7964,10 +7941,10 @@ public void TestTPMAikCertCATrue() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("aikCert Basic Constraints extension CA component must be false", ex.Result.Message); } } @@ -8112,7 +8089,7 @@ public void TestTPMAikCertMisingAAGUID() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -8120,27 +8097,23 @@ public void TestTPMAikCertMisingAAGUID() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); - - (Fido2.CredentialMakeResult, AssertionVerificationResult) res = (null, null); - res = MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c).Result; - Assert.Equal(string.Empty, res.Item1.ErrorMessage); - Assert.Equal("ok", res.Item1.Status); - Assert.Equal(_aaguid, res.Item1.Result.Aaguid); - Assert.Equal(_signCount, res.Item1.Result.Counter); - Assert.Equal("tpm", res.Item1.Result.CredType); - Assert.Equal(_credentialID, res.Item1.Result.CredentialId); - Assert.Null(res.Item1.Result.ErrorMessage); - Assert.Equal(_credentialPublicKey.GetBytes(), res.Item1.Result.PublicKey); - Assert.Null(res.Item1.Result.Status); - Assert.Equal("Test User", res.Item1.Result.User.DisplayName); - Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Item1.Result.User.Id); - Assert.Equal("testuser", res.Item1.Result.User.Name); - Assert.Equal(_signCount, res.Item2.Counter - 1); - Assert.Equal(new byte[] { 0xf1, 0xd0 }, res.Item2.CredentialId); - Assert.Equal(string.Empty, res.Item2.ErrorMessage); - Assert.Equal("ok", res.Item2.Status); + .Add("pubArea", pubArea)); + + + var res = MakeAttestationResponse().Result; + + Assert.Equal(string.Empty, res.ErrorMessage); + Assert.Equal("ok", res.Status); + Assert.Equal(_aaguid, res.Result.Aaguid); + Assert.Equal(_signCount, res.Result.Counter); + Assert.Equal("tpm", res.Result.CredType); + Assert.Equal(_credentialID, res.Result.CredentialId); + Assert.Null(res.Result.ErrorMessage); + Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey); + Assert.Null(res.Result.Status); + Assert.Equal("Test User", res.Result.User.DisplayName); + Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id); + Assert.Equal("testuser", res.Result.User.Name); } } } @@ -8286,7 +8259,7 @@ public void TestTPMAikCertAAGUIDNotMatchAuthData() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -8294,10 +8267,10 @@ public void TestTPMAikCertAAGUIDNotMatchAuthData() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("aaguid malformed, expected f1d0f1d0-f1d0-f1d0-f1d0-f1d0f1d0f1d0, got d0f1d0f1-d0f1-d0f1-f1d0-f1d0f1d0f1d0", ex.Result.Message); } } @@ -8442,7 +8415,7 @@ public void TestTPMECDAANotSupported() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -8450,16 +8423,16 @@ public void TestTPMECDAANotSupported() .Add("ecdaaKeyId", new byte[0]) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); + - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); + var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); Assert.Equal("ECDAA support for TPM attestation is not yet implemented", ex.Result.Message); } } } - /* + [Fact] public void TestTPMRSATemplate() { @@ -8599,7 +8572,7 @@ public void TestTPMRSATemplate() new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer ); - byte[] signature = SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); + byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); _attestationObject.Add("attStmt", CBORObject.NewMap() .Add("ver", "2.0") @@ -8607,15 +8580,12 @@ public void TestTPMRSATemplate() .Add("x5c", X5c) .Add("sig", signature) .Add("certInfo", certInfo) - .Add("pubArea", pubArea)) - .Add("authData", _authData); + .Add("pubArea", pubArea)); - var ex = Assert.ThrowsAsync(() => MakeAttestationResponse(_attestationObject, (COSE.KeyType)param[0], (COSE.Algorithm)param[1], rsa: rsaAtt, X5c: X5c)); - Assert.Equal("Invalid TPM_ALG_ID found in TPM2B_NAME", ex.Result.Message); + var res = MakeAttestationResponse(); } } } - */ internal static byte[] CreatePubArea(byte[] type, byte[] alg, byte[] attributes, byte[] policy, byte[] symmetric, byte[] scheme, byte[] keyBits, byte[] exponent, byte[] curveID, byte[] kdf, byte[] unique) diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index af7d49f3..ce7cdda4 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -133,6 +133,17 @@ public byte[] _clientDataHash return sha.ComputeHash(_clientDataJson); } } + + public byte[] _data + { + get + { + byte[] data = new byte[_authData.Length + _clientDataHash.Length]; + Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); + Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); + return data; + } + } public byte[] _credentialID; public const AuthenticatorFlags _flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; public ushort _signCount; @@ -241,163 +252,46 @@ public async Task MakeAttestationResponse() return credentialMakeResult; } - public async Task<(Fido2.CredentialMakeResult, AssertionVerificationResult)> MakeAttestationResponse(CBORObject attestationObject, COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv = COSE.EllipticCurve.P256, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null, CBORObject X5c = null) - { - const string rp = "fido2.azurewebsites.net"; - byte[] rpId = Encoding.UTF8.GetBytes(rp); - var rpIdHash = SHA256.Create().ComputeHash(rpId); - var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + internal byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv) + { + ECDsa ecdsa = null; + RSA rsa = null; + byte[] expandedPrivateKey = null, publicKey = null; switch (kty) { case COSE.KeyType.EC2: { - if (ecdsa == null) - { - ecdsa = MakeECDsa(alg, crv); - } - var ecparams = ecdsa.ExportParameters(true); - _credentialPublicKey = MakeCredentialPublicKey(kty, alg, crv, ecparams.Q.X, ecparams.Q.Y); + ecdsa = MakeECDsa(alg, crv); break; } case COSE.KeyType.RSA: { - if (rsa == null) - { - rsa = RSA.Create(); - } - var rsaparams = rsa.ExportParameters(true); - _credentialPublicKey = MakeCredentialPublicKey(kty, alg, rsaparams.Modulus, rsaparams.Exponent); + rsa = RSA.Create(); break; } case COSE.KeyType.OKP: { - byte[] publicKey = null; - if (expandedPrivateKey == null) - { - MakeEdDSA(out var privateKeySeed, out publicKey, out expandedPrivateKey); - } - - _credentialPublicKey = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); + MakeEdDSA(out var privateKeySeed, out publicKey, out expandedPrivateKey); break; } - throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); - } - - - var rng = RandomNumberGenerator.Create(); - - var sha = SHA256.Create(); - - var userHandle = new byte[16]; - rng.GetBytes(userHandle); - - var lib = new Fido2(new Fido2Configuration() - { - ServerDomain = rp, - ServerName = rp, - Origin = rp, - }); - - var clientData = new - { - Type = "webauthn.create", - Challenge = _challenge, - Origin = rp, - }; - - byte[] data = new byte[_authData.Length + _clientDataHash.Length]; - Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); - Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); - - if (!attestationObject.ContainsKey("authData")) - { - attestationObject.Add("authData", _authData); - } - - if (attestationObject["fmt"].AsString().Equals("packed")) - { - byte[] signature = SignData(kty, alg, data, ecdsa, rsa, expandedPrivateKey); - - if (attestationObject.ContainsKey("attStmt")) - { - if (!attestationObject["attStmt"].ContainsKey("sig")) - { - attestationObject["attStmt"].Add("sig", signature); - } - - if (X5c != null) - { - attestationObject["attStmt"].Add("x5c", X5c); - } - } + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); } - var attestationResponse = new AuthenticatorAttestationRawResponse - { - Type = PublicKeyCredentialType.PublicKey, - Id = new byte[] { 0xf1, 0xd0 }, - RawId = new byte[] { 0xf1, 0xd0 }, - Response = new AuthenticatorAttestationRawResponse.ResponseData() - { - AttestationObject = attestationObject.EncodeToBytes(), - ClientDataJson = _clientDataJson, - } - }; - - var origChallenge = new CredentialCreateOptions - { - Attestation = AttestationConveyancePreference.Direct, - AuthenticatorSelection = new AuthenticatorSelection - { - AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, - RequireResidentKey = true, - UserVerification = UserVerificationRequirement.Required, - }, - Challenge = _challenge, - ErrorMessage = "", - PubKeyCredParams = new List() - { - new PubKeyCredParam - { - Alg = -7, - Type = PublicKeyCredentialType.PublicKey, - } - }, - Rp = new PublicKeyCredentialRpEntity(rp, rp, ""), - Status = "ok", - User = new Fido2User - { - Name = "testuser", - Id = Encoding.UTF8.GetBytes("testuser"), - DisplayName = "Test User", - }, - Timeout = 60000, - }; - - IsCredentialIdUniqueToUserAsyncDelegate callback = (args) => - { - return Task.FromResult(true); - }; - - var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, origChallenge, callback); - - var assertionVerificationResult = await MakeAssertionResponse(kty, alg, crv, _credentialPublicKey, (ushort)credentialMakeResult.Result.Counter, ecdsa, rsa, expandedPrivateKey); - //r assertionVerificationResult = await MakeAssertionResponse(kty, alg, crv, new CredentialPublicKey(credentialMakeResult.Result.PublicKey), (ushort)credentialMakeResult.Result.Counter, ecdsa, rsa, expandedPrivateKey); - - return (credentialMakeResult, assertionVerificationResult); + return SignData(kty, alg, crv, ecdsa, rsa, expandedPrivateKey, publicKey); } - internal static byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, byte[] data, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null) + + internal byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve curve, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null, byte[] publicKey = null) { - byte[] signature = null; switch (kty) { case COSE.KeyType.EC2: { - signature = ecdsa.SignData(data, CryptoUtils.algMap[(int)alg]); - signature = EcDsaSigFromSig(signature, ecdsa.KeySize); - break; + var ecparams = ecdsa.ExportParameters(true); + _credentialPublicKey = MakeCredentialPublicKey(kty, alg, curve, ecparams.Q.X, ecparams.Q.Y); + var signature = ecdsa.SignData(_data, CryptoUtils.algMap[(int)alg]); + return EcDsaSigFromSig(signature, ecdsa.KeySize); } case COSE.KeyType.RSA: { @@ -419,23 +313,65 @@ internal static byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, byte[] dat default: throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}"); } - signature = rsa.SignData(data, CryptoUtils.algMap[(int)alg], padding); - break; + + var rsaparams = rsa.ExportParameters(true); + _credentialPublicKey = MakeCredentialPublicKey(kty, alg, rsaparams.Modulus, rsaparams.Exponent); + return rsa.SignData(_data, CryptoUtils.algMap[(int)alg], padding); } case COSE.KeyType.OKP: { - signature = Ed25519.Sign(data, expandedPrivateKey); - break; + _credentialPublicKey = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); + return Ed25519.Sign(_data, expandedPrivateKey); } default: throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); } - - return signature; } - } - + } + + internal static byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, byte[] data, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null) + { + switch (kty) + { + case COSE.KeyType.EC2: + { + var signature = ecdsa.SignData(data, CryptoUtils.algMap[(int)alg]); + return EcDsaSigFromSig(signature, ecdsa.KeySize); + } + case COSE.KeyType.RSA: + { + RSASignaturePadding padding; + switch (alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.PS256: + case COSE.Algorithm.PS384: + case COSE.Algorithm.PS512: + padding = RSASignaturePadding.Pss; + break; + + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + default: + throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}"); + } + return rsa.SignData(data, CryptoUtils.algMap[(int)alg], padding); + } + case COSE.KeyType.OKP: + { + return Ed25519.Sign(data, expandedPrivateKey); + } + + default: + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); + } + } + + [Fact] public void TestStringIsSerializable() { @@ -967,53 +903,6 @@ internal static async Task MakeAssertionResponse(CO return await lib.MakeAssertionAsync(response, options, cpk.GetBytes(), signCount, callback); } - internal static byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, byte[] data, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null) - { - byte[] signature = null; - switch (kty) - { - case COSE.KeyType.EC2: - { - signature = ecdsa.SignData(data, CryptoUtils.algMap[(int)alg]); - signature = EcDsaSigFromSig(signature, ecdsa.KeySize); - break; - } - case COSE.KeyType.RSA: - { - RSASignaturePadding padding; - switch (alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms - { - case COSE.Algorithm.PS256: - case COSE.Algorithm.PS384: - case COSE.Algorithm.PS512: - padding = RSASignaturePadding.Pss; - break; - - case COSE.Algorithm.RS1: - case COSE.Algorithm.RS256: - case COSE.Algorithm.RS384: - case COSE.Algorithm.RS512: - padding = RSASignaturePadding.Pkcs1; - break; - default: - throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}"); - } - signature = rsa.SignData(data, CryptoUtils.algMap[(int)alg], padding); - break; - } - case COSE.KeyType.OKP: - { - signature = Ed25519.Sign(data, expandedPrivateKey); - break; - } - - default: - throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); - } - - return signature; - } - internal static void MakeEdDSA(out byte[] privateKeySeed, out byte[] publicKey, out byte[] expandedPrivateKey) { using (var rng = RandomNumberGenerator.Create()) From 028ca0bcc9eea2116e426a2bcf9a14775375ec80 Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Wed, 13 May 2020 09:42:51 -0400 Subject: [PATCH 10/13] Update an Android Key test to StartsWith instead of Equal --- Test/Attestation/AndroidKey.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/Attestation/AndroidKey.cs b/Test/Attestation/AndroidKey.cs index 4e67cff4..4e38179a 100644 --- a/Test/Attestation/AndroidKey.cs +++ b/Test/Attestation/AndroidKey.cs @@ -143,7 +143,7 @@ public void TestAndroidKeyInvalidPublicKey() var X5c = CBORObject.NewArray().Add(CBORObject.FromObject(attestnCert)); _attestationObject["attStmt"].Set("x5c", X5c); var ex = Assert.ThrowsAsync(() => MakeAttestationResponse()); - Assert.Equal("Failed to extract public key from android key: Cannot find the requested object", ex.Result.Message); + Assert.StartsWith("Failed to extract public key from android key: ", ex.Result.Message); } [Fact] From 1ff9c7bc0f3525d7ee8b9059c496ab4b5cc33e2b Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Fri, 15 May 2020 09:35:16 -0400 Subject: [PATCH 11/13] Fix test data file name capitalization --- Test/Fido2Tests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index ce7cdda4..e3590be5 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -418,8 +418,8 @@ public async Task TestFido2AssertionAsync() //var key2 = "45-43-53-31-20-00-00-00-1D-60-44-D7-92-A0-0C-1E-3B-F9-58-5A-28-43-92-FD-F6-4F-BB-7F-8E-86-33-38-30-A4-30-5D-4E-2C-71-E3-53-3C-7B-98-81-99-FE-A9-DA-D9-24-8E-04-BD-C7-86-40-D3-03-1E-6E-00-81-7D-85-C3-A2-19-C9-21-85-8D"; //var key2 = "45-43-53-31-20-00-00-00-A9-E9-12-2A-37-8A-F0-74-E7-BA-52-54-B0-91-55-46-DB-21-E5-2C-01-B8-FB-69-CD-E5-ED-02-B6-C3-16-E3-1A-59-16-C1-43-87-0D-04-B9-94-7F-CF-56-E5-AA-5E-96-8C-5B-27-8F-83-F4-E2-50-AB-B3-F6-28-A1-F8-9E"; - var options = JsonConvert.DeserializeObject(File.ReadAllText("./AttestationNoneOptions.json")); - var response = JsonConvert.DeserializeObject(File.ReadAllText("./AttestationNoneResponse.json")); + var options = JsonConvert.DeserializeObject(File.ReadAllText("./attestationNoneOptions.json")); + var response = JsonConvert.DeserializeObject(File.ReadAllText("./attestationNoneResponse.json")); var o = AuthenticatorAttestationResponse.Parse(response); await o.VerifyAsync(options, _config, (x) => Task.FromResult(true), _metadataService, null); From 3407c57052a5faf3af704ac3cd7825cd526a9cc8 Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Fri, 15 May 2020 11:13:51 -0400 Subject: [PATCH 12/13] Change tests to use async properly --- Test/Attestation/AndroidKey.cs | 4 +- Test/Attestation/FidoU2f.cs | 4 +- Test/Attestation/None.cs | 4 +- Test/Attestation/Packed.cs | 10 +- Test/Attestation/Tpm.cs | 163 +-------------------------------- Test/Fido2Tests.cs | 6 +- 6 files changed, 18 insertions(+), 173 deletions(-) diff --git a/Test/Attestation/AndroidKey.cs b/Test/Attestation/AndroidKey.cs index 4e38179a..f0056cdc 100644 --- a/Test/Attestation/AndroidKey.cs +++ b/Test/Attestation/AndroidKey.cs @@ -63,9 +63,9 @@ public AndroidKey() } } [Fact] - public void TestAndroidKey() + public async void TestAndroidKey() { - var res = MakeAttestationResponse().Result; + var res = await MakeAttestationResponse(); Assert.Equal(string.Empty, res.ErrorMessage); Assert.Equal("ok", res.Status); Assert.Equal(_aaguid, res.Result.Aaguid); diff --git a/Test/Attestation/FidoU2f.cs b/Test/Attestation/FidoU2f.cs index 943d310a..9531a75d 100644 --- a/Test/Attestation/FidoU2f.cs +++ b/Test/Attestation/FidoU2f.cs @@ -51,9 +51,9 @@ public FidoU2f() } [Fact] - public void TestU2f() + public async void TestU2f() { - var res = MakeAttestationResponse().Result; + var res = await MakeAttestationResponse(); Assert.Equal(string.Empty, res.ErrorMessage); Assert.Equal("ok", res.Status); Assert.Equal(_aaguid, res.Result.Aaguid); diff --git a/Test/Attestation/None.cs b/Test/Attestation/None.cs index a18a8cec..b0b24865 100644 --- a/Test/Attestation/None.cs +++ b/Test/Attestation/None.cs @@ -16,13 +16,13 @@ public None() [Fact] public void TestNone() { - Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) + Fido2Tests._validCOSEParameters.ForEach(async delegate (object[] param) { _attestationObject.Add("attStmt", CBORObject.NewMap()); _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param); Fido2.CredentialMakeResult res = null; - res = MakeAttestationResponse().Result; + res = await MakeAttestationResponse(); Assert.Equal(string.Empty, res.ErrorMessage); Assert.Equal("ok", res.Status); diff --git a/Test/Attestation/Packed.cs b/Test/Attestation/Packed.cs index 9590a55f..41189f14 100644 --- a/Test/Attestation/Packed.cs +++ b/Test/Attestation/Packed.cs @@ -19,7 +19,7 @@ public Packed() [Fact] public void TestSelf() { - Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) + Fido2Tests._validCOSEParameters.ForEach(async delegate (object[] param) { var crv = (param.Length == 3) ? (COSE.EllipticCurve)param[2] : COSE.EllipticCurve.Reserved; @@ -29,7 +29,7 @@ public void TestSelf() .Add("alg", (COSE.Algorithm)param[1]) .Add("sig", signature)); - var res = MakeAttestationResponse().Result; + var res = await MakeAttestationResponse(); Assert.Equal(string.Empty, res.ErrorMessage); Assert.Equal("ok", res.Status); @@ -133,7 +133,7 @@ public void TestSigByteStringZeroLen() [Fact] public void TestFull() { - Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) + Fido2Tests._validCOSEParameters.ForEach(async delegate (object[] param) { if (COSE.KeyType.OKP == (COSE.KeyType)param[0]) { @@ -203,7 +203,7 @@ public void TestFull() .Add("sig", signature) .Add("x5c", X5c)); - res = MakeAttestationResponse().Result; + res = await MakeAttestationResponse(); } } break; @@ -261,7 +261,7 @@ public void TestFull() .Add("sig", signature) .Add("x5c", X5c)); - res = MakeAttestationResponse().Result; + res = await MakeAttestationResponse(); } } break; diff --git a/Test/Attestation/Tpm.cs b/Test/Attestation/Tpm.cs index b50bd1bf..866273f6 100644 --- a/Test/Attestation/Tpm.cs +++ b/Test/Attestation/Tpm.cs @@ -53,7 +53,7 @@ public Tpm() [Fact] public void TestTPM() { - Fido2Tests._validCOSEParameters.ForEach(delegate (object[] param) + Fido2Tests._validCOSEParameters.ForEach(async delegate (object[] param) { if (COSE.KeyType.OKP == (COSE.KeyType)param[0]) { @@ -374,7 +374,7 @@ public void TestTPM() break; } - var res = MakeAttestationResponse().Result; + var res = await MakeAttestationResponse(); Assert.Equal(string.Empty, res.ErrorMessage); Assert.Equal("ok", res.Status); @@ -7951,7 +7951,7 @@ public void TestTPMAikCertCATrue() } [Fact] - public void TestTPMAikCertMisingAAGUID() + public async void TestTPMAikCertMisingAAGUID() { var param = Fido2Tests._validCOSEParameters[3]; @@ -8100,7 +8100,7 @@ public void TestTPMAikCertMisingAAGUID() .Add("pubArea", pubArea)); - var res = MakeAttestationResponse().Result; + var res = await MakeAttestationResponse(); Assert.Equal(string.Empty, res.ErrorMessage); Assert.Equal("ok", res.Status); @@ -8431,162 +8431,7 @@ public void TestTPMECDAANotSupported() } } } - - [Fact] - public void TestTPMRSATemplate() - { - var param = Fido2Tests._validCOSEParameters[3]; - - var alg = (COSE.Algorithm)param[1]; - if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256) - tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray(); - if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384) - tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray(); - if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512) - tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray(); - if (alg == COSE.Algorithm.RS1) - tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray(); - - using (RSA rsaRoot = RSA.Create()) - { - RSASignaturePadding padding = RSASignaturePadding.Pss; - switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms - { - case COSE.Algorithm.RS1: - case COSE.Algorithm.RS256: - case COSE.Algorithm.RS384: - case COSE.Algorithm.RS512: - padding = RSASignaturePadding.Pkcs1; - break; - } - var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding); - rootRequest.CertificateExtensions.Add(caExt); - - using (rootCert = rootRequest.CreateSelfSigned( - notBefore, - notAfter)) - - using (var rsaAtt = RSA.Create()) - { - var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding); - - attRequest.CertificateExtensions.Add(notCAExt); - - attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt); - - attRequest.CertificateExtensions.Add(aikCertSanExt); - - attRequest.CertificateExtensions.Add(tcgKpAIKCertExt); - - byte[] serial = new byte[12]; - - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(serial); - } - using (X509Certificate2 publicOnly = attRequest.Create( - rootCert, - notBefore, - notAfter, - serial)) - { - attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt); - } - - var X5c = CBORObject.NewArray() - .Add(CBORObject.FromObject(attestnCert.RawData)) - .Add(CBORObject.FromObject(rootCert.RawData)); - var rsaparams = rsaAtt.ExportParameters(true); - - var cpk = CBORObject.NewMap(); - cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]); - cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]); - cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus); - cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent); - - _credentialPublicKey = new CredentialPublicKey(cpk); - - unique = rsaparams.Modulus; - exponent = rsaparams.Exponent; - type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray(); - - var pubArea = CreatePubArea( - type, // Type - tpmAlg, // Alg - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes - new byte[] { 0x00 }, // Policy - new byte[] { 0x00, 0x10 }, // Symmetric - new byte[] { 0x00, 0x10 }, // Scheme - new byte[] { 0x80, 0x00 }, // KeyBits - exponent?.ToArray(), // Exponent - curveId?.ToArray(), // CurveID - kdf?.ToArray(), // KDF - unique.ToArray() // Unique - ); - - byte[] data = new byte[_authData.Length + _clientDataHash.Length]; - Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length); - Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length); - - byte[] hashedData; - byte[] hashedPubArea; - var hashAlg = CryptoUtils.algMap[(int)alg]; - using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg])) - { - hashedData = hasher.ComputeHash(data); - hashedPubArea = hasher.ComputeHash(pubArea); - } - IEnumerable extraData = BitConverter - .GetBytes((UInt16)hashedData.Length) - .Reverse() - .ToArray() - .Concat(hashedData); - - var tpmAlgToDigestSizeMap = new Dictionary - { - {TpmAlg.TPM_ALG_SHA1, (160/8) }, - {TpmAlg.TPM_ALG_SHA256, (256/8) }, - {TpmAlg.TPM_ALG_SHA384, (384/8) }, - {TpmAlg.TPM_ALG_SHA512, (512/8) } - }; - - var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray(); - - IEnumerable tpm2bName = new byte[] { } - .Concat(tpm2bNameLen) - .Concat(tpmAlg) - .Concat(hashedPubArea); - - var certInfo = CreateCertInfo( - new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic - new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type - new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner - extraData.ToArray(), // ExtraData - new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount - new byte[] { 0x00 }, // Safe - new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion - tpm2bName.ToArray(), // TPM2BName - new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer - ); - - byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null); - - _attestationObject.Add("attStmt", CBORObject.NewMap() - .Add("ver", "2.0") - .Add("alg", alg) - .Add("x5c", X5c) - .Add("sig", signature) - .Add("certInfo", certInfo) - .Add("pubArea", pubArea)); - - var res = MakeAttestationResponse(); - } - } - } - internal static byte[] CreatePubArea(byte[] type, byte[] alg, byte[] attributes, byte[] policy, byte[] symmetric, byte[] scheme, byte[] keyBits, byte[] exponent, byte[] curveID, byte[] kdf, byte[] unique) { diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index e3590be5..bf642902 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -704,15 +704,15 @@ internal static byte[] EcDsaSigFromSig(byte[] sig, int keySize) public void TestAssertionResponse() { AssertionVerificationResult avr; - _validCOSEParameters.ForEach(delegate (object[] param) + _validCOSEParameters.ForEach(async delegate (object[] param) { if (param.Length == 3) { - avr = MakeAssertionResponse((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]).Result; + avr = await MakeAssertionResponse((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]); } else { - avr = MakeAssertionResponse((COSE.KeyType)param[0], (COSE.Algorithm)param[1]).Result; + avr = await MakeAssertionResponse((COSE.KeyType)param[0], (COSE.Algorithm)param[1]); } Assert.Equal("", avr.ErrorMessage); Assert.Equal("ok", avr.Status); From 8fc05e2067b2790ece7cd47b5bffb280bd1079c9 Mon Sep 17 00:00:00 2001 From: Alex Seigler Date: Fri, 15 May 2020 11:49:08 -0400 Subject: [PATCH 13/13] Try to prevent test hangs --- Test/Test.csproj | 5 +++++ Test/xunit.runner.json | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 Test/xunit.runner.json diff --git a/Test/Test.csproj b/Test/Test.csproj index 7a0fa935..3f80bd41 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -33,4 +33,9 @@ + + + PreserveNewest + + diff --git a/Test/xunit.runner.json b/Test/xunit.runner.json new file mode 100644 index 00000000..3ad9c00e --- /dev/null +++ b/Test/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "parallelizeAssembly": false, + "parallelizeTestCollections": false +} \ No newline at end of file