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