Skip to content

Commit

Permalink
implement A256GCM (#189)
Browse files Browse the repository at this point in the history
Signed-off-by: Kevin <kevin.dinh@lissi.id>
  • Loading branch information
Dindexx authored Sep 23, 2024
1 parent e8633c1 commit 7942c50
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
using Hyperledger.Aries.Extensions;
using Jose;
using LanguageExt;
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using WalletFramework.Core.Base64Url;
using WalletFramework.Oid4Vc.Oid4Vp.Jwk;
using WalletFramework.Oid4Vc.Oid4Vp.Models;

Expand All @@ -13,38 +19,82 @@ public record EncryptedAuthorizationResponse(string 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) => Encrypt(
response,
authorizationRequest.ClientMetadata!.Jwks.First(),
authorizationRequest.Nonce,
mdocNonce);

public static EncryptedAuthorizationResponse Encrypt(
this AuthorizationResponse response,
JsonWebKey verifierPubKey,
string apv,
Option<Nonce> mdocNonce)
{
var verifierPubKey = authorizationRequest.ClientMetadata!.Jwks.First();
var apvBase64 = Base64UrlString.CreateBase64UrlString(apv.GetUTF8Bytes());

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

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


var settings = new JwtSettings();
settings.RegisterJwe(JweEncryption.A256GCM, new AesGcmEncryption());

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

return new EncryptedAuthorizationResponse(jwe);
}

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

return new FormUrlEncodedContent(content);
}

private class AesGcmEncryption : IJweAlgorithm
{
public int KeySize => 256;

public byte[] Decrypt(byte[] aad, byte[] cek, byte[] iv, byte[] cipherText, byte[] authTag)
=> throw new NotImplementedException();

public byte[][] Encrypt(byte[] aad, byte[] plainText, byte[] cek)
{
var iv = new byte[12];
new SecureRandom().NextBytes(iv);

var gcmBlockCipher = new GcmBlockCipher(new AesEngine());
var parameters = new AeadParameters(new KeyParameter(cek), 128, iv, aad);
gcmBlockCipher.Init(true, parameters);

var cipherText = new byte[gcmBlockCipher.GetOutputSize(plainText.Length)];
var len = gcmBlockCipher.ProcessBytes(plainText, 0, plainText.Length, cipherText, 0);
gcmBlockCipher.DoFinal(cipherText, len);

var authTag = new byte[16];
Array.Copy(cipherText, cipherText.Length - authTag.Length, authTag, 0, authTag.Length);

var actualCipherText = new byte[cipherText.Length - authTag.Length];
Array.Copy(cipherText, 0, actualCipherText, 0, actualCipherText.Length);

return [iv, actualCipherText, authTag];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ private async Task<List<ICredential>> GetMatchingCredentials(InputDescriptor inp
try
{
var jObj = record.Mdoc.IssuerSigned.IssuerNameSpaces.ToJObject();
// TODO: This functionality to add quotes to JSON Path Expression to make them usable for Newtonsoft is better suited somewhere else
if (jObj.SelectToken(field.Path.First().Replace("[", "['").Replace("]", "']"), true) is JValue value
if (jObj.SelectToken(field.Path.First(), true) is JValue value
&& field.Filter?.Const != null)
{
return field.Filter?.Const == value.Value?.ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
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 @@ -142,8 +143,8 @@ 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);
// 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();
var elementId = ElementIdentifier
Expand Down Expand Up @@ -374,7 +375,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 keys = claim.Split(new[] { "['", "']" }, StringSplitOptions.RemoveEmptyEntries);
var nameSpace = NameSpace.ValidNameSpace(keys[0]).UnwrapOrThrow();
var elementId = ElementIdentifier
Expand Down
35 changes: 17 additions & 18 deletions src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpHaipClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,24 +75,23 @@ public async Task<AuthorizationRequest> ProcessAuthorizationRequestAsync(
var authRequest = requestObject.ToAuthorizationRequest();
var clientMetadata = await FetchClientMetadata(authRequest);

return
requestObject.ClientIdScheme.Value switch
{
X509SanDns => requestObject
.ValidateJwt()
.ValidateTrustChain()
.ValidateSanName()
.ToAuthorizationRequest()
.WithX509(requestObject)
.WithClientMetadata(clientMetadata),
RedirectUri => requestObject
.ToAuthorizationRequest()
.WithClientMetadata(clientMetadata),
VerifierAttestation =>
throw new NotImplementedException("Verifier Attestation not yet implemented"),
_ => throw new InvalidOperationException(
$"Client ID Scheme {requestObject.ClientIdScheme} not supported")
};
return requestObject.ClientIdScheme.Value switch
{
X509SanDns => requestObject
.ValidateJwt()
.ValidateTrustChain()
.ValidateSanName()
.ToAuthorizationRequest()
.WithX509(requestObject)
.WithClientMetadata(clientMetadata),
RedirectUri => requestObject
.ToAuthorizationRequest()
.WithClientMetadata(clientMetadata),
VerifierAttestation =>
throw new NotImplementedException("Verifier Attestation not yet implemented"),
_ => throw new InvalidOperationException(
$"Client ID Scheme {requestObject.ClientIdScheme} not supported")
};
}

private async Task<ClientMetadata?> FetchClientMetadata(AuthorizationRequest authorizationRequest)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using FluentAssertions;
using LanguageExt;
using WalletFramework.Oid4Vc.Oid4Vp.AuthResponse.Encryption;
using WalletFramework.Oid4Vc.Oid4Vp.Models;
using WalletFramework.Oid4Vc.Tests.Oid4Vp.AuthResponse.Encryption.Samples;

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

public class EncryptedAuthorizationResponseTests
{
[Fact]
public void Can_Encrypt_With_Mdoc()
{
var nonce = Nonce.GenerateNonce();
var authResponse = AuthResponseEncryptionSamples.MdocResponse;
var mdocNonce = Nonce.GenerateNonce();

var sut = authResponse.Encrypt(
AuthResponseEncryptionSamples.Jwk,
nonce.AsBase64Url.ToString(),
mdocNonce);

sut.Jwe.Length().Should().Be(AuthResponseEncryptionSamples.ValidMdocJwe.Length);
}

[Fact]
public void Can_Encrypt_With_Sd_Jwt()
{
var nonce = Nonce.GenerateNonce();
var authResponse = AuthResponseEncryptionSamples.SdJwtResponse;

var sut = authResponse.Encrypt(
AuthResponseEncryptionSamples.Jwk,
nonce.AsBase64Url.ToString(),
Option<Nonce>.None);

sut.Jwe.Length.Should().Be(AuthResponseEncryptionSamples.ValidSdJwtJwe.Length);
}
}
Loading

0 comments on commit 7942c50

Please sign in to comment.