LinuxでHTTPS通信時にデフォルトで使用される証明書ファイルの場所

はじめに

HTTPS通信をする際、サーバ側からはサーバ証明書(+中間証明書)を提示し、クライアント側では予めインストールされた信頼済みルート証明書を使って、サーバから提示されたサーバ証明書を検証する(※)。

※片方向TLSの場合。双方向の場合はクライアント側からもクライアント証明書を提示する。

サーバ側で使うサーバ証明書はWebサーバとなるソフトウェアの設定ファイル等で指定されるが、クライアント側のルート証明書は何が使われるのだろうか。Windows/MacWebブラウザがクライアントとなる場合の情報はどこにでもあるが、Linuxでクライアントが非ブラウザの場合の情報があまりなさそうだったので、調べた範囲でまとめてみる。ブラウザだったら証明書を無視して表示することもできるが、独自アプリケーションやスクリプトLinux間REST通信をする際にHTTPSを使っていたりすると、証明書の検証エラーは通信断を意味するので・・・。

(参考までに)サーバ側

Webサーバとなるソフトウェアの設定ファイルで、使用するサーバ証明書(+中間証明書)を指定する。

Apache + OpenSSL (mod_ssl) の場合

Apacheの設定ファイル(CentOS系の場合、/etc/httpd/conf.d/ssl.conf)でサーバ証明書秘密鍵の場所を定義

# less /etc/httpd/conf.d/ssl.conf
...
SSLCertificateFile /etc/pki/tls/certs/server.crt
SSLCertificateKeyFile /etc/pki/tls/private/server.key
...

Tomcatの場合

Tomcatサーバの設定ファイル(server.xml)内のSSL設定部分でサーバ証明書秘密鍵が含まれるキーストアファイルを指定

# less /opt/apache-tomcat-9.0.34/conf/server.xml
...
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true">
        <SSLHostConfig>
            <Certificate certificateKeystoreFile="conf/keystore.jks"
                         certificationKeystorePassword="changeit"
                         certificationKeyAlias="tomcat"
                         type="RSA"
                           />
        </SSLHostConfig>
    </Connector>
...

クライアント側

クライアントとなるアプリケーションによって異なる。 アプリケーション内で設定しない場合や実行時オプション指定で明に指定しない場合は、デフォルト値が使用される。デフォルト値はアプリケーションと環境(ディストリビューションとそのバージョン、クライアントアプリケーションのビルド時の設定等)によって異なる。

以下、クライアントとしてcurl, openssl, Javaアプリケーションの場合について記載する。

curlの場合

以下、①②の設定値があり、①→②の順に参照する。

①CAfile:使用するCA証明書ファイル(PEM形式)。複数の証明書が含まれてもよい。--cacertオプションで指定可能。
②CApath:CA証明書が格納されたディレクトリ。CA証明書(もしくは証明書へのシンボリックリンク)は{hash}.0という名前になっている必要がある。 {hash}は証明書のsubjectをハッシュ化した値。openssl x509 -in <証明書ファイル> -hash -nooutで確認できる。--capathオプションで指定可能。

①②について、オプションで指定しなかった場合、デフォルト値が使用される。

デフォルト値はどこで決まるか

デフォルト値はビルド時のオプションで指定可能。

例えば下記のようにcurl 7.58.0のソースをダウンロードし、configure --helpで説明が表示される。

# curl -O https://curl.haxx.se/download/curl-7.58.0.tar.gz
# tar xvzf curl-7.58.0.tar.gz
# cd curl-7.58.0
# ./configure --help
...
  --with-ca-bundle=FILE   Path to a file containing CA certificates (example:
                          /etc/ca-bundle.crt)
  ...
  --with-ca-path=DIRECTORY
                          Path to a directory containing CA certificates
                          stored individually, with their filenames in a hash
                          format. This option can be used with OpenSSL, GnuTLS
                          and PolarSSL backends. Refer to OpenSSL c_rehash for
                          details. (example: /etc/certificates)
...

CentOS 7.6.1810で実際にconfigureを実施した結果は以下のようになった。

# ./configure --enable-libcurl-option
...
configure: Configured to build curl/libcurl:

  curl version:     7.58.0
  Host setup:       x86_64-pc-linux-gnu
  Install prefix:   /usr/local
  Compiler:         gcc
  SSL support:      no      (--with-{ssl,gnutls,nss,polarssl,mbedtls,cyassl,axtls,winssl,darwinssl} )
  SSH support:      no      (--with-libssh2)
  zlib support:     no      (--with-zlib)
  brotli support:   no      (--with-brotli)
  GSS-API support:  no      (--with-gssapi)
  TLS-SRP support:  no      (--enable-tls-srp)
  resolver:         POSIX threaded
  IPv6 support:     enabled
  Unix sockets support: enabled
  IDN support:      no      (--with-{libidn2,winidn})
  Build libcurl:    Shared=yes, Static=yes
  Built-in manual:  enabled
  --libcurl option: enabled (--disable-libcurl-option)
  Verbose errors:   enabled (--disable-verbose)
  SSPI support:     no      (--enable-sspi)
  ca cert bundle:   /etc/pki/tls/certs/ca-bundle.crt
  ca cert path:     no
  ca fallback:      no
  LDAP support:     no      (--enable-ldap / --with-ldap-lib / --with-lber-lib)
  LDAPS support:    no      (--enable-ldaps)
  RTSP support:     enabled
  RTMP support:     no      (--with-librtmp)
  metalink support: no      (--with-libmetalink)
  PSL support:      no      (libpsl not found)
  HTTP2 support:    disabled (--with-nghttp2)
  Protocols:        DICT FILE FTP GOPHER HTTP IMAP POP3 RTSP SMTP TELNET TFTP

