root / doc / design-x509-ca.rst @ 7fa310f6
History | View | Annotate | Download (6.4 kB)
1 |
======================================= |
---|---|
2 |
Design for a X509 Certificate Authority |
3 |
======================================= |
4 |
|
5 |
.. contents:: :depth: 4 |
6 |
|
7 |
Current state and shortcomings |
8 |
------------------------------ |
9 |
|
10 |
Import/export in Ganeti have a need for many unique X509 certificates. |
11 |
So far these were all self-signed, but with the :doc:`new design for |
12 |
import/export <design-impexp2>` they need to be signed by a Certificate |
13 |
Authority (CA). |
14 |
|
15 |
|
16 |
Proposed changes |
17 |
---------------- |
18 |
|
19 |
The plan is to implement a simple CA in Ganeti. |
20 |
|
21 |
Interacting with an external CA is too difficult or impossible for |
22 |
automated processes like exporting instances, so each Ganeti cluster |
23 |
will have its own CA. The public key will be stored in |
24 |
``…/lib/ganeti/ca/cert.pem``, the private key (only readable by the |
25 |
master daemon) in ``…/lib/ganeti/ca/key.pem``. |
26 |
|
27 |
Similar to the RAPI certificate, a new CA certificate can be installed |
28 |
using the ``gnt-cluster renew-crypto`` command. Such a CA could be an |
29 |
intermediate of a third-party CA. By default a self-signed CA is |
30 |
generated and used. |
31 |
|
32 |
.. _x509-ca-serial: |
33 |
|
34 |
Each certificate signed by the CA is required to have a unique serial |
35 |
number. The serial number is stored in the file |
36 |
``…/lib/ganeti/ca/serial``, replicated to all master candidates and |
37 |
never reset, even when a new CA is installed. |
38 |
|
39 |
The threat model is expected to be the same as with self-signed |
40 |
certificates. To reinforce this, all certificates signed by the CA must |
41 |
be valid for less than one week (168 hours). |
42 |
|
43 |
Implementing support for Certificate Revocation Lists (CRL) using |
44 |
OpenSSL is non-trivial. Lighttpd doesn't support them at all and |
45 |
`apparently never will in version 1.4.x |
46 |
<http://redmine.lighttpd.net/issues/2278>`_. Some CRL-related parts have |
47 |
only been added in the most recent version of pyOpenSSL (0.11). Instead |
48 |
of a CRL, Ganeti will gain a new cluster configuration property defining |
49 |
the minimum accepted serial number. In case of a lost or compromised |
50 |
private key this property can be set to the most recently generated |
51 |
serial number. |
52 |
|
53 |
While possible to implement in the future, other X509 certificates used |
54 |
by the cluster (e.g. RAPI or inter-node communication) will not be |
55 |
automatically signed by the per-cluster CA. |
56 |
|
57 |
The ``commonName`` attribute of signed certificates must be set to the |
58 |
the cluster name or the name of a node in the cluster. |
59 |
|
60 |
|
61 |
Software requirements |
62 |
--------------------- |
63 |
|
64 |
- pyOpenSSL 0.10 or above (lower versions can't set the X509v3 extension |
65 |
``subjectKeyIdentifier`` recommended for certificate authority |
66 |
certificates by :rfc:`3280`, section 4.2.1.2) |
67 |
|
68 |
|
69 |
Code samples |
70 |
------------ |
71 |
|
72 |
Generating X509 CA using pyOpenSSL |
73 |
++++++++++++++++++++++++++++++++++ |
74 |
|
75 |
.. highlight:: python |
76 |
|
77 |
The following code sample shows how to generate a CA certificate using |
78 |
pyOpenSSL:: |
79 |
|
80 |
key = OpenSSL.crypto.PKey() |
81 |
key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) |
82 |
|
83 |
ca = OpenSSL.crypto.X509() |
84 |
ca.set_version(3) |
85 |
ca.set_serial_number(1) |
86 |
ca.get_subject().CN = "ca.example.com" |
87 |
ca.gmtime_adj_notBefore(0) |
88 |
ca.gmtime_adj_notAfter(24 * 60 * 60) |
89 |
ca.set_issuer(ca.get_subject()) |
90 |
ca.set_pubkey(key) |
91 |
ca.add_extensions([ |
92 |
OpenSSL.crypto.X509Extension("basicConstraints", True, |
93 |
"CA:TRUE, pathlen:0"), |
94 |
OpenSSL.crypto.X509Extension("keyUsage", True, |
95 |
"keyCertSign, cRLSign"), |
96 |
OpenSSL.crypto.X509Extension("subjectKeyIdentifier", False, "hash", |
97 |
subject=ca), |
98 |
]) |
99 |
ca.sign(key, "sha1") |
100 |
|
101 |
|
102 |
Signing X509 certificate using CA |
103 |
+++++++++++++++++++++++++++++++++ |
104 |
|
105 |
.. highlight:: python |
106 |
|
107 |
The following code sample shows how to sign an X509 certificate using a |
108 |
CA:: |
109 |
|
110 |
ca_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, |
111 |
"ca.pem") |
112 |
ca_key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, |
113 |
"ca.pem") |
114 |
|
115 |
key = OpenSSL.crypto.PKey() |
116 |
key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) |
117 |
|
118 |
cert = OpenSSL.crypto.X509() |
119 |
cert.get_subject().CN = "node1.example.com" |
120 |
cert.set_serial_number(1) |
121 |
cert.gmtime_adj_notBefore(0) |
122 |
cert.gmtime_adj_notAfter(24 * 60 * 60) |
123 |
cert.set_issuer(ca_cert.get_subject()) |
124 |
cert.set_pubkey(key) |
125 |
cert.sign(ca_key, "sha1") |
126 |
|
127 |
|
128 |
How to generate Certificate Signing Request |
129 |
+++++++++++++++++++++++++++++++++++++++++++ |
130 |
|
131 |
.. highlight:: python |
132 |
|
133 |
The following code sample shows how to generate an X509 Certificate |
134 |
Request (CSR):: |
135 |
|
136 |
key = OpenSSL.crypto.PKey() |
137 |
key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) |
138 |
|
139 |
req = OpenSSL.crypto.X509Req() |
140 |
req.get_subject().CN = "node1.example.com" |
141 |
req.set_pubkey(key) |
142 |
req.sign(key, "sha1") |
143 |
|
144 |
# Write private key |
145 |
print OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) |
146 |
|
147 |
# Write request |
148 |
print OpenSSL.crypto.dump_certificate_request(OpenSSL.crypto.FILETYPE_PEM, req) |
149 |
|
150 |
|
151 |
X509 certificate from Certificate Signing Request |
152 |
+++++++++++++++++++++++++++++++++++++++++++++++++ |
153 |
|
154 |
.. highlight:: python |
155 |
|
156 |
The following code sample shows how to create an X509 certificate from a |
157 |
Certificate Signing Request and sign it with a CA:: |
158 |
|
159 |
ca_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, |
160 |
"ca.pem") |
161 |
ca_key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, |
162 |
"ca.pem") |
163 |
req = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, |
164 |
open("req.csr").read()) |
165 |
|
166 |
cert = OpenSSL.crypto.X509() |
167 |
cert.set_subject(req.get_subject()) |
168 |
cert.set_serial_number(1) |
169 |
cert.gmtime_adj_notBefore(0) |
170 |
cert.gmtime_adj_notAfter(24 * 60 * 60) |
171 |
cert.set_issuer(ca_cert.get_subject()) |
172 |
cert.set_pubkey(req.get_pubkey()) |
173 |
cert.sign(ca_key, "sha1") |
174 |
|
175 |
print OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) |
176 |
|
177 |
|
178 |
Verify whether X509 certificate matches private key |
179 |
+++++++++++++++++++++++++++++++++++++++++++++++++++ |
180 |
|
181 |
.. highlight:: python |
182 |
|
183 |
The code sample below shows how to check whether a certificate matches |
184 |
with a certain private key. OpenSSL has a function for this, |
185 |
``X509_check_private_key``, but pyOpenSSL provides no access to it. |
186 |
|
187 |
:: |
188 |
|
189 |
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD) |
190 |
ctx.use_privatekey(key) |
191 |
ctx.use_certificate(cert) |
192 |
try: |
193 |
ctx.check_privatekey() |
194 |
except OpenSSL.SSL.Error: |
195 |
print "Incorrect key" |
196 |
else: |
197 |
print "Key matches certificate" |
198 |
|
199 |
|
200 |
.. vim: set textwidth=72 : |
201 |
.. Local Variables: |
202 |
.. mode: rst |
203 |
.. fill-column: 72 |
204 |
.. End: |