Zenn
📖

プライベートCAをLinuxに登録する仕組みについて

2025/03/28に公開
3

プライベートCAをLinuxに登録する仕組みについて

はじめに

三菱UFJインフォメーションテクノロジー株式会社の佐藤隆征と申します。
Zennでのテックブログは初投稿となります。よろしくお願いいたします。

本記事の趣旨

本記事では、プライベートCA(認証局)を利用する際に、Linuxサーバー全体で当該CAを信頼するための方法や仕組みについて説明します。

本記事で扱わない範囲

  • CA局の構築方法
  • CA局とは何かについての説明

CA(認証局)

CA(認証局)は大きく以下の2つに分類されます。

  • パブリックCA(認証局)
    • ブラウザベンダーが認めた認証局
  • プライベートCA(認証局)
    • 企業・組織が独自に構築する認証局

プライベートCA(認証局)は、主に企業・組織内で証明書を発行するために使用されます。一方、外部の組織向けには、パブリックCA(認証局)を利用するのが一般的です。

この記事の作成にあたり使用したプライベートCA(認証局)および、それによって署名された証明書は、内部でのみ利用しています。

プライベートCA(認証局)を登録する方法

Linuxディストリビューションごとに若干の違いはありますが、以下の手順で登録します。

Linuxディストリビューション プライベートCA(認証局)を配置する先 実行するコマンド
Red Hat系 /etc/pki/ca-trust/source/anchors update-ca-trust
Debian系 /usr/local/share/ca-certificates update-ca-certificates

仕組みについて

次のような動作を行います。

  • プライベートCA(認証局)を配置する先であるディレクトリ内のファイルおよびパブリックCA(認証局)の情報※1を収集
  • 収集した情報をファイルとしてまとめる※2

※1 パブリックCA(認証局)の情報は通常ディストリビューションのパッケージマネージャ(DNFやAPT等)がファイルとして配置します。
※2 Java等を利用している場合、追加で行われる仕組みが存在します。これについては後述します。

収集した情報をまとめたファイルを、curlやwget等のコマンドは参照するようになっています。

straceというコマンドを使用して、システムコールからcurlからどのようなファイルを参照しているかをみてみましょう。

openatおよびopenというシステムコールから読み取っているファイルを取得します。
以下はRed Hat Enterprise Linux 9で実行した例です。
(curlに渡す<url>部分は自身の環境にあわせて変更してください)

