家の鍵をLINEから開けられるようにしたらセキュリティを考えさせられた

# Switchbot ロックを導入
我が家にSwitchbot ロックを導入しました。
* 外出時に鍵を閉めるのが面倒
* 鍵の紛失時の代替手段の確保
* SwitchbotからAPIが提供されているので面白そう

といった理由で前々から欲しいなと思ってたのが、ちょうどAmazonセールで安くなってたのを見てついついポチってしまいました。

↓これを家の扉の鍵の部分につけるだけでオートロック化とスマホで施錠解錠ができてしまいます。扉の開閉も検知できるのでいろいろと使い勝手は良さそう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451285/4e86c41c-1d35-25f2-6bcf-1c2f9ed711d4.png)

参考
www.amazon.co.jp/dp/B09KCHLWH4



# Switchbot API v1.1の存在
通常、これらSwitchbot製品はSwitchbotが公式に提供しているアプリから操作を行うのですが、あわせてAPIの提供もされております。せっかくなのでAPIを使ってみたいなと思い、今回はLINEのリッチメニューから鍵を開けれるようにしてみました。

# まずはGASからロックを解除するコードの作成
[Swichbot API v1.1のリファレンス](https://github.com/OpenWonderLabs/SwitchBotAPI)通りに、ロック解錠のAPIを呼び出すGASのコードを以下のように作成してみました。```token```、```secret```、```deviceId```は[こちら](https://qiita.com/katta1024/items/6a5af91c986fe3c47f4d)を参考に取得してください。関数```setLock()```を実行すると、Swichbotロックが解錠されます。

```javascript:unlock.gs
// SwichBotAPIのサービスURL、認証情報
const url = 'https://api.switch-bot.com'
const token = "xxxxxxxxxxxxxxx"//Swichbotのトークン（アプリの開発者オプションから取得）
const secret = "xxxxxxxxxxxxxxx"//Swichbotのクライアントシークレット（アプリの開発者オプションから取得）

function setLock(command = "unlock") { //command = "lock" or "unlock"

  const deviceId = 'xxxxxxxxxxxx' //ロックのデバイスID
  const path = '/v1.1/devices/' + deviceId + '/commands'
  const nonce = Utilities.getUuid() // 任意の文字列で可 

  let timestamp = new Date()
  timestamp = timestamp.getTime().toString()

  let sign = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_256, token + timestamp + nonce, secret)
  sign = Utilities.base64Encode(sign).toUpperCase()

  const headers = {
    'Authorization': token,
    'sign': sign,
    't': timestamp,
    'nonce': nonce
  }
  const payload = JSON.stringify(
    {
      "command": command,
      "parameter": "default",
      "commandType": "default"
    }
  )
  const options = {
    'method': 'POST',
    'headers': headers,
    'payload': payload,
    'contentType': 'application/json; charset=utf-8',
  };
  const res = UrlFetchApp.fetch(url + path, options)
  Logger.log(res)
}
```

# LINE Official Account Managerでリッチメニュー作成
最も単純な形でリッチメニューを押したらテキストが飛び、それをGASで受け取ってロックを解除するAPIを呼ぶ仕様にします。イメージとしては以下のような感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451285/aa3fc523-76e5-825d-5f00-6e45bd6ebc34.png)


まずは、LINE Official Account Managerでアカウントを作成し、以下のようにリッチメニューを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451285/b870c4df-fcd6-6f2c-f9f3-7768bc343550.png)

次に、LINE DevelopersでMessaging APIを有効にしチャンネルアクセストークンを発行します。発行したチャンネルアクセストークンを用いて以下のコードをGASでデプロイします。デプロイして出てきたURLをLINE DEvelopersのwebhookに設定します。詳しいやり方はQiitaにも「GAS LINE」で調べると解説記事がたくさんあるので割愛します。

