55
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ruby の openssl ライブラリを使って、サーバ証明書やクライアント証明書を作成する

Last updated at Posted at 2015-05-03

はじめに

Ruby には OpenSSL を扱うためのライブラリ (openssl ライブラリ) が標準添付されていますが、リファレンスマニュアルだけでは使い方が少しわかりづらいので、標準的な使い方をまとめてみました。

以下のような人には役に立つかと思います。

  • シェルスクリプトよりも Ruby のスクリプトをよく使う人
  • SSL サーバ証明書の作成をよく行うが、それを自動化したい人
  • 証明書を操作する Ruby 製のアプリケーションを作成したい人
  • Rails で作った Web サービスにクライアント証明書による認証機能を追加したい人

プライベート CA (認証局) 構築

CA の役割については、こちらなどをご覧ください

  • ./ca_private_key.pem に CA の秘密鍵を、./ca.pem に CA 証明書を作成します
  • CA の名前は "Example CA" にします
  • CA の秘密鍵ファイルには "ca_passphrase" というパスフレーズを設定します
  • CA 証明書の有効期限は 1 年間に設定します
irb
require 'openssl'
OpenSSL::Random.seed File.read('/dev/random', 16)
digest = OpenSSL::Digest::SHA1.new

ca_passphrase = 'ca_passphrase' # CA の秘密鍵に設定するパスフレーズ (適切に設定してください)

# CA の情報を設定 (適切に設定してください)
issu = OpenSSL::X509::Name.new
issu.add_entry 'C' , 'JP'
issu.add_entry 'ST', 'Tokyo'
issu.add_entry 'DC', 'Minato-ku'
issu.add_entry 'O' , 'Example, Inc.'
issu.add_entry 'CN', 'Example CA'

# CA の秘密鍵/公開鍵を生成
issu_rsa = OpenSSL::PKey::RSA.generate 2048

# CA の秘密鍵を書き出し
File.open 'ca_private_key.pem', 'wb' do |f|
  f.write issu_rsa.export(OpenSSL::Cipher::Cipher.new('aes256'), ca_passphrase)
end

# CA 証明書を作成
issu_cer = OpenSSL::X509::Certificate.new
issu_cer.not_before = Time.now
issu_cer.not_after  = Time.now + 1*365*24*60*60 # 有効期限を 1 年後に設定
issu_cer.public_key = issu_rsa.public_key
issu_cer.serial  = 1
issu_cer.issuer  = issu
issu_cer.subject = issu
ex = OpenSSL::X509::Extension.new 'basicConstraints', OpenSSL::ASN1.Sequence([OpenSSL::ASN1::Boolean(true)])
issu_cer.add_extension ex
issu_cer.sign issu_rsa, digest
File.open 'ca.pem', 'wb' do |f|
  f.write issu_cer.to_pem
end

サーバ証明書を作る

サーバ証明書の役割については、こちらなどをご覧ください

  • secure.example.com 用のサーバ証明書を ./secure.example.com.crt に作成します
  • サーバの秘密鍵を ./secure.example.com.crt.key に作成します
  • 証明書の有効期限は 1 年間に設定します
irb
require 'openssl'
OpenSSL::Random.seed File.read('/dev/random', 16)
digest = OpenSSL::Digest::SHA1.new

ca_passphrase     = 'ca_passphrase'
domain = 'secure.example.com'

# CA 秘密鍵の再読み込み
issu_rsa = OpenSSL::PKey::RSA.new File.read('ca_private_key.pem'), ca_passphrase

# CA 証明書の再読み込み
issu_cer = OpenSSL::X509::Certificate.new File.read('ca.pem')
issu     = issu_cer.issuer

# サーバの情報を設定 (適切に設定してください)
sub = OpenSSL::X509::Name.new
sub.add_entry 'C' , 'JP'
sub.add_entry 'ST', 'Tokyo'
sub.add_entry 'DC', 'Minato-ku'
sub.add_entry 'O' , 'Example, Inc.'
sub.add_entry 'CN', domain

# サーバの秘密鍵/公開鍵を生成
sub_rsa = OpenSSL::PKey::RSA.generate 2048

