Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/publish-nuget.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ on:
push:
branches:
- main
- bugfix/jwks-parsing
pull_request:
branches:
- main
- bugfix/jwks-parsing

env:
BUILD_CONFIG: 'Release'
Expand Down Expand Up @@ -53,15 +55,15 @@ jobs:
run: dotnet restore $SOLUTION

- name: Build
run: dotnet build $SOLUTION --configuration $BUILD_CONFIG -p:Version=$APP_VERSION
run: dotnet build $SOLUTION --configuration $BUILD_CONFIG -p:Version=2.0.0-pr.303.0
Comment thread
JoTiTu marked this conversation as resolved.
Outdated

- name: Run tests
run: |
dotnet test test/WalletFramework.Oid4Vc.Tests --configuration $BUILD_CONFIG --no-restore --no-build
dotnet test test/WalletFramework.SdJwtVc.Tests --configuration $BUILD_CONFIG --no-restore --no-build

- name: Pack WalletFramework
run: dotnet pack $SOLUTION --configuration $BUILD_CONFIG -p:Version=$APP_VERSION --no-build --output .
run: dotnet pack $SOLUTION --configuration $BUILD_CONFIG -p:Version=2.0.0-pr.303.0 --no-build --output .
Comment thread
JoTiTu marked this conversation as resolved.
Outdated

