有効期限切れのルート証明書ファイルにより、SSL通信に失敗する際の対処法

PHPからSSL通信しようとしたところ、`SSL operation failed with code 40. OpenSSL Error messages: not in error state`というエラーで失敗するケースがありました。

## 環境

とある共有レンタルサーバ群の数台。
PHPも5.2.5とかなり古いものです。

## ソースコード

```php
<?php
echo file_get_contents("https://example.herokuapp.com/");
```

## 実行結果

```php
Warning: file_get_contents() [function.file-get-contents]: SSL operation failed with code 40. OpenSSL Error messages: not in error state in /****/****/ssl.php on line 2

Warning: file_get_contents() [function.file-get-contents]: Failed to enable crypto in /****/****/ssl.php on line 2

Warning: file_get_contents(https://example.herokuapp.com/) [function.file-get-contents]: failed to open stream: Bad file descriptor in /****/****/ssl.php on line 2
```

## 該当サーバでの状況確認

問題切り分けのために該当サーバのコンソールより、wgetやcurlコマンドの実行結果を確認しました。

```bash
# wgetコマンドを実行すると次のエラーが発生。
## 但し --no-check-certificate 引数を付ければ通信出来ます。
$ wget https://example.herokuapp.com/
--2015-01-07 15:52:51--  https://example.herokuapp.com/
Resolving example.herokuapp.com... 54.225.214.208
Connecting to example.herokuapp.com|54.225.214.208|:443... connected.
ERROR: The certificate of ‘example.herokuapp.com’ is not trusted.
ERROR: The certificate of ‘example.herokuapp.com’ hasn't got a known issuer.
```

```bash
# curlコマンドを実行すると次のエラーが発生。
## 但し -k 引数を付ければ通信出来ます。
$ curl https://example.herokuapp.com/
curl: (60) SSL certificate problem, verify that the CA cert is OK. Details:
error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). The default
 bundle is named curl-ca-bundle.crt; you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.
```
```bash
# opensslのバージョンは次の通りです
$ openssl
OpenSSL> version
OpenSSL 0.9.8za 5 Jun 2014
OpenSSL> quit
```

## 最新のルート証明書ファイル(CA)を入手

本来であればサーバの証明書ファイルをopensshのアップデート等で実施すべきです。
しかし共有レンタルサーバなどでそれが困難な場合は、任意のディレクトリに証明書ファイルを展開します。

それでは、次を参考にCA fileをユーザディレクトリに展開してそれを用いてSSL通信してみましょう。
ref. http://takuya-1st.hatenablog.jp/entry/20110306/1299436282

例に従って、まずはcacert.prmを分割します。

