TLSv1.0無効化に備えたAPIアクセスの事前確認方法

はじめに

cybozu.comにおける「TLS 1.0暗号化の無効化について」というアナウンスからもうすぐ1年、実施日の2018年6月10日も間近に迫ってきました。

kintone等cybozu.com上のサービスへTLSv1.0で接続しているブラウザアクセス、APIアクセスは2018年6年10日以降はアクセス不可になります。

概要や事前チェックの参考サイトは次のようなものが挙げられます。

【概要】
サイボウズにおけるTLS 1.0無効化について

【ブラウザアクセスチェック】
サイボウズにおける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のランタイムとして利用されているケースも結構多いのではないでしょうか。httpsrequestといったモジュールがありますが、今回は標準モジュールである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での打ち合わせも可能です。

同じカテゴリーの記事