summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorEd Tanous <ed@tanous.net>2024-04-08 06:24:12 +0300
committerEd Tanous <ed@tanous.net>2024-04-11 00:15:35 +0300
commit7b9e256961fe529ce874cae572c17baa4fa0bbc3 (patch)
tree5fa821647806452a61ac174874866ffcb93e60b2 /scripts
parentf1a1e3dcca1db1ee7c39b673a387ec6cd231561b (diff)
downloadbmcweb-7b9e256961fe529ce874cae572c17baa4fa0bbc3.tar.xz
Fix generate auth certs
bmcs might not have the correct time, so allow certificates for 100 years starting from epoch. As is, the script makes the certificate valid for now + 10 years. After changes make the script valid from epoch (1970) to 100 years later (2070). This makes the script run to completion against a qemu instance of the bmc. Additional changes include detecting if a CA key is already present, to not rewrite it. This allows installing a CA certificate on test machines once, and using it to authenticate forever. Additionally, add "alternative names" support, for pointing to a bmc at localhost, or on the default qemu port, which allows these things to work by default in those scenarios. Lastly, change the directory to use a path relative to the script path, instead of relative to current path when generating certificates. This ensures that certs are always generated in the same place, which helps when a CA is reused. Tested: Script runs to completion without errors. Change-Id: Ia5c31041dd5cb193b897bf1f7bae3cd9767656d0 Signed-off-by: Ed Tanous <ed@tanous.net>
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/generate_auth_certificates.py284
1 files changed, 196 insertions, 88 deletions
diff --git a/scripts/generate_auth_certificates.py b/scripts/generate_auth_certificates.py
index b5ae698476..47e3592f8e 100755
--- a/scripts/generate_auth_certificates.py
+++ b/scripts/generate_auth_certificates.py
@@ -2,6 +2,8 @@
import argparse
import os
+import socket
+import urllib
import requests
@@ -14,6 +16,8 @@ try:
except ImportError:
raise Exception("Please run pip install pyOpenSSL to run this script.")
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+
# 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
@@ -31,8 +35,9 @@ def generateCACert(serial):
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)
+
+ cert.set_notBefore(b"19700101000000Z")
+ cert.set_notAfter(b"20700101000000Z")
caCertSubject = cert.get_subject()
caCertSubject.countryName = "US"
@@ -58,7 +63,7 @@ def generateCACert(serial):
[
crypto.X509Extension(
b"authorityKeyIdentifier", False, b"keyid:always", issuer=cert
- )
+ ),
]
)
@@ -67,19 +72,65 @@ def generateCACert(serial):
return key, cert
-def generateCert(commonName, extensions, caKey, caCert, serial):
+def generateCertCsr(
+ redfishObject, commonName, extensions, caKey, caCert, serial
+):
+
+ try:
+ socket.inet_aton(commonName)
+ commonName = "IP: " + commonName
+ except socket.error:
+ commonName = "DNS: " + commonName
+
+ CSRRequest = {
+ "CommonName": commonName,
+ "City": "San Fransisco",
+ "Country": "US",
+ "Organization": "",
+ "OrganizationalUnit": "",
+ "State": "CA",
+ "CertificateCollection": {
+ "@odata.id": "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates",
+ },
+ "AlternativeNames": [
+ commonName,
+ "DNS: localhost",
+ "IP: 127.0.0.1",
+ ],
+ }
+
+ response = redfishObject.post(
+ "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR",
+ body=CSRRequest,
+ )
+
+ if response.status != 200:
+ raise Exception("Failed to create CSR")
+
+ csrString = response.dict["CSRString"]
+
+ return crypto.load_certificate_request(crypto.FILETYPE_PEM, csrString)
+
+
+def generateCert(commonName, extensions, caKey, caCert, serial, csr=None):
# 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)
+
+ if csr is None:
+ key = crypto.PKey()
+ key.generate_key(crypto.TYPE_RSA, 2048)
+ cert.set_pubkey(key)
+ else:
+ key = None
+ cert.set_subject(csr.get_subject())
+ cert.set_pubkey(csr.get_pubkey())
+
+ cert.set_notBefore(b"19700101000000Z")
+ cert.set_notAfter(b"20700101000000Z")
certSubject = cert.get_subject()
certSubject.countryName = "US"
@@ -90,15 +141,14 @@ def generateCert(commonName, extensions, caKey, caCert, serial):
certSubject.commonName = commonName
cert.set_issuer(caCert.get_issuer())
- cert.add_extensions(extensions)
- cert.add_extensions(
+ extensions.extend(
[
crypto.X509Extension(
b"authorityKeyIdentifier", False, b"keyid", issuer=caCert
- )
+ ),
]
)
-
+ cert.add_extensions(extensions)
cert.sign(caKey, "sha256")
return key, cert
@@ -124,26 +174,43 @@ def main():
'0penBmc'. Use --username and --password flags to change these,
respectively."""
)
- serial = 1000
+ if "//" not in host:
+ host = f"https://{host}"
+ url = urllib.parse.urlparse(host, scheme="https")
+ serial = 1000
+ certsDir = os.path.join(SCRIPT_DIR, "certs")
+ print(f"Writing certs to {certsDir}")
try:
print("Making certs directory.")
- os.mkdir("certs")
+ os.mkdir(certsDir)
except OSError as error:
if error.errno == 17:
print("certs directory already exists. Skipping...")
else:
print(error)
- caKey, caCert = generateCACert(serial)
+
+ cacertFilename = os.path.join(certsDir, "CA-cert.cer")
+ cakeyFilename = os.path.join(certsDir, "CA-key.pem")
+ if os.path.exists(cacertFilename):
+ with open(cacertFilename, "rb") as cacert_file:
+ caCertDump = cacert_file.read()
+ caCert = crypto.load_certificate(crypto.FILETYPE_PEM, caCertDump)
+ with open(cakeyFilename, "rb") as cakey_file:
+ caKeyDump = cakey_file.read()
+ caKey = crypto.load_privatekey(crypto.FILETYPE_PEM, caKeyDump)
+ else:
+
+ caKey, caCert = generateCACert(serial)
+ caKeyDump = crypto.dump_privatekey(crypto.FILETYPE_PEM, caKey)
+ caCertDump = crypto.dump_certificate(crypto.FILETYPE_PEM, caCert)
+ with open(cacertFilename, "wb") as f:
+ f.write(caCertDump)
+ print("CA cert generated.")
+ with open(cakeyFilename, "wb") as f:
+ f.write(caKeyDump)
+ print("CA key generated.")
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(
@@ -154,64 +221,127 @@ def main():
),
crypto.X509Extension(b"extendedKeyUsage", True, b"clientAuth"),
]
+
+ redfishObject = redfish.redfish_client(
+ base_url="https://" + url.netloc,
+ username=username,
+ password=password,
+ default_prefix="/redfish/v1",
+ )
+ redfishObject.login(auth="session")
+
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:
+ with open(os.path.join(certsDir, "client-key.pem"), "wb") as f:
f.write(clientKeyDump)
print("Client key generated.")
- with open("certs/client-cert.pem", "wb") as f:
+ serial += 1
+ clientCertDump = crypto.dump_certificate(crypto.FILETYPE_PEM, clientCert)
+
+ with open(os.path.join(certsDir, "client-cert.pem"), "wb") as f:
f.write(clientCertDump)
print("Client cert generated.")
+ san_list = [
+ b"DNS: localhost",
+ b"IP: 127.0.0.1",
+ ]
+
+ try:
+ socket.inet_aton(url.hostname)
+ san_list.append(b"IP: " + url.hostname.encode())
+ except socket.error:
+ san_list.append(b"DNS: " + url.hostname.encode())
+
serverExtensions = [
crypto.X509Extension(
b"keyUsage",
True,
- b"""digitalSignature,
- keyAgreement""",
+ b"digitalSignature, keyAgreement",
),
crypto.X509Extension(b"extendedKeyUsage", True, b"serverAuth"),
+ crypto.X509Extension(b"subjectAltName", False, b", ".join(san_list)),
]
+
+ useCSR = True
+
+ if useCSR:
+ csr = generateCertCsr(
+ redfishObject,
+ url.hostname,
+ serverExtensions,
+ caKey,
+ caCert,
+ serial,
+ )
+ serverKey = None
+ serverKeyDumpStr = ""
+ else:
+ csr = None
serverKey, serverCert = generateCert(
- host, serverExtensions, caKey, caCert, serial
+ url.hostname, serverExtensions, caKey, caCert, serial, csr=csr
)
+ if serverKey is not None:
+ serverKeyDump = crypto.dump_privatekey(crypto.FILETYPE_PEM, serverKey)
+ with open(os.path.join(certsDir, "server-key.pem"), "wb") as f:
+ f.write(serverKeyDump)
+ print("Server key generated.")
+ serverKeyDumpStr = serverKeyDump.decode()
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:
+
+ with open(os.path.join(certsDir, "server-cert.pem"), "wb") as f:
f.write(serverCertDump)
print("Server cert generated.")
- caCertJSON = {}
- caCertJSON["CertificateString"] = caCertDump.decode()
- caCertJSON["CertificateType"] = "PEM"
+ serverCertDumpStr = serverCertDump.decode()
+
+ print("Generating p12 cert file for browser authentication.")
+ pkcs12Cert = crypto.PKCS12()
+ pkcs12Cert.set_certificate(clientCert)
+ if clientKey:
+ pkcs12Cert.set_privatekey(clientKey)
+ pkcs12Cert.set_ca_certificates([caCert])
+ pkcs12Cert.set_friendlyname(bytes(username, encoding="utf-8"))
+ with open(os.path.join(certsDir, "client.p12"), "wb") as f:
+ f.write(pkcs12Cert.export())
+ print("Client p12 cert file generated and stored in 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."
+ )
+
+ caCertJSON = {
+ "CertificateString": caCertDump.decode(),
+ "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
+ caCertJSON["CertificateUri"] = {
+ "@odata.id": caCertPath + "/1",
+ }
+
response = redfishObject.post(replaceCertPath, body=caCertJSON)
if response.status == 200:
print("Successfully replaced existing CA certificate.")
@@ -226,16 +356,14 @@ def main():
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"
+ serverCertJSON = {
+ "CertificateString": serverKeyDumpStr + serverCertDumpStr,
+ "CertificateUri": {
+ "@odata.id": "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1",
+ },
+ "CertificateType": "PEM",
+ }
+
print("Replacing server certificate...")
response = redfishObject.post(replaceCertPath, body=serverCertJSON)
if response.status == 200:
@@ -254,36 +382,16 @@ def main():
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"),
+ f"https://{url.netloc}/redfish/v1/SessionService/Sessions",
+ verify=os.path.join(certsDir, "CA-cert.cer"),
+ cert=(
+ os.path.join(certsDir, "client-cert.pem"),
+ os.path.join(certsDir, "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()
+if __name__ == "__main__":
+ main()