①CAfileは/etc/pki/tls/certs/ca-bundle.crt、②CApathは設定なしとなった。また、ca fallbackについては、①②共にサーバ証明書の検証に失敗した場合に、OS標準の証明書ストアを使用するか、を設定するものと思われるが、これはまだ検証していない。

現在設定されているデフォルト値の確認方法

ではOSに最初から入っているcurlの場合や、yum/aptでインストールした場合はどうしたらいいのだろうか?探した限りでは、あまり標準的なやり方はない模様。

serverfault.com

こちらでも議論されており、様々な方法が提案されているが、個人的にはオプション-vをつけてhttpsサーバのURLにアクセスするのが一番シンプルだと思った(実際にping疎通可能なhttpsサーバがいることが前提だが...)。SSLセッション確立が成功しても失敗しても、CAfileとCApathのデフォルト値が表示される。

CentOS 7.6.1810, curl 7.29.0で実行した場合の例: 192.168.12.22(webserver)ではapache + mod_sslでWebサーバが動いており、オレオレ証明書(CN=webserver)が設定されている。

[root@localhost ~]# curl -v https://webserver/
* About to connect() to webserver port 443 (#0)
*   Trying 192.168.12.22...
* Connected to webserver (192.168.12.22) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* Server certificate:
*       subject: CN=webserver,O=Default Company Ltd,L=Default City,ST=tokyo,C=JP
*       start date: Apr 29 09:15:04 2020 GMT
*       expire date: May 29 09:15:04 2020 GMT
*       common name: webserver
*       issuer: CN=webserver,O=Default Company Ltd,L=Default City,ST=tokyo,C=JP
* NSS error -8172 (SEC_ERROR_UNTRUSTED_ISSUER)
* Peer's certificate issuer has been marked as not trusted by the user.
* Closing connection 0
curl: (60) Peer's certificate issuer has been marked as not trusted by the user.
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). If the default
 bundle file isn't adequate, 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.

CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none (設定なし)
が確認できる。

実際のデフォルト値はどうなっているか

基本的にはディストリビューションにより異なっている模様。またバージョンによって証明書を配置するディレクトリの構成が異なっている。

CentOS 6, CentOS 7, Ubuntu 18.04にバンドルされているcurlについて調査した。結果を先に記載する。

OS アプリとバージョン デフォルトで参照するルート証明書の場所
CentOS 6.10 curl 7.19.7 CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
CentOS 7.6.1810 curl 7.29.0 CAfile: /etc/pki/tls/certs/ca-bundle.crt -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
CApath: none
Ubuntu 18.04 curl 7.58.0 CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs

curlSSL系のライブラリとして、NSSを使用する場合とOpenSSLを使う場合があり、それによっても違うのかとも思ったが、調べた限り上記デフォルト値の考え方自体は変わらないように見えた。使用ライブラリについては、curl -Vでバージョンと共に確認可能。

以下確認の詳細。

CentOS 6

[root@centos6 ~]# cat /etc/redhat-release
CentOS release 6.10 (Final)

[root@centos6 ~]# curl -V
curl 7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Protocols: tftp ftp telnet dict ldap ldaps http file https ftps scp sftp
Features: GSS-Negotiate IDN IPv6 Largefile NTLM SSL libz

[root@centos6 ~]# curl -v https://webserver/
* About to connect() to webserver port 443 (#0)
*   Trying 192.168.12.22... connected
* Connected to webserver (192.168.12.22) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* Certificate is signed by an untrusted issuer: 'CN=webserver,O=Default Company Ltd,L=Default City,ST=tokyo,C=JP'
* NSS error -8172
* Closing connection #0
* Peer certificate cannot be authenticated with known CA certificates
curl: (60) Peer certificate cannot be authenticated with known CA certificates
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). If the default
 bundle file isn't adequate, 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.

CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
であることがわかる。

/etc/pki/tls/certsを実際に確認してみる。

[root@centos6 ~]# ll /etc/pki/tls/certs
total 1164
-rw-r--r--. 1 root root 755417 May  4 01:57 ca-bundle.crt
-rw-r--r--. 1 root root 418126 Feb 28  2018 ca-bundle.trust.crt
-rwxr-xr-x. 1 root root    610 Aug 14  2019 make-dummy-cert
-rw-r--r--. 1 root root   2242 Aug 14  2019 Makefile
-rwxr-xr-x. 1 root root    829 Aug 14  2019 renew-dummy-cert

ca-bundle.crtの実体が格納されている。

CentOS 7

[root@localhost ~]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)

[root@localhost ~]# curl -V
curl 7.29.0 (x86_64-redhat-linux-gnu) libcurl/7.29.0 NSS/3.36 zlib/1.2.7 libidn/1.28 libssh2/1.4.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smtp smtps telnet tftp
Features: AsynchDNS GSS-Negotiate IDN IPv6 Largefile NTLM NTLM_WB SSL libz unix-sockets

[root@localhost ~]# curl -v https://webserver/
* About to connect() to webserver port 443 (#0)
*   Trying 192.168.12.22...
* Connected to webserver (192.168.12.22) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* Server certificate:
*       subject: CN=webserver,O=Default Company Ltd,L=Default City,ST=tokyo,C=JP
*       start date: Apr 29 09:15:04 2020 GMT
*       expire date: May 29 09:15:04 2020 GMT
*       common name: webserver
*       issuer: CN=webserver,O=Default Company Ltd,L=Default City,ST=tokyo,C=JP
* NSS error -8172 (SEC_ERROR_UNTRUSTED_ISSUER)
* Peer's certificate issuer has been marked as not trusted by the user.
* Closing connection 0
curl: (60) Peer's certificate issuer has been marked as not trusted by the user.
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). If the default
 bundle file isn't adequate, 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.

CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
であることがわかる。(CentOS 6と同様)

/etc/pki/tls/certsを実際に確認してみる。

[root@localhost ~]# ll /etc/pki/tls/certs
total 16
lrwxrwxrwx. 1 root root   49 May  4 12:51 ca-bundle.crt -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
lrwxrwxrwx. 1 root root   55 Jun  1  2019 ca-bundle.trust.crt -> /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt
-rw-------. 1 root root 1468 Apr 11 08:28 localhost.crt
-rwxr-xr-x. 1 root root  610 Mar 12  2019 make-dummy-cert
-rw-r--r--. 1 root root 2516 Mar 12  2019 Makefile
-rwxr-xr-x. 1 root root  829 Mar 12  2019 renew-dummy-cert

ca-bundle.crtがシンボリックリンクになっている。

s-tajima.hateblo.jp

CentOS7から証明書の管理方針が変わり、update-ca-trustで管理するようになっている。

Ubuntu 18.04

バージョン確認

root@ubuntu-bionic:~# cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

root@ubuntu-bionic:~# curl -V
curl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.1 zlib/1.2.11 libidn2/2.0.4 libpsl/0.19.1 (+libidn2/2.0.4) nghttp2/1.30.0 librtmp/2.3
Release-Date: 2018-01-24
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL

ライブラリとしてNSSではなくOpenSSLを使っている。

root@ubuntu-bionic:~# curl -v https://webserver/
*   Trying 192.168.12.22...
* TCP_NODELAY set
* Connected to webserver (192.168.12.22) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  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 (OUT), TLS alert, Server hello (2):
* SSL certificate problem: self signed certificate
* stopped the pause stream!
* Closing connection 0
curl: (60) SSL certificate problem: self signed certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs
であることがわかる。

/etc/ssl/certs/を確認する。

root@ubuntu-bionic:~# ll /etc/ssl/certs
total 632
drwxr-xr-x 3 root root  16384 May  4 09:12  ./
drwxr-xr-x 4 root root   4096 Dec 18 19:12  ../
lrwxrwxrwx 1 root root     45 May  2 13:35  02265526.0 -> Entrust_Root_Certification_Authority_-_G2.pem
lrwxrwxrwx 1 root root     36 May  2 13:35  03179a64.0 -> Staat_der_Nederlanden_EV_Root_CA.pem
lrwxrwxrwx 1 root root     27 May  2 13:35  062cdee6.0 -> GlobalSign_Root_CA_-_R3.pem
lrwxrwxrwx 1 root root     25 May  2 13:35  064e0aa9.0 -> QuoVadis_Root_CA_2_G3.pem
lrwxrwxrwx 1 root root     50 May  2 13:35  06dc52d5.0 -> SSL.com_EV_Root_Certification_Authority_RSA_R2.pem
...以下略。ルート証明書へのシンボリックリンクがずらっと入っている。

root@ubuntu-bionic:~# ll /etc/ssl/certs/ca-certificates.crt
-rw-r--r-- 1 root root 207436 May  4 09:12 /etc/ssl/certs/ca-certificates.crt

/etc/ssl/certsには、信頼済みルート証明書へのシンボリックリンクと、信頼済みルート証明書の実体がバンドルされたファイルca-certificates.crtが入っている。

ちなみに、下記の方法(update-ca-certificates)で独自のルート証明書を追加した場合、シンボリックリンクの作成とca-certificates.crtへの追加の両方が実行される。

qiita.com

opensslの場合

openssl s_client -connect ...でクライアントアプリとして使う場合を想定。

curlと同様、①CAfileと②CApathで設定する。 参照する順番はcurlと同様①→②。 本家のドキュメントにも記載があった。

https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_load_verify_locations.html

When looking up CA certificates, the OpenSSL library will first search the certificates in CAfile, then those in CApath.

デフォルト値はどこで決まるか

先ほどと同じ本家のページに記載があった。

The default CA certificates directory is called "certs" in the default OpenSSL directory.

(中略)

The default CA certificates file is called "cert.pem" in the default OpenSSL directory.

CAfile: "OpenSSL directory"配下のcert.pem
CApath: "OpenSSL directory"配下のcertsディレクト
となる。

こちらのページ によると、cryptlib.hで定義されている模様。

"OpenSSL directory"はビルド時に設定可能。また環境変数での定義も可能。OpenSSLのソースをダウンロードし、フォルダ配下のINSTALLファイルを確認すると記載がある。

下記、OpenSSL 1.1.1で確認:

# less INSTALL
...
--openssldir=DIR
                   Directory for OpenSSL configuration files, and also the
                   default certificate and key store.  Defaults are:

                   Unix:           /usr/local/ssl
                   Windows:        C:\Program Files\Common Files\SSL
                                or C:\Program Files (x86)\Common Files\SSL
                   OpenVMS:        SYS$COMMON:[OPENSSL-COMMON]

Unixではデフォルトは/usr/local/sslになるようだ。

現在設定されているデフォルト値の確認方法

デフォルトのCAfileとCApathのファイル名/ディレクトリ名は上記の通り固定値のようなので、"OPENSSLDIR"を確認すればよい。

stackoverflow.com

openssl version -aで確認できるようだ。

実際のデフォルト値はどうなっているか

CentOS 6, CentOS 7, Ubuntu 18.04にバンドルされているopensslについて調査した。先に結果を記載する。

