Resume typos
[pwan.org.git] / content / hints / expired_ca.rst
1 ################
2 Expired CA Notes
3 ################
4
5 :date: 2022-10-31
6 :tags: hint, openssl, x509, expiration
7 :category: hints
8 :author: Jude N
9
10 Recently, I ran some tests to see what would happen when my root CA cert expired, and what I'd need to do to update the cert.
11
12 Spoiler alert: Updating the CA cert was not that hard...
13
14 First I created a CA that expired in 2 hours using the new-ca.py code below:
15
16 .. code-block:: python
17
18 from OpenSSL import crypto
19
20 #Following script will create a self signed root ca cert.
21 from OpenSSL import crypto, SSL
22 from os.path import join
23 import random
24
25 CN='expired-ca-test'
26 pubkey = "%s.crt" % CN #replace %s with CN
27 privkey = "%s.key" % CN # replcate %s with CN
28
29 pubkey = join(".", pubkey)
30 privkey = join(".", privkey)
31
32 k = crypto.PKey()
33 k.generate_key(crypto.TYPE_RSA, 2048)
34
35 # create a self-signed cert
36 cert = crypto.X509()
37 cert.get_subject().CN = CN
38 cert.set_serial_number(0)
39 cert.gmtime_adj_notBefore(0)
40 cert.gmtime_adj_notAfter(7200) # CA is only good for 2 hours
41 cert.set_issuer(cert.get_subject())
42 cert.set_subject(cert.get_subject())
43 cert.set_version(2)
44 xt = crypto.X509Extension(b'basicConstraints',1,b'CA:TRUE')
45 cert.add_extensions((xt,))
46
47 cert.set_pubkey(k)
48 cert.sign(k, 'sha512')
49 pub=crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
50 priv=crypto.dump_privatekey(crypto.FILETYPE_PEM, k)
51 open(pubkey,"wt").write(pub.decode("utf-8"))
52 open(privkey, "wt").write(priv.decode("utf-8") )
53
54 This block is based on how the ancient certmaster program `created its CA`_.
55
56 Then I created a expired-ca-test.srl with contents "01"
57
58 .. code-block:: bash
59
60 > echo 01 > expired-ca-test.srl
61
62 Then I issued a cert against this CA:
63
64 .. code-block:: bash
65
66 > openssl genrsa -out pre-expired-example.key 4096
67 > openssl req -new -key pre-expired-example.key -out pre-expired-example.csr
68 > openssl x509 -req -days 365 -in pre-expired-example.csr -CA expired-ca-test.crt -CAkey expired-ca-test.key -CAserial expired-ca-test.srl -out pre-expired-example.crt
69 > openssl x509 -in pre-expired-example.crt -text
70 > openssl verify -verbose -CAfile expired-ca-test.crt pre-expired-example.crt
71 pre-expired-example.crt: OK
72
73 Then I `waited 2 hours`_ and went back to check the certs:
74
75 .. code-block:: bash
76
77 > openssl x509 -in expired-ca-test.crt -noout -enddate
78 notAfter=Oct 2 17:41:11 2022 GMT
79
80 Then I tested what would happen if I tried verifying the cert signed with the expired CA:
81
82 .. code-block:: bash
83
84 > openssl verify -verbose -CAfile expired-ca-test.crt pre-expired-example.crt
85 CN = expired-ca-test
86 error 10 at 1 depth lookup: certificate has expired
87 error pre-expired-example.crt: verification failed
88
89
90 THIS FAILED. I thought previously signed keys would continue to verify against the expired CA but new certs wouldn't be created. Instead previously signed certs won't validate against the expired CA.
91
92 Then I tried signing a new cert with the expired CA. Certainly this fail, right ?
93
94 .. code-block:: bash
95
96 > openssl genrsa -out expired-example.key 4096
97 > openssl req -new -key expired-example.key -out expired-example.csr
98 > openssl x509 -req -days 365 -in expired-example.csr -CA expired-ca-test.crt -CAkey expired-ca-test.key -CAserial expired-ca-test.srl -out expired-example.crt
99
100 THIS WORKED in that it created the cert, though verification still fails:
101
102 .. code-block:: bash
103
104 > openssl verify -verbose -CAfile expired-ca-test.crt expired-example.crt
105 CN = expired-ca-test
106 error 10 at 1 depth lookup: certificate has expired
107 error expired-example.crt: verification failed
108
109
110 Now lets see what happens if we update the CA cert with the update-ca.py script below.
111
112 This is almost the same as the new-ca.py script above, except **the original CA key is reused instead of generating a new key**. Also **the CN and serial number need to be the same as the original expired CA cert**.
113
114 Verification will fail if the CN or serial number values are not the same as the original CA, but unfortuanetly I didn't save the errors from when I tried using 'updated-ca-test' as the CN, or when I tried bumping up the serial number to 1.
115
116 .. code-block:: python
117
118 from OpenSSL import crypto
119
120 #Following script will create a self signed root ca cert.
121 from OpenSSL import crypto, SSL
122 from os.path import join
123 import random
124
125 CN='updated-ca-test'
126 pubkey = "%s.crt" % CN #replace %s with CN
127 privkey = "%s.key" % CN # replcate %s with CN
128
129 pubkey = join(".", pubkey)
130 privkey = join(".", privkey)
131
132 # Instead of creating a new key, use the old CA's key
133 # nope: k = crypto.PKey()
134 # nope: k.generate_key(crypto.TYPE_RSA, 2048)
135 st_key=open('expired-ca-test.key', 'rt').read()
136 k = crypto.load_privatekey(crypto.FILETYPE_PEM, st_key)
137
138 # create a self-signed cert
139 cert = crypto.X509()
140 cert.get_subject().CN = 'expired-ca-test' # keep the same CN as the old CA cert
141 cert.set_serial_number(0) # keep the same serial number as the old CA cert
142 cert.gmtime_adj_notBefore(0)
143 cert.gmtime_adj_notAfter(63072000) # CA is only good for 2 years
144 cert.set_issuer(cert.get_subject())
145 cert.set_subject(cert.get_subject())
146 cert.set_version(2)
147 xt = crypto.X509Extension(b'basicConstraints',1,b'CA:TRUE')
148 cert.add_extensions((xt,))
149
150 cert.set_pubkey(k)
151 cert.sign(k, 'sha512')
152 pub=crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
153 priv=crypto.dump_privatekey(crypto.FILETYPE_PEM, k)
154 open(pubkey,"wt").write(pub.decode("utf-8"))
155 open(privkey, "wt").write(priv.decode("utf-8") )
156
157 Note that this code creates a updated-ca-test.key that's the same as expired-ca-test.key, so I could have continued using expired-ca-test.key in the cert creation below.
158
159 .. code-block:: bash
160
161 > diff expired-ca-test.key updated-ca-test.key
162 > echo $?
163 0
164
165
166 Next I created an updated-ca-test.srl file. I could have continuned using expired-ca-test.srl
167
168 .. code-block:: bash
169
170 > cp expired-ca-test.srl updated-ca-test.srl
171
172 Now let's see if the new CA can be used to create a new cert:
173
174 .. code-block:: bash
175
176 > openssl genrsa -out post-expired-example.key 4096
177 > openssl req -new -key post-expired-example.key -out post-expired-example.csr
178 > openssl x509 -req -days 365 -in post-expired-example.csr -CA updated-ca-test.crt -CAkey updated-ca-test.key -CAserial updated-ca-test.srl -out post-expired-example.crt
179 > openssl x509 -in post-expired-example.crt -text
180 > openssl verify -verbose -CAfile updated-ca-test.crt post-expired-example.crt
181 post-expired-example.crt: OK
182
183 Now verify the old cert verifies using the new CA:
184
185 .. code-block:: bash
186
187 > openssl verify -verbose -CAfile updated-ca-test.crt pre-expired-example.crt
188 pre-expired-example.crt: OK
189
190 THIS WORKED. The updated CA could be used to verify both new and previous created certs Hurray !!
191
192 Conclusion
193 ==========
194
195 An expired/expiring root CA may be a hassle, but it's not catastrophic. The biggest pain should be pushingout the updated root CA everywhere the cert is being used in your environment. If you're using an orchestration/CM tool like Salt or Ansible, updating the root CA cert shouldn't be too bad, but remember to reload or restart any services using the cert to force the updated CA cert to read.
196
197 Sources
198 =======
199 - https://serverfault.com/questions/306345/certification-authority-root-certificate-expiry-and-renewal
200 - https://gist.github.com/mohanpedala/468cf9cef473a8d7610320cff730cdd1
201
202
203 .. _`created its CA`: https://github.com/jude/certmaster/blob/master/certmaster/certs.py#L92
204 .. _`waited 2 hours`: https://store.steampowered.com/app/1366540/Dyson_Sphere_Program/
205
206