Files
@ 5a918cd2502e
Branch filter:
Location: gimmecert/functional_tests/test_renew.py
5a918cd2502e
17.2 KiB
text/x-python
Noticket: Switching to development version.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 | # -*- coding: utf-8 -*-
#
# Copyright (C) 2018, 2020, 2024 Branko Majic
#
# This file is part of Gimmecert.
#
# Gimmecert is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# Gimmecert is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Gimmecert. If not, see <http://www.gnu.org/licenses/>.
#
from .base import run_command
def test_renew_command_available_with_help():
# John has been issuing server and client certificates using
# Gimmecert for a while now. The project has been in use for quite
# some time, and John has realised the certificates might be about
# to expire. Thinking how tedious it would be to generate
# everything again from scratch, he tries to figure out if there
# is an easier way to do it instead of providing information for
# all of the entities instead.
stdout, stderr, exit_code = run_command("gimmecert")
# Looking at output, John notices the renew command.
assert exit_code == 0
assert stderr == ""
assert "renew" in stdout
# He goes ahead and has a look at command invocation to check what
# kind of parameters he might need to provide.
stdout, stderr, exit_code = run_command("gimmecert", "renew", "-h")
# John can see that the command accepts two positional argument -
# type of entity, and entity name.
assert exit_code == 0
assert stderr == ""
assert stdout.startswith("usage: gimmecert renew")
assert stdout.split('\n')[3].endswith("{server,client} entity_name") # Fourth line of help (first two are options)
def test_renew_command_requires_initialised_hierarchy(tmpdir):
# John decides it's time to renew one of the certificates. He
# switches to his project directory.
tmpdir.chdir()
# John tries to renew a server certificate.
stdout, stderr, exit_code = run_command("gimmecert", "renew", "server", "myserver")
# John has forgotten to initialise the CA hierarchy from within
# this directory, and is instead presented with an error.
assert exit_code != 0
assert stdout == ""
assert stderr == "No CA hierarchy has been initialised yet. Run the gimmecert init command and issue some certificates first.\n"
# John gives the screen a weird look, and tries again, this time
# with a client certificate renewal.
stdout, stderr, exit_code = run_command("gimmecert", "renew", "client", "myclient")
# John gets presented with the same error yet again. Suddenly, he
# realizes he is in a wrong directory... Oh well...
assert exit_code != 0
assert stdout == ""
assert stderr == "No CA hierarchy has been initialised yet. Run the gimmecert init command and issue some certificates first.\n"
def test_renew_command_reports_error_if_entity_does_not_exist(tmpdir):
# John finally finds his way around to the project directory where
# Gimmecert has already been used to set-up a hierarchy, and where
# a couple of server and client certificates have been issued.
tmpdir.chdir()
run_command("gimmecert", "init")
run_command("gimmecert", "server", "someserver")
run_command("gimmecert", "client", "someclient")
# He runs the command for renewing a server certificate.
stdout, stderr, exit_code = run_command('gimmecert', 'renew', 'server', 'myserver')
# Unfortunately for him, this server certificate has not been
# issued before, and he is presented with an error.
assert exit_code != 0
assert stdout == ''
assert stderr == "Cannot renew certificate. No existing certificate found for server myserver.\n"
# This is going to be one of those days... He tries then to renew
# a client certificate instead.
stdout, stderr, exit_code = run_command('gimmecert', 'renew', 'client', 'myclient')
# To his dismay, this results in error as well. He hasn't issued
# such a certificate before either.
assert exit_code != 0
assert stdout == ''
assert stderr == "Cannot renew certificate. No existing certificate found for client myclient.\n"
def test_renew_only_certificate(tmpdir):
# At the end of his wits, John finally finds the correct project
# directory where he has previuosly set-up the CA hierarchy and
# issued a couple of certificates.
tmpdir.chdir()
run_command("gimmecert", "init")
run_command("gimmecert", "server", "myserver", "myserver.local")
run_command("gimmecert", "client", "myclient")
# He fetches some information about the existing certificates.
old_server_private_key = tmpdir.join(".gimmecert", "server", "myserver.key.pem").read()
old_server_issuer_dn, _, _ = run_command('openssl', 'x509', '-noout', '-issuer', '-in', '.gimmecert/server/myserver.cert.pem')
old_server_subject_dn, _, _ = run_command('openssl', 'x509', '-noout', '-subject', '-in', '.gimmecert/server/myserver.cert.pem')
old_server_public_key, _, _ = run_command('openssl', 'x509', '-noout', '-pubkey', '-in', '.gimmecert/server/myserver.cert.pem')
old_server_certificate_info, _, _ = run_command('openssl', 'x509', '-noout', '-text', '-in', '.gimmecert/server/myserver.cert.pem')
old_server_issuer_dn = old_server_issuer_dn.replace('issuer=', '', 1).rstrip().replace(' /CN=', 'CN = ', 1) # OpenSSL 1.0 vs 1.1 formatting
old_server_subject_dn = old_server_subject_dn.replace('subject=', '', 1).rstrip().replace(' /CN=', 'CN = ', 1) # OpenSSL 1.0 vs 1.1 formatting
old_client_private_key = tmpdir.join(".gimmecert", "client", "myclient.key.pem").read()
old_client_issuer_dn, _, _ = run_command('openssl', 'x509', '-noout', '-issuer', '-in', '.gimmecert/client/myclient.cert.pem')
old_client_subject_dn, _, _ = run_command('openssl', 'x509', '-noout', '-subject', '-in', '.gimmecert/client/myclient.cert.pem')
old_client_public_key, _, _ = run_command('openssl', 'x509', '-noout', '-pubkey', '-in', '.gimmecert/client/myclient.cert.pem')
old_client_certificate_info, _, _ = run_command('openssl', 'x509', '-noout', '-text', '-in', '.gimmecert/client/myclient.cert.pem')
old_client_issuer_dn = old_client_issuer_dn.replace('issuer=', '', 1).rstrip().replace(' /CN=', 'CN = ', 1) # OpenSSL 1.0 vs 1.1 formatting
old_client_subject_dn = old_client_subject_dn.replace('subject=', '', 1).rstrip().replace(' /CN=', 'CN = ', 1) # OpenSSL 1.0 vs 1.1 formatting
# He runs the renewal command for server certificate.
stdout, stderr, exit_code = run_command('gimmecert', 'renew', 'server', 'myserver')
# No errors are reported, and he is presented with a nice
# informative message about certificate being renewed, as well as
# paths to artifacts.
assert exit_code == 0
assert stderr == ""
assert "Renewed certificate for server myserver." in stdout
assert ".gimmecert/server/myserver.key.pem" in stdout
assert ".gimmecert/server/myserver.cert.pem" in stdout
# He does the same for the client certificate.
stdout, stderr, exit_code = run_command('gimmecert', 'renew', 'client', 'myclient')
# No errors are reported, and he is presented with a nice
# informative message about certificate being renewed, as well as
# paths to artifacts.
assert exit_code == 0
assert stderr == ""
assert "Renewed certificate for client myclient." in stdout
assert ".gimmecert/client/myclient.key.pem" in stdout
assert ".gimmecert/client/myclient.cert.pem" in stdout
# John has a look at generated certificates.
new_server_private_key = tmpdir.join(".gimmecert", "server", "myserver.key.pem").read()
new_server_issuer_dn, _, _ = run_command('openssl', 'x509', '-noout', '-issuer', '-in', '.gimmecert/server/myserver.cert.pem')
new_server_subject_dn, _, _ = run_command('openssl', 'x509', '-noout', '-subject', '-in', '.gimmecert/server/myserver.cert.pem')
new_server_public_key, _, _ = run_command('openssl', 'x509', '-noout', '-pubkey', '-in', '.gimmecert/server/myserver.cert.pem')
new_server_certificate_info, _, _ = run_command('openssl', 'x509', '-noout', '-text', '-in', '.gimmecert/server/myserver.cert.pem')
new_server_issuer_dn = new_server_issuer_dn.replace('issuer=', '', 1).rstrip().replace(' /CN=', 'CN = ', 1) # OpenSSL 1.0 vs 1.1 formatting
new_server_subject_dn = new_server_subject_dn.replace('subject=', '', 1).rstrip().replace(' /CN=', 'CN = ', 1) # OpenSSL 1.0 vs 1.1 formatting
new_client_private_key = tmpdir.join(".gimmecert", "client", "myclient.key.pem").read()
new_client_issuer_dn, _, _ = run_command('openssl', 'x509', '-noout', '-issuer', '-in', '.gimmecert/client/myclient.cert.pem')
new_client_subject_dn, _, _ = run_command('openssl', 'x509', '-noout', '-subject', '-in', '.gimmecert/client/myclient.cert.pem')
new_client_public_key, _, _ = run_command('openssl', 'x509', '-noout', '-pubkey', '-in', '.gimmecert/client/myclient.cert.pem')
new_client_certificate_info, _, _ = run_command('openssl', 'x509', '-noout', '-text', '-in', '.gimmecert/client/myclient.cert.pem')
new_client_issuer_dn = new_client_issuer_dn.replace('issuer=', '', 1).rstrip().replace(' /CN=', 'CN = ', 1) # OpenSSL 1.0 vs 1.1 formatting
new_client_subject_dn = new_client_subject_dn.replace('subject=', '', 1).rstrip().replace(' /CN=', 'CN = ', 1) # OpenSSL 1.0 vs 1.1 formatting
# John compares the values from old certificates and new
# certificates. To his delight, the same private key and naming
# have been reused, but the certificates have definitively been
# replaced.
assert new_server_private_key == old_server_private_key
assert new_server_issuer_dn == old_server_issuer_dn
assert new_server_subject_dn == old_server_subject_dn
assert new_server_public_key == old_server_public_key
assert "DNS:myserver, DNS:myserver.local\n" in new_server_certificate_info
assert new_server_certificate_info != old_server_certificate_info
assert new_client_private_key == old_client_private_key
assert new_client_issuer_dn == old_client_issuer_dn
assert new_client_subject_dn == old_client_subject_dn
assert new_client_public_key == old_client_public_key
assert new_client_certificate_info != old_client_certificate_info
# Finally, he runs a check to ensure the certificates can be
# verified using the CA certificate chain.
_, _, verify_server_error_code = run_command(
"openssl", "verify",
"-CAfile",
".gimmecert/ca/chain-full.cert.pem",
".gimmecert/server/myserver.cert.pem"
)
_, _, verify_client_error_code = run_command(
"openssl", "verify",
"-CAfile",
".gimmecert/ca/chain-full.cert.pem",
".gimmecert/client/myclient.cert.pem"
)
# He is happy to see that verification succeeds.
assert verify_server_error_code == 0
assert verify_client_error_code == 0
def test_renew_both_certificate_and_private_key(tmpdir):
# John wants to replace private keys in one of his projects for
# testing purposes to ensure the service is capable of reloading a
# new key on the fly. He switches to project directory (where he
# had previously set-up the CA hierarchy and issued some
# certificates).
tmpdir.chdir()
run_command("gimmecert", "init")
run_command("gimmecert", "server", "myserver", "myserver.local")
run_command("gimmecert", "client", "myclient")
# He is curious if there might be some built-in option to perform
# this action. He has a look at the renew command CLI help.
stdout, stderr, exit_code = run_command("gimmecert", "renew", "-h")
# He notices the option for generating a new private key.
assert exit_code == 0
assert stderr == ""
assert "--new-private-key, -p\n" in stdout
# Before proceeding, John has a quick look at the existing private
# keys and certificats.
old_server_private_key = tmpdir.join(".gimmecert", "server", "myserver.key.pem").read()
old_server_certificate = tmpdir.join(".gimmecert", "server", "myserver.cert.pem").read()
old_client_private_key = tmpdir.join(".gimmecert", "client", "myclient.key.pem").read()
old_client_certificate = tmpdir.join(".gimmecert", "client", "myclient.cert.pem").read()
# He runs the renewal command for server certificate, requesting
# the private key to be regenerated.
stdout, stderrr, exit_code = run_command("gimmecert", "renew", "--new-private-key", "server", "myserver")
# No errors are reported, and he is informed that both private key
# and certificate have been renewed.
assert exit_code == 0
assert stderr == ""
assert "Generated new private key and renewed certificate for server myserver." in stdout
# He runs the same command for the client entity.
stdout, stderrr, exit_code = run_command("gimmecert", "renew", "--new-private-key", "client", "myclient")
# No errors are reported, and he is informed that both private key
# and certificate have been renewed.
assert exit_code == 0
assert stderr == ""
assert "Generated new private key and renewed certificate for client myclient." in stdout
# John has a quick peek at the newly generated artifacts.
new_server_private_key = tmpdir.join(".gimmecert", "server", "myserver.key.pem").read()
new_server_certificate = tmpdir.join(".gimmecert", "server", "myserver.cert.pem").read()
new_client_private_key = tmpdir.join(".gimmecert", "client", "myclient.key.pem").read()
new_client_certificate = tmpdir.join(".gimmecert", "client", "myclient.cert.pem").read()
# Seems like both private key and certificate have been replaced
# for both server and client.
assert old_server_private_key != new_server_private_key
assert old_server_certificate != new_server_certificate
assert old_client_private_key != new_client_private_key
assert old_client_certificate != new_client_certificate
# Finally, he runs a check to ensure the certificates can be
# verified using the CA certificate chain.
_, _, verify_server_error_code = run_command(
"openssl", "verify",
"-CAfile",
".gimmecert/ca/chain-full.cert.pem",
".gimmecert/server/myserver.cert.pem"
)
_, _, verify_client_error_code = run_command(
"openssl", "verify",
"-CAfile",
".gimmecert/ca/chain-full.cert.pem",
".gimmecert/client/myclient.cert.pem"
)
# He is happy to see that verification succeeds.
assert verify_server_error_code == 0
assert verify_client_error_code == 0
def test_renew_update_dns_option(tmpdir):
# John is in a bit of a rush to get his project going. Since he
# needs a server certificate issued, he goes ahead and quickly
# initialises CA and issues a server certificate, with intention
# of accessing the service via URL https://myservice.example.com/.
tmpdir.chdir()
run_command("gimmecert", "init")
run_command("gimmecert", "server", "myserver1", "mysercive.example.com")
# Once he imports the CA certificate into his browser, and tries
# to access the service page, he very quickly finds out that he
# has misspelled "myservice". Just to be on the safe side, he has
# a look at the certificate using the OpenSSL CLI.
stdout, stderr, exit_code = run_command('openssl', 'x509', '-noout', '-text', '-in', '.gimmecert/server/myserver1.cert.pem')
# And indeed, in addition to his server name, he can see that the
# extra DNS subject alternative name he provided is wrong.
assert "DNS:myserver1," in stdout
assert "DNS:mysercive.example.com\n" in stdout
assert "DNS:myservice.example.com" not in stdout
# Since he wants to just replace the certificate, while preserving
# the private key, John figures he needs to renew his certificate
# somehow, and update the DNS names while doing so. He takes a
# quick look at help for the renew command.
stdout, stderr, exit_code = run_command("gimmecert", "renew", "-h")
# He notices there is an option for updating DNS subject
# alternative names.
assert " --update-dns-names DNS_NAMES" in stdout
assert " -u DNS_NAMES\n" in stdout
# Based on help description, this seems to be exactly what he
# needs.He goes ahead and runs the renewal command, specifying
# correct DNS subject alternative names.
stdout, stderr, exit_code = run_command("gimmecert", "renew", "server", "--update-dns-names", "myservice.example.com", "myserver1")
# He notices that no error has been reported by the command, and
# that he is informed that the certificate has been renewed with
# new DNS names, while the private key has been preserved.
assert exit_code == 0
assert "subject alternative names have been updated" in stdout
# Being paranoid, he decides to double-check the certificate, just
# to be on the safe side. He uses the OpenSSL CLI for this
# purpose.
stdout, stderr, exit_code = run_command('openssl', 'x509', '-noout', '-text', '-in', '.gimmecert/server/myserver1.cert.pem')
# He notices that certificate includes the intended naming. He can
# finally move ahead with his project.
assert "DNS:myserver1," in stdout
assert "DNS:myservice.example.com\n" in stdout
|