PyOpenSSL empty socket property

Arn Vollebregt <[email protected]> Wed, 4 Sep 2019 10:37:39 +0200
Newsgroups gmane.comp.python.twisted
Message-ID <CAObs90D9WZvU0qnrQeGOPHsSqp7izmzJZ59uqwWEYpR9tnfp8w@mail.gmail.com>
--===============6229979268907749522==
Content-Type: multipart/alternative; boundary="000000000000c34f960591b61e15"

--000000000000c34f960591b61e15
Content-Type: text/plain; charset="UTF-8"

Hi,

I noticed that PyOpenSSL SNI callbacks (set with
*ctx.set_tlsext_servername_callback*) receive a *OpenSSL.SSL.Connection*
object within Twisted that have an empty *_socket* property, while this
property *is* actually set when using *PyOpenSSL* directly. For my use-case
this is a problem as I want to call *conn._socket.getpeername()* to
determine the peer's IP address. So I am wondering: why is this behaviour
different? And how do I get the peer IP address?

---console---
user:~$ sudo python testTwisted.py &
[3] 32842
user:~$ curl -s --insecure --key clientPrivateKey.pem --cert
clientCertificate.pem https://127.0.0.1 > /dev/null
'sniCallback' called.
        conn._socket: None
'verifyCallback' called for result 0
        conn._socket: None
'verifyCallback' called for result 1
        conn._socket: None
user:~$ sudo python testPyOpenSSL.py &
[1] 33270
user:~$ curl -s --insecure --key clientPrivateKey.pem --cert
clientCertificate.pem https://127.0.0.1 > /dev/null
'sniCallback' called.
        conn._socket: <socket._socketobject object at 0x7f34c5bd3130>
<class 'OpenSSL.SSL.Connection'>
'verifyCallback' called for result 0
        conn._socket: <socket._socketobject object at 0x7f34c5bd3130>
'verifyCallback' called for result 1
        conn._socket: <socket._socketobject object at 0x7f34c5bd3130>
127.0.0.1 - - [29/Aug/2019 11:45:47] "GET / HTTP/1.1" 200 -
------

---testTwisted.py---
### Generate server key material ###
# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout
serverPrivateKey.pem -out serverCertificate.pem -subj
"/C=''/O=''/OU=''/CN=server"
### Generate client key material ###
# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout
clientPrivateKey.pem -out clientCertificate.pem -subj
"/C=''/O=''/OU=''/CN=client"
from __future__ import print_function
#https://twistedmatrix.com/documents/12.0.0/core/howto/ssl.html
from OpenSSL import SSL
from twisted.internet import ssl, reactor
from twisted.web import server, resource
from twisted.internet.protocol import Factory, Protocol

def verifyCallback(conn, cert, errno, depth, result):
    print('\'verifyCallback\' called for result ' + str(result))
    print('\tconn._socket: ' + str(conn._socket))
    return True

def sniCallback(conn):
    print('\'sniCallback\' called.')
    print('\tconn._socket: ' + str(conn._socket))

class MainResource(resource.Resource):
    isLeaf = True

    def render_GET(self, request):
        request.responseHeaders.addRawHeader("Content-Type", "text/html;
charset=utf-8")
        return b"<html><body>Hello World</body></html>"

if __name__ == '__main__':
    myContextFactory = ssl.DefaultOpenSSLContextFactory(
        'serverPrivateKey.pem',
        'serverCertificate.pem'
    )
    ctx = myContextFactory.getContext()
    #
https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_verify
    ctx.set_verify(SSL.VERIFY_PEER, verifyCallback)
    #
https://pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_tlsext_servername_callback
    ctx.set_tlsext_servername_callback(sniCallback)

    site = server.Site(MainResource())
    reactor.listenSSL(443, site, myContextFactory)
    reactor.run()
------

---testPyOpenSSL.py---
### Generate server key material ###
# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout
serverPrivateKey.pem -out serverCertificate.pem -subj
"/C=''/O=''/OU=''/CN=server"
### Generate client key material ###
# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout
clientPrivateKey.pem -out clientCertificate.pem -subj
"/C=''/O=''/OU=''/CN=client"
from __future__ import print_function
import socket, sys, os
from SocketServer import BaseServer
from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
from OpenSSL import SSL

def verifyCallback(conn, cert, errno, depth, result):
    print('\'verifyCallback\' called for result ' + str(result))
    print('\tconn._socket: ' + str(conn._socket))
    return True

def sniCallback(conn):
    print('\'sniCallback\' called.')
    print('\tconn._socket: ' + str(conn._socket))
    print(type(conn))

class SecureHTTPServer(HTTPServer):
    def __init__(self, server_address, HandlerClass):
        BaseServer.__init__(self, server_address, HandlerClass)
        ctx = SSL.Context(SSL.TLSv1_2_METHOD)
        ctx.use_privatekey_file('serverPrivateKey.pem')
        ctx.use_certificate_file('serverCertificate.pem')
        #
https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_verify
        ctx.set_verify(SSL.VERIFY_PEER, verifyCallback)
        #
https://pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_tlsext_servername_callback
        ctx.set_tlsext_servername_callback(sniCallback)
        self.socket = SSL.Connection(ctx,
socket.socket(self.address_family,self.socket_type))
        self.server_bind()
        self.server_activate()

    def shutdown_request(self,request):
        request.shutdown()

class SecureHTTPRequestHandler(SimpleHTTPRequestHandler):
    def setup(self):
        self.connection = self.request
        self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)

    def do_GET(self):
        self.send_response(200)
        SimpleHTTPRequestHandler.end_headers(self)
        self.wfile.write('<html><body>Hello World</body></html>')

if __name__ == '__main__':
    ip,port = ('0.0.0.0', 443)
    httpd = SecureHTTPServer((ip, port), SecureHTTPRequestHandler)
    httpd.serve_forever()
------

(Please note that even though these examples are for Python2 (due to other
quirks) I am aiming to implement this in Python3.)

Regards,

Arn

--000000000000c34f960591b61e15
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr"><div>Hi,</div><div><br></div><div>I noticed that PyOpenSSL=
 SNI callbacks (set with <i>ctx.set_tlsext_servername_callback</i>) receive=
 a <i>OpenSSL.SSL.Connection</i> object within Twisted that have an empty <=
i>_socket</i> property, while this property <u>is</u> actually set when usi=
ng <i>PyOpenSSL</i> directly. For my use-case this is a problem as I want t=
o call <i>conn._socket.getpeername()</i> to determine the peer&#39;s IP add=
ress. So I am wondering: why is this behaviour different? And how do I get =
the peer IP address?</div><div><br></div><div>---console---</div><div>user:=
~$ sudo python testTwisted.py &amp;</div><div>[3] 32842</div><div>user:~$ c=
url -s --insecure --key clientPrivateKey.pem --cert clientCertificate.pem <=
a href=3D"https://127.0.0.1" target=3D"_blank">https://127.0.0.1</a> &gt; /=
dev/null<br>&#39;sniCallback&#39; called.<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 co=
nn._socket: None<br>&#39;verifyCallback&#39; called for result 0<br>=C2=A0 =
=C2=A0 =C2=A0 =C2=A0 conn._socket: None<br>&#39;verifyCallback&#39; called =
for result 1<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 conn._socket: None</div><div>us=
er:~$ sudo python testPyOpenSSL.py &amp;<br>[1] 33270</div><div>user:~$ cur=
l -s --insecure --key clientPrivateKey.pem --cert clientCertificate.pem <a =
href=3D"https://127.0.0.1" target=3D"_blank">https://127.0.0.1</a> &gt; /de=
v/null<br>&#39;sniCallback&#39; called.<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 conn=
._socket: &lt;socket._socketobject object at 0x7f34c5bd3130&gt;<br>&lt;clas=
s &#39;OpenSSL.SSL.Connection&#39;&gt;<br>&#39;verifyCallback&#39; called f=
or result 0<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 conn._socket: &lt;socket._socket=
object object at 0x7f34c5bd3130&gt;<br>&#39;verifyCallback&#39; called for =
result 1<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 conn._socket: &lt;socket._socketobj=
ect object at 0x7f34c5bd3130&gt;<br>127.0.0.1 - - [29/Aug/2019 11:45:47] &q=
uot;GET / HTTP/1.1&quot; 200 -</div><div>------<br></div><div><br></div><di=
v>---testTwisted.py---</div><div>### Generate server key material ###<br>#
 openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout=20
serverPrivateKey.pem -out serverCertificate.pem -subj=20
&quot;/C=3D&#39;&#39;/O=3D&#39;&#39;/OU=3D&#39;&#39;/CN=3Dserver&quot;<br>#=
## Generate client key material ###<br>#
 openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout=20
