3

My apologies right off if this isn't in the right SO (Information Security or Crypto).Anyways, I'm trying to figure out how to validate SSL certs client side in Python. I found a callback function here that looks similar to other functions I've seen online. However, in my code I'm unsure of how (or why, really) it works. It seems to work when I run my code, but why (in PyCharm) are the first four parameters grayed out, and only the fifth in white? Is there a way I can use this callback function to check for particular certificate errors?

Here's the output when I run it

Certs are fine
Certs are fine
Certs are fine
b'HTTP/1.1 200 OK\r\nDate: Tue, 12 Apr 2016...etc

I assume each line of "Certs are fine" is validating each cert in the chain?

import socket
from OpenSSL import SSL

HOST = "www.google.com"
PORT = 443

def verify_callback(connection, x509, errnum, errdepth, ok):
        if not ok:
            print("Bad Certs")
        else:
            print("Certs are fine")
        return ok


context = SSL.Context(SSL.TLSv1_2_METHOD)
context.load_verify_locations("cacerts.pem")
context.set_options(SSL.OP_NO_SSLv2)
context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)


# create socket and connect to server
sock = socket.socket()
sock = SSL.Connection(context, sock)
sock.connect((HOST, PORT))
sock.do_handshake()
sock.sendall("GET / HTTP/1.1\r\n\r\n")
Community
  • 1
  • 1
Amanda_Panda
  • 1,156
  • 4
  • 26
  • 68

2 Answers2

1

The pyOpenSSL docs on this are pretty sparse, but this function is a wrapper around the corresponding OpenSSL function, whose docs are much better.

The verify_callback function is used to control the behaviour when the SSL_VERIFY_PEER flag is set. It must be supplied by the application and receives two arguments: preverify_ok indicates, whether the verification of the certificate in question was passed (preverify_ok=1) or not (preverify_ok=0). x509_ctx is a pointer to the complete context used for the certificate chain verification.

The certificate chain is checked starting with the deepest nesting level (the root CA certificate) and worked upward to the peer's certificate. At each level signatures and issuer attributes are checked. Whenever a verification error is found, the error number is stored in x509_ctx and verify_callback is called with preverify_ok=0. By applying X509_CTX_store_* functions verify_callback can locate the certificate in question and perform additional steps (see EXAMPLES). If no error is found for a certificate, verify_callback is called with preverify_ok=1 before advancing to the next level.

The return value of verify_callback controls the strategy of the further verification process. If verify_callback returns 0, the verification process is immediately stopped with "verification failed" state. If SSL_VERIFY_PEER is set, a verification failure alert is sent to the peer and the TLS/SSL handshake is terminated. If verify_callback returns 1, the verification process is continued. If verify_callback always returns 1, the TLS/SSL handshake will not be terminated with respect to verification failures and the connection will be established. The calling process can however retrieve the error code of the last verification error using SSL_get_verify_result(3) or by maintaining its own error storage managed by verify_callback.

If no verify_callback is specified, the default callback will be used. Its return value is identical to preverify_ok, so that any verification failure will lead to a termination of the TLS/SSL handshake with an alert message, if SSL_VERIFY_PEER is set.

The docs also have a well-explained example verification function that checks if the chain being verified is too long. If a chain is too long, the error is logged and then, depending on a user-set value, the callback either returns 0 to cause the validation to fail, or returns 1 (i.e. ignores the error and validates anyway).

Additionally, this blog post has a pyOpenSSL example that simply checks for a couple specific errors and fails validation when they occur.

hairlessbear
  • 341
  • 3
  • 11
0

A few things to clear up

  1. VERIFY_FAIL_IF_NO_PEER_CERT is just ignored, specifically in the client context. So it can be removed. This is only used in a server context.

  2. The ok attribute is a 'pre-verification code', ok=1 if pre-verification succeeded ok=0 if not. Emphasis on pre-verification, the verification has not been completed and this is the purpose of this function. i.e. returning it as-is will effectively skip verification (you might as well never call set_verify and use context.verify_mode instead).

  3. the 4th attribute of verify_callback is certificate depth, not errdepth, there is no err it's just the depth to let you know which peer cert this is (see point 4)

  4. Your verify_callback will be called multiple times, once per peer certificate in the chain. The order is predetermined for you by openssl so you should not need to match the subjectKeyIdentifier with the peer cert authorityKeyIdentifier of which you have not seen yet in this enumeration. You can trust openssl did this check for you, and all you need to do is a few other checks depending on your situation.

What is the point again?

We are doing our own client certificate authentication, i.e. we need to check certain server response information against information the client knows to be trustworthy, i.e. the server response is authentic.

What kind of things does the client know?

  • A client should have trusted Issuers, so you should verify the peer certificates match this expectation first
  • A client may also know that the depth (see point 3) i.e. depth 3 is acceptable, 4 and over is not. We may know that we have 3 peer certs; the root CA (in a root trust store), the server certificate signer (who you paid for the cert), and the server cert (produced byt he CA who signed it). ergo a dpeth of 3 so 4 or more should return 0
  • A client has typically has client certificates to present to a server, if it applied zero-trust (the real one, not the one a vendor sells you in name only) it will need one certificate for every service it communicates with, a service mesh is a good example of this. So which cert is appropriate? the server can give you a hint which you can read using sock.get_client_ca_list() BUT if you blindly use this it may be a rerouted request to a malicious server, i.e. for the client certificate authentication to have any security characteristics we must make our own validations irrespective of any potential malicious influence. So treat the value of get_client_ca_list() as a guide (it may indicate a compromise if an unexpected value is sent) and you must present the appropriate certificate based on what you know and not what you are asked to trust (if it fails, it fails securely and would not connect to a malicious server)
Stof
  • 610
  • 7
  • 16