diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f8f3ff6..c91decf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, '3.10', '3.11'] + python-version: [3.8, 3.9, '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} @@ -51,7 +51,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, '3.10', '3.11'] + python-version: [3.8, 3.9, '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} @@ -77,7 +77,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, '3.10', '3.11'] + python-version: [3.8, 3.9, '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/one_intermediate/.gitignore b/one_intermediate/.gitignore new file mode 100644 index 0000000..5d95d5b --- /dev/null +++ b/one_intermediate/.gitignore @@ -0,0 +1 @@ +intermediate_ca*/ diff --git a/one_intermediate/Makefile b/one_intermediate/Makefile new file mode 100644 index 0000000..23f8a8f --- /dev/null +++ b/one_intermediate/Makefile @@ -0,0 +1 @@ +include ../common.mk diff --git a/one_intermediate/README.md b/one_intermediate/README.md new file mode 100644 index 0000000..9f6a2a1 --- /dev/null +++ b/one_intermediate/README.md @@ -0,0 +1,63 @@ +# Chained (With One Intermediate) Certificates + +This tls-gen variation generates a root CA, one intermediary CA and two +certificate/key pairs signed by the intermediate CA: + + * Chain 1: root CA => intermediate 1 => client certificate/key pair + * Chain 2: root CA => intermediate 1 => server certificate/key pair + +## Generating + +``` shell +# pass a password using the PASSWORD env variable +make +# results will be under the ./result directory +ls -lha ./result +``` + +Generated CA certificate as well as client and server certificate and private keys will be +under the `result` directory. + +It possible to use [ECC][ecc-intro] for intermediate and leaf keys: + +``` +# pass a private key password using the PASSWORD variable if needed +make USE_ECC=true ECC_CURVE="prime256v1" +# results will be under the ./result directory +ls -lha ./result +``` + +The list of available curves can be obtained with + +``` shell +openssl ecparam -list_curves +``` + +### Regeneration + +To regenerate, use + +``` shell +# pass a private key password using the PASSWORD variable if needed +make regen +``` + +The `regen` target accepts the same variables as `gen` (default target) above. + +### Verification + +You can verify the generated client and server certificates against the generated CA one with + +``` shell +make verify +``` + +## Certificate Information + +To display client and server certificate information, use + +``` shell +make info +``` + +This assumes the certificates were previously generated. diff --git a/one_intermediate/openssl.cnf b/one_intermediate/openssl.cnf new file mode 100644 index 0000000..5c7a990 --- /dev/null +++ b/one_intermediate/openssl.cnf @@ -0,0 +1,94 @@ +# Copied over from the Basic profile +# Note: LibreSSL 2.2.7 does not correctly support environment variables +# here and that is the version that ships with OS X High Sierra. So, we +# replace text using Python and generate a temporary cnf file + +common_name = @COMMON_NAME@ +client_alt_name = @CLIENT_ALT_NAME@ +server_alt_name = @SERVER_ALT_NAME@ + +[ ca ] +default_ca = test_root_ca + +[ test_root_ca ] +root_ca_dir = testca + +certificate = $root_ca_dir/cacert.pem +database = $root_ca_dir/index.txt +new_certs_dir = $root_ca_dir/certs +private_key = $root_ca_dir/private/cakey.pem +serial = $root_ca_dir/serial + +default_crl_days = 7 +default_days = 1825 +default_md = sha256 + +policy = test_root_ca_policy +x509_extensions = certificate_extensions + +[ test_root_ca_policy ] +commonName = supplied +stateOrProvinceName = optional +countryName = optional +emailAddress = optional +organizationName = optional +organizationalUnitName = optional +domainComponent = optional + +[ certificate_extensions ] +basicConstraints = CA:false + +[ req ] +default_bits = 4096 +default_md = sha256 +prompt = yes +distinguished_name = root_ca_distinguished_name +x509_extensions = root_ca_extensions + +[ root_ca_distinguished_name ] +commonName = hostname + +[ root_ca_extensions ] +basicConstraints = critical,CA:true +keyUsage = keyCertSign, cRLSign +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer + +[ ca_extensions ] +basicConstraints = critical,CA:true,pathlen:0 +keyUsage = keyCertSign, cRLSign +# nameConstraints = critical,@name_constraints + +[ client_extensions ] +basicConstraints = CA:false +keyUsage = digitalSignature,keyEncipherment +extendedKeyUsage = clientAuth +subjectAltName = @client_alt_names + +[ server_extensions ] +basicConstraints = CA:false +keyUsage = digitalSignature,keyEncipherment +extendedKeyUsage = serverAuth +subjectAltName = @server_alt_names +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer + +[ client_alt_names ] +DNS.1 = $common_name +DNS.2 = $client_alt_name +DNS.3 = localhost +# examples of more Subject Alternative Names +# DNS.4 = guest +# email = guest@warp10.local +# URI = amqps://123.client.warp10.local +# otherName = 1.3.6.1.4.1.54392.5.436;FORMAT:UTF8,UTF8String:other-username + +[ server_alt_names ] +DNS.1 = $common_name +DNS.2 = $server_alt_name +DNS.3 = localhost + +[ name_constraints ] +permitted;DNS.1 = .your.domain.name +permitted;DNS.2 = $server_alt_name +permitted;DNS.3 = localhost diff --git a/one_intermediate/profile.py b/one_intermediate/profile.py new file mode 100644 index 0000000..d80e1a0 --- /dev/null +++ b/one_intermediate/profile.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2007-2014 VMware, Inc. or its affiliates. All rights reserved. +# Copyright (c) 2014-2020 Michael Klishin and contributors. +# Copyright (c) 2022 VMware, Inc. or its affiliates. All rights reserved. + +import sys +import os +import shutil +from subprocess import call + + +def _copy_artifacts_to_results(): + os.makedirs(p.relative_path("result"), exist_ok=True) + g.copy_root_ca_certificate_and_key_pair() + g.copy_leaf_certificate_and_key_pair("server") + g.copy_leaf_certificate_and_key_pair("client") + + +def _concat_certificates(): + # concat [intermediate CA 1] [root CA] > full_chain + print("Will concatenate all CA certificates into {}".format(p.result_chained_certificate_path())) + chain_file = open(p.result_chained_certificate_path(), "w") + call(["cat", + p.intermediate_ca_certificate_path("1"), + p.root_ca_certificate_path()], + stdout=chain_file) + chain_file.close() + + +def generate(opts): + cli.validate_password_if_provided(opts) + print("Will generate a root CA and two certificate/key pairs (server and client)") + g.generate_root_ca(opts) + print("Will generate intermediate CA signed by the root CA") + g.generate_intermediate_ca(opts, + parent_certificate_path=p.root_ca_certificate_path(), + parent_key_path=p.root_ca_key_path(), + suffix="1") + print("Will generate server certificate/key pair signed by the intermediate CA") + g.generate_server_certificate_and_key_pair(opts, + parent_certificate_path=p.intermediate_ca_certificate_path("1"), + parent_key_path=p.intermediate_ca_key_path("1")) + print("Will generate client certificate/key pair signed by the intermediate CA") + g.generate_client_certificate_and_key_pair(opts, + parent_certificate_path=p.intermediate_ca_certificate_path("1"), + parent_key_path=p.intermediate_ca_key_path("1")) + _copy_artifacts_to_results() + _concat_certificates() + print("Done! Find generated certificates and private keys under ./result!") + + +def clean(opts): + for s in [p.root_ca_path(), + p.intermediate_ca_path("1"), + p.result_path(), + p.leaf_pair_path("server"), + p.leaf_pair_path("client")]: + print("Removing {}".format(s)) + try: + shutil.rmtree(s) + except FileNotFoundError: + pass + + +def regenerate(opts): + clean(opts) + generate(opts) + + +def verify(opts): + print("Will verify generated certificates against the CA certificate chain...") + v.verify_leaf_certificate_against_ca_chain("client") + v.verify_leaf_certificate_against_ca_chain("server") + + +def info(opts): + i.leaf_certificate_info("client") + i.leaf_certificate_info("server") + + +def alias_leaf_artifacts(opts): + print("This command is not supported by this profile") + + +commands = {"generate": generate, + "gen": generate, + "clean": clean, + "regenerate": regenerate, + "regen": regenerate, + "verify": verify, + "info": info, + "alias-leaf-artifacts": alias_leaf_artifacts} + +if __name__ == "__main__": + sys.path.append(os.path.realpath('..')) + import tls_gen.cli as cli + import tls_gen.gen as g + import tls_gen.info as i + import tls_gen.paths as p + import tls_gen.verify as v + cli.run(commands) diff --git a/separate_intermediates/.gitignore b/separate_intermediates/.gitignore index 281a137..dd3cb6a 100644 --- a/separate_intermediates/.gitignore +++ b/separate_intermediates/.gitignore @@ -1,2 +1 @@ -root_ca/openssl.cnf -intermediate_*_ca +intermediate_*/ diff --git a/two_shared_intermediates/.gitignore b/two_shared_intermediates/.gitignore index 50ee160..5d95d5b 100644 --- a/two_shared_intermediates/.gitignore +++ b/two_shared_intermediates/.gitignore @@ -1,3 +1 @@ -root_ca/openssl.cnf -intermediate_ca1/openssl.cnf -intermediate_ca2/openssl.cnf +intermediate_ca*/