Невозможно выполнить авторизованный API-вызов сертификата для конечной точки ING (api.sandbox.ing.com), используя C#. NET Core - PullRequest
2 голосов
/ 18 апреля 2020

Задача

Я пытаюсь следовать этому руководству start , чтобы запросить токен доступа. В качестве примера того, как это сделать, приводится сценарий bash . Другим требованием является выполнение этого вызова из Azure Function, поэтому я создал запущенный HTTP-проект Azure Function в Visual Studio 2019.

Моя попытка решения

Он состоит из 4 частей:

  1. Загрузка сертификатов
  2. Компьютерный дайджест
  3. Создание подписи
  4. Создание правильного запроса к предоставленной конечной точке API

Загрузка сертификатов

В файлах ключей .cer и .key предусмотрено два сертификата в виде пар ключей. Один для аутентификации запроса, другой для создания подписи. Я объединил publi c и закрытые ключи в контейнер .pfx с помощью команды openssl следующим образом:

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");
}

Computing Дайджест

Я использую класс 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, который я пытаюсь воспроизвести ( из упомянутого 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 этого не видит, поэтому он просто копирует / вставляет из окна консоли):

* 1 070 *

Вопросы

Что я делаю не так? Можно ли аутентифицировать запрос с помощью сертификатов, используя класс HttpClient? Пожалуйста, помогите!

ОБНОВЛЕНИЕ 1: добавлен HTTP-запрос, сгенерированный моим кодом, и HTTP-запрос, сгенерированный вышеупомянутым bash скриптом, предоставленным ( руководство по началу работы ).

Ответы [ 4 ]

1 голос
/ 26 апреля 2020

Я понял это.

Прежде всего, именно 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}";

Надеюсь, это поможет кому-то, пытающемуся интегрироваться с API ING. Спасибо всем за помощь.

0 голосов
/ 27 апреля 2020

хорошо, что вы его нашли. Я занят документированием дополнительных ошибок, которых еще нет в текущей документации. Текущий код возвращает эту ошибку, если, например, подпись запроса не включена.

На developer.ing.com вы найдете на вкладке документации OAUTH 2.0 дополнительную информацию о возвращенных ошибках.

Поскольку я не являюсь частью группы поддержки открытого банковского обслуживания ING, но в настоящее время работаю для ING, Open banking предпочитает самостоятельно решать все вопросы поддержки. Вы в хороших руках. Просто я хотел упомянуть об этом, потому что мне показалось странным, что ты ничего не получил от меня.

0 голосов
/ 21 апреля 2020

можете ли вы опубликовать запрос на самом деле отправить? Проверяли ли вы различия между теми, которые описаны в Руководстве по началу работы на портале для разработчиков ING?

Кроме того, там вы найдете сводку часто допущенных ошибок. Подписание HTTP требует внимания к деталям.

Вам потребуется использовать другую пару ключей для подписи и для TLS! Оба сертификата необходимо добавить в конфигурацию клиента на портале разработчика. Если вы не используете eIDAS. Они автоматически загружаются.

Просьба опубликовать сделанный запрос.

0 голосов
/ 20 апреля 2020

Я думаю, что мы работаем над той же проблемой.

Но похоже, что в вашем коде они запрашивают сертификат SSL, а не TLS (или оба). Может быть, вам нужно:

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

В среде ING dev есть 2 отдельных сертификата - SSL и TLS

...