Skip to content

Commit e18f1d7

Browse files
committed
Swappable MDS
1 parent 05ef199 commit e18f1d7

File tree

6 files changed

+69
-55
lines changed

6 files changed

+69
-55
lines changed

Fido2Demo/Controller.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ public class MyController : Controller
2626

2727
public MyController(IConfiguration config)
2828
{
29-
_lib = new Fido2(new Fido2.Configuration
29+
_lib = new Fido2(new Fido2.Configuration()
3030
{
3131
ServerDomain = config["fido2:serverDomain"],
3232
ServerName = "Fido2 test",
33-
Origin = config["fido2:origin"]
33+
Origin = config["fido2:origin"],
34+
MetadataService = MDSMetadata.Instance(config["fido2:MDSAccessKey"], config["fido2:MDSCacheDir"])
3435
});
3536
}
3637

Fido2Demo/TestController.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@ public class TestController : Controller
3030

3131
public TestController(IConfiguration config)
3232
{
33-
_lib = new Fido2(new Fido2NetLib.Fido2.Configuration
33+
_lib = new Fido2(new Fido2NetLib.Fido2.Configuration()
3434
{
3535
ServerDomain = config["fido2:serverDomain"],
3636
ServerName = "Fido2 test",
37-
Origin = config["fido2:origin"]
37+
Origin = config["fido2:origin"],
38+
MetadataService = MDSMetadata.Instance(config["fido2:MDSAccessKey"], config["fido2:MDSCacheDir"])
3839
});
3940
}
4041

fido2-net-lib.Test/UnitTest1.cs

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,8 @@ public async Task TestFido2AssertionAsync()
103103
var options = JsonConvert.DeserializeObject<CredentialCreateOptions>(File.ReadAllText("./AttestationNoneOptions.json"));
104104
var response = JsonConvert.DeserializeObject<AuthenticatorAttestationRawResponse>(File.ReadAllText("./AttestationNoneResponse.json"));
105105

106-
var fido2 = new Fido2NetLib.Fido2(new Fido2NetLib.Fido2.Configuration()
107-
{
108-
ServerDomain = "localhost",
109-
Origin = "https://localhost:44329",
110-
});
111-
112106
var o = AuthenticatorAttestationResponse.Parse(response);
113-
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null);
107+
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null, null);
114108

115109
var credId = "F1-3C-7F-08-3C-A2-29-E0-B4-03-E8-87-34-6E-FC-7F-98-53-10-3A-30-91-75-67-39-7A-D1-D8-AF-87-04-61-87-EF-95-31-85-60-F3-5A-1A-2A-CF-7D-B0-1D-06-B9-69-F9-AB-F4-EC-F3-07-3E-CF-0F-71-E8-84-E8-41-20";
116110
var allowedCreds = new List<PublicKeyCredentialDescriptor>() {
@@ -142,9 +136,8 @@ public async Task TestParsingAsync()
142136

143137
Assert.NotNull(jsonPost);
144138

145-
var fido2 = new Fido2NetLib.Fido2(new Fido2NetLib.Fido2.Configuration());
146139
var o = AuthenticatorAttestationResponse.Parse(jsonPost);
147-
await o.VerifyAsync(options, "https://localhost:44329", isCredentialIdUniqueToUser: (x) => Task.FromResult(true), requestTokenBindingId: null);
140+
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null, null);
148141
}
149142

