This should work on any modern Mac or Linux system with OpenSSL or LibreSSL installed.
OpenSSL implementation (more specifically, LibreSSL) is included in macOS, but default configuration does not include relevant options for Certification Authority generation.
We know three ways of solving that.
Following the recommendation from Michael MacFadden, add the following lines to /etc/ssl/openssl.cnf:
[ v3_ca ]
basicConstraints = critical,CA:TRUE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
If you use Docker or another container engine, OpenSSL operations can be wrapped in any container that includes fresh OpenSSL for Linux, e.g. nginx:
docker run --rm -it \
-v "$PWD:/working-directory" -w "/working-directory" \
nginx:latest \
openssl <...>
If you are using HomeBrew, the corresponding formula may be used:
brew install openssl@3
The following two steps are also implemented in generate-certificates.sh, which runs without parameters.
Note that, if you are using the modified version of
opensslfrom Homebrew:openssl@3, replaceopensslin the commands below with the path to Homebrew's version ofopensslwhich will likely be/opt/homebrew/Cellar/openssl@3/3.1.1/bin/opensslbut you can find the path to Brew'sopensslon your machine by running this command in the shell:brew list openssl | grep bin/openssl
Generate CA private key:
openssl genrsa -out test-ca.key 2048
This will create file test-ca.key which will be required on the next steps.
Generate self signed CA certificate valid for ten years:
openssl req \
-x509 -new -nodes -key test-ca.key \
-subj "/CN=My Test CA v1" \
-days 3650 -reqexts v3_req -extensions v3_ca \
-out test-ca.cert
Please note the CN (common name) parameter: it will be the name of the CA. File test-ca.cert will be root CA in our setup.
The following command will generate a certificate request and a private key. Like with CA, CN parameter should be defined by the user convenience, it will be used to identify the signing entity:
openssl req \
-utf8 -nameopt oneline,utf8 -new -newkey rsa:2048 -nodes \
-subj "/CN=My Testing Document Signer" \
-keyout test-signer.key -out test-signer.csr
Sign a certificate using test-signer.csr and CA private key and certificate created earlier:
openssl x509 \
-days 365 \
-CA test-ca.cert -CAkey test-ca.key -CAcreateserial \
-in test-signer.csr -req \
-out test-signer.cert
We now have three useful files:
test-ca.cert: this is our CA certificate, forming the whole CA chain.test-signer.cert: this is the certificate to be used to sign documents.test-signer.key: private key of the signer certificate.
CA:
openssl x509 -text -noout -in test-ca.cert
The output should include:
Certificate:
Data:
Version: 3 (0x2)
...
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=My Test CA v1
...
Subject: CN=My Test CA v1
X509v3 extensions:
...
X509v3 Basic Constraints: critical
CA:TRUE
Signing certificate:
openssl x509 -text -noout -in test-signer.cert
Expected output:
Certificate:
Data:
Version: 1 (0x0)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=My Test CA v1
Subject: CN=My Testing Document Signer