LINE Messaging APIでX-Line-Signatureの署名検証を行う(AzureFunctions/Node.js）

# はじめに

前回、Azure Functionsを使ってLINE Messaging APIを使ったbotを作成しました。
[Azure Functionsを使って非同期処理のLINE BOTを作成する](http://qiita.com/yorifuji/items/7f892564958cf464a812)
![スクリーンショット 2016-10-13 1.16.32.png](https://qiita-image-store.s3.amazonaws.com/0/104863/941ba3ca-f6a3-6bb4-78a0-52a0c34369e9.png)

前回の残作業として、以下の内容が残っていました。

```
紹介したコードは最小限のロジックのみ実装していてセキュリティやエラー処理などは考慮していません。
例えば、LINEサーバーからのメッセージであることの検証などが不足しています。
```

今回はメッセージの署名検証を行ってみます。

# 署名検証について

LINE developersのAPI Referenceに次の記述があります。

```
Webhook Authentication

X-Line-Signature Request Headerに入っているSignatureを検証することによって、
リクエストがLINE Platformから送信されたものであることを確認する必要があります。

検証は以下の手順で行います。

1. Channel Secretを秘密鍵として、HMAC-SHA256アルゴリズムによりRequest Bodyのダイジェスト値を得る。
2. ダイジェスト値をBASE64エンコードした文字列が、Request Headerに付与されたSignatureと一致することを確認する。
```
https://devdocs.line.me/ja/#webhook-authentication

つまり、用意したWebhookへのアクセスが本当にLINEのサーバーからのそれなのかチェックする必要があります。
検証手順も記載されているのでその通りに実装して見ます。

# 実装

Webを検索すると類似の実装をやっている例がありました。
[【乗るしかない】AWS LambdaとLINE BOT APIでRSSを通知する【このビッグウェーry】 ｜ Developers.IO](http://dev.classmethod.jp/cloud/build-line-bot-api-using-lambda/)

昔のLINE APIを使う際の認証のためのコードのようですが、やっていることはほとんど同じです。上記のサイトを参考にして実装したのがこちらです。

```js
var crypto = require("crypto");

function validate_signature(signature, body)
{
    const LINE_CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET;
    return signature == crypto.createHmac('sha256', LINE_CHANNEL_SECRET).update(Buffer.from(JSON.stringify(body))).digest('base64');
}

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    if (validate_signature(req.headers['x-line-signature'], req.body)) {
        context.bindings.outputQueueItem = req.body;
    }
    else {
        context.log('fail to validate signature');
    }
    
    context.res = { body : "" };
    context.done();
};
```
[yorifuji/azure-functions-line-signature-bot]( https://github.com/yorifuji/azure-functions-line-signature-bot)

個別に見ていきます。

```js
module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    if (validate_signature(req.headers['x-line-signature'], req.body)) {
        context.bindings.outputQueueItem = req.body;
    }
    else {
        context.log('fail to validate signature');
    }
    
    context.res = { body : "" };
    context.done();
};
```

まずLINEサーバーからのリクエストが届くと、こちらの関数が実行されます。
署名検証のために新たに追加したのはこちらのコードです。

```js
    if (validate_signature(req.headers['x-line-signature'], req.body)) {
        context.bindings.outputQueueItem = req.body;
    }
    else {
        context.log('fail to validate signature');
    }
```

validate_signatureにreq.headers['x-line-signature']とreq.bodyを渡しています。
req.headers['x-line-signature']にはLINEサーバから付与されたSignatureが入っています。
req.bodyはRequest Bodyそのものです（JSON）。

次にvalidate_signatureの実装はこちらです。

```js
var crypto = require("crypto");

function validate_signature(signature, body) {
    const LINE_CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET;
    return signature == crypto.createHmac('sha256', LINE_CHANNEL_SECRET).update(Buffer.from(JSON.stringify(body))).digest('base64');
}
```

先頭の var crypto... はNode.jsのcryptoを利用するための宣言です。HMAC-SHA256を求めるのに利用します。
validate_signatureの先頭でLINE_CHANNEL_SECRETを環境変数（process.env）から読み込んでいます。

```js
    const LINE_CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET;
```

これはHMAC-SHA256の秘密鍵として利用します。LINE_CHANNEL_SECRETの値はLINE Developersの管理画面に表示されるのでそちらからコピーしておきます。
環境変数から読み込んでいるのはGitHub上でコードを管理して、それを直接Azureにデプロイする運用にしているためです。

署名検証の手順としては、crypto.createHmacで生成したインスタンスに対してupdate関数を使ってbodyの内容をセットします。最後にBASE64に変換した値を出力します。
出力した値とsignatureが一致していれば署名検証に成功です。

# サンプルコード

GitHubはこちらです。[yorifuji/azure-functions-line-signature-bot](https://github.com/yorifuji/azure-functions-line-signature-bot)

前回と同様に、Azure Functionsで「継続的インテグレーションの構成」の設定を行なっている方は、リポジトリをこちらに切り替えていただくとソースコードを丸ごと切り替えることができます。

なお、署名検証のために以下のキーを環境変数に登録する必要があります。

キー：
LINE_CHANNEL_SECRET
値：
LINE developersのChannel Secretの値

# まとめ

LINE Messaging APIでX-Line-Signatureの署名検証を行いました。内容に誤りがあればコメントいただけると幸いです。

# 予定

- メッセージの種類に応じて応答を変える