この記事で書いてあること
先日CEKの一般公開を期に、CEK + Lambda + API Gateway
を実践してみました。
この記事を一通りやると、簡単なClovaスキルが作成できるようになります。
今回は、リクエストを検証してみる。
簡単に説明すると、CEKからのPOSTのヘッダーにあるSignatureCEK
をごにょごにょ検証して、本当にCEKからのリクエストか判断しょう!!って感じだと思う。
初心者向けの記事にしたいため、なるべく難しいことは避けたいので詳しくは公式ドキュメントを参考に。
ヘッダーをLambdaで取得できるようにする。
マッピングテンプレート作成
SignatureCEK
はヘッダーに書かれているのでこのままではLambdaで取得が出来ない。
API Gatewayで設定することでLambdaでも取得可能になる。
まずは、以下の画像の順に前回作成したAPIのメソッドを選択し、「結合リクエスト」をクリックする。
「結合リクエスト」画面が表示されるので、下の方にある「マッピングテンプレート」の項目を開く
「テンプレートが定義されていない場合」を選択。
Content-Typeにapplication/json
を入力して、チェックボタンをクリック。
すると、マッピングテンプレートの入力欄が出てくるので、以下を入力して「保存」を押す。
参考
{
"headers": {
#foreach( $key in $input.params().header.keySet() )
"$key": "$input.params().header.get($key)"#if( $foreach.hasNext ),#end
#end
},
"requestParameters": $input.json('$')
}
デプロイする
画面左の「アクション」から「APIのデプロイ」をクリックする。
前回作成した「prod」を選択してデプロイを完了する。
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で試してみる
うまくいってる。
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取れるようにしてくれたらいいのに。。。