# strace curl <url>  2>&1 | grep -e 'openat' -e 'open'
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libcurl.so.4", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libmetalink.so.3", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libssl.so.1.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libcrypto.so.1.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libz.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libnghttp2.so.14", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libidn2.so.0", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libssh.so.4", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libpsl.so.5", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libgssapi_krb5.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libkrb5.so.3", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libk5crypto.so.3", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libcom_err.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libldap-2.4.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/liblber-2.4.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libbrotlidec.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libexpat.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libunistring.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/librt.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libkrb5support.so.0", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libkeyutils.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libresolv.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libsasl2.so.3", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libbrotlicommon.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libcrypt.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libpcre2-8.so.0", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/pki/tls/openssl.cnf", O_RDONLY) = 3
openat(AT_FDCWD, "/etc/crypto-policies/back-ends/opensslcnf.config", O_RDONLY) = 4
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレクトリはありません)
openat(AT_FDCWD, "/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.UTF-8/LC_IDENTIFICATION", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレク トリはありません)
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_IDENTIFICATION", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib64/gconv/gconv-modules.cache", O_RDONLY) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.UTF-8/LC_MEASUREMENT", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレクトリはありません)
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_MEASUREMENT", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.UTF-8/LC_TELEPHONE", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレクトリはありません)
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_TELEPHONE", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.UTF-8/LC_ADDRESS", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレクトリはありません)
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_ADDRESS", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.UTF-8/LC_NAME", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレクトリはあり ません)
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_NAME", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.UTF-8/LC_PAPER", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレクトリはありません)
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_PAPER", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.UTF-8/LC_MESSAGES", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレクトリは ありません)
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_MESSAGES/SYS_LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.UTF-8/LC_MONETARY", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレクトリは ありません)
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_MONETARY", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.UTF-8/LC_COLLATE", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレクトリはありません)
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_COLLATE", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.UTF-8/LC_TIME", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレクトリはあり ません)
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_TIME", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.UTF-8/LC_NUMERIC", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレクトリはありません)
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_NUMERIC", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.UTF-8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = -1 ENOENT (そのようなファイルやディレクトリはありません)
openat(AT_FDCWD, "/usr/lib/locale/ja_JP.utf8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/root/.curlrc", O_RDONLY) = -1 ENOENT (そのようなファイルやディレクトリはありません)
openat(AT_FDCWD, "/usr/share/locale/ja_JP.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (そのようなファイルやディレクトリは ありません)
openat(AT_FDCWD, "/usr/share/locale/ja_JP.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (そのようなファイルやディレクトリはありません)
openat(AT_FDCWD, "/usr/share/locale/ja_JP/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (そのようなファイルやディレクトリはありま せん)
openat(AT_FDCWD, "/usr/share/locale/ja.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (そのようなファイルやディレクトリはありません)
openat(AT_FDCWD, "/usr/share/locale/ja.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (そのようなファイルやディレクトリはあり ません)
openat(AT_FDCWD, "/usr/share/locale/ja/LC_MESSAGES/libc.mo", O_RDONLY) = 4
openat(AT_FDCWD, "/etc/pki/tls/openssl.cnf", O_RDONLY) = 4
openat(AT_FDCWD, "/etc/crypto-policies/back-ends/opensslcnf.config", O_RDONLY) = 5
openat(AT_FDCWD, "/etc/crypto-policies/back-ends/openssl.config", O_RDONLY) = 4
openat(AT_FDCWD, "/etc/pki/tls/certs/ca-bundle.crt", O_RDONLY) = 4
openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = 4

ENOENT (そのようなファイルやディレクトリは ありません)が大量に出力されているため、これについて補足します。

/usr/lib/locale/ja_JP.UTF-8/LC_*に関するエラーメッセージが多数出力されていますが、これはシステムが ja_JP.UTF-8 のロケールを探したものの、見つからなかったことを示しています。多くの Linux システムでは ja_JP.UTF-8 の代わりに ja_JP.utf8を利用しており、上記ログでも代わりとなる/usr/lib/locale/ja_JP.utf8/LC_*を読み込めており、問題はありません。

また、これ以外にも/root/.curlrcなどがありますが、これは個別設定を行う際に使用するオプションで存在しなくても問題ありません。

ここで大事なのは下記の部分になります。

openat(AT_FDCWD, "/etc/pki/tls/certs/ca-bundle.crt", O_RDONLY) = 4

/etc/pki/tls/certs/ca-bundle.crtの実態は/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pemへのリンクとなっており、このファイルが前述した収集した情報をファイルとしてまとめるによって作成されているファイルになります。

作成されるファイルについて

前述のstraceによって調査したファイルのディレクトリを参照してみます。

# tree --charset=C /etc/pki/ca-trust/extracted
/etc/pki/ca-trust/extracted
|-- README
|-- edk2
|   |-- README
|   `-- cacerts.bin
|-- java
|   |-- README
|   `-- cacerts
|-- openssl
|   |-- README
|   `-- ca-bundle.trust.crt
`-- pem
    |-- README
    |-- email-ca-bundle.pem
    |-- objsign-ca-bundle.pem
    `-- tls-ca-bundle.pem

javaやopenssl、pemといったディレクトリおよびファイルがあります。
使うコマンドによって期待する書式が違うため、書式ごとに作成されていると推測できます。

使用するコマンドによって期待する書式が違うという点はありますが、各々のコマンドはこれらを参照するようにコンパイルされています。

Javaの例をさらに見てみます。

Javaにおいては、認証局の証明書は、cacertsというファイルで管理されています。

# readlink -f $(dirname $(readlink -f $(which java)))/../lib/security/cacerts
/etc/pki/ca-trust/extracted/java/cacerts

推測通り/etc/pki/ca-trust/extracted配下のjavaディレクトリ配下のファイルを参照していることがわかります。

推測が正しいか、実行するコマンドを見てみる

Red Hat Enterprise Linux 9を例にコマンドの中身を見てみます。

# cat /usr/bin/update-ca-trust
#!/bin/sh

#set -vx

# At this time, while this script is trivial, we ignore any parameters given.
# However, for backwards compatibility reasons, future versions of this script must
# support the syntax "update-ca-trust extract" trigger the generation of output
# files in $DEST.

DEST=/etc/pki/ca-trust/extracted

# Prevent p11-kit from reading user configuration files.
export P11_KIT_NO_USER_CONFIG=1

# OpenSSL PEM bundle that includes trust flags
# (BEGIN TRUSTED CERTIFICATE)
/usr/bin/p11-kit extract --format=openssl-bundle --filter=certificates --overwrite --comment $DEST/openssl/ca-bundle.trust.crt
/usr/bin/p11-kit extract --format=pem-bundle --filter=ca-anchors --overwrite --comment --purpose server-auth $DEST/pem/tls-ca-bundle.pem
/usr/bin/p11-kit extract --format=pem-bundle --filter=ca-anchors --overwrite --comment --purpose email $DEST/pem/email-ca-bundle.pem
/usr/bin/p11-kit extract --format=pem-bundle --filter=ca-anchors --overwrite --comment --purpose code-signing $DEST/pem/objsign-ca-bundle.pem
/usr/bin/p11-kit extract --format=java-cacerts --filter=ca-anchors --overwrite --purpose server-auth $DEST/java/cacerts
/usr/bin/p11-kit extract --format=edk2-cacerts --filter=ca-anchors --overwrite --purpose=server-auth $DEST/edk2/cacerts.bin

引数(--format=pem-bundle--format=java-cacerts)や出力するパス(DEST=/etc/pki/ca-trust/extracted)ファイル名(tls-ca-bundle.pemcacerts)等から推測した内容と一致していそうです。

まとめ

プライベートCA(認証局)を配置する先にファイルを配置し、コマンドを実行することで、
様々なコマンド用のファイルを生成、それを参照するようになっています。

プログラムごとに信頼するプライベートCA(認証局)を切り替える要件がなければ、こちらの仕組みを利用し、サーバーに登録することで効率的に管理できるのではないでしょうか。

3

Discussion

ログインするとコメントできます