blob: b5ae6984765ff64586b6412fe0547c3c3a6ca023 [file] [log] [blame]
#!/usr/bin/env python3
import argparse
import os
import requests
try:
import redfish
except ModuleNotFoundError:
raise Exception("Please run pip install redfish to run this script.")
try:
from OpenSSL import crypto
except ImportError:
raise Exception("Please run pip install pyOpenSSL to run this script.")
# Script to generate a certificates for a CA, server, and client
# allowing for client authentication using mTLS certificates.
# This can then be used to test mTLS client authentication for Redfish
# and webUI. Note that this requires the pyOpenSSL library to function.
# TODO: Use EC keys rather than RSA keys.
def generateCACert(serial):
# CA key
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 2048)
# CA cert
cert = crypto.X509()
cert.set_serial_number(serial)
cert.set_version(2)
cert.set_pubkey(key)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
caCertSubject = cert.get_subject()
caCertSubject.countryName = "US"
caCertSubject.stateOrProvinceName = "California"
caCertSubject.localityName = "San Francisco"
caCertSubject.organizationName = "OpenBMC"
caCertSubject.organizationalUnitName = "bmcweb"
caCertSubject.commonName = "Test CA"
cert.set_issuer(caCertSubject)
cert.add_extensions(
[
crypto.X509Extension(
b"basicConstraints", True, b"CA:TRUE, pathlen:0"
),
crypto.X509Extension(b"keyUsage", True, b"keyCertSign, cRLSign"),
crypto.X509Extension(
b"subjectKeyIdentifier", False, b"hash", subject=cert
),
]
)
cert.add_extensions(
[
crypto.X509Extension(
b"authorityKeyIdentifier", False, b"keyid:always", issuer=cert
)
]
)
# sign CA cert with CA key
cert.sign(key, "sha256")
return key, cert
def generateCert(commonName, extensions, caKey, caCert, serial):
# key
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 2048)
# cert
cert = crypto.X509()
serial
cert.set_serial_number(serial)
cert.set_version(2)
cert.set_pubkey(key)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(365 * 24 * 60 * 60)
certSubject = cert.get_subject()
certSubject.countryName = "US"
certSubject.stateOrProvinceName = "California"
certSubject.localityName = "San Francisco"
certSubject.organizationName = "OpenBMC"
certSubject.organizationalUnitName = "bmcweb"
certSubject.commonName = commonName
cert.set_issuer(caCert.get_issuer())
cert.add_extensions(extensions)
cert.add_extensions(
[
crypto.X509Extension(
b"authorityKeyIdentifier", False, b"keyid", issuer=caCert
)
]
)
cert.sign(caKey, "sha256")
return key, cert
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--host", help="Host to connect to", required=True)
parser.add_argument(
"--username", help="Username to connect with", default="root"
)
parser.add_argument(
"--password",
help="Password for user in order to install certs over Redfish.",
default="0penBmc",
)
args = parser.parse_args()
host = args.host
username = args.username
password = args.password
if username == "root" and password == "0penBMC":
print(
"""Note: Using default username 'root' and default password
'0penBmc'. Use --username and --password flags to change these,
respectively."""
)
serial = 1000
try:
print("Making certs directory.")
os.mkdir("certs")
except OSError as error:
if error.errno == 17:
print("certs directory already exists. Skipping...")
else:
print(error)
caKey, caCert = generateCACert(serial)
serial += 1
caKeyDump = crypto.dump_privatekey(crypto.FILETYPE_PEM, caKey)
caCertDump = crypto.dump_certificate(crypto.FILETYPE_PEM, caCert)
with open("certs/CA-cert.pem", "wb") as f:
f.write(caCertDump)
print("CA cert generated.")
with open("certs/CA-key.pem", "wb") as f:
f.write(caKeyDump)
print("CA key generated.")
clientExtensions = [
crypto.X509Extension(
b"keyUsage",
True,
b"""digitalSignature,
keyAgreement""",
),
crypto.X509Extension(b"extendedKeyUsage", True, b"clientAuth"),
]
clientKey, clientCert = generateCert(
username, clientExtensions, caKey, caCert, serial
)
serial += 1
clientKeyDump = crypto.dump_privatekey(crypto.FILETYPE_PEM, clientKey)
clientCertDump = crypto.dump_certificate(crypto.FILETYPE_PEM, clientCert)
with open("certs/client-key.pem", "wb") as f:
f.write(clientKeyDump)
print("Client key generated.")
with open("certs/client-cert.pem", "wb") as f:
f.write(clientCertDump)
print("Client cert generated.")
serverExtensions = [
crypto.X509Extension(
b"keyUsage",
True,
b"""digitalSignature,
keyAgreement""",
),
crypto.X509Extension(b"extendedKeyUsage", True, b"serverAuth"),
]
serverKey, serverCert = generateCert(
host, serverExtensions, caKey, caCert, serial
)
serial += 1
serverKeyDump = crypto.dump_privatekey(crypto.FILETYPE_PEM, serverKey)
serverCertDump = crypto.dump_certificate(crypto.FILETYPE_PEM, serverCert)
with open("certs/server-key.pem", "wb") as f:
f.write(serverKeyDump)
print("Server key generated.")
with open("certs/server-cert.pem", "wb") as f:
f.write(serverCertDump)
print("Server cert generated.")
caCertJSON = {}
caCertJSON["CertificateString"] = caCertDump.decode()
caCertJSON["CertificateType"] = "PEM"
caCertPath = "/redfish/v1/Managers/bmc/Truststore/Certificates"
replaceCertPath = "/redfish/v1/CertificateService/Actions/"
replaceCertPath += "CertificateService.ReplaceCertificate"
print("Attempting to install CA certificate to BMC.")
redfishObject = redfish.redfish_client(
base_url="https://" + host,
username=username,
password=password,
default_prefix="/redfish/v1",
)
redfishObject.login(auth="session")
response = redfishObject.post(caCertPath, body=caCertJSON)
if response.status == 500:
print(
"An existing CA certificate is likely already installed."
" Replacing..."
)
caCertificateUri = {}
caCertificateUri["@odata.id"] = caCertPath + "/1"
caCertJSON["CertificateUri"] = caCertificateUri
response = redfishObject.post(replaceCertPath, body=caCertJSON)
if response.status == 200:
print("Successfully replaced existing CA certificate.")
else:
raise Exception(
"Could not install or replace CA certificate."
"Please check if a certificate is already installed. If a"
"certificate is already installed, try performing a factory"
"restore to clear such settings."
)
elif response.status == 200:
print("Successfully installed CA certificate.")
else:
raise Exception("Could not install certificate: " + response.read)
serverCertJSON = {}
serverCertJSON["CertificateString"] = (
serverKeyDump.decode() + serverCertDump.decode()
)
serverCertificateUri = {}
serverCertificateUri["@odata.id"] = (
"/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1"
)
serverCertJSON["CertificateUri"] = serverCertificateUri
serverCertJSON["CertificateType"] = "PEM"
print("Replacing server certificate...")
response = redfishObject.post(replaceCertPath, body=serverCertJSON)
if response.status == 200:
print("Successfully replaced server certificate.")
else:
raise Exception("Could not replace certificate: " + response.read)
tlsPatchJSON = {"Oem": {"OpenBMC": {"AuthMethods": {"TLS": True}}}}
print("Ensuring TLS authentication is enabled.")
response = redfishObject.patch(
"/redfish/v1/AccountService", body=tlsPatchJSON
)
if response.status == 200:
print("Successfully enabled TLS authentication.")
else:
raise Exception("Could not enable TLS auth: " + response.read)
redfishObject.logout()
print("Testing redfish TLS authentication with generated certs.")
response = requests.get(
"https://" + host + "/redfish/v1/SessionService/Sessions",
verify=False,
cert=("certs/client-cert.pem", "certs/client-key.pem"),
)
response.raise_for_status()
print("Redfish TLS authentication success!")
print("Generating p12 cert file for browser authentication.")
pkcs12Cert = crypto.PKCS12()
pkcs12Cert.set_certificate(clientCert)
pkcs12Cert.set_privatekey(clientKey)
pkcs12Cert.set_ca_certificates([caCert])
pkcs12Cert.set_friendlyname(bytes(username, encoding="utf-8"))
with open("certs/client.p12", "wb") as f:
f.write(pkcs12Cert.export())
print(
"Client p12 cert file generated and stored in"
"./certs/client.p12."
)
print(
"Copy this file to a system with a browser and install the"
"cert into the browser."
)
print(
"You will then be able to test redfish and webui"
"authentication using this certificate."
)
print(
"Note: this p12 file was generated without a password, so it"
"can be imported easily."
)
main()