Skip to content

Commit d1706f9

Browse files
Create a wallet to pass the EBSI conformance tests
1 parent 2ebe5e8 commit d1706f9

5 files changed

Lines changed: 241 additions & 184 deletions

File tree

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
using Microsoft.IdentityModel.JsonWebTokens;
4+
using Microsoft.IdentityModel.Tokens;
5+
using SimpleIdServer.CredentialIssuer.Api.CredentialIssuer;
6+
using SimpleIdServer.Did.Crypto;
7+
using System.Security.Cryptography;
8+
using System.Text;
9+
using System.Text.Json;
10+
using System.Text.Json.Nodes;
11+
using System.Web;
12+
13+
namespace SimpleIdServer.CredentialIssuer.Console;
14+
15+
public class EsbiWallet
16+
{
17+
private const string url = "https://api-conformance.ebsi.eu/conformance/v3/issuer-mock/.well-known/openid-credential-issuer";
18+
private const string publicKey = "z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbpMAoXtZtunruYnM4gCV65AKAUX2AwEReRhEaf3BRQNJArZPwQdmf9ENZcF8VT13a58WsHeVjJtvAKKPYEibaEfdUxvU7sgxEUTJpjEkq6BJKrRV1JQ1CqhYvGbmJ1WyoUQ";
19+
private const string did = $"did:key:{publicKey}";
20+
private const string refDid = $"{did}#{publicKey}";
21+
22+
public static async Task RegisterEsbiWalletForConformance()
23+
{
24+
using (var httpClient = new HttpClient())
25+
{
26+
var (challenge, verifier) = PkceGenerate();
27+
var openidCredentialIssuer = GetOpenidCredentialIssuer(httpClient).Result;
28+
var configuration = GetAuthorizationEndpoint(httpClient, openidCredentialIssuer.AuthorizationServer).Result;
29+
var parameters = ExecuteAuthorizationRequest(httpClient, configuration.authorizationEndpoint, challenge, openidCredentialIssuer.CredentialIssuer).Result;
30+
var redirectUri = parameters["redirect_uri"];
31+
var nonce = parameters["nonce"];
32+
var state = parameters["state"];
33+
var postAuthResult = ExecutePostAuthorizationRequest(httpClient, redirectUri, openidCredentialIssuer.CredentialIssuer, nonce, state).Result;
34+
var tokenResult = GetToken(httpClient, configuration.tokenEndpoint, postAuthResult["code"], verifier).Result;
35+
GetCredential(httpClient, openidCredentialIssuer.CredentialEndpoint, openidCredentialIssuer.CredentialIssuer, tokenResult.cNonce, tokenResult.accessToken).Wait();
36+
}
37+
}
38+
39+
private static async Task<ESBICredentialIssuerResult> GetOpenidCredentialIssuer(HttpClient httpClient)
40+
{
41+
var url = "https://api-conformance.ebsi.eu/conformance/v3/issuer-mock/.well-known/openid-credential-issuer";
42+
var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
43+
var httpResult = await httpClient.SendAsync(requestMessage);
44+
var json = await httpResult.Content.ReadAsStringAsync();
45+
var openidCredentialIssuer = JsonSerializer.Deserialize<ESBICredentialIssuerResult>(json);
46+
return openidCredentialIssuer;
47+
}
48+
49+
private static async Task<(string authorizationEndpoint, string issuer, string tokenEndpoint)> GetAuthorizationEndpoint(HttpClient httpClient, string authUrl)
50+
{
51+
var requestMessage = new HttpRequestMessage(HttpMethod.Get, $"{authUrl}/.well-known/openid-configuration");
52+
var httpResult = await httpClient.SendAsync(requestMessage);
53+
var json = await httpResult.Content.ReadAsStringAsync();
54+
var jsonObj = JsonObject.Parse(json);
55+
return (jsonObj["authorization_endpoint"].ToString(), jsonObj["issuer"].ToString(), jsonObj["token_endpoint"].ToString());
56+
}
57+
58+
private static async Task<Dictionary<string, string>> ExecuteAuthorizationRequest(HttpClient httpClient, string url, string challenge, string aud)
59+
{
60+
var uriBuilder = new UriBuilder(url);
61+
var authorizationDetails = new JsonArray
62+
{
63+
new JsonObject
64+
{
65+
{ "type", "openid_credential" },
66+
{ "format", "jwt_vc" },
67+
{ "types", new JsonArray
68+
{
69+
"VerifiableCredential",
70+
"VerifiableAttestation",
71+
"CTIssueQualificationCredential"
72+
} },
73+
{ "locations", new JsonArray
74+
{
75+
aud
76+
} }
77+
}
78+
};
79+
var clientMetadata = new JsonObject
80+
{
81+
{ "response_types_supported",
82+
new JsonArray
83+
{
84+
"vp_token", "id_token"
85+
}
86+
},
87+
{ "authorization_endpoint", "openid://"}
88+
};
89+
var dic = new Dictionary<string, string>
90+
{
91+
{ "response_type", "code" },
92+
{ "scope", "openid" },
93+
// { "issuer_state", "issuer-state" },
94+
{ "state", "client-state" },
95+
{ "client_id", did },
96+
{ "authorization_details", HttpUtility.UrlEncode(authorizationDetails.ToJsonString()) },
97+
{ "redirect_uri", "openid://" },
98+
{ "nonce", "nonce" },
99+
{ "code_challenge", challenge },
100+
{ "code_challenge_method", "S256" },
101+
{ "client_metadata", HttpUtility.UrlEncode(clientMetadata.ToJsonString()) }
102+
};
103+
uriBuilder.Query = string.Join("&", dic.Select(kvp => $"{kvp.Key}={kvp.Value}"));
104+
var requestMessage = new HttpRequestMessage
105+
{
106+
RequestUri = uriBuilder.Uri
107+
};
108+
var httpResult = await httpClient.SendAsync(requestMessage);
109+
var result = httpResult.Headers.Location.AbsoluteUri;
110+
uriBuilder = new UriBuilder(result);
111+
var queryParameters = uriBuilder.Query.Trim('?').Split('&').Select(s => s.Split('=')).ToDictionary(arr => arr[0], arr => arr[1]);
112+
return queryParameters;
113+
}
114+
115+
private static async Task<Dictionary<string, string>> ExecutePostAuthorizationRequest(HttpClient httpClient, string url, string aud, string nonce, string state)
116+
{
117+
var serializedPrivateKey = System.IO.File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "privatekey.json"));
118+
var signatureKey = SignatureKeySerializer.Deserialize(serializedPrivateKey);
119+
var signingCredentials = signatureKey.BuildSigningCredentials(refDid);
120+
var handler = new JsonWebTokenHandler();
121+
var securityTokenDescriptor = new SecurityTokenDescriptor
122+
{
123+
IssuedAt = DateTime.UtcNow,
124+
SigningCredentials = signingCredentials,
125+
Audience = aud
126+
};
127+
var claims = new Dictionary<string, object>
128+
{
129+
{ "nonce", nonce },
130+
{ "iss", did },
131+
{ "sub", did }
132+
};
133+
securityTokenDescriptor.Claims = claims;
134+
var token = handler.CreateToken(securityTokenDescriptor);
135+
var requestMessage = new HttpRequestMessage
136+
{
137+
RequestUri = new Uri(HttpUtility.UrlDecode(url)),
138+
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
139+
{
140+
new KeyValuePair<string, string>("id_token", token),
141+
new KeyValuePair<string, string>("state", state)
142+
}),
143+
Method = HttpMethod.Post
144+
};
145+
var httpResult = await httpClient.SendAsync(requestMessage);
146+
var result = httpResult.Headers.Location.AbsoluteUri;
147+
var uriBuilder = new UriBuilder(result);
148+
var queryParameters = uriBuilder.Query.Trim('?').Split('&').Select(s => s.Split('=')).ToDictionary(arr => arr[0], arr => arr[1]);
149+
return queryParameters;
150+
}
151+
152+
private static async Task GetCredential(HttpClient httpClient, string credentialEndpoint, string aud, string nonce, string accessToken)
153+
{
154+
var serializedPrivateKey = System.IO.File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "privatekey.json"));
155+
var signatureKey = SignatureKeySerializer.Deserialize(serializedPrivateKey);
156+
var signingCredentials = signatureKey.BuildSigningCredentials(refDid);
157+
var handler = new JsonWebTokenHandler();
158+
var securityTokenDescriptor = new SecurityTokenDescriptor
159+
{
160+
IssuedAt = DateTime.UtcNow,
161+
SigningCredentials = signingCredentials,
162+
Audience = aud,
163+
TokenType = "openid4vci-proof+jwt"
164+
};
165+
var claims = new Dictionary<string, object>
166+
{
167+
{ "iss", did },
168+
{ "nonce", nonce }
169+
};
170+
securityTokenDescriptor.Claims = claims;
171+
var proofJwt = handler.CreateToken(securityTokenDescriptor);
172+
var proofRequest = new JsonObject();
173+
proofRequest.Add("proof_type", "jwt");
174+
proofRequest.Add("jwt", proofJwt);
175+
var request = new JsonObject
176+
{
177+
{ "types", new JsonArray
178+
{
179+
"VerifiableCredential",
180+
"VerifiableAttestation",
181+
"CTIssueQualificationCredential"
182+
} },
183+
{ "format", "jwt_vc" }
184+
};
185+
request.Add("proof", proofRequest);
186+
var requestMessage = new HttpRequestMessage
187+
{
188+
RequestUri = new Uri(HttpUtility.UrlDecode(credentialEndpoint)),
189+
Content = new StringContent(request.ToJsonString(), Encoding.UTF8, "application/json"),
190+
Method = HttpMethod.Post
191+
};
192+
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
193+
await httpClient.SendAsync(requestMessage);
194+
}
195+
196+
private static async Task<(string accessToken, string idToken, string cNonce)> GetToken(HttpClient httpClient, string url, string authorizationCode, string codeVerifier)
197+
{
198+
var requestMessage = new HttpRequestMessage
199+
{
200+
RequestUri = new Uri(url),
201+
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
202+
{
203+
new KeyValuePair<string, string>("grant_type", "authorization_code"),
204+
new KeyValuePair<string, string>("client_id", did),
205+
new KeyValuePair<string, string>("code", authorizationCode),
206+
new KeyValuePair<string, string>("code_verifier", codeVerifier)
207+
}),
208+
Method = HttpMethod.Post
209+
};
210+
var httpResult = await httpClient.SendAsync(requestMessage);
211+
var content = await httpResult.Content.ReadAsStringAsync();
212+
var jObj = JsonObject.Parse(content);
213+
return (jObj["access_token"].ToString(), jObj["id_token"].ToString(), jObj["c_nonce"].ToString());
214+
}
215+
216+
private static (string codeChallenge, string verifier) PkceGenerate(int size = 32)
217+
{
218+
using var rng = RandomNumberGenerator.Create();
219+
var randomBytes = new byte[size];
220+
rng.GetBytes(randomBytes);
221+
var verifier = Base64UrlEncode(randomBytes);
222+
223+
var buffer = Encoding.UTF8.GetBytes(verifier);
224+
var hash = SHA256.Create().ComputeHash(buffer);
225+
var challenge = Base64UrlEncode(hash);
226+
227+
return (challenge, verifier);
228+
}
229+
private static string Base64UrlEncode(byte[] data) =>
230+
Convert.ToBase64String(data)
231+
.Replace("+", "-")
232+
.Replace("/", "_")
233+
.TrimEnd('=');
234+
}

0 commit comments

Comments
 (0)