【问题标题】:Unable make a certificate authorized API call to ING (api.sandbox.ing.com) endpoint using C# .NET Core无法使用 C# .NET Core 对 ING (api.sandbox.ing.com) 端点进行证书授权 API 调用
【发布时间】:2020-04-18 07:32:46
【问题描述】:

任务

我正在尝试按照get started 指南发出访问令牌请求。它提供了一个bash script 作为如何做到这一点的示例。另一个要求是从 Azure Function 进行此调用,因此我在 Visual Studio 2019 中创建了一个 HTTP 触发的 Azure Function 项目。

我的解决方案尝试

它由4部分组成:

  1. 加载证书
  2. 计算摘要
  3. 生成签名
  4. 向提供的 API 端点发出正确的请求

加载证书

.cer.key 文件中提供了两个证书作为密钥对。一个是验证请求,另一个是创建签名。我已经使用openssl 命令将公钥和私钥组合到.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 类作为我的有效负载,因此摘要的计算方式如下:

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"

我正在使用 HttpClientHandlertls.pfx 添加到 opensslHttpClient 并发出如下授权请求:

using (var cert = ctx.GetCertificate("tls.pfx"))
{
    // https://stackoverflow.com/questions/40014047/add-client-certificate-to-net-core-httpclient
    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 没有看到这个,所以它只是从控制台窗口复制/粘贴):

*  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 类对带有证书的请求进行身份验证?请帮忙!

更新 1:添加了由我的代码生成的 HTTP 请求和由 (get started guide) 提供的上述 bash 脚本生成的 HTTP 请求。

【问题讨论】:

标签: c# curl .net-core httpclient x509certificate2


【解决方案1】:

我想通了。

首先,是 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 集成的人。谢谢大家帮忙。

【讨论】:

    【解决方案2】:

    我认为我们正在解决同样的问题。

    但看起来他们在您的代码中要求 SSL 证书,而不是 TLS(或两者)。可能你需要:

            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3 |
                                                   SecurityProtocolType.Tls | SecurityProtocolType.Tls11;
    

    在 ING 开发环境中有 2 个单独的证书 - SSL 和 TLS

    【讨论】:

    • 我从“入门”指南中了解到,有 2 个证书:一个用于请求身份验证 (example_client_tls.cer),另一个用于生成正确的签名 (example_client_signing.cer)。正如我在问题中描述的那样,我使用这两种方法。 Ssl2 和 Ssl3 已过时且已弃用,如果使用您建议的代码行,我会收到 System.Private.CoreLib: Exception while executing function: IGNAccountInfo. System.Net.ServicePoint: The requested security protocol is not supported. 错误。
    【解决方案3】:

    能否请您发布实际发送的请求?您是否检查过 ING 开发人员门户上的入门指南中记录的差异?

    此外,您还可以在此处找到常见错误的摘要。 HTTP 签名需要注意细节。

    您将需要使用不同的密钥对进行签名和 TLS!这两个证书都需要添加到开发人员门户上的客户端配置中。除非您使用 eIDAS。这些是自动载入的。

    请发布请求。

    【讨论】:

    • 用发送的请求更新了答案。我确实比较了这两个请求,但无法确定问题所在。我按照“入门”指南中的说明使用这两个证书。目前,我正在尝试使用提供的证书使其适用于最简单的情况,而无需任何注册和证书生成。提供的 bash 脚本正在运行,所以我的解决方案也应该:)
    【解决方案4】:

    很高兴你找到了它。我正忙于记录当前文档中尚未出现的其他错误。当前代码在例如不包含请求签名时会返回此错误。

    在 developers.ing.com 上,您可以在 OAUTH 2.0 的文档选项卡中找到有关返回的错误的更多信息。

    由于我不是 ING 开放式银行支持团队的一员,但目前为 ING 工作,因此开放式银行更愿意自己处理所有支持问题。你掌握得很好。我只是想提一下,因为我觉得你没有收到我的任何回复有点奇怪。

    【讨论】:

    • 感谢您的帮助,罗布。 ING 开放银行支持团队也非常有帮助。
    【解决方案5】:

    我已经构建了一个 Postman 集合来使用 ING OAuth PSD2 API。它可用于试验所有参数以熟悉 API 的错误处理,并在 Javascript 而不是 shell 脚本中查看其客户端要求。

    您可以自行决定使用它。我的项目根本没有与 ING 的官方链接!

    我希望对希望开始使用 ING PSD2 OAuth 2.0 API 的其他开发人员有所帮助。

    可以在这里找到它:https://github.com/robcordes/ING-PSD2-OAUTH2.O-API-Test-Client

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-13
      • 2020-09-25
      • 2019-10-30
      • 2012-02-25
      • 2015-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多