OS アプリとバージョン デフォルトで参照するルート証明書の場所
CentOS 6.10 OpenSSL 1.0.1e-fips CAfile: /etc/pki/tls/cert.pem -> /etc/pki/tls/certs/ca-bundle.crt
CApath: /etc/pki/tls/certs
CentOS 7.6.1810 OpenSSL 1.0.2k-fips CAfile: /etc/pki/tls/cert.pem -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
CApath: /etc/pki/tls/certs
Ubuntu 18.04 OpenSSL 1.1.1 CAfile: なし?
CApath: /usr/lib/ssl/certs -> /etc/ssl/certs/

以下、確認の詳細。

CentOS 6

[root@centos6 ~]# openssl version -a
OpenSSL 1.0.1e-fips 11 Feb 2013
built on: Wed Aug 14 16:32:19 UTC 2019
platform: linux-x86_64
options:  bn(64,64) md2(int) rc4(16x,int) des(idx,cisc,16,int) idea(int) blowfish(idx)
compiler: gcc -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -DKRB5_MIT -m64 -DL_ENDIAN -DTERMIO -Wall -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -Wa,--noexecstack -DPURIFY -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM
OPENSSLDIR: "/etc/pki/tls"
engines:  rdrand dynamic

OPENSSLDIRは/etc/pki/tlsとなっている。 確認してみる。

[root@centos6 ~]# ll /etc/pki/tls
total 24
lrwxrwxrwx. 1 root root    19 Nov 21 16:58 cert.pem -> certs/ca-bundle.crt
drwxr-xr-x. 2 root root  4096 May  4 22:00 certs
drwxr-xr-x. 2 root root  4096 Nov 21 17:01 misc
-rw-r--r--. 1 root root 10906 Jul  2  2019 openssl.cnf
drwxr-xr-x. 2 root root  4096 Aug 14  2019 private

cert.pemはシンボリックリンクになっているので、certs配下を確認。

[root@centos6 ~]# ll /etc/pki/tls/certs
total 1164
-rw-r--r--. 1 root root 755417 May  4 01:57 ca-bundle.crt
-rw-r--r--. 1 root root 418126 Feb 28  2018 ca-bundle.trust.crt
-rwxr-xr-x. 1 root root    610 Aug 14  2019 make-dummy-cert
-rw-r--r--. 1 root root   2242 Aug 14  2019 Makefile
-rwxr-xr-x. 1 root root    829 Aug 14  2019 renew-dummy-cert

ca-bundle.crtの実体があった。

CentOS 7

[root@localhost ~]# openssl version -a
OpenSSL 1.0.2k-fips  26 Jan 2017
built on: reproducible build, date unspecified
platform: linux-x86_64
options:  bn(64,64) md2(int) rc4(16x,int) des(idx,cisc,16,int) idea(int) blowfish(idx)
compiler: gcc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -DKRB5_MIT -m64 -DL_ENDIAN -Wall -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches   -m64 -mtune=generic -Wa,--noexecstack -DPURIFY -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DRC4_ASM -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
OPENSSLDIR: "/etc/pki/tls"
engines:  rdrand dynamic

OPENSSLDIRはCentOS6同様、/etc/pki/tlsになっている。

[root@localhost ~]# ll /etc/pki/tls
total 12
lrwxrwxrwx. 1 root root    49 May  4 04:46 cert.pem -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
drwxr-xr-x. 2 root root   138 May  5 05:11 certs
drwxr-xr-x. 2 root root    74 Jun  1  2019 misc
-rw-r--r--. 1 root root 10923 May  4 04:50 openssl.cnf
drwxr-xr-x. 2 root root    45 Apr 11 12:26 private

cert.pemの実体はCentOS6と異なり、例のupdate-ca-trustによって管理されている場所にあるようだ。 また、certsディレクトリは以下のとおり。

[root@localhost ~]# ll /etc/pki/tls/certs
total 16
lrwxrwxrwx. 1 root root   49 May  4 12:51 ca-bundle.crt -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
lrwxrwxrwx. 1 root root   55 Jun  1  2019 ca-bundle.trust.crt -> /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt
-rw-------. 1 root root 1468 Apr 11 08:28 localhost.crt
-rwxr-xr-x. 1 root root  610 Mar 12  2019 make-dummy-cert
-rw-r--r--. 1 root root 2516 Mar 12  2019 Makefile
-rwxr-xr-x. 1 root root  829 Mar 12  2019 renew-dummy-cert

curl用と思われるca-bundle.crt(シンボリックリンク)等が入っているが、Ubuntuと違い、信頼済みルートCA証明書のシンボリックリンク一覧は入っていない。openssl s_clientは当ディレクトリ配下の{hash}.0ファイルのみしか見ないため、これらの.crtファイルは無視される模様。

Ubuntu 18.04

root@ubuntu-bionic:~# openssl version -a
OpenSSL 1.1.1  11 Sep 2018
built on: Tue Nov 12 16:58:35 2019 UTC
platform: debian-amd64
options:  bn(64,64) rc4(16x,int) des(int) blowfish(ptr)
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -fdebug-prefix-map=/build/openssl-kxN_24/openssl-1.1.1=. -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPADLOCK_ASM -DPOLY1305_ASM -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2
OPENSSLDIR: "/usr/lib/ssl"
ENGINESDIR: "/usr/lib/x86_64-linux-gnu/engines-1.1"
Seeding source: os-specific

OPENSSLDIRは/usr/lib/ssl

