heyheytower

日々のメモです。誰かのお役に立てれば幸いです。

Internet of Aquarium : ESP-WROOM-02 と ThingSpeak を利用して

Aquarino(旧ケース)
Pic.0 Aquarino外観 (新しいケースのバージョンもあります。写真はこちらから)

目次

目的

アクアリウムを始めたので、表題の方法で適正水温監視を行う。

2016_aquarium_01

監視方法

  1. Arduino で水温(温度)測定
  2. ThingSpeak*1 に水温データを送信しアーカイブ・グラフ化
  3. 水温監視を ThingSpeak のアプリ "React"*2 で実行
  4. 適正温度を外れた場合は Twitter へツイート
  5. ThingSpeak からのツイートを IFTTT*3 で監視し警告メールを送信

作成

Arduino で水温(温度)測定

まず母体として、"ESP-WROOM-02" を "無線付Arduino" として使いました。回路図・ソースコードはまとめて後述しています。

また、本エントリで作成したものについては "Aquarino" と名付けましたので、以後そのように記載します。

温度センサ

手元に MCP9700*4 がありましたので、下記を参考に利用しました。

Arduinoで遊ぼう -温度センサIC、MCP9700を使って温度を測る - なんでも作っちゃう、かも。

注意としては、5V電源とGNDを繋ぎ間違えると一発で壊れてしますので、ご用心を。(壊れてからは、TOUT の許可されている電圧上限 1V の値を受け取っていたので、TOUT に負荷がかかっていたかもしれません^^; 自分は2つダメにしてしまいました…)

温度・通信状態表示のため LCD

こちらも手元に "LCDキャラクタディスプレイモジュール(16×2行バックライト無)"*5 がありましたので、下記を参考に利用しました。

【Arduino】SC1602B(16桁LCDディスプレイ)を使ってみる。 : Blazing Azuki's Blog

また、LCD のために GPIO2 をデジタル出力として利用しました。( GPIO0,2,15 をデジタル出力として使う場合は、実行モードを妨げないようにしないといけません。*6 )

省電力のための "Deep-Sleepモード" 利用

下記がとても参考になりました。

ESP8266の真骨頂Deep-Sleepモードの使い方 - Qiita

今回の場合ですと ESP-WROOM-02 が待機状態でも LCD は通電したままですので、LCD の最後に表示した状態が持続します。

ThingSpeak への接続に HTTPS を利用

"WiFiClientSecure class" を利用して対応し、証明書(フィンガープリント)確認なども行うようにしました。

GETリクエストは https://api.thingspeak.com/update?api_key=XXXXXXXXXX&field2=20 という感じです。

デバッグ用のシリアルモニタへの出力

リリースバージョンではコメントアウトしてますが、参考に載せておきます。

Connecting to XXXXmask-networkXXXX
.......
WiFi connected
IP address : 192.168.1.123

Connecting HOST   : api.thingspeak.com
TOUT from MCP9700 : 796 mV (count 0)
TOUT from MCP9700 : 778 mV (count 1)
TOUT from MCP9700 : 779 mV (count 2)
TOUT from MCP9700 : 779 mV (count 3)
TOUT from MCP9700 : 780 mV (count 4)
TOUT Average      : 782.40 mV ( 24.84 degress C)
Requesting URL    : /update?api_key=XXXXXXXXXXXXXXX&field1=24.84
closing connection.

ソースコード

下記で公開しております。

ハードウェア作成のための準備

EAGLE*7 で回路図を作成し、パーツ郡は下記を利用しました。

始めての EAGLE 利用なので間違った使い方などあるかと思いますが、下記が本エントリで作成した ESP-WROOM-02 を含む回路図です。

Aquarino 回路図
Pic.1 回路図

ユニバーサル基板でのパーツ配置を自動でできないかと思い、途中まで検討してみました。

Aquarino パターン図の検討
Pic.2 パターン図の検討

しかしながら回路も小規模でユニバーサル基板利用なので、結局自分で考えた方が早そうということで配置を検討したメモが下記です。

Aquarino ユニバーサル基板実装案
Pic.3 ユニバーサル基板実装案

ハードウェア作成

Pic.3 のメモを参考に実際に実装した基板が下記です。(裏側は試行錯誤があったためクオリティが酷いですが…)

