blob: b5ae6984765ff64586b6412fe0547c3c3a6ca023 [file] [log] [blame]
Alex Schendel47af8322023-07-21 13:32:38 -07001#!/usr/bin/env python3
2
3import argparse
4import os
5
6import requests
7
8try:
9 import redfish
10except ModuleNotFoundError:
11 raise Exception("Please run pip install redfish to run this script.")
12try:
13 from OpenSSL import crypto
14except ImportError:
15 raise Exception("Please run pip install pyOpenSSL to run this script.")
16
17# Script to generate a certificates for a CA, server, and client
18# allowing for client authentication using mTLS certificates.
19# This can then be used to test mTLS client authentication for Redfish
20# and webUI. Note that this requires the pyOpenSSL library to function.
21# TODO: Use EC keys rather than RSA keys.
22
23
24def generateCACert(serial):
25 # CA key
26 key = crypto.PKey()
27 key.generate_key(crypto.TYPE_RSA, 2048)
28
29 # CA cert
30 cert = crypto.X509()
31 cert.set_serial_number(serial)
32 cert.set_version(2)
33 cert.set_pubkey(key)
34 cert.gmtime_adj_notBefore(0)
35 cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
36
37 caCertSubject = cert.get_subject()
38 caCertSubject.countryName = "US"
39 caCertSubject.stateOrProvinceName = "California"
40 caCertSubject.localityName = "San Francisco"
41 caCertSubject.organizationName = "OpenBMC"
42 caCertSubject.organizationalUnitName = "bmcweb"
43 caCertSubject.commonName = "Test CA"
44 cert.set_issuer(caCertSubject)
45
46 cert.add_extensions(
47 [
48 crypto.X509Extension(
49 b"basicConstraints", True, b"CA:TRUE, pathlen:0"
50 ),
51 crypto.X509Extension(b"keyUsage", True, b"keyCertSign, cRLSign"),
52 crypto.X509Extension(
53 b"subjectKeyIdentifier", False, b"hash", subject=cert
54 ),
55 ]
56 )
57 cert.add_extensions(
58 [
59 crypto.X509Extension(
60 b"authorityKeyIdentifier", False, b"keyid:always", issuer=cert
61 )
62 ]
63 )
64
65 # sign CA cert with CA key
66 cert.sign(key, "sha256")
67 return key, cert
68
69
70def generateCert(commonName, extensions, caKey, caCert, serial):
71 # key
72 key = crypto.PKey()
73 key.generate_key(crypto.TYPE_RSA, 2048)
74
75 # cert
76 cert = crypto.X509()
77 serial
78 cert.set_serial_number(serial)
79 cert.set_version(2)
80 cert.set_pubkey(key)
81 cert.gmtime_adj_notBefore(0)
82 cert.gmtime_adj_notAfter(365 * 24 * 60 * 60)
83
84 certSubject = cert.get_subject()
85 certSubject.countryName = "US"
86 certSubject.stateOrProvinceName = "California"
87 certSubject.localityName = "San Francisco"
88 certSubject.organizationName = "OpenBMC"
89 certSubject.organizationalUnitName = "bmcweb"
90 certSubject.commonName = commonName
91 cert.set_issuer(caCert.get_issuer())
92
93 cert.add_extensions(extensions)
94 cert.add_extensions(
95 [
96 crypto.X509Extension(
97 b"authorityKeyIdentifier", False, b"keyid", issuer=caCert
98 )
99 ]
100 )
101
102 cert.sign(caKey, "sha256")
103 return key, cert
104
105
106def main():
107 parser = argparse.ArgumentParser()
108 parser.add_argument("--host", help="Host to connect to", required=True)
109 parser.add_argument(
110 "--username", help="Username to connect with", default="root"
111 )
112 parser.add_argument(
113 "--password",
114 help="Password for user in order to install certs over Redfish.",
115 default="0penBmc",
116 )
117 args = parser.parse_args()
118 host = args.host
119 username = args.username
120 password = args.password
121 if username == "root" and password == "0penBMC":
122 print(
123 """Note: Using default username 'root' and default password
124 '0penBmc'. Use --username and --password flags to change these,
125 respectively."""
126 )
127 serial = 1000
128
129 try:
130 print("Making certs directory.")
131 os.mkdir("certs")
132 except OSError as error:
133 if error.errno == 17:
134 print("certs directory already exists. Skipping...")
135 else:
136 print(error)
137 caKey, caCert = generateCACert(serial)
138 serial += 1
139 caKeyDump = crypto.dump_privatekey(crypto.FILETYPE_PEM, caKey)
140 caCertDump = crypto.dump_certificate(crypto.FILETYPE_PEM, caCert)
141 with open("certs/CA-cert.pem", "wb") as f:
142 f.write(caCertDump)
143 print("CA cert generated.")
144 with open("certs/CA-key.pem", "wb") as f:
145 f.write(caKeyDump)
146 print("CA key generated.")
147
148 clientExtensions = [
149 crypto.X509Extension(
150 b"keyUsage",
151 True,
152 b"""digitalSignature,
153 keyAgreement""",
154 ),
155 crypto.X509Extension(b"extendedKeyUsage", True, b"clientAuth"),
156 ]
157 clientKey, clientCert = generateCert(
158 username, clientExtensions, caKey, caCert, serial
159 )
160 serial += 1
161 clientKeyDump = crypto.dump_privatekey(crypto.FILETYPE_PEM, clientKey)
162 clientCertDump = crypto.dump_certificate(crypto.FILETYPE_PEM, clientCert)
163 with open("certs/client-key.pem", "wb") as f:
164 f.write(clientKeyDump)
165 print("Client key generated.")
166 with open("certs/client-cert.pem", "wb") as f:
167 f.write(clientCertDump)
168 print("Client cert generated.")
169
170 serverExtensions = [
171 crypto.X509Extension(
172 b"keyUsage",
173 True,
174 b"""digitalSignature,
175 keyAgreement""",
176 ),
177 crypto.X509Extension(b"extendedKeyUsage", True, b"serverAuth"),
178 ]
179 serverKey, serverCert = generateCert(
180 host, serverExtensions, caKey, caCert, serial
181 )
182 serial += 1
183 serverKeyDump = crypto.dump_privatekey(crypto.FILETYPE_PEM, serverKey)
184 serverCertDump = crypto.dump_certificate(crypto.FILETYPE_PEM, serverCert)
185 with open("certs/server-key.pem", "wb") as f:
186 f.write(serverKeyDump)
187 print("Server key generated.")
188 with open("certs/server-cert.pem", "wb") as f:
189 f.write(serverCertDump)
190 print("Server cert generated.")
191
192 caCertJSON = {}
193 caCertJSON["CertificateString"] = caCertDump.decode()
194 caCertJSON["CertificateType"] = "PEM"
195 caCertPath = "/redfish/v1/Managers/bmc/Truststore/Certificates"
196 replaceCertPath = "/redfish/v1/CertificateService/Actions/"
197 replaceCertPath += "CertificateService.ReplaceCertificate"
198 print("Attempting to install CA certificate to BMC.")
199 redfishObject = redfish.redfish_client(
200 base_url="https://" + host,
201 username=username,
202 password=password,
203 default_prefix="/redfish/v1",
204 )
205 redfishObject.login(auth="session")
206 response = redfishObject.post(caCertPath, body=caCertJSON)
207 if response.status == 500:
208 print(
209 "An existing CA certificate is likely already installed."
210 " Replacing..."
211 )
212 caCertificateUri = {}
213 caCertificateUri["@odata.id"] = caCertPath + "/1"
214 caCertJSON["CertificateUri"] = caCertificateUri
215 response = redfishObject.post(replaceCertPath, body=caCertJSON)
216 if response.status == 200:
217 print("Successfully replaced existing CA certificate.")
218 else:
219 raise Exception(
220 "Could not install or replace CA certificate."
221 "Please check if a certificate is already installed. If a"
222 "certificate is already installed, try performing a factory"
223 "restore to clear such settings."
224 )
225 elif response.status == 200:
226 print("Successfully installed CA certificate.")
227 else:
228 raise Exception("Could not install certificate: " + response.read)
229 serverCertJSON = {}
230 serverCertJSON["CertificateString"] = (
231 serverKeyDump.decode() + serverCertDump.decode()
232 )
233 serverCertificateUri = {}
Ed Tanous4c8c12d2024-01-28 22:25:50 -0800234 serverCertificateUri["@odata.id"] = (
235 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1"
236 )
Alex Schendel47af8322023-07-21 13:32:38 -0700237 serverCertJSON["CertificateUri"] = serverCertificateUri
238 serverCertJSON["CertificateType"] = "PEM"
239 print("Replacing server certificate...")
240 response = redfishObject.post(replaceCertPath, body=serverCertJSON)
241 if response.status == 200:
242 print("Successfully replaced server certificate.")
243 else:
244 raise Exception("Could not replace certificate: " + response.read)
245 tlsPatchJSON = {"Oem": {"OpenBMC": {"AuthMethods": {"TLS": True}}}}
246 print("Ensuring TLS authentication is enabled.")
247 response = redfishObject.patch(
248 "/redfish/v1/AccountService", body=tlsPatchJSON
249 )
250 if response.status == 200:
251 print("Successfully enabled TLS authentication.")
252 else:
253 raise Exception("Could not enable TLS auth: " + response.read)
254 redfishObject.logout()
255 print("Testing redfish TLS authentication with generated certs.")
256 response = requests.get(
257 "https://" + host + "/redfish/v1/SessionService/Sessions",
258 verify=False,
259 cert=("certs/client-cert.pem", "certs/client-key.pem"),
260 )
261 response.raise_for_status()
262 print("Redfish TLS authentication success!")
263 print("Generating p12 cert file for browser authentication.")
264 pkcs12Cert = crypto.PKCS12()
265 pkcs12Cert.set_certificate(clientCert)
266 pkcs12Cert.set_privatekey(clientKey)
267 pkcs12Cert.set_ca_certificates([caCert])
268 pkcs12Cert.set_friendlyname(bytes(username, encoding="utf-8"))
269 with open("certs/client.p12", "wb") as f:
270 f.write(pkcs12Cert.export())
271 print(
272 "Client p12 cert file generated and stored in"
273 "./certs/client.p12."
274 )
275 print(
276 "Copy this file to a system with a browser and install the"
277 "cert into the browser."
278 )
279 print(
280 "You will then be able to test redfish and webui"
281 "authentication using this certificate."
282 )
283 print(
284 "Note: this p12 file was generated without a password, so it"
285 "can be imported easily."
286 )
287
288
289main()