Add an example demonstrating SNI usage.
diff --git a/examples/sni/README b/examples/sni/README
new file mode 100644
index 0000000..4c74eb5
--- /dev/null
+++ b/examples/sni/README
@@ -0,0 +1,19 @@
+This directory contains client and server examples for the "Server Name
+Indication" (SNI) feature.
+
+Run server.py with no arguments. It will accept one client connection and
+then exit. It has two certificates it can use, one for "example.invalid"
+and another for "another.invalid". If a client indicates one of these names
+to it, it will use the corresponding certificate for that connection (if a
+client doesn't indicate a name or indicates another name, it won't try to
+use any certificate).
+
+Run client.py with one argument, the server name to indicate. For example:
+
+ $ python client.py example.invalid
+ Connecting... connected ('127.0.0.1', 8443)
+ Server subject is <X509Name object '/OU=Security/O=pyOpenSSL/CN=example.invalid/ST=New York/C=US/emailAddress=invalid@example.invalid/L=New York'>
+ $
+
+Depending on what hostname is supplied, the server will select a different
+certificate to use and the client output will be different.
diff --git a/examples/sni/another.invalid.crt b/examples/sni/another.invalid.crt
new file mode 100644
index 0000000..995e14c
--- /dev/null
+++ b/examples/sni/another.invalid.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICqTCCAhICAQEwDQYJKoZIhvcNAQEEBQAwgZwxETAPBgNVBAsTCFNlY3VyaXR5
+MRIwEAYDVQQKEwlweU9wZW5TU0wxGDAWBgNVBAMTD2Fub3RoZXIuaW52YWxpZDER
+MA8GA1UECBMITmV3IFlvcmsxCzAJBgNVBAYTAlVTMSYwJAYJKoZIhvcNAQkBFhdp
+bnZhbGlkQGFub3RoZXIuaW52YWxpZDERMA8GA1UEBxMITmV3IFlvcmswHhcNMTEw
+NjA2MTIyMTQyWhcNMTIwNjA1MTIyMTQyWjCBnDERMA8GA1UECxMIU2VjdXJpdHkx
+EjAQBgNVBAoTCXB5T3BlblNTTDEYMBYGA1UEAxMPYW5vdGhlci5pbnZhbGlkMREw
+DwYDVQQIEwhOZXcgWW9yazELMAkGA1UEBhMCVVMxJjAkBgkqhkiG9w0BCQEWF2lu
+dmFsaWRAYW5vdGhlci5pbnZhbGlkMREwDwYDVQQHEwhOZXcgWW9yazCBnzANBgkq
+hkiG9w0BAQEFAAOBjQAwgYkCgYEA7jUOM0EnH0/bvqyQfrGlZ5ROc29JWEq3wp7/
+n96cxQ/oSf5G6rlQ5ZYnDlp44csQOY3DIq5/7cRju/Qf5cZ03YMOjzYSi4ElS0+o
+3Av/VgL/ssC6Z0PfQO4+NyXIQTn+cS6P6T65AVBdqn6Z5t0eY0wkU6QznpdJ/1c2
+a7gIYnUCAwEAATANBgkqhkiG9w0BAQQFAAOBgQBqyrP1wmpTmfeZnoB7piJd+qIj
+VHpCDRAZcdsxKUl/8PahjtWPMB0G5VaMwOoIGIlMxZ/LPKf44cA+QNEIXq8rohr2
+XFaA4t4X4aP7OmwQ4pa8mh4r86mP+vQU2iRJOqRYP+/gKaAqI2+ZbORZXJ7bewb5
+DTvvQRw2PRBf270h8g==
+-----END CERTIFICATE-----
diff --git a/examples/sni/another.invalid.key b/examples/sni/another.invalid.key
new file mode 100644
index 0000000..8d955f6
--- /dev/null
+++ b/examples/sni/another.invalid.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDuNQ4zQScfT9u+rJB+saVnlE5zb0lYSrfCnv+f3pzFD+hJ/kbq
+uVDllicOWnjhyxA5jcMirn/txGO79B/lxnTdgw6PNhKLgSVLT6jcC/9WAv+ywLpn
+Q99A7j43JchBOf5xLo/pPrkBUF2qfpnm3R5jTCRTpDOel0n/VzZruAhidQIDAQAB
+AoGBAOGaJBHM8fWI17DVlKA5NVNNNaPEUW2qjjFoDuflmQpWD4UMqzOhQYm/VMwW
+SYhnnr0zkw1kwUp6Bo87HX6sH37b1GeqIyp+b0Hqc+vLyiXPo0suqV23B9K8jjZ0
+6ap8h6hxpa5D1HtYKKDzWLhLJVtmtslxsvimR/CS+rmpUgBBAkEA+lJ2dXMDsUzB
+xOpX8MLfQsl8XB5tx4ejmXGyNp/hmRFqFi38FFemJXX1YC3wL5jbQ2Ltz9rnbdnG
+Xb/IWrn25QJBAPOcPua6xiNTWW5519JGaNgWdYnUgbj/ib8waLoElHp5Hl5DLuYX
+y8U96Xl/wAE4aQnp5R/PS75tYrKZo79z9FECQQDALk1J8IpWNbLSRoRLkKEtulji
+tG3d8VH1/WcwLuFZzhfffWB6Eay6N+yx8bLkJ/u2qZ4gpVRmbvqvgQ0GMp3NAkBE
+FFczzeCPgLyOdjiNSCYGtYgVg7DZDXjmWFX8HkmMTIrjFu1lWiMVNS8pSD1VWflo
+zte8Ywcs6Y7akLtFRtdxAkEA346J1/Zqtibez2TcjzCK+s9Ihwta23ZN2YTjo60o
+sDZ5AVJwyLa7VFEzO/e9v2ytD7k9fCJjHcxIWIe8zj0dYA==
+-----END RSA PRIVATE KEY-----
diff --git a/examples/sni/client.py b/examples/sni/client.py
new file mode 100644
index 0000000..5b93671
--- /dev/null
+++ b/examples/sni/client.py
@@ -0,0 +1,35 @@
+# Copyright (C) Jean-Paul Calderone
+# See LICENSE for details.
+
+if __name__ == '__main__':
+ import client
+ raise SystemExit(client.main())
+
+from sys import argv, stdout
+from socket import socket
+
+from OpenSSL.SSL import TLSv1_METHOD, Context, Connection
+
+def main():
+ """
+ Connect to an SNI-enabled server and request a specific hostname, specified
+ by argv[1], of it.
+ """
+ if len(argv) < 2:
+ print 'Usage: %s <hostname>' % (argv[0],)
+ return 1
+
+ client = socket()
+
+ print 'Connecting...',
+ stdout.flush()
+ client.connect(('127.0.0.1', 8443))
+ print 'connected', client.getpeername()
+
+ client_ssl = Connection(Context(TLSv1_METHOD), client)
+ client_ssl.set_connect_state()
+ client_ssl.set_tlsext_host_name(argv[1])
+ client_ssl.do_handshake()
+ print 'Server subject is', client_ssl.get_peer_certificate().get_subject()
+ client_ssl.close()
+
diff --git a/examples/sni/example.invalid.crt b/examples/sni/example.invalid.crt
new file mode 100644
index 0000000..b0cabac
--- /dev/null
+++ b/examples/sni/example.invalid.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICqTCCAhICAQEwDQYJKoZIhvcNAQEEBQAwgZwxETAPBgNVBAsTCFNlY3VyaXR5
+MRIwEAYDVQQKEwlweU9wZW5TU0wxGDAWBgNVBAMTD2V4YW1wbGUuaW52YWxpZDER
+MA8GA1UECBMITmV3IFlvcmsxCzAJBgNVBAYTAlVTMSYwJAYJKoZIhvcNAQkBFhdp
+bnZhbGlkQGV4YW1wbGUuaW52YWxpZDERMA8GA1UEBxMITmV3IFlvcmswHhcNMTEw
+NjA2MTIyMTMzWhcNMTIwNjA1MTIyMTMzWjCBnDERMA8GA1UECxMIU2VjdXJpdHkx
+EjAQBgNVBAoTCXB5T3BlblNTTDEYMBYGA1UEAxMPZXhhbXBsZS5pbnZhbGlkMREw
+DwYDVQQIEwhOZXcgWW9yazELMAkGA1UEBhMCVVMxJjAkBgkqhkiG9w0BCQEWF2lu
+dmFsaWRAZXhhbXBsZS5pbnZhbGlkMREwDwYDVQQHEwhOZXcgWW9yazCBnzANBgkq
+hkiG9w0BAQEFAAOBjQAwgYkCgYEAwmLucR0IXvoGTOfzb2WJlHis2s/FFJfmYAKd
+hq9bs+XzPeAPG0VQqAsy+om1gBOb8KPGtSet2SeNc25FU+QuwAza8uws7EaxD9b9
+CcarIh2X5LMcmiI/p34FuVGUSVsfc4QCTYFWGA0Mrw4jz9sGGeSEmTjVRnc3uAix
+31orKScCAwEAATANBgkqhkiG9w0BAQQFAAOBgQBxm8Qta5wYFmQ3l3EAne9+HaQ5
+gPStgox6STmyOGfRkybSePgOeKftOasaXpKboiNg6PJEkaFEnl9epNwS+8PIjQqv
+mPiZdlrNIfw+YVWpqgcTAIzkhYFH0K4v6d5Wn2adNgd5KbrxYOjsr2w0ixQEtdW/
++z1x/ngjc08EPqOIPQ==
+-----END CERTIFICATE-----
diff --git a/examples/sni/example.invalid.key b/examples/sni/example.invalid.key
new file mode 100644
index 0000000..192e346
--- /dev/null
+++ b/examples/sni/example.invalid.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDCYu5xHQhe+gZM5/NvZYmUeKzaz8UUl+ZgAp2Gr1uz5fM94A8b
+RVCoCzL6ibWAE5vwo8a1J63ZJ41zbkVT5C7ADNry7CzsRrEP1v0JxqsiHZfksxya
+Ij+nfgW5UZRJWx9zhAJNgVYYDQyvDiPP2wYZ5ISZONVGdze4CLHfWispJwIDAQAB
+AoGBAL8L8qNTUHXgL68ITRZP6g71J5YKm/zoafA0wdOsp2lA+Hb4roAz+Nif4SOh
+krPlEd9JZ7OF4vRJTlmDqDmSS2qY7hJuZpdrdvhdxaPGeX4uftC43thEzxLxPQHd
+gCCxugbGJOHChjMPk06oC0w1q70ex3gWmki82Jt/5INV6Z6RAkEA4km0s0RvbVmW
+AT12PROplCRE86eJNlLCVp2TJNl0LPZe5uWqaZZ8wBvfFd1PXEk/Qcpj4IotMZ5M
+1Ai4zw2+6QJBANvo6R5yLRrY8/7YKw9Y/1bbSRLhGYok2Ur4fFz64G28wA1VI3yS
+uXrJ7NjTVykfrBq59WEfh3a15P9g/TMAPY8CQQDdW3Z9iqtpj6IScnowgwR22wfs
+RW4PCuP6cMhY2rMvrI3nVrDd+wzrrBgNPmF8iFZt2Drdkq1lBVJodGO8f9jJAj9O
+K3yyVeOyp2wUKsMjsX8SYOCY1Ws+r9qNy8ZpRsSAPZgHJTx4C6/i9eQ7LuTMuXV0
+CqYu4AZHLGE6Zj+a4XsCQQC8Ken471EXuahfPcKTzsphuZnYZkoVUsFUxJFfqG+S
+8k2Jo/4c+2NyyvVXhXu2at8kmu45c92BrCTXIvLEwtnn
+-----END RSA PRIVATE KEY-----
diff --git a/examples/sni/server.py b/examples/sni/server.py
new file mode 100644
index 0000000..8738416
--- /dev/null
+++ b/examples/sni/server.py
@@ -0,0 +1,64 @@
+# Copyright (C) Jean-Paul Calderone
+# See LICENSE for details.
+
+if __name__ == '__main__':
+ import server
+ raise SystemExit(server.main())
+
+from sys import stdout
+from socket import SOL_SOCKET, SO_REUSEADDR, socket
+
+from OpenSSL.crypto import FILETYPE_PEM, load_privatekey, load_certificate
+from OpenSSL.SSL import TLSv1_METHOD, Context, Connection
+
+def load(domain):
+ crt = open(domain + ".crt")
+ key = open(domain + ".key")
+ result = (
+ load_privatekey(FILETYPE_PEM, key.read()),
+ load_certificate(FILETYPE_PEM, crt.read()))
+ crt.close()
+ key.close()
+ return result
+
+
+def main():
+ """
+ Run an SNI-enabled server which selects between a few certificates in a
+ C{dict} based on the handshake request it receives from a client.
+ """
+ port = socket()
+ port.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
+ port.bind(('', 8443))
+ port.listen(3)
+
+ print 'Accepting...',
+ stdout.flush()
+ server, addr = port.accept()
+ print 'accepted', addr
+
+ server_context = Context(TLSv1_METHOD)
+ server_context.set_tlsext_servername_callback(pick_certificate)
+
+ server_ssl = Connection(server_context, server)
+ server_ssl.set_accept_state()
+ server_ssl.do_handshake()
+ server.close()
+
+
+certificates = {
+ "example.invalid": load("example.invalid"),
+ "another.invalid": load("another.invalid"),
+ }
+
+
+def pick_certificate(connection):
+ try:
+ key, cert = certificates[connection.get_servername()]
+ except KeyError:
+ pass
+ else:
+ new_context = Context(TLSv1_METHOD)
+ new_context.use_privatekey(key)
+ new_context.use_certificate(cert)
+ connection.set_context(new_context)