clientPrivateKey.pem -out clientCertificate.pem -subj=20
&quot;/C=3D&#39;&#39;/O=3D&#39;&#39;/OU=3D&#39;&#39;/CN=3Dclient&quot;<br>f=
rom __future__ import print_function<br>#<a href=3D"https://twistedmatrix.c=
om/documents/12.0.0/core/howto/ssl.html" target=3D"_blank">https://twistedm=
atrix.com/documents/12.0.0/core/howto/ssl.html</a><br>from OpenSSL import S=
SL<br>from twisted.internet import ssl, reactor<br>from twisted.web import =
server, resource<br>from twisted.internet.protocol import Factory, Protocol=
<br><br>def verifyCallback(conn, cert, errno, depth, result):<br>=C2=A0 =C2=
=A0 print(&#39;\&#39;verifyCallback\&#39; called for result &#39; + str(res=
ult))<br>=C2=A0 =C2=A0 print(&#39;\tconn._socket: &#39; + str(conn._socket)=
)<br>=C2=A0 =C2=A0 return True<br><br>def sniCallback(conn):<br>=C2=A0 =C2=
=A0 print(&#39;\&#39;sniCallback\&#39; called.&#39;)<br>=C2=A0 =C2=A0 print=
(&#39;\tconn._socket: &#39; + str(conn._socket))<br><br>class MainResource(=
resource.Resource):<br>=C2=A0 =C2=A0 isLeaf =3D True<br><br>=C2=A0 =C2=A0 d=
ef render_GET(self, request):<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 request.respon=
seHeaders.addRawHeader(&quot;Content-Type&quot;, &quot;text/html; charset=
=3Dutf-8&quot;)<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 return b&quot;&lt;html&gt;&l=
t;body&gt;Hello World&lt;/body&gt;&lt;/html&gt;&quot;<br><br>if __name__ =
=3D=3D &#39;__main__&#39;:<br>=C2=A0 =C2=A0 myContextFactory =3D ssl.Defaul=
tOpenSSLContextFactory(<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 &#39;serverPrivateKe=
y.pem&#39;,<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 &#39;serverCertificate.pem&#39;<=
br>=C2=A0 =C2=A0 )<br>=C2=A0 =C2=A0 ctx =3D myContextFactory.getContext()<b=
r>=C2=A0 =C2=A0 # <a href=3D"https://www.pyopenssl.org/en/stable/api/ssl.ht=
ml#OpenSSL.SSL.Context.set_verify" target=3D"_blank">https://www.pyopenssl.=
org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_verify</a><br>=C2=A0 =C2=
=A0 ctx.set_verify(SSL.VERIFY_PEER, verifyCallback)<br>=C2=A0 =C2=A0 # <a h=
ref=3D"https://pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set=
_tlsext_servername_callback" target=3D"_blank">https://pyopenssl.org/en/sta=
ble/api/ssl.html#OpenSSL.SSL.Context.set_tlsext_servername_callback</a><br>=
=C2=A0 =C2=A0 ctx.set_tlsext_servername_callback(sniCallback)<br><br>=C2=A0=
 =C2=A0 site =3D server.Site(MainResource())<br>=C2=A0 =C2=A0 reactor.liste=
nSSL(443, site, myContextFactory)<br>=C2=A0 =C2=A0 reactor.run()</div><div>=
------</div><div><br></div><div>---testPyOpenSSL.py---</div><div>### Genera=
te server key material ###<br>#
 openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout=20
serverPrivateKey.pem -out serverCertificate.pem -subj=20
&quot;/C=3D&#39;&#39;/O=3D&#39;&#39;/OU=3D&#39;&#39;/CN=3Dserver&quot;<br>#=
## Generate client key material ###<br>#
 openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout=20
clientPrivateKey.pem -out clientCertificate.pem -subj=20
&quot;/C=3D&#39;&#39;/O=3D&#39;&#39;/OU=3D&#39;&#39;/CN=3Dclient&quot;<br>f=
rom __future__ import print_function<br>import socket, sys, os<br>from Sock=
etServer import BaseServer<br>from BaseHTTPServer import HTTPServer<br>from=
 SimpleHTTPServer import SimpleHTTPRequestHandler<br>from OpenSSL import SS=
L<br><br>def verifyCallback(conn, cert, errno, depth, result):<br>=C2=A0 =
=C2=A0 print(&#39;\&#39;verifyCallback\&#39; called for result &#39; + str(=
result))<br>=C2=A0 =C2=A0 print(&#39;\tconn._socket: &#39; + str(conn._sock=
et))<br>=C2=A0 =C2=A0 return True<br><br>def sniCallback(conn):<br>=C2=A0 =
=C2=A0 print(&#39;\&#39;sniCallback\&#39; called.&#39;)<br>=C2=A0 =C2=A0 pr=
int(&#39;\tconn._socket: &#39; + str(conn._socket))<br>=C2=A0 =C2=A0 print(=
type(conn))<br><br>class SecureHTTPServer(HTTPServer):<br>=C2=A0 =C2=A0 def=
 __init__(self, server_address, HandlerClass):<br>=C2=A0 =C2=A0 =C2=A0 =C2=
=A0 BaseServer.__init__(self, server_address, HandlerClass)<br>=C2=A0 =C2=
=A0 =C2=A0 =C2=A0 ctx =3D SSL.Context(SSL.TLSv1_2_METHOD)<br>=C2=A0 =C2=A0 =
=C2=A0 =C2=A0 ctx.use_privatekey_file(&#39;serverPrivateKey.pem&#39;)<br>=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 ctx.use_certificate_file(&#39;serverCertificate=
.pem&#39;)<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 # <a href=3D"https://www.pyopenss=
l.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_verify" target=3D"_bla=
nk">https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.se=
t_verify</a><br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 ctx.set_verify(SSL.VERIFY_PEER,=
 verifyCallback)<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 # <a href=3D"https://pyopen=
ssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_tlsext_servername_ca=
llback" target=3D"_blank">https://pyopenssl.org/en/stable/api/ssl.html#Open=
SSL.SSL.Context.set_tlsext_servername_callback</a><br>=C2=A0 =C2=A0 =C2=A0 =
=C2=A0 ctx.set_tlsext_servername_callback(sniCallback)<br>=C2=A0 =C2=A0 =C2=
=A0 =C2=A0 self.socket =3D SSL.Connection(ctx, socket.socket(self.address_f=
amily,self.socket_type))<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.server_bind()<=
br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.server_activate()<br>=C2=A0 =C2=A0 <br>=
=C2=A0 =C2=A0 def shutdown_request(self,request):<br>=C2=A0 =C2=A0 =C2=A0 =
=C2=A0 request.shutdown()<br><br>class SecureHTTPRequestHandler(SimpleHTTPR=
equestHandler):<br>=C2=A0 =C2=A0 def setup(self):<br>=C2=A0 =C2=A0 =C2=A0 =
=C2=A0 self.connection =3D self.request<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 self=
.rfile =3D socket._fileobject(self.request, &quot;rb&quot;, self.rbufsize)<=
br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.wfile =3D socket._fileobject(self.reque=
st, &quot;wb&quot;, self.wbufsize)<br>=C2=A0 =C2=A0 <br>=C2=A0 =C2=A0 def d=
o_GET(self):<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.send_response(200)<br>=C2=
=A0 =C2=A0 =C2=A0 =C2=A0 SimpleHTTPRequestHandler.end_headers(self)<br>=C2=
=A0 =C2=A0 =C2=A0 =C2=A0 self.wfile.write(&#39;&lt;html&gt;&lt;body&gt;Hell=
o World&lt;/body&gt;&lt;/html&gt;&#39;)<br><br>if __name__ =3D=3D &#39;__ma=
in__&#39;:<br>=C2=A0 =C2=A0 ip,port =3D (&#39;0.0.0.0&#39;, 443)<br>=C2=A0 =
=C2=A0 httpd =3D SecureHTTPServer((ip, port), SecureHTTPRequestHandler)<br>=
=C2=A0 =C2=A0 httpd.serve_forever()</div><div>------</div><div><br></div><d=
iv>(Please note that even though these examples are for Python2 (due to oth=
er quirks) I am aiming to implement this in Python3.)<br></div><div><br></d=
iv><div>Regards,</div><div><br></div><div>Arn</div></div>

--000000000000c34f960591b61e15--

--===============6229979268907749522==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: inline

X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KVHdpc3RlZC1Q
eXRob24gbWFpbGluZyBsaXN0ClR3aXN0ZWQtUHl0aG9uQHR3aXN0ZWRtYXRyaXguY29tCmh0dHBz
Oi8vdHdpc3RlZG1hdHJpeC5jb20vY2dpLWJpbi9tYWlsbWFuL2xpc3RpbmZvL3R3aXN0ZWQtcHl0
aG9uCg==

--===============6229979268907749522==--