Aquarino ユニバーサル基板実装(表)
Pic.4 ユニバーサル基板実装(表)

Aquarino ユニバーサル基板実装(裏)
Pic.5 ユニバーサル基板実装(裏)

動作確認

Movie.1 "Aquarino" の動作(字幕に動作説明あり)

ThingSpeak に水温データを送信しアーカイブ・グラフ化

グラフ設定として、Y軸の最小・最大値を設定しました。

Graph.1 水槽の温度監視

水温監視を ThingSpeak のアプリ "React" で実行 & 適正温度を外れた場合は Twitter へツイート

適正水温の監視は、Arduino 側で行うのではなく、ThingSpeak のアプリ "react" が目的に合っていそうだったのでこちらを利用しました。

別解として "ThingTweet"*13 を利用する方法も考えたのですが、適性水温の変更やツイート内容を変更するためには "Arduino" のコードを編集した後に再書き込みが必要であり、これが面倒だと考えて採用を見送りました。

ThingSpeak からのツイートを IFTTT で監視し警告メールを送信

IFTTT で監視を行い、自分の Twitter アカウントでハッシュタグ "#thingspeak" が付加されてツイートされたら、指定アドレスへ警告メールを送信する設定を行いました。

IFTTT Recipe: If new tweet by target user with hashtag #thingspeak, then send an email from my gmail address connects twitter to gmail

TODO

水温低下時は別途ヒーターの設備があるのですが、水温上昇時に対処する設備がありません。
"Aquarino" ではシリアル通信のための RX・DX が空いているので、それで "水槽用冷却ファン" を制御しようと考えています。

付録

秋月さんで販売している "ESP-WROOM-02"*14 の "EAGLEライブラリ"

"ikesato" さんの "ESP-WROOM-02" の "EAGLEライブラリ"*15 を基に、見出しのパーツを作成しました。

下記にパーツイメージ画像と共に公開してますので、ご確認のうえご利用いただけましたらと。

制作費用

table.1 "Aquarino" 制作費用(はんだ等の消耗品は除く)

製品 単価 値段
ESP-WROOM-02(DIP秋月電子通商) 650 1 650
LCDキャラクタディスプレイモジュール(16×2行バックライト無) 500 1 500
温度センサーIC MCP9700 38 1 38
片面ユニバーサル基板(D) 30 1 30
低損失三端子レギュレータ TA48M033F(SQ) 100 1 100
カーボン抵抗1/4W 10kΩ 1 3 3
カーボン抵抗1/4W 20kΩ 1 1 1
半固定ボリューム(20kΩ) 40 1 40
DCジャック(DIP) 100 1 100
ブレッドボード・ジャンパーコード(オス-メス)(10本入) 220 1 220
電線50cm 20 2 40
ACアダプター5V2A GF12-US0520 650 1 650
ABS樹脂ケース(蝶番式・中) 112-TS 120 1 120
絶縁ラジアルリード型積層セラミックコンデンサ 0.1μ 10 1 10
丸ピンICソケット 3 11 33
丸ピンIC連結ソケット 5 3 15
合計 - - 2550

ケースを入れ替えました(2017/1/17追記)

ケースを上から見た写真
Pic.6 Aquarino外観(新ケース)

ケースを横から見た写真
Pic.7 Aquarino外観(新ケース)

エステーさんの冷蔵庫の脱臭炭のケースがちょうど良いサイズだったので、ケースを工作のうえケーブルも収まるように新たに細工しました。旧ケースよりもスリムで良いですね。

所感

タイトルからすると竜頭蛇尾な内容かもですが、自分としてはボリュームのあるプロジェクトで、初めの構想から1ヶ月半が経とうとしています。

EAGLE も勉強することができましたし、ESP-WROOM-02 の扱いにも慣れてきました。また YouTube で動画に字幕をいれられることが分かったり、ThingSpeak 利用も IFTTT のレシピ公開なども初めてでした。

センサのハードウェア・ソフトウェアの構想・実装、加えてWEBサービスマッシュアップまで、"IoT" の要素技術を一通り学ぶことができた実り多いプロジェクトでした。
しかしながらこれで終わりではなく、これからは運用・改修・機能追加など持続して取り組んでいきたいと思います。

