TLS client: In state cipher received SERVER ALERT: Fatal - Unknown CA for Apple Pay Payment Session

Hi all,
I’m stuck with a problem and would really appreciate any feedback or ideas you might have.

I followed the official documentation Apple Pay on the Web and finally got the Payment Processing Certificate (.cer) and the .p12 certificate. Then I extracted the certificate and the key from .p12 cert into 2 separate files.

The problem

I’m trying to start Payment Session via Httposon.post request like

   opts = [
       {:certfile, "priv/cert/apple_pay.cert.pem"},
       {:keyfile, "priv/cert/apple_pay.key.pem"},
       {:versions, [:"tlsv1.2"]},
     ]
    with %{body: response} <- HTTPoison.post("#{validation_url}/paymentSession", body,
                                      [{"Content-Type", "application/json"}],
                                      ssl: opts),
      {:ok, result} <- Jason.decode(response) do
        IO.inspect(result)
    else
      _ -> false
    end

Error

[info] TLS client: In state cipher received SERVER ALERT: Fatal - Unknown CA

{:error, %HTTPoison.Error{id: nil, reason: {:tls_alert, 'unknown ca'}}}

The server is saying it can’t accept your client certificate because it cannot build a trust chain to a trusted root CA. I’m guessing the p12 file contains not just an end certificate, but also one or more intermediate CAs. The server is expecting you to send the full chain, but in your current configuration only the end certificate is sent.

The challenge here is that Erlang’s :ssl application uses the cacerts / cacertfile option both as the local trust store (the root CA certificates it checks the server certificate against) and as a pool of intermediate CAs that may be sent with the client certificate. What’s more, HTTPoison (or rather Hackney) will set cacerts to its CA trust store, but only if you didn’t specify any ssl options of your own. Since you want to set a client certificate, you need to pass in a bunch of extra options to enable server certificate verification, or you’ll be susceptible to MitM attacks. See [here]https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/ssl and here for details.

The simplest way to get this working is to find out what is the root CA you need to talk to Apple, and put it in a file together with any intermediate CAs from the p12 file. Then add verify: :verify_peer, cacertfile: <path-to-your-new-file>, ... to the ssl options.