root@ubuntu-bionic:~# ll /usr/lib/ssl
total 12
drwxr-xr-x  3 root root 4096 Dec 18 19:12 ./
drwxr-xr-x 68 root root 4096 May  3 11:50 ../
lrwxrwxrwx  1 root root   14 Apr 25  2018 certs -> /etc/ssl/certs/
drwxr-xr-x  2 root root 4096 Dec 18 19:12 misc/
lrwxrwxrwx  1 root root   20 Nov 12 16:58 openssl.cnf -> /etc/ssl/openssl.cnf
lrwxrwxrwx  1 root root   16 Apr 25  2018 private -> /etc/ssl/private/

certsディレクトリ (-> /etc/ssl/certs)はあるが、cert.pemは見当たらない・・・。CAfileの設定なしでビルドされたから?試しに/usr/lib/ssl配下にcert.pemを作ってみたが、CAfileとしては認識されなかった。

Javaアプリケーションの場合

${JAVA_HOME}/jre/lib/security/cacertsが参照される、というのが定説。

それ以外に、

といった方法があるようだ。

Javaアプリケーションでは、信頼するCAを「(JREディレクトリ)/lib/security/cacerts」キーストアか、「javax.net.ssl.trustStore」プロパティで指定されるキーストアによって管理しています。デフォルトではjavax.net.ssl.trustStoreプロパティは指定されませんので、「(JREディレクトリ)/lib/security/cacerts」キーストアに証明書が登録されている、CAを信頼します。

11. 認証方式 (5) | TECHSCORE(テックスコア)

システムプロパティの"javax.net.ssl.trustStore"を設定した場合、そちらが参照される。システムプロパティの設定方法としては、Javaの実行時オプションとして設定する方法と、Javaプログラムの中で指定する方法がある。

Javaの実行時オプションとして設定する方法

実行時オプションとして、システムプロパティ"javax.net.ssl.trustStore"を設定する。

# java -Djavax.net.ssl.trustStore=<cacertsへのフルパス> -Djavax.
net.ssl.trustStorePassword=changeit <class名>

Javaプログラムの中で指定する方法

a4dosanddos.hatenablog.com

上記サイトを参考にさせていただいてます。

システムプロパティ"javax.net.ssl.trustStore"を設定

System.setProperty("javax.net.ssl.trustStore", "キーストア(トラストストア)のパス");
System.setProperty("javax.net.ssl.trustStorePassword ", "パスワード(default: changeit)");

その他、SSLSocketFactoryを使って自力でゴリゴリと設定する方法もあるようだ。

まとめると、Javaアプリケーションがどのルート証明書を参照するかは「アプリの作り次第」である。アプリ内で設定していればその場所が参照されるし、していなければデフォルトの場所が参照される。

実際のデフォルト値はどうなっているか

アプリケーションや実行時オプションで指定しなかった場合のデフォルト参照先について、調査結果を先にまとめておく。

OS Java Javaが参照するルート証明書の場所
CentOS 6.10 OpenJDK 1.8.0 ${JAVA_HOME}/jre/lib/security/cacerts -> /etc/pki/java/cacerts
CentOS 7.6.1810 OpenJDK 1.8.0 /etc/pki/java/cacerts -> /etc/pki/ca-trust/extracted/java/cacerts
Ubuntu 18.04 OpenJDK 1.8.0 ${JAVA_HOME}/jre/lib/security/cacerts -> /etc/ssl/certs/java/cacerts
  • CentOS7だけ異なり、${JAVA_HOME}/jre/lib/security/cacertsではなく、OSの証明書ストア(/etc/pki/java/cacerts)が参照されていた。理由は不明。
  • 今回調査した環境ではいずれも${JAVA_HOME}/jre/lib/security/cacertsはOSの証明書ストアへのシンボリックリンクになっていたが、私が知っている某商用環境(RHEL6, 7 + Oracle JDK 1.8)では${JAVA_HOME}/jre/lib/security/cacertsに実体があった。これはOpenJDKとOracleJDKの違いかもしれない。この場合、curlやopensslでルート証明書のテストをして問題ないとなっても、javaだとエラーになるケースも想定されるため、注意が必要。
  • OpenJDK 1.8.0しか調べていないので、他のバージョンだと変わるかもしれない。

security.data-site.info

CentOS6 + OpenJDK 1.8.0

OpenJDKはyumでインストールしたもの。

[root@centos6 ~]# cat /etc/redhat-release
CentOS release 6.10 (Final)

[root@centos6 ~]# java -version
openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-b09)
OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode)

${JAVA_HOME}/jre/lib/security/cacertsを確認。

[root@centos6 ~]# ll /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el6_10.x86_64/jre/lib/security/
total 64
-rw-r--r--. 1 root root  1273 Apr 27 08:19 blacklisted.certs
lrwxrwxrwx. 1 root root    41 May  4 00:53 cacerts -> ../../../../../../../etc/pki/java/cacerts
-rw-r--r--. 1 root root  2567 Apr 27 08:19 java.policy
-rw-r--r--. 1 root root 45794 Apr 27 08:19 java.security
-rw-r--r--. 1 root root   141 Apr 27 08:24 nss.cfg
drwxr-xr-x. 4 root root  4096 May  2 00:24 policy

cacertsが/etc/pki/java/cacertsへのシンボリックリンクになっている。

[root@centos6 ~]# ll /etc/pki/java/cacerts
-rw-r--r--. 1 root root 158194 May  4 00:44 /etc/pki/java/cacerts

こちらはシンボリックリンクではなく、実体がここにあるようだ。

ここで、Javaアプリケーションが本当に${JAVA_HOME}/jre/lib/security/cacertsを使っているか、念のためテストしてみる。テスト用のJavaアプリケーションとして、先ほども引用した、下記ページのコードを使わせてもらった。

http://a4dosanddos.hatenablog.com/entry/2015/03/29/125111

TestCertClient.java

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;

