15.3.3. Self-signed certificates¶
The fastest way to get TLS working between two devices you control. Both ends trust a single certificate that you generate yourself; the public CA flow on CA-signed (publicly trusted) certificates is only needed when third-party clients have to connect without being told to trust a custom certificate.
15.3.3.1. Creating a self-signed certificate¶
Run OpenSSL on your development machine. The
subjectAltName (SAN) is what modern TLS clients
check during hostname verification, so set it to the
host name(s) and/or IP address(es) clients will use to
reach the camera (CN alone is legacy and is ignored
by many clients). Replace DNS:openmv /
IP:192.168.1.50 with the address your clients
actually connect to.
ECDSA P-256 – recommended:
# Generate a P-256 private key.
openssl ecparam -name prime256v1 -genkey -noout -out server.key
# Self-signed certificate valid for one year, with a SAN.
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=openmv" -addext "subjectAltName=DNS:openmv,IP:192.168.1.50"
ECDSA P-384 – stronger, larger/slower:
openssl ecparam -name secp384r1 -genkey -noout -out server.key
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=openmv" -addext "subjectAltName=DNS:openmv,IP:192.168.1.50"
RSA-2048 – maximum compatibility:
openssl req -new -x509 -newkey rsa:2048 -nodes -keyout server.key \
-out server.crt -days 365 -subj "/CN=openmv" \
-addext "subjectAltName=DNS:openmv,IP:192.168.1.50"
Note
A client certificate (used for mutual
authentication, below) is created with exactly these
same commands – there is nothing client-specific
about the certificate itself. Just generate a second,
independent key/certificate pair under different
names (e.g. client.key / client.crt) and use
it on the client as shown in the mTLS example. The
subjectAltName only matters for the side whose
host name the peer verifies (the client checks the
server’s name; nothing checks the client’s), so it
can be omitted for a client-only certificate. The
-subj / CN is likewise just a label on a
client certificate – the server side here checks
only that the certificate chains to a trusted CA, it
never matches the name – so set it to whatever
identifies that client (e.g. /CN=sensor-01).
Keep some -subj value regardless, so OpenSSL
can generate the certificate non-interactively.
Certificate lifetime is set by -days; certificates
expire and must be regenerated and redeployed before
then – see Operations: keys, expiry, and troubleshooting.
15.3.3.2. Converting to DER¶
Convert both the certificate and the private key to DER before copying them to the camera:
openssl x509 -in server.crt -outform DER -out server.der
openssl pkey -in server.key -outform DER -out server.key.der
15.3.3.3. Copying files to the camera¶
Copy the DER files to the camera’s filesystem – for
example by dragging them onto the OpenMV Cam’s USB
drive, or with mpremote cp server.der : and
mpremote cp server.key.der :. On the verifying
side, also copy the CA / peer certificate in DER form.
The DER files do not have to live on the writable
filesystem. MicroPython can also mount a read-only
ROMFS image at /rom, and certificates placed
there are loaded exactly like any other file – e.g.
ctx.load_cert_chain("/rom/server.der",
"/rom/server.key.der"). A ROMFS image is prepared on
your development machine (for example with
mpremote romfs) and is read-only at runtime, so the
certificate cannot be altered on the device – useful
for locking down a production unit. Note that a private
key stored in ROMFS is still readable by code running
on the camera; ROMFS protects against modification,
not extraction. A ROMFS-resident certificate can only
be replaced by rebuilding and reflashing the image, so
weigh that against the rotation discussion on
Operations: keys, expiry, and troubleshooting.
15.3.3.4. Using the certificate¶
The clock setup from Prerequisites: OpenSSL and the clock has to happen before any of these examples; the validity check fails otherwise.
A complete client that sets the clock, opens a socket, verifies a self-signed server, and exchanges data:
import socket
import ssl
import ntptime
ntptime.settime() # correct clock for the validity check
# Open a plain TCP connection.
addr = socket.getaddrinfo("openmv", 8443)[0][-1]
sock = socket.socket()
sock.connect(addr)
# Wrap it for TLS, trusting the server's self-signed certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="server.der")
ssock = ctx.wrap_socket(sock, server_hostname="openmv")
ssock.write(b"hello\n")
print(ssock.read())
ssock.close()
A complete server presenting its certificate and key:
import socket
import ssl
import ntptime
ntptime.settime() # correct clock for the validity check
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key.der")
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(socket.getaddrinfo("0.0.0.0", 8443)[0][-1])
sock.listen(1)
while True:
client, addr = sock.accept()
sclient = ctx.wrap_socket(client, server_side=True)
sclient.write(b"hello\n")
print(sclient.read())
sclient.close()
For mutual authentication (mTLS) the server additionally requires and verifies a client certificate, and the client presents one of its own:
# Server side: also demand and verify a client certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key.der")
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="client.der")
# Client side: present a certificate of our own.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.load_cert_chain("client.der", "client.key.der")
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="server.der")
See the ssl module documentation for the full
API.
Note
Everything on this page applies unchanged to
DTLS (TLS over UDP). The keys, certificates,
DER format, trust model, expiry concerns, and the
load_cert_chain / load_verify_locations
calls are identical; only the transport differs –
you wrap a socket.SOCK_DGRAM socket and select
ssl.PROTOCOL_DTLS_CLIENT /
ssl.PROTOCOL_DTLS_SERVER instead of the
TLS protocol constants. The one extra wrinkle is
a server-side anti-spoofing cookie – the first
connection from a new client is expected to fail and
the client simply retries; see DTLS support for
details.