(日本語) Raspberry PiのPython3でSSLv3 handshake failureを解決した話

Sorry, this entry is only available in Japanese. For the sake of viewer convenience, the content is shown below in the alternative language. You may click the link to switch the active language.

あくあたん在室モニターは,Raspberry Piで動いています.
先日らぼのWebサーバがアップデートされたときから,うまく動作しなくなりました.

モニターでは,らぼのWebサーバで公開しているAPIを叩いて在室状況を取得します.
APIはhttpだけでなく,httpsも対応していました.

モニターのログを見ると,データ取得ルーチンが止まっています.

ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure

おっ,SSLのエラーか.
新しいサーバではTLS1.0は捨てられました.そこに異議を唱えるつもりはないので,こちらで対応すべきですね.

普通に考えて,何かSSL周りが古いとかそういう状況が考えられます.
これまで,http.clientを使ってアクセスしていたのですが,requestsのほうが良い感じと聞いたので,書き換えてみました.実際,requestsは良い感じに書けます.

テストは次の方法でできます.

$ /usr/bin/python3
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> import ssl
>>> print(ssl.OPENSSL_VERSION)
OpenSSL 1.1.0j  20 Nov 2018
>>> requests.get('https://se.is.kit.ac.jp/')
Traceback (most recent call last):
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py",
line 600, in urlopen
    chunked=chunked)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py",
line 343, in _make_request
    self._validate_conn(conn)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py",
line 839, in _validate_conn
    conn.connect()
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connection.py", line
 344, in connect
    ssl_context=context)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/util/ssl_.py", line
347, in ssl_wrap_socket
    return context.wrap_socket(sock, server_hostname=server_hostname)
  File "/usr/lib/python3.5/ssl.py", line 385, in wrap_socket
    _context=self)
  File "/usr/lib/python3.5/ssl.py", line 760, in __init__
    self.do_handshake()
  File "/usr/lib/python3.5/ssl.py", line 996, in do_handshake
    self._sslobj.do_handshake()
  File "/usr/lib/python3.5/ssl.py", line 641, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failur$ (_ssl.c:720)

しかし,エラーは消えませんでした.

pipで関連するモジュールを最新にしてみました.

しかし,エラーは消えませんでした.

試しに,Macで同じコードを実行しました.

~> python3
Python 3.7.3 (default, Mar 27 2019, 09:23:15)
[Clang 10.0.1 (clang-1001.0.46.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> import ssl
>>> print(ssl.OPENSSL_VERSION)
OpenSSL 1.0.2r  26 Feb 2019
>>> requests.get('https://se.is.kit.ac.jp')
<Response [200]>
>>>

エラーは出ません.

たまたまPython3.4が動いているraspberry piが生きていたので,そこから同じコードを実行しました.

Python 3.4.2 (default, Oct 19 2014, 13:31:11)
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> import ssl
>>> print (ssl.OPENSSL_VERSION)
OpenSSL 1.0.1t  3 May 2016
>>> requests.get('https://se.is.kit.ac.jp')
<Response [200]>
>>>

エラーは出ません.

古いpythonと古いsslでも動くのに,ちょうど今のpython3.5とopenssl 1.1.0jの組み合わせでは動かないの??
問題が切り分けられずに頭が痛くなってきます.こうなったら初期化です.Raspberry Piの公式から最新のイメージをダウンロードして,SDを初期化します.

まっさらなraspbianの上でなら…

$ /usr/bin/python3
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> import ssl
>>> print(ssl.OPENSSL_VERSION)
OpenSSL 1.1.0j  20 Nov 2018
>>> requests.get('https://se.is.kit.ac.jp/')
Traceback (most recent call last):
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py",
line 600, in urlopen
    chunked=chunked)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py",
line 343, in _make_request
    self._validate_conn(conn)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py",
line 839, in _validate_conn
    conn.connect()
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connection.py", line
 344, in connect
    ssl_context=context)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/util/ssl_.py", line
347, in ssl_wrap_socket
    return context.wrap_socket(sock, server_hostname=server_hostname)
  File "/usr/lib/python3.5/ssl.py", line 385, in wrap_socket
    _context=self)
  File "/usr/lib/python3.5/ssl.py", line 760, in __init__
    self.do_handshake()
  File "/usr/lib/python3.5/ssl.py", line 996, in do_handshake
    self._sslobj.do_handshake()
  File "/usr/lib/python3.5/ssl.py", line 641, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failur$ (_ssl.c:720)

こらあかん.
ついに,Raspberry Pi上でPython 3.7をbuildし直すことにしました.

$ sudo apt update
$ sudo apt install -y libffi-dev libbz2-dev liblzma-dev libsqlite3-dev libncurses5-dev libgdbm-dev zlib1g-dev libreadline-dev libssl-dev tk-dev build-essential libncursesw5-dev libc6-dev openssl
$ cd Python-3.7.3
$ ./configure --enable-optimizations
$ make
$ sudo make install

インストールが終わったら,requestsモジュールをインストールします.

$ sudo /usr/local/bin/pip3 install requests

テストします.(ほんま許して)

$ /usr/local/bin/python3
Python 3.7.3 (default, Apr 27 2019, 21:00:58)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> import ssl
>>> print(ssl.OPENSSL_VERSION)
OpenSSL 1.1.0j  20 Nov 2018
>>> requests.get('https://se.is.kit.ac.jp/')
<Response [200]>
>>>

動いた…

なお,この後.新しいPython3.7.3ではもう1つのはまりポイント,pygameにて更に苦労することになります.
それはまた,別の話