0

I'm trying to set-up a certificate chain (CA root -> Intermediate CA -> Server certificate)

Here's my attempt:

Creating CA Root:

##!/usr/bin/env bash
set -xeuo pipefail

CA_DIR=server/generated/ca-root REQ_DIR=server/requests

mkdir -p $CA_DIR touch $CA_DIR/ca.index openssl rand -hex 16 > $CA_DIR/ca.serial

mkdir "$CA_DIR/certs" "$CA_DIR/newcerts" "$CA_DIR/private"

openssl genpkey
-algorithm RSA -aes-256-cbc
-pkeyopt rsa_keygen_bits:4096
-out "$CA_DIR/private/ca.key.pem" chmod 400 "$CA_DIR/private/ca.key.pem"

openssl req
-new
-config "$CA_DIR/ca.conf"
-key "$CA_DIR/private/ca.key.pem"
-x509 -days 365
-out "$CA_DIR/certs/ca.cert.pem"

openssl x509
-outform PEM
-in "$CA_DIR/certs/ca.cert.pem"
-out "$CA_DIR/ca.pem"

With server/generated/ca-root/ca.conf

[ req ]
default_bits = 4096
encrypt_key = yes
default_md = sha256
string_mask = utf8only
utf8 = yes
prompt = no
x509_extensions = x509_ext
distinguished_name = distinguished_name

[ x509_ext ] basicConstraints = critical, CA:true nameConstraints = critical, @name_constraints subjectKeyIdentifier = hash issuerAltName = issuer:copy authorityKeyIdentifier = keyid:always, issuer:always

keyUsage = digitalSignature, keyCertSign, cRLSign

[ distinguished_name ] stateOrProvinceName = France postalCode = Lyon organizationalUnitName = A-Team organizationName = Black localityName = Lyon countryName = FR commonName = Barracuda Root Certificate Authority

[ ca ] default_ca = CA_default

[ CA_default ] base_dir = server/generated/ca-root database = $base_dir/ca.index serial = $base_dir/ca.serial certs = $base_dir/certs new_certs_dir = $base_dir/newcerts default_md = sha256 default_days = 365 email_in_dn = no copy_extensions = copy uniqueSubject = no

policy = root_ca_policy private_key = $base_dir/private/ca.key.pem certificate = $base_dir/certs/ca.cert.pem

[ root_ca_policy ] countryName = match stateOrProvinceName = match localityName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional

[ name_constraints ] permitted;DNS.0 = srv.local

Then, the intermediate CA:

##!/usr/bin/env bash
set -xeuo pipefail

CA_ROOT_DIR=server/generated/ca-root CA_PARENT_DIR=server/generated CA_DIRNAME=ca-intermediate CA_DIR=$CA_PARENT_DIR/$CA_DIRNAME REQ_DIR=server/requests

mkdir -p $CA_DIR touch $CA_DIR/ca.index openssl rand -hex 16 > $CA_DIR/ca.serial

mkdir "$CA_DIR/certs" "$CA_DIR/newcerts" "$CA_DIR/private"

openssl genpkey
-algorithm RSA -aes-256-cbc
-pkeyopt rsa_keygen_bits:4096
-out "$CA_DIR/private/ca.key.pem" chmod 400 "$CA_DIR/private/ca.key.pem"

openssl req
-new
-key "$CA_DIR/private/ca.key.pem"
-config "$CA_DIR/ca.conf"
-out "$CA_DIR/certs/ca.csr.pem"

openssl ca
-batch
-config "$CA_DIR/sign-ca.conf"
-notext
-in "$CA_DIR/certs/ca.csr.pem"
-out "$CA_DIR/certs/ca.cert.pem"

openssl x509
-outform PEM
-in "$CA_DIR/certs/ca.cert.pem"
-out "$CA_DIR/ca.pem"

CHAIN_FILE=ca-chain.pem cat "$CA_DIR/certs/ca.cert.pem" "$CA_ROOT_DIR/certs/ca.cert.pem" > "$CHAIN_FILE"

With server/generated/ca-intermediate/ca.conf:

[ req ]
default_bits = 4096
encrypt_key = yes
default_md = sha256
string_mask = utf8only
utf8 = yes
prompt = no
x509_extensions = x509_ext
distinguished_name = distinguished_name

[ x509_ext ] basicConstraints = critical, CA:true nameConstraints = critical, @name_constraints subjectKeyIdentifier = hash issuerAltName = issuer:copy authorityKeyIdentifier = keyid:always, issuer:always

keyUsage = digitalSignature, keyCertSign, cRLSign

[ distinguished_name ] stateOrProvinceName = France postalCode = Lyon organizationalUnitName = A-Team organizationName = Black localityName = Lyon countryName = FR commonName = Barracuda Intermediate CA

[ ca ] default_ca = CA_default

[ CA_default ] base_dir = server/generated/ca-intermediate database = $base_dir/ca.index serial = $base_dir/ca.serial certs = $base_dir/certs new_certs_dir = $base_dir/newcerts default_md = sha256 default_days = 365 email_in_dn = no copy_extensions = copy uniqueSubject = no

policy = root_ca_policy private_key = $base_dir/private/ca.key.pem certificate = $base_dir/certs/ca.cert.pem

[ root_ca_policy ] countryName = match stateOrProvinceName = match localityName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional

[ name_constraints ] permitted;DNS.0 = srv.local

And server/generated/ca-intermediate/sign-ca.conf:

[ req ]
default_bits = 4096
encrypt_key = yes
default_md = sha256
string_mask = utf8only
utf8 = yes
prompt = no
x509_extensions = x509_ext
distinguished_name = distinguished_name

