Add support for intermediate certificate chains in mTLS auth
When using the --use-intermediate flag, the script generates:
1. Root CA Certificate:
- Self-signed certificate (CA-cert.cer)
- Used as the root of trust
- Signs the intermediate certificates
2. Separate intermediate CAs for client and server:
- Client intermediate: client-intermediate-cert.cer
- Server intermediate: server-intermediate-cert.cer
- Each signed by the root CA
3. End-entity certificates with their respective intermediate chains:
- Client certificate chain: client-cert.pem (includes intermediate)
- Server certificate chain: server-cert.pem (includes intermediate)
Change-Id: Ifdfd1c044d1c8745638e44debfc90cad3b5aedb7
Signed-off-by: Ben Peled <bpeled@nvidia.com>
diff --git a/scripts/generate_auth_certificates.py b/scripts/generate_auth_certificates.py
index e20103b..1cba464 100755
--- a/scripts/generate_auth_certificates.py
+++ b/scripts/generate_auth_certificates.py
@@ -448,8 +448,8 @@
client_cert,
username,
url,
- ca_key,
- ca_cert,
+ server_intermediate_key,
+ server_intermediate_cert,
):
service_root = redfish_session.get("/redfish/v1/")
service_root.raise_for_status()
@@ -468,13 +468,27 @@
)
serverCert = generateServerCert(
url,
- ca_key,
- ca_cert,
+ server_intermediate_key,
+ server_intermediate_cert,
csr,
)
server_cert_dump = serverCert.public_bytes(
encoding=serialization.Encoding.PEM
)
+
+ # If using intermediate certificate, save server cert without intermediate for debugging
+ if (
+ isinstance(server_intermediate_cert, x509.Certificate)
+ and server_intermediate_cert.subject != server_intermediate_cert.issuer
+ ):
+ with open(os.path.join(certs_dir, "server-cert-only.pem"), "wb") as f:
+ f.write(server_cert_dump)
+ print("Server cert (without intermediate) saved for debugging.")
+ intermediate_cert_dump = server_intermediate_cert.public_bytes(
+ encoding=serialization.Encoding.PEM
+ )
+ server_cert_dump = server_cert_dump + intermediate_cert_dump
+
with open(os.path.join(certs_dir, "server-cert.pem"), "wb") as f:
f.write(server_cert_dump)
print("Server cert generated.")
@@ -493,7 +507,61 @@
response.raise_for_status()
-def generate_and_load_certs(url, username, password):
+def generateIntermediateCA(ca_key, ca_cert, name_prefix):
+ private_key = ec.generate_private_key(ec.SECP256R1())
+ public_key = private_key.public_key()
+ builder = x509.CertificateBuilder()
+
+ name = x509.Name(
+ [
+ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
+ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
+ x509.NameAttribute(NameOID.LOCALITY_NAME, "Santa Clara"),
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "OpenBMC"),
+ x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "bmcweb"),
+ x509.NameAttribute(NameOID.COMMON_NAME, "Test Intermediate"),
+ ]
+ )
+ builder = builder.subject_name(name)
+ builder = builder.issuer_name(ca_cert.subject)
+
+ builder = builder.not_valid_before(
+ datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
+ )
+ builder = builder.not_valid_after(
+ datetime.datetime(2070, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
+ )
+
+ builder = builder.serial_number(x509.random_serial_number())
+ builder = builder.public_key(public_key)
+
+ basic_constraints = x509.BasicConstraints(ca=True, path_length=0)
+ builder = builder.add_extension(basic_constraints, critical=True)
+
+ usage = x509.KeyUsage(
+ content_commitment=False,
+ crl_sign=True,
+ data_encipherment=False,
+ decipher_only=False,
+ digital_signature=False,
+ encipher_only=False,
+ key_agreement=False,
+ key_cert_sign=True,
+ key_encipherment=False,
+ )
+ builder = builder.add_extension(usage, critical=True)
+
+ auth_key = x509.AuthorityKeyIdentifier.from_issuer_public_key(public_key)
+ builder = builder.add_extension(auth_key, critical=False)
+
+ intermediate_cert = builder.sign(
+ private_key=ca_key, algorithm=hashes.SHA256()
+ )
+
+ return private_key, intermediate_cert
+
+
+def generate_and_load_certs(url, username, password, use_intermediate=False):
certs_dir = os.path.expanduser("~/certs")
print(f"Writing certs to {certs_dir}")
try:
@@ -532,8 +600,29 @@
ca_key_dump = ca_key_file.read()
ca_key = load_pem_private_key(ca_key_dump, None)
+ # Generate intermediate certificates if requested
+ if use_intermediate:
+ # Generate client intermediate
+ client_intermediate_key, client_intermediate_cert = (
+ generateIntermediateCA(ca_key, ca_cert, "client")
+ )
+ # ... save client intermediate certs ...
+
+ # Generate server intermediate
+ server_intermediate_key, server_intermediate_cert = (
+ generateIntermediateCA(ca_key, ca_cert, "server")
+ )
+ else:
+ client_intermediate_key = ca_key
+ client_intermediate_cert = ca_cert
+ server_intermediate_key = ca_key
+ server_intermediate_cert = ca_cert
+
+ # Update client cert generation to use intermediate
client_key, client_cert = generate_client_key_and_cert(
- ca_cert, ca_key, common_name=username
+ ca_cert=client_intermediate_cert,
+ ca_key=client_intermediate_key,
+ common_name=username,
)
client_key_dump = client_key.private_bytes(
encoding=serialization.Encoding.PEM,
@@ -548,6 +637,18 @@
encoding=serialization.Encoding.PEM
)
+ # Save client certificate without intermediate for debugging
+ if use_intermediate:
+ with open(os.path.join(certs_dir, "client-cert-only.pem"), "wb") as f:
+ f.write(client_cert_dump)
+ print("Client cert (without intermediate) saved for debugging.")
+ client_cert_dump = (
+ client_cert_dump
+ + client_intermediate_cert.public_bytes(
+ encoding=serialization.Encoding.PEM
+ )
+ ) # Append intermediate to create chain
+
with open(os.path.join(certs_dir, "client-cert.pem"), "wb") as f:
f.write(client_cert_dump)
print("Client cert generated.")
@@ -597,8 +698,8 @@
client_cert,
username,
url,
- ca_key,
- ca_cert,
+ server_intermediate_key,
+ server_intermediate_cert,
)
print("Testing redfish TLS authentication with generated certs.")
@@ -619,10 +720,18 @@
help="Password for user in order to install certs over Redfish.",
default="0penBmc",
)
+ parser.add_argument(
+ "--use-intermediate",
+ action="store_true",
+ default=False,
+ help="Generate and use an intermediate certificate",
+ )
parser.add_argument("host", help="Host to connect to")
args = parser.parse_args()
- generate_and_load_certs(args.host, args.username, args.password)
+ generate_and_load_certs(
+ args.host, args.username, args.password, args.use_intermediate
+ )
if __name__ == "__main__":