Design for a X509 Certificate Authority¶
- Created:
2011-Mar-23
- Status:
Draft
Current state and shortcomings¶
Import/export in Ganeti have a need for many unique X509 certificates. So far these were all self-signed, but with the new design for import/export they need to be signed by a Certificate Authority (CA).
Proposed changes¶
The plan is to implement a simple CA in Ganeti.
Interacting with an external CA is too difficult or impossible for
automated processes like exporting instances, so each Ganeti cluster
will have its own CA. The public key will be stored in
…/lib/ganeti/ca/cert.pem
, the private key (only readable by the
master daemon) in …/lib/ganeti/ca/key.pem
.
Similar to the RAPI certificate, a new CA certificate can be installed
using the gnt-cluster renew-crypto
command. Such a CA could be an
intermediate of a third-party CA. By default a self-signed CA is
generated and used.
Each certificate signed by the CA is required to have a unique serial
number. The serial number is stored in the file
…/lib/ganeti/ca/serial
, replicated to all master candidates and
never reset, even when a new CA is installed.
The threat model is expected to be the same as with self-signed certificates. To reinforce this, all certificates signed by the CA must be valid for less than one week (168 hours).
Implementing support for Certificate Revocation Lists (CRL) using OpenSSL is non-trivial. Lighttpd doesn’t support them at all and apparently never will in version 1.4.x. Some CRL-related parts have only been added in the most recent version of pyOpenSSL (0.11). Instead of a CRL, Ganeti will gain a new cluster configuration property defining the minimum accepted serial number. In case of a lost or compromised private key this property can be set to the most recently generated serial number.
While possible to implement in the future, other X509 certificates used by the cluster (e.g. RAPI or inter-node communication) will not be automatically signed by the per-cluster CA.
The commonName
attribute of signed certificates must be set to the
the cluster name or the name of a node in the cluster.
Software requirements¶
pyOpenSSL 0.10 or above (lower versions can’t set the X509v3 extension
subjectKeyIdentifier
recommended for certificate authority certificates by RFC 3280, section 4.2.1.2)
Code samples¶
Generating X509 CA using pyOpenSSL¶
The following code sample shows how to generate a CA certificate using pyOpenSSL:
key = OpenSSL.crypto.PKey()
key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
ca = OpenSSL.crypto.X509()
ca.set_version(3)
ca.set_serial_number(1)
ca.get_subject().CN = "ca.example.com"
ca.gmtime_adj_notBefore(0)
ca.gmtime_adj_notAfter(24 * 60 * 60)
ca.set_issuer(ca.get_subject())
ca.set_pubkey(key)
ca.add_extensions([
OpenSSL.crypto.X509Extension("basicConstraints", True,
"CA:TRUE, pathlen:0"),
OpenSSL.crypto.X509Extension("keyUsage", True,
"keyCertSign, cRLSign"),
OpenSSL.crypto.X509Extension("subjectKeyIdentifier", False, "hash",
subject=ca),
])
ca.sign(key, "sha1")
Signing X509 certificate using CA¶
The following code sample shows how to sign an X509 certificate using a CA:
ca_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
"ca.pem")
ca_key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM,
"ca.pem")
key = OpenSSL.crypto.PKey()
key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
cert = OpenSSL.crypto.X509()
cert.get_subject().CN = "node1.example.com"
cert.set_serial_number(1)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(24 * 60 * 60)
cert.set_issuer(ca_cert.get_subject())
cert.set_pubkey(key)
cert.sign(ca_key, "sha1")
How to generate Certificate Signing Request¶
The following code sample shows how to generate an X509 Certificate Request (CSR):
key = OpenSSL.crypto.PKey()
key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
req = OpenSSL.crypto.X509Req()
req.get_subject().CN = "node1.example.com"
req.set_pubkey(key)
req.sign(key, "sha1")
# Write private key
print(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key))
# Write request
print(OpenSSL.crypto.dump_certificate_request(OpenSSL.crypto.FILETYPE_PEM, req))
X509 certificate from Certificate Signing Request¶
The following code sample shows how to create an X509 certificate from a Certificate Signing Request and sign it with a CA:
ca_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
"ca.pem")
ca_key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM,
"ca.pem")
req = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM,
open("req.csr").read())
cert = OpenSSL.crypto.X509()
cert.set_subject(req.get_subject())
cert.set_serial_number(1)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(24 * 60 * 60)
cert.set_issuer(ca_cert.get_subject())
cert.set_pubkey(req.get_pubkey())
cert.sign(ca_key, "sha1")
print(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert))
Verify whether X509 certificate matches private key¶
The code sample below shows how to check whether a certificate matches
with a certain private key. OpenSSL has a function for this,
X509_check_private_key
, but pyOpenSSL provides no access to it.
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
ctx.use_privatekey(key)
ctx.use_certificate(cert)
try:
ctx.check_privatekey()
except OpenSSL.SSL.Error:
print("Incorrect key")
else:
print("Key matches certificate")