```bash
# 任意の証明書ディレクトリを作ります
$ mkdir certs
$ cd certs

# pemファイルをダウンロードします
$ wget http://curl.haxx.se/ca/cacert.pem

# ディレクトリ指定して動作するwget向けに、分割します
$ curl http://curl.haxx.se/ca/cacert.pem \
| awk 'split_after==1{n++;split_after=0}\
 /-----END CERTIFICATE-----/ {split_after=1} {print > "cert" n ".pem"}'

# 次のファイルが出来ました
$ ls certs/
cert100.pem  cert115.pem  cert12.pem   cert144.pem  cert20.pem  cert35.pem  cert4.pem   cert64.pem  cert79.pem  cert93.pem
cert101.pem  cert116.pem  cert130.pem  cert145.pem  cert21.pem  cert36.pem  cert50.pem  cert65.pem  cert7.pem   cert94.pem
cert102.pem  cert117.pem  cert131.pem  cert146.pem  cert22.pem  cert37.pem  cert51.pem  cert66.pem  cert80.pem  cert95.pem
cert103.pem  cert118.pem  cert132.pem  cert147.pem  cert23.pem  cert38.pem  cert52.pem  cert67.pem  cert81.pem  cert96.pem
cert104.pem  cert119.pem  cert133.pem  cert148.pem  cert24.pem  cert39.pem  cert53.pem  cert68.pem  cert82.pem  cert97.pem
cert105.pem  cert11.pem   cert134.pem  cert149.pem  cert25.pem  cert3.pem   cert54.pem  cert69.pem  cert83.pem  cert98.pem
cert106.pem  cert120.pem  cert135.pem  cert14.pem   cert26.pem  cert40.pem  cert55.pem  cert6.pem   cert84.pem  cert99.pem
cert107.pem  cert121.pem  cert136.pem  cert150.pem  cert27.pem  cert41.pem  cert56.pem  cert70.pem  cert85.pem  cert9.pem
cert108.pem  cert122.pem  cert137.pem  cert151.pem  cert28.pem  cert42.pem  cert57.pem  cert71.pem  cert86.pem  cert.pem
cert109.pem  cert123.pem  cert138.pem  cert152.pem  cert29.pem  cert43.pem  cert58.pem  cert72.pem  cert87.pem  
cert10.pem   cert124.pem  cert139.pem  cert15.pem   cert2.pem   cert44.pem  cert59.pem  cert73.pem  cert88.pem
cert110.pem  cert125.pem  cert13.pem   cert16.pem   cert30.pem  cert45.pem  cert5.pem   cert74.pem  cert89.pem
cert111.pem  cert126.pem  cert140.pem  cert17.pem   cert31.pem  cert46.pem  cert60.pem  cert75.pem  cert8.pem
cert112.pem  cert127.pem  cert141.pem  cert18.pem   cert32.pem  cert47.pem  cert61.pem  cert76.pem  cert90.pem
cert113.pem  cert128.pem  cert142.pem  cert19.pem   cert33.pem  cert48.pem  cert62.pem  cert77.pem  cert91.pem
cert114.pem  cert129.pem  cert143.pem  cert1.pem    cert34.pem  cert49.pem  cert63.pem  cert78.pem  cert92.pem
```

その後に新しく用意したCAファイルを参照してリクエストを発行してみましょう。
先ほど失敗していたコマンドが実行できるようになっているはずです。

```bash
# wgetは期待通り動きます
$ wget --ca-directory=/path/to/certs https://example.herokuapp.com/

# cacertを指定します。-k や --insecure オプションを使っても通信出来ます。
$ curl --cacert /path/to/certs/cacert.pem https://www.google.co.jp/
$ curl -V
curl 7.16.3 (i686-pc-linux-gnu) libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Protocols: tftp ftp telnet dict ldap http file https ftps
Features: Largefile NTLM SSL libz
```

## PHPで最新のルート証明書ファイル(CA)を使う方法

次のページを参考に試してみます。
ref. http://www.docnet.nu/tech-portal/2014/06/26/ssl-and-php-streams-part-1-you-are-doing-it-wrongtm/C0

```php
<?php
// Change this to point to your newly downloaded CA file
$caFile = '/path/to/certs/cacert.pem';

// Create a stream context to pass the relevant SSL information
$context = stream_context_create(array('ssl' => array(
    'verify_peer' => false,
    'verify_peer_name' => false,
    'allow_self_signed' => false,
    'cafile' => $caFile,
)));

echo file_get_contents("https://example.herokuapp.com/", false, $context);
```
しかし次の結果となり、cafile, capath共に上手く行きませんでした。・・・誰か教えてください ><

```php
Warning: file_get_contents() [function.file-get-contents]: SSL operation failed with code 40. OpenSSL Error messages: not in error state in /path/to/ssl.php on line 14

Warning: file_get_contents() [function.file-get-contents]: Failed to enable crypto in /path/to/ssl.php on line 14

Warning: file_get_contents(https://example.herokuapp.com/) [function.file-get-contents]: failed to open stream: Bad file descriptor in /path/to/ssl.php on line 14
```

