Hoernchen has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/40464?usp=email )
Change subject: smdpp: add proper tls support, cert generation ......................................................................
smdpp: add proper tls support, cert generation
If TLS is enabled (default) it will automagically generate missing pem files + dh params. A faithful reproduction of the certs found in SGP.26_v1.5_Certificates_18_07_2024.zip can be generated by running contrib/generate_certs.py. This allows adjusting the expiry dates, CA flag, and other parameters. They can be used by running $ python -u osmo-smdpp.py -c generated
Change-Id: I84b2666422b8ff565620f3827ef4d4d7635a21be --- M .gitignore A contrib/generate_certs.py M osmo-smdpp.py 3 files changed, 705 insertions(+), 12 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/64/40464/1
diff --git a/.gitignore b/.gitignore index e9fa1d3..6b74841 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ /smdpp-data/sm-dp-sessions dist tags +smdpp-data/certs/DPtls/CERT_S_SM_DP_TLS_NIST.pem +smdpp-data/generated +smdpp-data/certs/dhparam2048.pem diff --git a/contrib/generate_certs.py b/contrib/generate_certs.py new file mode 100755 index 0000000..c107737 --- /dev/null +++ b/contrib/generate_certs.py @@ -0,0 +1,659 @@ +#!/usr/bin/env python3 + +""" +Faithfully reproduces the certs contained in SGP.26_v1.5_Certificates_18_07_2024.zip +""" + +import os +import binascii +from datetime import datetime +from cryptography import x509 +from cryptography.x509.oid import NameOID, ExtensionOID +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.backends import default_backend + +# Custom OIDs used in certificates +OID_CERTIFICATE_POLICIES_CI = "2.23.146.1.2.1.0" # CI cert policy +OID_CERTIFICATE_POLICIES_TLS = "2.23.146.1.2.1.3" # DPtls cert policy +OID_CERTIFICATE_POLICIES_AUTH = "2.23.146.1.2.1.4" # DPauth cert policy +OID_CERTIFICATE_POLICIES_PB = "2.23.146.1.2.1.5" # DPpb cert policy + +# Subject Alternative Name OIDs +OID_CI_RID = "2.999.1" # CI Registered ID +OID_DP_RID = "2.999.10" # DP+ Registered ID +OID_DP2_RID = "2.999.12" # DP+2 Registered ID +OID_DP4_RID = "2.999.14" # DP+4 Registered ID +OID_DP8_RID = "2.999.18" # DP+8 Registered ID + + +class SimplifiedCertificateGenerator: + def __init__(self): + self.backend = default_backend() + # Store generated CI keys to sign other certs + self.ci_certs = {} # {"BRP": cert, "NIST": cert} + self.ci_keys = {} # {"BRP": key, "NIST": key} + + def get_curve(self, curve_type): + """Get the appropriate curve object.""" + if curve_type == "BRP": + return ec.BrainpoolP256R1() + else: + return ec.SECP256R1() + + def generate_key_pair(self, curve): + """Generate a new EC key pair.""" + private_key = ec.generate_private_key(curve, self.backend) + return private_key + + def load_private_key_from_hex(self, hex_key, curve): + """Load EC private key from hex string.""" + key_bytes = binascii.unhexlify(hex_key.replace(":", "").replace(" ", "").replace("\n", "")) + key_int = int.from_bytes(key_bytes, 'big') + return ec.derive_private_key(key_int, curve, self.backend) + + def generate_ci_cert(self, curve_type): + """Generate CI certificate for either BRP or NIST curve.""" + curve = self.get_curve(curve_type) + private_key = self.generate_key_pair(curve) + + # Build subject and issuer (self-signed) - same for both + subject = issuer = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, "Test CI"), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "TESTCERT"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "RSPTEST"), + x509.NameAttribute(NameOID.COUNTRY_NAME, "IT"), + ]) + + # Build certificate - all parameters same for both + builder = x509.CertificateBuilder() + builder = builder.subject_name(subject) + builder = builder.issuer_name(issuer) + builder = builder.not_valid_before(datetime(2020, 4, 1, 8, 27, 51)) + builder = builder.not_valid_after(datetime(2055, 4, 1, 8, 27, 51)) + builder = builder.serial_number(0xb874f3abfa6c44d3) + builder = builder.public_key(private_key.public_key()) + + # Add extensions - all same for both + builder = builder.add_extension( + x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()), + critical=False + ) + + builder = builder.add_extension( + x509.BasicConstraints(ca=True, path_length=None), + critical=True + ) + + builder = builder.add_extension( + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier(OID_CERTIFICATE_POLICIES_CI), + policy_qualifiers=None + ) + ]), + critical=True + ) + + builder = builder.add_extension( + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=True, + encipher_only=False, + decipher_only=False + ), + critical=True + ) + + builder = builder.add_extension( + x509.SubjectAlternativeName([ + x509.RegisteredID(x509.ObjectIdentifier(OID_CI_RID)) + ]), + critical=False + ) + + builder = builder.add_extension( + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier("http://ci.test.example.com/CRL-A.crl")], + relative_name=None, + reasons=None, + crl_issuer=None + ), + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier("http://ci.test.example.com/CRL-B.crl")], + relative_name=None, + reasons=None, + crl_issuer=None + ) + ]), + critical=False + ) + + certificate = builder.sign(private_key, hashes.SHA256(), self.backend) + + self.ci_keys[curve_type] = private_key + self.ci_certs[curve_type] = certificate + + return certificate, private_key + + def generate_dp_cert(self, curve_type, subject_cn, serial, key_hex, + cert_policy_oid, rid_oid, validity_start, validity_end): + """Generate a DP certificate signed by CI - works for both BRP and NIST.""" + curve = self.get_curve(curve_type) + private_key = self.load_private_key_from_hex(key_hex, curve) + + ci_cert = self.ci_certs[curve_type] + ci_key = self.ci_keys[curve_type] + + subject = x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "ACME"), + x509.NameAttribute(NameOID.COMMON_NAME, subject_cn), + ]) + + builder = x509.CertificateBuilder() + builder = builder.subject_name(subject) + builder = builder.issuer_name(ci_cert.subject) + builder = builder.not_valid_before(validity_start) + builder = builder.not_valid_after(validity_end) + builder = builder.serial_number(serial) + builder = builder.public_key(private_key.public_key()) + + builder = builder.add_extension( + x509.AuthorityKeyIdentifier.from_issuer_public_key(ci_key.public_key()), + critical=False + ) + + builder = builder.add_extension( + x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()), + critical=False + ) + + builder = builder.add_extension( + x509.SubjectAlternativeName([ + x509.RegisteredID(x509.ObjectIdentifier(rid_oid)) + ]), + critical=False + ) + + builder = builder.add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False + ), + critical=True + ) + + builder = builder.add_extension( + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier(cert_policy_oid), + policy_qualifiers=None + ) + ]), + critical=True + ) + + builder = builder.add_extension( + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier("http://ci.test.example.com/CRL-A.crl")], + relative_name=None, + reasons=None, + crl_issuer=None + ), + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier("http://ci.test.example.com/CRL-B.crl")], + relative_name=None, + reasons=None, + crl_issuer=None + ) + ]), + critical=False + ) + + certificate = builder.sign(ci_key, hashes.SHA256(), self.backend) + + return certificate, private_key + + def generate_tls_cert(self, curve_type, subject_cn, dns_name, serial, key_hex, + rid_oid, validity_start, validity_end): + """Generate a TLS certificate signed by CI.""" + curve = self.get_curve(curve_type) + private_key = self.load_private_key_from_hex(key_hex, curve) + + ci_cert = self.ci_certs[curve_type] + ci_key = self.ci_keys[curve_type] + + subject = x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "ACME"), + x509.NameAttribute(NameOID.COMMON_NAME, subject_cn), + ]) + + builder = x509.CertificateBuilder() + builder = builder.subject_name(subject) + builder = builder.issuer_name(ci_cert.subject) + builder = builder.not_valid_before(validity_start) + builder = builder.not_valid_after(validity_end) + builder = builder.serial_number(serial) + builder = builder.public_key(private_key.public_key()) + + builder = builder.add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False + ), + critical=True + ) + + builder = builder.add_extension( + x509.ExtendedKeyUsage([ + x509.oid.ExtendedKeyUsageOID.SERVER_AUTH, + x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH + ]), + critical=True + ) + + builder = builder.add_extension( + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier(OID_CERTIFICATE_POLICIES_TLS), + policy_qualifiers=None + ) + ]), + critical=False + ) + + builder = builder.add_extension( + x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()), + critical=False + ) + + builder = builder.add_extension( + x509.AuthorityKeyIdentifier.from_issuer_public_key(ci_key.public_key()), + critical=False + ) + + builder = builder.add_extension( + x509.SubjectAlternativeName([ + x509.DNSName(dns_name), + x509.RegisteredID(x509.ObjectIdentifier(rid_oid)) + ]), + critical=False + ) + + builder = builder.add_extension( + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier("http://ci.test.example.com/CRL-A.crl")], + relative_name=None, + reasons=None, + crl_issuer=None + ), + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier("http://ci.test.example.com/CRL-B.crl")], + relative_name=None, + reasons=None, + crl_issuer=None + ) + ]), + critical=False + ) + + certificate = builder.sign(ci_key, hashes.SHA256(), self.backend) + + return certificate, private_key + + def generate_eum_cert(self, curve_type, key_hex): + """Generate EUM certificate signed by CI.""" + curve = self.get_curve(curve_type) + private_key = self.load_private_key_from_hex(key_hex, curve) + + ci_cert = self.ci_certs[curve_type] + ci_key = self.ci_keys[curve_type] + + subject = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, "ES"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "RSP Test EUM"), + x509.NameAttribute(NameOID.COMMON_NAME, "EUM Test"), + ]) + + builder = x509.CertificateBuilder() + builder = builder.subject_name(subject) + builder = builder.issuer_name(ci_cert.subject) + builder = builder.not_valid_before(datetime(2020, 4, 1, 9, 28, 37)) + builder = builder.not_valid_after(datetime(2054, 3, 24, 9, 28, 37)) + builder = builder.serial_number(0x12345678) + builder = builder.public_key(private_key.public_key()) + + builder = builder.add_extension( + x509.AuthorityKeyIdentifier.from_issuer_public_key(ci_key.public_key()), + critical=False + ) + + builder = builder.add_extension( + x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()), + critical=False + ) + + builder = builder.add_extension( + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ), + critical=True + ) + + builder = builder.add_extension( + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.23.146.1.2.1.2"), # EUM policy + policy_qualifiers=None + ) + ]), + critical=True + ) + + builder = builder.add_extension( + x509.SubjectAlternativeName([ + x509.RegisteredID(x509.ObjectIdentifier("2.999.5")) + ]), + critical=False + ) + + builder = builder.add_extension( + x509.BasicConstraints(ca=True, path_length=0), + critical=True + ) + + builder = builder.add_extension( + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier("http://ci.test.example.com/CRL-B.crl")], + relative_name=None, + reasons=None, + crl_issuer=None + ) + ]), + critical=False + ) + + # Name Constraints + constrained_name = x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "RSP Test EUM"), + x509.NameAttribute(NameOID.SERIAL_NUMBER, "89049032"), + ]) + + name_constraints = x509.NameConstraints( + permitted_subtrees=[ + x509.DirectoryName(constrained_name) + ], + excluded_subtrees=None + ) + + builder = builder.add_extension( + name_constraints, + critical=True + ) + + certificate = builder.sign(ci_key, hashes.SHA256(), self.backend) + + return certificate, private_key + + def generate_euicc_cert(self, curve_type, eum_cert, eum_key, key_hex): + """Generate eUICC certificate signed by EUM.""" + curve = self.get_curve(curve_type) + private_key = self.load_private_key_from_hex(key_hex, curve) + + subject = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, "ES"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "RSP Test EUM"), + x509.NameAttribute(NameOID.SERIAL_NUMBER, "89049032123451234512345678901235"), + x509.NameAttribute(NameOID.COMMON_NAME, "Test eUICC"), + ]) + + builder = x509.CertificateBuilder() + builder = builder.subject_name(subject) + builder = builder.issuer_name(eum_cert.subject) + builder = builder.not_valid_before(datetime(2020, 4, 1, 9, 48, 58)) + builder = builder.not_valid_after(datetime(7496, 1, 24, 9, 48, 58)) + builder = builder.serial_number(0x0200000000000001) + builder = builder.public_key(private_key.public_key()) + + builder = builder.add_extension( + x509.AuthorityKeyIdentifier.from_issuer_public_key(eum_key.public_key()), + critical=False + ) + + builder = builder.add_extension( + x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()), + critical=False + ) + + builder = builder.add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False + ), + critical=True + ) + + builder = builder.add_extension( + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.23.146.1.2.1.1"), # eUICC policy + policy_qualifiers=None + ) + ]), + critical=True + ) + + certificate = builder.sign(eum_key, hashes.SHA256(), self.backend) + + return certificate, private_key + + def save_cert_and_key(self, cert, key, cert_path_der, cert_path_pem, key_path_sk, key_path_pk): + """Save certificate and key in various formats.""" + # Create directories if needed + os.makedirs(os.path.dirname(cert_path_der), exist_ok=True) + + with open(cert_path_der, "wb") as f: + f.write(cert.public_bytes(serialization.Encoding.DER)) + + if cert_path_pem: + with open(cert_path_pem, "wb") as f: + f.write(cert.public_bytes(serialization.Encoding.PEM)) + + if key and key_path_sk: + with open(key_path_sk, "wb") as f: + f.write(key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption() + )) + + if key and key_path_pk: + with open(key_path_pk, "wb") as f: + f.write(key.public_key().public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo + )) + + +def main(): + gen = SimplifiedCertificateGenerator() + + output_dir = "smdpp-data/generated" + os.makedirs(output_dir, exist_ok=True) + + print("=== Generating CI Certificates ===") + + for curve_type in ["BRP", "NIST"]: + ci_cert, ci_key = gen.generate_ci_cert(curve_type) + suffix = "_ECDSA_BRP" if curve_type == "BRP" else "_ECDSA_NIST" + gen.save_cert_and_key( + ci_cert, ci_key, + f"{output_dir}/CertificateIssuer/CERT_CI{suffix}.der", + f"{output_dir}/CertificateIssuer/CERT_CI{suffix}.pem", + None, None + ) + print(f"Generated CI {curve_type} certificate") + + print("\n=== Generating DPauth Certificates ===") + + dpauth_configs = [ + ("BRP", "TEST SM-DP+", 256, "93:fb:33:d0:58:4f:34:9b:07:f8:b5:d2:af:93:d7:c3:e3:54:b3:49:a3:b9:13:50:2e:6a:bc:07:0e:4d:49:29", OID_DP_RID, "DPauth"), + ("NIST", "TEST SM-DP+", 256, "0a:7c:c1:c2:44:e6:0c:52:cd:5b:78:07:ab:8c:36:0c:26:52:46:01:50:7d:ca:bc:5d:d5:98:b5:a6:16:d5:d5", OID_DP_RID, "DPauth"), + ("BRP", "TEST SM-DP+2", 512, "0c:17:35:5c:01:1d:0f:e8:d7:da:dd:63:f1:97:85:cf:6c:51:cb:cd:46:6a:e8:8b:e8:f8:1b:c1:05:88:46:f6", OID_DP2_RID, "DP2auth"), + ("NIST", "TEST SM-DP+2", 512, "9c:32:a0:95:d4:88:42:d9:ff:a4:04:f7:12:51:2a:a2:c5:42:5a:1a:26:38:6a:b6:a1:45:d5:81:1e:03:91:41", OID_DP2_RID, "DP2auth"), + ] + + for curve_type, cn, serial, key_hex, rid_oid, name_prefix in dpauth_configs: + cert, key = gen.generate_dp_cert( + curve_type, cn, serial, key_hex, + OID_CERTIFICATE_POLICIES_AUTH, rid_oid, + datetime(2020, 4, 1, 8, 31, 30), + datetime(2030, 3, 30, 8, 31, 30) + ) + suffix = "_ECDSA_BRP" if curve_type == "BRP" else "_ECDSA_NIST" + gen.save_cert_and_key( + cert, key, + f"{output_dir}/DPauth/CERT_S_SM_{name_prefix}{suffix}.der", + None, + f"{output_dir}/DPauth/SK_S_SM_{name_prefix}{suffix}.pem", + f"{output_dir}/DPauth/PK_S_SM_{name_prefix}{suffix}.pem" + ) + print(f"Generated {name_prefix} {curve_type} certificate") + + print("\n=== Generating DPpb Certificates ===") + + dppb_configs = [ + ("BRP", "TEST SM-DP+", 257, "75:ff:32:2f:41:66:16:da:e1:a4:84:ef:71:d4:87:4f:b0:df:32:95:fd:35:c2:cb:a4:89:fb:b2:bb:9c:7b:f6", OID_DP_RID, "DPpb"), + ("NIST", "TEST SM-DP+", 257, "dc:d6:94:b7:78:95:7e:8e:9a:dd:bd:d9:44:33:e9:ef:8f:73:d1:1e:49:1c:48:d4:25:a3:8a:94:91:bd:3b:ed", OID_DP_RID, "DPpb"), + ("BRP", "TEST SM-DP+2", 513, "9c:ae:2e:1a:56:07:a9:d5:78:38:2e:ee:93:2e:25:1f:52:30:4f:86:ee:b1:f1:70:8c:db:d3:c0:7b:e2:cd:3d", OID_DP2_RID, "DP2pb"), + ("NIST", "TEST SM-DP+2", 513, "66:93:11:49:63:9d:ba:ac:1d:c3:d3:06:c5:8b:d2:df:d2:2f:73:bf:63:ac:86:31:98:32:90:b5:7f:90:93:45", OID_DP2_RID, "DP2pb"), + ] + + for curve_type, cn, serial, key_hex, rid_oid, name_prefix in dppb_configs: + cert, key = gen.generate_dp_cert( + curve_type, cn, serial, key_hex, + OID_CERTIFICATE_POLICIES_PB, rid_oid, + datetime(2020, 4, 1, 8, 34, 46), + datetime(2030, 3, 30, 8, 34, 46) + ) + suffix = "_ECDSA_BRP" if curve_type == "BRP" else "_ECDSA_NIST" + gen.save_cert_and_key( + cert, key, + f"{output_dir}/DPpb/CERT_S_SM_{name_prefix}{suffix}.der", + None, + f"{output_dir}/DPpb/SK_S_SM_{name_prefix}{suffix}.pem", + f"{output_dir}/DPpb/PK_S_SM_{name_prefix}{suffix}.pem" + ) + print(f"Generated {name_prefix} {curve_type} certificate") + + print("\n=== Generating DPtls Certificates ===") + + dptls_configs = [ + ("BRP", "testsmdpplus1.example.com", "testsmdpplus1.example.com", 9, "3f:67:15:28:02:b3:f4:c7:fa:e6:79:58:55:f6:82:54:1e:45:e3:5e:ff:f4:e8:a0:55:65:a0:f1:91:2a:78:2e", OID_DP_RID, "DP_TLS_BRP"), + ("NIST", "testsmdpplus1.example.com", "testsmdpplus1.example.com", 9, "a0:3e:7c:e4:55:04:74:be:a4:b7:a8:73:99:ce:5a:8c:9f:66:1b:68:0f:94:01:39:ff:f8:4e:9d:ec:6a:4d:8c", OID_DP_RID, "DP_TLS_NIST"), + ("NIST", "testsmdpplus2.example.com", "testsmdpplus2.example.com", 12, "4e:65:61:c6:40:88:f6:69:90:7a:db:e3:94:b1:1a:84:24:2e:03:3a:82:a8:84:02:31:63:6d:c9:1b:4e:e3:f5", OID_DP2_RID, "DP2_TLS"), + ("NIST", "testsmdpplus4.example.com", "testsmdpplus4.example.com", 14, "f2:65:9d:2f:52:8f:4b:11:37:40:d5:8a:0d:2a:f3:eb:2b:48:e1:22:c2:b6:0a:6a:f6:fc:96:ad:86:be:6f:a4", OID_DP4_RID, "DP4_TLS"), + ("NIST", "testsmdpplus8.example.com", "testsmdpplus8.example.com", 18, "ff:6e:4a:50:9b:ad:db:38:10:88:31:c2:3c:cc:2d:44:30:7a:f2:81:e9:25:96:7f:8c:df:1d:95:54:a0:28:8d", OID_DP8_RID, "DP8_TLS"), + ] + + for curve_type, cn, dns, serial, key_hex, rid_oid, name_prefix in dptls_configs: + cert, key = gen.generate_tls_cert( + curve_type, cn, dns, serial, key_hex, rid_oid, + datetime(2024, 7, 9, 15, 29, 36), + datetime(2025, 8, 11, 15, 29, 36) + ) + gen.save_cert_and_key( + cert, key, + f"{output_dir}/DPtls/CERT_S_SM_{name_prefix}.der", + None, + f"{output_dir}/DPtls/SK_S_SM_{name_prefix.replace('_BRP', '_BRP').replace('_NIST', '_NIST')}.pem", + f"{output_dir}/DPtls/PK_S_SM_{name_prefix.replace('_BRP', '_BRP').replace('_NIST', '_NIST')}.pem" + ) + print(f"Generated {name_prefix} certificate") + + print("\n=== Generating EUM Certificates ===") + + eum_configs = [ + ("BRP", "12:9b:0a:b1:3f:17:e1:4a:40:b6:fa:4e:d8:23:e0:cf:46:5b:7b:3d:73:24:05:e6:29:5d:3b:23:b0:45:c9:9a"), + ("NIST", "25:e6:75:77:28:e1:e9:51:13:51:9c:dc:34:55:5c:29:ba:ed:23:77:3a:c5:af:dd:dc:da:d9:84:89:8a:52:f0"), + ] + + eum_certs = {} + eum_keys = {} + + for curve_type, key_hex in eum_configs: + cert, key = gen.generate_eum_cert(curve_type, key_hex) + eum_certs[curve_type] = cert + eum_keys[curve_type] = key + suffix = "_ECDSA_BRP" if curve_type == "BRP" else "_ECDSA_NIST" + gen.save_cert_and_key( + cert, key, + f"{output_dir}/EUM/CERT_EUM{suffix}.der", + None, + f"{output_dir}/EUM/SK_EUM{suffix}.pem", + f"{output_dir}/EUM/PK_EUM{suffix}.pem" + ) + print(f"Generated EUM {curve_type} certificate") + + print("\n=== Generating eUICC Certificates ===") + + euicc_configs = [ + ("BRP", "8d:c3:47:a7:6d:b7:bd:d6:22:2d:d7:5e:a1:a1:68:8a:ca:81:1e:4c:bc:6a:7f:6a:ef:a4:b2:64:19:62:0b:90"), + ("NIST", "11:e1:54:67:dc:19:4f:33:71:83:e4:60:c9:f6:32:60:09:1e:12:e8:10:26:cd:65:61:e1:7c:6d:85:39:cc:9c"), + ] + + for curve_type, key_hex in euicc_configs: + cert, key = gen.generate_euicc_cert(curve_type, eum_certs[curve_type], eum_keys[curve_type], key_hex) + suffix = "_ECDSA_BRP" if curve_type == "BRP" else "_ECDSA_NIST" + gen.save_cert_and_key( + cert, key, + f"{output_dir}/eUICC/CERT_EUICC{suffix}.der", + None, + f"{output_dir}/eUICC/SK_EUICC{suffix}.pem", + f"{output_dir}/eUICC/PK_EUICC{suffix}.pem" + ) + print(f"Generated eUICC {curve_type} certificate") + + print("\n=== Certificate generation complete! ===") + print(f"All certificates saved to: {output_dir}/") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/osmo-smdpp.py b/osmo-smdpp.py index 9328b99..5baa154 100755 --- a/osmo-smdpp.py +++ b/osmo-smdpp.py @@ -17,6 +17,13 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see http://www.gnu.org/licenses/.
+from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature +from cryptography import x509 +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec, dh +from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, PrivateFormat, NoEncryption, ParameterFormat +from pathlib import Path import json import sys import argparse @@ -72,12 +79,6 @@ if status_code_data: js['header']['functionExecutionStatus']['statusCodeData'] = status_code_data
-from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature -from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, PrivateFormat, NoEncryption -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives import hashes -from cryptography.exceptions import InvalidSignature -from cryptography import x509
def ecdsa_tr03111_to_dss(sig: bytes) -> bytes: """convert an ECDSA signature from BSI TR-03111 format to DER: first get long integers; then encode those.""" @@ -134,13 +135,13 @@ return cert return None
- def __init__(self, server_hostname: str, ci_certs_path: str, use_brainpool: bool = False): + def __init__(self, server_hostname: str, ci_certs_path: str, common_cert_path: str, use_brainpool: bool = False): self.server_hostname = server_hostname self.upp_dir = os.path.realpath(os.path.join(DATA_DIR, 'upp')) self.ci_certs = self.load_certs_from_path(ci_certs_path) # load DPauth cert + key self.dp_auth = CertAndPrivkey(oid.id_rspRole_dp_auth_v2) - cert_dir = os.path.join(DATA_DIR, 'certs') + cert_dir = common_cert_path if use_brainpool: self.dp_auth.cert_from_der_file(os.path.join(cert_dir, 'DPauth', 'CERT_S_SM_DPauth_ECDSA_BRP.der')) self.dp_auth.privkey_from_pem_file(os.path.join(cert_dir, 'DPauth', 'SK_S_SM_DPauth_ECDSA_BRP.pem')) @@ -583,13 +584,43 @@ parser = argparse.ArgumentParser() parser.add_argument("-H", "--host", help="Host/IP to bind HTTP to", default="localhost") parser.add_argument("-p", "--port", help="TCP port to bind HTTP to", default=8000) - #parser.add_argument("-v", "--verbose", help="increase output verbosity", action='count', default=0) - + parser.add_argument("-c", "--certpath", help=f"cert subdir relative to {DATA_DIR}", default="certs") + parser.add_argument("-s", "--nossl", help="do NOT use ssl", action='store_true', default=False) args = parser.parse_args()
- hs = SmDppHttpServer(HOSTNAME, os.path.join(DATA_DIR, 'certs', 'CertificateIssuer'), use_brainpool=False) - #hs.app.run(endpoint_description="ssl:port=8000:dhParameters=dh_param_2048.pem") + common_cert_path = os.path.join(DATA_DIR, args.certpath) + hs = SmDppHttpServer(server_hostname=HOSTNAME, ci_certs_path=os.path.join(common_cert_path, 'CertificateIssuer'), common_cert_path=common_cert_path, use_brainpool=False) + if(args.nossl): hs.app.run(args.host, args.port) + else: + cert_derpath = Path(common_cert_path) / 'DPtls' / 'CERT_S_SM_DP_TLS_NIST.der' + cert_pempath = Path(common_cert_path) / 'DPtls' / 'CERT_S_SM_DP_TLS_NIST.pem' + cert_skpath = Path(common_cert_path) / 'DPtls' / 'SK_S_SM_DP_TLS_NIST.pem' + dhparam_path = Path(common_cert_path) / "dhparam2048.pem" + if not dhparam_path.exists(): + print("Generating dh params, this takes a few seconds..") + # Generate DH parameters with 2048-bit key size and generator 2 + parameters = dh.generate_parameters(generator=2, key_size=2048) + pem_data = parameters.parameter_bytes(encoding=Encoding.PEM,format=ParameterFormat.PKCS3) + with open(dhparam_path, 'wb') as file: + file.write(pem_data) + print("DH params created successfully") + + if not cert_pempath.exists(): + print("Translating tls server cert from DER to PEM..") + with open(cert_derpath, 'rb') as der_file: + der_cert_data = der_file.read() + + cert = x509.load_der_x509_certificate(der_cert_data) + pem_cert = cert.public_bytes(Encoding.PEM) #.decode('utf-8') + + with open(cert_pempath, 'wb') as pem_file: + pem_file.write(pem_cert) + + SERVER_STRING = f'ssl:{args.port}:privateKey={cert_skpath}:certKey={cert_pempath}:dhParameters={dhparam_path}' + print(SERVER_STRING) + + hs.app.run(host=HOSTNAME, port=args.port, endpoint_description=SERVER_STRING)
if __name__ == "__main__": main(sys.argv)