64

I am trying to use connect to another party using Python 3 asyncio module and get this error:

     36     sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
---> 37     sslcontext.load_cert_chain(cert, keyfile=ca_cert)
     38

SSLError: [SSL] PEM lib (_ssl.c:2532)

The question is just what the error mean. My certificate is correct, the keyfile (CA certificate) might not.

David Z
  • 128,184
  • 27
  • 255
  • 279
sargas
  • 5,820
  • 7
  • 50
  • 69
  • You do mention that the key -file might not be correct. Cert loading will fail if the cert (public key) does not go with the right private key file. – Prabhu May 07 '15 at 19:08
  • You have `keyfile=ca_cert`, which seems likely to be incorrect (or you have chosen horrible variable names). Does your `ca_cert` really contain a private key? – larsks May 07 '15 at 19:13
  • @larsks How choosing horrible variable names can cause errors in the code? Any suggestions (or convention) on how to name them? The ca_cert file begins with `-----BEGIN CERTIFICATE-----`, what seems to be a valid encrypted key, and ends with `-----END CERTIFICATE-----`. Should header and footer say something different? – sargas May 07 '15 at 19:21
  • That is an SSL certificate, not an SSL private key file. You want the private key that corresponds to your local certificate. – larsks May 07 '15 at 19:24
  • @larsks If I am the client connecting to a server, wouldn't I need their _public key_ instead? – sargas May 07 '15 at 21:02
  • 11
    People that down vote a question should at least leave a comment at to why, and maybe even how to improve the question. – sargas Nov 24 '15 at 17:53
  • 1
    I've rolled back the last revision here since answers shouldn't be edited into the question. – David Z Nov 18 '17 at 10:03
  • Important information for https noobs: the certfile parameter for load_cert_chain is a public key created by the certificate authority after signing, and is NOT the certificate request file – ilcj Aug 07 '20 at 04:47

5 Answers5

37

Assuming that version 3.6 is being used:

See: https://github.com/python/cpython/blob/3.6/Modules/_ssl.c#L3523-L3534

 PySSL_BEGIN_ALLOW_THREADS_S(pw_info.thread_state);
 r = SSL_CTX_check_private_key(self->ctx);
 PySSL_END_ALLOW_THREADS_S(pw_info.thread_state);
 if (r != 1) { 
    _setSSLError(NULL, 0, __FILE__, __LINE__);
    goto error;
 }

What it is saying is that SSL_CTX_check_private_key failed; thus, the private key is not correct.

Reference to the likely version:

jmunsch
  • 22,771
  • 11
  • 93
  • 114
  • Agreed. You went to the source and found the right answer. – Adam Matan May 07 '15 at 19:23
  • 2
    I read it before but had no clue of what that meant. I think it is using _openssl_ (on Unix systems) and reporting the error it got at system level. That explains why the error would generate from that line in the source, right? – sargas May 07 '15 at 19:32
21

In your code, you are calling:

sslcontext.load_cert_chain(cert, keyfile=ca_cert)

From the documentation:

Load a private key and the corresponding certificate. The certfile string must be the path to a single file in PEM format containing the certificate as well as any number of CA certificates needed to establish the certificate’s authenticity. The keyfile string, if present, must point to a file containing the private key in. Otherwise the private key will be taken from certfile as well. See the discussion of Certificates for more information on how the certificate is stored in the certfile.

Based on the name of the arguments in your example, it looks like you are passing a CA certificate to the keyfile argument. That is incorrect, you need to pass in the private key that was used to generate your local certificate (otherwise the client cannot use your certificate). A private key file will look something like:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,9BA4973008F0A0B36FBE1426C198DD1B

...data...
-----END RSA PRIVATE KEY-----

You only need the CA certificate if you are trying to verify the validity of SSL certificates that have been signed by this certificate. In that case, you would probably use SSLContext.load_verify_locations() to load the CA certificate (although I have not worked with the SSL module recently, so don't take my word on that point).

larsks
  • 277,717
  • 41
  • 399
  • 399
  • 1
    I received the same error and I only need the certificate chain verified. The private key is not relevant so this answer helps as it explained that my ansible script is looking for a private key but should be set to only look for the certificate chain. – BenDavid Apr 27 '19 at 18:59
9

The error means a private key file is missing. Generate a key pair in openssl shell: openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365

Start Python SSL Server:

from http.server import HTTPServer, 
             SimpleHTTPRequestHandler

import ssl

httpd = HTTPServer(('localhost', 4443), 
                           SimpleHTTPRequestHandler)

httpd.socket = ssl.wrap_socket(httpd.socket, 
                 certfile='/tmp/cert.pem',keyfile='
                           /tmp/key.pem', server_side=True)

httpd.serve_forever()

(We use port 4443 so that I can run the tests as normal user; the usual port 443 requires root privileges).

Max Kleiner
  • 1,442
  • 1
  • 13
  • 14
4

In my case, this error meant that my certificate had the wrong file extension. I had to convert my cert.der file to a cert.pem file using the below:

openssl x509 -inform der -in cert.der -out cert.pem 
Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Pelonomi Moiloa
  • 516
  • 5
  • 12
1

Upon using openssl to generate a self-signed certificate protected with password I faced a similar issue where I got the following output:

ontext.load_cert_chain(certfile= certificate_private, keyfile= certificate)
ssl.SSLError: [SSL] PEM lib (_ssl.c:4012)

After reading twice to understand the documentation on load_cert_chain I came up with the following solution:

import ssl
    
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.load_cert_chain(certfile= certificate_private, keyfile= certificate_key, password= certificate_password)

connection = http.client.HTTPSConnection(host, port=443, context=context)
connection.request(method="POST", url=request_url, headers=request_headers, body=json.dumps(request_body_dict))
response = connection.getresponse()

Where the certfile is the cert.pem
the keyfile is the key.pem
and the password is the one I used when I generated the self-signed certificate similar as stated on this answer.

raulra08
  • 191
  • 2
  • 5