public class TestCertClient {
        public static void main(String[] args) throws Exception {
                URL url = new URL("https://webserver");
                HttpsURLConnection con = (HttpsURLConnection) url.openConnection();

                System.out.println("----- Headers -----");
                Map<String, List<String>> headers = con.getHeaderFields();
                for (String key : headers.keySet()) {
                        System.out.println(key + ": " + headers.get(key));
                }
                System.out.println();

                System.out.println("----- Body -----");
                BufferedReader reader = new BufferedReader(new InputStreamReader(
                                con.getInputStream()));
                String body;
                while ((body = reader.readLine()) != null) {
                        System.out.println(body);
                }

                reader.close();
                con.disconnect();
        }
}

また、先ほどと同じように、Apache + mod_sslでオレオレサーバ証明書(CN=webserver)を設定したWebサーバが192.168.12.22(/etc/hostsにwebserverと登録)で動いている。webserver側では、下記のようなhtmlを返すよう設定しておく。

<html>
  <head>
  </head>
  <body>
    <p>Hello, World!</p>
  </body>
</html>

オレオレ証明書はまだクライアントに設定していないので、${JAVA_HOME}/jre/lib/security/cacerts -> /etc/pki/java/cacertsの中身をkeytoolで確認すると、

[root@centos6 ~]# keytool -v -list -keystore /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el6_10.x86_64/jre/lib/security/cacerts -storepass changeit | grep webserver
[root@centos6 ~]#

webserverは登録されていない。 この状態でJavaアプリから疎通確認してみる。

[root@centos6 ~]# javac TestCertClient.java
[root@centos6 ~]# java TestCertClient
----- Headers -----

----- Body -----
Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:1950)
        at sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:1945)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.net.www.protocol.http.HttpURLConnection.getChainedException(HttpURLConnection.java:1944)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1514)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:268)
        at TestCertClient.main(TestCertClient.java:36)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:198)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1967)
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:331)
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:325)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1688)
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:226)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1082)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:1010)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1079)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1388)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1416)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1400)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1570)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498)
        at sun.net.www.protocol.http.HttpURLConnection.getHeaderFields(HttpURLConnection.java:3084)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getHeaderFields(HttpsURLConnectionImpl.java:297)
        at TestCertClient.main(TestCertClient.java:28)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:450)
        at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:317)
        at sun.security.validator.Validator.validate(Validator.java:262)
        at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:330)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:237)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:132)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1670)
        ... 14 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
        at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
        at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:445)
        ... 20 more

当然ながら、unable to find valid certification path to requested targetが出る。 今度はオレオレ証明書(カレントディレクトリにあるserver.crt)をcacertsに登録する。

[root@centos6 ~]# keytool -importcert -file server.crt -keystore /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el6_10.x86_64/jre/lib/security/cacerts -storepass changeit -alias webserver
Owner: CN=webserver, O=Default Company Ltd, L=Default City, ST=tokyo, C=JP
Issuer: CN=webserver, O=Default Company Ltd, L=Default City, ST=tokyo, C=JP
Serial number: 9a6640c6cb0cb258
Valid from: Wed Apr 29 02:15:04 PDT 2020 until: Fri May 29 02:15:04 PDT 2020
Certificate fingerprints:
         MD5:  3A:B6:25:B4:F0:E2:C3:54:41:B8:0F:1B:2A:F9:76:EB
         SHA1: C9:C3:50:95:88:51:56:CB:28:7F:D0:E8:D5:56:9B:55:40:90:29:B5
         SHA256: 3E:47:A0:0F:47:77:1C:F3:E6:0D:22:F2:D5:B7:2F:94:DE:AD:16:1E:75:83:78:0B:DE:27:57:7D:44:BD:DB:16
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 1
Trust this certificate? [no]:  yes
Certificate was added to keystore

cacertsの中身を確認してみる。

[root@centos6 ~]# keytool -v -list -keystore /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el6_10.x86_64/jre/lib/security/cacerts -storepass changeit | grep webserver
Alias name: webserver
Owner: CN=webserver, O=Default Company Ltd, L=Default City, ST=tokyo, C=JP
Issuer: CN=webserver, O=Default Company Ltd, L=Default City, ST=tokyo, C=JP

webserverが登録されたので、Javaアプリから疎通確認。

[root@centos6 ~]# java TestCertClient
----- Headers -----
Keep-Alive: [timeout=5, max=100]
Accept-Ranges: [bytes]
null: [HTTP/1.1 200 OK]
Server: [Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips]
ETag: ["54-5a46d692b208a"]
Connection: [Keep-Alive]
Last-Modified: [Wed, 29 Apr 2020 12:51:46 GMT]
Content-Length: [84]
Date: [Tue, 05 May 2020 12:35:27 GMT]
Content-Type: [text/html; charset=UTF-8]

----- Body -----
<html>
  <head>
  </head>
  <body>
    <p>Hello, World!</p>
  </body>
</html>

無事接続できている。 この状態で、試しにcacertsのシンボリックリンクをリネームしてみる。

[root@centos6 ~]# cd /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el6_10.x86_64/jre/lib/security/
[root@centos6 security]# ll
total 64
-rw-r--r--. 1 root root  1273 Apr 27 08:19 blacklisted.certs
lrwxrwxrwx. 1 root root    41 May  2 00:24 cacerts -> ../../../../../../../etc/pki/java/cacerts
-rw-r--r--. 1 root root  2567 Apr 27 08:19 java.policy
-rw-r--r--. 1 root root 45794 Apr 27 08:19 java.security
-rw-r--r--. 1 root root   141 Apr 27 08:24 nss.cfg
drwxr-xr-x. 4 root root  4096 May  2 00:24 policy