- name: Publish
run: nuget push **\*.nupkg -Source 'https://api.nuget.org/v3/index.json' -ApiKey ${{secrets.NUGET_API_KEY}}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
using WalletFramework.Oid4Vc.Oid4Vci.Implementations;
using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Abstractions;
using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Implementations;
using WalletFramework.Oid4Vc.Oid4Vp.AuthResponse.Encryption.Abstractions;
using WalletFramework.Oid4Vc.Oid4Vp.AuthResponse.Encryption.Implementations;
using WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Services;
using WalletFramework.Oid4Vc.Oid4Vp.Services;
using WalletFramework.SdJwtVc;
Expand All @@ -37,6 +39,7 @@ public static IServiceCollection AddOpenIdServices(this IServiceCollection build
{
builder.AddSingleton<IAesGcmEncryption, AesGcmEncryption>();
builder.AddSingleton<IAuthFlowSessionStorage, AuthFlowSessionStorage>();
builder.AddSingleton<IAuthorizationResponseEncryptionService, AuthorizationResponseEncryptionService>();
builder.AddSingleton<IAuthorizationRequestService, AuthorizationRequestService>();
builder.AddSingleton<ICoseSign1Signer, CoseSign1Signer>();
builder.AddSingleton<ICredentialOfferService, CredentialOfferService>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using LanguageExt;
using WalletFramework.Oid4Vc.Oid4Vp.Models;

namespace WalletFramework.Oid4Vc.Oid4Vp.AuthResponse.Encryption.Abstractions;

public interface IAuthorizationResponseEncryptionService
{
public Task<EncryptedAuthorizationResponse> Encrypt(
AuthorizationResponse response,
AuthorizationRequest request,
Option<Nonce> mdocNonce);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,6 @@ public record EncryptedAuthorizationResponse(string Jwe, Option<string> State)

public static class EncryptedAuthorizationResponseFun
{
public static EncryptedAuthorizationResponse Encrypt(
this AuthorizationResponse response,
AuthorizationRequest authorizationRequest,
Option<Nonce> mdocNonce) => Encrypt(
response,
authorizationRequest.ClientMetadata!.Jwks.First(),
authorizationRequest.Nonce,
authorizationRequest.ClientMetadata.AuthorizationEncryptedResponseEnc,
mdocNonce);

public static EncryptedAuthorizationResponse Encrypt(
this AuthorizationResponse response,
JsonWebKey verifierPubKey,
Expand All @@ -56,7 +46,7 @@ public static EncryptedAuthorizationResponse Encrypt(

var jwe = JWE.EncryptBytes(
response.ToJson().GetUTF8Bytes(),
new[] { new JweRecipient(JweAlgorithm.ECDH_ES, verifierPubKey.ToEcdh()) },
[new JweRecipient(JweAlgorithm.ECDH_ES, verifierPubKey.ToEcdh())],
authorizationEncryptedResponseEnc.ToNullable() switch {
"A256GCM" => JweEncryption.A256GCM,
"A128CBC-HS256" => JweEncryption.A128CBC_HS256,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using LanguageExt;
using Microsoft.IdentityModel.Tokens;
using WalletFramework.Core.Functional;
using WalletFramework.Oid4Vc.Oid4Vp.AuthResponse.Encryption.Abstractions;
using WalletFramework.Oid4Vc.Oid4Vp.Jwk;
using WalletFramework.Oid4Vc.Oid4Vp.Models;

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

public class AuthorizationResponseEncryptionService
(IHttpClientFactory httpClientFactory): IAuthorizationResponseEncryptionService
{
public async Task<EncryptedAuthorizationResponse> Encrypt(
AuthorizationResponse response,
AuthorizationRequest request,
Option<Nonce> mdocNonce)
{
var hasJwksUri = request.ClientMetadata?.JwksUri is not null;
var hasJwksInMetadata = request.ClientMetadata?.JwkSet.IsSome ?? false;

JsonWebKey verifierPubKey;
switch (hasJwksUri, hasJwksInMetadata)
{
case (true, false):
case (true, true):
var httpClient = httpClientFactory.CreateClient();
httpClient.DefaultRequestHeaders.Clear();
var httpResponseMessage = await httpClient.GetAsync(request.ClientMetadata!.JwksUri);
var jwkSetJsonStr = await httpResponseMessage.Content.ReadAsStringAsync();
verifierPubKey = JwkSet.FromJsonStr(jwkSetJsonStr).UnwrapOrThrow().GetFirst();
break;
case (false, true):
verifierPubKey = request.ClientMetadata!.JwkSet.UnwrapOrThrow().GetFirst();
break;
default:
throw new InvalidOperationException("Neither jwks or jwk_uri found");
}

return response.Encrypt(
verifierPubKey,
request.Nonce,
request.ClientMetadata?.AuthorizationEncryptedResponseEnc,
mdocNonce);
}
}
42 changes: 42 additions & 0 deletions src/WalletFramework.Oid4Vc/Oid4Vp/Jwk/JwkSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Linq;
using WalletFramework.Core.Functional;
using WalletFramework.Core.Json;
using WalletFramework.Core.Json.Errors;

namespace WalletFramework.Oid4Vc.Oid4Vp.Jwk;

public record JwkSet
{
private JwkSet(IEnumerable<JsonWebKey> jwkSet) => Value = jwkSet.ToList();

private List<JsonWebKey> Value { get; }

public IEnumerable<JsonWebKey> AsEnum() => Value;

public static Validation<JwkSet> FromJObject(JObject json) =>
from keys in json.GetByKey("keys")
from keysArray in keys.ToJArray()
let jwks = keysArray.Select(keyToken => JsonWebKey.Create(keyToken.ToString()))
select new JwkSet(jwks);

public static Validation<JwkSet> FromJsonStr(string json)
{
JObject jObject;
try
{
jObject = JObject.Parse(json);
}
catch (Exception e)
{
return new InvalidJsonError(json, e);
}

return FromJObject(jObject);
}
}

public static class JwkSetFun
{
public static JsonWebKey GetFirst(this JwkSet jwkSet) => jwkSet.AsEnum().First();
}
119 changes: 61 additions & 58 deletions src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,27 @@ public record AuthorizationRequest
public const string DirectPost = "direct_post";
public const string DirectPostJwt = "direct_post.jwt";

private const string VpToken = "vp_token";

public static readonly string[] SupportedClientIdSchemes =
[RedirectUriScheme, VerifierAttestationScheme, X509SanDnsScheme];

private const string VpToken = "vp_token";

/// <summary>
/// Gets the client id scheme.
/// </summary>
[JsonProperty("client_id_scheme")]
public ClientIdScheme ClientIdScheme { get; }

/// <summary>
/// Gets the client metadata. Contains the Verifier metadata.
/// </summary>
[JsonProperty("client_metadata")]
public ClientMetadata? ClientMetadata { get; init; }

[JsonIgnore]
public Option<List<TransactionData>> TransactionData { get; private init; } =
Option<List<TransactionData>>.None;

/// <summary>
/// Gets the presentation definition. Contains the claims that the Verifier wants to receive.
/// </summary>
Expand All @@ -49,20 +59,14 @@ public record AuthorizationRequest
[JsonProperty("nonce")]
public string Nonce { get; }

[JsonProperty("response_mode")]
public string ResponseMode { get; }

/// <summary>
/// Gets the response mode. Determines where to send the Authorization Response to.
/// </summary>
[JsonProperty("response_uri")]
public string ResponseUri { get; }

[JsonProperty("response_mode")]
public string ResponseMode { get; }

/// <summary>
/// Gets the client metadata. Contains the Verifier metadata.
/// </summary>
[JsonProperty("client_metadata")]
public ClientMetadata? ClientMetadata { get; init; }

/// <summary>
/// Gets the client metadata uri. Can be used to retrieve the verifier metadata.
Expand All @@ -82,10 +86,6 @@ public record AuthorizationRequest
[JsonProperty("state")]
public string? State { get; }

[JsonIgnore]
public Option<List<TransactionData>> TransactionData { get; private init; } =
Option<List<TransactionData>>.None;

/// <summary>
/// The X509 certificate of the verifier, this property is only set when ClientIDScheme is X509SanDNS.
/// </summary>
Expand All @@ -111,17 +111,18 @@ private AuthorizationRequest(
string? scope,
string? state)
{
if (SupportedClientIdSchemes.Exists(supportedClientIdScheme => clientId.StartsWith($"{supportedClientIdScheme}:")))
if (SupportedClientIdSchemes.Exists(supportedClientIdScheme =>
clientId.StartsWith($"{supportedClientIdScheme}:")))
{
ClientIdScheme = clientId.Split(':')[0];
ClientId = clientId.Split(':')[1];
}
else
{
ClientId = clientId;
ClientIdScheme = clientIdScheme;
ClientIdScheme = clientIdScheme;
}

ClientMetadata = clientMetadata;
ClientMetadataUri = clientMetadataUri;
Nonce = nonce;
Expand Down Expand Up @@ -267,59 +268,31 @@ private static bool IsHaipConform(JObject authorizationRequestJson)

string clientId;
string clientIdScheme;
if (SupportedClientIdSchemes.Exists(supportedClientIdScheme => authorizationRequestClientId.StartsWith($"{supportedClientIdScheme}:")))
if (SupportedClientIdSchemes.Exists(supportedClientIdScheme =>
authorizationRequestClientId.StartsWith($"{supportedClientIdScheme}:")))
{
clientIdScheme = authorizationRequestClientId.Split(':')[0];
clientIdScheme = authorizationRequestClientId.Split(':')[0];
clientId = authorizationRequestClientId.Split(':')[1];
}
else
{
clientIdScheme = authorizationRequestJson["client_id_scheme"]!.ToString();
clientId = authorizationRequestClientId;
clientId = authorizationRequestClientId;
}

return
responseType == VpToken
&& responseMode == DirectPost || responseMode == DirectPostJwt
&& !string.IsNullOrEmpty(responseUri)
&& redirectUri is null
&& (clientIdScheme is X509SanDnsScheme or VerifierAttestationScheme
|| (clientIdScheme is RedirectUriScheme && clientId == responseUri));
(responseType == VpToken
&& responseMode == DirectPost) || (responseMode == DirectPostJwt
&& !string.IsNullOrEmpty(responseUri)
&& redirectUri is null
&& (clientIdScheme is X509SanDnsScheme or VerifierAttestationScheme
|| (clientIdScheme is RedirectUriScheme &&
clientId == responseUri)));
}
}

internal static class AuthorizationRequestExtensions
{
internal static AuthorizationRequest WithX509(
this AuthorizationRequest authorizationRequest,
RequestObject requestObject)
{
var encodedCertificate = requestObject.GetLeafCertificate().GetEncoded();

var certificates =
requestObject
.GetCertificates()
.Select(x => x.GetEncoded())
.Select(x => new X509Certificate2(x));

var trustChain = new X509Chain();
foreach (var element in certificates)
{
trustChain.ChainPolicy.ExtraStore.Add(element);
}

return authorizationRequest with
{
X509Certificate = new X509Certificate2(encodedCertificate),
X509TrustChain = trustChain
};
}

internal static AuthorizationRequest WithClientMetadata(
this AuthorizationRequest authorizationRequest,
Option<ClientMetadata> clientMetadata)
=> authorizationRequest with { ClientMetadata = clientMetadata.ToNullable() };

internal static Option<Uri> GetResponseUriMaybe(string authRequestJson)
{
try
Expand All @@ -332,7 +305,7 @@ internal static Option<Uri> GetResponseUriMaybe(string authRequestJson)
return Option<Uri>.None;
}
}

internal static Option<Uri> GetResponseUriMaybe(JObject authRequestJObject)
{
try
Expand All @@ -345,6 +318,36 @@ internal static Option<Uri> GetResponseUriMaybe(JObject authRequestJObject)
return Option<Uri>.None;
}
}

internal static AuthorizationRequest WithClientMetadata(
this AuthorizationRequest authorizationRequest,
Option<ClientMetadata> clientMetadata)
=> authorizationRequest with { ClientMetadata = clientMetadata.ToNullable() };

internal static AuthorizationRequest WithX509(
this AuthorizationRequest authorizationRequest,
RequestObject requestObject)
{
var encodedCertificate = requestObject.GetLeafCertificate().GetEncoded();

var certificates =
requestObject
.GetCertificates()
.Select(x => x.GetEncoded())
.Select(x => new X509Certificate2(x));

var trustChain = new X509Chain();
foreach (var element in certificates)
{
trustChain.ChainPolicy.ExtraStore.Add(element);
}

return authorizationRequest with
{
X509Certificate = new X509Certificate2(encodedCertificate),
X509TrustChain = trustChain
};
}
}

public static class AuthorizationRequestFun
Expand Down
Loading