はじめに
cybozu.comにおける「TLS 1.0暗号化の無効化について」というアナウンスからもうすぐ1年、実施日の2018年6月10日も間近に迫ってきました。
kintone等cybozu.com上のサービスへTLSv1.0で接続しているブラウザアクセス、APIアクセスは2018年6年10日以降はアクセス不可になります。
概要や事前チェックの参考サイトは次のようなものが挙げられます。
【ブラウザアクセスチェック】
・サイボウズにおけるTLS 1.0無効化について
【APIアクセスチェック】
・TLS 1.0暗号化の無効化について(2017/06/07)
・TLS 1.0暗号化の無効化の対策について(2017/07/19)
・TLS 1.0 無効化 対策まとめ
ブラウザアクセスについては「サイボウズにおけるTLS 1.0無効化について」で丁寧に説明されていますので、皆さん日頃からお使いのブラウザでチェックされると良いでしょう。
APIアクセスについてはどうでしょうか。現状最も有力な情報源は「TLS 1.0 無効化 対策まとめ」になりそうに思われます。更に正直に言ってしまえば、この中でリンクされているSalesforceによる「TLS 1.0 の無効化」が最もよくまとめられているように思われます。
ただ、やはりこれでも十分とは言えないでしょう。アナウンスから1年、多種多様なkintone連携開発が世の中には生まれてきており、実行環境、開発言語、ライブラリも多岐に渡ることでしょう。では、このような状況下でまずは事前確認をどうするのが良いかというのが今回の主題です。
APIアクセスの事前確認方法
まずは結論です。ブラウザアクセス確認用に提供されているURL(https://tls1test.kintone.com)に現実行環境からHTTPSアクセスする方法が最もシンプルです。そして、HTMLタグがレスポンスとして返ればTLSv.1.1以上のアクセスがなされており今回影響なし、アクセス自体がエラーになれば、TLSv1.1未満で対策要ということになります。なぜそうなるかの理由は一旦置いておいて、幾つか例を見ていきます。
Node.js
まずは、Node.js(執筆時点v9.11.1)でのリクエスト例です。AWS Lambdaのランタイムとして利用されているケースも結構多いのではないでしょうか。httpsやrequestといったモジュールがありますが、今回は標準モジュールであるhttpsの例を見ます。
const https = require('https'); https.get({ hostname: 'tls1test.kintone.com', port: 443, path: '/' }, (res) => { res.setEncoding('utf8'); let rawData = ''; res.on('data', (chunk) => { rawData += chunk; }); res.on('end', () => { try { console.log(rawData); } catch (e) { console.error(e); } }); }).on('error', (e) => { console.error(e); });
リクエストに成功すると、このようにHTMLタグが現れます。
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>お客様が現在ご利用のWebブラウザーはTLS 1.1以上が有効です。</title> <link rel="stylesheet" href="css/tls.css"> </head> <body> <header> <div class="container"> <img src="images/img_01.png"> </div> </header> <section> <div class="container"> <h1>お客様が現在ご利用のWebブラウザーは<br>TLS 1.1以上が有効です。</h1> <p>※TLS 1.0の無効化について、詳しくはこちらをご確認ください。</p> <p>こちら:<a href="https://www.cybozu.com/jp/tls/" target="_blank">https://www.cybozu.com/jp/tls/</a></p> </div> </section> <footer> <div class="container"> <p>サイボウズ株式会社<br class="txt_sp"> Copyright © Cybozu, Inc.</p> </div> </footer> </body> </html>
TLSのバージョンが1.1未満でAPIアクセスに失敗した際には先に述べた通りエラーになります。Node.jsのhttpsの場合には次のような接続エラーをはきます。
{ Error: read ECONNRESET at exports._errnoException (util.js:1022:11) at TLSWrap.onread (net.js:569:26) code: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read' }
Python 3.6
Python(執筆時点v3.6.5)も試しておきましょう。Pythonにも幾つかライブラリがありますが、こちらも標準モジュールのurllibのリクエスト例を挙げます。
# coding: utf-8 import urllib.request endpoint = 'https://tls1test.kintone.com' req = urllib.request.Request(endpoint) req.add_header('Content-Type','text/html') response = urllib.request.urlopen(req) print(response.read().decode('utf-8'))
リクエスト成功時はNode.jsと同じくHTMLタグが現れますが、失敗時には次のようなエラーが表示されます。接続失敗の内容です。
Traceback (most recent call last): File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/urllib/request.py", line 1318, in do_open encode_chunked=req.has_header('Transfer-encoding')) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/http/client.py", line 1239, in request self._send_request(method, url, body, headers, encode_chunked) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/http/client.py", line 1285, in _send_request self.endheaders(body, encode_chunked=encode_chunked) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/http/client.py", line 1234, in endheaders self._send_output(message_body, encode_chunked=encode_chunked) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/http/client.py", line 1026, in _send_output self.send(msg) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/http/client.py", line 964, in send self.connect() File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/http/client.py", line 1400, in connect server_hostname=server_hostname) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/ssl.py", line 401, in wrap_socket _context=self, _session=session) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/ssl.py", line 808, in __init__ self.do_handshake() File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/ssl.py", line 1061, in do_handshake self._sslobj.do_handshake() File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/ssl.py", line 683, in do_handshake self._sslobj.do_handshake() ConnectionResetError: [Errno 54] Connection reset by peer During handling of the above exception, another exception occurred: Traceback (most recent call last): File "test.py", line 15, in <module> response = urllib.request.urlopen(req, context=context) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/urllib/request.py", line 223, in urlopen return opener.open(url, data, timeout) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/urllib/request.py", line 526, in open response = self._open(req, data) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/urllib/request.py", line 544, in _open '_open', req) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/urllib/request.py", line 504, in _call_chain result = func(*args) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/urllib/request.py", line 1361, in https_open context=self._context, check_hostname=self._check_hostname) File "/Users/yamaroo/.pyenv/versions/3.6.0/lib/python3.6/urllib/request.py", line 1320, in do_open raise URLError(err) urllib.error.URLError: <urlopen error [Errno 54] Connection reset by peer>
間接的な確認方法
さて、やや間接的に思える確認方法の例はこの辺にして、なぜこのような確認方法に至るのかを見ていきたいと思います。
そもそも
「現在利用しているTLSバージョンを確認する方法は?」
という声が聞こえてくるのが至極当然ですが、ドキュメントを読み込んでも各開発言語・HTTPクライアントライブラリで利用しているTLSバージョンを直接的に知る方法が思いの外存在しないのです。SSL/TLSバージョンを返すメソッドでもあって良さそうなものですが、意外に出てきません。ですので、実行環境で利用ライブラリからテストURLにアクセスするという方法が汎用的でわかりやすい方法になってきます。ただ、直接的でなくスッキリ感も少なめですので、SSL/TLSについて少し理解を深めることで納得感を得てもらえればと思います。
SSL/TLSとそのバージョン選択
まず、大きく2つ前提があることを押さえておきましょう。
(1) このSSL/TLS暗号化方式は、開発言語や利用ライブラリ以前にOSやOpenSSLに依存する
(2) 開発言語・HTTPクライアントラブラりはその実行環境中で最もセキュアなSSL/TLSバージョンを自動選択することが多い
それぞれ確認しましょう。
(1) OSやOpenSSLバージョンによって選択・利用できるSSL/TLSバージョンが異なる
タイトル通りですが、Javaのような言語を除いてSSL/TLSバージョンは基本的にOSやOpenSSLバージョンに依存してきます。例えば、Pythonのsslモジュールのドキュメントに次のような記載があります。
「OSのソケットAPIに対して実装されているので、幾つかの挙動はプラットフォーム依存になるかもしれません。インストールされているOpenSSLのバージョンの違いも挙動の違いの原因になるかもしれません。例えば、TLSv1.1, TLSv1.2 は openssl version 1.0.1 以降でのみ利用できます。」
つまり、openssl version 1.0.1未満の環境ではTLSv1.1未満となるため、今回アウトになります。OpenSSLのバージョンも確認しておくと今回の確認の役に立つことがあります。
(2) 開発言語・HTTPクライアントラブラりはその実行環境中で最もセキュアなSSL/TLSバージョンを自動選択することが多い
ここでもPythonの例を挙げたいと思います。先ほどのリクエスト例で利用したurllib.requestでは、SSLContext と呼ばれるオブジェクトを用いてSSL/TLSの設定を調整することができますが、指定しなければ実行環境で最もセキュアなバージョンを自動選択するようです。
SSL/TLSバージョンを指定してリクエストする方法
そして、リクエストに用いられているバージョンを直接的に知る方法は見つからないながら、バージョンを指定する方法はSSL/TLS部分のドキュメントを見ると記述があったりします。
Node.jsであれば、httpsのドキュメントより、secureProtocolオプションを使って次のようにして指定できます。
const https = require('https'); https.get({ hostname: 'tls1test.kintone.com', port: 443, path: '/', secureProtocol: "TLSv1_method" // <- 追記箇所 }, (res) => { res.setEncoding('utf8'); let rawData = ''; res.on('data', (chunk) => { rawData += chunk; }); res.on('end', () => { try { console.log(rawData); } catch (e) { console.error(e); } }); }).on('error', (e) => { console.error(e); });
この例は、TLSv1.0を指定していますので、エラーが返ります。OpenSSLのドキュメントを見ると、他のバージョンの指定方法もわかります。ここでも、TLSv1_method を TLSv1_1_method に変えればHTMLタグが無事表示されます。
Pythonでは、先ほどチラッと触れましたが、SSLContextオブジェクトを指定します。
# coding: utf-8 import urllib.request import ssl context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) # <- 追記箇所(TLSバージョン指定箇所) endpoint = 'https://tls1test.kintone.com' req = urllib.request.Request(endpoint) req.add_header('Content-Type','text/html') response = urllib.request.urlopen(req, context=context) # <- context追加部分を追記 print(response.read().decode('utf-8'))
この例でもTLSv1.0を指定していますので、エラーが返ります。ssl.PROTOCOL_TLSv1 を ssl.PROTOCOL_TLSv1_1 に変えれば、やはりHTMLタグが返ります。これらは、sslモジュールドキュメントに記載がある定数です。
まとめ
APIリクエストにおいて現行のTLSバージョンを確認するという方法が意外と見つからないので、確認用サイトのURLを利用して現行のシステムからTLSv1.1以上でHTTPS接続できていいるかを確認するという間接的な方法の提案でしたが、いかがだったでしょうか(さらに補足しますと、ググるとバージョンを指定する方法は幾らか出てきますが、バージョンを確認する方法はなかなか出てきません)。
現行の仕組みだけでなく、対策後の仕組みの接続可否の確認にも使えますし、シンプルなので比較的実践しやすい方法かと思います。
バージョン指定比べを自分でやって、接続の成功(TLSv1.1未満)と失敗(TLSv1.1以上)の違いを実感すると納得感が得られると思いますので、実感の上利用頂くと良いかと思います。
[おまけ] OpenSSLコマンドやcURLコマンドで接続プロセスを見てみる
OpenSSLコマンドやcURLコマンドを使ったバージョン指定のリクエスト比べを行うのも面白いです。
OpenSSLコマンド
まずは、OpenSSLコマンドです。特にTLSバージョンは指定せずにリクエストしてみます。
openssl s_client -connect tls1test.kintone.com:443
私のMacBook Airでの結果は次の通りです。ツラツラツラと証明書や鍵がやりとりされ、セッションが張られます。TLSv1.2で接続していることもわかります。
CONNECTED(00000006) depth=2 C = US, ST = Arizona, L = Scottsdale, O = "Starfield Technologies, Inc.", CN = Starfield Root Certificate Authority - G2 verify error:num=20:unable to get local issuer certificate verify return:0 --- Certificate chain 0 s:/OU=Domain Control Validated/CN=*.kintone.com i:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./OU=http://certs.starfieldtech.com/repository//CN=Starfield Secure Certificate Authority - G2 1 s:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./OU=http://certs.starfieldtech.com/repository//CN=Starfield Secure Certificate Authority - G2 i:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Root Certificate Authority - G2 2 s:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Root Certificate Authority - G2 i:/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority --- Server certificate -----BEGIN CERTIFICATE----- (省略) -----END CERTIFICATE----- subject=/OU=Domain Control Validated/CN=*.kintone.com issuer=/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./OU=http://certs.starfieldtech.com/repository//CN=Starfield Secure Certificate Authority - G2 --- No client certificate CA names sent --- SSL handshake has read 4519 bytes and written 444 bytes --- New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256 Server public key is 2048 bit Secure Renegotiation IS supported Compression: NONE Expansion: NONE No ALPN negotiated SSL-Session: Protocol : TLSv1.2 Cipher : ECDHE-RSA-AES128-GCM-SHA256 Session-ID: DA2BBB6C06D71C29B3A348BD82FE820D629A7F1DE6BD1B2C848DDC7A91B9C64D Session-ID-ctx: Master-Key: 1E414061879BAAB0CBEF85DECDFA823CAE1521892C511733E694344EC81D2ABC15C6E5E9BA4E80CE694007671B169EFD TLS session ticket lifetime hint: 600 (seconds) TLS session ticket: 0000 - fa 98 ed 46 b3 10 49 b4-44 c2 dc b1 3d 6a 32 f2 ...F..I.D...=j2. 0010 - 07 0d 73 89 74 ec 71 00-2b 0c 53 e2 6e 14 c9 0f ..s.t.q.+.S.n... 0020 - 48 72 c5 15 8b ff 3b 75-74 39 90 4b 82 f2 ae 48 Hr....;ut9.K...H 0030 - 79 ae 5e f1 6f 87 d3 ad-15 0c 51 95 7f 2b 05 22 y.^.o.....Q..+." 0040 - ff 62 24 d0 8c d8 f5 d5-06 54 33 58 5b b1 54 93 .b$......T3X[.T. 0050 - 15 ba 33 3e d8 87 8b 09-c4 d8 50 7e b1 b8 59 ef ..3>......P~..Y. 0060 - 78 f2 2e f0 f2 ee 40 e3-09 d2 a0 45 6d 89 49 7d x.....@....Em.I} 0070 - ce c9 28 27 6d 24 44 6c-f7 34 b5 c1 76 86 69 6b ..('m$Dl.4..v.ik 0080 - e1 c1 af f1 2a 05 c4 3e-71 af c7 a0 f6 90 ef 8a ....*..>q....... 0090 - fc 48 87 d3 16 d1 92 ba-d2 cf ec 6c cd d4 be ca .H.........l.... 00a0 - 4d 79 57 ae 59 a5 d0 90-00 c7 60 82 60 0c c2 2a MyW.Y.....`.`..* Start Time: 1523373824 Timeout : 300 (sec) Verify return code: 0 (ok) --- closed
他方、TLSv1.0指定でリクエストしてみます。
openssl s_client -connect tls1test.kintone.com:443 -tls1
https://tls1test.kintone.com 側の要求にマッチしないため、接続が張れません。いわゆるハンドシェイクに失敗した状態です。
CONNECTED(00000006) write:errno=54 --- no peer certificate available --- No client certificate CA names sent --- SSL handshake has read 0 bytes and written 0 bytes --- New, (NONE), Cipher is (NONE) Secure Renegotiation IS NOT supported Compression: NONE Expansion: NONE No ALPN negotiated SSL-Session: Protocol : TLSv1 Cipher : 0000 Session-ID: Session-ID-ctx: Master-Key: Start Time: 1523374157 Timeout : 7200 (sec) Verify return code: 0 (ok) ---
cURLコマンド
cURLコマンドでも似たようなことができます。まずはバージョン指定なしです。
curl -s -v https://tls1test.kintone.com
結果はこの通りです。証明書の内容等までは表示されませんが、OpenSSLコマンドと似たような内容が表示されます。
* Rebuilt URL to: https://tls1test.kintone.com/ * Trying 103.79.14.48... * TCP_NODELAY set * Connected to tls1test.kintone.com (103.79.14.48) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH * successfully set certificate verify locations: * CAfile: /etc/ssl/cert.pem CApath: none * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * NPN, negotiated HTTP1.1 * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS change cipher, Client hello (1): * TLSv1.2 (OUT), TLS handshake, Unknown (67): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS change cipher, Client hello (1): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 * ALPN, server did not agree to a protocol * Server certificate: * subject: OU=Domain Control Validated; CN=*.kintone.com * start date: May 6 09:06:38 2016 GMT * expire date: May 6 09:06:38 2019 GMT * subjectAltName: host "tls1test.kintone.com" matched cert's "*.kintone.com" * issuer: C=US; ST=Arizona; L=Scottsdale; O=Starfield Technologies, Inc.; OU=http://certs.starfieldtech.com/repository/; CN=Starfield Secure Certificate Authority - G2 * SSL certificate verify ok. > GET / HTTP/1.1 > Host: tls1test.kintone.com > User-Agent: curl/7.54.0 > Accept: */* > < HTTP/1.1 200 OK < Server: nginx < Date: Tue, 10 Apr 2018 23:03:47 GMT < Content-Type: text/html; charset=utf-8 < Content-Length: 933 < Last-Modified: Tue, 06 Mar 2018 06:58:18 GMT < Connection: keep-alive < Vary: Accept-Encoding < Expires: Wed, 11 Apr 2018 00:03:47 GMT < Cache-Control: max-age=3600 < Strict-Transport-Security: max-age=315360000; includeSubDomains; preload; < X-UA-Compatible: IE=Edge < X-Content-Type-Options: nosniff < X-XSS-Protection: 1; mode=block < Accept-Ranges: bytes < <!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>お客様が現在ご利用のWebブラウザーはTLS 1.1以上が有効です。</title> <link rel="stylesheet" href="css/tls.css"> </head> <body> <header> <div class="container"> <img src="images/img_01.png"> </div> </header> <section> <div class="container"> <h1>お客様が現在ご利用のWebブラウザーは<br>TLS 1.1以上が有効です。</h1> <p>※TLS 1.0の無効化について、詳しくはこちらをご確認ください。</p> <p>こちら:<a href="https://www.cybozu.com/jp/tls/" target="_blank">https://www.cybozu.com/jp/tls/</a></p> </div> </section> <footer> <div class="container"> <p>サイボウズ株式会社<br class="txt_sp"> Copyright © Cybozu, Inc.</p> </div> </footer> </body> </html> * Connection #0 to host tls1test.kintone.com left intact
続いてTLSv1.1を指定して見ます。
curl -s -v --tlsv1.0 https://tls1test.kintone.com
やはりハンドシェイクに失敗します。
* Rebuilt URL to: https://tls1test.kintone.com/ * Trying 103.79.14.48... * TCP_NODELAY set * Connected to tls1test.kintone.com (103.79.14.48) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH * successfully set certificate verify locations: * CAfile: /etc/ssl/cert.pem CApath: none * TLSv1.0 (OUT), TLS handshake, Client hello (1): * LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to tls1test.kintone.com:443 * stopped the pause stream! * Closing connection 0
ブラウザからにしても、HTTPクライアントからにしてもそもそも正味の接続(セッションの確立)に失敗するため、エラー扱いになることが腹落ちしますね。
弊社では初回開発無料の定額39万円でkintoneアプリを開発する定額型開発サービス「システム39」を提供しております。kintoneの導入やアプリ開発でお困りな方は、お気軽にご相談ください。
*Webでの打ち合わせも可能です。