6

How do I export certificate in a format that will be acceptable by PHP SSL context option cafile?

My code below uses openssl_x509_export to export a certificate chain of stackoverflow.com to a file. The code is based on How to get SSL certificate info with CURL in PHP?

$context = stream_context_create(["ssl" => ["capture_peer_cert_chain" => true]]);
$h = stream_socket_client(
         "ssl://stackoverflow.com:443", $errno, $errstr, 30, STREAM_CLIENT_CONNECT,
         $context);
$params = stream_context_get_params($h);

$ca = null;

foreach ($params["options"]["ssl"]["peer_certificate_chain"] as $cert)
{
    openssl_x509_export($cert, $output);
    $ca .= $output;
}

file_put_contents("cafile", $ca);

(I'm aware that it's not necessary to export whole chain.)


Then, I try with the following code snippet to connect to the same host using the exported file for verifying the certificate. But the code fails with:

PHP Warning: file_get_contents(): SSL operation failed with code 1. OpenSSL Error messages:

error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed in /path/test.php on line 22

$context = stream_context_create(["ssl" => ["cafile" => "cafile"]]);

file_get_contents("https://stackoverflow.com/", false, $context);

What is wrong with the format of the file?

Interestingly wget is happy with the exported certificate, the following succeeds:

wget --ca-certificate=cafile https://sourceforge.net/

The export is:

-----BEGIN CERTIFICATE-----
MIIHJDCCBgygAwIBAgISA76ZNTywMU3d6zogRjuFkuFnMA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDA2MDgxNjI0MDBaFw0y
MDA5MDYxNjI0MDBaMB4xHDAaBgNVBAMMEyouc3RhY2tleGNoYW5nZS5jb20wggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCp1azQ2CMWUtXrIZDFim7tu2xF
NFYSLt+lIjyZJ1MeCJkljjaiNB02guKeFRSuPsMk2J3Xz8OmOoEfbvVQejMcaw6i
BVP1V+QhF+JhgyOORQkZQV8JJ58IcOJ1PwokyMCAMOWvdxHzaj73jXsxC+n/+MU7
yuq+Y2LuZE1hQYtftYia/e0QtnPDUlpnjUSd7YEDvAzNnKY3+r1smFOHChDjAxMs
W+lJfKLscNKspncihsx16JhZcGZbXiPPOd90B/zA5xNFOCUolldtMAdxFHKoKYpN
mgWrQwVvDB7VCGAQ3CiNGRvdcMa/TpB4SVsZc07C6TvD35dr0wLoZN4WqKfrAgMB
AAGjggQuMIIEKjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG
CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFM73q8X//0OoBswZWBlG
kTPmW5HPMB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUF
BwEBBGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNy
eXB0Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNy
eXB0Lm9yZy8wggHkBgNVHREEggHbMIIB14IPKi5hc2t1YnVudHUuY29tghIqLmJs
b2dvdmVyZmxvdy5jb22CEioubWF0aG92ZXJmbG93Lm5ldIIYKi5tZXRhLnN0YWNr
ZXhjaGFuZ2UuY29tghgqLm1ldGEuc3RhY2tvdmVyZmxvdy5jb22CESouc2VydmVy
ZmF1bHQuY29tgg0qLnNzdGF0aWMubmV0ghMqLnN0YWNrZXhjaGFuZ2UuY29tghMq
LnN0YWNrb3ZlcmZsb3cuY29tghUqLnN0YWNrb3ZlcmZsb3cuZW1haWyCDyouc3Vw
ZXJ1c2VyLmNvbYINYXNrdWJ1bnR1LmNvbYIQYmxvZ292ZXJmbG93LmNvbYIQbWF0
aG92ZXJmbG93Lm5ldIIUb3BlbmlkLnN0YWNrYXV0aC5jb22CD3NlcnZlcmZhdWx0
LmNvbYILc3N0YXRpYy5uZXSCDXN0YWNrYXBwcy5jb22CDXN0YWNrYXV0aC5jb22C
EXN0YWNrZXhjaGFuZ2UuY29tghJzdGFja292ZXJmbG93LmJsb2eCEXN0YWNrb3Zl
cmZsb3cuY29tghNzdGFja292ZXJmbG93LmVtYWlsghFzdGFja3NuaXBwZXRzLm5l
dIINc3VwZXJ1c2VyLmNvbTBMBgNVHSAERTBDMAgGBmeBDAECATA3BgsrBgEEAYLf
EwEBATAoMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCC
AQIGCisGAQQB1nkCBAIEgfMEgfAA7gB1AF6nc/nfVsDntTZIfdBJ4DJ6kZoMhKES
EoQYdZaBcUVYAAABcpT136gAAAQDAEYwRAIhAP68FqsHN7WAMY3gio0PiAX1o0ID
d9G35wA7pgn/OetjAh9f9JkgTdN4U58fsIVcZAPstOIoKOblbzAof32fixlAAHUA
B7dcG+V9aP/xsMYdIxXHuuZXfFeUt2ruvGE6GmnTohwAAAFylPXfyQAABAMARjBE
AiArXFv1bOQ2wDYjJvVjSm13AnPCQmbkRRgw6YEaSDhP7gIgJQPAbeBEhu44aJQe
Ih4j33U59OY4Ux2f02dWw3oqhNIwDQYJKoZIhvcNAQELBQADggEBAHyW5+Ss+0vt
jKPFOL9atcI3WZYlwp7Djg3dXByWFBgVEzhrRGGJKEZyK9N178cG9zGXXXbxrxGY
L81pxfZNGiPBYueQ191bTKRS0/9NyCfQhSjFG9SUy7BHDC8OTX5OxEZfnG4wglCf
vPcBkdZ49b0TsTiDrRfCltQvqkeY+/6CfJnayFV/RuZcfRV2bcnu2lLE3da57mA0
TEc9hSW/AefP5ad4noqhSsPISPPbo8S1U4JAFnn1M0KnoEKr5/awHenQeqKgPSKr
DmtDcoy8Tf0RpqSMUwIKqBZEERthO6FC506E64wcuYww3EqA7Um7MM1MkQ8hosFN
WK9ZLI8ylk4=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----

