未完放流

終わりなど無い、有るのは試練だけだ……

Ubuntu 18.04 LTS で OpenSSL を使ったプライベート認証局&証明書を作る

触れることは無いだろうと思っていた SSL ですが、 LAN 内であっても Web Application が https 化しないと動いてくれない時代になったので仕方なく調べました。 メジャーな技術なので資料は豊富ですが、 玉石混交というか数を読み込まないと分からないことが多かったです。 コマンドをコピペしてOKと言う具合にはいきませんでした。

調べていて気になったところ

  • CA スクリプト: プライベート(オレオレ)認証局のためのヘルパースクリプト 試したが完全には動かなかった。 現在は動かないかもしれない。 メンテの状態が怪しい。 sh 版と perl 版が存在するが、現在は廃止されているオプションを使用している可能性。
  • シンプルな自己署名証明書は使えない: 警告が返ってきたり、Chrome では事実上 SAN (subjectAltName) が必須。 近年では地味に一番痛い変更だったかも。 認証局も必須になる場合もあった。
  • 拡張子が変わった: 公式マニュアルなども pem という拡張子で説明されているが、 現在は key, csr, crt などを使う方が一般的では?
  • OpenSSL のコマンド: 挙動が複雑かつ多機能。 証明書の作成以外のこともできたりする。 分離して別のソフトにしても良い気もする。 同じことでも複数の方法があったりする。 実は暗黙?に設定ファイルを参照している。

本稿の方針

個人がLAN内でSSLサーバ証明書が必要なソフトウェアの動作検証することを目的としております。 サーバーとクライアント(web ブラウザー)から文句をとりあえず言われないというレベルです。 会社など複数人が関わる運用を前提としておりませんので、Permission の設定などには触れません。

使用するコマンドの見た目は、できるだけ簡潔にがモットー

確認環境

  • ( Ubuntu 16.04, 18.04 LTS or Linux Mint 19.1 ) on ( LXC/LXD, WSL or 実機 )
  • OpenSSL version 1.1.0g

SSL によるサーバー等の本人確認

Web サイトなどの確認よく使われる SSL 証明書ですが、第三者による確認が基本のアイデアになります。

A さんが顔をしらない B さんと会おうとして、信用できる共通の友人 C さんに確認してもらうのと大体一緒なのではないかと思います。 A さんがクライアント(多くは web ブラウザー)とすると、B さんがサーバー、C さんが認証局にあたります。

この辺りの丁寧な説明は検索すると山のようにあるから省略。

認証された(署名付き)証明者ができるまで

OpenSSL での作業の流れは、「秘密鍵 (key) -> 証明書署名要求 (csr) -> 認証された証明書 (crt) 」の順にファイルを作成します。 () 内は拡張子

ターミナルでの操作は、 openssl <sub command> で行う。 -in, -out オプションは標準入出力で代用もできる。 本稿では "sub command" と表現しましたが、 ヘルプ openssl help では "Standard commands" と書かれており現在48個あるようです。 その内の3つ "genrsa, req, ca" を使って認証局と証明書を作ります。

$ openssl genrsa -out hoge.key                 # 秘密鍵の生成
$ openssl req -new -key hoge.key -out hoge.csr # 証明書署名要求
$ openssl ca -in hoge.csr -out hoge.crt        # 認証された証明書の発行

このようにコマンドを打つと 標準設定ファイル(/etc/ssl/openssl.cnf)で決められた変数を暗黙に読み込んで実行します。 オプションを駆使して一つに纏めたりもできますが、手順の理解を優先。

openssl req を実行すると対話式で質問され、会社などの組織の情報を入力を促されます。 プライベート使用なら厳密である必要はないですが、Common Name のみ一意的になるようにしましょう。

使用オプションの解説

  • -new : 新しい証明書署名要求を作る。忘れると作れない。
  • -key : 証明書の秘密鍵ファイルを指定。

openssl ca と実行すると証明書の確認と管理対象にするか質問されます。 認証局を作っていない状態では機能しません。

詳細を知るにはmanを見るのがお勧めです。 比較的古いコマンドのためか、man genrsa, man req, man ca など openssl と指定する必要がないのがちょっとした驚き。

認証局の作成

認証局も基本的には秘密鍵と証明書から成っていますが、他者を認証するため管理する仕組みを持つのが特徴です。 具体的には、署名した証明書の台帳(データベース)を持ちます。 認証と対になる失効もできます。

付属するCAと言うスクリプトで作成できることになっていますが、私は止まりました。 行われていることは複雑ではないので似たものを作成していきます。

一度で作業を終わらしたいものですがコマンドの作業量が意外と多いです。 条件に合わせて何度か作り直す必要が出てくるかもしれないのでスクリプトにしておいた方が便利かと思います。 openssl のコマンドはデフォルトでは /etc/ssl/openssl.cnf が読み込まれますが、 "OPENSSL_CONF" という変数を使い独自の設定が読み込まれるようにします。

以下の2つのファイルを作成します。

  • createCA : 認証局作成用スクリプト
  • original_opennssl.cnf : 標準設定で使われるファイルの拡張子を現在使われているものに変更。

認証局作成用スクリプトの作成

$ vi createCA

伝統的な作法にならい demoCA という名前の認証局を作ります。

#!/bin/sh

CA="demoCA"

mkdir $CA
mkdir $CA/certs      # 認証局の証明書を入れる
mkdir $CA/private    # 認証局の秘密鍵を入れる
mkdir $CA/crl        # certificate revocation list 証明書失効リスト
mkdir $CA/newcerts   # 発行した証明書の pem ファイルを入れる

## --- 台帳(簡易DB)、証明書管理用ファイルを作成

