常田です

新しい機能が沢山登場しているSoracomですが今回は「Soracom Endorse 」を試してみたいと思います。

  • Soracom側からJson Web Token(JWT) を発行してくれるサービスです。
  • JWTを利用して、端末から送信されたデータ(Token)が信頼されたデータであることを確認する事ができます(図④)。

この機能を利用することでIoTデバイスのから送信されるデータが改ざんされていない事を保証し安全にデータの遣り取りをすることが出来ます。単にデータを送信するときなどは厳重に何かを保護する必要が無いかもしれませんか端末側からなにか設定変更や制御が行われる場合に利用できそうです。

サイトに書かれているこのサービスが解決する課題を転記しておきます。

次の接続ケースを考えてみます。

Air SIM を使用し、Beam 経由でサーバーに接続するケース
Air SIM を使用し、SORACOM Canal/Direct で接続されたネットワークのサーバーに接続するケース
Air SIM を使用し、インターネットを介してサーバーに接続するケース
Wi-Fi など、SORACOM Air 以外の通信手段でインターネットを介してサーバーに接続するケース
「1.」のケースでは、Beam により付与された ISMI から、SIM を特定することができます。
「2.」では、Canal/Direct で接続できる SIM は、利用者により制限することができます。Air SIM で接続 する際に認証が行われるため、Canal/Direct で接続される通信は、利用者が設定した SIM(設定したグループに含まれる SIM)からであると確認できています。ただし、通信データには「どの SIM からの接続なのか」という情報は含まれていません。
「3.」および「4.」のケースでは、接続を受けるサーバーは、接続元が Air SIM を使用しているのか、そうでないのかを確認することができません。

また追記されていますが一度取得したTokenはSoracomを経由した通信だけでなくWifi経由でも利用できるというのも利点です。

例えば、我が家にあるRaspberry Piでは屋外(SIM通信)している状態で気温をサーバに送り、家に入った段階で Wifi に切り替わりますが token情報を持つためサーバ側ではこの端末が同じ SIM番号の端末であるということを認識してデータを格納することなども出来そうです。

NewImage

今回は上記のQuickStartの内容を実施して動きを見たいと思います。

Endorse の設定

グループの設定から以下を設定します。

?NewImage

  • この設定でトークンに含めるデータをON/OFF出来ます。
  • リダイレクト先URLは指定しておかないとリダイレクトエラーになりますので必要な場合には設定しておきます)

Tokenの発行

今回利用してる Raspberey Pi から以下のコマンドを実行してみましょう。Soracomに接続している端末でかつ上記の Endorseの設定がされていると以下のように “token”が取得できます。

