xref: /openbmc/bmcweb/scripts/generate_auth_certificates.py (revision ac2ff47405b7ae136f10a6407c3c63b666a384dc)
1#!/usr/bin/env python3
2"""
3Script to generate certificates for a CA, server, and client allowing for
4client authentication using mTLS certificates. This can then be used to test
5mTLS client authentication for Redfish and WebUI.
6"""
7
8import argparse
9import datetime
10import errno
11import ipaddress
12import os
13import socket
14import time
15from typing import Optional
16
17import asn1
18import httpx
19from cryptography import x509
20from cryptography.hazmat.primitives import hashes, serialization
21from cryptography.hazmat.primitives.asymmetric import ec
22from cryptography.hazmat.primitives.serialization import (
23    load_pem_private_key,
24    pkcs12,
25)
26from cryptography.x509.oid import NameOID
27
28replaceCertPath = "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate"
29# https://oidref.com/1.3.6.1.4.1.311.20.2.3
30upnObjectIdentifier = "1.3.6.1.4.1.311.20.2.3"
31
32
33class RedfishSessionContext:
34    def __init__(self, client, username="root", password="0penBmc"):
35        self.client = client
36        self.session_uri = None
37        self.x_auth_token = None
38        self.username = username
39        self.password = password
40
41    def __enter__(self):
42        r = self.client.post(
43            "/redfish/v1/SessionService/Sessions",
44            json={
45                "UserName": self.username,
46                "Password": self.password,
47                "Context": f"pythonscript::{os.path.basename(__file__)}",
48            },
49            headers={"content-type": "application/json"},
50        )
51        r.raise_for_status()
52        self.x_auth_token = r.headers["x-auth-token"]
53        self.session_uri = r.headers["location"]
54        return self
55
56    def __exit__(self, type, value, traceback):
57        if not self.session_uri:
58            return
59        r = self.client.delete(self.session_uri)
60        r.raise_for_status()
61
62
63def get_hostname(redfish_session, username, password, url):
64    service_root = redfish_session.get("/redfish/v1/")
65    service_root.raise_for_status()
66
67    manager_uri = service_root.json()["Links"]["ManagerProvidingService"][
68        "@odata.id"
69    ]
70
71    manager_response = redfish_session.get(manager_uri)
72    manager_response.raise_for_status()
73
74    network_protocol_uri = manager_response.json()["NetworkProtocol"][
75        "@odata.id"
76    ]
77
78    network_protocol_response = redfish_session.get(network_protocol_uri)
79    network_protocol_response.raise_for_status()
80
81    hostname = network_protocol_response.json()["HostName"]
82
83    return hostname
84
85
86def generateCA():
87    private_key = ec.generate_private_key(ec.SECP256R1())
88    public_key = private_key.public_key()
89    builder = x509.CertificateBuilder()
90
91    name = x509.Name(
92        [
93            x509.NameAttribute(NameOID.ORGANIZATION_NAME, "OpenBMC"),
94            x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "bmcweb"),
95            x509.NameAttribute(NameOID.COMMON_NAME, "Test CA"),
96        ]
97    )
98    builder = builder.subject_name(name)
99    builder = builder.issuer_name(name)
100
101    builder = builder.not_valid_before(
102        datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
103    )
104    builder = builder.not_valid_after(
105        datetime.datetime(2070, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
106    )
107    builder = builder.serial_number(x509.random_serial_number())
108    builder = builder.public_key(public_key)
109
110    basic_constraints = x509.BasicConstraints(ca=True, path_length=None)
111    builder = builder.add_extension(basic_constraints, critical=True)
112
113    usage = x509.KeyUsage(
114        content_commitment=False,
115        crl_sign=True,
116        data_encipherment=False,
117        decipher_only=False,
118        digital_signature=False,
119        encipher_only=False,
120        key_agreement=False,
121        key_cert_sign=True,
122        key_encipherment=False,
123    )
124    builder = builder.add_extension(usage, critical=False)
125
126    auth_key = x509.AuthorityKeyIdentifier.from_issuer_public_key(public_key)
127
128    builder = builder.add_extension(auth_key, critical=False)
129
130    root_cert = builder.sign(
131        private_key=private_key, algorithm=hashes.SHA256()
132    )
133
134    return private_key, root_cert
135
136
137def signCsr(csr, ca_key):
138    csr.sign(ca_key, algorithm=hashes.SHA256())
139    return
140
141
142def generate_client_key_and_cert(
143    ca_cert,
144    ca_key,
145    common_name: Optional[str] = None,
146    upn: Optional[str] = None,
147):
148    private_key = ec.generate_private_key(ec.SECP256R1())
149    public_key = private_key.public_key()
150    builder = x509.CertificateBuilder()
151
152    cert_names = [
153        x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
154        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
155        x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
156        x509.NameAttribute(NameOID.ORGANIZATION_NAME, "OpenBMC"),
157        x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "bmcweb"),
158    ]
159    if common_name is not None:
160        cert_names.append(x509.NameAttribute(NameOID.COMMON_NAME, common_name))
161
162    builder = builder.subject_name(x509.Name(cert_names))
163
164    builder = builder.issuer_name(ca_cert.subject)
165    builder = builder.public_key(public_key)
166    builder = builder.serial_number(x509.random_serial_number())
167    builder = builder.not_valid_before(
168        datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
169    )
170    builder = builder.not_valid_after(
171        datetime.datetime(2070, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
172    )
173
174    usage = x509.KeyUsage(
175        content_commitment=False,
176        crl_sign=False,
177        data_encipherment=False,
178        decipher_only=False,
179        digital_signature=True,
180        encipher_only=False,
181        key_agreement=True,
182        key_cert_sign=False,
183        key_encipherment=False,
184    )
185    builder = builder.add_extension(usage, critical=False)
186
187    exusage = x509.ExtendedKeyUsage([x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH])
188    builder = builder.add_extension(exusage, critical=True)
189
190    auth_key = x509.AuthorityKeyIdentifier.from_issuer_public_key(public_key)
191    builder = builder.add_extension(auth_key, critical=False)
192
193    if upn is not None:
194        encoder = asn1.Encoder()
195        encoder.start()
196        encoder.write(upn, asn1.Numbers.UTF8String)
197
198        builder = builder.add_extension(
199            x509.SubjectAlternativeName(
200                [
201                    x509.OtherName(
202                        x509.ObjectIdentifier(upnObjectIdentifier),
203                        encoder.output(),
204                    )
205                ]
206            ),
207            critical=False,
208        )
209
210    signed = builder.sign(private_key=ca_key, algorithm=hashes.SHA256())
211
212    return private_key, signed
213
214
215def generateServerCert(url, ca_key, ca_cert, csr):
216    builder = x509.CertificateBuilder()
217
218    builder = builder.subject_name(csr.subject)
219    builder = builder.issuer_name(ca_cert.subject)
220    builder = builder.public_key(csr.public_key())
221    builder = builder.serial_number(x509.random_serial_number())
222    builder = builder.not_valid_before(
223        datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
224    )
225    builder = builder.not_valid_after(
226        datetime.datetime(2070, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
227    )
228
229    usage = x509.KeyUsage(
230        content_commitment=False,
231        crl_sign=False,
232        data_encipherment=False,
233        decipher_only=False,
234        digital_signature=True,
235        encipher_only=False,
236        key_agreement=False,
237        key_cert_sign=True,
238        key_encipherment=True,
239    )
240    builder = builder.add_extension(usage, critical=True)
241
242    exusage = x509.ExtendedKeyUsage([x509.oid.ExtendedKeyUsageOID.SERVER_AUTH])
243    builder = builder.add_extension(exusage, critical=True)
244
245    san_list = [x509.DNSName("localhost")]
246    try:
247        value = ipaddress.ip_address(url)
248        san_list.append(x509.IPAddress(value))
249    except ValueError:
250        san_list.append(x509.DNSName(url))
251
252    altname = x509.SubjectAlternativeName(san_list)
253    builder = builder.add_extension(altname, critical=True)
254    basic_constraints = x509.BasicConstraints(ca=False, path_length=None)
255    builder = builder.add_extension(basic_constraints, critical=True)
256
257    builder = builder.add_extension(
258        x509.SubjectKeyIdentifier.from_public_key(ca_key.public_key()),
259        critical=False,
260    )
261    authkeyident = x509.AuthorityKeyIdentifier.from_issuer_public_key(
262        ca_key.public_key()
263    )
264    builder = builder.add_extension(authkeyident, critical=False)
265
266    signed = builder.sign(private_key=ca_key, algorithm=hashes.SHA256())
267
268    return signed
269
270
271def generateCsr(
272    redfish_session,
273    commonName,
274    manager_uri,
275):
276    try:
277        socket.inet_aton(commonName)
278        commonName = "IP: " + commonName
279    except socket.error:
280        commonName = "DNS: " + commonName
281
282    CSRRequest = {
283        "CommonName": commonName,
284        "City": "San Fransisco",
285        "Country": "US",
286        "Organization": "",
287        "OrganizationalUnit": "",
288        "State": "CA",
289        "CertificateCollection": {
290            "@odata.id": f"{manager_uri}/NetworkProtocol/HTTPS/Certificates",
291        },
292        "AlternativeNames": [
293            commonName,
294            "DNS: localhost",
295            "IP: 127.0.0.1",
296        ],
297    }
298
299    response = redfish_session.post(
300        "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR",
301        json=CSRRequest,
302    )
303    response.raise_for_status()
304
305    csrString = response.json()["CSRString"]
306    csr = x509.load_pem_x509_csr(csrString.encode())
307    if not csr.is_signature_valid:
308        raise Exception("CSR was not valid")
309    return csr
310
311
312def install_ca_cert(redfish_session, ca_cert_dump, manager_uri):
313    ca_certJSON = {
314        "CertificateString": ca_cert_dump.decode(),
315        "CertificateType": "PEM",
316    }
317    ca_certPath = f"{manager_uri}/Truststore/Certificates"
318    print("Attempting to install CA certificate to BMC.")
319
320    response = redfish_session.post(ca_certPath, json=ca_certJSON)
321    if response.status_code == 500:
322        print(
323            "An existing CA certificate is likely already installed."
324            " Replacing..."
325        )
326        ca_certJSON["CertificateUri"] = {
327            "@odata.id": ca_certPath + "/1",
328        }
329
330        response = redfish_session.post(replaceCertPath, json=ca_certJSON)
331        if response.status_code == 200:
332            print("Successfully replaced existing CA certificate.")
333        else:
334            raise Exception(
335                "Could not install or replace CA certificate."
336                "Please check if a certificate is already installed. If a"
337                "certificate is already installed, try performing a factory"
338                "restore to clear such settings."
339            )
340    response.raise_for_status()
341    print("Successfully installed CA certificate.")
342
343
344def install_server_cert(redfish_session, manager_uri, server_cert_dump):
345
346    server_cert_json = {
347        "CertificateString": server_cert_dump.decode(),
348        "CertificateUri": {
349            "@odata.id": f"{manager_uri}/NetworkProtocol/HTTPS/Certificates/1",
350        },
351        "CertificateType": "PEM",
352    }
353
354    print("Replacing server certificate...")
355    response = redfish_session.post(replaceCertPath, json=server_cert_json)
356    if response.status_code == 200:
357        print("Successfully replaced server certificate.")
358    else:
359        raise Exception(f"Could not replace certificate: {response.json()}")
360
361    tls_patch_json = {"Oem": {"OpenBMC": {"AuthMethods": {"TLS": True}}}}
362    print("Ensuring TLS authentication is enabled.")
363    response = redfish_session.patch(
364        "/redfish/v1/AccountService", json=tls_patch_json
365    )
366    if response.status_code == 200:
367        print("Successfully enabled TLS authentication.")
368    else:
369        raise Exception("Could not enable TLS auth: " + response.read)
370
371
372def generate_pk12(certs_dir, key, client_cert, username):
373    print("Generating p12 cert file for browser authentication.")
374    p12 = pkcs12.serialize_key_and_certificates(
375        username.encode(),
376        key,
377        client_cert,
378        None,
379        serialization.NoEncryption(),
380    )
381    with open(os.path.join(certs_dir, "client.p12"), "wb") as f:
382        f.write(p12)
383
384
385def test_mtls_auth(url, certs_dir, use_http2):
386    with httpx.Client(
387        base_url=f"https://{url}",
388        verify=os.path.join(certs_dir, "CA-cert.cer"),
389        cert=(
390            os.path.join(certs_dir, "client-cert.pem"),
391            os.path.join(certs_dir, "client-key.pem"),
392        ),
393        http2=use_http2,
394    ) as client:
395        print("Testing mTLS auth with CommonName")
396        response = client.get(
397            "/redfish/v1/SessionService/Sessions",
398        )
399        response.raise_for_status()
400
401        print("Changing CertificateMappingAttribute to use UPN")
402        patch_json = {
403            "MultiFactorAuth": {
404                "ClientCertificate": {
405                    "CertificateMappingAttribute": "UserPrincipalName"
406                }
407            }
408        }
409        response = client.patch(
410            "/redfish/v1/AccountService",
411            json=patch_json,
412        )
413        response.raise_for_status()
414
415    with httpx.Client(
416        base_url=f"https://{url}",
417        verify=os.path.join(certs_dir, "CA-cert.cer"),
418        cert=(
419            os.path.join(certs_dir, "upn-client-cert.pem"),
420            os.path.join(certs_dir, "upn-client-key.pem"),
421        ),
422        http2=use_http2,
423    ) as client:
424        print("Retesting mTLS auth with UPN")
425        response = client.get(
426            "/redfish/v1/SessionService/Sessions",
427        )
428        response.raise_for_status()
429
430        print("Changing CertificateMappingAttribute to use CommonName")
431        patch_json = {
432            "MultiFactorAuth": {
433                "ClientCertificate": {
434                    "CertificateMappingAttribute": "CommonName"
435                }
436            }
437        }
438        response = client.patch(
439            "/redfish/v1/AccountService",
440            json=patch_json,
441        )
442        response.raise_for_status()
443
444
445def setup_server_cert(
446    redfish_session,
447    ca_cert_dump,
448    certs_dir,
449    client_key,
450    client_cert,
451    username,
452    url,
453    server_intermediate_key,
454    server_intermediate_cert,
455):
456    service_root = redfish_session.get("/redfish/v1/")
457    service_root.raise_for_status()
458
459    manager_uri = service_root.json()["Links"]["ManagerProvidingService"][
460        "@odata.id"
461    ]
462
463    install_ca_cert(redfish_session, ca_cert_dump, manager_uri)
464    generate_pk12(certs_dir, client_key, client_cert, username)
465
466    csr = generateCsr(
467        redfish_session,
468        url,
469        manager_uri,
470    )
471    serverCert = generateServerCert(
472        url,
473        server_intermediate_key,
474        server_intermediate_cert,
475        csr,
476    )
477    server_cert_dump = serverCert.public_bytes(
478        encoding=serialization.Encoding.PEM
479    )
480
481    # If using intermediate certificate, save server cert without intermediate for debugging
482    if (
483        isinstance(server_intermediate_cert, x509.Certificate)
484        and server_intermediate_cert.subject != server_intermediate_cert.issuer
485    ):
486        with open(os.path.join(certs_dir, "server-cert-only.pem"), "wb") as f:
487            f.write(server_cert_dump)
488            print("Server cert (without intermediate) saved for debugging.")
489        intermediate_cert_dump = server_intermediate_cert.public_bytes(
490            encoding=serialization.Encoding.PEM
491        )
492        server_cert_dump = server_cert_dump + intermediate_cert_dump
493
494    with open(os.path.join(certs_dir, "server-cert.pem"), "wb") as f:
495        f.write(server_cert_dump)
496        print("Server cert generated.")
497
498    install_server_cert(redfish_session, manager_uri, server_cert_dump)
499
500    print("Make sure setting CertificateMappingAttribute to CommonName")
501    patch_json = {
502        "MultiFactorAuth": {
503            "ClientCertificate": {"CertificateMappingAttribute": "CommonName"}
504        }
505    }
506    response = redfish_session.patch(
507        "/redfish/v1/AccountService", json=patch_json
508    )
509    response.raise_for_status()
510
511
512def generateIntermediateCA(ca_key, ca_cert, name_prefix):
513    private_key = ec.generate_private_key(ec.SECP256R1())
514    public_key = private_key.public_key()
515    builder = x509.CertificateBuilder()
516
517    name = x509.Name(
518        [
519            x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
520            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
521            x509.NameAttribute(NameOID.LOCALITY_NAME, "Santa Clara"),
522            x509.NameAttribute(NameOID.ORGANIZATION_NAME, "OpenBMC"),
523            x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "bmcweb"),
524            x509.NameAttribute(NameOID.COMMON_NAME, "Test Intermediate"),
525        ]
526    )
527    builder = builder.subject_name(name)
528    builder = builder.issuer_name(ca_cert.subject)
529
530    builder = builder.not_valid_before(
531        datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
532    )
533    builder = builder.not_valid_after(
534        datetime.datetime(2070, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
535    )
536
537    builder = builder.serial_number(x509.random_serial_number())
538    builder = builder.public_key(public_key)
539
540    basic_constraints = x509.BasicConstraints(ca=True, path_length=0)
541    builder = builder.add_extension(basic_constraints, critical=True)
542
543    usage = x509.KeyUsage(
544        content_commitment=False,
545        crl_sign=True,
546        data_encipherment=False,
547        decipher_only=False,
548        digital_signature=False,
549        encipher_only=False,
550        key_agreement=False,
551        key_cert_sign=True,
552        key_encipherment=False,
553    )
554    builder = builder.add_extension(usage, critical=True)
555
556    auth_key = x509.AuthorityKeyIdentifier.from_issuer_public_key(public_key)
557    builder = builder.add_extension(auth_key, critical=False)
558
559    intermediate_cert = builder.sign(
560        private_key=ca_key, algorithm=hashes.SHA256()
561    )
562
563    return private_key, intermediate_cert
564
565
566def generate_and_load_certs(
567    url, username, password, use_http2, use_intermediate=False
568):
569    certs_dir = os.path.expanduser("~/certs")
570    print(f"Writing certs to {certs_dir}")
571    try:
572        print("Making certs directory.")
573        os.mkdir(certs_dir)
574    except OSError as error:
575        if error.errno != errno.EEXIST:
576            raise
577
578    ca_cert_filename = os.path.join(certs_dir, "CA-cert.cer")
579    ca_key_filename = os.path.join(certs_dir, "CA-key.pem")
580    if not os.path.exists(ca_cert_filename):
581        ca_key, ca_cert = generateCA()
582
583        ca_key_dump = ca_key.private_bytes(
584            encoding=serialization.Encoding.PEM,
585            format=serialization.PrivateFormat.TraditionalOpenSSL,
586            encryption_algorithm=serialization.NoEncryption(),
587        )
588        ca_cert_dump = ca_cert.public_bytes(
589            encoding=serialization.Encoding.PEM
590        )
591
592        with open(ca_cert_filename, "wb") as f:
593            f.write(ca_cert_dump)
594            print("CA cert generated.")
595        with open(ca_key_filename, "wb") as f:
596            f.write(ca_key_dump)
597            print("CA key generated.")
598
599    with open(ca_cert_filename, "rb") as ca_cert_file:
600        ca_cert_dump = ca_cert_file.read()
601    ca_cert = x509.load_pem_x509_certificate(ca_cert_dump)
602
603    with open(ca_key_filename, "rb") as ca_key_file:
604        ca_key_dump = ca_key_file.read()
605    ca_key = load_pem_private_key(ca_key_dump, None)
606
607    # Generate intermediate certificates if requested
608    if use_intermediate:
609        # Generate client intermediate
610        client_intermediate_key, client_intermediate_cert = (
611            generateIntermediateCA(ca_key, ca_cert, "client")
612        )
613        # ... save client intermediate certs ...
614
615        # Generate server intermediate
616        server_intermediate_key, server_intermediate_cert = (
617            generateIntermediateCA(ca_key, ca_cert, "server")
618        )
619    else:
620        client_intermediate_key = ca_key
621        client_intermediate_cert = ca_cert
622        server_intermediate_key = ca_key
623        server_intermediate_cert = ca_cert
624
625    # Update client cert generation to use intermediate
626    client_key, client_cert = generate_client_key_and_cert(
627        ca_cert=client_intermediate_cert,
628        ca_key=client_intermediate_key,
629        common_name=username,
630    )
631    client_key_dump = client_key.private_bytes(
632        encoding=serialization.Encoding.PEM,
633        format=serialization.PrivateFormat.TraditionalOpenSSL,
634        encryption_algorithm=serialization.NoEncryption(),
635    )
636
637    with open(os.path.join(certs_dir, "client-key.pem"), "wb") as f:
638        f.write(client_key_dump)
639        print("Client key generated.")
640    client_cert_dump = client_cert.public_bytes(
641        encoding=serialization.Encoding.PEM
642    )
643
644    # Save client certificate without intermediate for debugging
645    if use_intermediate:
646        with open(os.path.join(certs_dir, "client-cert-only.pem"), "wb") as f:
647            f.write(client_cert_dump)
648            print("Client cert (without intermediate) saved for debugging.")
649        client_cert_dump = (
650            client_cert_dump
651            + client_intermediate_cert.public_bytes(
652                encoding=serialization.Encoding.PEM
653            )
654        )  # Append intermediate to create chain
655
656    with open(os.path.join(certs_dir, "client-cert.pem"), "wb") as f:
657        f.write(client_cert_dump)
658        print("Client cert generated.")
659
660    print(f"Connecting to {url}")
661    with httpx.Client(
662        base_url=f"https://{url}",
663        verify=False,
664        follow_redirects=False,
665        http2=use_http2,
666    ) as redfish_session:
667        with RedfishSessionContext(
668            redfish_session, username, password
669        ) as rf_session:
670            redfish_session.headers["X-Auth-Token"] = rf_session.x_auth_token
671
672            hostname = get_hostname(redfish_session, username, password, url)
673            print(f"Hostname: {hostname}")
674
675            upn_client_key, upn_client_cert = generate_client_key_and_cert(
676                ca_cert,
677                ca_key,
678                upn=f"{username}@{hostname}",
679            )
680            upn_client_key_dump = upn_client_key.private_bytes(
681                encoding=serialization.Encoding.PEM,
682                format=serialization.PrivateFormat.TraditionalOpenSSL,
683                encryption_algorithm=serialization.NoEncryption(),
684            )
685            with open(
686                os.path.join(certs_dir, "upn-client-key.pem"), "wb"
687            ) as f:
688                f.write(upn_client_key_dump)
689                print("UPN client key generated.")
690
691            upn_client_cert_dump = upn_client_cert.public_bytes(
692                encoding=serialization.Encoding.PEM
693            )
694            with open(
695                os.path.join(certs_dir, "upn-client-cert.pem"), "wb"
696            ) as f:
697                f.write(upn_client_cert_dump)
698                print("UPN client cert generated.")
699
700            setup_server_cert(
701                redfish_session,
702                ca_cert_dump,
703                certs_dir,
704                client_key,
705                client_cert,
706                username,
707                url,
708                server_intermediate_key,
709                server_intermediate_cert,
710            )
711
712    print("Testing redfish TLS authentication with generated certs.")
713    time.sleep(2)
714    test_mtls_auth(url, certs_dir, use_http2)
715    print("Redfish TLS authentication success!")
716
717
718def main():
719    parser = argparse.ArgumentParser()
720    parser.add_argument(
721        "--username",
722        help="Username to connect with",
723        default="root",
724    )
725    parser.add_argument(
726        "--password",
727        help="Password for user in order to install certs over Redfish.",
728        default="0penBmc",
729    )
730    parser.add_argument(
731        "--use-intermediate",
732        action="store_true",
733        default=False,
734        help="Generate and use an intermediate certificate",
735    )
736    parser.add_argument(
737        "--http2",
738        action=argparse.BooleanOptionalAction,
739        default=False,
740        help="Use HTTP2 for testing",
741    )
742    parser.add_argument("host", help="Host to connect to")
743
744    args = parser.parse_args()
745    generate_and_load_certs(
746        args.host,
747        args.username,
748        args.password,
749        args.http2,
750        args.use_intermediate,
751    )
752
753
754if __name__ == "__main__":
755    main()
756