Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Make TPM attestation format verifier work more like packed with regard to X5C #180

Merged
merged 10 commits into from
Jun 28, 2020
4 changes: 2 additions & 2 deletions Demo/Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
<ProjectReference Include="..\Src\Fido2.AspNet\Fido2.AspNet.csproj" />
<ProjectReference Include="..\Src\Fido2.Models\Fido2.Models.csproj" />
<ProjectReference Include="..\Src\Fido2\Fido2.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.5" />

<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.2" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />
Expand Down
1 change: 0 additions & 1 deletion Demo/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ public void ConfigureServices(IServiceCollection services)
options.TimestampDriftTolerance = Configuration.GetValue<int>("fido2:timestampDriftTolerance");
options.MDSAccessKey = Configuration["fido2:MDSAccessKey"];
options.MDSCacheDirPath = Configuration["fido2:MDSCacheDirPath"];
options.RequireValidAttestationRoot = Configuration.GetValue<bool>("fido2:requireValidAttestationRoot");
})
.AddCachedMetadataService(config =>
{
Expand Down
2 changes: 1 addition & 1 deletion Demo/TestController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public TestController(IOptions<Fido2Configuration> fido2Configuration)
{
ServerDomain = fido2Configuration.Value.ServerDomain,
ServerName = fido2Configuration.Value.ServerName,
Origin = _origin
Origin = _origin,
},
ConformanceTesting.MetadataServiceInstance(
System.IO.Path.Combine(fido2Configuration.Value.MDSCacheDirPath, @"Conformance"), _origin)
Expand Down
1 change: 0 additions & 1 deletion Demo/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"origin": "https://localhost:44329",
"timestampDriftTolerance": 300000,
"MDSAccessKey": null,
"requireValidAttestationRoot": false
},
"Logging": {
"IncludeScopes": false,
Expand Down
4 changes: 2 additions & 2 deletions ExternalLibs/AsnElt/AsnElt/AsnIO.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
Expand Down Expand Up @@ -125,7 +125,7 @@ public static byte[] FindBER(byte[] buf,
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));
pemType = new StringBuilder(str.Substring(p, px - p)).ToString();
str = str.Substring(r, q - r);
}
}
Expand Down
12 changes: 6 additions & 6 deletions Src/Fido2.AspNet/Fido2.AspNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
<ProjectReference Include="..\Fido2\Fido2.csproj" />
<ProjectReference Include="..\Fido2.Models\Fido2.Models.csproj" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.5" />

<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.5" />
</ItemGroup>

<ItemGroup>
Expand Down
5 changes: 0 additions & 5 deletions Src/Fido2.Models/Fido2Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ public class Fido2Configuration
/// </summary>
public int TimestampDriftTolerance { get; set; } = 0; //Pretty sure 0 will never work - need a better default?

/// <summary>
/// When checking attestation, require the attestation to chain to a known root
/// </summary>
public bool RequireValidAttestationRoot { get; set; } = false;

/// <summary>
/// The size of the challenges sent to the client
/// </summary>
Expand Down
19 changes: 11 additions & 8 deletions Src/Fido2/AttestationFormat/AndroidKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,14 @@ public static bool IsPurposeSign(byte[] attExtBytes)

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
// 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields
// (handled in base class)
if (0 == attStmt.Keys.Count || 0 == attStmt.Values.Count)
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 android-key attestation signature");
// 2a. Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash
// 2. 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)
throw new Fido2VerificationException("Malformed x5c in android-key attestation");
Expand Down Expand Up @@ -185,16 +186,15 @@ public override void Verify()
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.
// 3. 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("Incorrect credentialPublicKey in android key attestation");

