1
0
Fork 0
6 mTLS with cfssl generated certificates
finkregh edited this page 2024-12-25 18:53:56 +00:00

Generate CA + intermediate + cert via cfssl

Alternative software, less complex: https://github.com/FiloSottile/mkcert

One self signed certificate per user/device

#!/usr/bin/env bash

set -euo pipefail

_user=$1
_device=$2

crtname="${_user}_${_device}"

if [[ -e "${crtname}.crt" ]]; then echo "${crtname}.crt already exists, existing" ; exit 1 ; fi

#step-cli certificate create "${crtname}@local" "${crtname}.crt" "${crtname}.key" --profile self-signed --subtle --insecure --no-password
# two years
step-cli certificate create "${crtname}@local" "${crtname}.crt" "${crtname}.key" --not-after=17520h --template selfsigned.tmpl.json --subtle --insecure --no-password

openssl pkcs12 -export -in "${crtname}.crt" -inkey "${crtname}.key" -out "${crtname}.p12"
# seems iphones need the -legacy switch... thanks to <https://github.com/paulgessinger/swift-paperless/issues/70#issuecomment-2308163983>
openssl pkcs12 -legacy -export -in "${crtname}.crt" -inkey "${crtname}.key" -out "${crtname}-for-iphone.p12"

exit 0

selfsigned.tmpl.json:

{
        "subject": {{ toJson .Subject }},
        "sans": {{ toJson .SANs }},
        "issuer": {{ toJson .Subject }},
    "keyUsage": ["digitalSignature","keyEncipherment"],
    "extKeyUsage": ["clientAuth"],
        "basicConstraints": {
                "isCA": false
        }
}

Usage: ./gen-user-cert.sh useridentifier deviceidentifier, e.g. ./gen-user-cert.sh sophie laptop

The trusted_ca_cert_file directive then has to be added for each certificate (see the caddy config at the end).

Revocation works by removing the line.

Single CA that signs certificates

This does not allow revocation of certificates, if you want to revoke access to individual certificates, you need to follow above steps or work out how to validate certificates via e.g. https://caddyserver.com/docs/caddyfile/directives/tls#verifier, but it seems that feature is broken: https://github.com/caddyserver/caddy/issues/4518

Beware: This does not support OCSP / CRLs. Seems you need some database for this...

mkdir -p {root,intermediate}-ca
mkdir -p {ocsp,client}

root CA

root-ca/root-ca.json:

{
  "CN": "xxx root CA",
  "key": {
    "algo": "ecdsa",
    "size": 256
  },
  "names": [
    {
      "C": "DE",
      "L": "Ort",
      "O": "xxx",
      "OU": "root ca"
    }
  ],
  "ca": {
    "expiry": "87600h"
  }
}
cfssl gencert -initca root-ca/root-ca.json | cfssljson -bare root-ca/root-ca

intermediate CA

mkdir -p intermediate-ca
touch intermediate-ca/intermediate-ca.json
{
  "CN": "xxx intermediate CA",
  "key": {
    "algo": "ecdsa",
    "size": 256
  },
  "names": [
    {
      "C": "DE",
      "L": "Ort",
      "O": "xxx",
      "OU": "intermediate ca"
    }
  ],
  "ca": {
    "expiry": "87600h"
  }
}
cfssl gencert -initca intermediate-ca/intermediate-ca.json  | cfssljson -bare intermediate-ca/intermediate-ca

config with profiles

config.json:

  • intermediate
    • valid for 1 year
    • intermediate signed for 8 years
    • intermediate may create + revoke certs
    • is a CA
    • may not issue child CAs
  • client
    • for client authentication
    • valid for 1 year
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "intermediate": {
        "usages": ["cert sign", "crl sign"],
        "expiry": "70080h",
        "ca_constraint": {
          "is_ca": true,
          "max_path_len": 1
        }
      },
      "client": {
        "usages": [
            "signing",
            "digital signature",
            "key encipherment",
            "client auth"
        ],
        "expiry": "8760h"
      },
      "ocsp": {
        "usages": ["digital signature", "ocsp signing"],
        "expiry": "70080h"
      }
    }
  }
}

sign intermediate CA with root CA

