Let's Encryptで更新しているワイルドカード証明書。

他のサーバにも適用するのを更に簡便にするため、自動化するスクリプトを出力しました。

動作を確認した環境

  • Ubuntu 20.o4
  • Apache2.4

要件

1. 証明書と鍵ペアがあるディレクトリを引数にしてコマンドを実行
 → ファイルがなければエラーを返す
 → ファイルが次の形式でなくてもエラーを返す
 ・hoge.example.com.crt.$(date +%Y%m) ← hoge.example.com.crtの部分を変数化
 ・hoge.example.com.key.$(date +%Y%m) ← hoge.example.com.keyの部分を変数化
2. 引数内のディレクトリのcrt.$(date +%Y%m)を対象に、以下のコマンドを実行する
  2.1 次のハッシュ値をそれぞれ参照し合っていれば後続
  openssl x509 -pubkey -in hoge.example.com.crt.$(date +%Y%m) -noout | openssl md5
  openssl pkey -pubout -in hoge.example.com.key.$(date +%Y%m)  | openssl md5
  2.2 次のハッシュ値をそれぞれ参照し合っていれば後続
  openssl x509 -issuer_hash -noout -in hoge.example.com.crt.$(date +%Y%m)
  sed -n -e'1d' -e'/BEGIN/,$p' hoge.example.com.crt.$(date +%Y%m) | openssl x509 -subject_hash -noout
3. openssl x509 -noout -dates -subject -in hoge.example.com.crt.$(date +%Y%m)
 のnotAfterを参照し、
 「ドメイン『hoge.example.com』有効期限:notAfterで出力した値 で更新します。よろしいですか?」
 を出力。y/nで後続判断
4. 整合性を確認したファイルを以下のディレクトリにコピー
 ・hoge.example.com.crt.$(date +%Y%m) → /etc/certs
 ・hoge.example.com.key.$(date +%Y%m) → /etc/private
 コピー完了の出力をする
5. 証明書のシンボリックリンクを貼り替える
  5.1 cd /etc/certs
  5.2 sudo ln -sf hoge.sample.com.crt.$(date +%Y%m) hoge.sample.com.crt
  5.3 1のコピーとリンクしたファイルのハッシュ値が同じであることを確認
6. 秘密鍵のシンボリックリンクを貼り替える
  6.1 cd /etc/private
  6.2 sudo ln -sf hoge.sample.com.key.$(date +%Y%m) hoge.sample.com.key
  6.3 1のコピーとリンクしたファイルのハッシュ値が同じであることを確認
7. 「Webサービスを再起動しますか?」y/n
 → yの場合は systemctl restart apache2.serviceして後続
 → nの場合はスクリプトを抜ける
8. 「証明書更新が完了しました」のメッセージを出してスクリプト終了

結果

出て来たのはこちら。

ssl_update.sh

#!/bin/bash

# 変更するドメイン
domain="hoge.example.com"

# OpenSSLのパス
openssl_path="/usr/local/ssl/bin/openssl"

# 証明書ファイルの格納位置
cert_dir="/etc/certs"

# 秘密鍵ファイルの格納位置
private_dir="/etc/private"

# 引数の数を確認
if [ "$#" -ne 1 ]; then
  echo "Usage: $0 <directory>"
  exit 1
fi

# ディレクトリが存在するか確認
directory="$1"
if [ ! -d "$directory" ]; then
  echo "Error: Directory not found"
  exit 1
fi

# 証明書と鍵のファイルパスを設定
cert_file="$directory/$domain.crt.$(date +%Y%m)"
key_file="$directory/$domain.key.$(date +%Y%m)"

# ファイルの存在を確認
if [ ! -e "$cert_file" ] || [ ! -e "$key_file" ]; then
  echo "Error: Certificate or key file not found"
  exit 1
fi

# ハッシュの確認
cert_hash=$("$openssl_path" x509 -pubkey -in "$cert_file" -noout | "$openssl_path" md5)
key_hash=$("$openssl_path" pkey -pubout -in "$key_file" | "$openssl_path" md5)

if [ "$cert_hash" != "$key_hash" ]; then
  echo "Error: Certificate and key do not match"
  exit 1
fi

issuer_hash=$("$openssl_path" x509 -issuer_hash -noout -in "$cert_file")
subject_hash=$(sed -n -e '1d' -e '/BEGIN/,$p' "$cert_file" | "$openssl_path" x509 -subject_hash -noout)

if [ "$issuer_hash" != "$subject_hash" ]; then
  echo "Error: Issuer hash and subject hash do not match"
  exit 1
fi

# 有効期限の確認
expiration_date=$("$openssl_path" x509 -noout -dates -subject -in "$cert_file" | grep "notAfter" | cut -d "=" -f 2)

echo "ドメイン『$domain』有効期限:$expiration_date で更新します。よろしいですか? (y/n)"
read response

if [ "$response" != "y" ]; then
  echo "証明書更新がキャンセルされました"
  exit 1
fi

# ファイルをコピー
sudo cp "$cert_file" "$cert_dir"
sudo cp "$key_file" "$private_dir"

# シンボリックリンクを貼り替え
cd "$cert_dir"
sudo ln -sf "$(basename $cert_file)" "$domain.crt"
cd "$private_dir"
sudo ln -sf "$(basename $key_file)" "$domain.key"

# リンクとコピーのハッシュが一致しているか確認
if [ "$("$openssl_path" x509 -pubkey -in "$cert_dir/$domain.crt" -noout | "$openssl_path" md5)" != "$cert_hash" ]; then
  echo "Error: Certificate hash mismatch after linking"
  exit 1
fi

if [ "$("$openssl_path" pkey -pubout -in "$private_dir/$domain.key" | "$openssl_path" md5)" != "$key_hash" ]; then
  echo "Error: Key hash mismatch after linking"
  exit 1
fi

echo "Webサービスを再起動しますか? (y/n)"
read restart_response

if [ "$restart_response" == "y" ]; then
  sudo systemctl restart apache2.service
  echo "Webサービスが再起動しました"
fi

echo "証明書更新が完了しました"
exit 0

作成後、

sudo chmod 744 ssl_update.sh

使う前の準備

  1. 変数を適度に修正します。
  2. 任意のディレクトリにドメインの条件に沿った証明書を格納します。

使い方

sudo bash ssl_update.sh [証明書を格納したディレクトリへのパス]

この後、各種を確認して

証明書更新が完了しました

まで出してくれます。