PythonでTLSのバージョンを確認する方法

プログラミング

PythonでのTLSバージョン確認方法

PythonでTLS(Transport Layer Security)のバージョンを確認する方法は、主に以下の3つのアプローチが考えられます。

1. `ssl` モジュールを利用した直接的な確認

Pythonの標準ライブラリである`ssl`モジュールは、SSL/TLSプロトコルの低レベルな操作を提供します。このモジュールを利用することで、接続するサーバーがどのTLSバージョンをサポートしているかを確認できます。

TLSバージョンを試行する

特定のTLSバージョンを明示的に指定して接続を試み、成功するかどうかで判断します。これは、サーバーが特定のTLSバージョンをサポートしているかどうかを確認する最も確実な方法の一つです。

コード例:

import socket
import ssl

def check_tls_version(host, port, tls_version):
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    context.minimum_version = tls_version
    context.maximum_version = tls_version
    try:
        with socket.create_connection((host, port)) as sock:
            with context.wrap_socket(sock, server_hostname=host) as ssock:
                print(f"{host}:{port} is reachable with TLS {tls_version.__name__}")
                return True
    except ssl.SSLError as e:
        print(f"{host}:{port} failed with TLS {tls_version.__name__}: {e}")
        return False
    except ConnectionRefusedError:
        print(f"Connection refused to {host}:{port}")
        return False
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
        return False

host = "www.example.com"
port = 443

# 一般的なTLSバージョンを順番に試す
tls_versions_to_check = [
    ssl.TLSVersion.TLSv1_3,
    ssl.TLSVersion.TLSv1_2,
    ssl.TLSVersion.TLSv1_1,
    ssl.TLSVersion.TLSv1,
]

print(f"Checking TLS versions for {host}:{port}...")
supported_versions = []
for version in tls_versions_to_check:
    if check_tls_version(host, port, version):
        supported_versions.append(version.__name__)

if supported_versions:
    print(f"n{host}:{port} supports the following TLS versions: {', '.join(supported_versions)}")
else:
    print(f"n{host}:{port} did not respond to any of the checked TLS versions.")

このコードでは、`ssl.SSLContext`を作成し、`minimum_version`と`maximum_version`に確認したいTLSバージョンをセットします。その後、標準のソケット通信と同様に接続を試みます。`ssl.SSLError`が発生しなければ、そのTLSバージョンがサポートされていると判断できます。

注意点:

  • この方法は、サーバーがどのTLSバージョンを「サポートしているか」を判断するのに有効ですが、クライアント(このPythonスクリプトを実行している環境)がどのTLSバージョンを「使用できるか」を直接示すものではありません。
  • サーバーによっては、特定のTLSバージョンでの接続を拒否したり、より新しいバージョンへのネゴシエーションを試みたりします。
  • TLSバージョンを列挙して試行する順番は重要です。通常、最も新しいバージョンから試すのが一般的です。

使用可能なTLSバージョンを取得する(限定的)

Pythonの`ssl`モジュール自体は、クライアントが「使用可能な」TLSバージョンを直接リストアップするようなAPIを提供していません。TLSのバージョン選択は、主にクライアントとサーバー間のハンドシェイクプロセス中に動的に行われます。しかし、`ssl.OPENSSL_VERSION`といった定数で、OpenSSLライブラリのバージョン情報を取得することは可能です。この情報から、間接的にサポートされるTLSバージョンの範囲を推測できる場合があります。

コード例:

import ssl

print(f"OpenSSL version used by Python: {ssl.OPENSSL_VERSION}")

この出力から、利用しているOpenSSLのバージョンがどのTLSバージョンまでサポートしているかを調べることができます。しかし、これはあくまでライブラリの機能であり、実際のサーバーとの通信でどのバージョンが使われるかを保証するものではありません。

2. `requests` ライブラリを利用した間接的な確認

Webリクエストを行うための一般的なライブラリである`requests`は、内部で`urllib3`を使用し、その`urllib3`は`ssl`モジュールを介してTLSを扱います。`requests`ライブラリ自体にTLSバージョンを直接指定する機能はありませんが、`urllib3`の`pool_connections`などで、TLSバージョンの設定をカスタマイズできる場合があります(ただし、これはやや高度な使い方になります)。

より一般的な使い方としては、`requests`でHTTPSサイトにアクセスした際に、SSL/TLSハンドシェイクが成功するかどうかで、そのサイトが(クライアントとサーバーのネゴシエーションによって)どのTLSバージョンで通信できたかを確認できます。

コード例:

import requests

def check_site_tls_compatibility(url):
    try:
        response = requests.get(url, verify=True, timeout=10)
        response.raise_for_status()  # HTTPエラーがあれば例外を発生させる
        print(f"Successfully connected to {url}")
        # 実際には、このリクエストがどのTLSバージョンで行われたかはrequestsでは直接取得できない
        # より詳細な情報が必要な場合は、requests-toolbeltなど、
        # あるいはrequestsの基盤となるurllib3の低レベルAPIを調査する必要がある
        return True
    except requests.exceptions.SSLError as e:
        print(f"SSL Error for {url}: {e}")
        # このエラーは、TLSバージョン不整合、証明書エラーなど、様々な原因で発生しうる
        return False
    except requests.exceptions.ConnectionError as e:
        print(f"Connection Error for {url}: {e}")
        return False
    except requests.exceptions.RequestException as e:
        print(f"An unexpected request error occurred for {url}: {e}")
        return False

