Statistics
| Branch: | Tag: | Revision:

root / doc / design-x509-ca.rst @ 9110fb4a

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: