Testing TLS certificate authentication in case of mismatched key/certificate - python

I have a web service which uses TLS client certificates for user authentication. Now I would like to write a full-stack test if the service implements ("is configured to do") certificate authentication correctly.
In particular I'm wondering if it is possible to encrypt a request with a key that does not match the presented client certificate.
Ideally the test would be written in Python but any other common language will do as well.
So my first attempt was by using Python's requests package:
requests.post(url, data=payload, cert=(cert_path, key_path), verify=False)
This works if the key matches the certificate but in case of a mismatch I just get
requests.exceptions.SSLError: [X509: KEY_VALUES_MISMATCH] key values mismatch (_ssl.c:2718)
which is a client side error so the request never hits the server (and hence does not test anything there).
So my question is: Is it actually possible to present a bad certificate or is that impossible by protocol design? Any hints how to implement such a test?

In the TLS protocol it is possible to send a CertificateVerify message where the included signature does not match the certificate sent by the client. So it is not excluded by protocol design and it would be impossible to exclude it since the goal of this signature is to proof that the client has access to the private key matching the sent certificate.
But, by using requests you are doing tests at a very high level which actually tries to protect you from typical mistakes. If you want to do tests with invalid signatures etc you need to work at a lower level, for example by constructing your own TLS handshake using scapy. Note that you need to have deeper knowledge of how TLS works for doing such tests.

Assuming you're running HTTPS, the underlying SSL connection can't be completed with "bad" certificates if you've configured your server to reject them.
Since there is no SSL connection, you can't pass HTTP requests of any kind.
Which is the point of the design - to keep unwanted clients out entirely.

Related

Are Python.requests safe?

I'm about to use Python.requests to get data from my own online api to my local pc. My api requires authentication which for now is done trough simply posting user/pass:
params = {'user': 'username', 'pass':'password'}
requests.post(url, params=params)
Are this requests safe or is it going to allow a middle-man to capture that user/pass?
P.S My api is using a letsencrypt ssl certificate. Python version 3.7.0
this has nothing to do with the python-requests package, but with the HTTP (and HTTPS) protocols. HTTP is plain-text so anyone that manages to sniff your packets can read the content (hence the username/password pair in clear text). HTTPS uses strong encryption, so even someone sniffing your traffic will have a hard-time deciphering it - no encryption scheme is 100% safe of course but decrypting SSL traffic is currently way too costly even for the NSA.
IOW, what will make your requests "safe" is the use of the HTTPS protocol, not which python (or not python) package you use to write your client code.
Use the HTTPS protocol and it's safe provided you have a valid SSL certificate on your api. If you still feel paranoid/insecure, you can implement end-to-end encryption using an existing algorithm or create your custom algorithm either.

Is there any reasonable way to get the (public) certificate from a TLS client without also decrypting the stream in Python?

The situation is this one: a client authenticates to a server using a signed certificate. Then a complete handshake is performed and the data are exchanged in a secure manner.
But depending on a number of elements, I need to associate each user to a specific server (many users can share the same server). The context can change in any instant and I want to be able to change this user -> server map without having to access the user device.
The most straightforward way seems to be the implementation of a gateway/router which uses the information from the client certificate to handle the routing.
The problem with that is that I have no idea of how to make such a router without resorting to MITMing (which I don't want for computational, security and privacy reasons): AFAIK, the ssl lib and openssl bindings in Python (which are fast, reliable and widespread) only provide wrapped sockets which handle the decryption part. There seems to be no public interface just to extract the certificate and forward the pristine stream to a suitable backend server.
Do you know any way to get that certificate and forward the complete, unaltered, stream to another server without resorting to complicated schemes/workarounds?

Is there a way to get remote peer certificate with certificate verification disabled (CERT_NONE) in Python?

I am using Python 3. I am using a SSLContext from the ssl library.
I would like to know how to request and get the remote peer certificate (as with SSLSocket.getpeercert()) but in the CERT_NONE mode. Currently, I get an empty dict (which is expected as per the documentation). So, can I do that without digging into private stuff?
N.B.
I am not trying to reimplement crypto. Actually, I don't need any crypto, however I need the context (and a socket) and the remote certificate (again, for information purpose, not to authenticate or actually encrypt anything).
I found a way without straying away from the default ssl library (which has the advantage of being there):
socket.getpeercert(binary_form=True)
gives the certificate in DER format which is good enough for what I have to do.
Afaik, returning an empty dict is to avoid blindly trusting what people send in clear text and has not been verified.
Python/OpenSSL mix requesting the client certificate certificate with validating it:
CERT_NONE (OpenSSL: SSL_VERIFY_NONE) will not request the certificate from the client at all. This means the client will not send a certificate.
CERT_REQUIRE (OpenSSL: SSL_VERFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT) and CERT_OPTIONAL (OpenSSL: SSL_VERIFY_PEER) will request the certificate but then also fail if the certificate could not be verified.
What you would need is to require the certificate but then have a way to handle the validation yourself and just accept all client certificates. This could be done with a custom verification callback. In OpenSSL this could be done with SSL_CTX_set_verify. But unfortunately the Python ssl library don't seem to expose the necessary interface to have your own validation callback. But it should be possible to do with pyOpenSSL which lets you set your own verification callback.

Two-way ssl authentication for flask

I have already implemented SSL communication where client application verifies the identity of the SSL server application using flask. Now I want SSL server application to verify the identity of the SSL-client application. Is it possible with flask ? How do I verify client certificate ? During first handshake client is sending CSR and in response I am sending back certificate signed by Self Signed CA certificate.
But I am not yet clear how the client will be verified by server while next communication. Is there any callback for cert verification. Link on Google groups says its not possible to have ssl authentication on Flask. in order to do this one need to use webserver like apache,ngnix. Is this the only way to authenticate client ?
There is one more thing that I want to achieve that I need to identify each client based on their certificate. is that even possible with flask.
my question could be naive as I am not yet much familiar to flask
Disclaimer
Before I start I would note #Emanuel Ey's comment. That you would want to consider if this was being done on a production or development server first. For example; if you are using Apache WebServer the HTTPS component can be done from Apache. The only thing you would do differently is pass through the certificate details as options and your server app would then verify the serial number within the app itself.
It is Possible
But it the way it is possible is not considered good programming practice. Unfortunately, it's not accessible from flask.request and not possible with the Flask package. However, Flask uses Werkzeug and it is possible by patching the werkzeug.serving package where will be writing your main Flask code. It is not recommended because you may want to update Flask or Werkzeug later and your patch might break and need to be re-factored. i.e. from 0.9 to 1.0.
This provides a solution without using a web server. But I would recommend the web server/environment variable combo. It is cleaner and comparatively good practice.
I have done some testing to see if this is easy to implement. I was able to confirm that this method can work using the latest development codebase 'Werkzeug-0.10_devdev_20141223-py2.7'.
You'll probably want to verify of the serial number (seed number) found in each certificate (and maybe even some other variables). As you may know, the serial is unique to each certificate and is determined during the certificate generation process by you on the server side. It helps to store this along with the clients record and certificate information (where appropriate) in order to verify client certificate serial number later on. Note: It may require alterations between hex and base 10 decimal.
Werkzeug dev_2014122
What I did was to add in the following options to the werkzeug.serving.BaseWSGIServer.__init__ call to wrap_socket().
Use these;
server_side=True, ca_certs= '/etc/apache2/ssl/ca.pem', cert_reqs=ssl.CERT_REQUIRED
ca_certs: Use this to verify against, this is the CA cert used to generate the client certificates)
ssl.CERT_REQUIRED: require client certificate verification against ca_certs
Note: If the client certificate is does not pass initial verification you will not be able to fetch the client certificate. It will be None.
Then in my Flask test class I patched verify_request
where
def verify_request(self, request, client_address):
cert = request.getpeercert(True)
raw = decoder.decode(cert)[0]
print "Serial Number of your certificate is: % " % str(raw[0][1])
# todo: do checks & if serial no is ok then return true
return True
werkzeug.serving.BaseWSGIServer.verify_request = verify_request
This proved it is possible but you'll probably want to investigate the request handlers of the HTTPServer class that the BaseWSGIServer inherits to find a better way to do a call back or override.
Werkzeug 0.9.X
If you are using Werkzeug 0.9.X I'm assuming you are using the import from OpenSSL import SSL. see code snippet here. I have not tested this.
Some of the calls you may be interested in for this version would be;
- Context.set_verify(mode, callback)
- Connection.get_peer_certificate()
Clarification
What I do not understand is your reference to sending a CSR during the first handshake. If this is your process of client certificate generation you may want to rethink how you do this in the context of your system and environment. If I could have some more information I could comment further..
Also, 'handshake' in an SSL/TLS context generally refers to the action of creating the secure connection in the first place using an existing certificate. Immediately after handshaking, loosely speaking, a connection is established.

Python SSL Socket Client Authentification

I'm trying to set up a server and client in python where the server authenticates clients using SSL with certificates. There are a lot of examples of SSL certificates online, but everything I've found has the server providing a certificate to the client and the client checking it. I need the server to ensure that the client has the authority to connect to the server. I understand how to generate and send certificates and the basics of how they work. I would type out my code, but my client/server without SSL is working fine and I've been referencing this for SSL. The client/server example at the bottom of that page summarizes my understanding of SSL certs in python.
I realize this isn't much to go on, but if someone could explain the basic modifications to that example to have the server authenticate the client instead of the other way around, that would be awesome. Alternatively, a link to an example or even just some socket methods to investigate would be very helpful. Let me know if more information is needed. I don't mean to be vague and promise I've spent all morning looking for info myself :).
Edit: I'm trying to stick to the basic ssl library. Aka "import ssl".
You would use SSLSocket.getpeercert to get the certificate. The client would need to specify a key and certificate when wrapping the socket just like the server side. On the server side, you will also need to pass ca_certs="path_to_ca_cert_file" and probably also want to specify cert_reqs=CERT_REQUIRED (see. args for ssl.wrap_socket.
In addition to this, it sounds like you might be looking to do certificate based client authentication/authorization? This is a simple matter of using getpeercert to get the client certificate and accessing fields within the certificate to use in a normal authentication path (i.e. Common Name == User Id)
Not really sure what your question refers, however you can see SSL in Python, other resource for SSL in Python, Validating SSL, get SSL Certificate information and you probably found other good links.

Categories