Skip to content

Commit 6f0de8a

Browse files
Merge branch 'main' into SessionEstablishment
2 parents 1878e76 + 469fa37 commit 6f0de8a

File tree

44 files changed

+1310
-260
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1310
-260
lines changed

src/WalletFramework.MdocLib/DocType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace WalletFramework.MdocLib;
88

9-
public readonly struct DocType
9+
public readonly record struct DocType
1010
{
1111
private string Value { get; }
1212

src/WalletFramework.Oid4Vc/DependencyInjection/ServiceCollectionExtensions.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
using WalletFramework.Oid4Vc.RelyingPartyAuthentication.Implementations;
3434
using WalletFramework.SdJwtVc;
3535
using WalletFramework.SdJwtVc.Services;
36-
using IRpRegistrarService = WalletFramework.Oid4Vc.RelyingPartyAuthentication.Implementations.IRpRegistrarService;
3736

3837
namespace WalletFramework.Oid4Vc.DependencyInjection;
3938

@@ -47,15 +46,20 @@ public static IServiceCollection AddOpenIdServices(this IServiceCollection build
4746
{
4847
builder.AddSingleton<IAesGcmEncryption, AesGcmEncryption>();
4948
builder.AddSingleton<IAuthFlowSessionStorage, AuthFlowSessionStorage>();
50-
builder.AddSingleton<IAuthorizationResponseEncryptionService, AuthorizationResponseEncryptionService>();
5149
builder.AddSingleton<IAuthorizationRequestService, AuthorizationRequestService>();
50+
builder.AddSingleton<IAuthorizationResponseEncryptionService, AuthorizationResponseEncryptionService>();
51+
builder.AddSingleton<IVerifierKeyService, VerifierKeyService>();
52+
builder.AddSingleton<ICandidateQueryService, CandidateQueryService>();
53+
builder.AddSingleton<IClientAttestationService, ClientAttestationService>();
5254
builder.AddSingleton<ICoseSign1Signer, CoseSign1Signer>();
55+
builder.AddSingleton<ICredentialNonceService, CredentialNonceService>();
5356
builder.AddSingleton<ICredentialOfferService, CredentialOfferService>();
5457
builder.AddSingleton<ICredentialRequestService, CredentialRequestService>();
5558
builder.AddSingleton<ICredentialSetService, CredentialSetService>();
5659
builder.AddSingleton<ICredentialSetStorage, CredentialSetStorage>();
57-
builder.AddSingleton<IDcqlService, DcqlService>();
5860
builder.AddSingleton<IDPopHttpClient, DPopHttpClient>();
61+
builder.AddSingleton<IDcApiService, DcApiService>();
62+
builder.AddSingleton<IDcqlService, DcqlService>();
5963
builder.AddSingleton<IIssuerMetadataService, IssuerMetadataService>();
6064
builder.AddSingleton<IMdocAuthenticationService, MdocAuthenticationService>();
6165
builder.AddSingleton<IMdocCandidateService, MdocCandidateService>();
@@ -66,15 +70,13 @@ public static IServiceCollection AddOpenIdServices(this IServiceCollection build
6670
builder.AddSingleton<IOid4VpHaipClient, Oid4VpHaipClient>();
6771
builder.AddSingleton<IOid4VpRecordService, Oid4VpRecordService>();
6872
builder.AddSingleton<IPexService, PexService>();
69-
builder.AddSingleton<ICandidateQueryService, CandidateQueryService>();
73+
builder.AddSingleton<IPresentationService, PresentationService>();
7074
builder.AddSingleton<IRecordsMigrationService, RecordsMigrationService>();
7175
builder.AddSingleton<IRpAuthService, RpAuthService>();
72-
builder.AddSingleton<RelyingPartyAuthentication.Abstractions.IRpRegistrarService, IRpRegistrarService>();
76+
builder.AddSingleton<IRpRegistrarService, RpRegistrarService>();
7377
builder.AddSingleton<IStatusListService, StatusListService>();
7478
builder.AddSingleton<ITokenService, TokenService>();
7579
builder.AddSingleton<IVctMetadataService, VctMetadataService>();
76-
builder.AddSingleton<ICredentialNonceService, CredentialNonceService>();
77-
builder.AddSingleton<IClientAttestationService, ClientAttestationService>();
7880

7981
builder.AddSdJwtVcServices();
8082

src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/SdJwtRecordExtensions.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,4 @@ select displays.Select(credentialDisplay =>
5151

5252
return record;
5353
}
54-
55-
internal static SdJwtDoc ToSdJwtDoc(this SdJwtRecord record)
56-
{
57-
return new SdJwtDoc(record.EncodedIssuerSignedJwt + "~" + string.Join("~", record.Disclosures) + "~");
58-
}
5954
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Microsoft.IdentityModel.Tokens;
2+
using WalletFramework.Oid4Vc.Oid4Vp.Models;
3+
4+
namespace WalletFramework.Oid4Vc.Oid4Vp.AuthResponse.Encryption.Abstractions;
5+
6+
public interface IVerifierKeyService
7+
{
8+
Task<JsonWebKey> GetPublicKey(AuthorizationRequest request);
9+
}

src/WalletFramework.Oid4Vc/Oid4Vp/AuthResponse/Encryption/Implementations/AuthorizationResponseEncryptionService.cs

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,18 @@
11
using LanguageExt;
2-
using Microsoft.IdentityModel.Tokens;
3-
using WalletFramework.Core.Functional;
42
using WalletFramework.Oid4Vc.Oid4Vp.AuthResponse.Encryption.Abstractions;
5-
using WalletFramework.Oid4Vc.Oid4Vp.Jwk;
63
using WalletFramework.Oid4Vc.Oid4Vp.Models;
74

85
namespace WalletFramework.Oid4Vc.Oid4Vp.AuthResponse.Encryption.Implementations;
96

10-
public class AuthorizationResponseEncryptionService
11-
(IHttpClientFactory httpClientFactory): IAuthorizationResponseEncryptionService
7+
public class AuthorizationResponseEncryptionService(IVerifierKeyService verifierKeyService)
8+
: IAuthorizationResponseEncryptionService
129
{
1310
public async Task<EncryptedAuthorizationResponse> Encrypt(
1411
AuthorizationResponse response,
1512
AuthorizationRequest request,
1613
Option<Nonce> mdocNonce)
1714
{
18-
var hasJwksUri = request.ClientMetadata?.JwksUri is not null;
19-
var hasJwksInMetadata = request.ClientMetadata?.JwkSet.IsSome ?? false;
20-
21-
JsonWebKey verifierPubKey;
22-
switch (hasJwksUri, hasJwksInMetadata)
23-
{
24-
case (true, false):
25-
case (true, true):
26-
var httpClient = httpClientFactory.CreateClient();
27-
httpClient.DefaultRequestHeaders.Clear();
28-
var httpResponseMessage = await httpClient.GetAsync(request.ClientMetadata!.JwksUri);
29-
var jwkSetJsonStr = await httpResponseMessage.Content.ReadAsStringAsync();
30-
verifierPubKey = JwkSet.FromJsonStr(jwkSetJsonStr).UnwrapOrThrow().GetEcP256Jwk();
31-
break;
32-
case (false, true):
33-
verifierPubKey = request.ClientMetadata!.JwkSet.UnwrapOrThrow().GetEcP256Jwk();
34-
break;
35-
default:
36-
throw new InvalidOperationException("Neither jwks or jwk_uri found");
37-
}
15+
var verifierPubKey = await verifierKeyService.GetPublicKey(request);
3816

3917
return response.Encrypt(
4018
verifierPubKey,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Microsoft.IdentityModel.Tokens;
2+
using WalletFramework.Core.Functional;
3+
using WalletFramework.Oid4Vc.Oid4Vp.AuthResponse.Encryption.Abstractions;
4+
using WalletFramework.Oid4Vc.Oid4Vp.Jwk;
5+
using WalletFramework.Oid4Vc.Oid4Vp.Models;
6+
7+
namespace WalletFramework.Oid4Vc.Oid4Vp.AuthResponse.Encryption.Implementations;
8+
9+
public class VerifierKeyService(IHttpClientFactory httpClientFactory) : IVerifierKeyService
10+
{
11+
public async Task<JsonWebKey> GetPublicKey(AuthorizationRequest request)
12+
{
13+
var hasJwksUri = request.ClientMetadata?.JwksUri is not null;
14+
var hasJwksInMetadata = request.ClientMetadata?.JwkSet.IsSome ?? false;
15+
16+
return (hasJwksUri, hasJwksInMetadata) switch
17+
{
18+
(true, false) or (true, true) => await GetKeyFromJwksUri(request.ClientMetadata!.JwksUri!),
19+
(false, true) => request.ClientMetadata!.JwkSet.UnwrapOrThrow().GetEcP256Jwk(),
20+
_ => throw new InvalidOperationException("Neither jwks or jwk_uri found")
21+
};
22+
}
23+
24+
private async Task<JsonWebKey> GetKeyFromJwksUri(string jwksUri)
25+
{
26+
var httpClient = httpClientFactory.CreateClient();
27+
httpClient.DefaultRequestHeaders.Clear();
28+
var httpResponseMessage = await httpClient.GetAsync(jwksUri);
29+
var jwkSetJsonStr = await httpResponseMessage.Content.ReadAsStringAsync();
30+
return JwkSet.FromJsonStr(jwkSetJsonStr).UnwrapOrThrow().GetEcP256Jwk();
31+
}
32+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Linq;
3+
using WalletFramework.Oid4Vc.Oid4Vp.Dcql.CredentialQueries;
4+
using OneOf;
5+
using WalletFramework.Core.Functional;
6+
using WalletFramework.Oid4Vc.Oid4Vp.Models;
7+
8+
namespace WalletFramework.Oid4Vc.Oid4Vp.AuthResponse;
9+
10+
// Second Parameter is for PEX which will be deprecated soon
11+
[JsonConverter(typeof(VpTokenConverter))]
12+
public record VpToken(OneOf<DcqlVpToken, string> Value)
13+
{
14+
public string AsString()
15+
{
16+
return Value.Match(
17+
dcql => dcql.AsJsonString(),
18+
pex => pex
19+
);
20+
}
21+
22+
public JObject AsJObject()
23+
{
24+
return Value.Match(
25+
dcql => dcql.AsJObject(),
26+
pex => throw new ArgumentException("PEX is not supported")
27+
);
28+
}
29+
}
30+
31+
public class VpTokenConverter : JsonConverter<VpToken>
32+
{
33+
public override void WriteJson(JsonWriter writer, VpToken? value, JsonSerializer serializer)
34+
{
35+
value!.Value.Switch(
36+
dcql =>
37+
{
38+
var jObject = new JObject();
39+
foreach (var pair in dcql.Presentations)
40+
{
41+
jObject[pair.Key.AsString()] = new JArray(pair.Value.Select(presentation => presentation.Value));
42+
}
43+
jObject.WriteTo(writer);
44+
// writer.WriteValue(dcql.AsString());
45+
},
46+
writer.WriteValue
47+
);
48+
}
49+
50+
public override VpToken ReadJson(JsonReader reader, Type objectType, VpToken? existingValue, bool hasExistingValue, JsonSerializer serializer)
51+
{
52+
var token = JToken.Load(reader);
53+
54+
if (token.Type == JTokenType.String)
55+
{
56+
// It's a PEX string
57+
return new VpToken(token.ToString());
58+
}
59+
else if (token.Type == JTokenType.Object)
60+
{
61+
// It's a DCQL VP Token
62+
var jObject = (JObject)token;
63+
var presentations = new Dictionary<CredentialQueryId, List<Presentation>>();
64+
65+
foreach (var property in jObject.Properties())
66+
{
67+
var credentialQueryId = CredentialQueryId.Create(property.Name).UnwrapOrThrow();
68+
var presentationList = new List<Presentation>();
69+
70+
if (property.Value is JArray array)
71+
{
72+
foreach (var item in array)
73+
{
74+
presentationList.Add(new Presentation(item.ToString()));
75+
}
76+
}
77+
78+
presentations[credentialQueryId] = presentationList;
79+
}
80+
81+
return new VpToken(new DcqlVpToken(presentations));
82+
}
83+
else
84+
{
85+
throw new JsonSerializationException($"Unexpected token type: {token.Type}");
86+
}
87+
}
88+
}
89+
90+
// VP Draft 29; This is the correct one
91+
public record DcqlVpToken(Dictionary<CredentialQueryId, List<Presentation>> Presentations)
92+
{
93+
public string AsJsonString()
94+
{
95+
var jObject = new JObject();
96+
97+
foreach (var pair in Presentations)
98+
{
99+
jObject[pair.Key.AsString()] = new JArray(pair.Value.Select(presentation => presentation.Value));
100+
}
101+
102+
return jObject.ToString();
103+
}
104+
105+
public JObject AsJObject()
106+
{
107+
var jObject = new JObject();
108+
foreach (var pair in Presentations)
109+
{
110+
jObject[pair.Key.AsString()] = new JArray(pair.Value.Select(presentation => presentation.Value));
111+
}
112+
return jObject;
113+
}
114+
}
115+
116+
public static class DcqlVpTokenFun
117+
{
118+
public static DcqlVpToken FromPresentationMaps(IEnumerable<PresentationMap> maps)
119+
{
120+
var dict = maps
121+
.GroupBy(map => CredentialQueryId.Create(map.Identifier).UnwrapOrThrow())
122+
.ToDictionary(
123+
map => map.Key,
124+
map => map
125+
.Select(presentationMap => new Presentation(presentationMap.Presentation))
126+
.ToList()
127+
);
128+
129+
return new DcqlVpToken(dict);
130+
}
131+
}
132+
133+
// Can be either Mdoc DeviceResponse or SD-JWT Presentation Format; we are currently lacking a strong type
134+
// for this
135+
public record Presentation(string Value);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace WalletFramework.Oid4Vc.Oid4Vp.DcApi;
2+
3+
public static class DcApiConstants
4+
{
5+
public const string SignedProtocol = "openid4vp-v1-signed";
6+
7+
public const string UnsignedProtocol = "openid4vp-v1-unsigned";
8+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Newtonsoft.Json.Linq;
2+
using WalletFramework.Core.Functional;
3+
using WalletFramework.Core.Functional.Errors;
4+
using WalletFramework.Core.Json;
5+
using WalletFramework.Core.Json.Errors;
6+
using static WalletFramework.Core.Functional.ValidationFun;
7+
8+
namespace WalletFramework.Oid4Vc.Oid4Vp.DcApi.Models;
9+
10+
/// <summary>
11+
/// Represents a batch of DC-API requests.
12+
/// </summary>
13+
public record DcApiRequestBatch
14+
{
15+
/// <summary>
16+
/// Gets the requests. Contains an array of DC-API request items.
17+
/// </summary>
18+
public DcApiRequestItem[] Requests { get; }
19+
20+
private DcApiRequestBatch(DcApiRequestItem[] requests)
21+
{
22+
Requests = requests;
23+
}
24+
25+
private static DcApiRequestBatch Create(DcApiRequestItem[] requests) => new(requests);
26+
27+
public static Validation<DcApiRequestBatch> From(string requestBatchJson)
28+
{
29+
if (string.IsNullOrWhiteSpace(requestBatchJson))
30+
{
31+
return new StringIsNullOrWhitespaceError<DcApiRequestBatch>();
32+
}
33+
34+
JObject jObject;
35+
try
36+
{
37+
jObject = JObject.Parse(requestBatchJson);
38+
}
39+
catch (Exception e)
40+
{
41+
return new InvalidJsonError(requestBatchJson, e).ToInvalid<DcApiRequestBatch>();
42+
}
43+
44+
return From(jObject);
45+
}
46+
47+
public static Validation<DcApiRequestBatch> From(JObject requestBatchJson)
48+
{
49+
var requestsValidation =
50+
from jToken in requestBatchJson.GetByKey("requests")
51+
from jArray in jToken.ToJArray()
52+
from items in jArray.TraverseAll(token =>
53+
{
54+
return
55+
from jObject in token.ToJObject()
56+
from item in DcApiRequestItem.ValidDcApiRequestItem(jObject)
57+
select item;
58+
})
59+
select items.ToArray();
60+
61+
return Valid(Create).Apply(requestsValidation);
62+
}
63+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using LanguageExt;
2+
3+
namespace WalletFramework.Oid4Vc.Oid4Vp.DcApi.Models;
4+
5+
/// <summary>
6+
/// Functions for DcApiRequestBatch.
7+
/// </summary>
8+
public static class DcApiRequestBatchFun
9+
{
10+
/// <summary>
11+
/// Gets the first request in the batch with the specified protocol.
12+
/// </summary>
13+
/// <param name="batch">The DC-API request batch.</param>
14+
/// <returns>The first request item with the specified protocol, or None if not found.</returns>
15+
public static Option<DcApiRequestItem> GetFirstVpRequest(this DcApiRequestBatch batch)
16+
{
17+
var firstRequest = batch.Requests.FirstOrDefault(request => request.Protocol.Contains("openid4vp"));
18+
return firstRequest ?? Option<DcApiRequestItem>.None;
19+
}
20+
}

0 commit comments

Comments
 (0)