diff --git a/tests/certs/client.cert.pem b/tests/certs/client.cert.pem index b6d9a91a..af49e39b 100644 --- a/tests/certs/client.cert.pem +++ b/tests/certs/client.cert.pem @@ -1,24 +1,29 @@ -----BEGIN CERTIFICATE----- -MIIEAzCCAuugAwIBAgIUPfej8IQ/5bCrihqWImrq2vKPOq0wDQYJKoZIhvcNAQEL -BQAwgaMxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdU -b3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsMDWFzeW5j -cGcgdGVzdHMxHzAdBgNVBAMMFmFzeW5jcGcgdGVzdCBjbGllbnQgQ0ExHTAbBgkq -hkiG9w0BCQEWDmhlbGxvQG1hZ2ljLmlvMB4XDTIxMDgwOTIxNTA1MloXDTMyMDEw -NDIxNTA1MlowgZUxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYD -VQQHDAdUb3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsM -DWFzeW5jcGcgdGVzdHMxETAPBgNVBAMMCHNzbF91c2VyMR0wGwYJKoZIhvcNAQkB -Fg5oZWxsb0BtYWdpYy5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AJjiP9Ik/KRRLK9GMvoH8m1LO+Gyrr8Gz36LpmKJMR/PpwTL+1pOkYSGhOyT3Cw9 -/kWWLJRCvYqKgFtYtbr4S6ReGm3GdSVW+sfVRYDrRQZLPgQSPeq25g2v8UZ63Ota -lPAyUPUZKpxyWz8PL77lV8psb9yv14yBH2kv9BbxKPksWOU8p8OCn1Z3WFFl0ItO -nzMvCp5os+xFrt4SpoRGTx9x4QleY+zrEsYZtmnV4wC+JuJkNw4fuCdrX5k7dghs -uZkcsAZof1nMdYsYiazeDfQKZtJqh5kO7mpwvCudKUWaLJJUwiQA87BwSlnCd/Hh -TZDbC+zeFNjTS49/4Q72xVECAwEAAaM7MDkwHwYDVR0jBBgwFoAUi1jMmAisuOib -mHIE2n0W2WnnaL0wCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwDQYJKoZIhvcNAQEL -BQADggEBACbnp5oOp639ko4jn8axF+so91k0vIcgwDg+NqgtSRsuAENGumHAa8ec -YOks0TCTvNN5E6AfNSxRat5CyguIlJ/Vy3KbkkFNXcCIcI/duAJvNphg7JeqYlQM -VIJhrO/5oNQMzzTw8XzTHnciGbrbiZ04hjwrruEkvmIAwgQPhIgq4H6umTZauTvk -DEo7uLm7RuG9hnDyWCdJxLLljefNL/EAuDYpPzgTeEN6JAnOu0ULIbpxpJKiYEId -8I0U2n0I2NTDOHmsAJiXf8BiHHmpK5SXFyY9s2ZuGkCzvmeZlR81tTXmHZ3v1X2z -8NajoAZfJ+QD50DrbF5E00yovZbyIB4= +MIIE7DCCA9SgAwIBAgIJAMXPbgYRbS6PMA0GCSqGSIb3DQEBCwUAMIGjMQswCQYD +VQQGEwJDQTEQMA4GA1UECAwHT250YXJpbzEQMA4GA1UEBwwHVG9yb250bzEYMBYG +A1UECgwPTWFnaWNTdGFjayBJbmMuMRYwFAYDVQQLDA1hc3luY3BnIHRlc3RzMR8w +HQYDVQQDDBZhc3luY3BnIHRlc3QgY2xpZW50IENBMR0wGwYJKoZIhvcNAQkBFg5o +ZWxsb0BtYWdpYy5pbzAeFw0yNjA5MjAxNTQ2MTNaFw00NTExMjAxNTQ2MTNaMIGV +MQswCQYDVQQGEwJDQTEQMA4GA1UECAwHT250YXJpbzEQMA4GA1UEBwwHVG9yb250 +bzEYMBYGA1UECgwPTWFnaWNTdGFjayBJbmMuMRYwFAYDVQQLDA1hc3luY3BnIHRl +c3RzMREwDwYDVQQDDAhzc2xfdXNlcjEdMBsGCSqGSIb3DQEJARYOaGVsbG9AbWFn +aWMuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCY4j/SJPykUSyv +RjL6B/JtSzvhsq6/Bs9+i6ZiiTEfz6cEy/taTpGEhoTsk9wsPf5FliyUQr2KioBb +WLW6+EukXhptxnUlVvrH1UWA60UGSz4EEj3qtuYNr/FGetzrWpTwMlD1GSqccls/ +Dy++5VfKbG/cr9eMgR9pL/QW8Sj5LFjlPKfDgp9Wd1hRZdCLTp8zLwqeaLPsRa7e +EqaERk8fceEJXmPs6xLGGbZp1eMAvibiZDcOH7gna1+ZO3YIbLmZHLAGaH9ZzHWL +GIms3g30CmbSaoeZDu5qcLwrnSlFmiySVMIkAPOwcEpZwnfx4U2Q2wvs3hTY00uP +f+EO9sVRAgMBAAGjggEtMIIBKTALBgNVHQ8EBAMCBaAwDAYDVR0TAQH/BAIwADAT +BgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQUrN4T5/0XSalNz7t/TFsyKBDp +Z/0wgdcGA1UdIwSBzzCBzIAUi1jMmAisuOibmHIE2n0W2WnnaL2hgamkgaYwgaMx +CzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdUb3JvbnRv +MRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsMDWFzeW5jcGcgdGVz +dHMxHzAdBgNVBAMMFmFzeW5jcGcgdGVzdCBjbGllbnQgQ0ExHTAbBgkqhkiG9w0B +CQEWDmhlbGxvQG1hZ2ljLmlvgghiZ4DNR5q6XDANBgkqhkiG9w0BAQsFAAOCAQEA +V2yzmJzxZUVYDOLkWdMI7w+easy8387xNDqVMoSMagnyoeJ1Qgwh1F7W8xYJ+XEz +mfI2FJVWOQtOfp0Uib1l0M5ysZr9iKiJ+z0daiw8l/9ru3U0D+UiGUqQcR9v8wpo +1o9gg+sDuMASd+3TwQHw61N6cs5J0hp8w2lJa8QmPWcnfEDwRWlaIbIPY9dRtSZ5 +geUEk7sj9XIN2gGeCQ4z1wUv/ZPJ8pCsHlam4KUt39Kx8FTUIKKlJrxQjrEW6yeY +DlbEdrGL1JpE80zBXGyGUQYXC76MMr0yDb70BFiGCNQrKdJeFe9zCl8N8Uq8IImg +URBnwi1RJjdumfMw1gC1Ow== -----END CERTIFICATE----- diff --git a/tests/certs/client_ca.cert.pem b/tests/certs/client_ca.cert.pem index 17d3f357..db0d2ab0 100644 --- a/tests/certs/client_ca.cert.pem +++ b/tests/certs/client_ca.cert.pem @@ -1,25 +1,25 @@ -----BEGIN CERTIFICATE----- -MIIEKTCCAxGgAwIBAgIUKmL8tfNS9LIB6GLB9RpZpTyk3uIwDQYJKoZIhvcNAQEL -BQAwgaMxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdU -b3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsMDWFzeW5j -cGcgdGVzdHMxHzAdBgNVBAMMFmFzeW5jcGcgdGVzdCBjbGllbnQgQ0ExHTAbBgkq -hkiG9w0BCQEWDmhlbGxvQG1hZ2ljLmlvMB4XDTIxMDgwOTIxNDQxM1oXDTQxMDgw -NDIxNDQxM1owgaMxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYD -VQQHDAdUb3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsM -DWFzeW5jcGcgdGVzdHMxHzAdBgNVBAMMFmFzeW5jcGcgdGVzdCBjbGllbnQgQ0Ex -HTAbBgkqhkiG9w0BCQEWDmhlbGxvQG1hZ2ljLmlvMIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAptRYfxKiWExfZguQDva53bIqYa4lJwZA86Qu0peBUcsd -E6zyHNgVv4XSMim1FH12KQ4KPKuQAcVqRMCRAHqB96kUfWQqF//fLajr0umdzcbx -+UTgNux8TkScTl9KNAxhiR/oOGbKFcNSs4raaG8puwwEN66uMhoKk2pN2NwDVfHa -bTekJ3jouTcTCnqCynx4qwI4WStJkuW4IPCmDRVXxOOauT7YalElYLWYtAOqGEvf -noDK2Imhc0h6B5XW8nI54rVCXWwhW1v3RLAJGP+LwSy++bf08xmpHXdKkAj5BmUO -QwJRiJ33Xa17rmi385egx8KpqV04YEAPdV1Z4QM6PQIDAQABo1MwUTAdBgNVHQ4E -FgQUi1jMmAisuOibmHIE2n0W2WnnaL0wHwYDVR0jBBgwFoAUi1jMmAisuOibmHIE -2n0W2WnnaL0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAifNE -ZLZXxECp2Sl6jCViZxgFf2+OHDvRORgI6J0heckYyYF/JHvLaDphh6TkSJAdT6Y3 -hAb7jueTMI+6RIdRzIjTKCGdJqUetiSfAbnQyIp2qmVqdjeFoXTvQL7BdkIE+kOW -0iomMqDB3czTl//LrgVQCYqKM0D/Ytecpg2mbshLfpPxdHyliCJcb4SqfdrDnKoV -HUduBjOVot+6bkB5SEGCrrB4KMFTzbAu+zriKWWz+uycIyeVMLEyhDs59vptOK6e -gWkraG43LZY3cHPiVeN3tA/dWdyJf9rgK21zQDSMB8OSH4yQjdQmkkvRQBjp3Fcy -w2SZIP4o9l1Y7+hMMw== +MIIEKjCCAxKgAwIBAgIIYmeAzUeaulwwDQYJKoZIhvcNAQELBQAwgaMxCzAJBgNV +BAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdUb3JvbnRvMRgwFgYD +VQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsMDWFzeW5jcGcgdGVzdHMxHzAd +BgNVBAMMFmFzeW5jcGcgdGVzdCBjbGllbnQgQ0ExHTAbBgkqhkiG9w0BCQEWDmhl +bGxvQG1hZ2ljLmlvMB4XDTI2MDkyMDE1NDYxM1oXDTQ1MTEyMDE1NDYxM1owgaMx +CzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdUb3JvbnRv +MRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsMDWFzeW5jcGcgdGVz +dHMxHzAdBgNVBAMMFmFzeW5jcGcgdGVzdCBjbGllbnQgQ0ExHTAbBgkqhkiG9w0B +CQEWDmhlbGxvQG1hZ2ljLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAptRYfxKiWExfZguQDva53bIqYa4lJwZA86Qu0peBUcsdE6zyHNgVv4XSMim1 +FH12KQ4KPKuQAcVqRMCRAHqB96kUfWQqF//fLajr0umdzcbx+UTgNux8TkScTl9K +NAxhiR/oOGbKFcNSs4raaG8puwwEN66uMhoKk2pN2NwDVfHabTekJ3jouTcTCnqC +ynx4qwI4WStJkuW4IPCmDRVXxOOauT7YalElYLWYtAOqGEvfnoDK2Imhc0h6B5XW +8nI54rVCXWwhW1v3RLAJGP+LwSy++bf08xmpHXdKkAj5BmUOQwJRiJ33Xa17rmi3 +85egx8KpqV04YEAPdV1Z4QM6PQIDAQABo2AwXjAPBgNVHRMBAf8EBTADAQH/MAsG +A1UdDwQEAwIBBjAdBgNVHQ4EFgQUi1jMmAisuOibmHIE2n0W2WnnaL0wHwYDVR0j +BBgwFoAUi1jMmAisuOibmHIE2n0W2WnnaL0wDQYJKoZIhvcNAQELBQADggEBAEI4 +hrs6eAd+AD1GwU1GQpuwOGYPlwKHAIYgpDkNi9UTF3munLnVIZffxeq8z9e31kxo +/1CJYanG3ajupBxj/+wRWKQkf9mWLo8N9fmEWC1k9Xp8r9WyupnfSRILZh/aEvSW +pkbJAwI9xB+fD8L225+wQKwR0XT//uqtlzIyxLSMMwZXAfr9p3LlMvPG/xwfywg+ +SiFIIBIUM64f4sH6wDZWpGeORdI3BlPo/fiZgvZiI6VJv16Kq4eU0KwMsGeFE5ON +99vSDNKqrLqahEv9vigDnCAWus9IN5Cap3xA7Q2C4naDB87y6JSIv+kuVreiuf5x +qzhNr5p37kx+78yclOQ= -----END CERTIFICATE----- diff --git a/tests/certs/gen.py b/tests/certs/gen.py index 19203840..5e5e89bf 100644 --- a/tests/certs/gen.py +++ b/tests/certs/gen.py @@ -1,3 +1,4 @@ +import argparse import datetime import os @@ -9,18 +10,30 @@ from cryptography.x509 import oid -def _new_cert(issuer=None, is_issuer=False, serial_number=None, **subject): - backend = backends.default_backend() - private_key = rsa.generate_private_key( - public_exponent=65537, key_size=4096, backend=backend - ) - public_key = private_key.public_key() - subject = x509.Name( +def _new_subject(**subject): + return x509.Name( [ x509.NameAttribute(getattr(oid.NameOID, key.upper()), value) for key, value in subject.items() ] ) + + +def _new_cert( + issuer=None, + is_issuer=False, + is_client=False, + private_key=None, + serial_number=None, + **subject, +): + backend = backends.default_backend() + if private_key is None: + private_key = rsa.generate_private_key( + public_exponent=65537, key_size=4096, backend=backend + ) + public_key = private_key.public_key() + subject = _new_subject(**subject) builder = ( x509.CertificateBuilder() .subject_name(subject) @@ -85,6 +98,11 @@ def _new_cert(issuer=None, is_issuer=False, serial_number=None, **subject): ) ) else: + extended_key_usage = ( + oid.ExtendedKeyUsageOID.CLIENT_AUTH + if is_client + else oid.ExtendedKeyUsageOID.SERVER_AUTH + ) builder = ( builder.add_extension( x509.KeyUsage( @@ -105,11 +123,7 @@ def _new_cert(issuer=None, is_issuer=False, serial_number=None, **subject): critical=True, ) .add_extension( - x509.ExtendedKeyUsage([oid.ExtendedKeyUsageOID.SERVER_AUTH]), - critical=False, - ) - .add_extension( - x509.SubjectAlternativeName([x509.DNSName("localhost")]), + x509.ExtendedKeyUsage([extended_key_usage]), critical=False, ) .add_extension( @@ -121,6 +135,11 @@ def _new_cert(issuer=None, is_issuer=False, serial_number=None, **subject): critical=False, ) ) + if not is_client: + builder = builder.add_extension( + x509.SubjectAlternativeName([x509.DNSName("localhost")]), + critical=False, + ) certificate = builder.sign( private_key=signing_key, algorithm=hashes.SHA256(), @@ -129,13 +148,12 @@ def _new_cert(issuer=None, is_issuer=False, serial_number=None, **subject): return certificate, private_key -def _write_cert(path, cert_key_pair, password=None): - certificate, private_key = cert_key_pair +def _write_key(path, private_key, password=None): if password: encryption = serialization.BestAvailableEncryption(password) else: encryption = serialization.NoEncryption() - with open(path + ".key.pem", "wb") as f: + with open(path, "wb") as f: f.write( private_key.private_bytes( encoding=serialization.Encoding.PEM, @@ -143,6 +161,23 @@ def _write_cert(path, cert_key_pair, password=None): encryption_algorithm=encryption, ) ) + + +def _load_key(path, password=None): + try: + with open(path, "rb") as f: + return serialization.load_pem_private_key( + f.read(), + password=password, + backend=backends.default_backend(), + ) + except FileNotFoundError: + return None + + +def _write_cert(path, cert_key_pair, password=None): + certificate, private_key = cert_key_pair + _write_key(path + ".key.pem", private_key, password) with open(path + ".cert.pem", "wb") as f: f.write( certificate.public_bytes( @@ -151,22 +186,50 @@ def _write_cert(path, cert_key_pair, password=None): ) -def new_ca(path, **subject): - cert_key_pair = _new_cert(is_issuer=True, **subject) +def new_ca(path, private_key=None, **subject): + cert_key_pair = _new_cert( + is_issuer=True, + private_key=private_key, + **subject, + ) _write_cert(path, cert_key_pair) return cert_key_pair def new_cert( - path, ca_cert_key_pair, password=None, is_issuer=False, **subject + path, + ca_cert_key_pair, + password=None, + is_issuer=False, + is_client=False, + private_key=None, + **subject, ): cert_key_pair = _new_cert( - issuer=ca_cert_key_pair, is_issuer=is_issuer, **subject + issuer=ca_cert_key_pair, + is_issuer=is_issuer, + is_client=is_client, + private_key=private_key, + **subject, ) _write_cert(path, cert_key_pair, password) return cert_key_pair +def new_csr(path, private_key, **subject): + request = ( + x509.CertificateSigningRequestBuilder() + .subject_name(_new_subject(**subject)) + .sign( + private_key, + hashes.SHA256(), + backends.default_backend(), + ) + ) + with open(path + ".csr.pem", "wb") as f: + f.write(request.public_bytes(serialization.Encoding.PEM)) + + def new_crl(path, issuer, cert): issuer_cert, signing_key = issuer revoked_cert = ( @@ -187,9 +250,15 @@ def new_crl(path, issuer, cert): f.write(crl.public_bytes(encoding=serialization.Encoding.PEM)) -def main(): +def main(*, rotate_keys=False): + def existing_key(path): + if rotate_keys: + return None + return _load_key(path) + ca = new_ca( "ca", + private_key=existing_key("ca.key.pem"), country_name="CA", state_or_province_name="Ontario", locality_name="Toronto", @@ -201,6 +270,7 @@ def main(): server = new_cert( "server", ca, + private_key=existing_key("server.key.pem"), country_name="CA", state_or_province_name="Ontario", organization_name="MagicStack Inc.", @@ -211,6 +281,43 @@ def main(): ) new_crl('server', ca, server) + client_ca = new_ca( + "client_ca", + private_key=existing_key("client_ca.key.pem"), + country_name="CA", + state_or_province_name="Ontario", + locality_name="Toronto", + organization_name="MagicStack Inc.", + organizational_unit_name="asyncpg tests", + common_name="asyncpg test client CA", + email_address="hello@magic.io", + ) + client_subject = dict( + country_name="CA", + state_or_province_name="Ontario", + locality_name="Toronto", + organization_name="MagicStack Inc.", + organizational_unit_name="asyncpg tests", + common_name="ssl_user", + email_address="hello@magic.io", + ) + client = new_cert( + "client", + client_ca, + is_client=True, + private_key=existing_key("client.key.pem"), + **client_subject, + ) + new_csr("client", client[1], **client_subject) + _write_key("client.key.protected.pem", client[1], b"secRet") + if __name__ == "__main__": - main() + parser = argparse.ArgumentParser() + parser.add_argument( + "--rotate-keys", + action="store_true", + help="generate new private keys instead of reusing existing key files", + ) + args = parser.parse_args() + main(rotate_keys=args.rotate_keys)