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

fix client_id_scheme redirect_uri #209

Merged
merged 6 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions .github/workflows/publish-nuget.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.*
global-json-file: global.json

# - name: Install libindy library
# run: |
Expand All @@ -58,7 +58,7 @@ jobs:
# apt-get install -y libindy

- name: Build
run: dotnet build $SOLUTION --configuration $BUILD_CONFIG -p:Version=$APP_VERSION --no-restore
run: dotnet build $SOLUTION --configuration $BUILD_CONFIG -p:Version=$APP_VERSION

- name: Run tests
run: |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Security.Cryptography.X509Certificates;
using LanguageExt;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WalletFramework.Core.Functional;
using WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models;
using static WalletFramework.Oid4Vc.Oid4Vp.Models.ClientIdScheme;

Expand Down Expand Up @@ -118,7 +120,7 @@ private AuthorizationRequest(
/// <exception cref="InvalidOperationException">Thrown when the request does not match the HAIP.</exception>
public static AuthorizationRequest CreateAuthorizationRequest(string authorizationRequestJson)
=> CreateAuthorizationRequest(JObject.Parse(authorizationRequestJson));

private static AuthorizationRequest CreateAuthorizationRequest(JObject authorizationRequestJson) =>
IsHaipConform(authorizationRequestJson)
? authorizationRequestJson.ToObject<AuthorizationRequest>()
Expand Down Expand Up @@ -175,6 +177,6 @@ internal static AuthorizationRequest WithX509(

internal static AuthorizationRequest WithClientMetadata(
this AuthorizationRequest authorizationRequest,
ClientMetadata? clientMetadata)
=> authorizationRequest with { ClientMetadata = clientMetadata };
Option<ClientMetadata> clientMetadata)
=> authorizationRequest with { ClientMetadata = clientMetadata.ToNullable() };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Web;
using LanguageExt;

namespace WalletFramework.Oid4Vc.Oid4Vp.Models;

public record AuthorizationRequestByReference
{
public Uri AuthorizationRequestUri { get; }

public Uri RequestUri { get; }

private AuthorizationRequestByReference(Uri authorizationRequestUri, Uri requestUri) => (AuthorizationRequestUri, RequestUri) = (authorizationRequestUri, requestUri);

public static Option<AuthorizationRequestByReference> CreateAuthorizationRequestByReference(Uri uri)
{
var queryString = HttpUtility.ParseQueryString(uri.Query);
var clientId = queryString["client_id"];
var requestUri = queryString["request_uri"];

if (string.IsNullOrEmpty(clientId)
|| string.IsNullOrEmpty(requestUri))
return Option<AuthorizationRequestByReference>.None;

return new AuthorizationRequestByReference(uri, new Uri(requestUri));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Web;
using LanguageExt;

namespace WalletFramework.Oid4Vc.Oid4Vp.Models;

public record AuthorizationRequestByValue
{
public Uri RequestUri { get; }

public Option<Uri> PresentationDefinitionUri { get; }

private AuthorizationRequestByValue(Uri requestUri, Option<Uri> presentationDefinitionUri) => (RequestUri, PresentationDefinitionUri) = (requestUri, presentationDefinitionUri);

public static Option<AuthorizationRequestByValue> CreateAuthorizationRequestByValue(Uri uri)
{
var queryString = HttpUtility.ParseQueryString(uri.Query);
var clientId = queryString["client_id"];
var nonce = queryString["nonce"];
var presentationDefinition = queryString["presentation_definition"];
var presentationDefinitionUri = queryString["presentation_definition_uri"];
var scope = queryString["scope"];

if (string.IsNullOrEmpty(clientId)
|| string.IsNullOrEmpty(nonce)
|| string.IsNullOrEmpty(presentationDefinition) && string.IsNullOrEmpty(presentationDefinitionUri) && string.IsNullOrEmpty(scope))
return Option<AuthorizationRequestByValue>.None;

return new AuthorizationRequestByValue(uri, string.IsNullOrEmpty(presentationDefinitionUri) ? Option<Uri>.None : new Uri(presentationDefinitionUri));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using OneOf;

namespace WalletFramework.Oid4Vc.Oid4Vp.Models;

public struct AuthorizationRequestUri
{
public OneOf<AuthorizationRequestByReference, AuthorizationRequestByValue> Value { get; }

private AuthorizationRequestUri(OneOf<AuthorizationRequestByReference, AuthorizationRequestByValue> value) => Value = value;

public static AuthorizationRequestUri FromUri(Uri uri)
{
return AuthorizationRequestByReference.CreateAuthorizationRequestByReference(uri).Match(
byReference => new AuthorizationRequestUri(byReference),
() => AuthorizationRequestByValue.CreateAuthorizationRequestByValue(uri).Match(
byValue => new AuthorizationRequestUri(byValue),
() => throw new InvalidOperationException("Authorization Request Uri is not a valid")
)
);
}
}

This file was deleted.

5 changes: 4 additions & 1 deletion src/WalletFramework.Oid4Vc/Oid4Vp/Models/RequestObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public static RequestObject CreateRequestObject(string requestObjectJson)
{
var tokenHandler = new JwtSecurityTokenHandler();

if (requestObjectJson.Split('.').Length == 2)
requestObjectJson += ".";

var jwt = tokenHandler.ReadJwtToken(requestObjectJson);

return new RequestObject(jwt);
Expand All @@ -73,7 +76,7 @@ public static class RequestObjectExtensions
/// </summary>
/// <returns>The validated request object.</returns>
/// <exception cref="InvalidOperationException">Throws when validation fails</exception>
public static RequestObject ValidateJwt(this RequestObject requestObject)
public static RequestObject ValidateJwtSignature(this RequestObject requestObject)
{
var jwt = (JwtSecurityToken)requestObject;
var pubKey = requestObject.GetLeafCertificate().GetPublicKey();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System.Net.Http.Headers;
using System.Web;
using LanguageExt;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WalletFramework.Oid4Vc.Oid4Vp.Models;
using static WalletFramework.Oid4Vc.Oid4Vp.Models.RequestObject;
using static WalletFramework.Oid4Vc.Oid4Vp.Models.ClientIdScheme.ClientIdSchemeValue;
using static Newtonsoft.Json.JsonConvert;

namespace WalletFramework.Oid4Vc.Oid4Vp.Services;

public class AuthorizationRequestService(
IHttpClientFactory httpClientFactory) : IAuthorizationRequestService
{
public async Task<AuthorizationRequest> CreateAuthorizationRequest(AuthorizationRequestByReference authRequestByReference)
{
var httpClient = httpClientFactory.CreateClient();
httpClient.DefaultRequestHeaders.Clear();

var requestObjectJson = await httpClient.GetStringAsync(authRequestByReference.RequestUri);
var requestObject = CreateRequestObject(requestObjectJson);

var clientMetadata = await FetchClientMetadata(requestObject.ToAuthorizationRequest());

return requestObject.ClientIdScheme.Value switch
{
X509SanDns => requestObject
.ValidateJwtSignature()
.ValidateTrustChain()
.ValidateSanName()
.ToAuthorizationRequest()
.WithX509(requestObject)
.WithClientMetadata(clientMetadata),
//TODO: Remove Redirect URi in the future (kept for now for compatibility)
RedirectUri => requestObject
.ToAuthorizationRequest()
.WithClientMetadata(clientMetadata),
VerifierAttestation =>
throw new NotImplementedException("Verifier Attestation not yet implemented"),
_ => throw new InvalidOperationException(
$"Client ID Scheme {requestObject.ClientIdScheme} not supported")
};
}

public async Task<AuthorizationRequest> CreateAuthorizationRequest(AuthorizationRequestByValue authRequestByValue)
{
var queryParams = HttpUtility.ParseQueryString(authRequestByValue.RequestUri.Query);

var jObject = new JObject();
foreach (var key in queryParams.AllKeys)
{
var value = queryParams[key];

if (string.IsNullOrEmpty(value))
continue;

try
{
jObject[key] = JToken.Parse(value);
}
catch (JsonReaderException)
{
jObject[key] = value;
}
}

if (jObject.TryGetValue("presentation_definition_uri", out var presentationDefinitionUri))
{
var httpClient = httpClientFactory.CreateClient();
httpClient.DefaultRequestHeaders.Clear();

var requestObjectJson = await httpClient.GetStringAsync(presentationDefinitionUri.ToString());
jObject["presentation_definition"] = JToken.Parse(requestObjectJson);
}

var jsonString = SerializeObject(jObject);
var authRequest = AuthorizationRequest.CreateAuthorizationRequest(jsonString);

var clientMetadata = await FetchClientMetadata(authRequest);

return authRequest.ClientIdScheme.Value switch
{
RedirectUri => authRequest
.WithClientMetadata(clientMetadata),
_ => throw new InvalidOperationException(
$"Client ID Scheme {authRequest.ClientIdScheme.Value} not supported when passing the Authorization Request within the Uri")
};
}

private async Task<Option<ClientMetadata>> FetchClientMetadata(AuthorizationRequest authorizationRequest)
{
var httpClient = httpClientFactory.CreateClient();
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

if (authorizationRequest.ClientMetadata != null)
return authorizationRequest.ClientMetadata;

if (string.IsNullOrWhiteSpace(authorizationRequest.ClientMetadataUri))
return null;

var response = await httpClient.GetAsync(authorizationRequest.ClientMetadataUri);
var clientMetadata = await response.Content.ReadAsStringAsync();
return DeserializeObject<ClientMetadata>(clientMetadata);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using WalletFramework.Oid4Vc.Oid4Vp.Models;

namespace WalletFramework.Oid4Vc.Oid4Vp.Services;

public interface IAuthorizationRequestService
{
Task<AuthorizationRequest> CreateAuthorizationRequest(AuthorizationRequestByReference authorizationRequestUri);

Task<AuthorizationRequest> CreateAuthorizationRequest(AuthorizationRequestByValue authorizationRequestUri);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ public interface IOid4VpHaipClient
/// <summary>
/// Processes an OpenID4VP Authorization Request Url.
/// </summary>
/// <param name="haipAuthorizationRequestUri"></param>
/// <param name="authorizationRequestUri"></param>
/// <returns>
/// A task representing the asynchronous operation. The task result contains the Authorization Response object associated with the OpenID4VP Authorization Request Url.
/// </returns>
Task<AuthorizationRequest> ProcessAuthorizationRequestAsync(HaipAuthorizationRequestUri haipAuthorizationRequestUri);
Task<AuthorizationRequest> ProcessAuthorizationRequestAsync(AuthorizationRequestUri authorizationRequestUri);

/// <summary>
/// Creates the Parameters that are necessary to send an OpenId4VP Authorization Response.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Text;
using Hyperledger.Aries.Agents;
using Hyperledger.Aries.Extensions;
using Jose;
using LanguageExt;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
Expand Down Expand Up @@ -85,7 +84,7 @@ public Oid4VpClientService(
public async Task<(AuthorizationRequest, IEnumerable<CredentialCandidates>)> ProcessAuthorizationRequestAsync(
Uri authorizationRequestUri)
{
var haipAuthorizationRequestUri = HaipAuthorizationRequestUri.FromUri(authorizationRequestUri);
var haipAuthorizationRequestUri = AuthorizationRequestUri.FromUri(authorizationRequestUri);

var authorizationRequest = await _oid4VpHaipClient.ProcessAuthorizationRequestAsync(
haipAuthorizationRequestUri
Expand Down
Loading
Loading