SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|

2023-08-28(Mon) Rubyでデジタル署名付きメールを生成する

  いまだどうも気分が晴れない日が続いている。が、何かに取り組んでいないと、ますます気分が沈むので、とりあえずさほど必要でもないようなプログラミングに取り組むのであった。先日に引き続き、自作のメーラであるMaveに電子署名(S/MIME)機能を搭載するのである。

  実はだいぶ前に試しに搭載してみたことがあり、ほぼ動いていたのだが、あくまで試しだったので、コードをクリンナップしないまま放置していた。今回、それをちゃんと搭載し直そうというわけである。

  まず、最初に必要なのが「メール署名用の証明書と秘密鍵」の生成だ。自分は「Easy-RSA」が好きなのだが「メール署名用証明書」の情報が見当たらない。まぁ、その用途は「サーバ証明書」よりもだいぶ少ないわけで、さらにそれを「オレオレ証明書」で済まそうというのだから、そら見当たらないわな。でも、できる。今回、最新版のEasy-RSAを使うために、素のFedora38のコンテナを上げて作業した。いやぁ、コンテナって本当にいいもんですね。方法はざっと以下だ。

・作業ディレクトリを準備する
# useradd t-yamada
# su - t-yamada
$ mkdir make_email_cert
$ cd make_email_cert
$ cp -a /usr/share/easy-rsa .
$ cd easy-rsa/3.1.5 
 
・環境を初期化する
$ ./easyrsa init-pki
 
・新たに認証局を立てる(認証局証明書/秘密鍵を生成する)
$ ./easyrsa build-ca 
Enter New CA Key Passphrase: ca_passphrase
Confirm New CA Key Passphrase: ca_passphrase
Common Name (eg: your user, host, or server name) [Easy-RSA CA]: OreOreSign
 
・認証局証明書/秘密鍵の内容を確認する
$ ./easyrsa show-ca
$ openssl x509 -in ./pki/ca.crt -text
$ openssl rsa -in ./pki/private/ca.key -text
Enter pass phrase for ./pki/private/ca.key: ca_passphrase
 
・メール用の証明書署名要求(秘密鍵)を生成する
$ ./easyrsa --dn-mode=org gen-req t-yamada ★ --dn-mode=org がキモ
Enter PEM pass phrase: yamada_key_passphrase
Verifying - Enter PEM pass phrase: yamada_key_passphrase
Country Name (2 letter code) [US]: JP
State or Province Name (full name) [California]: Tokyo
Locality Name (eg, city) [San Francisco]: Chiyoda
Organization Name (eg, company) [Copyleft Certificate Co]: Example Corp.
Organizational Unit Name (eg, section) [My Organizational Unit]: . ★ OU は略す
Common Name (eg: your user, host, or server name) [t-yamada]: Taro Yamada
Email Address [me@example.net]: t-yamada@example.com
Serial-number (eg, device serial-number) []:
 
・証明書署名要求(秘密鍵)の内容を確認する
$ ./easyrsa show-req t-yamada
$ openssl req -in ./pki/reqs/t-yamada.req -text
$ openssl rsa -in ./pki/private/t-yamada.key -text
Enter pass phrase for ./pki/private/t-yamada.key: yamada_key_passphrase
 
・認証局として証明書署名要求に署名する
$ ./easyrsa sign-req email t-yamada ★ email がキモ
subject=
    countryName               = JP
    stateOrProvinceName       = Tokyo
    localityName              = Chiyoda
    organizationName          = Example Corp.
    commonName                = Taro Yamada
    emailAddress              = t-yamada@example.com
  Confirm request details: yes   
Enter pass phrase for /home/t-yamada/make_email_cert/easy-rsa/3.1.5/pki/private/ca.key: ca_passphrase
 
・署名済みのメール用の証明書の内容を確認する 
$ ./easyrsa show-cert t-yamada
$ openssl x509 -in ./pki/issued/t-yamada.crt -text
 
・PKCS#12(証明書と秘密鍵などをまとめた形式)に変換する
$ ./easyrsa export-p12 t-yamada
Enter pass phrase for /home/t-yamada/make_email_cert/easy-rsa/3.1.5/pki/private/t-yamada.key: yamada_key_passphrase
Enter Export Password: yamada_p12_passphrase
Verifying - Enter Export Password: yamada_p12_passphrase
 
・PKCS#12の内容を確認する 
$ openssl x509 -in ./pki/private/t-yamada.p12 -text
Enter pass phrase for PKCS12 import pass phrase: yamada_p12_passphrase
$ openssl rsa -in ./pki/private/t-yamada.p12 -text
Enter pass phrase for PKCS12 import pass phrase: yamada_p12_passphrase

  ひとつめに重要なのが「./easyrsa --dn-mode=org gen-req t-yamada」の「--dn-mode=org」。これを指定しないと、証明書内のSubjectにメールアドレスが入らず「メール署名用の証明書」にならない。もうひとつ重要なのが「./easyrsa sign-req email t-yamada」の「email」。これを指定しないと、証明書内の「Extended Key Usage」に「E-mail Protection」が入らず、やはり「メール署名用の証明書」にならない。

  で、上記の作業の成果物が「pki/private/t-yamada.p12」だ。オレオレながら、認証局の署名済みの「メール署名用の証明書」である。普通にシステムの証明書ストアに置いたりしてメーラから使えるものだ。だが、今回はS/MIMEを自分で実装するのが目的だから、普通じゃないのである。以下のようなコードで扱うのである。

require 'openssl'
 
pkcs12 = OpenSSL::PKCS12.new(open('t-yamada.p12').read, 'yamada_p12_passphrase') 
pkcs12.certificate.subject.to_a.each {|name|
    puts("Subject: %s:\n\t%s" % name)
}
pkcs12.certificate.extensions.each {|ext|
    puts("Extension: %s:\n\t%s" % [ext.oid, ext.value])
}

  こんな結果が得られる。

Subject: C:
	JP
Subject: ST:
	Tokyo
Subject: L:
	Chiyoda
Subject: O:
	Example Corp.
Subject: CN:
	Taro Yamada
Subject: emailAddress:
	t-yamada@example.com
Extension: basicConstraints:
	CA:FALSE
Extension: extendedKeyUsage:
	E-mail Protection
Extension: keyUsage:
	Digital Signature, Non Repudiation, Key Encipherment

  さらに、pkcs12インスタンスをPKCS7クラスに食わせれば……

data = 'This is Mail text.'
pkcs7 = OpenSSL::PKCS7.sign(pkcs12.certificate, pkcs12.key, data, [], OpenSSL::PKCS7::DETACHED)
smime = OpenSSL::PKCS7.write_smime(pkcs7)
puts(smime)

  ……いとも簡単に、以下のような署名付きのメール(部分)が生成できてしまうのであった。

MIME-Version: 1.0
Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha-256"; boundary="----0123ABCD0123ABCD0123ABCD0123ABCD"
 
This is an S/MIME signed message
 
------0123ABCD0123ABCD0123ABCD0123ABCD
This is Mail text.
------0123ABCD0123ABCD0123ABCD0123ABCD
Content-Type: application/x-pkcs7-signature; name="smime.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
 
XXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxx
xxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXX
 :
XXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxxXXXXxxxx
xxxxXXXXxxxxXXXXxxxxXXXXxxxxXX==
 
------0123ABCD0123ABCD0123ABCD0123ABCD--

  メール本文は「'This is Mail text.'」の部分だ。ヘッダまで自動生成ですぞ。うぅむ、こんなに簡単にできてしまうと、ちょっと過保護じゃね? と思うくらいだな。