前回の記事「受信メッセージの改ざんチェックをする」で Signature を使った改ざんチェックを紹介したのですが、その後いろいろ試していると、データ内に &(アンパサント)や >(グレーターザン)などの記号が入るケースでチェックが正常に機能しなくなることがありました。
これはいったいどういうことだ??
まぁ、きっと LINEWORKS の開発が escape 処理をし損ねたんだろう。
まったく、お茶目さんめ(*‘∀‘)
なーんて思っていたら、Java だと & などの記号が入ってても問題ないよ!って連絡が。
Σ( ゚Д゚)!
そして、別のエンジニアからも Go 言語で問題なくチェックできたよ!って連絡が!
ΣΣ( ゚Д゚)!!
・・・あっれぇー?もしかして、javascript がいけないのかな?
それとも、express ??
ってか私の手順がおかしい!?なんてこった!(; ・`д・´)
LINEWORKS サーバ側の動き
改ざんチェック用の X-Works-Signature は body の内容をエンコードしたものです。
なので、送られてきた body の内容を手順通りにエンコードすれば X-Works-Signature と一致します。
一致しなければデータが改ざんされているということです。(ここまで前回のおさらい)
LINEWORKS サーバは & やいくつかの記号については送信する際に escape 処理をしています。
まぁ、当然ですよね。escape 処理をしないと意図していない動きをしてしまう可能性がありますから。
Unicode なので & は escape 処理されて \u0026 となります。
なので、& が body 内に含まれていると \u0026 に escape 処理された上でエンコードされ X-Works-Signature が生成されます。
そして、escape 処理した & を含む body と X-Works-Signature がサーバから送信されます。
ここまでは何も問題ないですね。
シグナル、オールグリーンです。
では、次に express の動きを見ていきましょう。
受信サーバ側の動き
LINEWORKS から送信されてくるデータは JSON 形式になります。
受信サーバでは express データ解析して JSON オブジェクトとして受け取ります。
crypto で作った hmac は buffer または string でないと update できないので、JSON.stringify して string に変換しました。
app.post('/callback', (req, res) => {
    const reqbody = req.body;
    const crypto = require('crypto');
    const hmac= crypto.createHmac('sha256', process.env.API_ID);
    hmac.update(JSON.stringify(req.body));
    const data=hmac.digest('base64');
    .......
んで、ここからは定かではないのですが、どちらかの時点で & の \u0026 は unescape 処理されてしまってるようなんですよ。
- JSON オブジェクトとして受け取った時点で unescape 処理している
- 
JSON.stringifyした時点で unescape 処理している
おそらく 1 だと思うのですが、確証が得られなかったので。。。
どなたかご存じでしたら教えてください(>_<)
express.json({verify:}) を使う
では、どうしたらいいかという話ですが、タイトル通り verify という機能を使うようです。
・・・express はとても便利なフレームワークです!(いまさら)
便利すぎて不勉強だった私は、JSON 形式のデータを受け取る際は昔習った通りのおまじないをしていました。
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
おまじないと言われたので今まで特に気にしなかったのですが、先に述べた受信リクエストを解析して、JSON 形式のものは JSON オブジェクトとして成形する動きは body-parser さんはがしてくれていたようです。
勉強不足が悔やまれます(; ・`д・´)
しかも、express の公式のドキュメントを読んでいて気付いたのですが、わざわざ body-parser さんをインストールしなくても express.json を使えば body-parser さんに基づいた設定が可能なんだそうです。なんてこった!
(公式)express.json([options])
ってなわけでちょっとコードを修正します。
const express = require('express');
const app = express();
app.use(express.json());
body-parser さん、サヨウナラ。また会う日まで( ノД`)
話が逸れましたので戻します。
受信したデータのチェックには、express.json のプロパティである verify を使うことが推奨されているそうです。
まったく知りませんでした!ヾ(´∀`)ノ
app.use で設定することにより、受信したデータのチェックを常に行うことができます。
引数の buf には request body の buffer データが入っているので hmac.update でそのまま使用することができます。
さらに!エラーの場合には throw することにより処理を中断することもできます!
なんて便利!( ゚Д゚)
以下、最終的なコードになります。
const express = require('express');
const app = express();
app.use(express.json({verify:(req, res, buf, encoding) => {
    const crypto = require('crypto');
    const hmac= crypto.createHmac('sha256', API_ID);
    hmac.update(buf);
    const data=hmac.digest('base64');
    const signature=req.headers["x-works-signature"];
    // signature が一致しない場合にはエラー処理をする
    if (data!=signature) throw 'NOT_MATCHED';
}}));
app.post('/callback', (req, res) => {
    // signature が一致したときだけ callback の処理が始まる
});
これで、& や他の記号などが escape されていても大丈夫!
正しく改ざんチェックを行うことができます。やったね!
おわりに
ここまでお付き合いいただきありがとうございました。
前回の記事を修正するか、新しく記事を書くか悩んだのですが、新しく記事を書いてしまいました。
いや、前回の記事も修正した方がいいな、うん。
あとで修正しよう。
express、本当にまだまだ知らない機能が盛りだくさんです。
フレームワークと呼ばれるものでまともに使っているのは express くらいなので、もっとしっかり勉強しようと思います。
ではまた!(^^)/