Some background: I'm using certificate bundle from https://curl.se/docs/caextract.html.

For some reason PHP is not able to validate certificates issued by "USERTrust RSA Certification Authority", although the root certificate seems to be present in the bundle. For example a certificate sectigo.com itself. This is a part of my attempt to find the root cause or a workaround.

2
  • @MartinPrikryl I was just verifying that the problem is actually the root cert not being in the file :) and it turns out I was right. Therefore, either we should find a way to instruct stream_socket_client to get the root cert (probably no way) or to use a different library that is capable of downloading the whole chain. Note that wget has no problem since it uses the system's root certs. Commented Jul 3, 2020 at 12:24
  • @SherifelKhatib My original question was actually based on wrong assumptions. I believed my CA file missed some root certificates, because I did not realize that the last certificate in the chain from capture_peer_cert_chain is not a root certificate (what @on8tom explained in the accepted answer). So I wanted to update the CA file. But the real problem seems to be (yet to be verified, but highly probable) that my version of PHP+OpenSSL (7.1.33+1.0.2t) does not support some root certificates. Commented Jul 3, 2020 at 12:33

3 Answers 3

4
+300

stackoverflow only provides the server cert, and the intermediate ca cert. not the root ca cert.

openssl cafile only works if the full chain (to a self signed root ca) can by verified,

so you either have to trust the ROOT CA your self by downloading it, or use some kind of pki so you have all the global trusted root CAs.

on Debian GNU / Linux based machines you can install the ca-certificates package, sudo apt-get install ca-certificates

or you can use the cacert from mozilla https://curl.haxx.se/docs/caextract.html