以上

Gmail などのWEBメールで適切に処理されない S/MIME受信メールに対して、Linux 上で改ざん・デジタル署名の確認などを行う

OpenSSL_logo.png

目次

背景

表題の件に関係し、銀行から送られてくるメールの添付ファイル smime.p7s にいつも疑問を感じていたため、何であるか調査を行い、Linux 上にて適切な処理ができるか確認しました。

smime.p7s とは

S/MIMEを扱えない電子メールソフトもあるため、"smime.p7m"という名の本文や、"smime.p7s"という名の添付ファイルに困惑する人が多い。

S/MIME - Wikipedia

S/MIME に対応していないメーラでは、S/MIME証明書を添付ファイルとして表示しているのですね。

S/MIME(エスマイム、Secure / Multipurpose Internet Mail Extensions)とは、MIMEカプセル化した電子メールの公開鍵方式による暗号化とデジタル署名に関する標準規格である。

S/MIME - Wikipedia

"公開鍵方式による" ということで、openssl*1コマンドでどうにかできそうです。

smime.p7s を openssl コマンドで処理してみる(下調べ)

自分は Gmail を使っているので、まずはsmime.p7sをダウンロードして処理してみます。

環境

$  cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=12.04
DISTRIB_CODENAME=precise
DISTRIB_DESCRIPTION="Ubuntu 12.04.5 LTS"
$ openssl version
OpenSSL 1.0.1 14 Mar 2012

証明書の有効性確認

openssl の smime コマンドで Verify も簡単にできるかと思ったのですが、一度デコードしないといけないようです。

How to handle OpenSSL and not get hurt using the CLI - GridWiki

RSA鍵、証明書のファイルフォーマットについて - Qiita

$ openssl pkcs7 -in smime.p7s -inform DER -print_certs -out /tmp/output.crt

そして、メールに添付された当該証明書の信頼性を、インストール済みのルート証明書から確認します。

・/usr/share/ca-certificates/ 以下にバラバラのファイルとして CA cert が置かれる
・/etc/ssl/certs/ にはそのファイルへのシンボリックリンクと c_rehash で生成されたファイルが置かれる
・上記のファイルを一つにまとめたもの(だと思う)が /etc/ssl/certs/ca-certificates.crt に置かれる

Debian の SSL 認証局証明書 (CA cert.) を最新に保つ - Lazy Diary @ はてな

openssl コマンドでのSSL証明書の検証 - なんだそのカオは -_-

openssl でSSL/TLSと戯れてみる - いますぐ実践! Linuxシステム管理 / Vol.252

$ openssl verify -verbose -x509_strict -CAfile /etc/ssl/certs/ca-certificates.crt /tmp/output.crt
/tmp/output.crt: OK 

返り値チェックで、成功・失敗が分かるかと思ったのですが、error 出力でも返り値0だったりしたので、"OK" という文字列出現を確認することとしました。

加えて、ここでエラーが発生する場合、後述するのですが一部の証明書で「サーバー証明書・中間証明書などを逆順に記載しているもの」があったため、その可能性を疑ってみると良いと思います。

補足 : SSL 認証局証明書の更新

自分の場合は、NG の場合のエラーメッセージで "ルート証明書が古い" 的なことを言われたので(内容はコピーし忘れました…)、下記を参考に証明書を更新しました。

Debian の SSL 認証局証明書 (CA cert.) を最新に保つ - Lazy Diary @ はてな

sudo update-ca-certificates --fresh

サーバー証明書の失効を確認する

下記にも記載があるが、サーバー証明書失効リスト (CRL) を使うのは現実的ではないので、OCSP(Online Certificate Status Protocol)*2を使います。

PKI基礎講座(5):証明書の有効性 - @IT

OCSPリクエストによるサーバー証明書の失効確認

ここではメール送信者の証明書だけを、その上位の認証局に失効していないか確認します。(手順が複雑になるため、証明書パス全体の確認は行わない。)

私が愛した openssl (PKI 編 その 2) - してみむとて