[ x509_ext ] basicConstraints = critical, CA:true, pathlen:0 nameConstraints = critical, @name_constraints subjectKeyIdentifier = hash issuerAltName = issuer:copy authorityKeyIdentifier = keyid:always, issuer:always

keyUsage = digitalSignature, keyCertSign, cRLSign

[ distinguished_name ] commonName = Barracuda Intermediate CA

[ ca ] default_ca = CA_default

[ CA_default ] base_dir = server/generated/ca-root database = $base_dir/ca.index serial = $base_dir/ca.serial certs = $base_dir/certs new_certs_dir = $base_dir/newcerts default_md = sha256 default_days = 32 email_in_dn = no copy_extensions = copy uniqueSubject = no

policy = intermediate_ca_policy private_key = $base_dir/private/ca.key.pem certificate = $base_dir/certs/ca.cert.pem

[ intermediate_ca_policy ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional

[ name_constraints ] permitted;DNS.0 = srv.local

Finally I build the certificate with:

#!/usr/bin/env bash
set -xeuo pipefail

if [ $# -ne 1 ]; then echo "Usage: $0 SERVICE" exit 2 fi

SERVICE=$1 CRT_DIR=server/generated/services/$SERVICE CA_DIR=server/generated/ca-intermediate REQ_DIR=server/requests

mkdir -p "$CRT_DIR"

CONF=$CRT_DIR/server.conf

Create a new certificate

openssl req
-new
-config "$CONF"
-nodes -newkey rsa:4096
-keyout "$CRT_DIR/server.key"
-out "$CRT_DIR/server.csr"

Sign it with our CA

openssl ca
-config "$CA_DIR/ca.conf"
-cert "$CA_DIR/certs/ca.cert.pem"
-keyfile "$CA_DIR/private/ca.key.pem"
-days 1
-out "$CRT_DIR/server.crt"
-infiles "$CRT_DIR/server.csr"

With server/generated/services/svc/server.conf:

[ req ]
default_bits = 4096
encrypt_key = no
default_md = sha256
string_mask = utf8only
utf8 = yes
prompt = no
req_extensions = req_ext
distinguished_name = distinguished_name

[ req_ext ] basicConstraints = CA:false

keyUsage = keyEncipherment, digitalSignature

[ distinguished_name ] stateOrProvinceName = France postalCode = Lyon organizationalUnitName = A-Team organizationName = Black localityName = Lyon countryName = FR commonName = svc.srv.local

[ alt_names ]

I try to only trust the root CA at my OS-level, but it fails to verify (curl/browsers).

Moreover, it does not pass the openssl verify phase:

$ openssl verify server/generated/services/svc/server.crt 
C = FR, ST = France, L = Lyon, O = Black, OU = A-Team, CN = svc.srv.local
error 20 at 0 depth lookup:unable to get local issuer certificate
server/generated/services/svc/server.crt: verification failed: 20 (unable to get local issuer certificate)

$ openssl verify -CAfile ca-chain.pem server/generated/services/svc/server.crt C = FR, ST = France, L = Lyon, O = Black, OU = A-Team, CN = Barracuda Intermediate CA error 24 at 1 depth lookup:invalid CA certificate server/generated/services/svc/server.crt: verification failed: 24 (invalid CA certificate)

$ openssl verify -CAfile server/generated/ca-root/certs/ca.cert.pem -untrusted ca-chain.pem server/generated/services/svc/server.crt C = FR, ST = France, L = Lyon, O = Black, OU = A-Team, CN = Barracuda Intermediate CA error 24 at 1 depth lookup:invalid CA certificate server/generated/services/svc/server.crt: verification failed: 24 (invalid CA certificate)

GlinesMome
  • 101
  • 2
  • In short: openssl verify expects to verify against a root CA (ca.pem). If you want to verify against the intermediate CA directly (ca-chain.pem) then you need to add -partial_chain option. Or use root CA but provide also chain CA: -CAfile ca.pem -untrusted ca-chain.pem. – Steffen Ullrich Aug 27 '23 at 18:18

1 Answers1

2

There are two problems here:

  1. The intermediate certificate is not properly generated
    The x509_extensions=x509_ext in the [req] section of ca.conf for the intermediate certificate is a no-op, since for a request there need to be req_extensions instead. So the settings for basicConstraints and nameConstraints have to be done in a [req_ext] section referenced by req_extensions=req_ext
  2. The name constraint given in the root CA is for barracuda.local while the name constraints for the intermediate CA is for srv.local and the actual name used in the server certificate is svc.srv.local - a clear violation of the name constraint in the root CA. Thus the name constraint in the root CA needs to be fixed.
Steffen Ullrich
  • 201,479
  • 30
  • 402
  • 465
  • 1
    Plus you can't put AKI or IAN in a request because the issuer/parent isn't determined yet. (Not to mention that IAN is useless AFAIK.) – dave_thompson_085 Aug 31 '23 at 06:05
  • @steffen-ullrich I get a Error Loading extension section req_ext with name=authorityKeyIdentifier, value=keyid:always, issuer:always. – GlinesMome Aug 31 '23 at 17:32
  • @dave_thompson_085 what are AKI/IAN? – GlinesMome Aug 31 '23 at 17:34
  • 1
    @GlinesMome: what dave_thompson_085 said is that authorityKeyIdentifier (AKI) and issuerAltName (IAN) make no sense for a certificate request, since they depend on the issuer of the certificate. These are only determined at time of issuing the certificate and not when requesting it. Hence you get the error. Don't just blindly copy x509_extensions to req_extensions but use only the ones I mentioned (basicConstraints, nameConstraints). – Steffen Ullrich Aug 31 '23 at 18:30