[入門]Clovaスキル(CEK)は作りながら覚えて行く（signature検証編）

# この記事で書いてあること

先日CEKの一般公開を期に、`CEK + Lambda + API Gateway`を実践してみました。
この記事を一通りやると、簡単なClovaスキルが作成できるようになります。


| No|タイトル|
|------------------|--------------------------------------|
|1|[[入門]Clovaスキル(CEK)は作りながら覚えて行く（前提知識編）](https://qiita.com/imajoriri/items/8d511cdfde8cb731ff82)|
|2|[[入門]Clovaスキル(CEK)は作りながら覚えて行く（インテント、スロットなど登録編）](https://qiita.com/imajoriri/items/604ff74c4e81859771e2)|
|3|[[入門]Clovaスキル(CEK)は作りながら覚えて行く（Lambda使ってコード実装変）](https://qiita.com/imajoriri/items/357026af6b0de0dfe330)|
|4|[[入門]Clovaスキル(CEK)は作りながら覚えて行く（signature検証編）<<今ここ](https://qiita.com/imajoriri/items/92d8a2906e554f43b2a5)|
|番外編|[公式SDKを使ってLambdaで開発した記事を書いてみました](https://qiita.com/imajoriri/items/0ef7aba9fea1f2d6ea1c)|

今回は、リクエストを検証してみる。
簡単に説明すると、CEKからのPOSTのヘッダーにある`SignatureCEK`をごにょごにょ検証して、本当にCEKからのリクエストか判断しょう!!って感じだと思う。

初心者向けの記事にしたいため、なるべく難しいことは避けたいので詳しくは[公式ドキュメント](https://clova-developers.line.me/guide/#/CEK/References/CEK_API.md#RequestMessageValidation)を参考に。

# ヘッダーをLambdaで取得できるようにする。

## マッピングテンプレート作成

`SignatureCEK`はヘッダーに書かれているのでこのままではLambdaで取得が出来ない。
API Gatewayで設定することでLambdaでも取得可能になる。

まずは、以下の画像の順に前回作成したAPIのメソッドを選択し、「結合リクエスト」をクリックする。

<img width="1404" alt="API_Gateway.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/4495cc0f-c448-e083-4674-b539a8138d23.png">

「結合リクエスト」画面が表示されるので、下の方にある「マッピングテンプレート」の項目を開く

![API_Gateway.png](https://qiita-image-store.s3.amazonaws.com/0/195882/ec9efffe-0487-d9ad-b597-3ebb35936f4c.png)

「テンプレートが定義されていない場合」を選択。
Content-Typeに`application/json`を入力して、チェックボタンをクリック。

![API_Gateway.png](https://qiita-image-store.s3.amazonaws.com/0/195882/440ee968-c229-188c-25ee-e16bb574377c.png)

すると、マッピングテンプレートの入力欄が出てくるので、以下を入力して「保存」を押す。
[参考](https://dev.classmethod.jp/cloud/aws/sugano-013-api-gateway/)

```
{
  "headers": {
#foreach( $key in $input.params().header.keySet() )
    "$key": "$input.params().header.get($key)"#if( $foreach.hasNext ),#end
#end
  },
  "requestParameters": $input.json('$')
}
```

![API_Gateway.png](https://qiita-image-store.s3.amazonaws.com/0/195882/37262c30-28b1-4a76-6ad8-b4294f692ce7.png)

## デプロイする

画面左の「アクション」から「APIのデプロイ」をクリックする。

![API_Gateway.png](https://qiita-image-store.s3.amazonaws.com/0/195882/22ddd0c8-a388-9d5d-2af0-d159975c5871.png)

前回作成した「prod」を選択してデプロイを完了する。

![API_Gateway.png](https://qiita-image-store.s3.amazonaws.com/0/195882/511ddaa2-ccd5-1648-7cd9-b45061315760.png)


# pemファイルのダウンロード

以下より、公開鍵をダウンロードすることができる。
https://clova-cek-requests.line.me/.well-known/signature-public-key.pem

このpemファイルを検証に使用するので、作業ディレクトリにコピーでもしておく。

すると、ディレクトリ構成は以下のようになるはず。

```
$ ls
index.js			package-lock.json		signature-public-key.pem
node_modules			package.json			upload.zip
```

# コードを変更する。

## モジュールのインストール

新たに必要なモジュールをインストール

```
$ npm install --save fs crypto
```


## index.jsの変更

[[入門]Clovaスキル(CEK)は作りながら覚えて行く（Lambda使ってコード実装変）](https://qiita.com/imajoriri/items/357026af6b0de0dfe330) にて作成した`index.js`の一部を以下のように変更する。
***applicationIdには各自設定した値を入れてください。***

before

```js
exports.handler = clova.extensionBuilders
  .addRequestHandlers(LaunchRequestHandler,SessionEndedRequestHandler,ClovaGuideIntentHandler,DivinationIntentHandler)
  .addErrorHandlers(errorHandler)
  .lambda()
```

after

```js
exports.handler = async function(event, content) {
  // 公開鍵を取得
  const certificateBody = getCertificateBody();

  // signatureを検証
  var headerSignature = event.headers.signaturecek || event.headers.SignatureCEK;
  checkSignature(certificateBody, headerSignature, JSON.stringify(event.requestParameters));

  // applicationIdを検証
  var applicationId = 'co.jp.tekito';
  checkApplicationId(event.requestParameters, applicationId);

  clova.extensionBuilders.addRequestHandlers(
    LaunchRequestHandler,
    SessionEndedRequestHandler,
    ClovaGuideIntentHandler,
    DivinationIntentHandler,
  ) 
    .addErrorHandlers(errorHandler)
  return clova.extensionBuilders.invoke(event.requestParameters);
};

// signatureを検証
function checkSignature(certificateBody, signature, requestParameters) {
  const { createVerify} = require('crypto')
  const veri = createVerify('RSA-SHA256');
  veri.update(requestParameters, 'utf8');

  if (!veri.verify(certificateBody, signature, 'base64')) {
    throw new Error('signatureが違うよ!! これはCEKからのリクエストじゃないかもよ!!');
  }
}

// applicationIdの検証
function checkApplicationId(jsonRequestBody, applicationId) {
  if (jsonRequestBody.context.System.application.applicationId !== applicationId) {
    throw new Error('ExtensionId(applicationId)が間違ってるよ');
  }
}

// ./signature-public-key.pemを読み込む
function getCertificateBody() {
  var fs = require('fs');
  var cert = fs.readFileSync('./signature-public-key.pem', 'utf8');
  return cert;
}
```

***説明上requireが変なところにきてますが、ご了承ください。***

## 忘れず、zip化

```
$ zip -r upload.zip *
```

## Lambdaにアップロード

作成したupload.zipをLambdaにあげる。
[参考](https://qiita.com/imajoriri/items/357026af6b0de0dfe330#lambda%E3%81%A7%E9%96%A2%E6%95%B0%E4%BD%9C%E6%88%90)

# CEKで試してみる

うまくいってる。

<img width="966" alt="Clova_Extensions_Kit_-_Interaction_Model_Builder.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/bb36b43a-2238-3e32-81b5-f53abaafc589.png">

# CEK以外から試してみる。

以下ではうまくいかないことを確認（CEKからのリクエストではないので)

```
$ curl -X POST https://api gatewayのドメイン/prod/fortune -H "content-type:application/json" -H "signaturecek:hogehoge"
{"errorMessage":"signatureが違うよ!! これはCEKからのリクエストじゃないかもよ!!","errorType":"Error","stackTrace":["checkSignature (/var/task/index.js:95:11)","exports.handler (/var/task/index.js:72:3)"]}
```

# 検証をやってみたの検証

めんどくさい。
api gatewayで設定しなくてもheader取れるようにしてくれたらいいのに。。。