Skip to content

Commit

Permalink
implement X509SanDNS for OID4VP
Browse files Browse the repository at this point in the history
Signed-off-by: Kevin <kevin.dinh@lissi.id>
  • Loading branch information
Dindexx committed Feb 8, 2024
1 parent 832adb0 commit 7cc2a31
Show file tree
Hide file tree
Showing 13 changed files with 715 additions and 435 deletions.
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Extension methods for <see cref="JwtSecurityToken" />.
/// </summary>
public static class JwtSecurityTokenExtensions
{
/// <summary>
/// Validates the signature of the JWT token using the provided public key.
/// </summary>
/// <param name="token">The JWT token to validate.</param>
/// <param name="publicKey">The public key to use for validation.</param>
/// <returns>True if the signature is valid, otherwise false.</returns>
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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Extension methods for <see cref="X509Certificate" />.
/// </summary>
public static class X509CertificateExtensions
{
/// <summary>
/// Validates the trust chain of the certificate.
/// </summary>
/// <param name="trustChain">The trust chain to validate.</param>
/// <returns>True if the trust chain is valid, otherwise false.</returns>
public static bool IsTrustChainValid(this List<X509Certificate> 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;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
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;

namespace Hyperledger.Aries.Features.OpenId4Vc.Vp.Models
{
Expand All @@ -12,156 +10,129 @@ namespace Hyperledger.Aries.Features.OpenId4Vc.Vp.Models
/// </summary>
public class AuthorizationRequest
{
/// <summary>
/// Gets or sets the response type. Determines what the Authorization Response should contain.
/// </summary>
[JsonProperty("response_type")]
public string ResponseType { get; set; } = null!;
private const string DirectPost = "direct_post";

/// <summary>
/// Gets or sets the client id. The Identifier of the Verifier
/// </summary>
[JsonProperty("client_id")]
public string ClientId { get; set; } = null!;
private const string VpToken = "vp_token";

/// <summary>
/// Gets or sets the client id scheme.
/// Gets the client id scheme.
/// </summary>
[JsonProperty("client_id_scheme")]
public string? ClientIdScheme { get; set; }
[JsonProperty("client_id_scheme")]
public ClientIdScheme ClientIdScheme { get; }

/// <summary>
/// Gets or sets the redirect uri.
/// Gets the presentation definition. Contains the claims that the Verifier wants to receive.
/// </summary>
[JsonProperty("redirect_uri")]
public string? RedirectUri { get; set; }

/// <summary>
/// Gets or sets the client metadata. Contains the Verifier metadata
/// </summary>
[JsonProperty("client_metadata")]
public string? ClientMetadata { get; set; }
[JsonProperty("presentation_definition")]
public PresentationDefinition PresentationDefinition { get; }

/// <summary>
/// Gets or sets the client metadata uri. Can be used to retrieve the verifier metadata.
/// Gets the client id. The Identifier of the Verifier.
/// </summary>
[JsonProperty("client_metadata_uri")]
public string? ClientMetadataUri { get; set; }
[JsonProperty("client_id")]
public string ClientId { get; }

/// <summary>
/// The scope of the request.
/// Gets the nonce. Random string for session binding.
/// </summary>
[JsonProperty("scope")]
public string? Scope { get; set; }
[JsonProperty("nonce")]
public string Nonce { get; }

/// <summary>
/// Gets or sets the nonce. Random string for session binding.
/// Gets the response mode. Determines how to send the Authorization Response.
/// </summary>
[JsonProperty("nonce")]
public string Nonce { get; set; } = null!;
/// <returns>Will always return "direct_post" due to the HAIP conformance.</returns>
[JsonProperty("response_mode")]
public string ResponseMode => DirectPost;

/// <summary>
/// Gets or sets the response mode. Determines how to send the Authorization Response.
/// Gets the response type. Determines what the Authorization Response should contain.
/// </summary>
[JsonProperty("response_mode")]
public string? ResponseMode { get; set; }
/// <returns>Will always return "vp_token" due to the HAIP conformance.</returns>
[JsonProperty("response_type")]
public string ResponseType => VpToken;

/// <summary>
/// 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.
/// </summary>
[JsonProperty("response_uri")]
public string ResponseUri { get; set; } = null!;
public string ResponseUri { get; }

/// <summary>
/// Gets or sets the state.
/// Gets the client metadata. Contains the Verifier metadata.
/// </summary>
[JsonProperty("state")]
public string? State { get; set; }
[JsonProperty("client_metadata")]
public string? ClientMetadata { get; }

/// <summary>
/// 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.
/// </summary>
[JsonIgnore]
public PresentationDefinition PresentationDefinition { get; set; } = null!;
[JsonProperty("client_metadata_uri")]
public string? ClientMetadataUri { get; }

/// <summary>
/// Parses an Authorization Request from a Jwt.
/// The scope of the request.
/// </summary>
/// <param name="jwt"></param>
/// <returns>The AuthorizationRequest</returns>
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<PresentationDefinition>(token.Payload["presentation_definition"].ToString());
var authorizationRequest = JsonConvert.DeserializeObject<AuthorizationRequest>(token.Payload.SerializeToJson());

if (!(authorizationRequest != null && presentationDefinition != null))
return null;

authorizationRequest.PresentationDefinition = presentationDefinition;

return authorizationRequest;
}
[JsonProperty("scope")]
public string? Scope { get; }

/// <summary>
/// Parses an Authorization Request from a Uri.
/// Gets the state.
/// </summary>
/// <param name="uri"></param>
/// <returns>The AuthorizationRequest</returns>
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<PresentationDefinition>(query["presentation_definition"]);
var authorizationRequest = JsonConvert.DeserializeObject<AuthorizationRequest>(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;
}
}

/// <summary>
/// Extension methods for the <see cref="AuthorizationRequest"/> class.
/// </summary>
public static class AuthorizationRequestExtension
{
/// <summary>
/// Checks if the Authorization Request is HAIP conform.
/// Creates a new instance of the <see cref="AuthorizationRequest" /> class.
/// </summary>
/// <param name="authorizationRequest"></param>
/// <returns>Returns bool indicating whether the AuthorizationRequest is haip conform</returns>
public static bool IsHaipConform(this AuthorizationRequest authorizationRequest)
/// <param name="authorizationRequestJson">The json representation of the authorization request.</param>
/// <returns>A new instance of the <see cref="AuthorizationRequest" /> class.</returns>
/// <exception cref="InvalidOperationException">Thrown when the request does not match the HAIP.</exception>
public static AuthorizationRequest CreateAuthorizationRequest(string authorizationRequestJson)
=> CreateAuthorizationRequest(JObject.Parse(authorizationRequestJson));

private static AuthorizationRequest CreateAuthorizationRequest(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"];

if (responseType == VpToken
&& responseMode == DirectPost
&& !string.IsNullOrEmpty(responseUri)
&& redirectUri is null)
{
return authorizationRequestJson.ToObject<AuthorizationRequest>()
?? throw new InvalidOperationException("Could not parse the Authorization Request");
}

throw new InvalidOperationException(
"Invalid Authorization Request. The request does not match the HAIP"
);
}
}
}
Loading

0 comments on commit 7cc2a31

Please sign in to comment.