# サーバの秘密鍵を書き出し
File.open "#{domain}.key", 'wb' do |f|
  f.write sub_rsa.export
end

# サーバ証明書を作成
sub_cer = OpenSSL::X509::Certificate.new
sub_cer.not_before = Time.now
sub_cer.not_after  = Time.now + 1*365*24*60*60 # 有効期限を 1 年後に設定
sub_cer.public_key = sub_rsa.public_key
sub_cer.serial  = 2
sub_cer.issuer  = issu
sub_cer.subject = sub
sub_cer.sign issu_rsa, digest
ex = OpenSSL::X509::Extension.new 'basicConstraints', OpenSSL::ASN1.Sequence([OpenSSL::ASN1::Boolean(false)])
sub_cer.add_extension ex
ex = OpenSSL::X509::Extension.new 'nsCertType', 'server'
sub_cer.add_extension ex
sub_cer.sign issu_rsa, digest
File.open "#{domain}.crt", 'wb' do |f|
  f.write sub_cer.to_pem
end

クライアント証明書を作る

クライアント証明書の役割については、こちらなどをご覧ください

  • クライアント証明書を ./client.pfx に作成します
  • 証明書の有効期限は 1 年間に設定します
irb
require 'openssl'
OpenSSL::Random.seed File.read('/dev/random', 16)
digest = OpenSSL::Digest::SHA1.new

ca_passphrase     = 'ca_passphrase'
client_passphrase = 'client_passphrase' # クライアント証明書に設定するパスワード (適切に設定してください)

# CA 秘密鍵の再読み込み
issu_rsa = OpenSSL::PKey::RSA.new File.read('ca_private_key.pem'), ca_passphrase

# CA 証明書の再読み込み
issu_cer = OpenSSL::X509::Certificate.new File.read('ca.pem')
issu     = issu_cer.issuer

# クライアントの情報を設定 (適切に設定してください)
sub = OpenSSL::X509::Name.new
sub.add_entry 'C' , 'JP'
sub.add_entry 'ST', 'Tokyo'
sub.add_entry 'DC', 'Minato-ku'
sub.add_entry 'O' , 'Example, Inc.'
sub.add_entry 'CN', 'Example Taro'

# クライアント用の秘密鍵/公開鍵を生成
sub_rsa = OpenSSL::PKey::RSA.generate 2048

# クライアント証明書を作成
sub_cer = OpenSSL::X509::Certificate.new
sub_cer.not_before = Time.now
sub_cer.not_after  = Time.now + 1*365*24*60*60 # 有効期限を 1 年後に設定
sub_cer.public_key = sub_rsa.public_key
sub_cer.serial  = 3
sub_cer.issuer  = issu
sub_cer.subject = sub
sub_cer.sign issu_rsa, digest
File.open 'client.pem', 'wb' do |f|
  f.write sub_cer.to_pem
end

# クライアント証明書を PKCS#12 形式で作成
sub_pkcs = OpenSSL::PKCS12.create client_passphrase, 'secure.example.com', sub_rsa, sub_cer, [issu_cer]
File.open 'client.pfx', 'wb' do |f|
  f.write sub_pkcs.to_der
end

CRL (証明書失効リスト) を作る

CRL の役割については、こちらなどをご覧ください

  • ./crl.pem に CRL を作成します
irb
require 'openssl'
digest = OpenSSL::Digest::SHA1.new

ca_passphrase = 'ca_passphrase'

# CA 秘密鍵の再読み込み
issu_rsa = OpenSSL::PKey::RSA.new File.read('ca_private_key.pem'), ca_passphrase

# CA 証明書の再読み込み
issu_cer = OpenSSL::X509::Certificate.new File.read('ca.pem')
issu     = issu_cer.issuer

# CRL を作成する
crl = OpenSSL::X509::CRL.new
crl.issuer      = issu
crl.last_update = Time.now
crl.next_update = Time.now + 1*24*60*60 # 次回アップデート予定日時を 1 日後に設定
crl.sign issu_rsa, digest
File.open 'crl.pem', 'wb' do |f|
  f.write crl.to_pem
end

