build_chroot: hard-code the version of blaze-builder
[ganeti-local] / doc / design-x509-ca.rst
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: