diff --git a/Lib/ssl.py b/Lib/ssl.py --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -74,10 +74,11 @@ SSL_ERROR_SYSCALL, SSL_ERROR_SSL, SSL_ERROR_WANT_CONNECT, + SSL_ERROR_UNKNOWN_PSK_IDENTITY, SSL_ERROR_EOF, SSL_ERROR_INVALID_ERROR_CODE, ) -from _ssl import HAS_SNI +from _ssl import HAS_SNI, HAS_TLS_SRP from socket import getnameinfo as _getnameinfo from socket import error as socket_error @@ -180,16 +181,23 @@ family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None, suppress_ragged_eofs=True, ciphers=None, server_hostname=None, + tls_username=None, tls_password=None, srp_vbase=None, _context=None): if _context: self.context = _context else: - if server_side and not certfile: - raise ValueError("certfile must be specified for server-side " - "operations") + if server_side and not (certfile or srp_vbase): + raise ValueError("certfile and/or srp_vbase must be " + "specified for server-side operations") if keyfile and not certfile: raise ValueError("certfile must be specified") + if server_side and (tls_username or tls_password): + raise ValueError("tls_username and tls_password can only " + "be specified in client mode") + if (not server_side) and srp_vbase: + raise ValueError("srp_vbase can only be specified " + "in server mode") if certfile and not keyfile: keyfile = certfile self.context = SSLContext(ssl_version) @@ -200,12 +208,19 @@ self.context.load_cert_chain(certfile, keyfile) if ciphers: self.context.set_ciphers(ciphers) + if tls_username: + self.context.set_tls_username_password(tls_username, + tls_password) + if srp_vbase: + self.context.set_srp_vbase(srp_vbase) self.keyfile = keyfile self.certfile = certfile self.cert_reqs = cert_reqs self.ssl_version = ssl_version self.ca_certs = ca_certs self.ciphers = ciphers + self.tls_username = tls_username + self.srp_vbase = srp_vbase if server_side and server_hostname: raise ValueError("server_hostname can only be specified " "in client mode") @@ -298,6 +313,12 @@ self._checkClosed() return self._sslobj.peer_certificate(binary_form) + def tls_username(self): + """Returns the TLS-SRP username of the underlying channel.""" + + self._checkClosed() + return self._sslobj.tls_username() + def cipher(self): self._checkClosed() if not self._sslobj: @@ -475,6 +496,8 @@ ssl_version=self.ssl_version, ca_certs=self.ca_certs, ciphers=self.ciphers, + tls_username=self.tls_username, + srp_vbase=self.srp_vbase, do_handshake_on_connect= self.do_handshake_on_connect), addr) @@ -488,14 +511,17 @@ server_side=False, cert_reqs=CERT_NONE, ssl_version=PROTOCOL_SSLv23, ca_certs=None, do_handshake_on_connect=True, - suppress_ragged_eofs=True, ciphers=None): + suppress_ragged_eofs=True, ciphers=None, + tls_username=None, tls_password=None, srp_vbase=None): return SSLSocket(sock=sock, keyfile=keyfile, certfile=certfile, server_side=server_side, cert_reqs=cert_reqs, ssl_version=ssl_version, ca_certs=ca_certs, do_handshake_on_connect=do_handshake_on_connect, suppress_ragged_eofs=suppress_ragged_eofs, - ciphers=ciphers) + ciphers=ciphers, + tls_username=tls_username, tls_password=tls_password, + srp_vbase=srp_vbase) # some utility functions diff --git a/Lib/test/ssl_servers.py b/Lib/test/ssl_servers.py --- a/Lib/test/ssl_servers.py +++ b/Lib/test/ssl_servers.py @@ -15,6 +15,7 @@ HOST = support.HOST CERTFILE = os.path.join(here, 'keycert.pem') +TLS_SRP_VBASE = os.path.join(here, 'tlssrp.srpv') # This one's based on HTTPServer, which is based on SocketServer @@ -82,8 +83,23 @@ self.log_date_time_string(), format%args)) +class SimpleTestHTTPRequestHandler(BaseHTTPRequestHandler): + """Superclass for test HTTP request handlers below. + """ -class StatsRequestHandler(BaseHTTPRequestHandler): + def do_GET(self, send_body=True): + raise NotImplemented + + def do_HEAD(self): + """Serve a HEAD request.""" + self.do_GET(send_body=False) + + def log_request(self, format, *args): + if support.verbose: + BaseHTTPRequestHandler.log_request(self, format, *args) + + +class StatsRequestHandler(SimpleTestHTTPRequestHandler): """Example HTTP request handler which returns SSL statistics on GET requests. """ @@ -103,14 +119,22 @@ if send_body: self.wfile.write(body) - def do_HEAD(self): - """Serve a HEAD request.""" - self.do_GET(send_body=False) - def log_request(self, format, *args): - if support.verbose: - BaseHTTPRequestHandler.log_request(self, format, *args) +class TLSUsernameRequestHandler(SimpleTestHTTPRequestHandler): + """HTTP request handler that returns the TLS username on GET requests.""" + server_version = "TLSUsernameHTTPS/1.0" + + def do_GET(self, send_body=True): + """Return the TLS username.""" + sock = self.rfile.raw._sock + body = sock.tls_username().encode('utf-8') + self.send_response(200) + self.send_header("Content-type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + if send_body: + self.wfile.write(body) class HTTPSServerThread(threading.Thread): @@ -142,10 +166,17 @@ self.server.shutdown() -def make_https_server(case, certfile=CERTFILE, host=HOST, handler_class=None): +def make_https_server(case, certfile=CERTFILE, host=HOST, + use_tls_srp=False, handler_class=None): # we assume the certfile contains both private key and certificate - context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + if use_tls_srp: + sslproto = ssl.PROTOCOL_TLSv1 + else: + sslproto = ssl.PROTOCOL_SSLv23 + context = ssl.SSLContext(sslproto) context.load_cert_chain(certfile) + if use_tls_srp: + context.set_srp_vbase(TLS_SRP_VBASE) server = HTTPSServerThread(context, host, handler_class) flag = threading.Event() server.start(flag) diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -532,6 +532,34 @@ resp = h.getresponse() self.assertEqual(resp.status, 404) + def test_local_good_srp_login(self): + import ssl + from test.ssl_servers import make_https_server, \ + TLSUsernameRequestHandler + server = make_https_server(self, use_tls_srp=True, + handler_class=TLSUsernameRequestHandler) + context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + context.set_tls_username_password('user', 'secret') + h = client.HTTPSConnection('localhost', server.port, context=context) + h.request('GET', '/') + resp = h.getresponse() + self.assertEqual(resp.status, 200) + self.assertEqual(resp.read(), b'user') + + def test_local_bad_srp_login(self): + import ssl + from test.ssl_servers import make_https_server + server = make_https_server(self, use_tls_srp=True) + failure_errnos = {'user': ssl.SSL_ERROR_SSL, # bad record MAC + 'baduser': ssl.SSL_ERROR_UNKNOWN_PSK_IDENTITY} + for tls_username, expect_errno in failure_errnos.items(): + context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + context.set_tls_username_password(tls_username, 'badpw') + h = client.HTTPSConnection('localhost', server.port, context=context) + with self.assertRaises(ssl.SSLError) as cm: + h.request('GET', '/') + self.assertEqual(cm.exception.errno, expect_errno) + class RequestBodyTest(TestCase): """Test cases where a request includes a message body.""" diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -50,6 +50,8 @@ WRONGCERT = data_file("XXXnonexisting.pem") BADKEY = data_file("badkey.pem") +TLS_SRP_VBASE = data_file("tlssrp.srpv") + def handle_error(prefix): exc_format = ' '.join(traceback.format_exception(*sys.exc_info())) @@ -90,6 +92,7 @@ ssl.CERT_OPTIONAL ssl.CERT_REQUIRED self.assertIn(ssl.HAS_SNI, {True, False}) + self.assertIn(ssl.HAS_TLS_SRP, {True, False}) def test_random(self): v = ssl.RAND_status() @@ -189,11 +192,17 @@ "certfile must be specified", ssl.wrap_socket, sock, keyfile=CERTFILE) self.assertRaisesRegex(ValueError, - "certfile must be specified for server-side operations", + "certfile and/or srp_vbase must be specified " + "for server-side operations", ssl.wrap_socket, sock, server_side=True) self.assertRaisesRegex(ValueError, - "certfile must be specified for server-side operations", + "certfile and/or srp_vbase must be specified " + "for server-side operations", ssl.wrap_socket, sock, server_side=True, certfile="") + self.assertRaisesRegex(ValueError, + "certfile and/or srp_vbase must be specified " + "for server-side operations", + ssl.wrap_socket, sock, server_side=True, srp_vbase="") s = ssl.wrap_socket(sock, server_side=True, certfile=CERTFILE) self.assertRaisesRegex(ValueError, "can't connect in server-side mode", s.connect, (HOST, 8080)) @@ -397,6 +406,31 @@ # Issue #10989: crash if the second argument type is invalid self.assertRaises(TypeError, ctx.load_verify_locations, None, True) + def test_set_tls_username_password(self): + if ssl.HAS_TLS_SRP: + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + ctx.set_tls_username_password('user', 'secret') + ctx.set_tls_username_password('', '') + self.assertRaises(TypeError, ctx.set_tls_username_password, + None, None) + self.assertRaises(TypeError, ctx.set_tls_username_password, + '', None) + self.assertRaises(TypeError, ctx.set_tls_username_password, 1, 2) + else: + self.assertRaises(Exception, ctx.set_tls_username_password, + 'user', 'secret') + + def test_set_srp_vbase(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + if ssl.HAS_TLS_SRP: + ctx.set_srp_vbase(TLS_SRP_VBASE) + self.assertRaises(TypeError, ctx.set_srp_vbase, None) + with self.assertRaises(IOError) as cm: + ctx.set_srp_vbase(WRONGCERT) + self.assertEqual(cm.exception.errno, errno.ENOENT) + else: + self.assertRaises(Exception, ctx.set_srp_vbase, TLS_SRP_VBASE) + @skip_if_broken_ubuntu_ssl def test_session_stats(self): for proto in PROTOCOLS: @@ -788,6 +822,7 @@ def __init__(self, certificate=None, ssl_version=None, certreqs=None, cacerts=None, + srp_vbase=None, chatty=True, connectionchatty=False, starttls_server=False, ciphers=None, context=None): if context: @@ -802,6 +837,8 @@ self.context.load_verify_locations(cacerts) if certificate: self.context.load_cert_chain(certificate) + if srp_vbase: + self.context.set_srp_vbase(srp_vbase) if ciphers: self.context.set_ciphers(ciphers) self.chatty = chatty @@ -842,6 +879,37 @@ def stop(self): self.active = False + class ThreadedUsernameCheckServer(ThreadedEchoServer): + + class ConnectionHandler(ThreadedEchoServer.ConnectionHandler): + + def run(self): + self.running = True + if not self.wrap_conn(): + return + while self.running: + try: + msg = self.read() + stripped = msg.strip() + conn_username = self.sslconn.tls_username().encode('utf8') + if stripped == conn_username: + if support.verbose and self.server.connectionchatty: + sys.stdout.write(" server: read %r, sending " + "back %r" + % (stripped, conn_username)) + self.write(conn_username) + else: + if support.verbose and self.server.connectionchatty: + sys.stdout.write(" server: read %r, " + "expected %r" + % (stripped, conn_username)) + self.write(b"ERROR") + raise + finally: + self.close() + self.running = False + self.server.stop() + class AsyncoreEchoServer(threading.Thread): # this one's based on asyncore.dispatcher @@ -1068,6 +1136,58 @@ % (ssl.get_protocol_name(client_protocol), ssl.get_protocol_name(server_protocol))) + def tls_srp_test(srp_username, srp_password, use_server_cert, + expect_ssl_err=None): + if use_server_cert: + # No way to specify "only SRP cipher suites with certs", so + # pick a specific one. + ciphers = 'SRP-RSA-AES-256-CBC-SHA' + server_cert = CERTFILE + else: + # Non-certificate SRP cipher suites. + ciphers = '!aRSA:!aDSS:kSRP' + server_cert = None + server = ThreadedUsernameCheckServer(srp_vbase=TLS_SRP_VBASE, + certificate=server_cert, + ciphers=ciphers, + chatty=False, + connectionchatty=False) + flag = threading.Event() + server.start(flag) + # wait for it to start + flag.wait() + # try to connect + try: + with socket.socket() as sock: + if support.verbose: + sys.stdout.write(" client: use TLS-SRP login %r/%r\n" + % (srp_username, srp_password)) + s = ssl.wrap_socket(sock, + certfile=server_cert, + ssl_version=ssl.PROTOCOL_TLSv1, + ciphers=ciphers, + tls_username=srp_username, + tls_password=srp_password) + s.connect((HOST, server.port)) + indata = srp_username.encode('utf8') + s.write(indata) + outdata = s.read() + if outdata != indata: + raise AssertionError( + "Expected to read TLS-SRP username %r from server; " + "received %r" % (indata, outdata)) + except ssl.SSLError as e: + if e.errno != expect_ssl_err: + raise AssertionError( + "Caught SSLError %r; expected to catch SSLError %r" + % (e, expect_ssl_err)) + if support.verbose: + sys.stdout.write(" client: caught expected %r\n" % e) + else: + if expect_ssl_err: + raise AssertionError( + "TLS-SRP login succeeded with credentials %r/%r" + % (srp_username, srp_password)) class ThreadedTests(unittest.TestCase): @@ -1139,6 +1259,22 @@ bad_cert_test(os.path.join(os.path.dirname(__file__) or os.curdir, "badkey.pem")) + def test_tls_srp_login(self): + if not ssl.HAS_TLS_SRP: + return + if support.verbose: + sys.stdout.write("\n") + for use_cert in (False, True): + tls_srp_test('user', 'secret', use_cert) + tls_srp_test('user', 'badpw', use_cert, + expect_ssl_err=ssl.SSL_ERROR_SSL) + tls_srp_test('baduser', 'badpw', use_cert, + expect_ssl_err=ssl.SSL_ERROR_UNKNOWN_PSK_IDENTITY) + tls_srp_test('baduser', '', use_cert, + expect_ssl_err=ssl.SSL_ERROR_UNKNOWN_PSK_IDENTITY) + tls_srp_test('', '', use_cert, + expect_ssl_err=ssl.SSL_ERROR_SSL) + def test_rude_shutdown(self): """A brutal shutdown of an SSL server should raise an IOError in the client when attempting handshake. @@ -1583,6 +1719,7 @@ (ssl.OPENSSL_VERSION, ssl.OPENSSL_VERSION_INFO)) print(" under %s" % plat) print(" HAS_SNI = %r" % ssl.HAS_SNI) + print(" HAS_TLS_SRP = %r" % ssl.HAS_TLS_SRP) for filename in [ CERTFILE, SVN_PYTHON_ORG_ROOT_CERT, BYTES_CERTFILE, diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py --- a/Lib/test/test_urllib2_localnet.py +++ b/Lib/test/test_urllib2_localnet.py @@ -328,6 +328,9 @@ for (header, value) in headers: self.send_header(header, value % {'port':self.port}) + if hasattr(self.rfile.raw._sock, 'tls_username'): + self.send_header("X-TLS-Username", + self.rfile.raw._sock.tls_username()) if body: self.send_header("Content-type", "text/plain") self.end_headers() @@ -384,14 +387,16 @@ handler.port = port return handler - def start_https_server(self, responses=None, certfile=CERT_localhost): + def start_https_server(self, responses=None, certfile=CERT_localhost, + use_tls_srp=False): if not hasattr(urllib.request, 'HTTPSHandler'): self.skipTest('ssl support required') from test.ssl_servers import make_https_server if responses is None: responses = [(200, [], b"we care a bit")] handler = GetRequestHandler(responses) - server = make_https_server(self, certfile=certfile, handler_class=handler) + server = make_https_server(self, certfile=certfile, handler_class=handler, + use_tls_srp=use_tls_srp) handler.port = server.port return handler @@ -474,6 +479,13 @@ self.urlopen("https://localhost:%s/bizarre" % handler.port, cafile=CERT_fakehostname) + def test_https_using_tls_srp(self): + handler = self.start_https_server(use_tls_srp=True) + res = urllib.request.urlopen("https://localhost:%s/" % handler.port, + tls_username='user', tls_password='secret') + self.assertEqual(res.info()["X-TLS-Username"], 'user') + self.assertEqual(res.read(), b"we care a bit") + def test_sending_headers(self): handler = self.start_server() req = urllib.request.Request("http://localhost:%s/" % handler.port, diff --git a/Lib/test/tlssrp.srpv b/Lib/test/tlssrp.srpv new file mode 100644 --- /dev/null +++ b/Lib/test/tlssrp.srpv @@ -0,0 +1,1 @@ +V Ql5DzWwAmEgV4dDweruehUJwNhVy.dQj7DM3/IV1YMYCPyIaXyDeRQDYLbE1hlBrDMLeKczhAtRZZijgSq6rKoTnoO5596bTM4qhOYG4tQ9UXtK3i.Djab3orJmB0jEa4vxZJ6eI9R1otfKsGnFGuRKBbturYTspSl/4aPYwAW03peIDjXLI1CXb6KOsQ9kE2n2MOH1JzoCQC7Gge0hwG4o8V15oRHwsZxXYmNy8bUcKbbECnQMw8F8AqPrpQtji EbOGsaJKDQxeoMN4/l4P/PFQE7u user 1536 diff --git a/Lib/test/tlssrp.srpv.attr b/Lib/test/tlssrp.srpv.attr new file mode 100644 --- /dev/null +++ b/Lib/test/tlssrp.srpv.attr @@ -0,0 +1,1 @@ +unique_subject = yes diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -116,12 +116,16 @@ _opener = None def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - *, cafile=None, capath=None): + *, cafile=None, capath=None, tls_username=None, tls_password=None): global _opener - if cafile or capath: + if cafile or capath or tls_username: if not _have_ssl: raise ValueError('SSL support not available') - context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + if tls_username: + sslproto = ssl.PROTOCOL_TLSv1 + else: + sslproto = ssl.PROTOCOL_SSLv23 + context = ssl.SSLContext(sslproto) context.options |= ssl.OP_NO_SSLv2 if cafile or capath: context.verify_mode = ssl.CERT_REQUIRED @@ -129,6 +133,8 @@ check_hostname = True else: check_hostname = False + if tls_username: + context.set_tls_username_password(tls_username, tls_password) https_handler = HTTPSHandler(context=context, check_hostname=check_hostname) opener = build_opener(https_handler) elif _opener is None: diff --git a/Misc/ACKS b/Misc/ACKS --- a/Misc/ACKS +++ b/Misc/ACKS @@ -811,6 +811,7 @@ George Sipe J. Sipprell Kragen Sitaker +Quinn Slack Eric V. Smith Christopher Smith Gregory P. Smith diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -485,6 +485,8 @@ - Issue #9347: Fix formatting for tuples in argparse type= error messages. +- Issue #XXXX: Add TLS-SRP (RFC 5054) support to ssl, _ssl, http, and urllib. + Build ----- diff --git a/Modules/_ssl.c b/Modules/_ssl.c --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -45,6 +45,7 @@ PY_SSL_ERROR_SYSCALL, /* look at error stack/return value/errno */ PY_SSL_ERROR_ZERO_RETURN, PY_SSL_ERROR_WANT_CONNECT, + PY_SSL_ERROR_UNKNOWN_PSK_IDENTITY, /* start of non ssl.h errorcodes */ PY_SSL_ERROR_EOF, /* special case of SSL_ERROR_SYSCALL */ PY_SSL_ERROR_NO_SOCKET, /* socket has been GC'd */ @@ -122,9 +123,20 @@ # undef HAVE_SSL_CTX_CLEAR_OPTIONS #endif +/* TLS-SRP got added to OpenSSL in 1.0.1 */ +#if OPENSSL_VERSION_NUMBER < 0x10001000 +# define OPENSSL_NO_SRP +#endif +#ifndef OPENSSL_NO_SRP +# include "openssl/srp.h" +#endif + typedef struct { PyObject_HEAD SSL_CTX *ctx; +#ifndef OPENSSL_NO_SRP + SRP_VBASE *srp_vbase; +#endif } PySSLContext; typedef struct { @@ -143,6 +155,7 @@ static int check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing); static PyObject *PySSL_peercert(PySSLSocket *self, PyObject *args); +static PyObject *PySSL_tls_username(PySSLSocket *self); static PyObject *PySSL_cipher(PySSLSocket *self); #define PySSLContext_Check(v) (Py_TYPE(v) == &PySSLContext_Type) @@ -240,6 +253,8 @@ else { /* possible? */ errstr = "A failure in the SSL library occurred"; } + if (ERR_GET_REASON(e) == 1115) /* TODO(sqs) */ + p = PY_SSL_ERROR_UNKNOWN_PSK_IDENTITY; break; } default: @@ -919,6 +934,22 @@ peer certificate, or None if no certificate was provided. This will\n\ return the certificate even if it wasn't validated."); +static PyObject *PySSL_tls_username (PySSLSocket *self) +{ + char *username; + +#ifndef OPENSSL_NO_SRP + if (self->ssl == NULL) + Py_RETURN_NONE; + username = SSL_get_srp_username(self->ssl); + if (username == NULL) + Py_RETURN_NONE; + return PyUnicode_FromString(username); +#else + Py_RETURN_NONE; +#endif +} + static PyObject *PySSL_cipher (PySSLSocket *self) { PyObject *retval, *v; @@ -1387,6 +1418,7 @@ {"peer_certificate", (PyCFunction)PySSL_peercert, METH_VARARGS, PySSL_peercert_doc}, {"cipher", (PyCFunction)PySSL_cipher, METH_NOARGS}, + {"tls_username", (PyCFunction)PySSL_tls_username, METH_NOARGS}, {"shutdown", (PyCFunction)PySSL_SSLshutdown, METH_NOARGS, PySSL_SSLshutdown_doc}, {NULL, NULL} @@ -1708,6 +1740,95 @@ } static PyObject * +set_tls_username_password(PySSLContext *self, PyObject *args) +{ + char *username; + char *password; + + if (!PyArg_ParseTuple(args, "ss:set_tls_username_password", + &username, &password)) + return NULL; + +#ifndef OPENSSL_NO_SRP + if (!SSL_CTX_set_srp_username(self->ctx, username)) { + PyErr_SetString(PySSLErrorObject, + "Error in setting TLS username."); + return NULL; + } + + if (!SSL_CTX_set_srp_password(self->ctx, password)) { + PyErr_SetString(PySSLErrorObject, + "Error in setting TLS password."); + return NULL; + } + + Py_RETURN_NONE; +#else + PyErr_SetString(PyExc_Exception, + "TLS-SRP is not supported in this build of Python."); + return NULL; +#endif +} + +#ifndef OPENSSL_NO_SRP +static int tls_srp_server_param_cb(SSL *ssl, int *ad, void *arg) +{ + char *username; + SRP_user_pwd *u; + PySSLContext *self = (PySSLContext *)arg; + + username = SSL_get_srp_username(ssl); + u = SRP_VBASE_get_by_user(self->srp_vbase, username); + + if (u == NULL) { + *ad = SSL_AD_UNKNOWN_PSK_IDENTITY; + return SSL3_AL_FATAL; + } + + if (SSL_set_srp_server_param(ssl, u->N, u->g, u->s, u->v, u->info) < 0) { + *ad = SSL_AD_INTERNAL_ERROR; + return SSL3_AL_FATAL; + } + + return SSL_ERROR_NONE; +} + +#endif /* OPENSSL_NO_SRP */ + +static PyObject * +set_srp_vbase(PySSLContext *self, PyObject *args) +{ + char *srpvbase_path; + int r; + + errno = 0; + if (!PyArg_ParseTuple(args, "s:set_srp_vbase", &srpvbase_path)) + return NULL; + +#ifndef OPENSSL_NO_SRP + self->srp_vbase = SRP_VBASE_new(NULL); + r = SRP_VBASE_init(self->srp_vbase, srpvbase_path); + if (r != SRP_NO_ERROR) { + if (errno != 0) { + PyErr_SetFromErrno(PyExc_IOError); + } else { + PyErr_SetString(PySSLErrorObject, "Could not open SRP vbase."); + } + return NULL; + } + + SSL_CTX_set_srp_cb_arg(self->ctx, self); + SSL_CTX_set_srp_username_callback(self->ctx, tls_srp_server_param_cb); + + Py_RETURN_NONE; +#else + PyErr_SetString(PyExc_Exception, + "TLS-SRP is not supported in this build of Python."); + return NULL; +#endif +} + +static PyObject * context_wrap_socket(PySSLContext *self, PyObject *args, PyObject *kwds) { char *kwlist[] = {"sock", "server_side", "server_hostname", NULL}; @@ -1809,6 +1930,10 @@ METH_VARARGS | METH_KEYWORDS, NULL}, {"load_verify_locations", (PyCFunction) load_verify_locations, METH_VARARGS | METH_KEYWORDS, NULL}, + {"set_tls_username_password", (PyCFunction) set_tls_username_password, + METH_VARARGS, NULL}, + {"set_srp_vbase", (PyCFunction) set_srp_vbase, + METH_VARARGS, NULL}, {"session_stats", (PyCFunction) session_stats, METH_NOARGS, NULL}, {"set_default_verify_paths", (PyCFunction) set_default_verify_paths, @@ -2093,6 +2218,8 @@ PY_SSL_ERROR_SSL); PyModule_AddIntConstant(m, "SSL_ERROR_WANT_CONNECT", PY_SSL_ERROR_WANT_CONNECT); + PyModule_AddIntConstant(m, "SSL_ERROR_UNKNOWN_PSK_IDENTITY", + PY_SSL_ERROR_UNKNOWN_PSK_IDENTITY); /* non ssl.h errorcodes */ PyModule_AddIntConstant(m, "SSL_ERROR_EOF", PY_SSL_ERROR_EOF); @@ -2130,6 +2257,14 @@ Py_INCREF(r); PyModule_AddObject(m, "HAS_SNI", r); +#ifndef OPENSSL_NO_SRP + r = Py_True; +#else + r = Py_False; +#endif + Py_INCREF(r); + PyModule_AddObject(m, "HAS_TLS_SRP", r); + /* OpenSSL version */ /* SSLeay() gives us the version of the library linked against, which could be different from the headers version.