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

Encrypted Authorization response #170

Merged
merged 3 commits into from
Aug 23, 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: 3 additions & 1 deletion src/WalletFramework.Core/Base64Url/Base64UrlString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ namespace WalletFramework.Core.Base64Url;
public readonly struct Base64UrlString
{
private string Value { get; }

public byte[] AsByteArray => Base64UrlEncoder.DecodeBytes(Value);

public string AsString => Value;

private Base64UrlString(string value)
{
Value = value;
Expand Down
1 change: 1 addition & 0 deletions src/WalletFramework.Core/WalletFramework.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="jose-jwt" Version="5.0.0" />
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Expand Down
9 changes: 4 additions & 5 deletions src/WalletFramework.MdocLib/Mdoc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,10 @@ public static Validation<Mdoc> FromIssuerSigned(string base64UrlencodedCborByteS
});

var validateIntegrity = new List<Validator<Mdoc>>
{
MdocFun.DocTypeMatches,
MdocFun.DigestsMatch
}
.AggregateValidators();
{
MdocFun.DocTypeMatches,
MdocFun.DigestsMatch
}.AggregateValidators();

return
from bytes in decodeBase64Url(base64UrlencodedCborByteString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,7 @@ from issState in code.IssuerState
null,
null);

var authServerMetadata =
await FetchAuthorizationServerMetadataAsync(issuerMetadata);
var authServerMetadata = await FetchAuthorizationServerMetadataAsync(issuerMetadata);

_httpClient.DefaultRequestHeaders.Clear();
var response = await _httpClient.PostAsync(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Hyperledger.Aries.Extensions;
using Jose;
using LanguageExt;
using WalletFramework.Oid4Vc.Oid4Vp.Jwk;
using WalletFramework.Oid4Vc.Oid4Vp.Models;

namespace WalletFramework.Oid4Vc.Oid4Vp.AuthResponse.Encryption;

public record EncryptedAuthorizationResponse(string Jwe)
{
public override string ToString() => Jwe;
}

public static class EncryptedAuthorizationResponseFun
{
public static FormUrlEncodedContent ToFormUrl(this EncryptedAuthorizationResponse response)
{
var content = new Dictionary<string, string>
{
{ "response", response.ToString() },
};

return new FormUrlEncodedContent(content);
}

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

var headers = new Dictionary<string, object>
{
{ "apv", authorizationRequest.Nonce },
{ "kid", verifierPubKey.Kid }
};

mdocNonce.IfSome(nonce => headers.Add("apu", nonce.AsBase64Url.ToString()));

var jwe = JWE.EncryptBytes(
response.ToJson().GetUTF8Bytes(),
new [] { new JweRecipient(JweAlgorithm.ECDH_ES, verifierPubKey.ToEcdh()) },
JweEncryption.A256GCM,
mode: SerializationMode.Compact,
extraProtectedHeaders: headers);

return new EncryptedAuthorizationResponse(jwe);
}
}
23 changes: 23 additions & 0 deletions src/WalletFramework.Oid4Vc/Oid4Vp/Jwk/JwkFun.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;

namespace WalletFramework.Oid4Vc.Oid4Vp.Jwk;

public static class JwkFun
{
public static ECDiffieHellman ToEcdh(this JsonWebKey jwk)
{
var ecParameters = new ECParameters
{
Q =
{
X = Base64UrlEncoder.DecodeBytes(jwk.X),
Y = Base64UrlEncoder.DecodeBytes(jwk.Y)
},
// TODO: Map curve from jwk
Curve = ECCurve.NamedCurves.nistP256
};

return ECDiffieHellman.Create(ecParameters);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ namespace WalletFramework.Oid4Vc.Oid4Vp.Models;
/// </summary>
public record AuthorizationRequest
{
private const string DirectPost = "direct_post";
private const string DirectPostJwt = "direct_post.jwt";
public const string DirectPost = "direct_post";
public const string DirectPostJwt = "direct_post.jwt";

private const string VpToken = "vp_token";

Expand Down Expand Up @@ -45,6 +45,9 @@ public record AuthorizationRequest
/// </summary>
[JsonProperty("response_uri")]
public string ResponseUri { get; }

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

/// <summary>
/// Gets the client metadata. Contains the Verifier metadata.
Expand Down Expand Up @@ -89,6 +92,7 @@ private AuthorizationRequest(
string clientId,
string nonce,
string responseUri,
string responseMode,
ClientMetadata? clientMetadata,
string? clientMetadataUri,
string? scope,
Expand All @@ -101,6 +105,7 @@ private AuthorizationRequest(
Nonce = nonce;
PresentationDefinition = presentationDefinition;
ResponseUri = responseUri;
ResponseMode = responseMode;
Scope = scope;
State = state;
}
Expand Down
23 changes: 18 additions & 5 deletions src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationResponse.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models;

namespace WalletFramework.Oid4Vc.Oid4Vp.Models;

Expand All @@ -10,18 +11,30 @@ public class AuthorizationResponse
/// <summary>
/// Gets or sets the VP Token.
/// </summary>
[JsonProperty ("vp_token")]
[JsonProperty("vp_token")]
public string VpToken { get; set; } = null!;

/// <summary>
/// Gets or sets the Presentation Submission.
/// </summary>
[JsonProperty ("presentation_submission")]
public string PresentationSubmission { get; set; } = null!;
[JsonProperty("presentation_submission")]
public PresentationSubmission PresentationSubmission { get; set; } = null!;

/// <summary>
/// Gets or sets the State.
/// </summary>
[JsonProperty ("state", NullValueHandling = NullValueHandling.Ignore)]
public string? State { get; set; } = null!;
[JsonProperty("state", NullValueHandling = NullValueHandling.Ignore)]
public string? State { get; set; }
}

public static class AuthorizationResponseFun
{
public static FormUrlEncodedContent ToFormUrl(this AuthorizationResponse authorizationResponse)
{
var json = JsonConvert.SerializeObject(authorizationResponse);
var nameValueCollection =
JsonConvert.DeserializeObject<Dictionary<string, string>>(json)!.ToList();

return new FormUrlEncodedContent(nameValueCollection);
}
}
40 changes: 40 additions & 0 deletions src/WalletFramework.Oid4Vc/Oid4Vp/Models/ClientJwksConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WalletFramework.Core.Functional;
using WalletFramework.Core.Json;

namespace WalletFramework.Oid4Vc.Oid4Vp.Models;

public class ClientJwksConverter : JsonConverter<List<JsonWebKey>>
{
public override bool CanRead => true;

public override bool CanWrite => false;

public override void WriteJson(JsonWriter writer, List<JsonWebKey>? value, JsonSerializer serializer)
{
throw new NotImplementedException();
}

public override List<JsonWebKey> ReadJson(
JsonReader reader,
Type objectType,
List<JsonWebKey>? existingValue,
bool hasExistingValue,
JsonSerializer serializer)
{
var json = JObject.Load(reader);
var validKeysArray =
from keys in json.GetByKey("keys")
from keysArray in keys.ToJArray()
select keysArray;

var result = validKeysArray.OnSuccess(keysArray =>
{
return keysArray.Select(keyToken => JsonWebKey.Create(keyToken.ToString())).ToList();
}).UnwrapOrThrow();

return result;
}
}
18 changes: 14 additions & 4 deletions src/WalletFramework.Oid4Vc/Oid4Vp/Models/ClientMetadata.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;

namespace WalletFramework.Oid4Vc.Oid4Vp.Models;

/// <summary>
/// Represents the metadata of a client (verifier).
/// </summary>
// TODO: Rename this to VerifierMetadata
public record ClientMetadata
{
/// <summary>
Expand Down Expand Up @@ -38,8 +38,9 @@ public record ClientMetadata
[JsonProperty("logo_uri")]
public string? LogoUri { get; }

// TODO: JWKs of the verifier
// public List<JWK> PublicKeys { get; }
[JsonProperty("jwks")]
[JsonConverter(typeof(ClientJwksConverter))]
public List<JsonWebKey> Jwks { get; }

/// <summary>
/// The URI to a human-readable privacy policy document for the client (verifier).
Expand All @@ -54,7 +55,15 @@ public record ClientMetadata
public string? TosUri { get; }

[JsonConstructor]
private ClientMetadata(string? clientName, string? clientUri, string[]? contacts, string? logoUri, string? policyUri, string? tosUri, string[] redirectUris)
private ClientMetadata(
string? clientName,
string? clientUri,
string[]? contacts,
string? logoUri,
string? policyUri,
string? tosUri,
string[] redirectUris,
List<JsonWebKey> jwks)
{
ClientName = clientName;
ClientUri = clientUri;
Expand All @@ -63,5 +72,6 @@ private ClientMetadata(string? clientName, string? clientUri, string[]? contacts
PolicyUri = policyUri;
TosUri = tosUri;
RedirectUris = redirectUris;
Jwks = jwks;
}
}
28 changes: 11 additions & 17 deletions src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpClientService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using WalletFramework.MdocLib.Security;
using WalletFramework.MdocVc;
using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models;
using WalletFramework.Oid4Vc.Oid4Vp.AuthResponse.Encryption;
using WalletFramework.Oid4Vc.Oid4Vp.Models;
using WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Services;
using WalletFramework.SdJwtVc.Models.Records;
Expand Down Expand Up @@ -126,6 +127,7 @@ from path in field.Path.Select(path => path.TrimStart('$', '.'))

var toDisclose = claims.Select(claim =>
{
// TODO: This is needed because in mdoc the requested attributes look like this: $[Namespace][ElementId]. Refactor this more clean
var keys = claim.Split(new[] { "[", "]" }, StringSplitOptions.RemoveEmptyEntries);

var nameSpace = NameSpace.ValidNameSpace(keys[0]).UnwrapOrThrow();
Expand Down Expand Up @@ -170,36 +172,28 @@ from path in field.Path.Select(path => path.TrimStart('$', '.'))
presentationMaps.Select(tuple => (tuple.InputDescriptorId, tuple.Presentation, tuple.Format)).ToArray()
);

var httpClient = _httpClientFactory.CreateClient();
httpClient.DefaultRequestHeaders.Clear();
if (clientAttestation is not null)
httpClient.AddClientAttestationPopHeader(clientAttestation);

var json = SerializeObject(authorizationResponse);
var nameValueCollection = DeserializeObject<Dictionary<string, string>>(json)!.ToList();

// TODO: This is only a hack until the encryption response is implemented
mdocNonce.IfSome(nonce =>
var content = authorizationRequest.ResponseMode switch
{
var pair = new KeyValuePair<string, string>("apu", nonce.AsBase64Url.ToString());
nameValueCollection.Add(pair);
});

var content = new FormUrlEncodedContent(nameValueCollection);
AuthorizationRequest.DirectPost => authorizationResponse.ToFormUrl(),
AuthorizationRequest.DirectPostJwt => authorizationResponse.Encrypt(authorizationRequest, mdocNonce).ToFormUrl(),
_ => throw new ArgumentOutOfRangeException(nameof(authorizationRequest.ResponseMode))
};

var message = new HttpRequestMessage
{
RequestUri = new Uri(authorizationRequest.ResponseUri),
Method = HttpMethod.Post,
Content = content
};

// TODO: Introduce timeout
var httpClient = _httpClientFactory.CreateClient();
httpClient.DefaultRequestHeaders.Clear();
var responseMessage = await httpClient.SendAsync(message);
if (!responseMessage.IsSuccessStatusCode)
{
var str = await responseMessage.Content.ReadAsStringAsync();
throw new InvalidOperationException($"Authorization Response could not be sent with message {str}");
throw new InvalidOperationException($"Authorization Response failed with message {str}");
}

var presentedCredentials = presentationMaps.Select(presentationMap =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public async Task<AuthorizationResponse> CreateAuthorizationResponseAsync(

return new AuthorizationResponse
{
PresentationSubmission = SerializeObject(presentationSubmission),
PresentationSubmission = presentationSubmission,
VpToken = vpToken.Count > 1 ? SerializeObject(vpToken) : vpToken[0],
State = authorizationRequest.State
};
Expand Down
1 change: 0 additions & 1 deletion src/WalletFramework.Oid4Vc/WalletFramework.Oid4Vc.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down
Loading