$ OCSP_URI=`openssl x509 -in /tmp/output.crt -noout -text | egrep ocsp | sed -e "s/.*\(http.*\)$/\1/"`
$ awk 'BEGIN{RS="";FS="\n"};{a[NR]=$0}END{print a[2]}' /tmp/outpu.crt > /tmp/intermediate.crt
$ openssl ocsp -issuer /tmp/intermediate.crt -cert /tmp/output.crt -url $OCSP_URI -resp_text -respout resp.der -no_nonce -CAfile /tmp/intermediate.crt
〜略〜
-----END CERTIFICATE-----
Response verify OK
/tmp/output_reverse.crt: good
        This Update: Mar 30 09:02:34 2016 GMT
        Next Update: Apr  6 09:02:34 2016 GMT

OCSP 記載が無い場合は失効確認は行わない。OCSP記載が無く、CRL記載だけのものは少ないと思いますし、運用してみて改修するか考えようと思います。

【別解】サーバー証明書失効リスト (CRL) を取得する

証明書に記載のある CRL をダウンロードし、それとの突き合わせを行います。

opensslによるサーバー証明書失効リスト (CRL) 確認 - IKB: 雑記帖

自堕落な技術者の日記 : OpenSSL 1.0.0 beta1 タイムスタンプ検証機能 - livedoor Blog(ブログ)

メール送信者の証明書中から CRL 配布ポイントを確認・ダウンロードを行い、デジタル署名の確認に合わせて CRL との突き合わせを行なっています。

mkdir ~/.crl
pushd ~/.crl
curl -O ` openssl x509 -noout -text -in output.crt | grep .crl | sed -e "s/.*\(http.*\.crl\)$/\1/" `
c_rehash .
popd
openssl verify -verbose -crl_check -x509_strict -CApath ~/.crl -CAfile /etc/ssl/certs/ca-certificates.crt /tmp/output.crt

送信元の確認

メールに記載された送信元と、下記コマンド実行で出力される証明書に記載された署名者アドレスを突き合わせます。

grep emailAddress /tmp/output.crt | sed -e "s/.*emailAddress=\(.*\)/\1/"

メール内容の改ざん有無の確認

これは下記の記事を参考にしましたが、簡単なようです。

openssl - smime.p7sからメッセージダイジェストを求めたい - スタック・オーバーフロー

Gmail で "メッセージのソースを表示" を行い、ここではgmail.emlと名前をつけて保存し、下記コマンドを実施します。

openssl smime -verify -in gmail.eml

改ざん無し

  • 返り値 : 0
  • 標準出力 : Verification successful

※メッセージボディの sha1 ハッシュを確認し改ざんを確認しているので、試しにメッセージヘッダを改ざんしても上記結果となります。

メッセージボディの改ざん有り

  • 返り値 : 4
  • 標準出力 : Verification failure
  • エラー出力 : 140465929549472:error:21071065:PKCS7 routines:PKCS7_signatureVerify:digest failure:pk7_doit.c:1158:
  • エラー出力 : 140465929549472:error:21075069:PKCS7 routines:PKCS7_verify:signature failure:pk7_smime.c:410:

電子証明書の期限切れ

  • 返り値 : 4
  • 標準出力 : Verification failure
  • エラー出力 : 139838430467744:error:21075075:PKCS7 routines:PKCS7_verify:certificate verify error:pk7_smime.c:342:Verify error:certificate has expired

S/MIME証明書チェックスクリプトを作成

これまでの内容では、smime.p7sを操作していましたが、Gmail の "メッセージのソースを表示" からメールを保存したものは eml 形式なので、そこからS/MIME電子証明書をパースします。

Gmail からメールをローカルに保存する

いちいちメールを閲覧時に "メッセージのソースを表示" するのは面倒なので、メッセージのダウンロードを簡単にできるようにします。

メールを普通に表示している時のURLを下に記します。("YYYYYYYYYYYYYYYY" はおそらくユーザ毎のメールID)

https://mail.google.com/mail/u/0/?zx=XXXXXXXXXXXX#inbox/YYYYYYYYYYYYYYYY

メッセージのソースを表示した時のURLを下に記します。("ZZZZZZZZZZ" はおそらくユーザID)

https://mail.google.com/mail/u/0/?ui=2&ik=ZZZZZZZZZZ&view=om&th=YYYYYYYYYYYYYYYY