11
  • Thanks. That (kind of) makes sense. I'm actually using cacert from curl.haxx.se. But it's from january and not up to date anymore (for example it does not include "SHA-2 Root USERTrust RSA Certification Authority"). So I'm probably stuck with updating the root certificates manually. Commented Jun 23, 2020 at 14:53
  • idd, pki is a pita, you can generate the ca-bundle with the mk-ca-bundle of curl github.com/curl/curl/blob/master/lib/mk-ca-bundle.pl which hopefully is more up to date
    – on8tom
    Commented Jun 23, 2020 at 15:12
  • I still cannot make it work with "USERTrust RSA Certification Authority". For example www.kb.cert.org need that root certificate. But PHP still fails to verify it. That root certificate is even part of curl.haxx.se pack. Manual download from support.sectigo.com/Com_KnowledgeDetailPage?Id=kA01N000000rfBO does not help either (not that I expected it, as it's the same as in the curl.haxx.se). Commented Jun 24, 2020 at 8:58
  • Do you sitll get the same error?, because echo file_get_contents("https://www.kb.cert.org/", false, stream_context_create(["ssl"=> [ "cafile" => __DIR__ . "/cacert.pem"]])); works for me.
    – on8tom
    Commented Jun 24, 2020 at 11:52
  • What is in your cacert.pem? Commented Jun 24, 2020 at 13:24
0
  1. Download CA certificate from here https://curl.haxx.se/ca/cacert.pem
  2. Execute the following code:
<?php

$context = stream_context_create([
    "ssl" => [
        "cafile" => "cacert.pem"
    ]
]);

file_get_contents("https://stackoverflow.com/", false, $context);
  1. you will see no errors

Explanations: regarding documentation cafile accept Certificate Authority file but you tried to put chain certificates

1
  • Thanks for your answer. This is basically what I've learned form the answer by @on8tom already. Though how does "Certificate Authority file" differ to the root certificate from the chain, were it included in the chain? I've understood that the problem is that the root certificate is absent in the chain. Not that the format of the file is wrong. But there may be more than that. As per my comment to the other answer, if I download a root certificate from support.sectigo.com/Com_KnowledgeDetailPage?Id=kA01N000000rfBO and use it for cafile, I still cannot connect to sectigo.com. Commented Jun 25, 2020 at 6:55
0

There might be a misunderstanding. stream_context_create() is to create a TCP server. It can handle handshakes, but to do so must have a whole keyring, formatted like below:

This is a .crt and a private .key file generated by openssl, concatenated in one file.

-----BEGIN CERTIFICATE-----
MIIDaDCCAlCgAwIB(...)
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAK(...)
-----END RSA PRIVATE KEY-----

With a key ring like this, I am able to implement ssl/https and secure wss:// websockets servers.

Not sure what you are looking to do, why file_get_contents() doesn't works without a context. Usually you don't have to import any CAcert, and if so, it belong to the browser.

This can be useful in an enterprise context, or to avoid errors with self_signed keyrings.

Thus, what's wrong with just:

echo file_get_contents("https://stackoverflow.com/");

Or

wget https://sourceforge.net/

It just works without hassle.

Please explain precisely what you are trying to achieve, got many notes from experiments around ssl and php.

To extract the public key of a given server, you have to query on the port 443:

<?php
$opt = [
  "capture_peer_cert" => true,
  "capture_peer_cert_chain" => true
];
$a = stream_context_create(["ssl"=>$opt]);
$b = stream_socket_client("tls://stackoverflow.com:443", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $a);
$cont = stream_context_get_params($b);
$key = openssl_pkey_get_public($cont["options"]["ssl"]["peer_certificate"]);
$c = openssl_pkey_get_details($key);
var_dump($c["key"]);

Output

string(451) "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqdWs0NgjFlLV6yGQxYpu
7btsRTRWEi7fpSI8mSdTHgiZJY42ojQdNoLinhUUrj7DJNid18/DpjqBH271UHoz
HGsOogVT9VfkIRfiYYMjjkUJGUFfCSefCHDidT8KJMjAgDDlr3cR82o+9417MQvp
//jFO8rqvmNi7mRNYUGLX7WImv3tELZzw1JaZ41Ene2BA7wMzZymN/q9bJhThwoQ
4wMTLFvpSXyi7HDSrKZ3IobMdeiYWXBmW14jzznfdAf8wOcTRTglKJZXbTAHcRRy
qCmKTZoFq0MFbwwe1QhgENwojRkb3XDGv06QeElbGXNOwuk7w9+Xa9MC6GTeFqin
6wIDAQAB
-----END PUBLIC KEY-----
"

Maybe this answer can help

2
  • Thanks for your response. Though it's not true that stream_context_create is for servers. Most, if not all, PHP SSL context options are relevant for TLS (HTTPS) clients too. + What I want to achieve is to generate CA file that I can use to connect to any given HTTPS server for which I do not have CA file yet (in case my CA file is outdated). Commented Jun 29, 2020 at 7:26
  • You can export all majors CAcert locally from a browser. «View certificate» You will find a big list of .crt of the last versions of all the majors CA authorities.
    – NVRM
    Commented Jun 29, 2020 at 13:54

Not the answer you're looking for? Browse other questions tagged or ask your own question.