150143
[Fact]
@@ -162,58 +155,52 @@ public async Task TestU2FAttestationAsync()
162155
{
163156
var jsonPost = JsonConvert.DeserializeObject<AuthenticatorAttestationRawResponse>(File.ReadAllText("./attestationResultsU2F.json"));
164157
var options = JsonConvert.DeserializeObject<CredentialCreateOptions>(File.ReadAllText("./attestationOptionsU2F.json"));
165-
var fido2 = new Fido2NetLib.Fido2(new Fido2NetLib.Fido2.Configuration());
166158
var o = AuthenticatorAttestationResponse.Parse(jsonPost);
167-
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null);
159+
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null, null);
168160
byte[] ad = o.AttestationObject.AuthData;
169161
}
170162
[Fact]
171163
public async Task TestPackedAttestationAsync()
172164
{
173165
var jsonPost = JsonConvert.DeserializeObject<AuthenticatorAttestationRawResponse>(File.ReadAllText("./attestationResultsPacked.json"));
174166
var options = JsonConvert.DeserializeObject<CredentialCreateOptions>(File.ReadAllText("./attestationOptionsPacked.json"));
175-
var fido2 = new Fido2NetLib.Fido2(new Fido2NetLib.Fido2.Configuration());
176167
var o = AuthenticatorAttestationResponse.Parse(jsonPost);
177-
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null);
168+
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null, null);
178169
byte[] ad = o.AttestationObject.AuthData;
179170
}
180171
[Fact]
181172
public async Task TestNoneAttestationAsync()
182173
{
183174
var jsonPost = JsonConvert.DeserializeObject<AuthenticatorAttestationRawResponse>(File.ReadAllText("./attestationResultsNone.json"));
184175
var options = JsonConvert.DeserializeObject<CredentialCreateOptions>(File.ReadAllText("./attestationOptionsNone.json"));
185-
var fido2 = new Fido2NetLib.Fido2(new Fido2NetLib.Fido2.Configuration());
186176
var o = AuthenticatorAttestationResponse.Parse(jsonPost);
187-
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null);
177+
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null, null);
188178
}
189179
[Fact]
190180
public async Task TestTPMSHA256AttestationAsync()
191181
{
192182
var jsonPost = JsonConvert.DeserializeObject<AuthenticatorAttestationRawResponse>(File.ReadAllText("./attestationTPMSHA256Response.json"));
193183
var options = JsonConvert.DeserializeObject<CredentialCreateOptions>(File.ReadAllText("./attestationTPMSHA256Options.json"));
194-
var fido2 = new Fido2NetLib.Fido2(new Fido2NetLib.Fido2.Configuration());
195184
var o = AuthenticatorAttestationResponse.Parse(jsonPost);
196-
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null);
185+
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null, null);
197186
byte[] ad = o.AttestationObject.AuthData;
198187
}
199188
[Fact]
200189
public async Task TestTPMSHA1AttestationAsync()
201190
{
202191
var jsonPost = JsonConvert.DeserializeObject<AuthenticatorAttestationRawResponse>(File.ReadAllText("./attestationTPMSHA1Response.json"));
203192
var options = JsonConvert.DeserializeObject<CredentialCreateOptions>(File.ReadAllText("./attestationTPMSHA1Options.json"));
204-
var fido2 = new Fido2NetLib.Fido2(new Fido2NetLib.Fido2.Configuration());
205193
var o = AuthenticatorAttestationResponse.Parse(jsonPost);
206-
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null);
194+
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null, null);
207195
byte[] ad = o.AttestationObject.AuthData;
208196
}
209197
[Fact]
210198
public async Task TestAndroidKeyAttestationAsync()
211199
{
212200
var jsonPost = JsonConvert.DeserializeObject<AuthenticatorAttestationRawResponse>(File.ReadAllText("./attestationAndroidKeyResponse.json"));
213201
var options = JsonConvert.DeserializeObject<CredentialCreateOptions>(File.ReadAllText("./attestationAndroidKeyOptions.json"));
214-
var fido2 = new Fido2NetLib.Fido2(new Fido2NetLib.Fido2.Configuration());
215202
var o = AuthenticatorAttestationResponse.Parse(jsonPost);
216-
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null);
203+
await o.VerifyAsync(options, "https://localhost:44329", (x) => Task.FromResult(true), null, null);
217204
byte[] ad = o.AttestationObject.AuthData;
218205
}
219206
//public void TestHasCorrentAAguid()

fido2-net-lib/AuthenticatorAttestationResponse.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public static AuthenticatorAttestationResponse Parse(AuthenticatorAttestationRaw
6161
return response;
6262
}
6363