上記URL構造から、メール閲覧時に下記 JavaScript にてダウンロードを実施します。("ZZZZZZZZZZ" は適宜置き換えてください。)

javascript:(function(){var str=""+document.location;num=str.slice(-16);
    url="https://mail.google.com/mail/u/0/?ui=2&ik=ZZZZZZZZZZ&view=om&th="+num;
    var a = document.createElement('a');
    a.href = url;
  a.setAttribute('download', "gmail.eml");
  a.dispatchEvent(new CustomEvent('click'));
})()

JavaScript でのダウンロードについては、下記サイトを参考にさせていただきました。

javascriptからファイル保存ダイアログを出す - Qiita

改ざん確認

先程のテストしたコマンドの返り値から、改ざん判定を行います。

if `openssl smime -verify -in gmail.eml 1>/dev/null` ; then
    echo 'NoPolute: OK'
else
    exit 1
fi

emlファイルよりS/MIME証明書部分を切り出す

  1. ダウンロードした eml ファイルの改行が0d0aなので、awk利用のため0aに変換する
  2. 最終行(付近)の multi-part 終了を示すハイフンを含む部分を削除する
  3. awk で、S/MIME証明書部分を切り取る
  4. BASE64デコードを実行する
perl -pe 's/\r\n/\n/' gmail.eml | sed 's/------.*--//g' | awk 'BEGIN{RS="";FS="\n"};{a[NR]=$0}END{print a[NR]}' | base64 -d > /tmp/smime.p7s

上記手順により添付ファイルとして見えていたsmime.p7sと同じものが取り出せました。

切り出した S/MIME証明書の確認

ここでは確認として、取り出した/tmp配下のファイルと、ダウンロードしたsmime.p7sファイルのハッシュ値を比較します。

$ md5sum /tmp/smime.p7s
ca49c2ed84c7d108f23bc66157766e88  /tmp/smime.p7s
$ md5sum smime.p7s
ca49c2ed84c7d108f23bc66157766e88  smime.p7s

ダウンロードしたsmime.p7sファイルと同じものを、うまく eml ファイルから切り出せたようです。

証明書の Verify を行う (一部のS/MIME証明書など、サーバー証明書・中間証明書などを逆順に記載しているものにも対応)

いざサーバー証明書の Verify を行う段になりまして、一部 S/MIME証明書で Verify が失敗する事象に出くわしました。

これは "三菱東京UFJダイレクト" さんの S/MIME証明書の場合で生じた事象だったのですが、どうも連結されているサーバー証明書の順番が一般的なものと逆だったようです。

(ELB に)中間証明書とクロスルート証明書の連結する順番に注意 - tkuchikiの日記

SSLサーバー証明書に中間証明書を結合する [(全部俺)何でも Advent Calendar 2013 8日目] | maruTA(Bis5)'s Weblog – Side D:iary

したがいまして、連結されたサーバー証明書の中の証明書を逆順にしてチェックを行うのがopenssl的に正しいのかなと思うのですが、正順(?)の場合も合わせてチェックすることにします。

  1. S/MIME証明書のデコード
  2. サーバー証明書(中身は正順)の Verify
  3. 連結されたサーバー証明書の中の証明書を逆順にする
  4. サーバー証明書(中身は逆順)の Verify
openssl pkcs7 -in /tmp/smime.p7s -inform DER -print_certs -out /tmp/output.crt
openssl verify -verbose -x509_strict -CAfile /etc/ssl/certs/ca-certificates.crt /tmp/output.crt
cat /tmp/output.crt | awk 'BEGIN{RS="";FS="\n"};{a[NR]=$0}END{for(i=NR;i>0;i--)print a[i]"\n"}' > /tmp/output_reverse.crt
openssl verify -verbose -x509_strict -CAfile /etc/ssl/certs/ca-certificates.crt /tmp/output_reverse.crt
補足 : 証明書の中身を見てみる
openssl x509 -text -noout -in /tmp/output.crt

OCSPリクエストによるサーバー証明書の失効確認

OCSP記載があるか確認し、OCSPリクエスト結果の返り値で失効しているか判定しています。