url = "https://www.google.com"
print(f"Checking compatibility for {url}...")
check_site_tls_compatibility(url)

url_old_tls = "https://expired.badssl.com/" # 古いTLSバージョンしかサポートしていないサイトの例 (テスト用)
# print(f"nChecking compatibility for {url_old_tls}...")
# check_site_tls_compatibility(url_old_tls) # このサイトはTLS1.0/1.1をサポート

注意点:

  • `requests`ライブラリは、デフォルトでクライアントとサーバーがサポートする最も高いTLSバージョンで通信しようとします。
  • `requests.exceptions.SSLError`が発生した場合、それはTLSバージョンが原因である可能性もありますが、証明書の検証失敗など他のSSL/TLS関連の問題である可能性も高いです。
  • どのTLSバージョンで通信が成功したのかを`requests`の標準機能で知ることはできません。

3. デバッグツールや外部サービスとの連携

Pythonスクリプト内で直接TLSバージョンを詳細にデバッグ・確認するのは限界がある場合があります。そのような場合は、外部のツールやサービスと連携することを検討します。

`openssl s_client` コマンド

Pythonスクリプトから外部コマンドを呼び出すことができます。`openssl s_client`コマンドは、TLS接続の詳細情報を表示するのに非常に強力です。

コード例:

import subprocess

def check_tls_version_with_openssl(host, port, tls_version_string):
    try:
        # 例: -tls1_2, -tls1_3
        command = [
            "openssl", "s_client",
            "-connect", f"{host}:{port}",
            f"-{tls_version_string}",
            "-servername", host, # SNI (Server Name Indication) を有効にする
            "-brief" # より簡潔な出力を得る
        ]
        print(f"Running command: {' '.join(command)}")
        result = subprocess.run(command, capture_output=True, text=True, timeout=10)

        if "CONNECTED(" in result.stdout:
            print(f"{host}:{port} is reachable with {tls_version_string}")
            # ここで、result.stdoutからより詳細な情報を解析することも可能
            return True
        else:
            print(f"{host}:{port} failed with {tls_version_string}. Output: {result.stderr or result.stdout}")
            return False
    except FileNotFoundError:
        print("Error: 'openssl' command not found. Please ensure OpenSSL is installed and in your PATH.")
        return False
    except subprocess.TimeoutExpired:
        print(f"Command timed out for {host}:{port} with {tls_version_string}")
        return False
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
        return False

host = "www.google.com"
port = 443

# openssl s_clientでサポートされるバージョン文字列
tls_versions_for_openssl = [
    "tls1_3",
    "tls1_2",
    "tls1_1",
    "tls1",
]

print(f"Checking TLS versions for {host}:{port} using openssl s_client...")
supported_versions_openssl = []
for version_str in tls_versions_for_openssl:
    if check_tls_version_with_openssl(host, port, version_str):
        supported_versions_openssl.append(version_str)

if supported_versions_openssl:
    print(f"n{host}:{port} supports the following TLS versions (via openssl s_client): {', '.join(supported_versions_openssl)}")
else:
    print(f"n{host}:{port} did not respond to any of the checked TLS versions via openssl s_client.")

利点:

  • `openssl s_client`はTLSプロトコルの詳細なデバッグに優れており、どのバージョンがネゴシエートされたか、証明書情報などを確認できます。
  • Pythonスクリプトから簡単に呼び出せます。

注意点:

  • `openssl`コマンドがシステムにインストールされている必要があります。
  • コマンドの出力解析は、バージョンによってフォーマットが変わる可能性があるため、注意が必要です。

オンラインTLSスキャナー

ImmuniWeb, SSL Labs (Qualys), Hardenize などのオンラインサービスは、指定したホストのTLS設定を詳細にスキャンし、サポートされているTLSバージョン、脆弱性などをレポートしてくれます。Pythonスクリプトからこれらのサービスを直接呼び出すのはAPIが公開されていない限り難しいですが、手動での確認や、CI/CDパイプラインで定期的に実行する際の参照情報として非常に有用です。

まとめ

PythonでTLSのバージョンを確認するには、`ssl`モジュールの低レベルAPIを利用して各バージョンを試行するのが最も直接的で確実な方法です。`requests`のような高レベルライブラリは、直接的なTLSバージョン確認機能は提供しませんが、HTTPS接続の成否から間接的な互換性を推測できます。より詳細なデバッグや、システム全体でのTLSサポート状況の確認には、`openssl s_client`のような外部コマンドや、専門のオンラインスキャナーツールとの連携が有効です。どの方法を選択するかは、確認したい目的(特定のサーバーのサポート状況、クライアント環境の能力、セキュリティ設定の検証など)によって異なります。