Search code examples
androidsslopensslssl-certificate

Self-Signed CA Certificates Rejected by Android


I'm using the following script to create certificates using my own internal CA(Work in progress):

#!/bin/bash
while [[ "$#" < 1 ]];do
  echo "Usage: cert-admin.sh [COMMAND] [OPTIONS]\n"
  echo "  Commands:"
  echo "    newpair   - Creates a key and signed certificate"
  echo "    revoke    - Revokes a certificate and re-creates the intermediate CRL"
  echo "    newcrl    - Re-creates the intermediate CRL"
  echo ""
  echo "  Examples: cert-admin.sh newpair server example.mysite.com"
  echo "            cert-admin.sh revoke example.mysite.com"
  echo ""
  echo "  Types: server"
  exit 1
done
command=$1
function newCRL {
  cd /root/ca
  rm -f intermediate/crl/intermediate.crl.pem
  openssl ca -config intermediate/openssl.cnf \
      -gencrl -out intermediate/crl/intermediate.crl.pem
  openssl crl -in intermediate/crl/intermediate.crl.pem -noout -text
}
while [[ $command == 'newpair' ]];do
  type=$2
  fqdn=$3
  while [[ $type == 'server' ]];do
    cd /root/ca
    openssl genrsa -out intermediate/private/$fqdn.key.pem 2048
    openssl req -reqexts SAN \
      -config <(cat intermediate/openssl.cnf \
        <(printf "\n[SAN]\nsubjectAltName=DNS:$fqdn\n\n")) \
      -key intermediate/private/$fqdn.key.pem \
      -new -sha256 -out intermediate/csr/$fqdn.csr.pem
    openssl ca -config intermediate/openssl.cnf \
      -extensions server_cert -days 375 -notext -md sha256 \
      -in intermediate/csr/$fqdn.csr.pem \
      -out intermediate/certs/$fqdn.cert.pem
    chmod 444 intermediate/private/$fqdn.key.pem
    chmod 444 intermediate/certs/$fqdn.cert.pem
    openssl x509 -noout -text \
      -in intermediate/certs/$fqdn.cert.pem
    openssl verify -CAfile intermediate/certs/ca-chain.cert.pem \
      intermediate/certs/$fqdn.cert.pem
    echo "ca-chain:  /root/ca/intermediate/certs/ca-chain.cert.pem"
    echo "key:        /root/ca/intermediate/private/$fqdn.key.pem"
    echo "cert:       /root/ca/intermediate/certs/$fqdn.cert.pem"
    break;
  done
  break;
done
while [[ $command == 'revoke' ]];do
  cert=$2
  cd /root/ca
  openssl ca -config intermediate/openssl.cnf \
      -revoke intermediate/certs/$cert.cert.pem
  newCRL
  break;
done
while [[ $command == 'newcrl' ]];do
  newCRL
    while [[ $2 != 'cron' ]];do
    echo "CRL should be re-created by cron every 30 days. Example:"
    echo "30 12 2 * * /root/ca/cert-admin.sh newcrl cron"
    break;
  done
  break;
done

intermediate/openssl.cnf


[ ca ]
default_ca = CA_default

[ CA_default ]
dir               = /root/ca/intermediate
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

private_key       = $dir/private/intermediate.key.pem
certificate       = $dir/certs/intermediate.cert.pem

crlnumber         = $dir/crlnumber
crl               = $dir/crl/intermediate.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_loose

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

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

[ req ]
default_bits        = 4096
distinguished_name  = req_distinguished_name
string_mask         = utf8only

default_md          = sha256

x509_extensions     = v3_ca

[ req_distinguished_name ]
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

countryName_default             = US
stateOrProvinceName_default     = Denial
localityName_default            = Springfield
0.organizationName_default      = MyOrg
organizationalUnitName_default  = Research
emailAddress_default            = [email protected]

[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ crl_ext ]
authorityKeyIdentifier=keyid:always

[ ocsp ]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning

With the intermediate CA trusted in Arch Linux, the certificate doesn't throw any errors.

However, on Android I get errors. I think it has something to do with SAN as the Bitwarden Android app shows subjectAltNames[] and Kiwi browser(Chrome Desktop for Android) shows ERR_CERT_COMMON_NAME_INVALID.

Using -addext "subjectAltName = DNS:www.example.com" with -config intermediate/openssl.cnf doesn't work either.

Creating a self signed cert using:

openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \
-addext "subjectAltName = DNS:www.example.com" \
-subj "/C=US/ST=Denial/L=Springfield/O=MyOrg/CN=www.example.com" \
-keyout web.key  -out web.crt

works fine.

EDIT: Common Name is set to the FQDN when prompted.

EDIT2: To clarify, this is after installing the CA chain cert on the Android device. Running Android 10.

EDIT3: I've narrowed the issue down to SAN not being added due to -config <(cat intermediate/openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:$fqdn\n\n")) not working. Is there another way to include SAN while specifying openssl.cnf? It's not working for me...


Solution

  • My script above is at fault, I never ended up fixing it. If you are looking for a solution, odds are you are having issues with your openssl commands and or openssl config. I ended up using EJBCA in docker to create and manage my CA and certificates. So far EJBCA is working flawlessly (I suggest keeping it offline for security reasons). Certificates work well with Android 10 to Android 14 (14 being the latest tested as of writing this answer).