【发布时间】:2020-04-18 07:32:46
【问题描述】:
任务
我正在尝试按照get started 指南发出访问令牌请求。它提供了一个bash script 作为如何做到这一点的示例。另一个要求是从 Azure Function 进行此调用,因此我在 Visual Studio 2019 中创建了一个 HTTP 触发的 Azure Function 项目。
我的解决方案尝试
它由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"
我正在使用 HttpClientHandler 将 tls.pfx 添加到 openssl 到 HttpClient 并发出如下授权请求:
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 请求。
【问题讨论】:
-
也许您可以从查看示例 curl 发送的请求的外观开始 (stackoverflow.com/a/3121175/5521670)。然后将其与您发送的内容进行比较(
request.ToString()应该足够了)。 -
我想通了。标题看起来不错。我猜问题出在 TLS 握手上。
-
您可以尝试的一些事情是:如果您的证书在链中,请提取正确的证书 (stackoverflow.com/a/9260696/5521670)。使用适当的标志导入
.pfx(提示 4:paulstovell.com/blog/x509certificate2、stackoverflow.com/a/3826390/5521670)。 -
感谢您的建议。没有链,只有一对密钥。我尝试了不同的标志,但结果仍然相同。
标签: c# curl .net-core httpclient x509certificate2