SSLContextのデフォルト引数
公式のリファレンスでは、3系ではpython3.5.3以降の情報しか掲載されていません。
ということで実際のソースを見ることにします。
私の環境では以下のパスにありました。
/usr/lib/python3.4/ssl.py
class SSLContext(_SSLContext): """An SSLContext holds various SSL-related configuration options and data, such as certificates and possibly a private key."""
_SSLContextとは何か。
import _ssl # if we can't import it, let the error propagate (中略) from _ssl import _SSLContext
_sslモジュールがなんなのか正確には不明ですが、おそらくdevelop環境か昔のモジュールのどちらかでしょう。将来修正する予定のモジュールを読み込んでいると思われます。
_sslはどこにあるのか。こういう場合は、コンソールを起動して読み込んで見たらパスがわかります。
>>> import _ssl >>> _ssl <module '_ssl' from '/usr/lib/python3.4/lib-dynload/_ssl.cpython-34m-arm-linux-gnueabihf.so'>
.so拡張子はLinuxで共有ライブラリ(Shared Object)を指すそうです。初めて知りました。
とりあえず_ssl.cpython-34m-arm-linux-gnueabihf.soを開いてみましたが、コンパイルされていて読めませんでした(cpythonて書いてあるし)。コメントっぽいのは読めますが、残念ながらよくわかりません。行き詰まりました。
ここで気づいたのですが、普通に呼び出してみたらどうなるのでしょうか。
>> ssl.SSLContext() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __new__() missing 1 required positional argument: 'protocol'
あっ。デフォルト引数ないんですね。そうですか。protocol引数に必ず値を指定しなくてはいけないようです。
create_default_context()を使用したとき
そもそもなぜSSLContextのデフォルト引数を調べようかと思ったかというと、create_default_context()を使用して呼び出していたからです。なので本来ソースを見るべきはこちらの関数だったというオチです。
def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None, capath=None, cadata=None): """Create a SSLContext object with default settings. NOTE: The protocol and settings may change anytime without prior deprecation. The values represent a fair balance between maximum compatibility and security. """ if not isinstance(purpose, _ASN1Object): raise TypeError(purpose) context = SSLContext(PROTOCOL_SSLv23) # SSLv2 considered harmful. context.options |= OP_NO_SSLv2 # SSLv3 has problematic security and is only required for really old # clients such as IE6 on Windows XP context.options |= OP_NO_SSLv3 # disable compression to prevent CRIME attacks (OpenSSL 1.0+) context.options |= getattr(_ssl, "OP_NO_COMPRESSION", 0)
PROTOCOL_SSLv23をセットしています。それにOP_NO_SSLv2やOP_NO_SSLv3のフラグを立てて、脆弱性のあるSSLv2等でネゴシエーションしないようにしています。
ではPROTOCOL_SSLv23はなんなのか。
ssl.PROTOCOL_SSLv23(原文) data:PROTOCOL_TLS のエイリアスです。 バージョン 3.6 で撤廃: 代わりに PROTOCOL_TLS を使用してください。
ssl.PROTOCOL_TLS¶(原文) チャンネル暗号化プロトコルとして、クライアントとサーバの両方がサポートする中の、プロトコルバージョンが最も大きなものを選択します。その名前にも関わらず、このオプションは “SSL” とともに “TLS” プロトコルも選択できます。 バージョン 2.7.13 で追加.
ssl.PROTOCOL_TLS(原文) チャンネル暗号化プロトコルとして、クライアントとサーバの両方がサポートする中の、プロトコルバージョンが最も大きなものを選択します。その名前にも関わらず、このオプションは “SSL” とともに “TLS” プロトコルも選択できます。 バージョン 3.5.3 で追加.
ssl.PROTOCOL_TLSはバージョン3.5.3で追加されていますが、バージョン2.7.13でもすでに追加されています。バージョン2.7.13で追加されているということは、python3.4.2でも同じ挙動するはずと考えるのは甘いでしょうか。たぶん甘いでしょう。わざわざ3.5.3で追加と書いてあるのは、2系から3系に移植する際に実装を後回しにしている可能性があります。
これ以上正確な挙動を知ろうと思うなら、実際の挙動を見る他ないと思います。
たとえばgmailとSTARTTLSネゴシエーションをしたときの通信の様子は次のようになります(2017/10)
>> smtplib.SMTP("smtp.gmail.com", 587) <smtplib.SMTP object at 0x7671df70> >> M = smtplib.SMTP("smtp.gmail.com", 587) >> M.set_debuglevel(True) >> context = ssl.create_default_context() >> M.starttls(context=context) send: 'ehlo [127.0.1.1]\r\n' reply: b'250-smtp.gmail.com at your service, [114.147.68.118]\r\n' reply: b'250-SIZE 35882577\r\n' reply: b'250-8BITMIME\r\n' reply: b'250-STARTTLS\r\n' reply: b'250-ENHANCEDSTATUSCODES\r\n' reply: b'250-PIPELINING\r\n' reply: b'250-CHUNKING\r\n' reply: b'250 SMTPUTF8\r\n' reply: retcode (250); Msg: b'smtp.gmail.com at your service, [114.147.68.118]\nS IZE 35882577\n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nPIPELINING\nCHUNKING\nSMT PUTF8' send: 'STARTTLS\r\n' reply: b'220 2.0.0 Ready to start TLS\r\n' reply: retcode (220); Msg: b'2.0.0 Ready to start TLS' (220, b'2.0.0 Ready to start TLS')