Skip to content

Commit

Permalink
Encrypted Authorization response (#170)
Browse files Browse the repository at this point in the history
* encryption

Signed-off-by: Kevin <[email protected]>

* fix build

Signed-off-by: Kevin <[email protected]>

---------

Signed-off-by: Kevin <[email protected]>
  • Loading branch information
Dindexx authored Aug 23, 2024
1 parent ba3c20a commit 49afecf
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 38 deletions.
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

0 comments on commit 49afecf

Please sign in to comment.