[root@centos6 security]# ln -nfs ../../../../../../../etc/pki/java/cacerts cacerts2
[root@centos6 security]# ll
total 64
-rw-r--r--. 1 root root  1273 Apr 27 08:19 blacklisted.certs
lrwxrwxrwx. 1 root root    41 May  2 00:24 cacerts -> ../../../../../../../etc/pki/java/cacerts
lrwxrwxrwx. 1 root root    41 May  4 00:46 cacerts2 -> ../../../../../../../etc/pki/java/cacerts
-rw-r--r--. 1 root root  2567 Apr 27 08:19 java.policy
-rw-r--r--. 1 root root 45794 Apr 27 08:19 java.security
-rw-r--r--. 1 root root   141 Apr 27 08:24 nss.cfg
drwxr-xr-x. 4 root root  4096 May  2 00:24 policy

[root@centos6 security]# unlink cacerts
[root@centos6 security]# ll
total 64
-rw-r--r--. 1 root root  1273 Apr 27 08:19 blacklisted.certs
lrwxrwxrwx. 1 root root    41 May  4 00:46 cacerts2 -> ../../../../../../../etc/pki/java/cacerts
-rw-r--r--. 1 root root  2567 Apr 27 08:19 java.policy
-rw-r--r--. 1 root root 45794 Apr 27 08:19 java.security
-rw-r--r--. 1 root root   141 Apr 27 08:24 nss.cfg
drwxr-xr-x. 4 root root  4096 May  2 00:24 policy

この状態で再度疎通確認してみる。

[root@centos6 ~]# java TestCertClient
----- Headers -----

----- Body -----
Exception in thread "main" javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:1950)
        at sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:1945)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.net.www.protocol.http.HttpURLConnection.getChainedException(HttpURLConnection.java:1944)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1514)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:268)
        at TestCertClient.main(TestCertClient.java:36)
Caused by: javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:214)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1967)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1924)
        at sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1907)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1423)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1400)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1570)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498)
        at sun.net.www.protocol.http.HttpURLConnection.getHeaderFields(HttpURLConnection.java:3084)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getHeaderFields(HttpsURLConnectionImpl.java:297)
        at TestCertClient.main(TestCertClient.java:28)
Caused by: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:104)
        at sun.security.validator.Validator.getInstance(Validator.java:181)
        at sun.security.ssl.X509TrustManagerImpl.getValidator(X509TrustManagerImpl.java:318)
        at sun.security.ssl.X509TrustManagerImpl.checkTrustedInit(X509TrustManagerImpl.java:179)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:193)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:132)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1670)
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:226)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1082)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:1010)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1079)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1388)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1416)
        ... 8 more
Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:200)
        at java.security.cert.PKIXParameters.<init>(PKIXParameters.java:120)
        at java.security.cert.PKIXBuilderParameters.<init>(PKIXBuilderParameters.java:104)
        at sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:102)
        ... 20 more

java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-emptyが出たので、やはりjavaは${JAVA_HOME}/jre/lib/security/cacerts -> /etc/pki/java/cacertsを使っているようだ。

CentOS7 + OpenJDK 1.8.0

OpenJDKはyumでインストールしたもの。

[root@localhost ~]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)

[root@localhost ~]# java -version
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (build 1.8.0_242-b08)
OpenJDK 64-Bit Server VM (build 25.242-b08, mixed mode)

${JAVA_HOME}/jre/lib/security/cacertsを確認。

[root@localhost ~]# ll /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.el7.x86_64/jre/lib/security/
total 56
-rw-r--r--. 1 root root  1273 Apr  2 16:02 blacklisted.certs
lrwxrwxrwx. 1 root root    41 May  4 07:19 cacerts -> ../../../../../../../etc/pki/java/cacerts
-rw-r--r--. 1 root root  2567 Apr  2 16:02 java.policy
-rw-r--r--. 1 root root 44792 Jan  1  2014 java.security
-rw-r--r--. 1 root root   139 Apr  2 16:07 nss.cfg
drwxr-xr-x. 4 root root    38 Apr  2 16:02 policy

CentOS6同様、/etc/pki/java/cacertsへのシンボリックリンクになっている。

[root@localhost ~]# ll /etc/pki/java/cacerts
lrwxrwxrwx. 1 root root 40 May  4 07:03 /etc/pki/java/cacerts -> /etc/pki/ca-trust/extracted/java/cacerts

/etc/pki/java/cacertsはさらにシンボリックリンクとなっており、実体はupdate-ca-trustの管理下にあるようだ。

先ほどと同じように、${JAVA_HOME}/jre/lib/security/cacertsをリネームしてみる。

[root@localhost security]# ll 
total 56
-rw-r--r--. 1 root root  1273 Apr  2 16:02 blacklisted.certs
lrwxrwxrwx. 1 root root    41 May  4 07:16 cacerts2 -> ../../../../../../../etc/pki/java/cacerts
-rw-r--r--. 1 root root  2567 Apr  2 16:02 java.policy
-rw-r--r--. 1 root root 44792 Jan  1  2014 java.security
-rw-r--r--. 1 root root   139 Apr  2 16:07 nss.cfg
drwxr-xr-x. 4 root root    38 Apr  2 16:02 policy

疎通確認。

[root@localhost ~]# java TestCertClient
----- Headers -----
Keep-Alive: [timeout=5, max=100]
Accept-Ranges: [bytes]
null: [HTTP/1.1 200 OK]
Server: [Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips]
ETag: ["54-5a46d692b208a"]
Connection: [Keep-Alive]
Last-Modified: [Wed, 29 Apr 2020 12:51:46 GMT]
Content-Length: [84]
Date: [Mon, 04 May 2020 07:17:01 GMT]
Content-Type: [text/html; charset=UTF-8]

