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)
1
  • 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. Commented Aug 27, 2023 at 18:18

1 Answer 1

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.
4
  • 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.) Commented Aug 31, 2023 at 6:05
  • @steffen-ullrich I get a Error Loading extension section req_ext with name=authorityKeyIdentifier, value=keyid:always, issuer:always.
    – GlinesMome
    Commented Aug 31, 2023 at 17:32
  • @dave_thompson_085 what are AKI/IAN?
    – GlinesMome
    Commented Aug 31, 2023 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). Commented Aug 31, 2023 at 18:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .