Using openssl from the command line was getting really annoying, so I looked for a Python wrapper. There is one, but they recommend using the cryptography library instead if one doesn’t actually need to do client/server stuff.
Handy Python Script
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography import x509
from cryptography.x509.oid import NameOID, ObjectIdentifier
from cryptography.hazmat.primitives import hashes
import datetime
import os
class CertificateManager:
def __init__(self, key_path, cert_path, passphrase):
self.key_path = os.path.expanduser(key_path)
self.cert_path = os.path.expanduser(cert_path)
self.passphrase = passphrase.encode()
def generate_key(self):
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
os.makedirs(os.path.dirname(self.key_path), exist_ok=True)
with open(self.key_path, "wb") as f:
f.write(key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.BestAvailableEncryption(self.passphrase),
))
return key
def load_key(self):
with open(self.key_path, "rb") as key_file:
key = serialization.load_pem_private_key(
key_file.read(),
password=self.passphrase,
)
return key
def create_certificate(self, key, subject, issuer, serial_number, valid_days, extensions):
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
key.public_key()
).serial_number(
serial_number
).not_valid_before(
datetime.datetime.now(datetime.timezone.utc)
).not_valid_after(
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=valid_days)
)
for ext in extensions:
cert = cert.add_extension(ext['extension'], critical=ext['critical'])
cert = cert.sign(key, hashes.SHA256())
os.makedirs(os.path.dirname(self.cert_path), exist_ok=True)
with open(self.cert_path, "wb") as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
return cert
def print_certificate_info(self, cert):
buf = cert.subject.rfc4514_string()
print(f"Subject: {buf}")
buf = cert.issuer.rfc4514_string()
print(f"Issuer: {buf}")
print(f"Valid from: {cert.not_valid_before}")
print(f"Valid to: {cert.not_valid_after}")
print(f"Serial Number: {cert.serial_number}")
#show the x509 extensions we added
for ext in cert.extensions:
print(f"Extension: {ext.oid}")
print(f"Critical: {ext.critical}")
print(f"Value: {ext.value}")
public_key = cert.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
print(f"Public Key: {public_key.decode()}")
# Usage example
if __name__ == "__main__":
key_path = "~/cert/pykey.pem"
cert_path = "~/cert/pycert.pem"
passphrase = "123456789"
manager = CertificateManager(key_path, cert_path, passphrase)
# Generate or load key
if not os.path.exists(manager.key_path):
key = manager.generate_key()
print("Key generated")
else:
key = manager.load_key()
print("Key loaded")
# Define subject and issuer
issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Iowa"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "Muscatine"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Where Labs LLC"),
x509.NameAttribute(NameOID.COMMON_NAME, "https://buspirate.com"),
])
subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "CN"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Guangdong"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "Shenzhen"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Bus Pirate 6"),
x509.NameAttribute(NameOID.COMMON_NAME, "Rev 2"),
])
# Define custom OIDs for the extensions
bpsn_oid = ObjectIdentifier("1.3.6.1.4.1.11129.2.1.1")
rpsn_oid = ObjectIdentifier("1.3.6.1.4.1.11129.2.1.2")
mfgdate_oid = ObjectIdentifier("1.3.6.1.4.1.11129.2.1.3")
# Define extensions
extensions = [
{'extension': x509.UnrecognizedExtension(bpsn_oid, b"BP serial BP123456"), 'critical': False},
{'extension': x509.UnrecognizedExtension(rpsn_oid, b"rPI serial RP123456"), 'critical': False},
{'extension': x509.UnrecognizedExtension(mfgdate_oid, b"MFG date 20230101"), 'critical': False},
]
# Create certificate
cert = manager.create_certificate(key, subject, issuer, 0x1337, (365*100), extensions)
# Print certificate info
manager.print_certificate_info(cert)
This creates a cert from a private key.
ian@DESKTOP-7VKKLTO:~/cert$ openssl x509 -in pycert.pem --inform PEM --text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 4919 (0x1337)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, ST = Iowa, L = Muscatine, O = Where Labs LLC, CN = https://buspirate.com
Validity
Not Before: Jan 30 14:12:59 2025 GMT
Not After : Jan 6 14:12:59 2125 GMT
Subject: C = CN, ST = Guangdong, L = Shenzhen, O = Bus Pirate 6, CN = Rev 2
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:da:1c:42:d3:d2:d5:23:00:34:86:70:cb:95:e2:
...
c5:63
Exponent: 65537 (0x10001)
X509v3 extensions:
1.3.6.1.4.1.11129.2.1.1:
BP serial BP123456
1.3.6.1.4.1.11129.2.1.2:
rPI serial RP123456
1.3.6.1.4.1.11129.2.1.3:
MFG date 20230101
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
bf:f3:0d:13:a2:b5:17:44:6d:47:5d:03:97:10:65:ef:98:f2:
...
45:32:cf:39
Actual cert
-----BEGIN CERTIFICATE-----
MIIDqjCCApKgAwIBAgICEzcwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCVVMx
DTALBgNVBAgMBElvd2ExEjAQBgNVBAcMCU11c2NhdGluZTEXMBUGA1UECgwOV2hl
cmUgTGFicyBMTEMxHjAcBgNVBAMMFWh0dHBzOi8vYnVzcGlyYXRlLmNvbTAgFw0y
NTAxMzAxNDEyNTlaGA8yMTI1MDEwNjE0MTI1OVowWzELMAkGA1UEBhMCQ04xEjAQ
BgNVBAgMCUd1YW5nZG9uZzERMA8GA1UEBwwIU2hlbnpoZW4xFTATBgNVBAoMDEJ1
cyBQaXJhdGUgNjEOMAwGA1UEAwwFUmV2IDIwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDaHELT0tUjADSGcMuV4qqfkbgYyvVjb9txdlR2Dsag88fp5Zhx
vrqHVj1hPsU6DPk7umBlj7bHLUCW4/oajgTwA2ZGRC6kQOgVhVDJJAUdQOY1m5Yu
P25ykRM6P/FiJKpIjPHNXaD3/20CVT7y6VhmreOdxxYMgfeLnCNTh4aVGaHJYVkP
icl2OePLKal8uGjQQndL7BLCKM304UpaN1yF+cfzb03uUyfZiC1cQKr8+9gliTv7
WifwmywMelj6u23tLuOnEUoSMGCP2iUPwaQR7K27tbRNsboblqTB3PpO7aVDrOBP
mp0E1sLPKpBuHu9e9hbEqkAkPPsVMnbLCMVjAgMBAAGjaDBmMCAGCisGAQQB1nkC
AQEEEkJQIHNlcmlhbCBCUDEyMzQ1NjAhBgorBgEEAdZ5AgECBBNyUEkgc2VyaWFs
IFJQMTIzNDU2MB8GCisGAQQB1nkCAQMEEU1GRyBkYXRlIDIwMjMwMTAxMA0GCSqG
SIb3DQEBCwUAA4IBAQC/8w0TorUXRG1HXQOXEGXvmPJRT8M9NPc36rSZ3xN7Nmut
MdaMVNkJ96Wa2usR2TwsvR46MkYcEvasxNdilG3sFYbdSlFFGjXessYDYTc0xudn
eWzzMVd8B00tnXrHi1uOGUWa0PIJcc7X/wm5EEpd/He5438j9LpbATe+S7u18dEi
npz5J1/Dn/MuecubHyYWgpGxTPG4jhFqbB/mlfVAdLfA3qLDhBIBXuFWsMHMbFdY
HghRbBxnR2QYc812RpHmAypE01eIOHfIuxMlyXgxxRjvb3UpFcKqQXevsjXkkC53
hooP3neT9rq1Qj7QkqwJ2KtnJllWI2jcv8ZFMs85
-----END CERTIFICATE-----
With public key:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2hxC09LVIwA0hnDLleKq
n5G4GMr1Y2/bcXZUdg7GoPPH6eWYcb66h1Y9YT7FOgz5O7pgZY+2xy1AluP6Go4E
8ANmRkQupEDoFYVQySQFHUDmNZuWLj9ucpETOj/xYiSqSIzxzV2g9/9tAlU+8ulY
Zq3jnccWDIH3i5wjU4eGlRmhyWFZD4nJdjnjyympfLho0EJ3S+wSwijN9OFKWjdc
hfnH829N7lMn2YgtXECq/PvYJYk7+1on8JssDHpY+rtt7S7jpxFKEjBgj9olD8Gk
Eeytu7W0TbG6G5akwdz6Tu2lQ6zgT5qdBNbCzyqQbh7vXvYWxKpAJDz7FTJ2ywjF
YwIDAQAB
-----END PUBLIC KEY-----
These don’t verify on the Bus Pirate which led me to an issue in the cert command…
uint32_t flags;
ret = mbedtls_x509_crt_verify(&cert, &cert, NULL, NULL, &flags, NULL, NULL);
if (ret != 0) {
char error_buf[100];
mbedtls_strerror(ret, error_buf, 100);
printf("Failed to verify certificate: %s\r\n", error_buf);
return;
}
Yesterday I cribbed this from an example, but today I realize it is verifying the cert against the cert, and not the public key.
ret = mbedtls_pk_verify(
&public_key,
MBEDTLS_MD_SHA256,
cert.raw.p, cert.raw.len - cert.sig.len,
cert.sig.p, cert.sig.len
);
if (ret != 0) {
char error_buf[100];
mbedtls_strerror(ret, error_buf, 100);
printf("Failed to verify certificate: %s\r\n", error_buf);
return;
}
This is the correct function to verify against a public key.
Failed to verify certificate: PK - Bad input parameters to function
Both today’s cert and yesterdays cert now fail verification.
if (ctx->pk_info == NULL ||
pk_hashlen_helper(md_alg, &hash_len) != 0) {
return MBEDTLS_ERR_PK_BAD_INPUT_DATA;
}
This seems to be the code that is returning the error.
I’m really confused at this point.
I think the next step is to verify the python generated cert against the python generated public key using openssl, but I haven’t figured out that part yet.
Update to cert command pushed.