----- Body -----
<html>
  <head>
  </head>
  <body>
    <p>Hello, World!</p>
  </body>
</html>

・・・なぜか通ってしまった。${JAVA_HOME}/jre/lib/security/cacertsは見ていない?

ちなみに、シンボリックリンク/etc/pki/java/cacertsをリネームしたらNG(java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty)になった。どこかでOSの方の証明書ストアを見に行くように設定がされたのだろうか?

Ubuntu 18.04 + OpenJDK 1.8.0

OpenJDKはaptでインストールしたもの。

root@ubuntu-bionic:~# cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

root@ubuntu-bionic:~# java -version
openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-8u252-b09-1~18.04-b09)
OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode)

${JAVA_HOME}/jre/lib/security/cacertsを確認。

root@ubuntu-bionic:~# ll /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/security/
total 12
drwxr-xr-x 3 root root 4096 May  4 07:08 ./
drwxr-xr-x 8 root root 4096 May  2 08:02 ../
lrwxrwxrwx 1 root root   46 Apr 15 18:48 blacklisted.certs -> /etc/java-8-openjdk/security/blacklisted.certs
lrwxrwxrwx 1 root root   27 May  4 07:08 cacerts -> /etc/ssl/certs/java/cacerts
lrwxrwxrwx 1 root root   40 Apr 15 18:48 java.policy -> /etc/java-8-openjdk/security/java.policy
lrwxrwxrwx 1 root root   42 Apr 15 18:48 java.security -> /etc/java-8-openjdk/security/java.security
lrwxrwxrwx 1 root root   36 Apr 15 18:48 nss.cfg -> /etc/java-8-openjdk/security/nss.cfg
drwxr-xr-x 4 root root 4096 May  2 08:02 policy/

/etc/ssl/certs/java/cacertsへのシンボリックリンクになっている。

root@ubuntu-bionic:~# ll /etc/ssl/certs/java/cacerts
-rw-r--r-- 1 root root 156228 May  4 09:12 /etc/ssl/certs/java/cacerts

実体はこちらにある。ちなみにこのファイルはupdate-ca-certificatesの管理下であり、update-ca-certificatesを実行して信頼済みCA証明書を追加する際に、/etc/ssl/certs配下のca-certificates.crtおよびシンボリックリンク{hash}.0と共に更新されるようだ。

${JAVA_HOME}/jre/lib/security/cacertsをリネームしてみる。

root@ubuntu-bionic:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/security# ll
total 12
drwxr-xr-x 3 root root 4096 May  5 13:38 ./
drwxr-xr-x 8 root root 4096 May  2 08:02 ../
lrwxrwxrwx 1 root root   46 Apr 15 18:48 blacklisted.certs -> /etc/java-8-openjdk/security/blacklisted.certs
lrwxrwxrwx 1 root root   27 May  5 13:38 cacerts2 -> /etc/ssl/certs/java/cacerts
lrwxrwxrwx 1 root root   40 Apr 15 18:48 java.policy -> /etc/java-8-openjdk/security/java.policy
lrwxrwxrwx 1 root root   42 Apr 15 18:48 java.security -> /etc/java-8-openjdk/security/java.security
lrwxrwxrwx 1 root root   36 Apr 15 18:48 nss.cfg -> /etc/java-8-openjdk/security/nss.cfg
drwxr-xr-x 4 root root 4096 May  2 08:02 policy/

疎通確認。

root@ubuntu-bionic:~# java TestCertClient
----- Headers -----

----- Body -----
Exception in thread "main" javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
...以下略

the trustAnchors parameter must be non-emptyが出たので、Javaは${JAVA_HOME}/jre/lib/security/cacertsを参照しているようだ。

まとめ

結論的に、調べた範囲だとOS標準の場所を参照しているように見える(ただしOS標準の場所はディストリビューションとバージョンによって異なる)。アプリの作りとビルド時の設定にも依存するので、証明書の有効性を確認する時はそこまで確認する必要がある。

curl

OS アプリとバージョン デフォルトで参照するルート証明書の場所
CentOS 6.10 curl 7.19.7 CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
CentOS 7.6.1810 curl 7.29.0 CAfile: /etc/pki/tls/certs/ca-bundle.crt -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
CApath: none
Ubuntu 18.04 curl 7.58.0 CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs

【openssl】

OS アプリとバージョン デフォルトで参照するルート証明書の場所
CentOS 6.10 OpenSSL 1.0.1e-fips CAfile: /etc/pki/tls/cert.pem -> /etc/pki/tls/certs/ca-bundle.crt
CApath: /etc/pki/tls/certs
CentOS 7.6.1810 OpenSSL 1.0.2k-fips CAfile: /etc/pki/tls/cert.pem -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
CApath: /etc/pki/tls/certs
Ubuntu 18.04 OpenSSL 1.1.1 CAfile: なし?
CApath: /usr/lib/ssl/certs -> /etc/ssl/certs/

Java

OS アプリとバージョン デフォルトで参照するルート証明書の場所
CentOS 6.10 OpenJDK 1.8.0 ${JAVA_HOME}/jre/lib/security/cacerts -> /etc/pki/java/cacerts
CentOS 7.6.1810 OpenJDK 1.8.0 /etc/pki/java/cacerts -> /etc/pki/ca-trust/extracted/java/cacerts
Ubuntu 18.04 OpenJDK 1.8.0 ${JAVA_HOME}/jre/lib/security/cacerts -> /etc/ssl/certs/java/cacerts

参考