SSL handshake failure when connecting to Apple Pay gateway

I'm trying to connect to the Apple gateway to get through the merchant validation process but it is not working on .NET. For starters, when I call the payment gateway using CURL it works fine, I used the following command:

curl --data '{"merchantIdentifier":"merchant.########", "domainName":"########.net", "displayName":"merchant.########"}' -H "Content-Type: application/json" -X POST --cert ./apple-pay-cert.pem https://apple-pay-gateway.apple.com/paymentservices/paymentSession

and it successfully returns the merchant session back. However, I'm trying to do it on .NET and I've simplified the code here:

var x509 = new X509Certificate2(System.IO.File.ReadAllBytes("apple-pay-cert.pem"));
            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            X509Certificate2 certificate = new X509Certificate2("apple-pay-cert.pem", "",
            X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet |
            X509KeyStorageFlags.Exportable);
            var handler = new HttpClientHandler();
            //var certificate = new X509Certificate2(fileName: "apple-pay-cert.pem", password: "");

            handler.ClientCertificates.Add(x509);

            using (var httpClient = new HttpClient())
            {
                using (var request = new HttpRequestMessage(new HttpMethod("POST"), "https://apple-pay-gateway.apple.com/paymentservices/paymentSession"))
                {
                    var response = await httpClient.SendAsync(request);
                }
            }

I removed the body/data part to keep it simple. I have tried every variation of this code, reading the certificate from file then passing it to handler, or directly assigning the certificate, or changing the security protocol to accept only TLS 1.2 channels.

The error I get is almost always the same:

An unhandled exception has occurred while executing the request.
      System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
       ---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
       ---> Interop+AppleCrypto+SslException: handshake failure
         --- End of inner exception stack trace ---
         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)
         --- End of inner exception stack trace ---
         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)

EDIT: I was able to make it work. Kinda. I used a different http handler called SocketsHttpHandler like this:


var cert = new X509Certificate2("apple-pay-cert.pem", "",
            X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet |
            X509KeyStorageFlags.Exportable);
            var sslOptions = new SslClientAuthenticationOptions();
            var shHandler = new SocketsHttpHandler
            {
                MaxConnectionsPerServer = 100,
                AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
                PooledConnectionLifetime = TimeSpan.FromMinutes(1),
                ConnectTimeout = TimeSpan.FromSeconds(10),
                PooledConnectionIdleTimeout = TimeSpan.FromSeconds(10),
                ResponseDrainTimeout = TimeSpan.FromSeconds(10),
            };

            if (cert != null)
            {
                shHandler.SslOptions = new SslClientAuthenticationOptions()
                {
                    ClientCertificates = new X509CertificateCollection(),
                };

                shHandler.SslOptions.ClientCertificates.Add(cert);
                shHandler.SslOptions.LocalCertificateSelectionCallback = (object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers) => cert;
            }

            using (var httpClient = new HttpClient(shHandler))
            {
                using (var request = new HttpRequestMessage(new HttpMethod("POST"), "https://apple-pay-gateway.apple.com/paymentservices/paymentSession"))
                {
                   
                    var response = await httpClient.SendAsync(request);
                    Console.WriteLine(response.Content.ReadAsStringAsync().Result);
                }
            }

Add in the body of the request and I was able to return a usable merchant session. The only problem is that it prompts me to enter my mac's login credentials every time I run the code to access the certificate from the keychain, how can I prevent that?

There are a few things going on here. First, because you are able to get a successful session in CURL, this can only point to two things going wrong that may be very similar; first is that there is something wrong with the way your server side APIs are processing what looks like your stacked PEM, /apple-pay-cert.pem. Second, that your server side APIs are not not able to process your stacked PEM, and this file needs to be broken up in a different format, with your certificate and key from this identity in different files.

Now, I do not claim to be an expert on your server side APIs, but make sure that X509Certificate2 can take a PEM wrapped identity. If not, they you may need to remove the PEM wrapper and extract the bytes of your certificate and key and load them in a different API, the API you are currently using. A lot of APIs take the raw bytes of the cryptographic asset and do not want the PEM wrapper. Having said that, this is only food for thought, I do not claim to be an expert of .NET APIs.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

In my case, I enabled the Load User Profile in the advanced settings of the IIS app pool. It works! It seems that some Windows security features are not enabled without the user profile.

Hi,

"apple-pay-cert.pem" where can i get this certificate, Is it same as "Merchant certificate" or "Payment certificate", which are downloaded form apple developers account?

i am using "merchanecertificate.p12" for send a request to apple servers, But i am getting error as below.

InnerException:System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel.

SSL handshake failure when connecting to Apple Pay gateway
 
 
Q