// Verify that in the attestation certificate extension data:
// 4. Verify that the attestationChallenge field in the attestation certificate extension data is identical to clientDataHash
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.
try
{
var attestationChallenge = GetAttestationChallenge(attExtBytes);
Expand All @@ -205,15 +205,18 @@ public override void Verify()
{
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.

// 5. Verify the following using the appropriate authorization list from the attestation certificate extension data

// 5a. 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");

// 3. The value in the AuthorizationList.origin field is equal to KM_ORIGIN_GENERATED ( which == 0).
// 5bi. The value in the AuthorizationList.origin field is equal to KM_ORIGIN_GENERATED ( which == 0).
if (false == IsOriginGenerated(attExtBytes))
throw new Fido2VerificationException("Found origin field not set to KM_ORIGIN_GENERATED in android key attestation certificate extension");

// 4. The value in the AuthorizationList.purpose field is equal to KM_PURPOSE_SIGN (which == 2).
// 5bii. The value in the AuthorizationList.purpose field is equal to KM_PURPOSE_SIGN (which == 2).
if (false == IsPurposeSign(attExtBytes))
throw new Fido2VerificationException("Found purpose field not set to KM_PURPOSE_SIGN in android key attestation certificate extension");
}
Expand Down
15 changes: 9 additions & 6 deletions Src/Fido2/AttestationFormat/AndroidSafetyNet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ private X509Certificate2 GetX509Certificate(string certString)

public override void Verify()
{
// Verify that attStmt is valid CBOR conforming to the syntax defined above and perform
// 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform
// CBOR decoding on it to extract the contained fields
// (handled in base class)
if ((CBORType.TextString != attStmt["ver"].Type) ||
(0 == attStmt["ver"].AsString().Length))
throw new Fido2VerificationException("Invalid version in SafetyNet data");

// Verify that response is a valid SafetyNet response of version ver
// 2. Verify that response is a valid SafetyNet response of version ver
var ver = attStmt["ver"].AsString();

if ((CBORType.ByteString != attStmt["response"].Type) ||
Expand Down Expand Up @@ -140,7 +141,7 @@ public override void Verify()
throw new Fido2VerificationException(string.Format("SafetyNet timestampMs must be present and between one minute ago and now, got: {0}", timestampMs.ToString()));
}

// Verify that the nonce in the response is identical to the SHA-256 hash of the concatenation of authenticatorData and clientDataHash
// 3. Verify that the nonce in the response is identical to the SHA-256 hash of the concatenation of authenticatorData and clientDataHash
if ("" == nonce)
throw new Fido2VerificationException("Nonce value not found in SafetyNet attestation");

Expand All @@ -167,15 +168,17 @@ public override void Verify()
);
}

// Verify that the attestation certificate is issued to the hostname "attest.android.com"
// 4. Let attestationCert be the attestation certificate
var subject = certs[0].GetNameInfo(X509NameType.DnsName, false);

// 5. Verify that the attestation certificate is issued to the hostname "attest.android.com"
if (false == ("attest.android.com").Equals(subject))
throw new Fido2VerificationException(string.Format("SafetyNet attestation cert DnsName invalid, want {0}, got {1}", "attest.android.com", subject));

// 6. Verify that the ctsProfileMatch attribute in the payload of response is true
if (null == ctsProfileMatch)
throw new Fido2VerificationException("SafetyNet response ctsProfileMatch missing");

// Verify that the ctsProfileMatch attribute in the payload of response is true

if (true != ctsProfileMatch)
throw new Fido2VerificationException("SafetyNet response ctsProfileMatch false");
}
Expand Down
28 changes: 28 additions & 0 deletions Src/Fido2/AttestationFormat/AttestationFormat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,34 @@ internal static int U2FTransportsFromAttnCert(X509ExtensionCollection exts)
}
return u2ftransports;
}

internal bool ValidateTrustChain(X509Certificate2[] trustPath, X509Certificate2[] attestationRootCertificates)
{
foreach (var attestationRootCert in attestationRootCertificates)
{
var chain = new X509Chain();
chain.ChainPolicy.ExtraStore.Add(attestationRootCert);
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
if (trustPath.Length > 1)
{
foreach (var cert in trustPath.Skip(1).Reverse())
{
chain.ChainPolicy.ExtraStore.Add(cert);
}
}
var valid = chain.Build(trustPath[0]);

// because we are using AllowUnknownCertificateAuthority we have to verify that the root matches ourselves
var chainRoot = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
valid = valid && chainRoot.RawData.SequenceEqual(attestationRootCert.RawData);

if (true == valid)
return true;
}
return false;
}

public abstract void Verify();
}
}
Loading