Skip to content

Commit 9aeaa4b

Browse files
committed
Add MDS architecture documentation
1 parent 31e84d1 commit 9aeaa4b

File tree

1 file changed

+224
-0
lines changed

1 file changed

+224
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# FIDO2 Metadata Service (MDS) Developer Guide
2+
3+
This guide explains how the FIDO2 Metadata Service (MDS) components work together and how to implement and register custom metadata services and repositories in the FIDO2 .NET Library.
4+
5+
## Architecture Overview
6+
7+
The MDS system follows a clean separation of concerns with two main layers:
8+
9+
```
10+
IMetadataService (Caching/Access Layer)
11+
12+
IMetadataRepository (Data Source Layer)
13+
```
14+
15+
### Key Concepts
16+
17+
- **`IMetadataRepository`** - Handles the complexity of fetching, validating, and parsing metadata from various sources (FIDO Alliance, local files, conformance endpoints)
18+
- **`IMetadataService`** - Provides a simple caching wrapper to allow sourcing attestation data from multiple repositories and support multi-level caching strategies
19+
- **Registration API** - Fluent builder pattern for easy configuration and dependency injection
20+
21+
## Core Interfaces
22+
23+
### IMetadataService
24+
25+
The service layer provides a simple API for retrieving metadata entries:
26+
27+
```csharp
28+
public interface IMetadataService
29+
{
30+
/// <summary>
31+
/// Gets the metadata payload entry by AAGUID asynchronously.
32+
/// </summary>
33+
Task<MetadataBLOBPayloadEntry?> GetEntryAsync(Guid aaguid, CancellationToken cancellationToken = default);
34+
35+
/// <summary>
36+
/// Gets a value indicating whether conformance testing mode is active. This should return false in production.
37+
/// </summary>
38+
bool ConformanceTesting();
39+
}
40+
```
41+
42+
### IMetadataRepository
43+
44+
The repository layer handles the heavy lifting of metadata retrieval and validation:
45+
46+
```csharp
47+
public interface IMetadataRepository
48+
{
49+
/// <summary>
50+
/// Downloads and validates the metadata BLOB from the source.
51+
/// </summary>
52+
Task<MetadataBLOBPayload> GetBLOBAsync(CancellationToken cancellationToken = default);
53+
54+
/// <summary>
55+
/// Gets a specific metadata statement from the BLOB.
56+
/// </summary>
57+
Task<MetadataStatement?> GetMetadataStatementAsync(MetadataBLOBPayload blob, MetadataBLOBPayloadEntry entry, CancellationToken cancellationToken = default);
58+
}
59+
```
60+
61+
## Built-in Implementations
62+
63+
### Repositories
64+
65+
| Repository | Purpose | Features |
66+
| ---------------------------------- | --------------------------- | ------------------------------------------------ |
67+
| **Fido2MetadataServiceRepository** | Official FIDO Alliance MDS3 | JWT validation, certificate chains, CRL checking |
68+
| **FileSystemMetadataRepository** | Local file storage | Fast local access, offline/development/testing |
69+
| **ConformanceMetadataRepository** | FIDO conformance testing | Multiple test endpoints, fake certificates |
70+
71+
### Services
72+
73+
| Service | Purpose | Features |
74+
| ----------------------------------- | -------------------------- | ------------------------------------------------------ |
75+
| **DistributedCacheMetadataService** | Production caching service | multi-tier caching (Memory → Distributed → Repository) |
76+
77+
## Quick Start
78+
79+
### Basic Setup with Official MDS
80+
81+
```csharp
82+
services
83+
.AddFido2(config => {
84+
config.ServerName = "My FIDO2 Server";
85+
config.ServerDomain = "example.com";
86+
config.Origins = new HashSet<string> { "https://example.com" };
87+
})
88+
.AddFidoMetadataRepository() // Official FIDO Alliance MDS
89+
.AddCachedMetadataService(); // 2-tier caching
90+
```
91+
92+
### Multiple Repositories
93+
94+
```csharp
95+
services
96+
.AddFido2(config => { /* ... */ })
97+
.AddFidoMetadataRepository() // Official MDS (primary)
98+
.AddFileSystemMetadataRepository("/mds/path") // Local files (fallback)
99+
.AddCachedMetadataService(); // Caching wrapper
100+
```
101+
102+
### Custom HTTP Client Configuration
103+
104+
```csharp
105+
services
106+
.AddFido2(config => { /* ... */ })
107+
.AddFidoMetadataRepository(httpBuilder => {
108+
httpBuilder.ConfigureHttpClient(client => {
109+
client.Timeout = TimeSpan.FromSeconds(30);
110+
});
111+
httpBuilder.AddRetryPolicy();
112+
})
113+
.AddCachedMetadataService();
114+
```
115+
116+
## Custom Implementation Guide
117+
118+
### Creating a Custom Repository
119+
120+
Implement `IMetadataRepository` to create your own metadata source:
121+
122+
```csharp
123+
public class DatabaseMetadataRepository : IMetadataRepository
124+
{
125+
private readonly IDbContext _context;
126+
private readonly ILogger<DatabaseMetadataRepository> _logger;
127+
128+
public DatabaseMetadataRepository(IDbContext context, ILogger<DatabaseMetadataRepository> logger)
129+
{
130+
_context = context;
131+
_logger = logger;
132+
}
133+
134+
public async Task<MetadataBLOBPayload> GetBLOBAsync(CancellationToken cancellationToken = default)
135+
{
136+
_logger.LogInformation("Loading metadata BLOB from database");
137+
// TODO: Implement
138+
}
139+
140+
public Task<MetadataStatement?> GetMetadataStatementAsync(
141+
MetadataBLOBPayload blob,
142+
MetadataBLOBPayloadEntry entry,
143+
CancellationToken cancellationToken = default)
144+
{
145+
// Statement is already loaded in the entry from GetBLOBAsync
146+
return Task.FromResult(entry.MetadataStatement);
147+
}
148+
}
149+
```
150+
151+
### Creating a Custom Service
152+
153+
Implement `IMetadataService` for custom caching strategies:
154+
155+
```csharp
156+
public class SimpleMetadataService : IMetadataService
157+
{
158+
private readonly IEnumerable<IMetadataRepository> _repositories;
159+
private readonly ILogger<SimpleMetadataService> _logger;
160+
private readonly ConcurrentDictionary<Guid, MetadataBLOBPayloadEntry?> _cache = new();
161+
private DateTime _lastRefresh = DateTime.MinValue;
162+
private readonly TimeSpan _refreshInterval = TimeSpan.FromHours(6);
163+
164+
public SimpleMetadataService(
165+
IEnumerable<IMetadataRepository> repositories,
166+
ILogger<SimpleMetadataService> logger)
167+
{
168+
_repositories = repositories;
169+
_logger = logger;
170+
}
171+
172+
public async Task<MetadataBLOBPayloadEntry?> GetEntryAsync(Guid aaguid, CancellationToken cancellationToken = default)
173+
{
174+
await RefreshIfNeededAsync(cancellationToken);
175+
return _cache.TryGetValue(aaguid, out var entry) ? entry : null;
176+
}
177+
178+
public bool ConformanceTesting() => false;
179+
180+
private async Task RefreshIfNeededAsync(CancellationToken cancellationToken)
181+
{
182+
foreach (var repository in _repositories)
183+
{
184+
try
185+
{
186+
var blob = await repository.GetBLOBAsync(cancellationToken);
187+
foreach (var entry in blob.Entries)
188+
{
189+
// Cache it
190+
}
191+
}
192+
catch (Exception ex)
193+
{
194+
_logger.LogWarning(ex, "Failed to refresh from repository {Repository}", repository.GetType().Name);
195+
}
196+
}
197+
}
198+
}
199+
```
200+
201+
### Registration
202+
203+
Register your custom implementations:
204+
205+
```csharp
206+
// Register custom service + repository
207+
services
208+
.AddFido2(config => { /* ... */ })
209+
.AddMetadataRepository<DatabaseMetadataRepository>() // Custom repository
210+
.AddMetadataService<SimpleMetadataService>(); // Custom service
211+
212+
// Register custom service
213+
services
214+
.AddFido2(config => { /* ... */ })
215+
.AddFidoMetadataRepository() // FIDO Alliance repository
216+
.AddMetadataService<SimpleMetadataService>(); // Custom service
217+
218+
219+
// Or use with built-in caching service
220+
services
221+
.AddFido2(config => { /* ... */ })
222+
.AddMetadataRepository<DatabaseMetadataRepository>() // Custom repository
223+
.AddCachedMetadataService(); // Built-in caching
224+
```

0 commit comments

Comments
 (0)