cfssl sign -ca root-ca/root-ca.pem -ca-key root-ca/root-ca-key.pem -config config.json -profile intermediate intermediate-ca/intermediate-ca.csr | cfssljson -bare intermediate-ca/intermediate-ca

generate CA bundle

generates cert-bundle.crt:

cfssl-mkbundle root-ca/root-ca.pem intermediate-ca/intermediate-ca.pem

generate certificate to sign OCSP

NOT REQUIRED, requires e.g. an sqlite DB and also e.g. LetsEncrypt do not provide this anymore: https://letsencrypt.org/2024/07/23/replacing-ocsp-with-crls/

ocsp/ocsp.json:

{
  "CN": "ocsp.example.internal",
  "key": {
    "algo": "ecdsa",
    "size": 256
  },
  "names": [
    {
      "C":  "DE",
      "ST": "NRW",
      "L":  "City",
      "O":  "Ahmed Example",
      "OU": "ocsp cfssl demo"
    }
  ]
}
cfssl gencert -ca intermediate-ca/intermediate-ca.pem -ca-key intermediate-ca/intermediate-ca-key.pem -config config.json -profile ocsp ocsp/ocsp.json | cfssljson -bare ocsp/ocsp

generate client certificate (script wrapper)

gen-client-cert.sh:

#!/usr/bin/env bash

# example usage:
# gen-client-cert.sh khalila laptop

set -euo pipefail

_user=$1
_device=$2

_jsonfile="clients/${_user}-${_device}.json"

if [[ -e "${_jsonfile}" ]]; then echo "${_jsonfile} already exists, existing" ; exit 1 ; fi

sed "s,DEVICE,${_device}," clients/template.json > "${_jsonfile}"
sed -i "s,USER,${_user}," "${_jsonfile}"

cfssl gencert -ca intermediate-ca/intermediate-ca.pem -ca-key intermediate-ca/intermediate-ca-key.pem -config config.json -profile client "${_jsonfile}" | cfssljson -bare "clients/${_user}-${_device}"

openssl pkcs12 -export -in "clients/${_user}-${_device}.pem" -inkey "clients/${_user}-${_device}-key.pem" -out "clients/${_user}-${_device}.p12"
# seems iphones need the -legacy switch... thanks to <https://github.com/paulgessinger/swift-paperless/issues/70#issuecomment-2308163983>
openssl pkcs12 -legacy -export -in "clients/${_user}-${_device}.pem" -inkey "clients/${_user}-${_device}-key.pem" -out "clients/${_user}-${_device}-for-iphone.p12"

echo "created: $(ls clients/${_user}-${_device}*)"

Installation on iOS (iPhone) needs to happen via safari, you can not install a certificate via file browser. Also you need to install the CA as trusted (AFAIR) to be able to install the client certificate. You can spawn a http server e.g. via python3 -m http.server 9000.

clients/template.json:

{
  "CN": "DEVICE@USER",
  "hosts": ["DEVICE@USER"],
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "C":  "DE",
      "ST": "AYY",
      "L":  "City",
      "O":  "USER",
      "OU": "DEVICE"
    }
  ]
}

generate client certificate (manually)

clients/example.json:

{
  "CN": "client.example.internal",
  "hosts": "client@exameple.internal",
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "C":  "DE",
      "ST": "NRW",
      "L":  "City",
      "O":  "Ahmed Example",
      "OU": "client cfssl demo"
    }
  ]
}
cfssl gencert -ca intermediate-ca/intermediate-ca.pem -ca-key intermediate-ca/intermediate-ca-key.pem -config config.json -profile client clients/example.json | cfssljson -bare clients/example

OCSP CRL

#cfssl bundle -cert intermediate-ca/intermediate-ca.pem -key intermediate-ca/intermediate-ca-key.pem -ca-bundle root-ca/root-ca.pem -int-bundle intermediate-ca/intermediate-ca.pem

Usage in caddy web server

# re-usable snippet
(mtls_conf) {
    tls your@mailaddress.com.example {
        on_demand
        client_auth {
            mode require_and_verify
            trusted_ca_cert_file /etc/caddy/root-ca.pem
            trusted_ca_cert_file /etc/caddy/intermediate-ca.pem
        }
    }
}

# usage with e.g. reverse proxy
domain.example {
        import mtls_conf
        reverse_proxy 192.168.1.1:80
}