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): 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 ) as client: 394 print("Testing mTLS auth with CommonName") 395 response = client.get( 396 "/redfish/v1/SessionService/Sessions", 397 ) 398 response.raise_for_status() 399 400 print("Changing CertificateMappingAttribute to use UPN") 401 patch_json = { 402 "MultiFactorAuth": { 403 "ClientCertificate": { 404 "CertificateMappingAttribute": "UserPrincipalName" 405 } 406 } 407 } 408 response = client.patch( 409 "/redfish/v1/AccountService", 410 json=patch_json, 411 ) 412 response.raise_for_status() 413 414 with httpx.Client( 415 base_url=f"https://{url}", 416 verify=os.path.join(certs_dir, "CA-cert.cer"), 417 cert=( 418 os.path.join(certs_dir, "upn-client-cert.pem"), 419 os.path.join(certs_dir, "upn-client-key.pem"), 420 ), 421 ) as client: 422 print("Retesting mTLS auth with UPN") 423 response = client.get( 424 "/redfish/v1/SessionService/Sessions", 425 ) 426 response.raise_for_status() 427 428 print("Changing CertificateMappingAttribute to use CommonName") 429 patch_json = { 430 "MultiFactorAuth": { 431 "ClientCertificate": { 432 "CertificateMappingAttribute": "CommonName" 433 } 434 } 435 } 436 response = client.patch( 437 "/redfish/v1/AccountService", 438 json=patch_json, 439 ) 440 response.raise_for_status() 441 442 443def setup_server_cert( 444 redfish_session, 445 ca_cert_dump, 446 certs_dir, 447 client_key, 448 client_cert, 449 username, 450 url, 451 server_intermediate_key, 452 server_intermediate_cert, 453): 454 service_root = redfish_session.get("/redfish/v1/") 455 service_root.raise_for_status() 456 457 manager_uri = service_root.json()["Links"]["ManagerProvidingService"][ 458 "@odata.id" 459 ] 460 461 install_ca_cert(redfish_session, ca_cert_dump, manager_uri) 462 generate_pk12(certs_dir, client_key, client_cert, username) 463 464 csr = generateCsr( 465 redfish_session, 466 url, 467 manager_uri, 468 ) 469 serverCert = generateServerCert( 470 url, 471 server_intermediate_key, 472 server_intermediate_cert, 473 csr, 474 ) 475 server_cert_dump = serverCert.public_bytes( 476 encoding=serialization.Encoding.PEM 477 ) 478 479 # If using intermediate certificate, save server cert without intermediate for debugging 480 if ( 481 isinstance(server_intermediate_cert, x509.Certificate) 482 and server_intermediate_cert.subject != server_intermediate_cert.issuer 483 ): 484 with open(os.path.join(certs_dir, "server-cert-only.pem"), "wb") as f: 485 f.write(server_cert_dump) 486 print("Server cert (without intermediate) saved for debugging.") 487 intermediate_cert_dump = server_intermediate_cert.public_bytes( 488 encoding=serialization.Encoding.PEM 489 ) 490 server_cert_dump = server_cert_dump + intermediate_cert_dump 491 492 with open(os.path.join(certs_dir, "server-cert.pem"), "wb") as f: 493 f.write(server_cert_dump) 494 print("Server cert generated.") 495 496 install_server_cert(redfish_session, manager_uri, server_cert_dump) 497 498 print("Make sure setting CertificateMappingAttribute to CommonName") 499 patch_json = { 500 "MultiFactorAuth": { 501 "ClientCertificate": {"CertificateMappingAttribute": "CommonName"} 502 } 503 } 504 response = redfish_session.patch( 505 "/redfish/v1/AccountService", json=patch_json 506 ) 507 response.raise_for_status() 508 509 510def generateIntermediateCA(ca_key, ca_cert, name_prefix): 511 private_key = ec.generate_private_key(ec.SECP256R1()) 512 public_key = private_key.public_key() 513 builder = x509.CertificateBuilder() 514 515 name = x509.Name( 516 [ 517 x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), 518 x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), 519 x509.NameAttribute(NameOID.LOCALITY_NAME, "Santa Clara"), 520 x509.NameAttribute(NameOID.ORGANIZATION_NAME, "OpenBMC"), 521 x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "bmcweb"), 522 x509.NameAttribute(NameOID.COMMON_NAME, "Test Intermediate"), 523 ] 524 ) 525 builder = builder.subject_name(name) 526 builder = builder.issuer_name(ca_cert.subject) 527 528 builder = builder.not_valid_before( 529 datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) 530 ) 531 builder = builder.not_valid_after( 532 datetime.datetime(2070, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) 533 ) 534 535 builder = builder.serial_number(x509.random_serial_number()) 536 builder = builder.public_key(public_key) 537 538 basic_constraints = x509.BasicConstraints(ca=True, path_length=0) 539 builder = builder.add_extension(basic_constraints, critical=True) 540 541 usage = x509.KeyUsage( 542 content_commitment=False, 543 crl_sign=True, 544 data_encipherment=False, 545 decipher_only=False, 546 digital_signature=False, 547 encipher_only=False, 548 key_agreement=False, 549 key_cert_sign=True, 550 key_encipherment=False, 551 ) 552 builder = builder.add_extension(usage, critical=True) 553 554 auth_key = x509.AuthorityKeyIdentifier.from_issuer_public_key(public_key) 555 builder = builder.add_extension(auth_key, critical=False) 556 557 intermediate_cert = builder.sign( 558 private_key=ca_key, algorithm=hashes.SHA256() 559 ) 560 561 return private_key, intermediate_cert 562 563 564def generate_and_load_certs(url, username, password, use_intermediate=False): 565 certs_dir = os.path.expanduser("~/certs") 566 print(f"Writing certs to {certs_dir}") 567 try: 568 print("Making certs directory.") 569 os.mkdir(certs_dir) 570 except OSError as error: 571 if error.errno != errno.EEXIST: 572 raise 573 574 ca_cert_filename = os.path.join(certs_dir, "CA-cert.cer") 575 ca_key_filename = os.path.join(certs_dir, "CA-key.pem") 576 if not os.path.exists(ca_cert_filename): 577 ca_key, ca_cert = generateCA() 578 579 ca_key_dump = ca_key.private_bytes( 580 encoding=serialization.Encoding.PEM, 581 format=serialization.PrivateFormat.TraditionalOpenSSL, 582 encryption_algorithm=serialization.NoEncryption(), 583 ) 584 ca_cert_dump = ca_cert.public_bytes( 585 encoding=serialization.Encoding.PEM 586 ) 587 588 with open(ca_cert_filename, "wb") as f: 589 f.write(ca_cert_dump) 590 print("CA cert generated.") 591 with open(ca_key_filename, "wb") as f: 592 f.write(ca_key_dump) 593 print("CA key generated.") 594 595 with open(ca_cert_filename, "rb") as ca_cert_file: 596 ca_cert_dump = ca_cert_file.read() 597 ca_cert = x509.load_pem_x509_certificate(ca_cert_dump) 598 599 with open(ca_key_filename, "rb") as ca_key_file: 600 ca_key_dump = ca_key_file.read() 601 ca_key = load_pem_private_key(ca_key_dump, None) 602 603 # Generate intermediate certificates if requested 604 if use_intermediate: 605 # Generate client intermediate 606 client_intermediate_key, client_intermediate_cert = ( 607 generateIntermediateCA(ca_key, ca_cert, "client") 608 ) 609 # ... save client intermediate certs ... 610 611 # Generate server intermediate 612 server_intermediate_key, server_intermediate_cert = ( 613 generateIntermediateCA(ca_key, ca_cert, "server") 614 ) 615 else: 616 client_intermediate_key = ca_key 617 client_intermediate_cert = ca_cert 618 server_intermediate_key = ca_key 619 server_intermediate_cert = ca_cert 620 621 # Update client cert generation to use intermediate 622 client_key, client_cert = generate_client_key_and_cert( 623 ca_cert=client_intermediate_cert, 624 ca_key=client_intermediate_key, 625 common_name=username, 626 ) 627 client_key_dump = client_key.private_bytes( 628 encoding=serialization.Encoding.PEM, 629 format=serialization.PrivateFormat.TraditionalOpenSSL, 630 encryption_algorithm=serialization.NoEncryption(), 631 ) 632 633 with open(os.path.join(certs_dir, "client-key.pem"), "wb") as f: 634 f.write(client_key_dump) 635 print("Client key generated.") 636 client_cert_dump = client_cert.public_bytes( 637 encoding=serialization.Encoding.PEM 638 ) 639 640 # Save client certificate without intermediate for debugging 641 if use_intermediate: 642 with open(os.path.join(certs_dir, "client-cert-only.pem"), "wb") as f: 643 f.write(client_cert_dump) 644 print("Client cert (without intermediate) saved for debugging.") 645 client_cert_dump = ( 646 client_cert_dump 647 + client_intermediate_cert.public_bytes( 648 encoding=serialization.Encoding.PEM 649 ) 650 ) # Append intermediate to create chain 651 652 with open(os.path.join(certs_dir, "client-cert.pem"), "wb") as f: 653 f.write(client_cert_dump) 654 print("Client cert generated.") 655 656 print(f"Connecting to {url}") 657 with httpx.Client( 658 base_url=f"https://{url}", verify=False, follow_redirects=False 659 ) as redfish_session: 660 with RedfishSessionContext( 661 redfish_session, username, password 662 ) as rf_session: 663 redfish_session.headers["X-Auth-Token"] = rf_session.x_auth_token 664 665 hostname = get_hostname(redfish_session, username, password, url) 666 print(f"Hostname: {hostname}") 667 668 upn_client_key, upn_client_cert = generate_client_key_and_cert( 669 ca_cert, 670 ca_key, 671 upn=f"{username}@{hostname}", 672 ) 673 upn_client_key_dump = upn_client_key.private_bytes( 674 encoding=serialization.Encoding.PEM, 675 format=serialization.PrivateFormat.TraditionalOpenSSL, 676 encryption_algorithm=serialization.NoEncryption(), 677 ) 678 with open( 679 os.path.join(certs_dir, "upn-client-key.pem"), "wb" 680 ) as f: 681 f.write(upn_client_key_dump) 682 print("UPN client key generated.") 683 684 upn_client_cert_dump = upn_client_cert.public_bytes( 685 encoding=serialization.Encoding.PEM 686 ) 687 with open( 688 os.path.join(certs_dir, "upn-client-cert.pem"), "wb" 689 ) as f: 690 f.write(upn_client_cert_dump) 691 print("UPN client cert generated.") 692 693 setup_server_cert( 694 redfish_session, 695 ca_cert_dump, 696 certs_dir, 697 client_key, 698 client_cert, 699 username, 700 url, 701 server_intermediate_key, 702 server_intermediate_cert, 703 ) 704 705 print("Testing redfish TLS authentication with generated certs.") 706 time.sleep(2) 707 test_mtls_auth(url, certs_dir) 708 print("Redfish TLS authentication success!") 709 710 711def main(): 712 parser = argparse.ArgumentParser() 713 parser.add_argument( 714 "--username", 715 help="Username to connect with", 716 default="root", 717 ) 718 parser.add_argument( 719 "--password", 720 help="Password for user in order to install certs over Redfish.", 721 default="0penBmc", 722 ) 723 parser.add_argument( 724 "--use-intermediate", 725 action="store_true", 726 default=False, 727 help="Generate and use an intermediate certificate", 728 ) 729 parser.add_argument("host", help="Host to connect to") 730 731 args = parser.parse_args() 732 generate_and_load_certs( 733 args.host, args.username, args.password, args.use_intermediate 734 ) 735 736 737if __name__ == "__main__": 738 main() 739