なお、[curl](http://php.net/manual/ja/curl.examples-basic.php)が使える環境なら回避策がまだあるかもしれません。

## ルート証明書ファイルの有効期限を確認する方法

任意のルート証明書ファイルの中身を確認するには次のコマンドを利用します。

```bash
$ openssl x509 -text -noout -in 確認対象ファイル.pem
```

指定ディレクトリ以下のファイルを対象に一括で確認する方法は次の通りです。

```bash
$ find  /etc/ssl/certs/*.pem -type f -exec echo "--------------------" \; -exec echo {} \; -exec bash -c "openssl x509 -text -noout -in {} | head" \;
```

このコマンドを、問題の共有レンタルサーバで実行しました。
かなり困る状態ですね。

```bash

--------------------
/etc/ssl/certs/eng1.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 0 (0x0)
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=CA, ST=ON, L=Toronto, O=BankEngine Inc., OU=Certification Authority Division, CN=bankengine/emailAddress=ca@bankengine.com
        Validity
            Not Before: Jan  1 00:00:00 1998 GMT
            Not After : Jan 17 00:00:00 2038 GMT
        Subject: C=CA, ST=ON, L=Toronto, O=BankEngine Inc., OU=Certification Authority Division, CN=bankengine/emailAddress=ca@bankengine.com
--------------------
/etc/ssl/certs/eng2.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 0 (0x0)
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=CA, ST=ON, L=Toronto, O=CertEngine Inc., OU=Certification Authority Division, CN=certengine/emailAddress=ca@certengine.com
        Validity
            Not Before: Jan  1 00:00:00 1998 GMT
            Not After : Jan 17 00:00:00 2038 GMT
        Subject: C=CA, ST=ON, L=Toronto, O=CertEngine Inc., OU=Certification Authority Division, CN=certengine/emailAddress=ca@certengine.com
--------------------
/etc/ssl/certs/eng3.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 0 (0x0)
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=CA, ST=ON, L=Toronto, O=FortEngine Inc., OU=Certification Authority Division, CN=fortengine/emailAddress=ca@fortengine.com
        Validity
            Not Before: Jan  1 00:00:00 1998 GMT
            Not After : Jan 17 00:00:00 2038 GMT
        Subject: C=CA, ST=ON, L=Toronto, O=FortEngine Inc., OU=Certification Authority Division, CN=fortengine/emailAddress=ca@fortengine.com
--------------------
/etc/ssl/certs/eng4.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 0 (0x0)
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=CA, ST=ON, L=Toronto, O=MailEngine Inc., OU=Certification Authority Division, CN=mailengine/emailAddress=ca@mailengine.com
        Validity
            Not Before: Jan  1 00:00:00 1998 GMT
            Not After : Jan 17 00:00:00 2038 GMT
        Subject: C=CA, ST=ON, L=Toronto, O=MailEngine Inc., OU=Certification Authority Division, CN=mailengine/emailAddress=ca@mailengine.com
--------------------
/etc/ssl/certs/eng5.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 0 (0x0)
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=CA, ST=ON, L=Toronto, O=TraderEngine Inc., OU=Certification Authority Division, CN=traderengine/emailAddress=ca@traderengine.com
        Validity
            Not Before: Jan  1 00:00:00 1998 GMT
            Not After : Jan 17 00:00:00 2038 GMT
        Subject: C=CA, ST=ON, L=Toronto, O=TraderEngine Inc., OU=Certification Authority Division, CN=traderengine/emailAddress=ca@traderengine.com
--------------------
/etc/ssl/certs/Equifax-root1.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 903804111 (0x35def4cf)
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
        Validity
            Not Before: Aug 22 16:41:51 1998 GMT
            Not After : Aug 22 16:41:51 2018 GMT
        Subject: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
--------------------
/etc/ssl/certs/ICP-Brasil.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 4 (0x4)
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=BR, O=ICP-Brasil, OU=Instituto Nacional de Tecnologia da Informacao - ITI, L=Brasilia, ST=DF, CN=Autoridade Certificadora Raiz Brasileira
        Validity
            Not Before: Nov 30 12:58:00 2001 GMT
            Not After : Nov 30 23:59:00 2011 GMT
        Subject: C=BR, O=ICP-Brasil, OU=Instituto Nacional de Tecnologia da Informacao - ITI, L=Brasilia, ST=DF, CN=Autoridade Certificadora Raiz Brasileira
--------------------
/etc/ssl/certs/RegTP-5R.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 820138 (0xc83aa)
        Signature Algorithm: ripemd160WithRSA
        Issuer: C=DE, O=Regulierungsbeh\xC8orde f\xC8ur Telekommunikation und Post/0.2.262.1.10.7.20=1, CN=5R-CA 1:PN
        Validity
            Not Before: Mar 22 08:55:51 2000 GMT
            Not After : Mar 22 08:55:51 2005 GMT
        Subject: C=DE, O=Regulierungsbeh\xC8orde f\xC8ur Telekommunikation und Post/0.2.262.1.10.7.20=1, CN=5R-CA 1:PN
--------------------
/etc/ssl/certs/RegTP-6R.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 3330445 (0x32d18d)
        Signature Algorithm: ripemd160WithRSA
        Issuer: C=DE, O=Regulierungsbeh\xC8orde f\xC8ur Telekommunikation und Post/0.2.262.1.10.7.20=1, CN=6R-Ca 1:PN
        Validity
            Not Before: Feb  1 09:52:17 2001 GMT
            Not After : Jun  1 09:52:17 2005 GMT
        Subject: C=DE, O=Regulierungsbeh\xC8orde f\xC8ur Telekommunikation und Post/0.2.262.1.10.7.20=1, CN=6R-Ca 1:PN
--------------------
/etc/ssl/certs/thawteCb.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1 (0x1)
        Signature Algorithm: md5WithRSAEncryption
        Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com
        Validity
            Not Before: Aug  1 00:00:00 1996 GMT
            Not After : Dec 31 23:59:59 2020 GMT
        Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com
--------------------
/etc/ssl/certs/thawteCp.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1 (0x1)
        Signature Algorithm: md5WithRSAEncryption
        Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com
        Validity
            Not Before: Aug  1 00:00:00 1996 GMT
            Not After : Dec 31 23:59:59 2020 GMT
        Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com
--------------------
/etc/ssl/certs/vsign1.pem
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            32:50:33:cf:50:d1:56:f3:5c:81:ad:65:5c:4f:c8:25
        Signature Algorithm: md2WithRSAEncryption
        Issuer: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority
        Validity
            Not Before: Jan 29 00:00:00 1996 GMT
            Not After : Jan  7 23:59:59 2020 GMT
--------------------
/etc/ssl/certs/vsign3.pem
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            70:ba:e4:1d:10:d9:29:34:b6:38:ca:7b:03:cc:ba:bf
        Signature Algorithm: md2WithRSAEncryption
        Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
        Validity
            Not Before: Jan 29 00:00:00 1996 GMT
            Not After : Aug  1 23:59:59 2028 GMT
--------------------
/etc/ssl/certs/vsignss.pem
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            02:ad:66:7e:4e:45:fe:5e:57:6f:3c:98:19:5e:dd:c0
        Signature Algorithm: md2WithRSAEncryption
        Issuer: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority
        Validity
            Not Before: Nov  9 00:00:00 1994 GMT
            Not After : Jan  7 23:59:59 2010 GMT
--------------------
/etc/ssl/certs/wellsfgo.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 971282334 (0x39e4979e)
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=US, O=Wells Fargo, OU=Wells Fargo Certification Authority, CN=Wells Fargo Root Certificate Authority
        Validity
            Not Before: Oct 11 16:41:28 2000 GMT
            Not After : Jan 14 16:41:28 2021 GMT
        Subject: C=US, O=Wells Fargo, OU=Wells Fargo Certification Authority, CN=Wells Fargo Root Certificate Authority
```

## 併せて読みたい

* [PHP][cURL] cURLでSSL（https）のCA証明書警告の回避や設定
http://mio-koduki.blogspot.jp/2012/08/php-curlsslhttpsca.html

* サーバのSSL CA(認証局)証明書が古くてcurl がエラーになる件
http://d.hatena.ne.jp/hogem/20120705/1340284071