```javascript:lineunlock.gs
// SwichBotAPIのサービスURL、認証情報
const url = 'https://api.switch-bot.com'
const token = "xxxxxxxxxxxxxxx"//Swichbotのトークン（アプリの開発者オプションから取得）
const secret = "xxxxxxxxxxxxxxx"//Swichbotのクライアントシークレット（アプリの開発者オプションから取得）

// LINE のチャンネルアクセストークン
const CHANNEL_ACCESS_TOKEN = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

function doPost(request) {
  const receiveJSON = JSON.parse(request.postData.contents) // POSTリクエストをJSONデータにパース
  for (i = 0; i < receiveJSON.events.length; i++) {
    let event = receiveJSON.events[i];
    if (event.type == "message") { // messageイベントか判定
      let replyToken = event.replyToken
      if (event.message.type = "text") { //textメッセージか判定
        if (event.message.text == "ロック解除") {
          if(setLock("unlock")==100) {
            replyToUser(replyToken, "ロックを解除しました。")
          }
        }
      }
    }
  }
}

// replyトークンを用いて返信
function replyToUser(replyToken, text) {
  const replyText = {
    "replyToken": replyToken,
    "messages": [{
      "type": "text",
      "text": text,
    }]
  }
  const options = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + CHANNEL_ACCESS_TOKEN,
    },
    "payload": JSON.stringify(replyText)
  };
  UrlFetchApp.fetch("https://api.line.me/v2/bot/message/reply", options);
}

function setLock(command = "unlock") { //command = "lock" or "unlock"

  const deviceId = 'xxxxxxxxxxxx' //ロックのデバイスID
  const path = '/v1.1/devices/' + deviceId + '/commands'
  const nonce = Utilities.getUuid() // 任意の文字列で可 

  let timestamp = new Date()
  timestamp = timestamp.getTime().toString()

  let sign = ignature = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_256, token + timestamp + nonce, secret)
  sign = Utilities.base64Encode(sign).toUpperCase()

  const headers = {
    'Authorization': token,
    'sign': sign,
    't': timestamp,
    'nonce': nonce
  }
  const payload = JSON.stringify(
    {
      "command": command,
      "parameter": "default",
      "commandType": "default"
    }
  )
  const options = {
    'method': 'POST',
    'headers': headers,
    'payload': payload,
    'contentType': 'application/json; charset=utf-8',
  };
  const res = JSON.parse(UrlFetchApp.fetch(url + path, options).getContentText())
  return res["statusCode"] //ロック解除に成功すると100
}
```

これでLINEリッチメニューから家の鍵を開けることができるようになりました！！

# セキュリティの甘さを考える
ここからが本題ですが、私はセキュリティに関してあまり深い知識は持っていないですが、ここまでの開発でセキュリティのリスクが発生していることは容易に想像できてしまいます。ここでは、その気づいたリスクについて記載していこうと思います。

### LINE公式アカウントを拡散されるリスク
当然ながらLINE公式アカウントのリッチメニューは友達追加すれば誰でも押せるので、LINE公式アカウントをもし拡散されるようなことがあればセキュリティリスクは非常に高くなります。もちろん自分あるいは信頼のおける人のみにしかアカウントを教えないということはできますが、一方で、LINE公式アカウントには「@+数字3文字+英字5文字」のベーシックIDが付与されますが、これは誰でもID検索で検索することができます。例えば、悪意のある人間がランダムにIDを入力し、アカウントを引き当てる可能性はというのは否定できません。これに対しては、友達数を定期的に確認する、[解錠された時点で必ず自分のところに連絡が来るようにする](https://developers.line.biz/ja/reference/messaging-api/#send-push-message)、[リッチメニューを表示できる人間を制限する](https://developers.line.biz/ja/reference/messaging-api/#link-rich-menu-to-user)などの対策が出来ると思います。

### GASのWebアプリのURLが漏洩するリスク
LINEのWebhookを受け取るためにGASでWebアプリとして公開するときにインターネット上に公開する形でデプロイする必要があります。すなわちデプロイされたURLが漏洩すると誰でも呼び出すことができてしまいます。特に[GASではdoPost のリクエストパラメータでHTTPヘッダを取得することはできず](https://qiita.com/kamiryo/items/d8a55d8f9cc1d0921917)、LINEサーバから送信される署名を検証できないため、受信したHTTP POSTリクエストが、LINEから送信されているか他所から送信されているかの区別がつきません。もしかしたら適当なリクエストで攻撃されてドアロックを解除されてしまうかもしれません。

以上、2つのリスクを考えました。一方で、両者とも悪意のある人が鍵を開けられるようになったからといって、その人はどこの場所の鍵かなんてことは、滅多なことがない限り特定することはできません。そう考えれば、これらのリスクはあまり大きな問題にはならないのかもしれませんが、通常の設計よりもリスクがぐっと高まっているということは自覚するべきかと思います。