SVX日記
2023-08-29(Tue) Maveでデジタル署名付きメールを送信する
自作のメーラであるMaveに電子署名(S/MIME)機能を実装したい、というわけで、先の試行を経て、実装をするのである。
一応、MVCスタイル(Model/View/Controller)で記述してあるので、それに沿って実装する。といっても、署名とは、つまり添付ファイルの一種なわけで、ファイルを添付する処理を実行後に、さらに署名を添付する処理を実行する形になる。
MaveControllerの送信直前のファイルの添付処理の直後に、署名の添付処理を追加。MaveAccountモデルに、証明書ハンドリング処理を追加。MaveFolderモデルに、下書きメールを署名付きメールに上書きする処理を追加。MavePseudoMailモデルに、下書きメールから署名付きメールを生成する処理を追加。
diff --git a/mave.config.sample b/mave.config.sample
@@ -27,6 +27,9 @@ account[:POP_KEEP_TIME] = 24 * 60 * 60 # サーバに残す時間(秒)
account[:SMTP_SERVER] = 'smtp.example.com' # メール送信(SMTP)サーバ
+account[:PKCS12] = 't-yamada.p12'
+account[:PKCS12_PASSPHRASE] = 'yamada_p12_passphrase'
+
# インポート設定
#account[:IMPORT_COMMAND] = %Q!/usr/bin/find /home/old_user/mave.mails -name '*.eml' | grep -E '/Inbox/' | sed 's/\\/.*\\//& /'| sort -k 2 | sed 's/ //'!
diff --git a/mave_controller.rb b/mave_controller.rb
@@ -310,7 +310,8 @@ class MaveController
}
@models[:STATUS].log(['rcpt to=%s', rcpt_to.inspect]) if(debug = false) # 送信先のデバッグ
outbox_folder.enclose_attachments(mail) # 必要なら、メールに添付ファイルを入れ込む
+ outbox_folder.attach_sign(mail, account) # 必要なら、メールに署名する
result = smtp.ready(account.mail_from, rcpt_to) {|fw|
mail.header_each(nobcc = true) {|line|
fw.write(line + "\r\n")
diff --git a/mave_models.rb b/mave_models.rb
@@ -189,6 +189,7 @@ class MaveAccount < MaveBaseModel
attr_reader :smtp_server
attr_reader :import_command
attr_reader :hash_id
+ attr_reader :pkcs12
def initialize(params)
super
@@ -220,6 +221,8 @@ class MaveAccount < MaveBaseModel
@smtp_tls_verify= @account[:SMTP_TLS_VERIFY]
@smtp_tls_certs = @account[:SMTP_TLS_CERTS]
+ (it = @account[:PKCS12]) and @pkcs12 = OpenSSL::PKCS12.new(open(it).read, @account[:PKCS12_PASSPHRASE])
+
@import_command = @account[:IMPORT_COMMAND]
@hash_id = Digest::MD5.hexdigest(@account[:USER_ADDRESS])[0, 8]
@@ -1134,6 +1137,23 @@ class MaveFolder < MaveDirectory
@dirty += 1 ####
end
+ #------------------------------------------- MaveFolder ----
+ #
+ # メールに署名する
+ #
+ def attach_sign(source_mail, account)
+ return unless(account.pkcs12)
+ halfname = create_mailfile {|fh| # 一時ファイルに書き出す
+ MavePseudoMail.new({:CONFIGS => @configs, :MODE => :SIGN, :MAIL => source_mail, :ACCOUNT => account}).pseudo_each {|line|
+ fh.write(line + "\n")
+ }
+ }
+ mail = MavePseudoMail.new({:CONFIGS => @configs, :FILE => (xmail = File.new(path + '/' + halfname))})
+ overwrite_mail(xmail, source_mail)
+ delete(halfname) unless(RUBY_PLATFORM =~ /i.86-mswin32/) ####
+ @dirty += 1 ####
+ end
+
#------------------------------------------- MaveFolder ----
#
# 任意の内容のメールを内部生成する
@@ -1723,6 +1743,7 @@ class MavePseudoMail < MaveMail
:VIEW => method(:view_message_each),
:VIEW_RAW => method(:view_raw_message_each),
:ENCLOSE => method(:enclose_attachments_each),
+ :SIGN => method(:attach_sign_each),
}
@each_func = @formtype[params[:MODE]] || nil
@through_date = params[:THROUGH_DATE]
@@ -2135,6 +2156,46 @@ class MavePseudoMail < MaveMail
yield("--#{boundary}--")
end
+
+ #--------------------------------------- MavePseudoMail ----
+ #
+ # 署名付きメールの作成
+ #
+ def attach_sign_each
+ data = ''
+ @mail.header_each {|line|
+ data << (line + "\n") if(line =~ /^Content-Type:/)
+ data << (line + "\n") if(line =~ /^Content-Transfer-Encoding:/)
+ } if(@mail)
+ data << "\n"
+ @mail.raw_body_each {|line|
+ line =~ /^This is a multi-part/ and next
+ data << (line + "\n")
+ } if(@mail)
+ pkcs12 = @account.pkcs12
+ pkcs7 = OpenSSL::PKCS7.sign(pkcs12.certificate, pkcs12.key, data, [], OpenSSL::PKCS7::DETACHED)
+ smime = OpenSSL::PKCS7.write_smime(pkcs7) # 署名付きメール(crlf, lf が混じっている)
+
+ @mail.header_each {|line|
+ if(line =~ /^(\S+?):/)
+ header = $1.downcase
+ if(header == 'mime-version')
+ # smime 中にあるので捨てる
+ elsif(header == 'content-type')
+ # smime 中にあるので捨てる
+ elsif(header == 'content-transfer-encoding')
+ # マルチパートになるので捨てる
+ else
+ yield(line); header = false
+ end
+ else
+ yield(line) unless(header)
+ end
+ } if(@mail)
+ smime.split(/\r?\n/).each {|line|
+ yield(line)
+ }
+ end
end
#===============================================================================
自分で言うのも何だが、エラく美しく追加できた。既存のコードの修正は一切なく、最初から用意されていた実装すべき場所に、必要なコードを加えただけ、という感じ。実に気分がいいなぁ。これも、たぶん使わないんだけど。
実装が終わったところでテスト。テストは、ちゃんとS/MIMEが実装されているメーラを使って行う。手元のFedoraに標準添付のThunderbirdだ。Maveから送信して、Thunderbirdで受信させる。
署名付きのメールを受信すると、封筒マークに赤い×が付いたアイコンが現れ、それをクリックすると「デジタル署名が正しくありません」「メッセージは暗号化されていません」と出る。だが、どちらも期待通りだ。前者は、オレオレ認証局による署名だから、信頼されていないという意味だし、後者は、そもそも暗号化を実装していないし、実装するつもりもないから。
署名の実装自体が正しいことの確認に、本文を改ざんしてみた。すると「メッセージ内容に対して署名が一致しません」と出る。これも期待通り。逆に言えば、改ざん前の状態では正しく署名されていた、ということになるから。
オレオレ認証局の証明書をThunderbirdに追加するとこうなる。封筒マークから赤い×が消え「メッセージは署名されています」となる。これが正しい状態だ。認める認証局が署名した相手からのメールで、本文も改ざんされていないことが保証された状態。