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)
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
.