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|05|06|07|

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に追加するとこうなる。封筒マークから赤い×が消え「メッセージは署名されています」となる。これが正しい状態だ。認める認証局が署名した相手からのメールで、本文も改ざんされていないことが保証された状態。

  実際には、この署名の確認は相手側によって行われるものなので、オレオレ証明書を使っている限りはバツということになるが、実装としてはちゃんとS/MIMEが実現できているといえる。試しに会社から発行されているちゃんとした証明書を使ってテストしても、正しい結果が得られている。

  つうわけで、リポジトリにコミットして完了である。