Skip to content
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
2 changes: 1 addition & 1 deletion src/WalletFramework.MdocLib/DocType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace WalletFramework.MdocLib;

public readonly struct DocType
public readonly record struct DocType
{
private string Value { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
using WalletFramework.Oid4Vc.RelyingPartyAuthentication.Implementations;
using WalletFramework.SdJwtVc;
using WalletFramework.SdJwtVc.Services;
using IRpRegistrarService = WalletFramework.Oid4Vc.RelyingPartyAuthentication.Implementations.IRpRegistrarService;

namespace WalletFramework.Oid4Vc.DependencyInjection;

Expand All @@ -47,15 +46,19 @@ public static IServiceCollection AddOpenIdServices(this IServiceCollection build
{
builder.AddSingleton<IAesGcmEncryption, AesGcmEncryption>();
builder.AddSingleton<IAuthFlowSessionStorage, AuthFlowSessionStorage>();
builder.AddSingleton<IAuthorizationResponseEncryptionService, AuthorizationResponseEncryptionService>();
builder.AddSingleton<IAuthorizationRequestService, AuthorizationRequestService>();
builder.AddSingleton<IAuthorizationResponseEncryptionService, AuthorizationResponseEncryptionService>();
builder.AddSingleton<ICandidateQueryService, CandidateQueryService>();
builder.AddSingleton<IClientAttestationService, ClientAttestationService>();
builder.AddSingleton<ICoseSign1Signer, CoseSign1Signer>();
builder.AddSingleton<ICredentialNonceService, CredentialNonceService>();
builder.AddSingleton<ICredentialOfferService, CredentialOfferService>();
builder.AddSingleton<ICredentialRequestService, CredentialRequestService>();
builder.AddSingleton<ICredentialSetService, CredentialSetService>();
builder.AddSingleton<ICredentialSetStorage, CredentialSetStorage>();
builder.AddSingleton<IDcqlService, DcqlService>();
builder.AddSingleton<IDPopHttpClient, DPopHttpClient>();
builder.AddSingleton<IDcApiService, DcApiService>();
builder.AddSingleton<IDcqlService, DcqlService>();
builder.AddSingleton<IIssuerMetadataService, IssuerMetadataService>();
builder.AddSingleton<IMdocAuthenticationService, MdocAuthenticationService>();
builder.AddSingleton<IMdocCandidateService, MdocCandidateService>();
Expand All @@ -66,15 +69,13 @@ public static IServiceCollection AddOpenIdServices(this IServiceCollection build
builder.AddSingleton<IOid4VpHaipClient, Oid4VpHaipClient>();
builder.AddSingleton<IOid4VpRecordService, Oid4VpRecordService>();
builder.AddSingleton<IPexService, PexService>();
builder.AddSingleton<ICandidateQueryService, CandidateQueryService>();
builder.AddSingleton<IPresentationService, PresentationService>();
builder.AddSingleton<IRecordsMigrationService, RecordsMigrationService>();
builder.AddSingleton<IRpAuthService, RpAuthService>();
builder.AddSingleton<RelyingPartyAuthentication.Abstractions.IRpRegistrarService, IRpRegistrarService>();
builder.AddSingleton<IRpRegistrarService, RpRegistrarService>();
builder.AddSingleton<IStatusListService, StatusListService>();
builder.AddSingleton<ITokenService, TokenService>();
builder.AddSingleton<IVctMetadataService, VctMetadataService>();
builder.AddSingleton<ICredentialNonceService, CredentialNonceService>();
builder.AddSingleton<IClientAttestationService, ClientAttestationService>();

builder.AddSdJwtVcServices();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,4 @@ select displays.Select(credentialDisplay =>

return record;
}

internal static SdJwtDoc ToSdJwtDoc(this SdJwtRecord record)
{
return new SdJwtDoc(record.EncodedIssuerSignedJwt + "~" + string.Join("~", record.Disclosures) + "~");
}
}
135 changes: 135 additions & 0 deletions src/WalletFramework.Oid4Vc/Oid4Vp/AuthResponse/VpToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WalletFramework.Oid4Vc.Oid4Vp.Dcql.CredentialQueries;
using OneOf;
using WalletFramework.Core.Functional;
using WalletFramework.Oid4Vc.Oid4Vp.Models;

namespace WalletFramework.Oid4Vc.Oid4Vp.AuthResponse;

// Second Parameter is for PEX which will be deprecated soon
[JsonConverter(typeof(VpTokenConverter))]
public record VpToken(OneOf<DcqlVpToken, string> Value)
{
public string AsString()
{
return Value.Match(
dcql => dcql.AsJsonString(),
pex => pex
);
}

public JObject AsJObject()
{
return Value.Match(
dcql => dcql.AsJObject(),
pex => throw new ArgumentException("PEX is not supported")
);
}
}

public class VpTokenConverter : JsonConverter<VpToken>
{
public override void WriteJson(JsonWriter writer, VpToken? value, JsonSerializer serializer)
{
value!.Value.Switch(
dcql =>
{
var jObject = new JObject();
foreach (var pair in dcql.Presentations)
{
jObject[pair.Key.AsString()] = new JArray(pair.Value.Select(presentation => presentation.Value));
}
jObject.WriteTo(writer);
// writer.WriteValue(dcql.AsString());
},
writer.WriteValue
);
}

public override VpToken ReadJson(JsonReader reader, Type objectType, VpToken? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);

if (token.Type == JTokenType.String)
{
// It's a PEX string
return new VpToken(token.ToString());
}
else if (token.Type == JTokenType.Object)
{
// It's a DCQL VP Token
var jObject = (JObject)token;
var presentations = new Dictionary<CredentialQueryId, List<Presentation>>();

foreach (var property in jObject.Properties())
{
var credentialQueryId = CredentialQueryId.Create(property.Name).UnwrapOrThrow();
var presentationList = new List<Presentation>();

if (property.Value is JArray array)
{
foreach (var item in array)
{
presentationList.Add(new Presentation(item.ToString()));
}
}

presentations[credentialQueryId] = presentationList;
}

return new VpToken(new DcqlVpToken(presentations));
}
else
{
throw new JsonSerializationException($"Unexpected token type: {token.Type}");
}
}
}

// VP Draft 29; This is the correct one
public record DcqlVpToken(Dictionary<CredentialQueryId, List<Presentation>> Presentations)
{
public string AsJsonString()
{
var jObject = new JObject();

foreach (var pair in Presentations)
{
jObject[pair.Key.AsString()] = new JArray(pair.Value.Select(presentation => presentation.Value));
}

return jObject.ToString();
}

public JObject AsJObject()
{
var jObject = new JObject();
foreach (var pair in Presentations)
{
jObject[pair.Key.AsString()] = new JArray(pair.Value.Select(presentation => presentation.Value));
}
return jObject;
}
}

public static class DcqlVpTokenFun
{
public static DcqlVpToken FromPresentationMaps(IEnumerable<PresentationMap> maps)
{
var dict = maps
.GroupBy(map => CredentialQueryId.Create(map.Identifier).UnwrapOrThrow())
.ToDictionary(
map => map.Key,
map => map
.Select(presentationMap => new Presentation(presentationMap.Presentation))
.ToList()
);

return new DcqlVpToken(dict);
}
}

// Can be either Mdoc DeviceResponse or SD-JWT Presentation Format; we are currently lacking a strong type
// for this
public record Presentation(string Value);
8 changes: 8 additions & 0 deletions src/WalletFramework.Oid4Vc/Oid4Vp/DcApi/DcApiConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace WalletFramework.Oid4Vc.Oid4Vp.DcApi;

public static class DcApiConstants
{
public const string SignedProtocol = "openid4vp-v1-signed";

public const string UnsignedProtocol = "openid4vp-v1-unsigned";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Newtonsoft.Json.Linq;
using WalletFramework.Core.Functional;
using WalletFramework.Core.Functional.Errors;
using WalletFramework.Core.Json;
using WalletFramework.Core.Json.Errors;
using static WalletFramework.Core.Functional.ValidationFun;

namespace WalletFramework.Oid4Vc.Oid4Vp.DcApi.Models;

/// <summary>
/// Represents a batch of DC-API requests.
/// </summary>
public record DcApiRequestBatch
{
/// <summary>
/// Gets the requests. Contains an array of DC-API request items.
/// </summary>
public DcApiRequestItem[] Requests { get; }

private DcApiRequestBatch(DcApiRequestItem[] requests)
{
Requests = requests;
}

private static DcApiRequestBatch Create(DcApiRequestItem[] requests) => new(requests);

public static Validation<DcApiRequestBatch> From(string requestBatchJson)
{
if (string.IsNullOrWhiteSpace(requestBatchJson))
{
return new StringIsNullOrWhitespaceError<DcApiRequestBatch>();
}

JObject jObject;
try
{
jObject = JObject.Parse(requestBatchJson);
}
catch (Exception e)
{
return new InvalidJsonError(requestBatchJson, e).ToInvalid<DcApiRequestBatch>();
}

return From(jObject);
}

public static Validation<DcApiRequestBatch> From(JObject requestBatchJson)
{
var requestsValidation =
from jToken in requestBatchJson.GetByKey("requests")
from jArray in jToken.ToJArray()
from items in jArray.TraverseAll(token =>
{
return
from jObject in token.ToJObject()
from item in DcApiRequestItem.ValidDcApiRequestItem(jObject)
select item;
})
select items.ToArray();

return Valid(Create).Apply(requestsValidation);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using LanguageExt;

namespace WalletFramework.Oid4Vc.Oid4Vp.DcApi.Models;

/// <summary>
/// Functions for DcApiRequestBatch.
/// </summary>
public static class DcApiRequestBatchFun
{
/// <summary>
/// Gets the first request in the batch with the specified protocol.
/// </summary>
/// <param name="batch">The DC-API request batch.</param>
/// <returns>The first request item with the specified protocol, or None if not found.</returns>
public static Option<DcApiRequestItem> GetFirstVpRequest(this DcApiRequestBatch batch)
{
var firstRequest = batch.Requests.FirstOrDefault(request => request.Protocol.Contains("openid4vp"));
return firstRequest ?? Option<DcApiRequestItem>.None;
}
}
98 changes: 98 additions & 0 deletions src/WalletFramework.Oid4Vc/Oid4Vp/DcApi/Models/DcApiRequestItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using LanguageExt;
using Newtonsoft.Json.Linq;
using WalletFramework.Core.Functional;
using WalletFramework.Core.Functional.Errors;
using WalletFramework.Core.Json;
using WalletFramework.Oid4Vc.Oid4Vp.Errors;
using WalletFramework.Oid4Vc.Oid4Vp.Models;
using static WalletFramework.Core.Functional.ValidationFun;

namespace WalletFramework.Oid4Vc.Oid4Vp.DcApi.Models;

/// <summary>
/// Represents a single request item in the DC-API batch request.
/// </summary>
public record DcApiRequestItem
{
/// <summary>
/// Gets the data. Contains the actual DcApiRequest.
/// </summary>
public AuthorizationRequest Data { get; }

/// <summary>
/// Gets the protocol. Specifies the protocol used for this request.
/// </summary>
public string Protocol { get; }

public Option<Origin> Origin { get; init; } = Option<Origin>.None;

private DcApiRequestItem(
AuthorizationRequest data,
string protocol)
{
Data = data;
Protocol = protocol;
}

public static Validation<DcApiRequestItem> ValidDcApiRequestItem(JObject requestItemJson)
{
var protocolValidation = requestItemJson
.GetByKey("protocol")
.OnSuccess(token => token.ToJValue())
.OnSuccess(jValue =>
{
var value = jValue.Value?.ToString();
return string.IsNullOrWhiteSpace(value)
? new StringIsNullOrWhitespaceError<string>()
: Valid(value);
});

var dataValidation = requestItemJson
.GetByKey("data")
.OnSuccess(token => token.ToJObject())
.OnSuccess(jObject =>
{
var protocol = protocolValidation.Fallback(DcApiConstants.UnsignedProtocol);
return ProcessAuthRequest(jObject, protocol);
});

return Valid(Create)
.Apply(dataValidation)
.Apply(protocolValidation);
}

private static DcApiRequestItem Create(
AuthorizationRequest data,
string protocol) => new(data, protocol);

private static Validation<AuthorizationRequest> LiftRequest(
Validation<AuthorizationRequestCancellation, AuthorizationRequest> validation)
{
return validation.Match(
request => request,
errors =>
{
var vpErrors = errors.SelectMany(x => x.Errors);
return vpErrors.First().ToInvalid<AuthorizationRequest>();
}
);
}

private static Validation<AuthorizationRequest> ProcessAuthRequest(JObject jObject, string protocol)
{
switch (protocol)
{
case DcApiConstants.UnsignedProtocol:
var r = AuthorizationRequest.CreateAuthorizationRequest(jObject);
return LiftRequest(r);
case DcApiConstants.SignedProtocol:
var jToken = jObject.GetByKey("request").UnwrapOrThrow();
var result =
from requestObject in RequestObject.FromStr(jToken.ToString(), Option<string>.None)
select requestObject.ToAuthorizationRequest();
return LiftRequest(result);
default:
return new InvalidRequestError($"Invalid protocol: {protocol}");
}
}
}
Loading
Loading