CRL に追加する (クライアント証明書を無効にする)

  • 「クライアント証明書を作る」で作成したクライアント証明書を CRL に追加します
irb
require 'openssl'
digest = OpenSSL::Digest::SHA1.new

ca_passphrase     = 'ca_passphrase'

# CA 秘密鍵の再読み込み
issu_rsa = OpenSSL::PKey::RSA.new File.read('ca_private_key.pem'), ca_passphrase

# CRL の再読み込み
crl = OpenSSL::X509::CRL.new File.read('crl.pem')

# クライアント証明書の再読み込み
sub_cer = OpenSSL::X509::Certificate.new File.read('client.pem')

# CRL の更新
revoked = OpenSSL::X509::Revoked.new
revoked.serial = sub_cer.serial
revoked.time = Time.now
crl.add_revoked revoked
crl.last_update = Time.now
crl.sign issu_rsa, digest
File.open 'crl.pem', 'wb' do |f|
  f.write crl.to_pem
end

CRL から取り除く (クライアント証明書を有効にする)

  • 「クライアント証明書を作る」で作成したクライアント証明書を CRL から取り除きます
irb
require 'openssl'
digest = OpenSSL::Digest::SHA1.new

ca_passphrase     = 'ca_passphrase'

# CA 秘密鍵の再読み込み
issu_rsa = OpenSSL::PKey::RSA.new File.read('ca_private_key.pem'), ca_passphrase

# CRL の再読み込み
crl = OpenSSL::X509::CRL.new File.read('crl.pem')

# クライアント証明書の再読み込み
sub_cer = OpenSSL::X509::Certificate.new File.read('client.pem')

# CRL の更新
sub_cer_serial = sub_cer.serial.to_i
crl.revoked = crl.revoked.reject {|_revoked| _revoked.serial.to_i == sub_cer_serial}
crl.last_update = Time.now
crl.sign issu_rsa, digest
File.open 'crl.pem', 'wb' do |f|
  f.write crl.to_pem
end

Apache に設定する

CentOS の場合を例にしています。その他のディストリビューションをお使いの場合は、パスが異なる場合があるのでご注意ください。

ファイルを整理する

cmd
# mv ca.pem /etc/pki/CA/
# mv secure.example.com.crt /etc/pki/CA/certs/
# mv secure.example.com.key /etc/pki/CA/private/
# mv crl.pem /etc/pki/CA/

設定ファイルを編集する

設定ファイルに以下を追記してください

/etc/httpd/conf.d/ssl.conf
# サーバ証明書の設定
SSLCertificateFile /etc/pki/CA/certs/secure.example.com.crt # サーバ証明書
SSLCertificateKeyFile /etc/pki/CA/private/secure.example.com.key # サーバ秘密鍵

# クライアント証明書の設定
SSLCACertificateFile /etc/pki/CA/cacert.pem # CA 証明書
SSLVerifyClient require # クライアント証明書を要求する設定
SSLVerifyDepth  1

# CRL の設定
SSLCARevocationFile /etc/pki/CA/crl.pem

注: プライベート CA により署名されたサーバ証明書なのでブラウザ側でプライベート CA の証明書を信頼する設定を行う必要があります。

Apache を再起動する

cmd
# service httpd restart

注: CRL の更新をした場合も Apache を再起動する必要があります

クライアント証明書のインストール

クライアント証明書のインストールはブラウザ毎に行う必要があります

Mac で Google Chrome を使う場合

  1. 「クライアント証明書を作る」で作成した client.pfx をダブルクリックする
  2. パスワードの入力を求められるので "client_passphrase" と入力し、キーチェーンアクセスに登録する
  3. Web サーバにアクセスすると証明書の選択を求められるので、インストールした証明書を選択する

その他の OS、ブラウザを使う場合

サイボウズさんのサイトによくまとまっているので、こちらを参考に設定を行ってください

終わりに

私自身は何かを自動処理させる場合、シェルスクリプトよりも Ruby のスクリプトで書くことが多いので、今回 Ruby の openssl のライブラリの使い方がわかって非常に楽になりました。"シェルスクリプトより Ruby!"という方は是非ご活用ください!!

55
51
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
55
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?