无法使用 C# .NET Core 对 ING (api.sandbox.ing.com) 端点进行证书授权 API 调用
Unable make a certificate authorized API call to ING (api.sandbox.ing.com) endpoint using C# .NET Core
任务
我试图以这个 get started guide to make an access token request. It provides a bash script 为例来说明如何做到这一点。另一个要求是从 Azure Function 进行此调用,因此我在 Visual Studio 2019 年创建了一个 HTTP 触发的 Azure Function 项目。
我的解决方案尝试
它由4个部分组成:
- 正在加载证书
- 计算文摘
- 正在生成签名
- 向提供的 API 端点发出正确的请求
正在加载证书
.cer
和 .key
文件中提供了两个证书作为密钥对。一个是验证请求,另一个是创建签名。我已经使用 openssl
命令将 public 和私钥组合到一个 .pfx
容器中,如下所示:
openssl pkcs12 -export -in my.cer -inkey my.key -out mycert.pfx
并使用以下方式加载它们:
public static X509Certificate2 GetCertificate(this ExecutionContext ctx, string certFileName)
{
return new X509Certificate2(Path.GetFullPath(Path.Combine(ctx.FunctionAppDirectory, @$"Certs\{certFileName}")), "mypass");
}
计算文摘
我正在使用 FormUrlEncodedContent
class 作为我的有效载荷,因此摘要的计算方式如下:
public static string ComputeSHA256HashAsBase64String(this string stringToHash)
{
using (var hash = SHA256.Create())
{
Byte[] result = hash.ComputeHash(Encoding.UTF8.GetBytes(stringToHash));
return Convert.ToBase64String(result);
}
}
public static async Task<string> DigestValue(this FormUrlEncodedContent content)
{
var payload = await content.ReadAsStringAsync();
return "SHA-256=" + payload.ComputeSHA256HashAsBase64String();
}
正在生成签名
var currentDate = DateTime.Now.ToUniversalTime().ToString("r");
var signingString =
@$"(request-target): {IgnAccountApi.HttpMethodStr} {IgnAccountApi.AccessTokenPath}
date: {currentDate}
digest: {digest}";
var signature = cert.SignData(signingString);
public static string SignData(this X509Certificate2 cert, string stringToSign)
{
using (var hash = SHA256.Create())
{
var dataToSign = Encoding.UTF8.GetBytes(stringToSign);
Byte[] hashToSign = hash.ComputeHash(dataToSign);
var signedData = cert.GetRSAPrivateKey().SignData(hashToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signedData);
}
}
提出请求
我正在尝试重现的 curl
请求 (from the mentioned bash script):
curl -v -i -X POST "${httpHost}${reqPath}" \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H "Digest: ${digest}" \
-H "Date: ${reqDate}" \
-H "authorization: Signature keyId=\"$keyId\",algorithm=\"rsa-sha256\",headers=\"(request-target) date digest\",signature=\"$signature\"" \
-d "${payload}" \
--cert "${certPath}example_client_tls.cer" \
--key "${certPath}example_client_tls.key"
我正在使用 HttpClientHandler
将我使用 openssl
创建的 tls.pfx
添加到 HttpClient
并像这样发出授权请求:
using (var cert = ctx.GetCertificate("tls.pfx"))
{
//
var _clientHandler = new HttpClientHandler();
_clientHandler.ClientCertificates.Add(cert);
_clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
_clientHandler.SslProtocols = SslProtocols.Tls12;
var dataToSend = new Dictionary<string, string>
{
{ "grant_type","client_credentials" },
};
using (var content = new FormUrlEncodedContent(dataToSend))
using (var _client = new HttpClient(_clientHandler))
{
var request = new HttpRequestMessage(HttpMethod.Post, $"{HttpHost}{AccessTokenPath}");
request.Content = content;
request.AddHeaders(ctx.GetCertificate("sign.pfx"), await content.DigestValue());
using (HttpResponseMessage response = await _client.SendAsync(request))
{
// TODO: process the response
}
}
}
为了不遗漏任何内容,这里是 AddHeaders
扩展方法:
public static void AddHeaders(this HttpRequestMessage request, X509Certificate2 cert, string digest)
{
var currentDate = DateTime.Now.ToUniversalTime().ToString("r");
var signingString =
@$"(request-target): {IgnAccountApi.HttpMethodStr} {IgnAccountApi.AccessTokenPath}
date: {currentDate}
digest: {digest}";
var signature = cert.SignData(signingString);
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Digest", digest);
request.Headers.Add("Date", currentDate);
request.Headers.Add("authorization", $"Signature keyId=\"{IgnAccountApi.ClienId}\",algorithm=\"rsa-sha256\",headers=\"(request-target) date digest\",signature=\"{signature}\"");
}
以上代码生成如下请求:
POST https://api.sandbox.ing.com/oauth2/token HTTP/1.1
Host: api.sandbox.ing.com
Accept: application/json
Digest: SHA-256=w0mymuL8aCrbJmmabs1pytZhon8lQucTuJMUtuKr+uw=
Date: Tue, 21 Apr 2020 09:02:56 GMT
Authorization: Signature keyId="e77d776b-90af-4684-bebc-521e5b2614dd",algorithm="rsa-sha256",headers="(request-target) date digest",signature="BaQgDXTsGBcZfZa+9oeaQhkv7bQwbMw92h4Dwp/EexJnjScScqVMYFwRSskkN1fYfu/1lDE+/K27qEJD9cq8i68C6u29I9wsUWlRtAiHu10d/hzTcZkfWLpoSKSo4mg016I//K/4scdnwf0fcsNgDOXYaoe9/KscltreXn6UQuYuwP98uZDTP3j/V7k34R5VIMPaUm1MSvE3H5opGNbLqpBjK8IenKUHjF0B9aqCzGB30eA7Y+fL025wRko6mGY2f+u4w3mi1RJzTb72Cw3SPejaa5s65sYIAus14g975RPBI4B7A2o/vsZ39Np1yJNvCW1tbZaTGAF4IJUfXQashw=="
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
grant_type=client_credentials
我得到的回复相当令人失望:
HTTP/1.1 400
Date: Sat, 18 Apr 2020 06:17:20 GMT
Content-Type: application/json
Content-Length: 98
Connection: keep-alive
X-Frame-Options: deny
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31622400; includeSubDomains
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Content-Security-Policy: default-src 'self'; prefetch-src 'none'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests; block-all-mixed-content; connect-src 'self'; style-src 'self' 'unsafe-inline' data:; img-src https: data:; script-src 'self' data: 'unsafe-inline' 'unsafe-eval'
{
"message" : "InputValidation failed: Field 'X-ENV-SSL_CLIENT_CERTIFICATE' was not provided."
}
为了比较 curl 发出的请求,这里是使用提供的 bash 脚本生成的请求(不幸的是 fiddler 没有看到这个,所以它只是 copy/paste 来自控制台 window):
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fffe7479580)
> POST /oauth2/token HTTP/2
> Host: api.sandbox.ing.com
> User-Agent: curl/7.58.0
> Accept: application/json
> Content-Type: application/x-www-form-urlencoded
> Digest: SHA-256=w0mymuL8aCrbJmmabs1pytZhon8lQucTuJMUtuKr+uw=
> Date: Fri, 17 Apr 2020 14:56:12 GMT
> authorization: Signature keyId="e77d776b-90af-4684-bebc-521e5b2614dd",algorithm="rsa-sha256",headers="(request-target) date digest",signature="KynniiPdkPoVWu5YqXl+YMXQlmYa5C7Wp5ih+/HQtiSlgcNXZlHiRoKmhrwvaDo/0qXHGexJxxrrcgWYnJz3DusgUpr30Xg6DbcD8VlN6kYvk3DUez2q2+CmYo93ulVdz9W7+V0xQdEr6jLHZc/TLcpMUQly11ADiiBPUMhGd4VfN4XTwCcsoq/mPQ5tqVM+3hln5r85jDzf2sFjt/Is4do8WCwZjfdoNBdgtS3k73oBH1kS/foRxzS5ke6fxFaN2Al1o9dkDMhrOV7TQl0wOCIbmkgBRdQXA4Rq83HO3t3R65x+RVHafRfRT6o8bNTIqgy51aKVzqhdBvUQC6Dwkg=="
> Content-Length: 29
问题
我做错了什么?是否可以使用 HttpClient
class 对带有证书的请求进行身份验证?请帮忙!
更新 1:添加了由我的代码生成的 HTTP 请求和由 (get started guide).
提供的上述 bash 脚本生成的 HTTP 请求
我想我们正在解决同样的问题。
但在您的代码中,他们似乎要求的是 SSL 证书,而不是 TLS(或两者)。可能您需要:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3 |
SecurityProtocolType.Tls | SecurityProtocolType.Tls11;
在 ING 开发环境中有 2 个单独的证书 - SSL 和 TLS
你能post请求实际发送吗?您是否检查了 ING 开发人员门户上入门指南中记录的内容之间的差异?
此外,您还可以在其中找到常见错误的摘要。 HTTP Signing 需要注意细节。
您将需要使用不同的密钥对进行签名和 TLS!这两个证书都需要添加到您使用 eIDAS 的开发人员 portal.Unless 的客户端配置中。这些是自动加入的。
请post提出请求。
我明白了。
首先,是 Fiddler 以某种方式破坏了请求并从中删除了证书。
当我让开 Fiddler 时,我发现我生成签名的方法是错误的,而且我生成它的数据也不正确。
正确的签名生成方法如下:
public static string SignData(this X509Certificate2 cert, string stringToSign)
{
var dataToSign = Encoding.UTF8.GetBytes(stringToSign);
var signedData = cert.GetRSAPrivateKey().SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var base64Signature = Convert.ToBase64String(signedData);
return base64Signature;
}
正确的签名数据如下:
var stringToSign = $"(request-target): {httpMethod} {httpPath}\ndate: {currentDate}\ndigest: {digest}";
希望这对尝试与 ING API 集成的人有所帮助。谢谢大家的帮助。
很高兴你找到了。我正忙于记录当前文档中尚未出现的其他错误。例如,当不包括请求签名时,当前代码会 return 此错误。
在 developers.ing.com 上,您将在 OAUTH 2.0 的文档选项卡中找到有关错误的更多信息 returned。
由于我不是 ING 开放银行支持团队的成员,但目前在 ING 工作,因此 Open banking 更喜欢自己处理所有支持问题。你得到了很好的照顾。只是我想提一下,因为我觉得你没有收到我的任何回音有点奇怪。
我已经构建了一个 Postman 集合来使用 ING OAuth PSD2 API。它可用于试验所有参数以熟悉 API 的错误处理并在 Javascript 而不是 shell 脚本中查看其客户端要求。
请自行决定使用。我的项目根本没有 ING 的官方 link!
我希望对其他想要开始使用 ING PSD2 OAuth 2.0 的开发人员有所帮助 API。
可以在这里找到:https://github.com/robcordes/ING-PSD2-OAUTH2.O-API-Test-Client
任务
我试图以这个 get started guide to make an access token request. It provides a bash script 为例来说明如何做到这一点。另一个要求是从 Azure Function 进行此调用,因此我在 Visual Studio 2019 年创建了一个 HTTP 触发的 Azure Function 项目。
我的解决方案尝试
它由4个部分组成:
- 正在加载证书
- 计算文摘
- 正在生成签名
- 向提供的 API 端点发出正确的请求
正在加载证书
.cer
和 .key
文件中提供了两个证书作为密钥对。一个是验证请求,另一个是创建签名。我已经使用 openssl
命令将 public 和私钥组合到一个 .pfx
容器中,如下所示:
openssl pkcs12 -export -in my.cer -inkey my.key -out mycert.pfx
并使用以下方式加载它们:
public static X509Certificate2 GetCertificate(this ExecutionContext ctx, string certFileName)
{
return new X509Certificate2(Path.GetFullPath(Path.Combine(ctx.FunctionAppDirectory, @$"Certs\{certFileName}")), "mypass");
}
计算文摘
我正在使用 FormUrlEncodedContent
class 作为我的有效载荷,因此摘要的计算方式如下:
public static string ComputeSHA256HashAsBase64String(this string stringToHash)
{
using (var hash = SHA256.Create())
{
Byte[] result = hash.ComputeHash(Encoding.UTF8.GetBytes(stringToHash));
return Convert.ToBase64String(result);
}
}
public static async Task<string> DigestValue(this FormUrlEncodedContent content)
{
var payload = await content.ReadAsStringAsync();
return "SHA-256=" + payload.ComputeSHA256HashAsBase64String();
}
正在生成签名
var currentDate = DateTime.Now.ToUniversalTime().ToString("r");
var signingString =
@$"(request-target): {IgnAccountApi.HttpMethodStr} {IgnAccountApi.AccessTokenPath}
date: {currentDate}
digest: {digest}";
var signature = cert.SignData(signingString);
public static string SignData(this X509Certificate2 cert, string stringToSign)
{
using (var hash = SHA256.Create())
{
var dataToSign = Encoding.UTF8.GetBytes(stringToSign);
Byte[] hashToSign = hash.ComputeHash(dataToSign);
var signedData = cert.GetRSAPrivateKey().SignData(hashToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signedData);
}
}
提出请求
我正在尝试重现的 curl
请求 (from the mentioned bash script):
curl -v -i -X POST "${httpHost}${reqPath}" \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H "Digest: ${digest}" \
-H "Date: ${reqDate}" \
-H "authorization: Signature keyId=\"$keyId\",algorithm=\"rsa-sha256\",headers=\"(request-target) date digest\",signature=\"$signature\"" \
-d "${payload}" \
--cert "${certPath}example_client_tls.cer" \
--key "${certPath}example_client_tls.key"
我正在使用 HttpClientHandler
将我使用 openssl
创建的 tls.pfx
添加到 HttpClient
并像这样发出授权请求:
using (var cert = ctx.GetCertificate("tls.pfx"))
{
//
var _clientHandler = new HttpClientHandler();
_clientHandler.ClientCertificates.Add(cert);
_clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
_clientHandler.SslProtocols = SslProtocols.Tls12;
var dataToSend = new Dictionary<string, string>
{
{ "grant_type","client_credentials" },
};
using (var content = new FormUrlEncodedContent(dataToSend))
using (var _client = new HttpClient(_clientHandler))
{
var request = new HttpRequestMessage(HttpMethod.Post, $"{HttpHost}{AccessTokenPath}");
request.Content = content;
request.AddHeaders(ctx.GetCertificate("sign.pfx"), await content.DigestValue());
using (HttpResponseMessage response = await _client.SendAsync(request))
{
// TODO: process the response
}
}
}
为了不遗漏任何内容,这里是 AddHeaders
扩展方法:
public static void AddHeaders(this HttpRequestMessage request, X509Certificate2 cert, string digest)
{
var currentDate = DateTime.Now.ToUniversalTime().ToString("r");
var signingString =
@$"(request-target): {IgnAccountApi.HttpMethodStr} {IgnAccountApi.AccessTokenPath}
date: {currentDate}
digest: {digest}";
var signature = cert.SignData(signingString);
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Digest", digest);
request.Headers.Add("Date", currentDate);
request.Headers.Add("authorization", $"Signature keyId=\"{IgnAccountApi.ClienId}\",algorithm=\"rsa-sha256\",headers=\"(request-target) date digest\",signature=\"{signature}\"");
}
以上代码生成如下请求:
POST https://api.sandbox.ing.com/oauth2/token HTTP/1.1
Host: api.sandbox.ing.com
Accept: application/json
Digest: SHA-256=w0mymuL8aCrbJmmabs1pytZhon8lQucTuJMUtuKr+uw=
Date: Tue, 21 Apr 2020 09:02:56 GMT
Authorization: Signature keyId="e77d776b-90af-4684-bebc-521e5b2614dd",algorithm="rsa-sha256",headers="(request-target) date digest",signature="BaQgDXTsGBcZfZa+9oeaQhkv7bQwbMw92h4Dwp/EexJnjScScqVMYFwRSskkN1fYfu/1lDE+/K27qEJD9cq8i68C6u29I9wsUWlRtAiHu10d/hzTcZkfWLpoSKSo4mg016I//K/4scdnwf0fcsNgDOXYaoe9/KscltreXn6UQuYuwP98uZDTP3j/V7k34R5VIMPaUm1MSvE3H5opGNbLqpBjK8IenKUHjF0B9aqCzGB30eA7Y+fL025wRko6mGY2f+u4w3mi1RJzTb72Cw3SPejaa5s65sYIAus14g975RPBI4B7A2o/vsZ39Np1yJNvCW1tbZaTGAF4IJUfXQashw=="
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
grant_type=client_credentials
我得到的回复相当令人失望:
HTTP/1.1 400
Date: Sat, 18 Apr 2020 06:17:20 GMT
Content-Type: application/json
Content-Length: 98
Connection: keep-alive
X-Frame-Options: deny
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31622400; includeSubDomains
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Content-Security-Policy: default-src 'self'; prefetch-src 'none'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests; block-all-mixed-content; connect-src 'self'; style-src 'self' 'unsafe-inline' data:; img-src https: data:; script-src 'self' data: 'unsafe-inline' 'unsafe-eval'
{
"message" : "InputValidation failed: Field 'X-ENV-SSL_CLIENT_CERTIFICATE' was not provided."
}
为了比较 curl 发出的请求,这里是使用提供的 bash 脚本生成的请求(不幸的是 fiddler 没有看到这个,所以它只是 copy/paste 来自控制台 window):
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fffe7479580)
> POST /oauth2/token HTTP/2
> Host: api.sandbox.ing.com
> User-Agent: curl/7.58.0
> Accept: application/json
> Content-Type: application/x-www-form-urlencoded
> Digest: SHA-256=w0mymuL8aCrbJmmabs1pytZhon8lQucTuJMUtuKr+uw=
> Date: Fri, 17 Apr 2020 14:56:12 GMT
> authorization: Signature keyId="e77d776b-90af-4684-bebc-521e5b2614dd",algorithm="rsa-sha256",headers="(request-target) date digest",signature="KynniiPdkPoVWu5YqXl+YMXQlmYa5C7Wp5ih+/HQtiSlgcNXZlHiRoKmhrwvaDo/0qXHGexJxxrrcgWYnJz3DusgUpr30Xg6DbcD8VlN6kYvk3DUez2q2+CmYo93ulVdz9W7+V0xQdEr6jLHZc/TLcpMUQly11ADiiBPUMhGd4VfN4XTwCcsoq/mPQ5tqVM+3hln5r85jDzf2sFjt/Is4do8WCwZjfdoNBdgtS3k73oBH1kS/foRxzS5ke6fxFaN2Al1o9dkDMhrOV7TQl0wOCIbmkgBRdQXA4Rq83HO3t3R65x+RVHafRfRT6o8bNTIqgy51aKVzqhdBvUQC6Dwkg=="
> Content-Length: 29
问题
我做错了什么?是否可以使用 HttpClient
class 对带有证书的请求进行身份验证?请帮忙!
更新 1:添加了由我的代码生成的 HTTP 请求和由 (get started guide).
提供的上述 bash 脚本生成的 HTTP 请求我想我们正在解决同样的问题。
但在您的代码中,他们似乎要求的是 SSL 证书,而不是 TLS(或两者)。可能您需要:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3 |
SecurityProtocolType.Tls | SecurityProtocolType.Tls11;
在 ING 开发环境中有 2 个单独的证书 - SSL 和 TLS
你能post请求实际发送吗?您是否检查了 ING 开发人员门户上入门指南中记录的内容之间的差异?
此外,您还可以在其中找到常见错误的摘要。 HTTP Signing 需要注意细节。
您将需要使用不同的密钥对进行签名和 TLS!这两个证书都需要添加到您使用 eIDAS 的开发人员 portal.Unless 的客户端配置中。这些是自动加入的。
请post提出请求。
我明白了。
首先,是 Fiddler 以某种方式破坏了请求并从中删除了证书。
当我让开 Fiddler 时,我发现我生成签名的方法是错误的,而且我生成它的数据也不正确。
正确的签名生成方法如下:
public static string SignData(this X509Certificate2 cert, string stringToSign)
{
var dataToSign = Encoding.UTF8.GetBytes(stringToSign);
var signedData = cert.GetRSAPrivateKey().SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var base64Signature = Convert.ToBase64String(signedData);
return base64Signature;
}
正确的签名数据如下:
var stringToSign = $"(request-target): {httpMethod} {httpPath}\ndate: {currentDate}\ndigest: {digest}";
希望这对尝试与 ING API 集成的人有所帮助。谢谢大家的帮助。
很高兴你找到了。我正忙于记录当前文档中尚未出现的其他错误。例如,当不包括请求签名时,当前代码会 return 此错误。
在 developers.ing.com 上,您将在 OAUTH 2.0 的文档选项卡中找到有关错误的更多信息 returned。
由于我不是 ING 开放银行支持团队的成员,但目前在 ING 工作,因此 Open banking 更喜欢自己处理所有支持问题。你得到了很好的照顾。只是我想提一下,因为我觉得你没有收到我的任何回音有点奇怪。
我已经构建了一个 Postman 集合来使用 ING OAuth PSD2 API。它可用于试验所有参数以熟悉 API 的错误处理并在 Javascript 而不是 shell 脚本中查看其客户端要求。
请自行决定使用。我的项目根本没有 ING 的官方 link!
我希望对其他想要开始使用 ING PSD2 OAuth 2.0 的开发人员有所帮助 API。
可以在这里找到:https://github.com/robcordes/ING-PSD2-OAUTH2.O-API-Test-Client