pi@raspi /etc $ curl https://endorse.soracom.io
{"token":"eyJraWQiOiJ2MS1mMmZlYTA2MGI5M2Y1MTBiZmI3MjJmMmNkNGIzNzc0ZS14NTA5LnBlbSIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJodHRwczovL3NvcmFjb20uaW8iLCJhdWQiOiJzb3JhY29tLWV((略))
QXTKHYmQn8VaquaFtsS6w8h17YrPOaZyhdrKvX1Hjai9Xq7TBmd7Yoka7y-TRZ-evzodeazHAyiZ4Q720ArxU3NBGb-14Fl11ZMrcMeq-5xwX0LJj1kG1r31U4vvIa11uXI6sw5GZgXWyn4ORfni6JhpG521qfoU8aR2YVec8d7s5sEkopl-_Q"}

この”token”は Json Web Token フォーマットになっています。
この内容を視覚的に見るためにhttps://jwt.io/ に接続し、[Encoded]の欄に先ほどの Token をペーストしてみます。PATLOAD: Data の項目に欲しい値が設定されています。

NewImage

Tokenの検証

?
この内容が正しいかを改ざんされた内容でないかを確認するには、先ほどの上の中の HEADER: ALGORITHM&TOKEN TYPE に指定されている kid 値を元に証明書を取得します。

$ curl https://s3-ap-northeast-1.amazonaws.com/soracom-public-keys/v1-f2fea060b93f510bfb722f2cd4b3774e-x509.pem

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgbngVZOHP4xgwx4ZiEiZ
((略))
VV8GLZknMsP4r4FZ7wdYwOe/PuInSy0K0SeH1ovuOO3hyAu6kwqASIgGj/Ytz79t
0QIDAQAB
-----END PUBLIC KEY-----

この内容を先ほどのサイトの「VERIFY SIGNATURE」に貼り付け改ざんがチェックされ問題場合には以下のようにVerifiedが表示されます。

?NewImage

クライアントからTokenの取得からTokenの送信を行い認証結果を取得

さて、上記の事から次のような動きをすることでサーバ側でクライアントを認証するということになります。

  1. 端末(Raspberry Pi)からトークンの取得と値をログインサーバにリダイレクトの実施(取得したTokenを送信する部分をRedirectURLとして実施します、これにより意図しないサーバへデータを送信する事を防ぐことが出来ます)

  2. ログインサーバでトークンが正しいものであるかを認証(JWTformatを証明することで代替)

今回はログインサーバを localhostで代替(物理的には Raspberry pi なのですが。。)してみたいと思います。受け側の処理を node-red を利用してみたいと思います。node-redには jwt を処理するための node があるので導入してみたいと思います。

$ sudo npm install -g node-red-contrib-jwt
node-red-contrib-jwt@0.0.7 /usr/lib/node_modules/node-red-contrib-jwt
└── jsonwebtoken@5.7.0 (ms@0.7.1, xtend@4.0.1, jws@3.1.3)

Noderedを再起動します。

$ pm2 restart node-red
[PM2] Applying action restartProcessId on app [node-red](ids: 0)
[PM2] [node-red](0) ✓
┌──────────┬────┬──────┬──────┬────────┬─────────┬────────┬────────────┬──────────┐
│ App name │ id │ mode │ pid  │ status │ restart │ uptime │ memory     │ watching │
├──────────┼────┼──────┼──────┼────────┼─────────┼────────┼────────────┼──────────┤
│ node-red │ 0  │ fork │ 9908 │ online │ 2       │ 0s     │ 6.504 MB   │ disabled │
└──────────┴────┴──────┴──────┴────────┴─────────┴────────┴────────────┴──────────┘
 Use `pm2 show <id|name>` to get more details about an app

ログインサーバ側は、送信されてきたデータ{token}が改ざんされていない正しいものであるかをJWTから確認することになります。 node-red のjwt ノードでは PEMファイルを自動的に抽出をしてくれないので暫定的にこの部分はファイルに保管しておきたいと思います(実際に利用する際にはこの部分もコーディングする必要がありますが 今回は Endorseの動作を見たいので割愛します) 先ほど取得した情報を /tmp/soracom.pem として保管しておきます。

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgbngVZOHP4xgwx4ZiEiZ
3IFn7pThgyvr/jTbP5RQIxPX6UcU4xalBvIs0UZfiPZ48gy9eT14KYJDPCJntJ/0
((略))
VV8GLZknMsP4r4FZ7wdYwOe/PuInSy0K0SeH1ovuOO3hyAu6kwqASIgGj/Ytz79t
0QIDAQAB
-----END PUBLIC KEY-----

次にノードでFlowを作成します。

?
NewImage

[{"id":"d1f8041.45847f8","type":"http in","z":"c1293cb4.e51778","name":"soracom","url":"/soracom","method":"get","swaggerDoc":"","x":163.5,"y":183.5,"wires":[["45d20971.4e6998"]]},{"id":"4d79fbe0.6684f4","type":"debug","z":"c1293cb4.e51778","name":"","active":true,"console":"false","complete":"false","x":649.5,"y":104,"wires":[]},{"id":"e506dbdd.0104","type":"jwt verify","z":"c1293cb4.e51778","name":"","topic":"","alg":["RS256"],"secret":"","key":"/tmp/soracom.pem","signvar":"payload","storetoken":"payload","x":505.5,"y":183,"wires":[["4d79fbe0.6684f4","6dac77fc.f9e9e8"]]},{"id":"45d20971.4e6998","type":"function","z":"c1293cb4.e51778","name":"","func":"msg.payload = msg.payload.soracom_endorse_token;\nreturn msg;","outputs":1,"noerr":0,"x":329.5,"y":182,"wires":[["e506dbdd.0104","4d79fbe0.6684f4"]]},{"id":"6dac77fc.f9e9e8","type":"http response","z":"c1293cb4.e51778","name":"","x":684.5,"y":228,"wires":[]}]

このFlowでは、 /soracom でアクセスされると JWT verify を行い問題がない場合には msg.payload に token の内容が表示されます。その結果を上記では http response として返していますがここの部分に、何らかの認証情報やセッションキーなどを入れると良いでしょう。

実際にクライアント側から実行してみます。

$ curl -v -L https://endorse.soracom.io?redirect_url=http://localhost:1880/soracom
* About to connect() to endorse.soracom.io port 443 (#0)
*   Trying 54.250.252.99...
* connected
* Connected to endorse.soracom.io (54.250.252.99) port 443 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Server key exchange (12):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSL connection using ECDHE-RSA-AES256-GCM-SHA384
* Server certificate:
*    subject: CN=endorse.soracom.io
*    start date: 2016-03-07 07:52:00 GMT
*    expire date: 2016-06-05 07:52:00 GMT
*    subjectAltName: endorse.soracom.io matched
*    issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X1
*    SSL certificate verify ok.
> GET /?username=foo&sessionId=bar&redirect_url=http://localhost:1880/soracom HTTP/1.1
> User-Agent: curl/7.26.0
> Host: endorse.soracom.io
> Accept: */*
>
* additional stuff not fine transfer.c:1037: 0 0
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 302 Moved Temporarily
< Location: http://localhost:1880/soracom?soracom_endorse_token=eyJraWQiOiJ2MS1mMmZlYTA2MGI5M2Y1MTBiZmI3MjJmMmNkNGIzNzc0ZS14NTA5LnBlbSIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJodHRwczovL3NvcmFjb20uaW8iLCJhdWQiOi((略))
Rfoz4XP7ZyZCMeNX3In1AkS2Jp7yYJYZCtaLaOQxJKypCinMve_0ln5v6XYtjz3n2t5op7Oew0zS1xzJC7yrSzEN7gvOqHbWT97mXRO-m-GyIibYlMouQvPT97kgCpOKFW_p1gS24VT9HKchQjFatVnrpnkZRdjE4YsBavmSCUL5O8cKjpXl74SoWuRJllF-9ydHIQ0-yLbIBIM-Uwa3EMP_p5D-hJVd-ov9n6InSaoRowX5Y8GGBTa0kQ-alE2ws2yE2JNP5dU6w1Mwg4yQyLCG3g
< Date: Tue, 03 May 2016 07:50:14 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
* Ignoring the response-body
* Connection #0 to host endorse.soracom.io left intact
* Issue another request to this URL: 'http://localhost:1880/soracom?soracom_endorse_token=eyJraWQiOiJ2MS1mMmZlYTA2MGI5M2Y1MTBiZmI3MjJmMmNkNGIzNzc0ZS14NTA5LnBlbSIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJodHRwczovL3NvcmFjb20uaW8iLCJhdWQiOiJzb3JhY29tLWVuZG9yc2UtYXVkaWVuY2UiLCJleHAiOjE0NjIyNjI0MTQsImp0aSI6InJLWTVVVGROT((略))
7yrSzEN7gvOqHbWT97mXRO-m-GyIibYlMouQvPT97kgCpOKFW_p1gS24VT9HKchQjFatVnrpnkZRdjE4YsBavmSCUL5O8cKjpXl74SoWuRJllF-9ydHIQ0-yLbIBIM-Uwa3EMP_p5D-hJVd-ov9n6InSaoRowX5Y8GGBTa0kQ-alE2ws2yE2JNP5dU6w1Mwg4yQyLCG3g'
* About to connect() to localhost port 1880 (#1)
*   Trying 127.0.0.1...
* connected
* Connected to localhost (127.0.0.1) port 1880 (#1)
> GET /soracom?soracom_endorse_token=eyJraWQiOiJ2MS1mMmZlYTA2MGI5M2Y1MTBiZmI3MjJmMmNkNGIzNzc0ZS14NTA5LnBlbSIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJodHRwczovL3NvcmFjb20uaW8iLCJhdWQiOiJzb3JhY29tLWVuZG9yc2UtYXVkaWVuY2UiLCJleHAiOjE0NjIyNjI0MTQsImp0aSI6InJLWTVVVGROTW9nZWRBbG1TdVd6d2ciLCJpYXQiOjE0NjIyNjE4MTQsIm5iZiI6MTQ2MjI2MTc1NCwic3ViIjoic29yY((略))
1KODANHt_Bqnd7aYNuN-y5APYKTyVEwF0WkZCtQnWsd6KNsugoIj9GqC9J-Rfoz4XP7ZyZCMeNX3In1AkS2Jp7yYJYZCtaLaOQxJKypCinMve_0ln5v6XYtjz3n2t5op7Oew0zS1xzJC7yrSzEN7gvOqHbWT97mXRO-m-GyIibYlMouQvPT97kgCpOKFW_p1gS24VT9HKchQjFatVnrpnkZRdjE4YsBavmSCUL5O8cKjpXl74SoWuRJllF-9ydHIQ0-yLbIBIM-Uwa3EMP_p5D-hJVd-ov9n6InSaoRowX5Y8GGBTa0kQ-alE2ws2yE2JNP5dU6w1Mwg4yQyLCG3g HTTP/1.1
> User-Agent: curl/7.26.0
> Host: localhost:1880
> Accept: */*
>
* additional stuff not fine transfer.c:1037: 0 0
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 200 OK
< X-Powered-By: Express
< X-Content-Type-Options: nosniff
< Content-Type: application/json; charset=utf-8
< Content-Length: 267
< ETag: W/"10b-VtHtam2qTswGQSEoiv/X2Q"
< Date: Tue, 03 May 2016 08:00:00 GMT
< Connection: keep-alive
<
* Connection #1 to host localhost left intact
{"iss":"https://soracom.io","aud":"soracom-endorse-audience","exp":1462263000,"jti":"53fOCzxgWs4k0o8e_ZueWg","iat":1462262400,"nbf":1462262340,"sub":"soracom-endorse","soracom-endorse-claim":{"imsi":"440103144854741","imei":"359545041733834","msisdn":"817040000353"}}* Closing connection #0
* SSLv3, TLS alert, Client hello (1):
* Closing connection #1

上記のようにクライアントは Tokenを取得しその内容を redirect しています。その結果、ログインサーバ側で処理が行われ結果として上記の場合には Token の内容をJSONで受け取っています。

?ログインサーバ側では、これによりこのアクセスを行ってきたのが IMSI: 44~~ の端末であるということがわかりその内容は改ざんされていないデータである事がわかることになります。これをもって「認証」されたとしセッションキーなどを発行しても良いですし、クライアントからのリクエスト時に追加でデータを含めても良いでしょう。

まとめ

IoTにおいて、端末を如何に制御するかと言うことはとても重要な課題の一つです。Soracomはその中でも3G/LTE通信環境において一つの方法を提示していると思いますがこのEndorseを活用することでまた幅が広がりますね。