HttpClient SSL Handshake failure self signed certificate - Swish

1.3k Views Asked by At

I will literally pull my hair out. This should not be hard to implement, but anyway I cannot get it to work. I am trying to implement the Swedish Swish API for payments. All we need is to send a payload and add a certificate to the HttpClientHandler.

I am trying to implement the PaymentRequest-method as described here: https://developer.swish.nu/api/payment-request/v2

My code is as follows, firstly a configuration class:

public class SwishApiDetails
{
    public string BaseUrl { get; set; } // Base URL for the API
    public string CallbackUrl { get; set; } // Optional callback URL
    public string ClientCertificate { get; set; } // Client certificate thumbprint
    public string RootCertificateV1 { get; set; } // First CA certificate
    public string RootCertificateV2 { get; set; } // Second CA certificate
}

Code for the actual API calling (yes I know I am not disposing the client, but that is not the issue right now (well unless it is))

public class SwishApi
{
    private readonly HttpClient _client;
    private readonly SwishApiDetails _configuration;

    public SwishApi(SwishApiDetails configuration)
    {
        _configuration = configuration;
        var handler = new HttpClientHandler
        {
            ServerCertificateCustomValidationCallback = (
                sender,
                certificate,
                chain,
                sslPolicyErrors) => true,
            ClientCertificateOptions = ClientCertificateOption.Manual
        };

        var thumbprints = new List<string>
        {
            _configuration.ClientCertificate,
            _configuration.RootCertificateV1,
            _configuration.RootCertificateV2
        };

        var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadOnly);

        foreach (var thumbprint in thumbprints)
        {
            var certificates = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
            handler.ClientCertificates.AddRange(certificates);
        }

        _client = new HttpClient(handler) {BaseAddress = new Uri(_configuration.BaseUrl)};
    }

    public async Task<(bool HasError, string Message)> MakePaymentRequest(
        int amountToPay,
        string reference,
        string phoneNumber,
        string receiverAlias,
        string messageToCustomer)
    {
        var phoneAlias = phoneNumber.Replace(" ", "").Replace("+", "");
        if (phoneAlias.StartsWith("0")) phoneAlias = phoneAlias.Substring(1, phoneAlias.Length);

        var result = await _client.SendAsync(new HttpRequestMessage
        {
            Method = HttpMethod.Put,
            RequestUri = new Uri($"{_configuration.BaseUrl}/api/v2/paymentrequests/{reference}"),
            Content = JsonContent.Create(new
            {
                amount = amountToPay,
                payerAlias = phoneAlias,
                payeeAlias = receiverAlias,
                message = messageToCustomer,
                payeePaymentReference = reference,
                currency = SwishDefaults.Currency.Sek,
                callbackUrl = _configuration.CallbackUrl
            })
        });

        return result.IsSuccessStatusCode
            ? (false, string.Empty)
            : (true, await result.Content.ReadAsStringAsync());
    }
}

To test it all I am using this code:

public class Test
{
    private readonly SwishApi _swish;

    public Test()
    {
        _swish = new SwishApi(new SwishApiDetails
        {
            BaseUrl = "https://cpc.getswish.net/swish-cpcapi",
            CallbackUrl = null,
            ClientCertificate = "F06644FAF53150D5B31716ABF121FE112A225AF1", // Local thumbprint
            RootCertificateV1 = "A8985D3A65E5E5C4B2D7D66D40C6DD2FB19C5436", // Local thumbprint
            RootCertificateV2 = "03BFF7B54C712504C5BE5A8528163C931618A3C0" // Local thumbprint
        });
    }

    [Fact]
    public async Task TestPaymentRequest()
    {
        var (hasError, _) = await _swish.MakePaymentRequest(
            1,
            Guid.NewGuid().ToString("N").ToUpper(),
            "4671111111",
            "Swish Test",
            "Testing");

        Assert.False(hasError);
    }
}

The certificates are downloaded from here: https://assets.ctfassets.net/4dca8u8ebqnn/5B6HqHPnsnGf0klDoeQSJh/1d4a6bf66a1269859cf00c01b312c601/mss_test_1.9.zip

They are public for the test environment.

The certificates are not by a real CA, they are signed by Swish themselves so it might be some issues with that. Other than that I have literally no idea whats wrong and why other people can get it to work.

Could it be OS-specific, or that the cipher-suites on my machine are busted?

If you want to try this out, go to this repo: https://github.com/lhammarstrom/swish-net

And if you want to try it out with a local certificate file then you can go to this branch: https://github.com/lhammarstrom/swish-net/tree/feature/with_cert (feature/with_cert). The client certificate is included in the files with the test project.

Also, for clarification, the error I am getting is:

System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.

System.Net.Http.HttpRequestException
The SSL connection could not be established, see inner exception.
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
   at Swish.Services.SwishApi.MakePaymentRequest(Int32 amountToPay, String reference, String phoneNumber, String receiverAlias, String messageToCustomer) in /Users/leni/Projects/Swish/Swish/Services/SwishApi.cs:line 73
   at Swish.Tests.Test.TestPaymentRequest() in /Users/leni/Projects/Swish/Swish.Tests/Tests.cs:line 30
   at Xunit.Sdk.TestInvoker`1.<>c__DisplayClass48_1.<<InvokeTestMethodAsync>b__1>d.MoveNext() in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\Runners\TestInvoker.cs:line 264
--- End of stack trace from previous location ---
   at Xunit.Sdk.ExecutionTimer.AggregateAsync(Func`1 asyncAction) in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\ExecutionTimer.cs:line 48
   at Xunit.Sdk.ExceptionAggregator.RunAsync(Func`1 code) in C:\Dev\xunit\xunit\src\xunit.core\Sdk\ExceptionAggregator.cs:line 90

System.Security.Authentication.AuthenticationException
Authentication failed, see inner exception.
   at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)

Interop+AppleCrypto+SslException
handshake failure
  Exception doesn't have a stacktrace

Edit:

I have followed this guide (https://johan.driessen.se/posts/Calling-the-Swish-Payment-API-from-Azure-AppService/). They got it to work and I dont. Furthermore this repository which claims to work on .NET 5 (https://github.com/RickardPettersson/swish-api-csharp) won't work on my machine either.

0

There are 0 best solutions below