OCSP_URI=`openssl x509 -in /tmp/output_reverse.crt -noout -text | egrep ocsp | sed -e "s/.*\(http.*\)$/\1/"`
if [ -n "$OCSP_URI" ]; then
    awk 'BEGIN{RS="";FS="\n"};{a[NR]=$0}END{print a[2]}' /tmp/output_reverse.crt > /tmp/intermediate.crt
    if `openssl ocsp -issuer /tmp/intermediate.crt -cert /tmp/output_reverse.crt -url $OCSP_URI -resp_text -no_nonce -CAfile /tmp/intermediate.crt 1>/dev/null` ; then
        echo 'Status  : OK'
    else
        echo 'Status  : NG'
    fi
    rm /tmp/intermediate.crt
else
    echo 'OCSP_URI: None'
fi

送信元の確認

証明書に記載されたアドレスと、メールに記載された送信元を突き合わせます。

FROM_ADDRESS=`egrep "^From:" $TARGET_MAIL | perl -pe 's/.*?([a-zA-Z0-9!$&\*\.=^\`|~#%\+\/?_{}\-]+@[a-zA-Z0-9_\-\.]+).*/$1/' | perl -pe 's/\r\n/\n/'`
CERT_ADDRESS=`grep emailAddress /tmp/output.crt | sed -e "s/.*emailAddress=\(.*\)/\1/" | perl -pe 's/\r\n/\n/'`
if [ $FROM_ADDRESS = $CERT_ADDRESS ] ; then
    echo 'Address : OK'
else
    echo 'Address : NG'
    echo "FROM_ADDRESS = $FROM_ADDRESS"
    echo "CERT_ADDRESS = $CERT_ADDRESS"
    exit 1
fi

S/MIME証明書チェックスクリプト(完成版)

gist.github.com

例:成功

$ smime gmail.eml
Verification successful
NoPolute: OK
Certify : OK
Response verify OK
Status  : OK
Address : OK

例:改ざんされている

$ smime gmail_replace.eml 
Verification failure
139750278760096:error:21071065:PKCS7 routines:PKCS7_signatureVerify:digest failure:pk7_doit.c:1158:
139750278760096:error:21075069:PKCS7 routines:PKCS7_verify:signature failure:pk7_smime.c:410:

例:証明書が古い

$ smime gmail.eml 
Verification failure
139844339644064:error:21075075:PKCS7 routines:PKCS7_verify:certificate verify error:pk7_smime.c:342:Verify error:certificate has expired

例:送信者が署名者と違う

$ smime gmail_bad_sender.eml 
Verification successful
NoPolute: OK
Certify : OK
Response verify OK
Status  : OK
Address : NG
FROM_ADDRESS = notice@hogemail.ocn.ne.jp
CERT_ADDRESS = notice@infomail.ocn.ne.jp

所感

サーバ証明書まわりの知識があまり無かったので勉強になりました。そして、S/MIMEの世間での扱われ方もわかりましたし、在り方として普及が難しいのもわかりました。WEBメール、つまるところブラウザからローカルの資源にアクセスするという意味でもWEBメールとは相性が悪いですし、署名をGmailサーバ上でやる場合の秘密鍵の取り扱いは…などなど。

本件は、MUA(Mail User Agent)を利用すれば簡単に代替できるのですが*3、会社ならまだしも、プライベートではどこからでもアクセスできるWEBメールが楽なんですよね。そして、どうしてもE2E(End-to-End)で暗号化したい場合の手段ではS/MIMEも必要かもしれませんが、それなら今時ならメールでなくとも…という感じはありますね。

なにはともあれ、「smime.p7sを見て何だろうなと思わなくて良くなり、いざという時はそれを適切に処理できるようになった」というのが、精神安定上的な意味で一番の成果ですね。

追記 ( 2016/04/04 )

Gmail では、タイトルの件とは別に2011年位から "送信ドメイン認証" は行なっているようですね。(だから S/MIME は…)

メール認証 - Gmail ヘルプ

"Google Apps" で DKIM+SPF 設定を行い、 Gmail は "送信ドメイン認証" チェックもできるわけですし、そもそもメールに限らず Google サービス内 (・間) のやり取りは google プライベートクラウドともいうべき中で行われているわけでして…囲い込まれ感が凄いですね。

通信時のメールの暗号化(TLS) - Gmail ヘルプ

社会が Google に取り込まれていくようです。

以上