Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

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

No タイトル
1 [入門]Clovaスキル(CEK)は作りながら覚えて行く(前提知識編)
2 [入門]Clovaスキル(CEK)は作りながら覚えて行く(インテント、スロットなど登録編)
3 [入門]Clovaスキル(CEK)は作りながら覚えて行く(Lambda使ってコード実装変)
4 [入門]Clovaスキル(CEK)は作りながら覚えて行く(signature検証編)<<今ここ
番外編 公式SDKを使ってLambdaで開発した記事を書いてみました

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

初心者向けの記事にしたいため、なるべく難しいことは避けたいので詳しくは公式ドキュメントを参考に。

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

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

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

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

API_Gateway.png

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

API_Gateway.png

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

API_Gateway.png

すると、マッピングテンプレートの入力欄が出てくるので、以下を入力して「保存」を押す。
参考

{
  "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

デプロイする

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

API_Gateway.png

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

API_Gateway.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使ってコード実装変) にて作成したindex.jsの一部を以下のように変更する。
applicationIdには各自設定した値を入れてください。

before

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

after

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にあげる。
参考

CEKで試してみる

うまくいってる。

Clova_Extensions_Kit_-_Interaction_Model_Builder.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取れるようにしてくれたらいいのに。。。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした