TLS/SSL socket python server - python-3.x

I am trying to build a simple HTTPS server with Python3 using the socket and ssl modules.
I have a self signed certificate and a private key files generated by OpenSSL and I tried to use them with the ssl module but every time I try, I get a "ssl.SSLError: [SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] sslv3 alert certificate unknown (_ssl.c:1076)" error.
My code is
import socket
import ssl
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
context.load_cert_chain(certfile='cert.pem', keyfile='my_key.key')
context.verify_mode = ssl.CERT_NONE
sock = socket.socket()
sock.bind(('', 443))
sock.listen(5)
while True:
new_conn, addr = sock.accept()
ssl_conn = context.wrap_socket(new_conn, server_side=True)
print(ssl_conn.recv(1024).decode()) # this is where i get the error
The error I get is:
File "C:\AllInOne\PortableApps\Python374\lib\ssl.py", line 1139, in do_handshake
self._sslobj.do_handshake()
ssl.SSLError: [SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] sslv3 alert certificate unknown (_ssl.c:1076)
Does anyone know why this happens or how to fix it?

Generate server.pem with the following command:
mkdir .ssh
openssl req -new -x509 -keyout .ssh/key.pem -out .ssh/cert.pem -days 365 -nodes
run as follows:
python3 simple-https-server.py
Then in your browser, visit:
https://localhost:4443
Here is the code:
import http.server
from http.server import HTTPServer, BaseHTTPRequestHandler, SimpleHTTPRequestHandler
import ssl
import sys
# This class will handles any incoming request from the browser
class myHandler(BaseHTTPRequestHandler):
# Handler for the GET requests
def do_GET(self):
print(self.requestline)
# print(self.rfile.read(content_length))
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
# Send the html message
self.wfile.write("Hello World !".encode())
return
try:
separator = "-" * 80
server_address = ("", 4443)
# server_address = ('localhost', 4443)
httpd = http.server.HTTPServer(server_address, myHandler)
# httpd = http.server.HTTPServer(server_address, http.server.SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket(
httpd.socket,
server_side=True,
certfile=".ssh/cert.pem",
keyfile=".ssh/key.pem",
ssl_version=ssl.PROTOCOL_TLS,
)
print(separator)
print("Server running on https://localhost:4443")
print(separator)
# Wait forever for incoming htto requests
httpd.serve_forever()
except KeyboardInterrupt:
print("^C received, shutting down the web server")
server.socket.close()

ssl.SSLError: [SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] sslv3 alert certificate unknown (_ssl.c:1076)
The client signals your server that it is does not trust your certificate. Which is expected since this is not a certificate issued by a trusted CA and you did not make the client explicit trust this certificate. If the client would not complain it would be insecure since every man in the middle could just use a fake certificate to identify itself as a trusted server.

Related

SSL error while sending email using smtp with Python

I want to send an email using outlook. The code is as follows:
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg['From'] = '*******'
msg['Subject'] = 'Some subject here'
msg['To'] = '********'
msg.set_content('Some text here')
with smtplib.SMTP_SSL('smtp-mail.outlook.com', 587) as smtp:
smtp.login('******', '****')
smtp.send_message(msg)
print('Email sent!')
I get the following error:
---------------------------------------------------------------------------
SSLError Traceback (most recent call last)
<ipython-input-8-4d5956f55c88> in <module>
6 msg.set_content('Some text here')
7
----> 8 with smtplib.SMTP_SSL('smtp-mail.outlook.com', 587) as smtp:
9 smtp.login('sender_email', 'password')
10 smtp.send_message(msg)
~/anaconda/envs/quant2/lib/python3.6/smtplib.py in __init__(self, host, port, local_hostname, keyfile, certfile, timeout, source_address, context)
1029 self.context = context
1030 SMTP.__init__(self, host, port, local_hostname, timeout,
-> 1031 source_address)
1032
1033 def _get_socket(self, host, port, timeout):
~/anaconda/envs/quant2/lib/python3.6/smtplib.py in __init__(self, host, port, local_hostname, timeout, source_address)
249
250 if host:
--> 251 (code, msg) = self.connect(host, port)
252 if code != 220:
253 self.close()
~/anaconda/envs/quant2/lib/python3.6/smtplib.py in connect(self, host, port, source_address)
334 if self.debuglevel > 0:
335 self._print_debug('connect:', (host, port))
--> 336 self.sock = self._get_socket(host, port, self.timeout)
337 self.file = None
338 (code, msg) = self.getreply()
~/anaconda/envs/quant2/lib/python3.6/smtplib.py in _get_socket(self, host, port, timeout)
1037 self.source_address)
1038 new_socket = self.context.wrap_socket(new_socket,
-> 1039 server_hostname=self._host)
1040 return new_socket
1041
~/anaconda/envs/quant2/lib/python3.6/ssl.py in wrap_socket(self, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, session)
405 suppress_ragged_eofs=suppress_ragged_eofs,
406 server_hostname=server_hostname,
--> 407 _context=self, _session=session)
408
409 def wrap_bio(self, incoming, outgoing, server_side=False,
~/anaconda/envs/quant2/lib/python3.6/ssl.py in __init__(self, sock, keyfile, certfile, server_side, cert_reqs, ssl_version, ca_certs, do_handshake_on_connect, family, type, proto, fileno, suppress_ragged_eofs, npn_protocols, ciphers, server_hostname, _context, _session)
815 # non-blocking
816 raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
--> 817 self.do_handshake()
818
819 except (OSError, ValueError):
~/anaconda/envs/quant2/lib/python3.6/ssl.py in do_handshake(self, block)
1075 if timeout == 0.0 and block:
1076 self.settimeout(None)
-> 1077 self._sslobj.do_handshake()
1078 finally:
1079 self.settimeout(timeout)
~/anaconda/envs/quant2/lib/python3.6/ssl.py in do_handshake(self)
687 def do_handshake(self):
688 """Start the SSL/TLS handshake."""
--> 689 self._sslobj.do_handshake()
690 if self.context.check_hostname:
691 if not self.server_hostname:
SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:852)
Microsoft Outlook uses STARTTLS when sending an email. So you need to replace smtplib.SMTP_SSL with smtplib.SMTP and you need to call starttls
reference: smtplib.SMTP.starttls
import smtplib
from email.message import EmailMessage
sender = 'somename#outlook.com'
recipient = 'somename#gmail.com'
msg = EmailMessage()
msg.set_content('this is a test')
msg['From'] = 'somename#outlook.com'
msg['To'] = 'somename#gmail.com'
msg['Subject'] = 'test email'
with smtplib.SMTP('smtp.office365.com', 587) as server:
server.ehlo()
server.starttls()
server.ehlo()
server.ehlo()
server.login('your_login', "your_password", initial_response_ok=True)
server.ehlo()
server.sendmail(sender, recipient, msg.as_string())
print('Email sent!')
server.close()
Here is the Outlook message in my Gmail account.
I noted that I had to change my Outlook password, because it had a \n, which Python read as a new line.
----------------------------------------
My system information
----------------------------------------
Platform: macOS
OS Version: 10.15.7
Python Version: 3.9
----------------------------------------
Your question didn't identity the type of Outlook account you have.
Free account
Corporate account
You stated in the comments below that your error message included ask your email administrator. I haven't seem this message with the Free account so I'm assuming that you might have a Corporate account. If you do have the latter please review this Enable or disable authenticated client SMTP submission, because an email administrator would need to enable Authenticated SMTP on your corporate account.
The answer from user "Life is complex" is almost right. I would like to add few more things from myside which might help. I am not very sure which python version you are using here. I am guessing it as Python 3.X. You need to go through based on actual version of python you are using in your case.
From Python smtplib documents, link. Please check which python version you are using with below guide line.
class smtplib.SMTP_SSL(host='', port=0, local_hostname=None,
keyfile=None, certfile=None, [timeout, ]context=None,
source_address=None) An SMTP_SSL instance behaves exactly the same as
instances of SMTP. SMTP_SSL should be used for situations where SSL is
required from the beginning of the connection and using starttls() is
not appropriate. If host is not specified, the local host is used. If
port is zero, the standard SMTP-over-SSL port (465) is used. The
optional arguments local_hostname, timeout and source_address have the
same meaning as they do in the SMTP class. context, also optional, can
contain a SSLContext and allows configuring various aspects of the
secure connection. Please read Security considerations for best
practices.
keyfile and certfile are a legacy alternative to context, and can
point to a PEM formatted private key and certificate chain file for
the SSL connection.
Changed in version 3.3: context was added.
Changed in version 3.3: source_address argument was added.
Changed in version 3.4: The class now supports hostname check with
ssl.SSLContext.check_hostname and Server Name Indication (see
ssl.HAS_SNI).
Deprecated since version 3.6: keyfile and certfile are deprecated in
favor of context. Please use ssl.SSLContext.load_cert_chain() instead,
or let ssl.create_default_context() select the system’s trusted CA
certificates for you.
Changed in version 3.9: If the timeout parameter is set to be zero, it
will raise a ValueError to prevent the creation of a non-blocking
socket
You need to check that SSLV3 is enabled at Server end or not. Check out this link to see which client version can connect to which server version SSL Version Compatibility first.
import smtplib
import ssl
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
#Above line is required to switch default SSLV3 protocol to TLS, recommended by python docs
# If you want to use SSLV3 and you are sure that it is enabled on server end then use
#context = ssl.SSLContext(ssl.PROTOCOL_SSLv3)
connection = smtplib.SMTP('smtp-mail.outlook.com', 587)
connection.ehlo()
connection.starttls(context=context)
connection.ehlo()
connection.login('now_your_real_login_data#outlook.com', 'otherwise_SMTPServerDisconnect')
The accepted answer didn't work for me, I kept getting this error:
smtplib.SMTPNotSupportedError: STARTTLS extension not supported by server.
I also tried SMTP_SSL instead of SMTP:
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
with smtplib.SMTP_SSL('smtp-mail.outlook.com', 587, context=context) as server:
server.ehlo()
server.starttls(context=context)
but got this error:
[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1131)
Fortunately this post (thanks #dhruv-gami) helped me find my issue.
I found out that my hostname (using WSL on windows) is uppercase, but Outlook servers do not accept uppercase host names.
The solution is to just add a lowercase string in the ehlo commands:
with smtplib.SMTP('smtp.office365.com', 587) as server:
server.ehlo('lowercase')
server.starttls()
server.ehlo('lowercase')
server.login('your_login', "your_password", initial_response_ok=True)

Is there a way to process CRL updates without creating a new socket

I'm trying to create a socket server which includes a CRL, the clients maintain socket connection so restarting or reseting the server socket dissconnects all clients. I would like to maintain a CRL however I dont want to have to restart the server every time the CRL is updated to reset the socket. Does anyone know of a way to update an existing ssl socket?
Server code:
import socket, ssl
from certvalidator import CertificateValidator, ValidationContext
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF
context.verify_mode = ssl.CERT_REQUIRED
context.load_verify_locations(cafile=r"path to ca with crl appended")
context.load_cert_chain(r"path to crt", r"path to key", password='pass') ###############
bindsocket = socket.socket()
bindsocket.bind(('', 9000))
bindsocket.listen(5)
while True:
newsocket, fromaddr = bindsocket.accept()
sslsoc = context.wrap_socket(newsocket, server_side=True)
request = sslsoc.read()
print(request)
Client code:
import socket
import ssl
hostname = '127.0.0.1'
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations(cafile="path to CA")
context.load_cert_chain(r"path to cer", r"path to key", password='pass')
context.check_hostname = False
with socket.create_connection((hostname, 8089)) as sock:
with context.wrap_socket(sock) as ssock:
print(ssock.version())
ssock.do_handshake()
print('connected')
ssock.send(b'122222')
while True:
header = ssock.recv(4)
print(header)
data = ssock.recv(10000)
print(header + data)
print(len(data))
print()
The server socket bindsocket is not affected from the change of CRL in the first place, since it is a plain TCP socket. All what needs to be changed is the SSL context context and this can be done at any time without any changes needed to bindsocket. The new SSL context with the changed URL will then be used for the next client when doing the TLS handshake using context.wrap_socket.
restarting or reseting the server socket dissconnects all clients
This is not true. The listener bindsocket and the client connection newsocket are independent from each other, i.e. closing bindsocket has no effect on newsocket. Of course, restarting the full server application will disconnect all clients since close of the application will close all sockets.

"Connection unexpectedly closed" error while sending email using SES from AWS

So I'm trying to send an email to my self using SMTP and AWS. The email I'm using on my configuration is verified since I'm still using sandbox mode in SES. While running the script I keep getting the error Connection unexpectedly closed even dough I tried to connect with OpenSSL and it connected but it showed a Didn't find STARTTLS in server response, trying anyway... error after connecting.
Here is my code:
MAIL = {}
MAIL['USERNAME'] = 'AKIAXARHTFGFKCDAO7PD'
MAIL['PASSWORD'] = 'BE0tXPq8wteiKZYtgD2TgtfFTGhgFGOhUp3F0lG0uqn'
MAIL['HOST'] = 'email-smtp.eu-central-1.amazonaws.com'
MAIL['PORT'] = 465
# Set user code
code = random.randrange(000000, 999999)
# Send email to user
print(code)
print(current_user.email)
msg = MIMEMultipart('alternative')
msg['Subject'] = 'Ruby - Verification code'
msg['From'] = 'amng835#gmail.com'
msg['To'] = current_user.email
msg.attach(MIMEText(f'Your verification code is: {code}', 'plain'))
try:
server = smtplib.SMTP(MAIL['HOST'], MAIL['PORT'])
server.ehlo()
server.starttls()
server.ehlo()
server.login(MAIL('MAIL_USERNAME'), MAIL('MAIL_PASSWORD'))
server.sendmail('amng835#gmail.com', current_user.email, msg.as_string())
server.close()
except Exception as error:
print('Failed to send message to user')
print(error)
OpenSSL output:
The command:
openssl s_client -connect email-smtp.eu-central-1.amazonaws.com:465 -starttls smtp
The output:
CONNECTED(00000005)
Didn't find STARTTLS in server response, trying anyway...
write:errno=0
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 372 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
My documentation source:
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/examples-send-using-smtp.html
There seems to have some issue with port 465.Change the code to below and it will work fine.
MAIL['PORT'] = 587

Python module that will handle connections and add in a Proxy? [duplicate]

I'd like to manually (using the socket and ssl modules) make an HTTPS request through a proxy which itself uses HTTPS.
I can perform the initial CONNECT exchange just fine:
import ssl, socket
PROXY_ADDR = ("proxy-addr", 443)
CONNECT = "CONNECT example.com:443 HTTP/1.1\r\n\r\n"
sock = socket.create_connection(PROXY_ADDR)
sock = ssl.wrap_socket(sock)
sock.sendall(CONNECT)
s = ""
while s[-4:] != "\r\n\r\n":
s += sock.recv(1)
print repr(s)
The above code prints HTTP/1.1 200 Connection established plus some headers, which is what I expect. So now I should be ready to make the request, e.g.
sock.sendall("GET / HTTP/1.1\r\n\r\n")
but the above code returns
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
Reason: You're speaking plain HTTP to an SSL-enabled server port.<br />
Instead use the HTTPS scheme to access this URL, please.<br />
</body></html>
This makes sense too, since I still need to do an SSL handshake with the example.com server to which I'm tunneling. However, if instead of immediately sending the GET request I say
sock = ssl.wrap_socket(sock)
to do the handshake with the remote server, then I get an exception:
Traceback (most recent call last):
File "so_test.py", line 18, in <module>
ssl.wrap_socket(sock)
File "/usr/lib/python2.6/ssl.py", line 350, in wrap_socket
suppress_ragged_eofs=suppress_ragged_eofs)
File "/usr/lib/python2.6/ssl.py", line 118, in __init__
self.do_handshake()
File "/usr/lib/python2.6/ssl.py", line 293, in do_handshake
self._sslobj.do_handshake()
ssl.SSLError: [Errno 1] _ssl.c:480: error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol
So how can I do the SSL handshake with the remote example.com server?
EDIT: I'm pretty sure that no additional data is available before my second call to wrap_socket because calling sock.recv(1) blocks indefinitely.
This should work if the CONNECT string is rewritten as follows:
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
Not sure why this works, but maybe it has something to do with the proxy I'm using. Here's an example code:
from OpenSSL import SSL
import socket
def verify_cb(conn, cert, errun, depth, ok):
return True
server = 'mail.google.com'
port = 443
PROXY_ADDR = ("proxy.example.com", 3128)
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(PROXY_ADDR)
s.send(CONNECT)
print s.recv(4096)
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_verify(SSL.VERIFY_PEER, verify_cb)
ss = SSL.Connection(ctx, s)
ss.set_connect_state()
ss.do_handshake()
cert = ss.get_peer_certificate()
print cert.get_subject()
ss.shutdown()
ss.close()
Note how the socket is first opened and then open socket placed in SSL context. Then I manually initialize SSL handshake. And output:
HTTP/1.1 200 Connection established
<X509Name object '/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mail.google.com'>
It's based on pyOpenSSL because I needed to fetch invalid certificates too and Python built-in ssl module will always try to verify the certificate if it's received.
Judging from the API of the OpenSSL and GnuTLS library, stacking a SSLSocket onto a SSLSocket is actually not straightforwardly possible as they provide special read/write functions to implement the encryption, which they are not able to use themselves when wrapping a pre-existing SSLSocket.
The error is therefore caused by the inner SSLSocket directly reading from the system socket and not from the outer SSLSocket. This ends in sending data not belonging to the outer SSL session, which ends badly and for sure never returns a valid ServerHello.
Concluding from that, I would say there is no simple way to implement what you (and actually myself) would like to accomplish.
Finally I got somewhere expanding on #kravietz and #02strich answers.
Here's the code
import threading
import select
import socket
import ssl
server = 'mail.google.com'
port = 443
PROXY = ("localhost", 4433)
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
class ForwardedSocket(threading.Thread):
def __init__(self, s, **kwargs):
threading.Thread.__init__(self)
self.dest = s
self.oursraw, self.theirsraw = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
self.theirs = socket.socket(_sock=self.theirsraw)
self.start()
self.ours = ssl.wrap_socket(socket.socket(_sock=self.oursraw), **kwargs)
def run(self):
rl, wl, xl = select.select([self.dest, self.theirs], [], [], 1)
print rl, wl, xl
# FIXME write may block
if self.theirs in rl:
self.dest.send(self.theirs.recv(4096))
if self.dest in rl:
self.theirs.send(self.dest.recv(4096))
def recv(self, *args):
return self.ours.recv(*args)
def send(self, *args):
return self.outs.recv(*args)
def test():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(PROXY)
s = ssl.wrap_socket(s, ciphers="ALL:aNULL:eNULL")
s.send(CONNECT)
resp = s.read(4096)
print (resp, )
fs = ForwardedSocket(s, ciphers="ALL:aNULL:eNULL")
fs.send("foobar")
Don't mind custom cihpers=, that only because I didn't want to deal with certificates.
And there's depth-1 ssl output, showing CONNECT, my response to it ssagd and depth-2 ssl negotiation and binary rubbish:
[dima#bmg ~]$ openssl s_server -nocert -cipher "ALL:aNULL:eNULL"
Using default temp DH parameters
Using default temp ECDH parameters
ACCEPT
-----BEGIN SSL SESSION PARAMETERS-----
MHUCAQECAgMDBALAGQQgmn6XfJt8ru+edj6BXljltJf43Sz6AmacYM/dSmrhgl4E
MOztEauhPoixCwS84DL29MD/OxuxuvG5tnkN59ikoqtfrnCKsk8Y9JtUU9zuaDFV
ZaEGAgRSnJ81ogQCAgEspAYEBAEAAAA=
-----END SSL SESSION PARAMETERS-----
Shared ciphers: [snipped]
CIPHER is AECDH-AES256-SHA
Secure Renegotiation IS supported
CONNECT mail.google.com:443 HTTP/1.0
Connection: close
sagq
�u\�0�,�(�$��
�"�!��kj98���� �m:��2�.�*�&���=5�����
��/�+�'�#�� ����g#32��ED���l4�F�1�-�)�%���</�A������
�� ������
�;��A��q�J&O��y�l
It doesn't sound like there's anything wrong with what you're doing; it's certainly possible to call wrap_socket() on an existing SSLSocket.
The 'unknown protocol' error can occur (amongst other reasons) if there's extra data waiting to be read on the socket at the point you call wrap_socket(), for instance an extra \r\n or an HTTP error (due to a missing cert on the server end, for instance). Are you certain you've read everything available at that point?
If you can force the first SSL channel to use a "plain" RSA cipher (i.e. non-Diffie-Hellman) then you may be able to use Wireshark to decrypt the stream to see what's going on.
Building on #kravietz answer. Here is a version that works in Python3 through a Squid proxy:
from OpenSSL import SSL
import socket
def verify_cb(conn, cert, errun, depth, ok):
return True
server = 'mail.google.com'
port = 443
PROXY_ADDR = ("<proxy_server>", 3128)
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(PROXY_ADDR)
s.send(str.encode(CONNECT))
s.recv(4096)
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_verify(SSL.VERIFY_PEER, verify_cb)
ss = SSL.Connection(ctx, s)
ss.set_connect_state()
ss.do_handshake()
cert = ss.get_peer_certificate()
print(cert.get_subject())
ss.shutdown()
ss.close()
This works in Python 2 also.

Trying to automate certificate generation with python3.cryptography; not having luck

I'm trying to programmatically create client certificates for mosquitto clients to use. Using XCA, I've been able to create a self signed CA, an intermediate, and then a certificate from the intermediate. I've configured mosquitto with the `require_certificate true' option. Using keys exported from XCA, I'm able to use the paho clients for testing:
mosquitto_sub -h ubuntu -p 8765 -t /1/2/3 --cafile ~/CA2/Chain.crt -d --cert ./test1.crt --key ./test1.pem
This works.
Now I'm trying to automate the key generation with a python3 script using the cryptography module.
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.x509.oid import NameOID
from pathlib import Path
from datetime import datetime, timezone, timedelta
import uuid
privateKey = rsa.generate_private_key(
public_exponent=65537,
key_size=4096,
backend=default_backend())
with Path('test2.pem').open('wb') as stream:
stream.write(privateKey.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()))
builder = x509.CertificateBuilder()
builder = builder.subject_name(x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, uuid.uuid4().hex),
]))
builder = builder.issuer_name(x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, u'ubuntu'),
]))
builder = builder.not_valid_before(datetime.now(timezone.utc) - timedelta(days=1))
builder = builder.not_valid_after(datetime.now(timezone.utc) + timedelta(days=365*50))
builder = builder.serial_number(x509.random_serial_number())
builder = builder.public_key(privateKey.public_key())
builder = builder.add_extension(
x509.BasicConstraints(ca=False, path_length=None), critical=True,
)
builder = builder.add_extension(
x509.SubjectKeyIdentifier.from_public_key(privateKey.public_key()), critical=False,
)
builder = builder.add_extension(
x509.KeyUsage(digital_signature=True,
content_commitment=False,
key_encipherment=True,
data_encipherment=True,
key_agreement=False,
key_cert_sign=False,
crl_sign=False,
encipher_only=False,
decipher_only=False), critical=False,
)
certificate = builder.sign(
private_key=privateKey, algorithm=hashes.SHA256(),
backend=default_backend()
)
print(certificate)
with Path('test2.crt').open('wb') as stream:
stream.write(certificate.public_bytes(encoding=serialization.Encoding.PEM))
I've used openssl x509 -in certFile.crt -text -noout to compare the XCA variant vs my automated one. They look darn near identical to me. However, the sub doesn't work:
$ mosquitto_sub -h ubuntu -p 8765 -t /1/2/3 --cafile ~/CA2/Chain.crt -d --cert ./cert.crt --key ./key.pem
Client mosqsub|34613-ubuntu sending CONNECT
Error: A TLS error occurred.
The problem as pointed out in another source, was that I was signing the new certificate with the same key as I was generating the certificate from. Like the issue name is to be the same name as the issuing certificate, the signing key should be the private key of that same issuing certificate. Replacing the signing clause with something like:
parentKey = serialization.load_pem_private_key(
Path('path/to/intermediate_key.pem').read_bytes(),
password=None,
backend=default_backend())
certificate = builder.sign(
private_key=parentKey,
algorithm=hashes.SHA256(),
backend=default_backend()
)

Resources