echo 01 > $CA/serial     # 証明書のシリアルナンバー
echo 01 > $CA/crlnumber  # 失効操作時に番号を記録するらしい?
touch $CA/index.txt      # 証明書の台帳
touch $CA/index.txt.attr # 証明書の重複を許すか。古い資料には無いが必須

## --- 認証局の作成

export OPENSSL_CONF=original_openssl.cnf # 変更に合わせた設定の読み込み

openssl genrsa -out $CA/private/ca.key
openssl req -new -key $CA/private/ca.key -out $CA/ca.csr
openssl ca -in $CA/ca.csr -out $CA/ca.crt -selfsign -extensions v3_ca

ca コマンドのオプション

  • -selfsign : 自己署名。ルート証明局を作成している。
  • -extensions : ここでは、設定ファイルの [ v3_ca ] というセクションに従いを拡張部分を使用している。

独自の設定ファイルの作成と編集

/etc/ssl/openssl.cnf を作業ディレクトリにコピーする。

$ cp /etc/ssl/openssl.cnf original_openssl.cnf
$ vi original_openssl.cnf

設定ファイルに書かれた拡張子を最近使われているものに変更変更。 ファイルを開いたついでに、[ v3_ca ] のセクションに移動し認証局CA : true になっているのを確認しておきましょう。

~~~
[ CA_default ]
dir = ./demoCA                    # 自分がつけた名前
~~~
certificate = $dir/ca.crt         # 元は cacert.pem
~~~
private_key = $dir/private/ca.key # 元は cakey.pem
~~~
~~~
[ v3_ca ]


# Extensions for a typical CA
~~~
basicConstraints = critical,CA:true

スクリプトの実行

設問に適切に答えていけば、動くはず。

$ sh createCA

ca 自身の証明書発行と発行と登録が求められます。 特に X509v3 extensions が CA:TRUE になっているのを確認して問題が無ければ、署名とデータベースへコミットしましょう。

きちんと出来た後にディレクトリの中身を覗いてみるとファイルが変化しているのが分かります。

  • intex.txt : 1行で1つの証明書、実は6列の情報になっている*1
    1. 状態 (V=valid, R=revoked, E=expired)
    2. 有効期限 YYMMDDHHMMSSZ format
    3. 失効日時 空ならば失効していない
    4. シリアル 16進表記
    5. ファイル名か "unknown" が入る
    6. distinguished name
  • index.txt.attr : "unique_subject = yes" の1行だけ書かれる。
  • serial : 1だけ足された数字になる。
  • crlnumber : 変わらず

newcerts ディレクトリには、ca.crt と同じ内容の 01.pem というファイルができている。 シリアルナンバーと同じ証明書が入っているらしい。

サーバー証明書の発行

色々なところで書かれておりますが、Chrome version 58 から SAN(subjectAltName) が事実上の必須になりました。 具体的にはサーバー証明書ごとに、拡張部分 subjectAltName を追記する必要ががあります。 現在 CLI の対話で個別に設定することができないので、やはりスクリプト化しておいた方がよいと思います。

openssl caにて -extfile という拡張部分を外部ファイルから読み取るオプションを使って対処する方法を取りたいと思います。 このオプションを使うと拡張部分をファイルの内容に全て上書きされてしまうため、 予め標準設定に追記する形にしています。 名前に決まりはありませんが、ここでは ext_v3 といファイルを用意し以下のように記述します。

subjectAltName は複数登録することができます。DNS、IPの組み合わせて使うこともできます。 <<YourDomain>> に、サーバーの名前、使用ドメインなど適切に入力してください。 私は個人で試すだけでしたので、IPのみで使いましたが問題ありませんでした。

basicConstraints=CA:FALSE
nsComment="OpenSSL Generated Certificate"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
subjectAltName=DNS:<<YourDomain>>,IP:XXX.XXX.XXX.XXX

OPENSSL_CONF で先ほど作成した認証局を使うようにしてあります。

#!/bin/sh

DIR="server"

NAME="fuga"
mkdir $DIR

export OPENSSL_CONF=original_openssl.cnf

openssl genrsa -out $DIR/$NAME.key
openssl req -new -key $DIR/$NAME.key -out $DIR/$NAME.csr
openssl ca -in $DIR/$NAME.csr -out $DIR/$NAME.crt -extfile $DIR/ext_v3

ちなみに -extfile は最後に持ってこないと、証明書の中が空になるようです。 req, ca でこれまでと同じように質問されます。適当に答えて問題なければ進めしょう。 特に subjectAltName が意図したとおりになっているか確認しておきましょう。

もしも上手くいかない場合、コマンドを直接打って確認しましょう。

$ openssl ca -keyfile demoCA/private/ca.key -cert demoCA/ca.crt \
 -in server/fuga.csr -out server/fuga.crt -extfile server/ext_v3

設定ファイルを使わない場合は、省略されている入力を明示的にする必要があります。

認証局の作成後と同じように、demoCA の中身を確認してみると変化していることが分かります。

証明書の失効

プライベート使用の場合、あまりないかもしれませんが証明書の失効を試してみましょう。 index.txt を見てみましょう。

$ cat demoCA/index.txt
V       200301074053Z           01      unknown /C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=hoge
V       200301090816Z           02      unknown /C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=fuga

シリアル02サーバー証明書を失効してみましょう。

$ export OPENSSL_CONF=original_openssl.cnf
$ openssl ca -revoke demoCA/newcerts/02.pem

再び、index.txt を確認。

$ cat demoCA/index.txt
V       200301074053Z           01      unknown /C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=hoge
R       200301090816Z   190302144840Z   02      unknown /C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=fuga

きちんと失効されているようです。

*1:こちらを参照しました Appendix B: CA Database — OpenSSL PKI Tutorial