diff -r 2b4a89f82485 Doc/library/ssl.rst --- a/Doc/library/ssl.rst Fri Sep 14 01:40:41 2012 +0300 +++ b/Doc/library/ssl.rst Fri Sep 14 23:06:19 2012 +1000 @@ -675,7 +675,9 @@ (rather than :meth:`SSLContext.wrap_socket`), this is a custom context object created for this SSL socket. - .. versionadded:: 3.2 + .. versionchanged:: 3.4 + The context can now be changed in the middle of a ``SSLSocket`` + connection. SSL Contexts @@ -780,6 +782,34 @@ .. versionadded:: 3.3 +.. method:: SSLContext.set_servername_callback(server_name_callback) + + Register a callback function that will be called after the TLS Client Hello + handshake message has been received by the SSL/TLS server with a server name + indicatation. The server name indication is specified in `RFC6066 Transport + Layer Security (TLS) Extensions: Extension Definitions + `_. + + The callback function, *server_name_callback*, will be called with three + arguments the first being the ``SSLSocket``, and the second is a string that + represents the server name that the client is intending to communicate. + The server name argument is the idna decoded server name. If there is a + idna decoding error the SSL/TLS socket will terminate with a handshake + failure. The third argument is the ``SSLContext``. + + A typical use of this callback is to change the ``SSLContext`` and hence the + certificate chain of the `SSLSocket` to a certificate that matches the + server name. + + If *server_name_callback* is ``None`` then the callback is disabled. Only + one callback can be set per ``SSLContext``. + + If an exception is raised in the *server_name_callback* function the + SSL/TLS connection terminates with a fatal TLS alert message described as + Internal Error. + + .. versionadded:: 3.4 + .. method:: SSLContext.load_dh_params(dhfile) Load the key generation parameters for Diffie-Helman (DH) key exchange. diff -r 2b4a89f82485 Lib/ssl.py --- a/Lib/ssl.py Fri Sep 14 01:40:41 2012 +0300 +++ b/Lib/ssl.py Fri Sep 14 23:06:19 2012 +1000 @@ -236,7 +236,7 @@ _context=None): if _context: - self.context = _context + self._context = _context else: if server_side and not certfile: raise ValueError("certfile must be specified for server-side " @@ -245,16 +245,16 @@ raise ValueError("certfile must be specified") if certfile and not keyfile: keyfile = certfile - self.context = SSLContext(ssl_version) - self.context.verify_mode = cert_reqs + self._context = SSLContext(ssl_version) + self._context.verify_mode = cert_reqs if ca_certs: - self.context.load_verify_locations(ca_certs) + self._context.load_verify_locations(ca_certs) if certfile: - self.context.load_cert_chain(certfile, keyfile) + self._context.load_cert_chain(certfile, keyfile) if npn_protocols: - self.context.set_npn_protocols(npn_protocols) + self._context.set_npn_protocols(npn_protocols) if ciphers: - self.context.set_ciphers(ciphers) + self._context.set_ciphers(ciphers) self.keyfile = keyfile self.certfile = certfile self.cert_reqs = cert_reqs @@ -296,7 +296,7 @@ if connected: # create the SSL object try: - self._sslobj = self.context._wrap_socket(self, server_side, + self._sslobj = self._context._wrap_socket(self, server_side, server_hostname) if do_handshake_on_connect: timeout = self.gettimeout() @@ -308,6 +308,14 @@ except socket_error as x: self.close() raise x + @property + def context(self): + return self._context + + @context.setter + def context(self, ctx): + self._context = ctx + self._set_context(ctx) def dup(self): raise NotImplemented("Can't dup() %s instances" % diff -r 2b4a89f82485 Lib/test/keycert3.pem --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/test/keycert3.pem Fri Sep 14 23:06:19 2012 +1000 @@ -0,0 +1,128 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + ef:6b:57:59:66:d1:cc:94 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=thecaserverof.python.org + Validity + Not Before: Aug 21 23:49:26 2012 GMT + Not After : Aug 19 23:49:26 2022 GMT + Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=dangerserver + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:de:fa:4f:3e:e6:0b:75:09:db:34:45:22:f9:ff: + bb:34:b8:22:e6:1b:0f:93:d7:61:6b:27:22:6f:ff: + a2:b2:dd:f8:b3:ee:62:7b:8c:07:5b:b9:1a:d8:a9: + e7:83:74:c1:c8:51:92:00:7b:f5:cd:13:5a:60:db: + f7:f9:9b:9f:f3:6f:37:32:d5:c4:e3:34:f0:78:ab: + 7f:4f:da:6d:48:e2:f3:08:c3:76:f8:90:1b:1f:89: + db:f5:5e:cd:8a:2c:f4:3f:7b:7c:d9:96:ba:3b:1e: + 94:04:2d:88:30:86:4a:0d:ec:95:ff:02:70:98:13: + c8:d9:1c:82:9f:20:4b:6d:09:ad:0e:db:31:9e:f6: + f3:72:e4:c3:98:d4:c6:eb:2c:85:cb:7c:b5:73:b2: + be:e5:04:ed:02:93:54:1e:42:88:67:fe:94:b1:2e: + a5:10:3a:d1:89:eb:21:fd:fe:ba:fd:fb:c4:ba:ad: + f7:47:01:7b:ba:40:9b:a4:57:a4:7b:cf:96:c2:77: + 47:36:9a:49:10:08:cc:17:dd:f3:ce:d3:a5:dc:db: + 56:4b:09:3a:95:0d:65:53:fb:da:bb:54:35:e5:39: + 60:24:ac:be:69:8e:07:f7:51:08:d8:58:a3:eb:cd: + c1:c0:a5:e1:cd:97:68:bc:12:47:58:71:9e:11:fb: + 6e:41 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 6E:9A:04:77:18:CF:D9:58:79:EF:D0:72:51:27:25:58:EA:D9:7A:BF + X509v3 Authority Key Identifier: + keyid:5C:D0:15:0D:3B:EE:F3:AA:42:F8:68:86:1D:B6:B8:78:CB:0A:F3:E3 + + Signature Algorithm: sha1WithRSAEncryption + 11:b3:ca:8e:0f:45:87:d5:4e:ca:4c:b7:c2:e6:e6:60:2b:8e: + e9:08:8f:5b:db:db:f3:92:ff:62:d1:60:5e:74:e7:f9:f3:65: + 65:1f:fb:a8:dd:dd:97:c6:be:51:0c:20:0a:d3:9b:0b:3b:53: + a3:78:0b:ca:7c:ae:22:3e:b1:7e:77:b9:bb:0e:8a:8b:2c:7d: + 2e:d5:dd:90:92:e8:b1:62:e0:27:ef:a3:d8:76:d6:da:a2:c9: + 28:0f:1b:fc:e4:b9:45:b5:4a:ec:85:77:d4:95:88:70:08:44: + f0:53:7e:62:a7:1f:a6:5d:54:d5:38:24:96:8d:62:e7:af:80: + 9b:b8:9b:77:89:14:5c:de:e8:50:cf:f8:9f:c3:0d:86:bc:ed: + 08:5d:a3:57:cd:2f:61:de:01:2f:9f:9a:45:85:2e:31:d9:b0: + a7:8c:da:94:1b:1f:88:8f:f4:42:92:a7:72:0b:9b:8e:e0:7f: + 1b:6c:93:39:35:13:ce:9e:6a:90:dc:d2:45:07:0f:a4:9a:ca: + ba:c8:75:f3:6a:4e:f3:33:db:f5:5c:26:dc:78:d3:7f:10:25: + 1b:84:45:ac:34:f7:16:78:df:f7:30:0e:d9:5d:e9:52:46:b6: + 51:57:d9:22:1e:0a:91:56:18:df:31:8c:3d:4e:cb:88:c6:61: + 20:59:fa:b7 +-----BEGIN CERTIFICATE----- +MIIDzjCCAragAwIBAgIJAO9rV1lm0cyUMA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNV +BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xITAfBgNVBAMMGHRoZWNhc2VydmVyb2YucHl0 +aG9uLm9yZzAeFw0xMjA4MjEyMzQ5MjZaFw0yMjA4MTkyMzQ5MjZaMGIxCzAJBgNV +BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xFTATBgNVBAMMDGRhbmdlcnNlcnZlcjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN76Tz7mC3UJ2zRFIvn/uzS4IuYb +D5PXYWsnIm//orLd+LPuYnuMB1u5Gtip54N0wchRkgB79c0TWmDb9/mbn/NvNzLV +xOM08Hirf0/abUji8wjDdviQGx+J2/VezYos9D97fNmWujselAQtiDCGSg3slf8C +cJgTyNkcgp8gS20JrQ7bMZ7283Lkw5jUxusshct8tXOyvuUE7QKTVB5CiGf+lLEu +pRA60YnrIf3+uv37xLqt90cBe7pAm6RXpHvPlsJ3RzaaSRAIzBfd887TpdzbVksJ +OpUNZVP72rtUNeU5YCSsvmmOB/dRCNhYo+vNwcCl4c2XaLwSR1hxnhH7bkECAwEA +AaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0 +ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFG6aBHcYz9lYee/QclEnJVjq2Xq/MB8G +A1UdIwQYMBaAFFzQFQ077vOqQvhohh22uHjLCvPjMA0GCSqGSIb3DQEBBQUAA4IB +AQARs8qOD0WH1U7KTLfC5uZgK47pCI9b29vzkv9i0WBedOf582VlH/uo3d2Xxr5R +DCAK05sLO1OjeAvKfK4iPrF+d7m7DoqLLH0u1d2QkuixYuAn76PYdtbaoskoDxv8 +5LlFtUrshXfUlYhwCETwU35ipx+mXVTVOCSWjWLnr4CbuJt3iRRc3uhQz/ifww2G +vO0IXaNXzS9h3gEvn5pFhS4x2bCnjNqUGx+Ij/RCkqdyC5uO4H8bbJM5NRPOnmqQ +3NJFBw+kmsq6yHXzak7zM9v1XCbceNN/ECUbhEWsNPcWeN/3MA7ZXelSRrZRV9ki +HgqRVhjfMYw9TsuIxmEgWfq3 +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDe+k8+5gt1Cds0 +RSL5/7s0uCLmGw+T12FrJyJv/6Ky3fiz7mJ7jAdbuRrYqeeDdMHIUZIAe/XNE1pg +2/f5m5/zbzcy1cTjNPB4q39P2m1I4vMIw3b4kBsfidv1Xs2KLPQ/e3zZlro7HpQE +LYgwhkoN7JX/AnCYE8jZHIKfIEttCa0O2zGe9vNy5MOY1MbrLIXLfLVzsr7lBO0C +k1QeQohn/pSxLqUQOtGJ6yH9/rr9+8S6rfdHAXu6QJukV6R7z5bCd0c2mkkQCMwX +3fPO06Xc21ZLCTqVDWVT+9q7VDXlOWAkrL5pjgf3UQjYWKPrzcHApeHNl2i8EkdY +cZ4R+25BAgMBAAECggEAAw/k7rQyDBVqkTkx1cURSRQORqKwgBNBHzuWWXtlPJeX +xsDLUW8G2teb6sXKjB+aWanlG2SYZ4yQfSwIJ8FfXPR2CZit74DWnJGeWfZh8dJM +DB7DD37LII/AGYqHwBIF+Kk1ebd8LzuoRLZrXF87vR02oh3idfQvmeuuT4keAA1P +Y91T3QntHXzUX6nLdAGLrUA4VpYdr5PoGAJ76FuScAAzaiQaVs0/+rLd1k+3x/6C +YapKjdFgZfibtSHPmDSstSjRGP9e4b4tILUTEzzag3J/Tmr9SyKk72go2NKXg14K +z18L47ElrS3YMeycgtOv0//2GNRBu2/vm/Y3lu6AAQKBgQD+c1hmn/YYvpkE/Cnr +WuDPGSoHVYUmxRvnlBf1CPoRZePBT/jSJ4Lr+lgHmQ2HmHkeSImqa8xSqyNot4t+ +OMiJweISmqOUm+lCzICEf/Bi8gmihTUr2ZEltnkyOnrHCRHeiJxGlzb6Z3VggQ9Q +PXbPmoueCvyU4TbwQXmzxP6ZwQKBgQDgVeb+qSkznh/6xiX9L0zxEMmx3ub5feSQ +Q6ahZ1nfzieB7m6TgUwnkQpq12pZBmxbTYqjS94PE2D89FR54j1ngLfVqFiWjf25 +ZguToNP44EqpRb6FmDtgBwMnp7GD66QK4PDSEJ97Q8NflsXKK3BcDSGh+IxyF9KC +WrcwhCT0gQKBgDPt2GnBVZlBNLt2txtoRZ8edJxgkLcT3RkK+USx2083qx6lspM5 +Kxf71IFgdAlrTbSneykDrQRmFujlJJvS/OJYZkoDs0S9+QH/+G6SKb4XxW2cn/he +A6hdsChXFP2P1UzENpiVfcvm12alkDmBAcFmuf28IkZEHFE2G59bodZBAoGBAMIk +9ZUctqCKTyjLBSN5JQBXRAZgXso6Tm0fbvyuIdSTaBOFUkLrmgl/92EBbnL/IRVf +JQUvakznu38OwHD+/58sgWwxlgEtfxBNJOWtQOVYzqRxzHF3jqMLToqqEPGXI8+a +Xi+d3TdjLJj0NdZCA6pWXu362SkAPtk7QuCE7CqBAoGBAK52TVLfKXNeX7I5wHe3 +eYrj27NKsigNp7BVHvBevzSGFrZ8/NueTusBOV680G73+MqJQzRyGYMfxL3tp7dI +TTgg16Lna4Q5En5gWmoVBhcFINqeY1kQU2Vk/2EzyqEGeY1pkwxT4o4LPuLPtghG +VeFytyIO5t4lgVtkCt7pMCbM +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE REQUEST----- +MIICpzCCAY8CAQAwYjELMAkGA1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRo +cmF4MSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEVMBMGA1UE +AwwMZGFuZ2Vyc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +3vpPPuYLdQnbNEUi+f+7NLgi5hsPk9dhaycib/+ist34s+5ie4wHW7ka2Knng3TB +yFGSAHv1zRNaYNv3+Zuf8283MtXE4zTweKt/T9ptSOLzCMN2+JAbH4nb9V7Niiz0 +P3t82Za6Ox6UBC2IMIZKDeyV/wJwmBPI2RyCnyBLbQmtDtsxnvbzcuTDmNTG6yyF +y3y1c7K+5QTtApNUHkKIZ/6UsS6lEDrRiesh/f66/fvEuq33RwF7ukCbpFeke8+W +wndHNppJEAjMF93zztOl3NtWSwk6lQ1lU/vau1Q15TlgJKy+aY4H91EI2Fij683B +wKXhzZdovBJHWHGeEftuQQIDAQABoAAwDQYJKoZIhvcNAQEFBQADggEBABmg/CZ3 +5D5l4PhqI98RFJ/3HvRs0ycNtTs1GsE/4s2oBIu8XIYU6nlH5D/42rrXHuPun/mU +mQs9X43KE1YDSiTgr6iU4D6GYTiOTn9+O8IGrM2ODKEFe7/iCOWgDObnwogz1EwM +mCVc2LHw2xMZXp6ylN/Ps63oDa0ySX9zf6za6WXnYrob1dF9t8tKIk48Eg/1aZZ6 +Di1mbkStp9exXygUdjlQXNcRQPf5Aehw4fbsTXiSmma9qpZC2JZKQalGIOuiRFOV +ZKim+anfR4wKcj+lXU8rK1yK6GTA2Mms7orHVMO9jljNcSp3VxnPwvvrS4RQTa+N +pPZiTPE4rdWHEjA= +-----END CERTIFICATE REQUEST----- diff -r 2b4a89f82485 Lib/test/keycert4.pem --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/test/keycert4.pem Fri Sep 14 23:06:19 2012 +1000 @@ -0,0 +1,129 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + ef:6b:57:59:66:d1:cc:93 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=thecaserverof.python.org + Validity + Not Before: Aug 21 23:46:38 2012 GMT + Not After : Aug 19 23:46:38 2022 GMT + Subject: C=XY, L=Castle Anthrax, O=Python Software Foundatio, CN=fakehostname + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:9b:ad:2f:c5:21:e4:ed:d5:10:c1:54:39:78:18: + 2e:21:05:fa:b3:67:9b:40:2e:d2:6e:af:83:c5:99: + da:e9:6f:df:87:46:90:11:07:31:c8:ca:ce:16:db: + f8:28:f4:23:16:fa:0b:cb:4c:15:bd:fc:f9:de:d6: + e4:3d:11:9f:6f:60:5e:5a:05:26:06:6e:91:df:1d: + 77:db:0a:69:65:24:76:b6:4a:c0:15:44:6c:46:f0: + 5d:f5:1d:4d:78:ae:99:f6:92:21:52:d7:3b:8b:d6: + b8:5c:75:e2:c3:a4:69:70:17:bf:fe:50:df:42:b7: + d2:99:71:1d:23:86:29:46:70:8a:c3:76:e5:a1:7b: + f9:e0:eb:5b:27:76:6d:83:03:e3:2a:9a:87:a1:22: + 3f:e1:bf:72:04:bd:8e:03:ce:95:2a:ca:aa:0f:1c: + c5:5e:8e:13:9a:c7:cd:8b:b5:dc:7a:ed:0a:53:e2: + 7c:9a:89:40:0d:ca:94:e3:6f:61:28:3a:a3:de:41: + 00:23:f5:d3:34:56:62:e1:b8:98:81:a5:fa:cc:a1: + 8c:0c:0c:5b:94:58:12:06:cb:77:3e:5d:e9:ae:8e: + ac:0a:07:83:73:99:c0:e1:e3:22:d1:11:b3:d2:d6: + 7d:3d:b1:93:e0:2e:50:5d:02:fa:d2:4a:8b:bc:70: + 73:91 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 81:ED:7B:9D:B0:E3:47:21:14:C6:AF:F3:14:01:95:16:F0:9C:8B:15 + X509v3 Authority Key Identifier: + keyid:5C:D0:15:0D:3B:EE:F3:AA:42:F8:68:86:1D:B6:B8:78:CB:0A:F3:E3 + + Signature Algorithm: sha1WithRSAEncryption + 0c:2d:2d:81:57:5e:d8:a8:5b:f5:86:e6:53:33:a2:b3:42:98: + f8:8f:17:1f:56:06:1b:c8:8f:c2:a0:fd:64:b3:f1:46:54:80: + 20:a2:b4:69:88:54:65:6a:f7:f2:56:57:e9:84:58:2c:fa:eb: + 25:5e:8d:13:6f:7b:98:be:ba:6f:ab:6d:e4:31:ca:e6:ce:0a: + 27:ee:fd:ed:ff:f2:7d:25:2b:49:44:58:cc:fd:7f:ba:76:92: + b7:eb:1e:0b:ff:ba:4b:d0:0c:ea:5e:1a:c4:02:e5:b8:ef:9c: + fd:66:ae:6b:b6:70:41:ec:92:73:dd:d8:ae:68:56:5a:8b:43: + ec:98:30:5f:69:4e:45:c4:43:ad:cc:7b:79:bd:fd:79:f3:76: + a6:d0:1b:d7:0a:a8:5b:4c:ca:9a:03:92:5e:05:37:6c:86:42: + 69:f3:d7:9c:66:d4:b7:51:57:59:f3:9d:9d:da:cd:d1:3f:86: + f7:57:2e:64:f9:e9:d6:1b:c2:f4:0e:aa:a7:ef:da:20:22:64: + b7:f1:07:c1:79:00:c5:ca:1a:06:a4:0a:7f:a0:9f:30:b7:ad: + 35:75:87:a4:20:05:e6:0c:6a:24:49:cd:cc:42:11:c9:be:e4: + ac:df:6f:ed:d1:6a:91:4e:af:0c:a9:64:52:73:0e:52:d3:e9: + 29:7b:9e:dc + +-----BEGIN CERTIFICATE----- +MIIDzTCCArWgAwIBAgIJAO9rV1lm0cyTMA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNV +BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xITAfBgNVBAMMGHRoZWNhc2VydmVyb2YucHl0 +aG9uLm9yZzAeFw0xMjA4MjEyMzQ2MzhaFw0yMjA4MTkyMzQ2MzhaMGExCzAJBgNV +BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEiMCAGA1UECgwZUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpbzEVMBMGA1UEAwwMZmFrZWhvc3RuYW1lMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm60vxSHk7dUQwVQ5eBguIQX6s2eb +QC7Sbq+DxZna6W/fh0aQEQcxyMrOFtv4KPQjFvoLy0wVvfz53tbkPRGfb2BeWgUm +Bm6R3x132wppZSR2tkrAFURsRvBd9R1NeK6Z9pIhUtc7i9a4XHXiw6RpcBe//lDf +QrfSmXEdI4YpRnCKw3bloXv54OtbJ3ZtgwPjKpqHoSI/4b9yBL2OA86VKsqqDxzF +Xo4TmsfNi7Xceu0KU+J8molADcqU429hKDqj3kEAI/XTNFZi4biYgaX6zKGMDAxb +lFgSBst3Pl3pro6sCgeDc5nA4eMi0RGz0tZ9PbGT4C5QXQL60kqLvHBzkQIDAQAB +o3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRl +ZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUge17nbDjRyEUxq/zFAGVFvCcixUwHwYD +VR0jBBgwFoAUXNAVDTvu86pC+GiGHba4eMsK8+MwDQYJKoZIhvcNAQEFBQADggEB +AAwtLYFXXtioW/WG5lMzorNCmPiPFx9WBhvIj8Kg/WSz8UZUgCCitGmIVGVq9/JW +V+mEWCz66yVejRNve5i+um+rbeQxyubOCifu/e3/8n0lK0lEWMz9f7p2krfrHgv/ +ukvQDOpeGsQC5bjvnP1mrmu2cEHsknPd2K5oVlqLQ+yYMF9pTkXEQ63Me3m9/Xnz +dqbQG9cKqFtMypoDkl4FN2yGQmnz15xm1LdRV1nznZ3azdE/hvdXLmT56dYbwvQO +qqfv2iAiZLfxB8F5AMXKGgakCn+gnzC3rTV1h6QgBeYMaiRJzcxCEcm+5Kzfb+3R +apFOrwypZFJzDlLT6Sl7ntw= +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbrS/FIeTt1RDB +VDl4GC4hBfqzZ5tALtJur4PFmdrpb9+HRpARBzHIys4W2/go9CMW+gvLTBW9/Pne +1uQ9EZ9vYF5aBSYGbpHfHXfbCmllJHa2SsAVRGxG8F31HU14rpn2kiFS1zuL1rhc +deLDpGlwF7/+UN9Ct9KZcR0jhilGcIrDduWhe/ng61sndm2DA+MqmoehIj/hv3IE +vY4DzpUqyqoPHMVejhOax82Ltdx67QpT4nyaiUANypTjb2EoOqPeQQAj9dM0VmLh +uJiBpfrMoYwMDFuUWBIGy3c+XemujqwKB4NzmcDh4yLREbPS1n09sZPgLlBdAvrS +Sou8cHORAgMBAAECggEAPTAaCZKj53MKbeKL4A8o+mPeQOulEtoUbdrgoScy6vPE +2Oe8MmUwhSppTITTCk3DCQHo5tQV66HmGFdASFyH3P/0tc82PerxvM9ZT6fM+ysU +2duRmgpVwKAzXedRjvCZpVeUb3oXdoRD3FudcKZLpvo+xVfGGPQle5gBCvhterie +WBbywv9hkZdWrPozWSApE4AZmNVjpBEzkvot+tzNdhACSHDYAvoiiceF9hd8gK7p +NkgNM2ygxDTKPru4727oTsmOPRjgGbfryXqUNflbybk8zJvqGNhy74ozDIt5dwZN +zQcw6Z5+RwvLqKMpEbZZD2+KJtF9PYWNLoYYBT5VzQKBgQDPIeLZYrsxcocUwAcL +XHFY28Vl/FNTGt7uZLq2HpOPHmcWV7LoFFAEA6fWlTlKIe8uUR+h23oKhASH/MJK +JVkHWjEhPvbW9GBHOV+/erVRBNzpRKuw2hpX9i/ctpun4VjKnxZK511mE6FAx/H+ +IX9pyWmwvtM28hGPAcp1UH+M4wKBgQDAZ4kv0JNwW8c6TleZDm/u5YZcFNzLRTlO +Z2AVq9vryvXsswUBnb4Kdolh7quxbESNKJp9h9BycQqrcPAOqVvf5oCAOdkPFkAN +v/8btRpJfNLF17L3+udvWVrKUOoKM7I9ab1ktJdPkfD1YZOIhkiMakRachaI2HcD +rjNb08M7+wKBgFiXB60uWx3U/A1V7C1hxmDY7l6mQwUiUwLnNiw6e6YUvMaDj9NP +DMIKxjgy7qPUS7YloD/b3SCuQjnfU0HfI2rPmn+7rFtXfe44jckZtH8Ic2uZoU0s +m2PPubzOpVhyO0W+MQysU/gaoTdRzSCCNzqJmJitpSy07+mYPH1chpE9AoGBAK0X +uByTCUFo3+f9eOMSUPLb/K80eZpJUY/OgmucrAIKGopyV0un7MMe9uNqw32fiGBx +qeLK32VFezLXLLAT/rqEdRBmencPLlrwYoI7wEVijYA9WB2t1K2g9oPSnWrGLVEA +oslJimKkSeGHgiGtpPDl3U59GvbLvu5Jtpyi+x4nAoGAEnBdp9oo5c2fcjdT3Mwa +U68RSVs4dWb/dXw6kR8wcqGs/7c6s87FHMEQwHH3o+VuJMrxVVD1piOL3qYsASG6 +28W30EFZV5W7G3mfK+oy5sEI8k/oNC5FswG/c6XKQwKYHFRkrwXkyELJVsfkYPfE +VBblXCr9WYR8swB4+RS6m6M= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE REQUEST----- +MIICpjCCAY4CAQAwYTELMAkGA1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRo +cmF4MSIwIAYDVQQKDBlQeXRob24gU29mdHdhcmUgRm91bmRhdGlvMRUwEwYDVQQD +DAxmYWtlaG9zdG5hbWUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCb +rS/FIeTt1RDBVDl4GC4hBfqzZ5tALtJur4PFmdrpb9+HRpARBzHIys4W2/go9CMW ++gvLTBW9/Pne1uQ9EZ9vYF5aBSYGbpHfHXfbCmllJHa2SsAVRGxG8F31HU14rpn2 +kiFS1zuL1rhcdeLDpGlwF7/+UN9Ct9KZcR0jhilGcIrDduWhe/ng61sndm2DA+Mq +moehIj/hv3IEvY4DzpUqyqoPHMVejhOax82Ltdx67QpT4nyaiUANypTjb2EoOqPe +QQAj9dM0VmLhuJiBpfrMoYwMDFuUWBIGy3c+XemujqwKB4NzmcDh4yLREbPS1n09 +sZPgLlBdAvrSSou8cHORAgMBAAGgADANBgkqhkiG9w0BAQUFAAOCAQEAUu3HoMHq +EPSOSCtM3UJRdDtxWex7MzacvLkNuWsM9oPnBNGzk5QftixlkUAQ3SyVMXL8YHBS +oM/4DPDBbxgPckEupVEtz4merLtJKu3q16I67aA+VBDiaoWlyNvDzzCggIYMfcZ+ +ZTbIrcjaOOnH/yz8yfmlAZoBxPdvLxLW4ls+oOf/nyeIr4LvWxmAly9dQvNCEyD+ +z/E9WlUS9RCJGguPoE5cbbzLHX+EutXWVrdYpTRJ1NfGb2siue2lUcCmHSUqhJ0Y +wZwbhfqqwKEwDSBAO+6g9a14uVP1+xxY1MT00W8q4JN8ey6swqg7YmMqfClkacBV +cgLrnEQUCG+QtA== +-----END CERTIFICATE REQUEST----- diff -r 2b4a89f82485 Lib/test/pycacert.pem --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/test/pycacert.pem Fri Sep 14 23:06:19 2012 +1000 @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIJAO9rV1lm0cyRMA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNV +BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xITAfBgNVBAMMGHRoZWNhc2VydmVyb2YucHl0 +aG9uLm9yZzAeFw0xMjA4MjEyMzI5MTlaFw0yMjA4MTkyMzI5MTlaMG4xCzAJBgNV +BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xITAfBgNVBAMMGHRoZWNhc2VydmVyb2YucHl0 +aG9uLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALMhaq3ht3i6 +QGes1X+z6ZitzYCy/Gwb1tD6WbN9hDjDyvY90ARnjeRC4cVV11+7sBNr5+NYLEMh +IadWMRbM6nycsBoApQ0m9hF04MKLJdBj8WmeTuaREnetuD2PVD7FmfUAVVTyze9g +V0jyXNYXipjJA29vBhws9kyKpw0Aj10PQyrbzuUs6sObCks45DziXR7raBhqPSsC +/JOyafeW4X7hAu6VdCmmAh3peJHjmDsQsTGMBN9th9mT5uYiEFSUUp6OXdyvOZbb +ZE2k6q4nuojKiiq0QQ6Og2YX0cch1N0KQ1Dmpm5VUEvqjsaiI5dsabRhHqVhcDFL +bYXFSELwz9sCAwEAAaNQME4wHQYDVR0OBBYEFFzQFQ077vOqQvhohh22uHjLCvPj +MB8GA1UdIwQYMBaAFFzQFQ077vOqQvhohh22uHjLCvPjMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBADu0NUrbTbBKaAxoKpHj4uKg8CP+M2dsGtmHINaE +gyDqo3TnDlbv0Mi0pJtGP9u3nYed637+NWixf70OcvWT9iPAkYAiIGfDG2gwx/1B +R6pMH3N8VPcuy86jL66AXCSsBMQ2QHLP1nvX5CFArbsRutrL6NkfXoQpyBksYFCk +wRgdva+BPCpcmA2DuCOKYMblqAeFyavsWI84jWtCeTIKaKWPQVlIlNvzVBQsUVZ9 +nO3Hp1HJcWSGhxz2grI+OoemS3NLZ0XhSnwAS2vn8rRn9SSyZw1EJwvHW8NuIzJ/ +Ja82f0utLMK9llSP0NnVN/3uN6BgoccneaqBi34trvOOzlM= +-----END CERTIFICATE----- diff -r 2b4a89f82485 Lib/test/pycakey.pem --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/test/pycakey.pem Fri Sep 14 23:06:19 2012 +1000 @@ -0,0 +1,31 @@ +password: python +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIoHZu9AJMBBcCAggA +MBQGCCqGSIb3DQMHBAgBxsElrJvkrASCBMh/ZTbJZ24TcIaneAtFxWDMcC85Ajzb +Tj8YrIP3L06FsRR/i1LkCfY5satqzBGL7Uw46aXWlj/qWX815RYALBjQ7rQBzUgz +UK1D4S6qljqxT+0jvxdUoKtxJENZydPVhWZlOjKHftE6peb+R0IHTaPf9YGP6uDO +Ic2IeGMDJlKWFv5NnGfivhZeNdY7/Fm2mKmk072E7crqGgNlokLVVmDfEvqTn+aU +WfWIrNwvroK2y8Py6KBBUD6fhh8oYBOoo1dnMsAaMj+Rw1CkjJXkmL9paNNuj7Hx +4kcboPMdQ0jT4uk+F1156AiiwnhKp/wLYQg9tR4qPmRUAs0+G0hsi+YIC2YesrP/ +VHOWtJzveLUpGfSBoycRY1oKs9smPiv1Yp0ZY18jmrJ22u/dVj9HdyBQt3oCe8Ij +9lIUNbKXj2yI+z2Tk1e5irADRWSbn6OaN4zIKMlwid1vr1LNDQSfkbgT2TuiP5uY +dblmp7SkAV/kIZtobLvoEjTwAVG2DyyeuB2yLdNezsxZ9jB/PhkTzJnZ2qo7RMr/ +qes1hzVtzUebEAI9Q51UhbuxmlhShdlrytk4Rj4U+aaB0IUllGAlgD2SzdtRno2K +z3igFlrHr30rGL9QtX7uEFKY3/BQ1dQ+tzABgbBOgMGj1mN1A1+ta0PbOEeSuspA +udWguFgSZIfR81U0d/vqoEJcMI+GSVVlD5kqm72vjDyN9oy9V05qHhubuGtega5t +2MHQojDqwCyhQV1EcSM1mAUX+lwJixHGjAZLpSgVPTSGIoqH/GkB/2p7t3jnBTgs +Np9p031R5KDR3WV0rAvVKWej3Cf2JoQtraZYg/yPAo9cX83TkUSwHNfR3HtcsCCv +JB7Q7mzGWUni5L30WV5EvRMH0L9I77ft/jqWJ++LgJhzIu74MWmgu4l719r9hD23 +Zb3T4KfX7WCMGxrmZ5NPQiPVYeFJzn3bPm+RgV/nwlCEqNy1AfkJkGWeBFJg/4wt +0CO9ClgFoUeyIMBBZ9zszh7x8JTi3wmFS84R9t2x17LIVzu9vPy0S49R0DNxBXj5 +m/0xZiWiMSgOx6rUCEx+QDP6RAQYrjOcz8JNxPgOygIO8h75GF0DkvoKoKyALoxA +33fOPHeUtGIYryYNG51vYvmYDEPEo0tUYibL0Xs23oZXZ5jmMVGaNESrGxR1JlSA +TfOzbgnDtdAz28DfYXPJJeQqkj2CQIBTZzvOCATUOZCjJcxBA7+FTKJeXvSM4TlW +gTLNjf8kfEQbJdEmMSlYhrwwWYBH7PX52LEQVTjIpXhYKHcyddNzrgNnXnY5vwqA +JtWYlB0x3cC2pjgBgFuWU0LOqid/SoTym/gC3KgjGUvrKKf1WRbcMLc/nMi7cUxm +BSHag4Vd6PhLo6D443buhDCkSxcT+7/ZK+dJgMaVIIv6gm2Nez2IqQCuvuJuZD4o +9zLT96h5adeMvXNxfl96WWn7Dw03cwSDx309k0/YQiEytDNiwnt3MqmxlF57LbTX +JY8xoFZXfW/ykLQ15GYDh7HPX+TzdlwjBwZtyHSYoSO2LkH2n+eXTyVo2RP11gDV +4WFAVUzZpQBy9UctNAwnapnCyU/7/tHa4uGOvTRvolrpxiPCn69yp9Qnsc67GV7l +CgE= +-----END ENCRYPTED PRIVATE KEY----- diff -r 2b4a89f82485 Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py Fri Sep 14 01:40:41 2012 +0300 +++ b/Lib/test/test_ssl.py Fri Sep 14 23:06:19 2012 +1000 @@ -37,6 +37,11 @@ # are meant to authenticate. CERTFILE = data_file("keycert.pem") +CERTFILE2 = data_file("keycert2.pem") +CERTFILE3 = data_file("keycert3.pem") +CERTFILE4 = data_file("keycert4.pem") +CAFORKEY = data_file("pycacert.pem") +CAFORKEY_PASSWORD = "python" BYTES_CERTFILE = os.fsencode(CERTFILE) ONLYCERT = data_file("ssl_cert.pem") ONLYKEY = data_file("ssl_key.pem") @@ -1238,7 +1243,7 @@ raise AssertionError("Use of invalid cert should have failed!") def server_params_test(client_context, server_context, indata=b"FOO\n", - chatty=True, connectionchatty=False): + chatty=True, connectionchatty=False, sni_name=None): """ Launch a server, connect a client to it and try various reads and writes. @@ -1248,7 +1253,8 @@ chatty=chatty, connectionchatty=False) with server: - with client_context.wrap_socket(socket.socket()) as s: + with client_context.wrap_socket(socket.socket(), + server_hostname=sni_name) as s: s.connect((HOST, server.port)) for arg in [indata, bytearray(indata), memoryview(indata)]: if connectionchatty: @@ -1272,6 +1278,7 @@ stats.update({ 'compression': s.compression(), 'cipher': s.cipher(), + 'peercert': s.getpeercert(True), 'client_npn_protocol': s.selected_npn_protocol() }) s.close() @@ -1941,6 +1948,70 @@ if len(stats['server_npn_protocols']) else 'nothing' self.assertEqual(server_result, expected, msg % (server_result, "server")) + @unittest.skipUnless(ssl.HAS_SNI, "SNI support needed for this test") + def test_sni(self): + hostname = "funny" + def servername(s,servername,oldctx): + nonlocal hostname + newctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + newctx.load_cert_chain(CERTFILE4) + #s.context = newctx + s._set_context(newctx) + hostname = servername + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + + self.assertRaises(ValueError, server_context.set_servername_callback, 4) + + server_context.set_servername_callback(servername) + server_context.load_cert_chain(CERTFILE) + + client_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + #client_context.verify_mode = ssl.CERT_REQUIRED + #client_context.load_verify_locations(CAFORKEY) + stats = server_params_test(client_context, server_context, + chatty=True, + connectionchatty=True, + sni_name='supermessage') + + self.assertEqual(hostname, "supermessage", + "sni server didn't get hostname 'supermessage' got %s" % hostname) + + def certreceived(stats,serial): + try: + from tempfile import NamedTemporaryFile + peercert = ssl.DER_cert_to_PEM_cert(stats['peercert']) + with NamedTemporaryFile() as f: + f.write(bytes(peercert,'UTF-8')) + f.flush() + cert = ssl._ssl._test_decode_cert(f.name) + if support.verbose: + sys.stderr.write("peercert: %s\n" % pprint.pformat(cert)) + self.assertEqual(cert['serialNumber'], serial, + "Callback didn't change certificate got %s" % pprint.pformat(cert)) + except KeyError: + raise AssertionError('[peercert][serialNumber] missing %s' % pprint.pformat(peercert)) + + certreceived(stats, 'EF6B575966D1CC93') + + hostname = "funny" + server_context.set_servername_callback(None) + + server_context.load_cert_chain(CERTFILE3) + stats = server_params_test(client_context, server_context, + chatty=True, + connectionchatty=True, + sni_name='notfunny') + self.assertEqual(hostname, "funny", + "sni server callback disabled. hostname changed") + + certreceived(stats, 'EF6B575966D1CC94') + + def dummycallback(s,name,ctx): + pass + + # Check our object reference counting + server_context.set_servername_callback(dummycallback) def test_main(verbose=False): if support.verbose: @@ -1962,7 +2033,8 @@ print(" HAS_SNI = %r" % ssl.HAS_SNI) for filename in [ - CERTFILE, SVN_PYTHON_ORG_ROOT_CERT, BYTES_CERTFILE, + CERTFILE, CERTFILE3, CERTFILE4, SVN_PYTHON_ORG_ROOT_CERT, + BYTES_CERTFILE, ONLYCERT, ONLYKEY, BYTES_ONLYCERT, BYTES_ONLYKEY, BADCERT, BADKEY, EMPTYCERT]: if not os.path.exists(filename): diff -r 2b4a89f82485 Modules/_ssl.c --- a/Modules/_ssl.c Fri Sep 14 01:40:41 2012 +0300 +++ b/Modules/_ssl.c Fri Sep 14 23:06:19 2012 +1000 @@ -181,6 +181,9 @@ char *npn_protocols; int npn_protocols_len; #endif +#ifndef OPENSSL_NO_TLSEXT + PyObject *set_hostname; +#endif } PySSLContext; typedef struct { @@ -458,6 +461,7 @@ PySSL_BEGIN_ALLOW_THREADS self->ssl = SSL_new(ctx); PySSL_END_ALLOW_THREADS + SSL_set_app_data(self->ssl,self); SSL_set_fd(self->ssl, sock->sock_fd); #ifdef SSL_MODE_AUTO_RETRY SSL_set_mode(self->ssl, SSL_MODE_AUTO_RETRY); @@ -1164,6 +1168,28 @@ #endif } +static PyObject *PySSL_set_context(PySSLSocket *self, PyObject *args) { + PySSLContext *ctx; + + if (!PyArg_ParseTuple(args, "O!", &PySSLContext_Type, &ctx)) { + return NULL; + } + if (self->ssl) + SSL_set_SSL_CTX(self->ssl, ctx->ctx); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PySSL_set_context_doc, +"_set_context(ctx)\n\ +\ +This changes the context associated with the SSLSocket. This is typically\n\ +used from within a callback function set by the set_servername_callback\n\ +on the SSLContext to change the certificate information associated with the\n\ +SSLSocket before the cryptographic exchange handshake messages\n"); + + + static void PySSL_dealloc(PySSLSocket *self) { if (self->peer_cert) /* Possible not to have one? */ @@ -1620,6 +1646,7 @@ #ifdef OPENSSL_NPN_NEGOTIATED {"selected_npn_protocol", (PyCFunction)PySSL_selected_npn_protocol, METH_NOARGS}, #endif + {"_set_context", (PyCFunction)PySSL_set_context, METH_VARARGS}, {"compression", (PyCFunction)PySSL_compression, METH_NOARGS}, {"shutdown", (PyCFunction)PySSL_SSLshutdown, METH_NOARGS, PySSL_SSLshutdown_doc}, @@ -2301,6 +2328,100 @@ } #endif +#ifndef OPENSSL_NO_TLSEXT +static int _servername_callback(SSL *s, int *al, void *args) { + int ret = SSL_TLSEXT_ERR_OK; + PySSLContext *ssl_ctx = (PySSLContext *) args; + PySSLSocket *ssl = SSL_get_app_data(s); + + PyObject *servername_idna; + const char * servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + + if (ssl_ctx->set_hostname == NULL) { + /* remove race condition in this the call back while if removing the + * callback is in progress */ + return ret; + } + + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + PyObject *servername_o = PyBytes_FromString(servername); + if (servername_o == NULL) { + goto error; + } + servername_idna = PyUnicode_FromEncodedObject(servername_o,"idna",NULL); + Py_DECREF(servername_o); + if (servername_idna == NULL) { + goto error; + } + PyObject *arglist = Py_BuildValue("(OsO)", ssl, servername, ssl_ctx); + PyObject *result = PyObject_CallObject(ssl_ctx->set_hostname, arglist); + Py_DECREF(arglist); + + if (result == NULL) { + *al = SSL_AD_HANDSHAKE_FAILURE; + ret = SSL_TLSEXT_ERR_ALERT_FATAL; + } else { + /* ssl/t1_enc.c in the openssl source has some other alerts that may + * be useful */ + Py_DECREF(result); + } + + PyGILState_Release(gstate); + return ret; + +error: + PyGILState_Release(gstate); + *al = SSL_AD_INTERNAL_ERROR; + ret = SSL_TLSEXT_ERR_ALERT_FATAL; + return ret; +} + +PyDoc_STRVAR(PySSL_set_servername_callback_doc, +"set_servername(method)\n\ +\ +This sets a callback that will be called when a server name is provided by\n\ +the SSL/TLS client in the SNI extension.\n\ +\ +If the argument is None then the callback is disabled. The method is called\n\ +with the SSLSocket, the server name as a string, and the SSLContext object.\n\ +See RFC 6066 for details of the SNI"); +#endif + +static PyObject * +set_servername_callback(PySSLContext *self, PyObject *args) +{ +#ifndef OPENSSL_NO_TLSEXT + PyObject *o = self->set_hostname; + + if (!PyArg_ParseTuple(args, "O", &self->set_hostname)) { + return NULL; + } + + if (self->set_hostname == Py_None) { + SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL); + self->set_hostname = NULL; + } else { + if (!PyCallable_Check(self->set_hostname)) { + PyErr_SetString(PyExc_ValueError, + "not a callable object"); + return NULL; + } + Py_INCREF(self->set_hostname); + SSL_CTX_set_tlsext_servername_callback(self->ctx, _servername_callback); + SSL_CTX_set_tlsext_servername_arg(self->ctx, self); + } + if (o != NULL) { + Py_DECREF(o); + } + Py_RETURN_NONE; +#else + PyErr_SetString(PyExc_NotImplementedError, + "This TLS extension servername callback is not in the current OpenSSL library."); +#endif +} + static PyGetSetDef context_getsetlist[] = { {"options", (getter) get_options, (setter) set_options, NULL}, @@ -2330,6 +2451,8 @@ {"set_ecdh_curve", (PyCFunction) set_ecdh_curve, METH_O, NULL}, #endif + {"set_servername_callback", (PyCFunction) set_servername_callback, + METH_VARARGS, PySSL_set_servername_callback_doc}, {NULL, NULL} /* sentinel */ };