diff --git a/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Extensions/JwtSecurityTokenExtensions.cs b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Extensions/JwtSecurityTokenExtensions.cs
new file mode 100644
index 00000000..c566a2d2
--- /dev/null
+++ b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Extensions/JwtSecurityTokenExtensions.cs
@@ -0,0 +1,57 @@
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using System.IdentityModel.Tokens.Jwt;
+using System.Linq;
+using System;
+using static Microsoft.IdentityModel.Tokens.Base64UrlEncoder;
+using static Org.BouncyCastle.Security.SignerUtilities;
+using static System.Text.Encoding;
+
+namespace Hyperledger.Aries.Features.OpenID4VC.Vp.Extensions
+{
+ ///
+ /// Extension methods for .
+ ///
+ public static class JwtSecurityTokenExtensions
+ {
+ ///
+ /// Validates the signature of the JWT token using the provided public key.
+ ///
+ /// The JWT token to validate.
+ /// The public key to use for validation.
+ /// True if the signature is valid, otherwise false.
+ public static bool IsSignatureValid(this JwtSecurityToken token, ECPublicKeyParameters publicKey)
+ {
+ var signer = GetSigner("SHA-256withECDSA");
+
+ signer.Init(false, publicKey);
+
+ var encodedHeaderAndPayload = UTF8.GetBytes(token.EncodedHeader + "." + token.EncodedPayload);
+
+ signer.BlockUpdate(encodedHeaderAndPayload, 0, encodedHeaderAndPayload.Length);
+
+ return signer.VerifySignature(
+ ConvertRawToDerFormat(
+ DecodeBytes(token.RawSignature)
+ )
+ );
+ }
+
+ private static byte[] ConvertRawToDerFormat(byte[] rawSignature)
+ {
+ if (rawSignature.Length != 64)
+ throw new ArgumentException("Raw signature should be 64 bytes long", nameof(rawSignature));
+
+ var r = new BigInteger(1, rawSignature.Take(32).ToArray());
+ var s = new BigInteger(1, rawSignature.Skip(32).ToArray());
+
+ var derSignature = new DerSequence(
+ new DerInteger(r),
+ new DerInteger(s)
+ ).GetDerEncoded();
+
+ return derSignature;
+ }
+ }
+}
diff --git a/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Extensions/X509CertificateExtensions.cs b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Extensions/X509CertificateExtensions.cs
new file mode 100644
index 00000000..cf5596a0
--- /dev/null
+++ b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Extensions/X509CertificateExtensions.cs
@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using System.Linq;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Pkix;
+using Org.BouncyCastle.Utilities.Collections;
+using Org.BouncyCastle.X509;
+using Org.BouncyCastle.X509.Store;
+
+namespace Hyperledger.Aries.Features.OpenID4VC.Vp.Extensions
+{
+ ///
+ /// Extension methods for .
+ ///
+ public static class X509CertificateExtensions
+ {
+ ///
+ /// Validates the trust chain of the certificate.
+ ///
+ /// The trust chain to validate.
+ /// True if the trust chain is valid, otherwise false.
+ public static bool IsTrustChainValid(this List trustChain)
+ {
+ var leafCert = trustChain.First();
+
+ var rootCerts =
+ new HashSet(
+ trustChain
+ .Skip(1)
+ .Where(cert => cert.IssuerDN.Equivalent(cert.SubjectDN))
+ .Select(cert => new TrustAnchor(cert, null))
+ .ToList()
+ );
+
+ var intermediateCerts =
+ new HashSet(
+ trustChain
+ .Skip(1)
+ .Where(cert => !cert.IssuerDN.Equivalent(cert.SubjectDN))
+ .Append(leafCert)
+ );
+
+ var builderParams =
+ new PkixBuilderParameters(
+ rootCerts,
+ new X509CertStoreSelector
+ {
+ Certificate = leafCert
+ }
+ )
+ {
+ IsRevocationEnabled =
+ leafCert.GetExtensionValue(X509Extensions.CrlDistributionPoints) is not null
+ };
+
+ builderParams.AddStore(
+ X509StoreFactory.Create(
+ "Certificate/Collection",
+ new X509CollectionStoreParameters(intermediateCerts)
+ )
+ );
+
+ // This throws if validation fails
+ new PkixCertPathValidator()
+ .Validate(
+ new PkixCertPathBuilder()
+ .Build(builderParams)
+ .CertPath,
+ builderParams
+ );
+
+ return true;
+ }
+ }
+}
diff --git a/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Models/AuthorizationRequest.cs b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Models/AuthorizationRequest.cs
index c85c8c69..fc7c41f5 100644
--- a/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Models/AuthorizationRequest.cs
+++ b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Models/AuthorizationRequest.cs
@@ -1,9 +1,8 @@
using System;
-using System.IdentityModel.Tokens.Jwt;
-using System.Linq;
-using System.Web;
using Hyperledger.Aries.Features.Pex.Models;
using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using static Hyperledger.Aries.Features.OpenId4Vc.Vp.Models.ClientIdScheme;
namespace Hyperledger.Aries.Features.OpenId4Vc.Vp.Models
{
@@ -12,156 +11,118 @@ namespace Hyperledger.Aries.Features.OpenId4Vc.Vp.Models
///
public class AuthorizationRequest
{
- ///
- /// Gets or sets the response type. Determines what the Authorization Response should contain.
- ///
- [JsonProperty("response_type")]
- public string ResponseType { get; set; } = null!;
-
- ///
- /// Gets or sets the client id. The Identifier of the Verifier
- ///
- [JsonProperty("client_id")]
- public string ClientId { get; set; } = null!;
+ private const string DirectPost = "direct_post";
- ///
- /// Gets or sets the client id scheme.
- ///
- [JsonProperty("client_id_scheme")]
- public string? ClientIdScheme { get; set; }
-
- ///
- /// Gets or sets the redirect uri.
- ///
- [JsonProperty("redirect_uri")]
- public string? RedirectUri { get; set; }
-
- ///
- /// Gets or sets the client metadata. Contains the Verifier metadata
- ///
- [JsonProperty("client_metadata")]
- public string? ClientMetadata { get; set; }
+ private const string VpToken = "vp_token";
///
- /// Gets or sets the client metadata uri. Can be used to retrieve the verifier metadata.
+ /// Gets the client id scheme.
///
- [JsonProperty("client_metadata_uri")]
- public string? ClientMetadataUri { get; set; }
+ [JsonProperty("client_id_scheme")]
+ public ClientIdScheme ClientIdScheme { get; }
///
- /// The scope of the request.
+ /// Gets the presentation definition. Contains the claims that the Verifier wants to receive.
///
- [JsonProperty("scope")]
- public string? Scope { get; set; }
+ [JsonProperty("presentation_definition")]
+ public PresentationDefinition PresentationDefinition { get; }
///
- /// Gets or sets the nonce. Random string for session binding.
+ /// Gets the client id. The Identifier of the Verifier.
///
- [JsonProperty("nonce")]
- public string Nonce { get; set; } = null!;
+ [JsonProperty("client_id")]
+ public string ClientId { get; }
///
- /// Gets or sets the response mode. Determines how to send the Authorization Response.
+ /// Gets the nonce. Random string for session binding.
///
- [JsonProperty("response_mode")]
- public string? ResponseMode { get; set; }
+ [JsonProperty("nonce")]
+ public string Nonce { get; }
///
- /// Gets or sets the response mode. Determines where to send the Authorization Response to.
+ /// Gets the response mode. Determines where to send the Authorization Response to.
///
[JsonProperty("response_uri")]
- public string ResponseUri { get; set; } = null!;
+ public string ResponseUri { get; }
///
- /// Gets or sets the state.
+ /// Gets the client metadata. Contains the Verifier metadata.
///
- [JsonProperty("state")]
- public string? State { get; set; }
+ [JsonProperty("client_metadata")]
+ public string? ClientMetadata { get; }
///
- /// Gets or sets the presentation definition. Contains the claims that the Verifier wants to receive.
+ /// Gets the client metadata uri. Can be used to retrieve the verifier metadata.
///
- [JsonIgnore]
- public PresentationDefinition PresentationDefinition { get; set; } = null!;
+ [JsonProperty("client_metadata_uri")]
+ public string? ClientMetadataUri { get; }
///
- /// Parses an Authorization Request from a Jwt.
+ /// The scope of the request.
///
- ///
- /// The AuthorizationRequest
- public static AuthorizationRequest? ParseFromJwt(string jwt)
- {
- var jwtHandler = new JwtSecurityTokenHandler();
- var token = jwtHandler.ReadToken(jwt) as JwtSecurityToken;
- if (token == null) return null;
-
- var presentationDefinition = JsonConvert.DeserializeObject(token.Payload["presentation_definition"].ToString());
- var authorizationRequest = JsonConvert.DeserializeObject(token.Payload.SerializeToJson());
-
- if (!(authorizationRequest != null && presentationDefinition != null))
- return null;
-
- authorizationRequest.PresentationDefinition = presentationDefinition;
-
- return authorizationRequest;
- }
+ [JsonProperty("scope")]
+ public string? Scope { get; }
///
- /// Parses an Authorization Request from a Uri.
+ /// Gets the state.
///
- ///
- /// The AuthorizationRequest
- public static AuthorizationRequest? ParseFromUri(Uri uri)
+ [JsonProperty("state")]
+ public string? State { get; }
+
+ [JsonConstructor]
+ private AuthorizationRequest(
+ ClientIdScheme clientIdScheme,
+ PresentationDefinition presentationDefinition,
+ string clientId,
+ string nonce,
+ string responseUri,
+ string? clientMetadata,
+ string? clientMetadataUri,
+ string? scope,
+ string? state)
{
- var query = HttpUtility.ParseQueryString(uri.Query);
- var dict = query.AllKeys.ToDictionary(key => key, key => query[key]);
- var json = JsonConvert.SerializeObject(dict);
-
- var presentationDefinition = JsonConvert.DeserializeObject(query["presentation_definition"]);
- var authorizationRequest = JsonConvert.DeserializeObject(json);
-
- if (!(authorizationRequest != null && presentationDefinition != null))
- return null;
-
- authorizationRequest.PresentationDefinition = presentationDefinition;
-
- return authorizationRequest;
+ ClientId = clientId;
+ ClientIdScheme = clientIdScheme;
+ ClientMetadata = clientMetadata;
+ ClientMetadataUri = clientMetadataUri;
+ Nonce = nonce;
+ PresentationDefinition = presentationDefinition;
+ ResponseUri = responseUri;
+ Scope = scope;
+ State = state;
}
- }
- ///
- /// Extension methods for the class.
- ///
- public static class AuthorizationRequestExtension
- {
///
- /// Checks if the Authorization Request is HAIP conform.
+ /// Creates a new instance of the class.
///
- ///
- /// Returns bool indicating whether the AuthorizationRequest is haip conform
- public static bool IsHaipConform(this AuthorizationRequest authorizationRequest)
+ /// The json representation of the authorization request.
+ /// A new instance of the class.
+ /// Thrown when the request does not match the HAIP.
+ public static AuthorizationRequest CreateAuthorizationRequest(string authorizationRequestJson)
+ => CreateAuthorizationRequest(JObject.Parse(authorizationRequestJson));
+
+ private static AuthorizationRequest CreateAuthorizationRequest(JObject authorizationRequestJson) =>
+ IsHaipConform(authorizationRequestJson)
+ ? authorizationRequestJson.ToObject()
+ ?? throw new InvalidOperationException("Could not parse the Authorization Request")
+ : throw new InvalidOperationException(
+ "Invalid Authorization Request. The request does not match the HAIP"
+ );
+
+ private static bool IsHaipConform(JObject authorizationRequestJson)
{
- if (authorizationRequest.ResponseType != "vp_token")
- return false;
-
- if (authorizationRequest.ResponseMode != "direct_post")
- return false;
-
- if (String.IsNullOrEmpty(authorizationRequest.ResponseUri))
- return false;
-
- if (!String.IsNullOrEmpty(authorizationRequest.RedirectUri))
- return false;
-
- if (authorizationRequest.ClientIdScheme == "redirect_uri"
- & authorizationRequest.ClientId != authorizationRequest.ResponseUri)
- return false;
-
- //TODO: Not supported yet
- //if (!(authorizationRequest.ClientIdScheme == "x509_san_dns" || authorizationRequest.ClientIdScheme == "verifier_attestation"))
- //return false;
-
- return true;
+ var responseType = authorizationRequestJson["response_type"]!.ToString();
+ var responseUri = authorizationRequestJson["response_uri"]!.ToString();
+ var responseMode = authorizationRequestJson["response_mode"]!.ToString();
+ var redirectUri = authorizationRequestJson["redirect_uri"];
+ var clientIdScheme = authorizationRequestJson["client_id_scheme"];
+
+ return
+ responseType == VpToken
+ && responseMode == DirectPost
+ && !string.IsNullOrEmpty(responseUri)
+ && redirectUri is null
+ && clientIdScheme!.ToString() is X509SanDnsScheme or VerifierAttestationScheme;
}
}
}
diff --git a/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Models/ClientIdScheme.cs b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Models/ClientIdScheme.cs
new file mode 100644
index 00000000..4a9aef0c
--- /dev/null
+++ b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Models/ClientIdScheme.cs
@@ -0,0 +1,67 @@
+using System;
+using static Hyperledger.Aries.Features.OpenId4Vc.Vp.Models.ClientIdScheme.ClientIdSchemeValue;
+
+namespace Hyperledger.Aries.Features.OpenId4Vc.Vp.Models
+{
+ ///
+ /// The client ID scheme used to obtain and validate metadata of the verifier.
+ ///
+ public record ClientIdScheme
+ {
+ ///
+ /// The client ID scheme value.
+ ///
+ public enum ClientIdSchemeValue
+ {
+ ///
+ /// The X509 SAN DNS client ID scheme.
+ ///
+ X509SanDns,
+
+ ///
+ /// The verifier attestation client ID scheme.
+ ///
+ VerifierAttestation
+ }
+
+ ///
+ /// The Verifier Attestation scheme.
+ ///
+ public const string VerifierAttestationScheme = "verifier_attestation";
+
+ ///
+ /// The X509 SAN DNS scheme.
+ ///
+ public const string X509SanDnsScheme = "x509_san_dns";
+
+ ///
+ /// The client ID scheme value.
+ ///
+ public ClientIdSchemeValue Value { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ private ClientIdScheme(ClientIdSchemeValue value) => Value = value;
+
+ ///
+ /// Creates a client ID scheme from the specified input.
+ ///
+ /// The input to create the client ID scheme from.
+ /// The client ID scheme created from the input.
+ /// The client ID scheme is not supported.
+ public static ClientIdScheme CreateClientIdScheme(string input) =>
+ input switch
+ {
+ X509SanDnsScheme => new ClientIdScheme(X509SanDns),
+ VerifierAttestationScheme =>
+ throw new NotImplementedException("Verifier Attestation not yet implemented"),
+ _ => throw new InvalidOperationException($"Client ID Scheme {input} is not supported")
+ };
+
+ ///
+ /// Implicitly converts the input to a client ID scheme.
+ ///
+ public static implicit operator ClientIdScheme(string input) => CreateClientIdScheme(input);
+ }
+}
diff --git a/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Models/RequestObject.cs b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Models/RequestObject.cs
new file mode 100644
index 00000000..dec60ffe
--- /dev/null
+++ b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Models/RequestObject.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.IdentityModel.Tokens.Jwt;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using Hyperledger.Aries.Features.OpenID4VC.Vp.Extensions;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.X509;
+using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
+using static Hyperledger.Aries.Features.OpenId4Vc.Vp.Models.AuthorizationRequest;
+using static Newtonsoft.Json.Linq.JArray;
+using static System.Convert;
+
+namespace Hyperledger.Aries.Features.OpenId4Vc.Vp.Models
+{
+ ///
+ /// Represents a request object as defined in the context of OpenID4VP.
+ ///
+ public readonly struct RequestObject
+ {
+ private JwtSecurityToken Value { get; }
+
+ ///
+ /// The client ID scheme used to obtain and validate metadata of the verifier.
+ ///
+ public ClientIdScheme ClientIdScheme => AuthorizationRequest.ClientIdScheme;
+
+ ///
+ /// The client ID of the verifier.
+ ///
+ public string ClientId => AuthorizationRequest.ClientId;
+
+ private AuthorizationRequest AuthorizationRequest { get; }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public static implicit operator RequestObject(JwtSecurityToken token) => new(token);
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public static implicit operator JwtSecurityToken(RequestObject requestObject) => requestObject.Value;
+
+ private RequestObject(JwtSecurityToken token)
+ {
+ AuthorizationRequest = CreateAuthorizationRequest(token.Payload.SerializeToJson());
+ Value = token;
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public static RequestObject CreateRequestObject(string requestObjectJson)
+ {
+ var tokenHandler = new JwtSecurityTokenHandler();
+
+ var jwt = tokenHandler.ReadJwtToken(requestObjectJson);
+
+ return new RequestObject(jwt);
+ }
+
+ ///
+ /// Gets the authorization request from the request object.
+ ///
+ public AuthorizationRequest ToAuthorizationRequest() => AuthorizationRequest;
+ }
+
+ ///
+ /// Extension methods for .
+ ///
+ public static class RequestObjectExtensions
+ {
+ ///
+ /// Validates the JWT signature.
+ ///
+ /// The validated request object.
+ /// Throws when validation fails
+ public static RequestObject ValidateJwt(this RequestObject requestObject) =>
+ ((JwtSecurityToken)requestObject).IsSignatureValid(
+ (ECPublicKeyParameters)requestObject.GetLeafCertificate().GetPublicKey()
+ )
+ ? requestObject
+ : throw new InvalidOperationException("Invalid JWT Signature");
+
+ ///
+ /// Validates the SAN name of the leaf certificate
+ ///
+ /// The validated request object
+ /// Throws when validation fails
+ public static RequestObject ValidateSanName(this RequestObject requestObject) =>
+ new X509Certificate2(
+ requestObject.GetLeafCertificate().GetEncoded()
+ )
+ .GetNameInfo(X509NameType.DnsName, false) == requestObject.ClientId
+ ? requestObject
+ : throw new InvalidOperationException("SAN does not match Client ID");
+
+ ///
+ /// Validates the trust chain of the leaf certificate
+ ///
+ /// The validated request object
+ /// Throws when validation fails
+ public static RequestObject ValidateTrustChain(this RequestObject requestObject) =>
+ requestObject
+ .GetCertificates()
+ .IsTrustChainValid()
+ ? requestObject
+ : throw new InvalidOperationException("Validation of trust chain failed");
+
+ private static List GetCertificates(this RequestObject requestObject) =>
+ Parse(((JwtSecurityToken)requestObject).Header.X5c)
+ .Select(
+ certAsJToken =>
+ new X509CertificateParser()
+ .ReadCertificate(
+ FromBase64String(certAsJToken.ToString())
+ )
+ )
+ .ToList();
+
+ private static X509Certificate GetLeafCertificate(this RequestObject requestObject) =>
+ GetCertificates(requestObject).First();
+ }
+}
diff --git a/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Services/IOid4VpClientService.cs b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Services/IOid4VpClientService.cs
index c67d8fdd..a23f9d52 100644
--- a/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Services/IOid4VpClientService.cs
+++ b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Services/IOid4VpClientService.cs
@@ -30,6 +30,6 @@ public interface IOid4VpClientService
///
/// A task representing the asynchronous operation. The task result contains the Callback Url of the Authorization Response if present.
///
- Task PrepareAndSendAuthorizationResponseAsync(IAgentContext agentContext, AuthorizationRequest authorizationRequest, SelectedCredential[] selectedCredentials);
+ Task SendAuthorizationResponseAsync(IAgentContext agentContext, AuthorizationRequest authorizationRequest, SelectedCredential[] selectedCredentials);
}
}
diff --git a/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Services/Oid4VpClientService.cs b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Services/Oid4VpClientService.cs
index 9f96b1d3..a83b1d8e 100644
--- a/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Services/Oid4VpClientService.cs
+++ b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Services/Oid4VpClientService.cs
@@ -2,27 +2,20 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
-using System.Reactive.Linq;
using System.Threading.Tasks;
using Hyperledger.Aries.Agents;
using Hyperledger.Aries.Features.OpenId4Vc.Vp.Models;
using Hyperledger.Aries.Features.OpenId4Vc.Vp.Services;
-using Hyperledger.Aries.Features.Pex.Models;
using Hyperledger.Aries.Features.SdJwt.Models.Records;
using Hyperledger.Aries.Features.SdJwt.Services.SdJwtVcHolderService;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
+using static Newtonsoft.Json.JsonConvert;
+using static Newtonsoft.Json.Linq.JObject;
namespace Hyperledger.Aries.Features.OpenID4VC.Vp.Services
{
///
internal class Oid4VpClientService : IOid4VpClientService
{
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly IOid4VpHaipClient _oid4VpHaipClient;
- private readonly IOid4VpRecordService _oid4VpRecordService;
- private readonly ISdJwtVcHolderService _sdJwtVcHolderService;
-
///
/// Initializes a new instance of the class.
///
@@ -42,118 +35,137 @@ public Oid4VpClientService(
_oid4VpRecordService = oid4VpRecordService;
}
+ private readonly IHttpClientFactory _httpClientFactory;
+ private readonly IOid4VpHaipClient _oid4VpHaipClient;
+ private readonly IOid4VpRecordService _oid4VpRecordService;
+ private readonly ISdJwtVcHolderService _sdJwtVcHolderService;
+
///
public async Task<(AuthorizationRequest, CredentialCandidates[])> ProcessAuthorizationRequestAsync(
IAgentContext agentContext, Uri authorizationRequestUri)
{
var haipAuthorizationRequestUri = HaipAuthorizationRequestUri.FromUri(authorizationRequestUri);
-
- var authorizationRequest = await _oid4VpHaipClient.ProcessAuthorizationRequestAsync(haipAuthorizationRequestUri);
+
+ var authorizationRequest =
+ await _oid4VpHaipClient.ProcessAuthorizationRequestAsync(haipAuthorizationRequestUri);
var credentials = await _sdJwtVcHolderService.ListAsync(agentContext);
- var credentialCandidates = await _sdJwtVcHolderService.FindCredentialCandidates(credentials.ToArray(),
- authorizationRequest.PresentationDefinition.InputDescriptors);
+
+ var credentialCandidates = await _sdJwtVcHolderService.FindCredentialCandidates(
+ credentials.ToArray(),
+ authorizationRequest.PresentationDefinition.InputDescriptors
+ );
return (authorizationRequest, credentialCandidates);
}
///
- public async Task PrepareAndSendAuthorizationResponseAsync(
+ public async Task SendAuthorizationResponseAsync(
IAgentContext agentContext,
AuthorizationRequest authorizationRequest,
SelectedCredential[] selectedCredentials)
{
- var authorizationResponse = await PrepareAuthorizationResponse(
- authorizationRequest,
- selectedCredentials);
-
- var redirectUri = await SendAuthorizationResponse(
- authorizationResponse,
- new Uri(authorizationRequest.ResponseUri));
-
- var presentedCredentials =
- from credential in selectedCredentials
- let inputD = authorizationRequest
- .PresentationDefinition
- .InputDescriptors
- .FirstOrDefault(x => x.Id == credential.InputDescriptorId)
- select new PresentedCredential
- {
- CredentialId = ((SdJwtRecord)credential.Credential).Id,
- PresentedClaims = GetPresentedClaimsForCredential(inputD, (SdJwtRecord)credential.Credential)
- };
-
- await _oid4VpRecordService.StoreAsync(
- agentContext,
- authorizationRequest.ClientId,
- authorizationRequest.ClientMetadata,
- authorizationRequest.PresentationDefinition.Name,
- presentedCredentials.ToArray());
-
- return redirectUri;
- }
-
- private async Task PrepareAuthorizationResponse(AuthorizationRequest authorizationRequest, SelectedCredential[] selectedCredentials)
- {
- var presentationMaps =
- from credential in selectedCredentials.ToObservable()
+ var createPresentationMaps =
+ from credential in selectedCredentials
from inputDescriptor in authorizationRequest.PresentationDefinition.InputDescriptors
where credential.InputDescriptorId == inputDescriptor.Id
- from presentation in Observable.FromAsync(async () => await CreatePresentation(
- inputDescriptor,
+ let disclosureNames = inputDescriptor
+ .Constraints
+ .Fields?
+ .SelectMany(field => field
+ .Path
+ .Select(path => path.Split(".").Last())
+ )
+ let createPresentation = _sdJwtVcHolderService.CreatePresentation(
(SdJwtRecord)credential.Credential,
+ disclosureNames.ToArray(),
authorizationRequest.ClientId,
- authorizationRequest.Nonce))
- select (inputDescriptor.Id, presentation);
-
- return await _oid4VpHaipClient.CreateAuthorizationResponseAsync(authorizationRequest,
- await presentationMaps.ToArray());
- }
-
- private async Task SendAuthorizationResponse(AuthorizationResponse authorizationResponse, Uri responseUri)
- {
- var authorizationResponseJson = JsonConvert.SerializeObject(authorizationResponse);
- var authorizationResponseDict = JsonConvert.DeserializeObject>(authorizationResponseJson);
+ authorizationRequest.Nonce
+ )
+ select (inputDescriptor.Id, createPresentation);
- var request = new HttpRequestMessage
+ var presentationMaps = new List<(string, string)>();
+ foreach (var (inputDescriptorId, createPresentation) in createPresentationMaps)
{
- RequestUri = responseUri,
- Method = HttpMethod.Post,
- Content = new FormUrlEncodedContent(authorizationResponseDict)
- };
+ presentationMaps.Add((inputDescriptorId, await createPresentation));
+ }
+
+ var authorizationResponse = await _oid4VpHaipClient.CreateAuthorizationResponseAsync(
+ authorizationRequest,
+ presentationMaps.ToArray()
+ );
var httpClient = _httpClientFactory.CreateClient();
httpClient.DefaultRequestHeaders.Clear();
- var responseMessage = await httpClient.SendAsync(request);
+ var responseMessage =
+ await httpClient.SendAsync(
+ new HttpRequestMessage
+ {
+ RequestUri = new Uri(authorizationRequest.ResponseUri),
+ Method = HttpMethod.Post,
+ Content = new FormUrlEncodedContent(
+ DeserializeObject>(
+ SerializeObject(authorizationResponse)
+ )?
+ .ToList()
+ ?? throw new InvalidOperationException("Authorization Response could not be parsed")
+ )
+ }
+ );
if (!responseMessage.IsSuccessStatusCode)
throw new InvalidOperationException("Authorization Response could not be sent");
-
- var content = await responseMessage.Content.ReadAsStringAsync();
- var redirectUri = string.IsNullOrEmpty(content) ? null : JObject.Parse(content)["redirect_uri"]?.ToString();
- return redirectUri == null ? null : new Uri(redirectUri);
- }
- private async Task CreatePresentation(InputDescriptor inputDescriptor, SdJwtRecord credential,
- string clientId, string nonce)
- {
- var claimsToDisclose = GetDisclosureNamesFromInputDescriptor(inputDescriptor);
- return await _sdJwtVcHolderService.CreatePresentation(credential, claimsToDisclose, clientId, nonce);
- }
+ var responseContent = await responseMessage.Content.ReadAsStringAsync();
- private static string[]? GetDisclosureNamesFromInputDescriptor(InputDescriptor inputDescriptor)
- => inputDescriptor.Constraints.Fields == null
+ var redirectUri = string.IsNullOrEmpty(responseContent)
? null
- : (from field in inputDescriptor.Constraints.Fields
- from path in field.Path
- select path.Split(".").Last()).ToArray();
-
- private static Dictionary GetPresentedClaimsForCredential(InputDescriptor inputDescriptor,
- SdJwtRecord sdJwtRecord)
- => (from field in inputDescriptor.Constraints.Fields
- from path in field.Path
- select sdJwtRecord.Claims.FirstOrDefault(x => x.Key == path.Split(".").Last()))
- .ToDictionary(claim => claim.Key, claim => new PresentedClaim(){Value = claim.Value});
+ : new Uri(Parse(responseContent)["redirect_uri"]?.ToString()!);
+
+ var presentedCredentials = selectedCredentials
+ .Select(credential =>
+ {
+ var inputDescriptor =
+ authorizationRequest
+ .PresentationDefinition
+ .InputDescriptors
+ .Single(descriptor => descriptor.Id == credential.InputDescriptorId);
+
+ return new PresentedCredential
+ {
+ CredentialId = ((SdJwtRecord)credential.Credential).Id,
+ PresentedClaims =
+ inputDescriptor
+ .Constraints
+ .Fields?
+ .SelectMany(
+ field => field.Path.Select(
+ path =>
+ ((SdJwtRecord)credential.Credential)
+ .Claims
+ .First(x =>
+ x.Key == path.Split(".").Last()
+ )
+ )
+ )
+ .ToDictionary(
+ claim => claim.Key,
+ claim => new PresentedClaim { Value = claim.Value }
+ )!
+ };
+ }
+ );
+
+ await _oid4VpRecordService.StoreAsync(
+ agentContext,
+ authorizationRequest.ClientId,
+ authorizationRequest.ClientMetadata,
+ authorizationRequest.PresentationDefinition.Name,
+ presentedCredentials.ToArray()
+ );
+
+ return redirectUri;
+ }
}
}
diff --git a/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Services/Oid4VpHaipClient.cs b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Services/Oid4VpHaipClient.cs
index 4bfdd371..65d06f0c 100644
--- a/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Services/Oid4VpHaipClient.cs
+++ b/src/Hyperledger.Aries/Features/OpenID4VC/Vp/Services/Oid4VpHaipClient.cs
@@ -1,21 +1,19 @@
-using System;
-using System.Collections.Generic;
-using System.Net;
-using System.Net.Http;
-using System.Threading.Tasks;
using Hyperledger.Aries.Features.OpenId4Vc.Vp.Models;
using Hyperledger.Aries.Features.Pex.Models;
using Hyperledger.Aries.Features.Pex.Services;
-using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System;
+using static Newtonsoft.Json.JsonConvert;
+using static Hyperledger.Aries.Features.OpenId4Vc.Vp.Models.RequestObject;
+using static Hyperledger.Aries.Features.OpenId4Vc.Vp.Models.ClientIdScheme.ClientIdSchemeValue;
namespace Hyperledger.Aries.Features.OpenId4Vc.Vp.Services
{
///
internal class Oid4VpHaipClient : IOid4VpHaipClient
{
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly IPexService _pexService;
-
///
/// Initializes a new instance of the class.
///
@@ -29,47 +27,21 @@ public Oid4VpHaipClient(
_pexService = pexService;
}
- ///
- public async Task ProcessAuthorizationRequestAsync(HaipAuthorizationRequestUri haipAuthorizationRequestUri)
- {
- AuthorizationRequest? authorizationRequest = null;
-
- if (!String.IsNullOrEmpty(haipAuthorizationRequestUri.RequestUri))
- {
- var httpClient = _httpClientFactory.CreateClient();
- httpClient.DefaultRequestHeaders.Clear();
-
- var response = await httpClient.GetAsync(haipAuthorizationRequestUri.RequestUri);
-
- if (response.StatusCode == HttpStatusCode.OK)
- {
- var content = await response.Content.ReadAsStringAsync();
- authorizationRequest = AuthorizationRequest.ParseFromJwt(content);
- }
- }
- else
- {
- authorizationRequest = AuthorizationRequest.ParseFromUri(haipAuthorizationRequestUri.Uri);
- }
-
- if (authorizationRequest == null)
- throw new InvalidOperationException("Could not parse Authorization Request Url");
-
- if (!authorizationRequest.IsHaipConform())
- throw new InvalidOperationException("Authorization Request is not HAIP conform");
+ private readonly IHttpClientFactory _httpClientFactory;
+ private readonly IPexService _pexService;
- return authorizationRequest;
- }
-
///
- public async Task CreateAuthorizationResponseAsync(AuthorizationRequest authorizationRequest, (string inputDescriptorId, string presentation)[] presentationMap)
+ public async Task CreateAuthorizationResponseAsync(
+ AuthorizationRequest authorizationRequest,
+ (string inputDescriptorId, string presentation)[] presentationMap)
{
var descriptorMaps = new List();
var vpToken = new List();
+
for (var index = 0; index < presentationMap.Length; index++)
{
vpToken.Add(presentationMap[index].presentation);
-
+
var descriptorMap = new DescriptorMap
{
Format = "vc+sd-jwt",
@@ -80,14 +52,46 @@ public async Task CreateAuthorizationResponseAsync(Author
descriptorMaps.Add(descriptorMap);
}
- var presentationSubmission = await _pexService.CreatePresentationSubmission(authorizationRequest.PresentationDefinition, descriptorMaps.ToArray());
-
+ var presentationSubmission =
+ await _pexService.CreatePresentationSubmission(authorizationRequest.PresentationDefinition,
+ descriptorMaps.ToArray());
+
return new AuthorizationResponse
{
- PresentationSubmission = JsonConvert.SerializeObject(presentationSubmission),
- VpToken = vpToken.Count > 1 ? JsonConvert.SerializeObject(vpToken) : vpToken[0],
+ PresentationSubmission = SerializeObject(presentationSubmission),
+ VpToken = vpToken.Count > 1 ? SerializeObject(vpToken) : vpToken[0],
State = authorizationRequest.State
};
}
+
+ ///
+ public async Task ProcessAuthorizationRequestAsync(
+ HaipAuthorizationRequestUri haipAuthorizationRequestUri)
+ {
+ var httpClient = _httpClientFactory.CreateClient();
+ httpClient.DefaultRequestHeaders.Clear();
+
+ var requestObject =
+ CreateRequestObject(
+ await httpClient.GetStringAsync(haipAuthorizationRequestUri.RequestUri)
+ );
+
+ return
+ requestObject.ClientIdScheme.Value switch
+ {
+ X509SanDns =>
+ requestObject
+ .ValidateJwt()
+ .ValidateTrustChain()
+ .ValidateSanName()
+ .ToAuthorizationRequest(),
+ VerifierAttestation =>
+ throw new NotImplementedException("Verifier Attestation not yet implemented"),
+ _ =>
+ throw new InvalidOperationException(
+ $"Client ID Scheme {requestObject.ClientIdScheme} not supported"
+ )
+ };
+ }
}
}
diff --git a/src/Hyperledger.Aries/Features/Pex/Models/PresentationDefinition.cs b/src/Hyperledger.Aries/Features/Pex/Models/PresentationDefinition.cs
index 1efa62a1..fb80c387 100644
--- a/src/Hyperledger.Aries/Features/Pex/Models/PresentationDefinition.cs
+++ b/src/Hyperledger.Aries/Features/Pex/Models/PresentationDefinition.cs
@@ -4,44 +4,62 @@
namespace Hyperledger.Aries.Features.Pex.Models
{
///
- /// Represents objects that articulate what proofs a Verifier requires
+ /// Represents objects that articulate what proofs a Verifier requires
///
public class PresentationDefinition
{
///
- /// This MUST be a string. The string SHOULD provide a unique ID for the desired context.
+ /// Gets or sets the format of the presentation definition
+ /// This property is optional.
///
- [JsonProperty("id", Required = Required.Always)]
- public string Id { get; private set; } = null!;
+ [JsonProperty("format")]
+ public Dictionary Formats { get; }
///
- /// Represents a collection of submission requirements
+ /// Represents a collection of input descriptors.
///
- [JsonProperty("submission_requirements")]
- public SubmissionRequirement[] SubmissionRequirements { get; private set; } = null!;
-
+ [JsonProperty("input_descriptors", Required = Required.Always)]
+ public InputDescriptor[] InputDescriptors { get; }
+
///
- /// Represents a collection of input descriptors.
+ /// This MUST be a string. The string SHOULD provide a unique ID for the desired context.
///
- [JsonProperty("input_descriptors", Required = Required.Always)]
- public InputDescriptor[] InputDescriptors { get; private set; } = null!;
-
+ [JsonProperty("id", Required = Required.Always)]
+ public string Id { get; }
+
///
- /// This SHOULD be a human-friendly string intended to constitute a distinctive designation of the Presentation Definition.
+ /// This SHOULD be a human-friendly string intended to constitute a distinctive designation of the Presentation
+ /// Definition.
///
[JsonProperty("name")]
- public string? Name { get; private set; }
+ public string? Name { get; }
///
- /// This MUST be a string that describes the purpose for which the Presentation Definition's inputs are being used for.
+ /// This MUST be a string that describes the purpose for which the Presentation Definition's inputs are being used for.
///
- public string? Purpose { get; private set; }
-
+ public string? Purpose { get; }
+
///
- /// Gets or sets the format of the presentation definition
- /// This property is optional.
+ /// Represents a collection of submission requirements
///
- [JsonProperty("format")]
- public Dictionary Formats { get; private set; } = null!;
+ [JsonProperty("submission_requirements")]
+ public SubmissionRequirement[] SubmissionRequirements { get; }
+
+ [JsonConstructor]
+ private PresentationDefinition(
+ Dictionary formats,
+ InputDescriptor[] inputDescriptors,
+ string id,
+ string? name,
+ string? purpose,
+ SubmissionRequirement[] submissionRequirements)
+ {
+ Formats = formats;
+ InputDescriptors = inputDescriptors;
+ Id = id;
+ Name = name;
+ Purpose = purpose;
+ SubmissionRequirements = submissionRequirements;
+ }
}
}
diff --git a/src/Hyperledger.Aries/Hyperledger.Aries.csproj b/src/Hyperledger.Aries/Hyperledger.Aries.csproj
index d5973db4..7b8b6435 100644
--- a/src/Hyperledger.Aries/Hyperledger.Aries.csproj
+++ b/src/Hyperledger.Aries/Hyperledger.Aries.csproj
@@ -17,6 +17,7 @@
+
diff --git a/test/Hyperledger.Aries.Tests/Features/OpenId4Vc/Vp/Services/Oid4VpHaipClientTests.cs b/test/Hyperledger.Aries.Tests/Features/OpenId4Vc/Vp/Services/Oid4VpHaipClientTests.cs
index bb89f7fe..1be76237 100644
--- a/test/Hyperledger.Aries.Tests/Features/OpenId4Vc/Vp/Services/Oid4VpHaipClientTests.cs
+++ b/test/Hyperledger.Aries.Tests/Features/OpenId4Vc/Vp/Services/Oid4VpHaipClientTests.cs
@@ -1,5 +1,5 @@
using System;
-using System.Collections.Generic;
+using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
@@ -7,9 +7,7 @@
using FluentAssertions;
using Hyperledger.Aries.Features.OpenId4Vc.Vp.Models;
using Hyperledger.Aries.Features.OpenId4Vc.Vp.Services;
-using Hyperledger.Aries.Features.Pex.Models;
using Hyperledger.Aries.Features.Pex.Services;
-using Hyperledger.Aries.Tests.Extensions;
using Moq;
using Moq.Protected;
using Xunit;
@@ -18,90 +16,69 @@ namespace Hyperledger.Aries.Tests.Features.OpenId4Vc.Vp.Services
{
public class Oid4VpHaipClientTests
{
- private const string AuthRequestViaUri =
- "haip://?response_type=vp_token&nonce=87554784260280280442092184171274132458&client_id=https%3A%2F%2Fnc-sd-jwt.lambda.d3f.me%2Findex.php%2Fapps%2Fssi_login%2Foidc%2Fcallback&redirect_uri=https%3A%2F%2Fnc-sd-jwt.lambda.d3f.me%2Findex.php%2Fapps%2Fssi_login%2Foidc%2Fcallback&presentation_definition=%7B%22id%22%3A%224dd1c26a-2f46-43ae-a711-70888c93fb4f%22%2C%22input_descriptors%22%3A%5B%7B%22id%22%3A%22NextcloudCredential%22%2C%22format%22%3A%7B%22vc%2Bsd-jwt%22%3A%7B%22proof_type%22%3A%5B%22JsonWebSignature2020%22%5D%7D%7D%2C%22constraints%22%3A%7B%22limit_disclosure%22%3A%22required%22%2C%22fields%22%3A%5B%7B%22path%22%3A%5B%22%24.type%22%5D%2C%22filter%22%3A%7B%22type%22%3A%22string%22%2C%22const%22%3A%22VerifiedEMail%22%7D%7D%2C%7B%22path%22%3A%5B%22%24.credentialSubject.email%22%5D%7D%5D%7D%7D%5D%7D";
-
private const string AuthRequestWithRequestUri =
"haip://?client_id=https%3A%2F%2Fnc-sd-jwt.lambda.d3f.me%2Findex.php%2Fapps%2Fssi_login%2Foidc%2Fcallback&request_uri=https%3A%2F%2Fnc-sd-jwt.lambda.d3f.me%2Findex.php%2Fapps%2Fssi_login%2Foidc%2Frequestobject%2F4ba20ad0cb08545830aa549ab4305c03";
private const string RequestUriResponse =
"eyJhbGciOiJub25lIn0.ew0KICAicmVzcG9uc2VfdHlwZSI6ICJ2cF90b2tlbiIsDQogICJyZXNwb25zZV9tb2RlIjogImRpcmVjdF9wb3N0IiwNCiAgImNsaWVudF9pZCI6ICJodHRwczovL25jLXNkLWp3dC5sYW1iZGEuZDNmLm1lL2luZGV4LnBocC9hcHBzL3NzaV9sb2dpbi9vaWRjL2NhbGxiYWNrIiwNCiAgInJlc3BvbnNlX3VyaSI6ICJodHRwczovL25jLXNkLWp3dC5sYW1iZGEuZDNmLm1lL2luZGV4LnBocC9hcHBzL3NzaV9sb2dpbi9vaWRjL2NhbGxiYWNrIiwNCiAgIm5vbmNlIjogIjg3NTU0Nzg0MjYwMjgwMjgwNDQyMDkyMTg0MTcxMjc0MTMyNDU4IiwNCiAgInByZXNlbnRhdGlvbl9kZWZpbml0aW9uIjogew0KICAgICJpZCI6ICI0ZGQxYzI2YS0yZjQ2LTQzYWUtYTcxMS03MDg4OGM5M2ZiNGYiLA0KICAgICJpbnB1dF9kZXNjcmlwdG9ycyI6IFsNCiAgICAgIHsNCiAgICAgICAgImlkIjogIk5leHRjbG91ZENyZWRlbnRpYWwiLA0KICAgICAgICAiZm9ybWF0Ijogew0KICAgICAgICAgICJ2YytzZC1qd3QiOiB7DQogICAgICAgICAgICAicHJvb2ZfdHlwZSI6IFsNCiAgICAgICAgICAgICAgIkpzb25XZWJTaWduYXR1cmUyMDIwIg0KICAgICAgICAgICAgXQ0KICAgICAgICAgIH0NCiAgICAgICAgfSwNCiAgICAgICAgImNvbnN0cmFpbnRzIjogew0KICAgICAgICAgICJsaW1pdF9kaXNjbG9zdXJlIjogInJlcXVpcmVkIiwNCiAgICAgICAgICAiZmllbGRzIjogWw0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAicGF0aCI6IFsNCiAgICAgICAgICAgICAgICAiJC50eXBlIg0KICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAiZmlsdGVyIjogew0KICAgICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsDQogICAgICAgICAgICAgICAgImNvbnN0IjogIlZlcmlmaWVkRU1haWwiDQogICAgICAgICAgICAgIH0NCiAgICAgICAgICAgIH0sDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICJwYXRoIjogWw0KICAgICAgICAgICAgICAgICIkLmNyZWRlbnRpYWxTdWJqZWN0LmVtYWlsIg0KICAgICAgICAgICAgICBdDQogICAgICAgICAgICB9DQogICAgICAgICAgXQ0KICAgICAgICB9DQogICAgICB9DQogICAgXQ0KICB9DQp9.";
- private Oid4VpHaipClient _oid4VpHaipClient;
-
private readonly Mock _httpMessageHandlerMock = new Mock();
private readonly Mock _httpClientFactoryMock = new Mock();
- [Theory]
- [InlineData(AuthRequestWithRequestUri, RequestUriResponse)]
- public async Task CanProcessAuthorizationRequest(string authorizationRequestUri, string httpResponse)
+ private Oid4VpHaipClient _oid4VpHaipClient;
+
+ [Fact]
+ public async Task CanProcessAuthorizationRequest()
{
// Arrange
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(httpResponse)
+ Content = new StringContent(RequestUriResponse)
};
-
+
_httpMessageHandlerMock.Protected()
- .Setup>("SendAsync", ItExpr.IsAny(),
- ItExpr.IsAny())
+ .Setup>(
+ "SendAsync",
+ ItExpr.IsAny(),
+ ItExpr.IsAny()
+ )
.ReturnsAsync(() => httpResponseMessage);
var httpClient = new HttpClient(_httpMessageHandlerMock.Object);
- _httpClientFactoryMock.Setup(f => f.CreateClient(It.IsAny())).Returns(httpClient);
-
+
+ _httpClientFactoryMock.Setup(
+ f => f.CreateClient(It.IsAny())
+ )
+ .Returns(httpClient);
+
_oid4VpHaipClient = new Oid4VpHaipClient(_httpClientFactoryMock.Object, new PexService());
-
- var expectedAuthorizationRequest = GetExpectedAuthorizationRequest();
// Act
var authorizationRequest = await _oid4VpHaipClient.ProcessAuthorizationRequestAsync(
- HaipAuthorizationRequestUri.FromUri(new Uri(authorizationRequestUri)));
+ HaipAuthorizationRequestUri.FromUri(new Uri(AuthRequestWithRequestUri))
+ );
// Assert
- authorizationRequest.Should().BeEquivalentTo(expectedAuthorizationRequest);
- }
+ authorizationRequest.ClientId.Should().Be("https://verifier.com/presentation/authorization-response");
+ authorizationRequest.ResponseUri.Should().Be("https://verifier.com/presentation/authorization-response");
+ authorizationRequest.Nonce.Should().Be("87554784260280280442092184171274132458");
+ authorizationRequest.PresentationDefinition.Id.Should().Be("4dd1c26a-2f46-43ae-a711-70888c93fb4f");
- private AuthorizationRequest GetExpectedAuthorizationRequest()
- {
- var format = new Format();
- format.PrivateSet(x => x.ProofTypes, new[] { "JsonWebSignature2020" });
-
- var filter = new Filter();
- filter.PrivateSet(x => x.Type, "string");
- filter.PrivateSet(x => x.Const, "VerifiedEMail");
-
- var fieldOne = new Field();
- fieldOne.PrivateSet(x => x.Filter, filter);
- fieldOne.PrivateSet(x => x.Path, new [] {"$.type"});
-
- var fieldTwo = new Field();
- fieldTwo.PrivateSet(x => x.Path, new [] {"$.credentialSubject.email"});
-
- var constraints = new Constraints();
- constraints.PrivateSet(x => x.LimitDisclosure, "required");
- constraints.PrivateSet(x => x.Fields, new [] {fieldOne, fieldTwo});
-
- var inputDescriptor = new InputDescriptor();
- inputDescriptor.PrivateSet(x => x.Id, "NextcloudCredential");
- inputDescriptor.PrivateSet(x => x.Formats, new Dictionary { {"vc+sd-jwt", format }});
- inputDescriptor.PrivateSet(x => x.Constraints, constraints);
-
- var presentationDefinition = new PresentationDefinition();
- presentationDefinition.PrivateSet(x => x.Id, "4dd1c26a-2f46-43ae-a711-70888c93fb4f");
- presentationDefinition.PrivateSet(x => x.InputDescriptors, new[] { inputDescriptor });
-
- return new AuthorizationRequest()
- {
- ResponseType = "vp_token",
- ClientId = "https://verifier.com/presentation/authorization-response",
- ResponseUri = "https://verifier.com/presentation/authorization-response",
- Nonce = "87554784260280280442092184171274132458",
- ResponseMode = "direct_post",
- PresentationDefinition = presentationDefinition,
- };
+ var inputDescriptor = authorizationRequest.PresentationDefinition.InputDescriptors.First();
+
+ inputDescriptor.Id.Should().Be("NextCloudCredential");
+
+ inputDescriptor.Formats.First().Key.Should().Be("vc+sd-jwt");
+ inputDescriptor.Formats.First().Value.ProofTypes.First().Should().Be("JsonWebSignature2020");
+
+ inputDescriptor.Constraints.LimitDisclosure.Should().Be("required");
+
+ inputDescriptor.Constraints.Fields!.First().Filter!.Type.Should().Be("string");
+ inputDescriptor.Constraints.Fields!.First().Filter!.Const.Should().Be("VerifiedEmail");
+ inputDescriptor.Constraints.Fields!.First().Path.First().Should().Be("$.type");
+
+ inputDescriptor.Constraints.Fields![1].Path.First().Should().Be("$.credentialSubject.email");
}
-
}
}
diff --git a/test/Hyperledger.Aries.Tests/Features/Pex/Services/PexServiceTests.cs b/test/Hyperledger.Aries.Tests/Features/Pex/Services/PexServiceTests.cs
index 25837d0f..708d41bb 100644
--- a/test/Hyperledger.Aries.Tests/Features/Pex/Services/PexServiceTests.cs
+++ b/test/Hyperledger.Aries.Tests/Features/Pex/Services/PexServiceTests.cs
@@ -51,29 +51,5 @@ public async Task Can_Create_Presentation_Submission()
presentationSubmission.DescriptorMap[i].Path.Should().Be(credentials[i].Path);
}
}
-
- [Fact]
- public async Task Throws_Exception_When_Descriptors_Are_Missing()
- {
- var inputDescriptor = new InputDescriptor();
- inputDescriptor.PrivateSet(x => x.Id, Guid.NewGuid().ToString());
- inputDescriptor.PrivateSet(x => x.Formats, new Dictionary { {"format-1", null }});
-
- var presentationDefinition = new PresentationDefinition();
- presentationDefinition.PrivateSet(x => x.Id, Guid.NewGuid().ToString());
- presentationDefinition.PrivateSet(x => x.InputDescriptors, new[] { inputDescriptor });
-
- var credentials = new []
- {
- new DescriptorMap
- {
- Id = Guid.NewGuid().ToString(),
- Format = presentationDefinition.InputDescriptors[0].Formats.First().Key,
- Path = "$.credentials[0]"
- }
- };
-
- await Assert.ThrowsAsync(() => _pexService.CreatePresentationSubmission(presentationDefinition, credentials));
- }
}
}
diff --git a/test/Hyperledger.Aries.Tests/Integration/Oid4VpClientServiceTests.cs b/test/Hyperledger.Aries.Tests/Integration/Oid4VpClientServiceTests.cs
index f5f34a5e..9b836105 100644
--- a/test/Hyperledger.Aries.Tests/Integration/Oid4VpClientServiceTests.cs
+++ b/test/Hyperledger.Aries.Tests/Integration/Oid4VpClientServiceTests.cs
@@ -13,11 +13,9 @@
using Hyperledger.Aries.Features.OpenId4Vc.Vp.Models;
using Hyperledger.Aries.Features.OpenId4Vc.Vp.Services;
using Hyperledger.Aries.Features.OpenID4VC.Vp.Services;
-using Hyperledger.Aries.Features.Pex.Models;
using Hyperledger.Aries.Features.Pex.Services;
using Hyperledger.Aries.Features.SdJwt.Services.SdJwtVcHolderService;
using Hyperledger.Aries.Storage;
-using Hyperledger.Aries.Tests.Extensions;
using Hyperledger.TestHarness.Mock;
using Moq;
using Moq.Protected;
@@ -31,22 +29,23 @@ public class Oid4VpClientServiceTests : IAsyncLifetime
private const string AuthRequestWithRequestUri =
"haip://?client_id=https%3A%2F%2Fnc-sd-jwt.lambda.d3f.me%2Findex.php%2Fapps%2Fssi_login%2Foidc%2Fcallback&request_uri=https%3A%2F%2Fnc-sd-jwt.lambda.d3f.me%2Findex.php%2Fapps%2Fssi_login%2Foidc%2Frequestobject%2F4ba20ad0cb08545830aa549ab4305c03";
- private const string Vct = "VerifiedEmail";
+ private const string CombinedIssuance =
+ "eyJ4NWMiOlsiTUlJQ09qQ0NBZUdnQXdJQkFnSUJBekFLQmdncWhrak9QUVFEQWpCak1Rc3dDUVlEVlFRR0V3SkVSVEVQTUEwR0ExVUVCd3dHUW1WeWJHbHVNUjB3R3dZRFZRUUtEQlJDZFc1a1pYTmtjblZqYTJWeVpXa2dSMjFpU0RFS01BZ0dBMVVFQ3d3QlNURVlNQllHQTFVRUF3d1BTVVIxYm1sdmJpQlVaWE4wSUVOQk1CNFhEVEl6TURjeE9ERXlOVE16TlZvWERUSTRNRGN4TmpFeU5UTXpOVm93V1RFTE1Ba0dBMVVFQmhNQ1JFVXhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SOHdIUVlEVlFRRERCWldaWEpwWm1sbFpDQkZMVTFoYVd3Z1NYTnpkV1Z5TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFOGoxOEsyZTRjZGRkdjRzaGRFUE84Z251MTJnM242RDFtRC9KU09TcEdDZlc5YUdoaU92bHpPck5icGRzTGVlWjVtclV3SXpra3BrMUhPVnZwSTNwVXFPQmp6Q0JqREFkQmdOVkhRNEVGZ1FVTFo4eWFCbDJJUVJWeCtrTGY4d3ZmRFpIY1pRd0RBWURWUjBUQVFIL0JBSXdBREFPQmdOVkhROEJBZjhFQkFNQ0I0QXdMQVlEVlIwUkJDVXdJNEloYVhOemRXVnlMVzl3Wlc1cFpEUjJZeTV6YzJrdWRHbHlMbUoxWkhKMUxtUmxNQjhHQTFVZEl3UVlNQmFBRkUrVzZ6N2FqVHVtZXgrWWNGYm9OclZlQzJ0Uk1Bb0dDQ3FHU000OUJBTUNBMGNBTUVRQ0lDZU5KYi85OENkV3RPdEtrREs0bm1WSGV4N0ZJclJQMlBRY3lmOVIzUGdPQWlCUHNkeENsakZXcTdxUGFOdUthUzhnTjRqZEkyVXUrNlNKaWZLZGp6SDdsQT09IiwiTUlJQ0xUQ0NBZFNnQXdJQkFnSVVNWVVIaEdEOWhVL2MwRW82bVc4cmpqZUordDB3Q2dZSUtvWkl6ajBFQXdJd1l6RUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWNNQmtKbGNteHBiakVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneENqQUlCZ05WQkFzTUFVa3hHREFXQmdOVkJBTU1EMGxFZFc1cGIyNGdWR1Z6ZENCRFFUQWVGdzB5TXpBM01UTXdPVEkxTWpoYUZ3MHpNekEzTVRBd09USTFNamhhTUdNeEN6QUpCZ05WQkFZVEFrUkZNUTh3RFFZRFZRUUhEQVpDWlhKc2FXNHhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SZ3dGZ1lEVlFRRERBOUpSSFZ1YVc5dUlGUmxjM1FnUTBFd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTRUh6OFlqckZ5VE5IR0x2TzE0RUF4bTl5aDhiS09na1V6WVdjQzFjdnJKbjVKZ0hZSE14WmJOTU8xM0VoMEVyMjczOFFRT2dlUm9aTUlUYW9ka2ZOU28yWXdaREFkQmdOVkhRNEVGZ1FVVDViclB0cU5PNlo3SDVod1Z1ZzJ0VjRMYTFFd0h3WURWUjBqQkJnd0ZvQVVUNWJyUHRxTk82WjdINWh3VnVnMnRWNExhMUV3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBT0JnTlZIUThCQWY4RUJBTUNBWVl3Q2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnWTBEZXJkQ3h0NHpHUFluOHlOckR4SVdDSkhwenE0QmRqZHNWTjJvMUdSVUNJQjBLQTdiRzFGVkIxSWlLOGQ1N1FBTCtQRzlYNWxkS0c3RWtvQW1oV1ZLZSJdLCJraWQiOiJNR3d3WjZSbE1HTXhDekFKQmdOVkJBWVRBa1JGTVE4d0RRWURWUVFIREFaQ1pYSnNhVzR4SFRBYkJnTlZCQW9NRkVKMWJtUmxjMlJ5ZFdOclpYSmxhU0JIYldKSU1Rb3dDQVlEVlFRTERBRkpNUmd3RmdZRFZRUUREQTlKUkhWdWFXOXVJRlJsYzNRZ1EwRUNBUU09IiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJfc2QiOlsibmJkd2Z0NE9QdmcycFFlaVFYOHdoc2hnZ0VVTTdBY29mdHRWUE95ejJpdyIsIkxoQjF2dE9WM1ZHd3V6QmhKWnhoUUd5OUNSY0l0dC1QSmkydDRvRk83X28iLCJZNEl2Uk4yY2VDU2V6aXZKRjREMHFDc0JQNW81eUZVdDJiXy1YRkFXTGZjIiwieU9kNkRJbGFDUERXTG9xLUJfY2JQWTY4dFZmV18wU25NRGQzeU5qRDIxRSJdLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLW9wZW5pZDR2Yy5zc2kudGlyLmJ1ZHJ1LmRlIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IjN1ZVRuN3VYLTZpSmxJYllOU2xrM0NSa3pwVDZGYldNMkxjdDZhdy1HZEEiLCJ5IjoiWlFlZnFJVnlzOG1MT05PZXBSclNsOWhXVGhGai1HTUVWR0pGUVk1TXQ2TSJ9fSwidHlwZSI6IlZlcmlmaWVkRU1haWwiLCJleHAiOjE2OTcyODIwOTgsImlhdCI6MTY5NjQxODA5OH0.Sbj1LaWpz45iqsdS8NFaLFgZ7G5hj1ofYLlO4rTI-jHELD6ORMGe1LVHe7IiOr_DNCDDde0ScGIEZKRiNCHEfA~WyJZTVVoZzh3Q2RHUGJjV245NW9MbUtBIiwiZW1haWwiLCJqb3RlbWVrMzYxQGZlc2dyaWQuY29tIl0";
+
+ private const string ExpectedRedirectUrl =
+ "https://client.example.org/cb#response_code=091535f699ea575c7937fa5f0f454aee";
private const string KeyBindingJwtMock =
"eyJhbGciOiJFUzI1NiIsInR5cCI6ImtiK2p3dCJ9.eyJhdWQiOiJodHRwczovL3ZlcmlmaWVyLnNzaS50aXIuYnVkcnUuZGUvcHJlc2VudGF0aW9uL2F1dGhvcml6YXRpb24tcmVzcG9uc2UiLCJub25jZSI6IkxIc1lGRnlpMnNXQzZIM3ZSWVFsNFQiLCJpYXQiOjE2OTY0MjcwNzR9.Kxj1e7ucZeAnFnfOjo05QnW-DYeEprciDqkOhe6fhXIWprEYd1NJ6a0gpZJ66oTJsv49ExvDOKTLOzt6R75gcg";
+ private const string KeyId = "KeyId";
+
private const string RequestUriResponse =
"eyJ4NWMiOlsiTUlJQ0x6Q0NBZFdnQXdJQkFnSUJCREFLQmdncWhrak9QUVFEQWpCak1Rc3dDUVlEVlFRR0V3SkVSVEVQTUEwR0ExVUVCd3dHUW1WeWJHbHVNUjB3R3dZRFZRUUtEQlJDZFc1a1pYTmtjblZqYTJWeVpXa2dSMjFpU0RFS01BZ0dBMVVFQ3d3QlNURVlNQllHQTFVRUF3d1BTVVIxYm1sdmJpQlVaWE4wSUVOQk1CNFhEVEl6TURnd016QTROREkwTkZvWERUSTRNRGd3TVRBNE5ESTBORm93VlRFTE1Ba0dBMVVFQmhNQ1JFVXhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1Sc3dHUVlEVlFRRERCSlBjR1Z1U1dRMFZsQWdWbVZ5YVdacFpYSXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUnNoUzVDaVBrSzVXRUN1RHpybmN0SXBwYm1nc1lkOURzT1lEcElFeFpFczFmUWNOeXZrQjVFZU5Xc2MwU0ExUU5xd3dHVzRndUZLZzBJZjFKR0R4VWZvNEdITUlHRU1CMEdBMVVkRGdRV0JCUmZMQVBzeG1Mc3AxblEvRk12RkkzN0MzQmxZREFNQmdOVkhSTUJBZjhFQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBa0JnTlZIUkVFSFRBYmdobDJaWEpwWm1sbGNpNXpjMmt1ZEdseUxtSjFaSEoxTG1SbE1COEdBMVVkSXdRWU1CYUFGRStXNno3YWpUdW1leCtZY0Zib05yVmVDMnRSTUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSUNWZURUMnNkZHhySEMrZ0ZJTUVmc3huc0lXRmdIdnZlZnBuWXZrb0RjbHdBaUVBMlFnRVRHV3hIWUVObWxsNDA2VUNwYnFRb1kzMzJPbE9qdDUwWjc2WHBtQT0iLCJNSUlDTFRDQ0FkU2dBd0lCQWdJVU1ZVUhoR0Q5aFUvYzBFbzZtVzhyamplSit0MHdDZ1lJS29aSXpqMEVBd0l3WXpFTE1Ba0dBMVVFQmhNQ1JFVXhEekFOQmdOVkJBY01Ca0psY214cGJqRWRNQnNHQTFVRUNnd1VRblZ1WkdWelpISjFZMnRsY21WcElFZHRZa2d4Q2pBSUJnTlZCQXNNQVVreEdEQVdCZ05WQkFNTUQwbEVkVzVwYjI0Z1ZHVnpkQ0JEUVRBZUZ3MHlNekEzTVRNd09USTFNamhhRncwek16QTNNVEF3T1RJMU1qaGFNR014Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUW93Q0FZRFZRUUxEQUZKTVJnd0ZnWURWUVFEREE5SlJIVnVhVzl1SUZSbGMzUWdRMEV3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVNFSHo4WWpyRnlUTkhHTHZPMTRFQXhtOXloOGJLT2drVXpZV2NDMWN2ckpuNUpnSFlITXhaYk5NTzEzRWgwRXIyNzM4UVFPZ2VSb1pNSVRhb2RrZk5TbzJZd1pEQWRCZ05WSFE0RUZnUVVUNWJyUHRxTk82WjdINWh3VnVnMnRWNExhMUV3SHdZRFZSMGpCQmd3Rm9BVVQ1YnJQdHFOTzZaN0g1aHdWdWcydFY0TGExRXdFZ1lEVlIwVEFRSC9CQWd3QmdFQi93SUJBREFPQmdOVkhROEJBZjhFQkFNQ0FZWXdDZ1lJS29aSXpqMEVBd0lEUndBd1JBSWdZMERlcmRDeHQ0ekdQWW44eU5yRHhJV0NKSHB6cTRCZGpkc1ZOMm8xR1JVQ0lCMEtBN2JHMUZWQjFJaUs4ZDU3UUFMK1BHOVg1bGRLRzdFa29BbWhXVktlIl0sImtpZCI6Ik1Hd3daNlJsTUdNeEN6QUpCZ05WQkFZVEFrUkZNUTh3RFFZRFZRUUhEQVpDWlhKc2FXNHhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SZ3dGZ1lEVlFRRERBOUpSSFZ1YVc5dUlGUmxjM1FnUTBFQ0FRUT0iLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJyZXNwb25zZV90eXBlIjoidnBfdG9rZW4iLCJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJpZCI6IjE1ZDQwNjU0LWM2NTgtNDkzOC1hYzA3LWVjYjQxYzlhZmIxMCIsImlucHV0X2Rlc2NyaXB0b3JzIjpbeyJpZCI6IjUwYjZlNGYzLTYyMmEtNDk3NC1iMzMwLTVlNzIwZWM5MjJiZiIsImZvcm1hdCI6eyJ2YytzZC1qd3QiOnsicHJvb2ZfdHlwZSI6WyJKc29uV2ViU2lnbmF0dXJlMjAyMCJdfX0sImNvbnN0cmFpbnRzIjp7ImxpbWl0X2Rpc2Nsb3N1cmUiOiJyZXF1aXJlZCIsImZpZWxkcyI6W3sicGF0aCI6WyIkLnR5cGUiXSwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJjb25zdCI6IlZlcmlmaWVkRU1haWwifX0seyJwYXRoIjpbIiQuZW1haWwiXX1dfX1dfSwicmVzcG9uc2VfdXJpIjoiaHR0cHM6Ly92ZXJpZmllci5zc2kudGlyLmJ1ZHJ1LmRlL3ByZXNlbnRhdGlvbi9hdXRob3JpemF0aW9uLXJlc3BvbnNlIiwibm9uY2UiOiJZZjg4dGRlZzhZTTkyM3E0aFFBRzlPIiwiY2xpZW50X2lkIjoiaHR0cHM6Ly92ZXJpZmllci5zc2kudGlyLmJ1ZHJ1LmRlL3ByZXNlbnRhdGlvbi9hdXRob3JpemF0aW9uLXJlc3BvbnNlIiwicmVzcG9uc2VfbW9kZSI6ImRpcmVjdF9wb3N0In0.sdeLcG6Ta4ozfbDuHBr2Vq-Ro2WpdUIhJWy3BgazyvrgkQw27uTFGioPWXNCruK5H5E5nvHS420u5tv0671tjg";
- private const string CombinedIssuance =
- "eyJ4NWMiOlsiTUlJQ09qQ0NBZUdnQXdJQkFnSUJBekFLQmdncWhrak9QUVFEQWpCak1Rc3dDUVlEVlFRR0V3SkVSVEVQTUEwR0ExVUVCd3dHUW1WeWJHbHVNUjB3R3dZRFZRUUtEQlJDZFc1a1pYTmtjblZqYTJWeVpXa2dSMjFpU0RFS01BZ0dBMVVFQ3d3QlNURVlNQllHQTFVRUF3d1BTVVIxYm1sdmJpQlVaWE4wSUVOQk1CNFhEVEl6TURjeE9ERXlOVE16TlZvWERUSTRNRGN4TmpFeU5UTXpOVm93V1RFTE1Ba0dBMVVFQmhNQ1JFVXhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SOHdIUVlEVlFRRERCWldaWEpwWm1sbFpDQkZMVTFoYVd3Z1NYTnpkV1Z5TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFOGoxOEsyZTRjZGRkdjRzaGRFUE84Z251MTJnM242RDFtRC9KU09TcEdDZlc5YUdoaU92bHpPck5icGRzTGVlWjVtclV3SXpra3BrMUhPVnZwSTNwVXFPQmp6Q0JqREFkQmdOVkhRNEVGZ1FVTFo4eWFCbDJJUVJWeCtrTGY4d3ZmRFpIY1pRd0RBWURWUjBUQVFIL0JBSXdBREFPQmdOVkhROEJBZjhFQkFNQ0I0QXdMQVlEVlIwUkJDVXdJNEloYVhOemRXVnlMVzl3Wlc1cFpEUjJZeTV6YzJrdWRHbHlMbUoxWkhKMUxtUmxNQjhHQTFVZEl3UVlNQmFBRkUrVzZ6N2FqVHVtZXgrWWNGYm9OclZlQzJ0Uk1Bb0dDQ3FHU000OUJBTUNBMGNBTUVRQ0lDZU5KYi85OENkV3RPdEtrREs0bm1WSGV4N0ZJclJQMlBRY3lmOVIzUGdPQWlCUHNkeENsakZXcTdxUGFOdUthUzhnTjRqZEkyVXUrNlNKaWZLZGp6SDdsQT09IiwiTUlJQ0xUQ0NBZFNnQXdJQkFnSVVNWVVIaEdEOWhVL2MwRW82bVc4cmpqZUordDB3Q2dZSUtvWkl6ajBFQXdJd1l6RUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWNNQmtKbGNteHBiakVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneENqQUlCZ05WQkFzTUFVa3hHREFXQmdOVkJBTU1EMGxFZFc1cGIyNGdWR1Z6ZENCRFFUQWVGdzB5TXpBM01UTXdPVEkxTWpoYUZ3MHpNekEzTVRBd09USTFNamhhTUdNeEN6QUpCZ05WQkFZVEFrUkZNUTh3RFFZRFZRUUhEQVpDWlhKc2FXNHhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SZ3dGZ1lEVlFRRERBOUpSSFZ1YVc5dUlGUmxjM1FnUTBFd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTRUh6OFlqckZ5VE5IR0x2TzE0RUF4bTl5aDhiS09na1V6WVdjQzFjdnJKbjVKZ0hZSE14WmJOTU8xM0VoMEVyMjczOFFRT2dlUm9aTUlUYW9ka2ZOU28yWXdaREFkQmdOVkhRNEVGZ1FVVDViclB0cU5PNlo3SDVod1Z1ZzJ0VjRMYTFFd0h3WURWUjBqQkJnd0ZvQVVUNWJyUHRxTk82WjdINWh3VnVnMnRWNExhMUV3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBT0JnTlZIUThCQWY4RUJBTUNBWVl3Q2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnWTBEZXJkQ3h0NHpHUFluOHlOckR4SVdDSkhwenE0QmRqZHNWTjJvMUdSVUNJQjBLQTdiRzFGVkIxSWlLOGQ1N1FBTCtQRzlYNWxkS0c3RWtvQW1oV1ZLZSJdLCJraWQiOiJNR3d3WjZSbE1HTXhDekFKQmdOVkJBWVRBa1JGTVE4d0RRWURWUVFIREFaQ1pYSnNhVzR4SFRBYkJnTlZCQW9NRkVKMWJtUmxjMlJ5ZFdOclpYSmxhU0JIYldKSU1Rb3dDQVlEVlFRTERBRkpNUmd3RmdZRFZRUUREQTlKUkhWdWFXOXVJRlJsYzNRZ1EwRUNBUU09IiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJfc2QiOlsibmJkd2Z0NE9QdmcycFFlaVFYOHdoc2hnZ0VVTTdBY29mdHRWUE95ejJpdyIsIkxoQjF2dE9WM1ZHd3V6QmhKWnhoUUd5OUNSY0l0dC1QSmkydDRvRk83X28iLCJZNEl2Uk4yY2VDU2V6aXZKRjREMHFDc0JQNW81eUZVdDJiXy1YRkFXTGZjIiwieU9kNkRJbGFDUERXTG9xLUJfY2JQWTY4dFZmV18wU25NRGQzeU5qRDIxRSJdLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLW9wZW5pZDR2Yy5zc2kudGlyLmJ1ZHJ1LmRlIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IjN1ZVRuN3VYLTZpSmxJYllOU2xrM0NSa3pwVDZGYldNMkxjdDZhdy1HZEEiLCJ5IjoiWlFlZnFJVnlzOG1MT05PZXBSclNsOWhXVGhGai1HTUVWR0pGUVk1TXQ2TSJ9fSwidHlwZSI6IlZlcmlmaWVkRU1haWwiLCJleHAiOjE2OTcyODIwOTgsImlhdCI6MTY5NjQxODA5OH0.Sbj1LaWpz45iqsdS8NFaLFgZ7G5hj1ofYLlO4rTI-jHELD6ORMGe1LVHe7IiOr_DNCDDde0ScGIEZKRiNCHEfA~WyJZTVVoZzh3Q2RHUGJjV245NW9MbUtBIiwiZW1haWwiLCJqb3RlbWVrMzYxQGZlc2dyaWQuY29tIl0";
-
- private const string KeyId = "KeyId";
-
- private const string ExpectedRedirectUrl = "https://client.example.org/cb#response_code=091535f699ea575c7937fa5f0f454aee";
-
-
+ private const string Vct = "VerifiedEmail";
+
+
public Oid4VpClientServiceTests()
{
var holder = new Holder();
@@ -63,34 +62,40 @@ public Oid4VpClientServiceTests()
_oid4VpRecordService
);
- _keyStoreMock.Setup(j =>
- j.GenerateProofOfPossessionAsync(It.IsAny(), It.IsAny(), It.IsAny(),
- It.IsAny()))
+ _keyStoreMock.Setup(keyStore =>
+ keyStore.GenerateProofOfPossessionAsync(
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny()
+ )
+ )
.ReturnsAsync(KeyBindingJwtMock);
}
-
+
+ private readonly DefaultSdJwtVcHolderService _sdJwtVcHolderService;
+
private readonly Mock _httpMessageHandlerMock = new Mock();
private readonly Mock _httpClientFactoryMock = new Mock();
private readonly Mock _keyStoreMock = new Mock();
private MockAgent? _agent1;
private readonly MockAgentRouter _router = new MockAgentRouter();
-
+
private readonly Oid4VpClientService _oid4VpClientService;
- private readonly Oid4VpRecordService _oid4VpRecordService;
- private readonly DefaultSdJwtVcHolderService _sdJwtVcHolderService;
private readonly Oid4VpHaipClient _oid4VpHaipClient;
+ private readonly Oid4VpRecordService _oid4VpRecordService;
- private readonly OidIssuerMetadata _oidIssuerMetadata = new OidIssuerMetadata
+ private readonly OidIssuerMetadata _oidIssuerMetadata = new OidIssuerMetadata()
{
CredentialIssuer = "https://issuer.io",
CredentialEndpoint = "https://issuer.io/credential",
- CredentialsSupported = new Dictionary()
+ CredentialsSupported = new Dictionary
{
{
"VerifiedEmail", new OidCredentialMetadata
{
Format = "vc+sdjwt",
- CredentialDefinition = new OidCredentialDefinition()
+ CredentialDefinition = new OidCredentialDefinition
{
Vct = Vct,
Claims = new Dictionary()
@@ -100,21 +105,29 @@ public Oid4VpClientServiceTests()
}
};
- private readonly WalletConfiguration _config1 = new WalletConfiguration { Id = Guid.NewGuid().ToString() };
- private readonly WalletCredentials _cred = new WalletCredentials { Key = "2" };
+ private readonly WalletConfiguration _config1 = new WalletConfiguration() { Id = Guid.NewGuid().ToString() };
+ private readonly WalletCredentials _cred = new WalletCredentials() { Key = "2" };
[Fact]
public async Task CanExecuteOpenId4VpFlow()
{
//Arrange
SetupHttpClient(RequestUriResponse);
-
- await _sdJwtVcHolderService.StoreAsync(_agent1.Context, CombinedIssuance, KeyId, _oidIssuerMetadata, "VerifiedEmail");
+
+ await _sdJwtVcHolderService.StoreAsync(
+ _agent1.Context,
+ CombinedIssuance,
+ KeyId,
+ _oidIssuerMetadata,
+ "VerifiedEmail"
+ );
//Act
var (authorizationRequest, credentials) =
- await _oid4VpClientService.ProcessAuthorizationRequestAsync(_agent1.Context,
- new Uri(AuthRequestWithRequestUri));
+ await _oid4VpClientService.ProcessAuthorizationRequestAsync(
+ _agent1.Context,
+ new Uri(AuthRequestWithRequestUri)
+ );
var selectedCandidates = new SelectedCredential
{
@@ -123,16 +136,20 @@ await _oid4VpClientService.ProcessAuthorizationRequestAsync(_agent1.Context,
};
SetupHttpClient(
- "{'redirect_uri':'https://client.example.org/cb#response_code=091535f699ea575c7937fa5f0f454aee'}");
-
- var response = await _oid4VpClientService.PrepareAndSendAuthorizationResponseAsync(_agent1.Context,
- authorizationRequest, new[] { selectedCandidates });
+ "{'redirect_uri':'https://client.example.org/cb#response_code=091535f699ea575c7937fa5f0f454aee'}"
+ );
- var expectedAuthorizationRequest = GetExpectedAuthorizationRequest();
+ var response = await _oid4VpClientService.SendAuthorizationResponseAsync(
+ _agent1.Context,
+ authorizationRequest,
+ new[] { selectedCandidates }
+ );
- authorizationRequest.Should().BeEquivalentTo(expectedAuthorizationRequest);
+ // Assert
credentials.Length.Should().Be(1);
+
response.Should().BeEquivalentTo(new Uri(ExpectedRedirectUrl));
+
(await _oid4VpRecordService.ListAsync(_agent1.Context)).Count.Should().Be(1);
}
@@ -143,51 +160,19 @@ public async Task DisposeAsync()
public async Task InitializeAsync()
{
- _agent1 = await MockUtils.CreateAsync("agent1", _config1, _cred,
- new MockAgentHttpHandler(cb => _router.RouteMessage(cb.name, cb.data)));
+ _agent1 =
+ await MockUtils.CreateAsync(
+ "agent1",
+ _config1,
+ _cred,
+ new MockAgentHttpHandler(
+ cb => _router.RouteMessage(cb.name, cb.data)
+ )
+ );
+
_router.RegisterAgent(_agent1);
}
- private AuthorizationRequest GetExpectedAuthorizationRequest()
- {
- var format = new Format();
- format.PrivateSet(x => x.ProofTypes, new[] { "JsonWebSignature2020" });
-
- var filter = new Filter();
- filter.PrivateSet(x => x.Type, "string");
- filter.PrivateSet(x => x.Const, "VerifiedEMail");
-
- var fieldOne = new Field();
- fieldOne.PrivateSet(x => x.Filter, filter);
- fieldOne.PrivateSet(x => x.Path, new[] { "$.type" });
-
- var fieldTwo = new Field();
- fieldTwo.PrivateSet(x => x.Path, new[] { "$.email" });
-
- var constraints = new Constraints();
- constraints.PrivateSet(x => x.LimitDisclosure, "required");
- constraints.PrivateSet(x => x.Fields, new[] { fieldOne, fieldTwo });
-
- var inputDescriptor = new InputDescriptor();
- inputDescriptor.PrivateSet(x => x.Id, "50b6e4f3-622a-4974-b330-5e720ec922bf");
- inputDescriptor.PrivateSet(x => x.Formats, new Dictionary { { "vc+sd-jwt", format } });
- inputDescriptor.PrivateSet(x => x.Constraints, constraints);
-
- var presentationDefinition = new PresentationDefinition();
- presentationDefinition.PrivateSet(x => x.Id, "15d40654-c658-4938-ac07-ecb41c9afb10");
- presentationDefinition.PrivateSet(x => x.InputDescriptors, new[] { inputDescriptor });
-
- return new AuthorizationRequest
- {
- ResponseType = "vp_token",
- ClientId = "https://verifier.com/presentation/authorization-response",
- Nonce = "Yf88tdeg8YM923q4hQAG9O",
- ResponseMode = "direct_post",
- ResponseUri = "https://verifier.com/presentation/authorization-response",
- PresentationDefinition = presentationDefinition
- };
- }
-
private void SetupHttpClient(string response)
{
var httpResponseMessage = new HttpResponseMessage