64-
public async Task<AttestationVerificationSuccess> VerifyAsync(CredentialCreateOptions originalOptions, string expectedOrigin, IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser, byte[] requestTokenBindingId)
64+
public async Task<AttestationVerificationSuccess> VerifyAsync(CredentialCreateOptions originalOptions, string expectedOrigin, IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser, IMetadataService metadataService, byte[] requestTokenBindingId)
6565
{
6666
AttestationType attnType;
6767
X509Certificate2[] trustPath = null;
@@ -532,14 +532,10 @@ public async Task<AttestationVerificationSuccess> VerifyAsync(CredentialCreateOp
532532
// use aaguid (authData.AttData.Aaguid) to find root certs in metadata
533533
// use root plus trustPath to build trust chain
534534

535-
MetadataTOCPayloadEntry entry = null;
536-
var metadata = MDSMetadata.Instance();
537-
if (null != metadata)
535+
if (null != metadataService)
538536
{
539-
if (true == metadata.payload.ContainsKey(authData.AttData.GuidAaguid))
540-
{
541-
entry = metadata.payload[authData.AttData.GuidAaguid];
542-
}
537+
MetadataTOCPayloadEntry entry = metadataService.GetEntry(authData.AttData.GuidAaguid);
538+
543539
if (null != entry)
544540
{
545541
if (entry.Hash != entry.MetadataStatement.Hash) throw new Fido2VerificationException("Authenticator metadata statement has invalid hash");

fido2-net-lib/Fido2NetLib.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,23 @@ public class Configuration
4343
/// Server origin, including protocol host and port.
4444
/// </summary>
4545
public string Origin { get; set; }
46+
47+
/// <summary>
48+
/// MetdataService to verify metadata statements https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-metadata-service-v2.0-rd-20180702.html
49+
/// </summary>
50+
public IMetadataService MetadataService { get; set; }
51+
52+
/// <summary>
53+
/// Create the configuration for Fido2
54+
/// </summary>
55+
public Configuration()
56+
{
57+
}
58+
59+
public Configuration(IMetadataService metadataService)
60+
{
61+
MetadataService = metadataService;
62+
}
4663
}
4764

4865
private Configuration Config { get; }
@@ -90,7 +107,7 @@ public CredentialCreateOptions RequestNewCredential(User user, List<PublicKeyCre
90107
public async Task<CredentialMakeResult> MakeNewCredentialAsync(AuthenticatorAttestationRawResponse attestationResponse, CredentialCreateOptions origChallenge, IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser, byte[] requestTokenBindingId = null)
91108
{
92109
var parsedResponse = AuthenticatorAttestationResponse.Parse(attestationResponse);
93-
var success = await parsedResponse.VerifyAsync(origChallenge, Config.Origin, isCredentialIdUniqueToUser, requestTokenBindingId);
110+
var success = await parsedResponse.VerifyAsync(origChallenge, Config.Origin, isCredentialIdUniqueToUser, Config.MetadataService, requestTokenBindingId);
94111

95112
// todo: Set Errormessage etc.
96113
return new CredentialMakeResult { Status = "ok", ErrorMessage = string.Empty, Result = success };

fido2-net-lib/MetadataService.cs

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Linq;
1+
using System;
2+
using System.Linq;
23
using System.Security.Cryptography;
34
using System.Security.Cryptography.X509Certificates;
45
using Microsoft.Extensions.Configuration;
@@ -303,42 +304,47 @@ public class MetadataStatement
303304
public ExtensionDescriptor[] SupportedExtensions { get; set; }
304305
public string Hash { get; set; }
305306
}
306-
public sealed class MDSMetadata
307+
308+
public interface IMetadataService
309+
{
310+
MetadataTOCPayloadEntry GetEntry(Guid aaguid);
311+
}
312+
313+
public sealed class MDSMetadata : IMetadataService
307314
{
308315
private static volatile MDSMetadata mDSMetadata;
309316
private static object syncRoot = new System.Object();
310-
public Microsoft.Extensions.Configuration.IConfiguration Configuration { get; }
311317
public static readonly string mds1url = "https://mds.fidoalliance.org";
312318
public static readonly string mds2url = "https://mds2.fidoalliance.org";
313319
public static readonly string tokenParamName = "/?token=";
314320
private static string _accessToken;
315321
private static string _cacheDir;
316322
private string tocAlg;
317323

318-
private MDSMetadata()
324+
private MDSMetadata(string accessToken, string cachedirPath)
319325
{
320-
// Extract app secrets for development
321-
// https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-2.1&tabs=windows
322-
string env = System.Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
323-
if (string.IsNullOrWhiteSpace(env))
324-
{
325-
env = "Development";
326-
}
326+
//// Extract app secrets for development
327+
//// https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-2.1&tabs=windows
328+
//string env = System.Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
329+
//if (string.IsNullOrWhiteSpace(env))
330+
//{
331+
// env = "Development";
332+
//}
327333

328-
var builder = new Microsoft.Extensions.Configuration.ConfigurationBuilder();
334+
//var builder = new Microsoft.Extensions.Configuration.ConfigurationBuilder();
329335

330-
if (env == "Development")
331-
{
332-
builder.AddUserSecrets<MDSMetadata>();
333-
}
334-
Configuration = builder.Build();
336+
//if (env == "Development")
337+
//{
338+
// builder.AddUserSecrets<MDSMetadata>();
339+
//}
340+
//Configuration = builder.Build();
335341

336342
// We need either an access token or a cache directory, but prefer both
337343
// If we have only an access token, we can get metadata from directly from MDS and only cache in memory
338344
// If we have only a cache directory, we can read cached data (as long as it is not expired)
339345
// If we have both, we can read from either and update cache as necessary
340-
_accessToken = Configuration["MDSAccessToken"];
341-
_cacheDir = Configuration["CacheDir"];
346+
_accessToken = accessToken;
347+
_cacheDir = cachedirPath;
342348
if (0x30 != _accessToken.Length && 3 > _cacheDir.Length) throw new Fido2VerificationException("Either MDSAccessToken or CacheDir is required to instantiate Metadata instance");
343349

344350
payload = new System.Collections.Generic.Dictionary<System.Guid, MetadataTOCPayloadEntry>();
@@ -364,14 +370,14 @@ private MDSMetadata()
364370
// If the payload count is zero, we've failed to load metadata
365371
if (0 == payload.Count) throw new Fido2VerificationException("Failed to load MDS metadata");
366372
}
367-
public static MDSMetadata Instance()
373+
public static IMetadataService Instance(string accesskey, string cachedir)
368374
{
369375
if (null == mDSMetadata)
370376
{
371377
lock (syncRoot)
372378
{
373379
if (null == mDSMetadata)
374-
mDSMetadata = new MDSMetadata();
380+
mDSMetadata = new MDSMetadata(accesskey, cachedir);
375381
}
376382
}
377383
return mDSMetadata;
@@ -534,5 +540,11 @@ public void CustomTOCPayloadFromCache()
534540
}
535541
}
536542
}
543+
544+
public MetadataTOCPayloadEntry GetEntry(Guid aaguid)
545+
{
546+
mDSMetadata.payload.TryGetValue(aaguid, out var entry);
547+
return entry;
548+
}
537549
}
538550
}

0 commit comments

Comments
 (0)