If you don’t want to pin the connection to one root CA, you could load the intermediate certificates into memory in DER format, and then pass verify: :verify_peer, cacertfile: [int_ca1_der, int_ca2_der | :certifi.cacert().

5 Likes

Thanks for your reply.
I extracted the .p12 certificate via openssl

openssl pkcs12 -in key.p12 -out apple_pay.key.pem -nocerts -nodes
openssl pkcs12 -in key.p12 -out apple_pay.cert.pem -clcerts -nokeys

and the apple_pay.cert.pem has only one field of
-----BEGIN CERTIFICATE-----

-----END CERTIFICATE-----

I added {:verify, :verify_peer} but still have that problem.
Perhaps the problem is not only in Erlang’s :ssl application. I also made a request on server via culr

curl -v --data '{"merchantIdentifier":"merchant.com.test", "domainName":"test.com", "displayName":"displayName"}' \
--cert-type P12 --cert /priv/cert/test.p12:password \
--cacert /etc/ssl/certs/cabundle.pem \
-X POST https://apple-pay-gateway-nc-pod4.apple.com/paymentservices/startSession

And got

Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 17.171.78.199...
* TCP_NODELAY set
* Connected to apple-pay-gateway-nc-pod4.apple.com (17.171.78.199) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/cabundle.pem
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS handshake, CERT verify (15):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS alert, Server hello (2):
* error:14094418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca
* stopped the pause stream!
* Closing connection 0
curl: (35) error:14094418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca

I cannot understand which certificates are not valid.

What do you get when you run openssl pkcs12 -in key.p12 -out apple_pay.cert.pem -nokeys (without the -clcerts option)? You’ll probably get more than one certificate in apple_pay.cert.pem, and if you try your curl command again with the resulting file, you might have more luck.

If that works, then for Erlang’s :ssl you’re going to have to split the certificates into the end certificate (for use with the certfile: option; use openssl’s -clcerts for that) and the CA certificates (to combine with the CA trust store, as described above; use openssl’s -cacerts).

Unfortunately, this did not help me.
When I run openssl pkcs12 -in key.p12 -out apple_pay.cert.pem -nokeys I get:

Bag Attributes
    friendlyName: Apple Pay Payment Processing:merchant.com.test
    localKeyID: AA AA AA AA AA
subject=UID = merchant.com.test, CN = Apple Pay Payment Processing:merchant.com.test, OU = AA, O = test.com OU, C = EE

issuer=CN = Apple Worldwide Developer Relations CA - G2, OU = Apple Certification Authority, O = Apple Inc., C = US

-----BEGIN CERTIFICATE-----

-----END CERTIFICATE-----
Bag Attributes
    friendlyName: Test
    localKeyID: AA AA AA AA AA
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
.....
-----END PRIVATE KEY-----

I also found a list of Apple CA certificates. Maybe I need to add some of them to my server?

Maybe Apple does not include the intermediate CAs in the p12 files. To find out which CAs are accepted by the server, run something like this:

openssl s_client -connect host.domain:443 -servername host.domain

Then look for “Acceptable client certificate CA names” in the output. There are two options:

  1. If there is a CA certificate with CN “Apple Worldwide Developer Relations CA - G2” (that’s the issuer of your certificate) listed here, then the issue is not one of missing intermediate CAs

  2. If that CN is not listed, then you’re going to have to find the CA certificate(s) that complete(s) the chain from your client certificate to (one of) the acceptable CA names

I run this openssl s_client -connect testoplaty.ria.com:443 -servername testoplaty.ria.com. It is my test server.
I got Go Daddy Secure Certificate Authority - G2.
The server is now available and could you check if I was wrong.

Maybe a problem with the proxy? I use nginx to redirect request to the test server and certificates may not be set.

I actually meant the Apple server where you use your client certificate.

Oh, I understand.
Now I run openssl s_client -connect apple-pay-gateway-nc-pod5.apple.com:443 -servername apple-pay-gateway-nc-pod5.apple.com

Got

depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root G2
verify return:1
depth=1 C = US, O = "DigiCert, Inc.", OU = www.digicert.com, CN = DigiCert Global CA-3 G2
verify return:1
depth=0 businessCategory = Private Organization, jurisdictionC = US, jurisdictionST = California, serialNumber = C0806592, C = US, ST = California, L = Cupertino, O = Apple Inc., OU = GNCS Traffic Management, CN = apple-pay-gateway.apple.com
verify return:1
140711956099520:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../ssl/record/rec_layer_s3.c:1528:SSL alert number 40
---
Certificate chain
 0 s:businessCategory = Private Organization, jurisdictionC = US, jurisdictionST = California, serialNumber = C0806592, C = US, ST = California, L = Cupertino, O = Apple Inc., OU = GNCS Traffic Management, CN = apple-pay-gateway.apple.com
   i:C = US, O = "DigiCert, Inc.", OU = www.digicert.com, CN = DigiCert Global CA-3 G2
 1 s:C = US, O = "DigiCert, Inc.", OU = www.digicert.com, CN = DigiCert Global CA-3 G2
   i:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root G2
---
Server certificate
-----BEGIN CERTIFICATE-----
MIINSDCCDDCgAwIBAgIQA8lppyXTVrDcMOGlTTKakDANBgkqhkiG9w0BAQsFADBj
MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xGTAXBgNVBAsT
EHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBDQS0z
IEcyMB4XDTIwMDIyMDAwMDAwMFoXDTIxMDIxOTEyMDAwMFowgfcxHTAbBgNVBA8M
FFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYBBAGCNzwCAQMTAlVTMRswGQYL
KwYBBAGCNzwCAQITCkNhbGlmb3JuaWExETAPBgNVBAUTCEMwODA2NTkyMQswCQYD
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJQ3VwZXJ0aW5v
MRMwEQYDVQQKEwpBcHBsZSBJbmMuMSAwHgYDVQQLExdHTkNTIFRyYWZmaWMgTWFu
YWdlbWVudDEkMCIGA1UEAxMbYXBwbGUtcGF5LWdhdGV3YXkuYXBwbGUuY29tMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomWXrH2PbEjmPI0/eW3o1XjL
pLxXQLFCNW1+NuwOD8zEH0FHsE1gNrca6vj0+aLxVFmiT3zMDf4ZYqKS0dPASKAY
qvsT6QbUXcbELO3Ka9CZgVQrFdLkVUf7qXB3iSC3DRYTque+7w1zFJF4kN0JjzKY
s/FHng9pzTdsm/3pA7ubX2TKgPab89QVDzRZp8/odZGcGlrvdWYM4Qq3wuu0eZXR
ctPtFHA09FIY+LjuaRLViMDSKG6YuuaxccQfm7YjcQJc5zH/W+tL+4VUT06L5GmN
oCLjzXbWD2R1SmdJ1TBKgbmKr0XrqAUX1c9hZfGAIyDwAekEM2TzndqYY8pRSQID
AQABo4IJYTCCCV0wHwYDVR0jBBgwFoAUm2b1dH/LXQ6vGq5QagErxYSmVMYwHQYD
VR0OBBYEFCvC1Ey5ljpQTSPo5l8VqhcT8pMeMIIFoQYDVR0RBIIFmDCCBZSCHmFw
cGxlLXBheS1nYXRld2F5LW5jLmFwcGxlLmNvbYIeYXBwbGUtcGF5LWdhdGV3YXkt
cHIuYXBwbGUuY29tgiJhcHBsZS1wYXktZ2F0ZXdheS1uYy1wb2QuYXBwbGUuY29t
giJhcHBsZS1wYXktZ2F0ZXdheS1wci1wb2QuYXBwbGUuY29tgiNhcHBsZS1wYXkt
Z2F0ZXdheS1uYy1wb2QxLmFwcGxlLmNvbYIjYXBwbGUtcGF5LWdhdGV3YXktbmMt
cG9kMi5hcHBsZS5jb22CI2FwcGxlLXBheS1nYXRld2F5LW5jLXBvZDMuYXBwbGUu
Y29tgiNhcHBsZS1wYXktZ2F0ZXdheS1uYy1wb2Q0LmFwcGxlLmNvbYIjYXBwbGUt
cGF5LWdhdGV3YXktbmMtcG9kNS5hcHBsZS5jb22CI2FwcGxlLXBheS1nYXRld2F5
LXByLXBvZDEuYXBwbGUuY29tgiNhcHBsZS1wYXktZ2F0ZXdheS1wci1wb2QyLmFw
cGxlLmNvbYIjYXBwbGUtcGF5LWdhdGV3YXktcHItcG9kMy5hcHBsZS5jb22CI2Fw
cGxlLXBheS1nYXRld2F5LXByLXBvZDQuYXBwbGUuY29tgiNhcHBsZS1wYXktZ2F0
ZXdheS1wci1wb2Q1LmFwcGxlLmNvbYIeY24tYXBwbGUtcGF5LWdhdGV3YXkuYXBw
bGUuY29tgiVjbi1hcHBsZS1wYXktZ2F0ZXdheS1zaC1wb2QuYXBwbGUuY29tgiZj
bi1hcHBsZS1wYXktZ2F0ZXdheS1zaC1wb2QxLmFwcGxlLmNvbYImY24tYXBwbGUt
cGF5LWdhdGV3YXktc2gtcG9kMi5hcHBsZS5jb22CJmNuLWFwcGxlLXBheS1nYXRl
d2F5LXNoLXBvZDMuYXBwbGUuY29tgiZjbi1hcHBsZS1wYXktZ2F0ZXdheS10ai1w
b2QxLmFwcGxlLmNvbYImY24tYXBwbGUtcGF5LWdhdGV3YXktdGotcG9kMi5hcHBs
ZS5jb22CJmNuLWFwcGxlLXBheS1nYXRld2F5LXRqLXBvZDMuYXBwbGUuY29tgiZj
bi1hcHBsZS1wYXktZ2F0ZXdheS1uYy1wb2QxLmFwcGxlLmNvbYImY24tYXBwbGUt
cGF5LWdhdGV3YXktbmMtcG9kMi5hcHBsZS5jb22CJmNuLWFwcGxlLXBheS1nYXRl
d2F5LW5jLXBvZDMuYXBwbGUuY29tgiZjbi1hcHBsZS1wYXktZ2F0ZXdheS1uYy1w
b2Q0LmFwcGxlLmNvbYImY24tYXBwbGUtcGF5LWdhdGV3YXktbmMtcG9kNS5hcHBs
ZS5jb22CJmNuLWFwcGxlLXBheS1nYXRld2F5LXByLXBvZDEuYXBwbGUuY29tgiZj
bi1hcHBsZS1wYXktZ2F0ZXdheS1wci1wb2QyLmFwcGxlLmNvbYImY24tYXBwbGUt
cGF5LWdhdGV3YXktcHItcG9kMy5hcHBsZS5jb22CJmNuLWFwcGxlLXBheS1nYXRl
d2F5LXByLXBvZDQuYXBwbGUuY29tgiNhcHBsZS1wYXktZ2F0ZXdheS1zaC1wb2Qx
LmFwcGxlLmNvbYIjYXBwbGUtcGF5LWdhdGV3YXktc2gtcG9kMi5hcHBsZS5jb22C
I2FwcGxlLXBheS1nYXRld2F5LXNoLXBvZDMuYXBwbGUuY29tgiNhcHBsZS1wYXkt
Z2F0ZXdheS10ai1wb2QxLmFwcGxlLmNvbYIjYXBwbGUtcGF5LWdhdGV3YXktdGot
cG9kMi5hcHBsZS5jb22CI2FwcGxlLXBheS1nYXRld2F5LXRqLXBvZDMuYXBwbGUu
Y29tghthcHBsZS1wYXktZ2F0ZXdheS5hcHBsZS5jb20wDgYDVR0PAQH/BAQDAgWg
MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB7BgNVHR8EdDByMDegNaAz
hjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxDQS0zRzIu
Y3JsMDegNaAzhjFodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9i
YWxDQS0zRzIuY3JsMEsGA1UdIAREMEIwNwYJYIZIAYb9bAIBMCowKAYIKwYBBQUH
AgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwBwYFZ4EMAQEwdgYIKwYB
BQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w
QAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy
dEdsb2JhbENBLTNHMi5jcnQwCQYDVR0TBAIwADCCAfgGCisGAQQB1nkCBAIEggHo
BIIB5AHiAHcA9lyUL9F3MCIUVBgIMJRWjuNNExkzv98MLyALzE7xZOMAAAFwZOdP
xwAABAMASDBGAiEApEpt8ns7d347mJuhlMK+gxo8ZSWiqhvUxcvMxlaBQmMCIQDw
ulSpUeKCH45kLYEZBvvN7gT1Bs/dK78CiT8X2uyvywB1AFzcQ5L+5qtFRLFemtRW
5hA3+9X6R9yhc5SyXub2xw7KAAABcGTnUA4AAAQDAEYwRAIgSH8mKrOQ8enCIwpt
W7q/Sv5oe/eviO51sfN0kEjOmTQCIA7N4ZTpR1IjV4xQnRt2SqxGTty2Y4qwvBIi
bN4HWqdpAHcAVhQGmi/XwuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFwZOdR
4gAABAMASDBGAiEA0EZYoE4OWh/WEpu2c67+9PHp/Mv47m+NGnO2OgFcq/gCIQDI
Lo8EWPpgaA0c1oVN1wW+LeDNGGk/OUWJBEPeNsIFNwB3AKS5CZC0GFgUh7sTosxn
cAo8NZgE+RvfuON3zQ7IDdwQAAABcGTnUvgAAAQDAEgwRgIhAJr7MrrNa6w+t2U+
qdnD6NWPSZ9pfkhFtVs16bxge8HMAiEA6TIWI5rNKQ4Kcscy5pse4YXYsB5nyuIj
kuMbRF428B0wDQYJKoZIhvcNAQELBQADggEBAFn0cJDttFzT07t1AnLICf/W9MsZ
Qaf8RPwAUD5w0mCvUYv0v3e8G2YP3fp8Fm0dSrrfvqvdPANxlBzbscthHa0BKvDN
4BbN92qeqPdJUSmFu/m9+pp0LL5tknkql5rrdDrzt5185UM/XjXbflx5g0PLiibO
74V8DUf74OsAoI8vlS7sElRp/dz02A9z0QN7l35H3bcvbaG/nyOV0NKdaLiKR501
z868HI2Im5z5oeY/woIkugZmYJyczi5j+nhUOVQBLdywMaGTf1j90ZmyBCRolbAJ
/sUHZATqDR0/c+vEWdYNaIvQmoKrrmeJW6shO7Xa8BNVaXqqf4Sf5rtgsyI=
-----END CERTIFICATE-----
subject=businessCategory = Private Organization, jurisdictionC = US, jurisdictionST = California, serialNumber = C0806592, C = US, ST = California, L = Cupertino, O = Apple Inc., OU = GNCS Traffic Management, CN = apple-pay-gateway.apple.com

issuer=C = US, O = "DigiCert, Inc.", OU = www.digicert.com, CN = DigiCert Global CA-3 G2

---
Acceptable client certificate CA names
C = US, O = Apple Inc., OU = Apple Worldwide Developer Relations, CN = Apple Worldwide Developer Relations Certification Authority
CN = Apple Corporate Root CA, OU = Certification Authority, O = Apple Inc., C = US
CN = Apple Corporate External Authentication CA 1, OU = Certification Authority, O = Apple Inc., C = US
C = US, O = Apple Inc., OU = Apple Certification Authority, CN = Apple Root CA
Client Certificate Types: RSA sign, ECDSA sign
Requested Signature Algorithms: RSA+SHA512:ECDSA+SHA512:RSA+SHA384:ECDSA+SHA384:RSA+SHA256:ECDSA+SHA256:RSA+SHA224:ECDSA+SHA224:RSA+SHA1:ECDSA+SHA1
Shared Requested Signature Algorithms: RSA+SHA512:ECDSA+SHA512:RSA+SHA384:ECDSA+SHA384:RSA+SHA256:ECDSA+SHA256:RSA+SHA224:ECDSA+SHA224:RSA+SHA1:ECDSA+SHA1
Peer signing digest: SHA512
Peer signature type: RSA
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 5565 bytes and written 475 bytes
Verification: OK
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: 8F53B3260155079216848FCA8ED618E51A9BC083A2B8CAD32918FDF2CB9592C3
    Session-ID-ctx: 
    Master-Key: BFD11717632B49D4A861B583FC9E66F0B9420B291E33BA06834E954C9909C5CE2C67C61DBACEF21DB6A27F76CA0867E0
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1588865372
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
---

So those are the four certificates the server wants to check your client certificate against. Your certificate was issued by CN = Apple Worldwide Developer Relations CA - G2, OU = Apple Certification Authority, O = Apple Inc., C = US, which is not listed.

Either you need one or more intermediate CAs that can complete the chain between your client certificate and one of these root CAs, or you are mixing up production and testing endpoints and your client certificate just isn’t designed to work with the current endpoint.

I quickly glanced over the list of intermediate CAs listed on the Apple PKI page, and I did see the issuer of your client certificate, but it does not appear to have been issued by one of the CAs accepted by the server. I can’t look into this further right now, maybe later…

I am very grateful for the explanation and hope for your help, because I have no ideas how to solve this problem.

I added four Root Certificates

and three Intermediate Certificates:

but it didn’t help.

Yeah, I couldn’t find a valid path to one of the accepted root CAs, so I’m not surprised it didn’t help.

I think it might be best to first try and establish a successful connection using an officially supported client/language. Once you have verified that the certificate is valid for a given API endpoint, then we can see what exact options you need to set to make this work with Elixir/Erlang…

What do you mean by officially supported client/language?

The initial assumption was that you had a valid certificate for the given endpoint, and the question was just which options to pass to make an Elixir client connect. But based on what we’ve seen I’m not sure that assumption is correct: I was unable to find a certificate path that would let you satisfy the server’s requirements with your client certificate.

Presumably others have successfully integrated with this API using other languages. If you could get some help from someone who as done this before and together make it work with Python, curl, JS, Swift, or whatever, using the files you were given, then I may be able to help translate the working setup into Elixir.

I finally figured out what the problem was.
I made a mistake in the beginning when creating certificates. The problem was that I did not have all the necessary additional certificates installed, namely:

Then I created new certificates and everything worked!
But now I have a new challenge. I need to decrypt the encrypted payment data as described here.
I found a sample code on JS, but I didn’t figure out how to rewrite it on Elixir.
I would be very grateful if you would show me a sample code on Elixir.