Skip to content

Certificate development for Onion Services

This document is intended for those who want to develop and experiment with custom certification procedures.

For general certificate usage, check this document instead.

For research on certificates for Onion Services, check this other document.

Test CAs and certificates

This is a quick guide with procedures for setting up custom CAs and certificates, useful for development, Quality Assurance and testing purposes.

Please use it in a test/disposable virtual machine.

Test CA with mkcert

With mkcert (package), self-signed certificate management gets really simple:

mkcert '*.exav2agnwayz4hxbm2elifyn25ivey7pg7glg676nz2udsobbvsihkad.onion'

Test CA with Easy-RSA

Creating a custom test CA using Easy-RSA:

sudo apt update
sudo apt install -y easy-rsa
EASY_RSA="$HOME/code/easy-rsa"
mkdir -p $EASY_RSA
ln -s /usr/share/easy-rsa/* $EASY_RSA
chmod 700 $EASY_RSA
cd $EASY_RSA
./easyrsa init-pki
cat <<EOF >> pki/vars
set_var EASYRSA_REQ_COUNTRY    "US"
set_var EASYRSA_REQ_PROVINCE   "NewYork"
set_var EASYRSA_REQ_CITY       "New York City"
set_var EASYRSA_REQ_ORG        "Custom CA"
set_var EASYRSA_REQ_EMAIL      "admin@example.com"
set_var EASYRSA_REQ_OU         "Custom CA"
set_var EASYRSA_REQ_CN         "Custom CA"
set_var EASYRSA_ALGO           "ec"
set_var EASYRSA_DIGEST         "sha384"
set_var EASYRSA_CURVE          "secp384r1"
set_var EASYRSA_CERT_EXPIRE    "365"
EOF
./easyrsa build-ca nopass

Certification using Easy-RSA

Select the domain:

DOMAIN="exav2agnwayz4hxbm2elifyn25ivey7pg7glg676nz2udsobbvsihkad.onion"

Create the key and certificate:

./easyrsa --subject-alt-name="DNS:${DOMAIN},DNS:*.${DOMAIN}" \
  --req-c=AQ                   \
  --req-st=Anywhere            \
  --req-city=Nowhere           \
  --req-org="The Onion Space"  \
  --req-email=none@example.org \
  --req-ou="Onion Hyperspace"  \
 build-server-full ${DOMAIN} nopass

Create the fullchain version:

sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' \
  pki/issued/${DOMAIN}.crt | cat - pki/ca.crt > pki/issued/${DOMAIN}.fullchain.crt

Certification using Easy-RSA and OpenSSL

Select the domain:

DOMAIN="exav2agnwayz4hxbm2elifyn25ivey7pg7glg676nz2udsobbvsihkad.onion"

Create the key and the CSR:

OPENSSL=$HOME/code/openssl_certs

mkdir -p $OPENSSL

cat <<EOF > $OPENSSL/${DOMAIN}.conf
[ req ]
distinguished_name = req_distinguished_name

[ req_distinguished_name ]
commonName_default     = $DOMAIN
organizationName       = The Onion Space
organizationalUnitName = Onion Hyperspace
emailAddress           = none@example.org
localityName           = Nowhere
stateOrProvinceName    = Anywhere
countryName            = The Internet
commonName             = Torified Project
EOF

openssl req -batch -config $OPENSSL/${DOMAIN}.conf -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 \
            -keyout $OPENSSL/${DOMAIN}.privatekey.pem -out $OPENSSL/${DOMAIN}.csr.pem -sha384 \
            -nodes

openssl req -in $OPENSSL/${DOMAIN}.csr.pem -text

Sign using the custom CA created in the previous section:

cd ~/code/easy-rsa

./easyrsa import-req ~/code/openssl_certs/${DOMAIN}.csr.pem ${DOMAIN}

./easyrsa \
  --subject-alt-name="DNS:${DOMAIN},DNS:*.${DOMAIN}" \
  sign-req server ${DOMAIN}

sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' \
  pki/issued/${DOMAIN}.crt | cat - pki/ca.crt > ~/code/openssl_certs/${DOMAIN}.cert.pem

Certification using Easy-RSA and mkcert

Certificates created with this procedure currently do not work and will trigger a SSL_ERROR_NO_CYPHER_OVERLAP in Tor Browser.

First, adapt the Easy RSA CA into the mkcert format:

mkdir -p ~/code/easy-rsa/mkcert
cd ~/code/easy-rsa/mkcert
ln -s ../pki/ca.crt rootCA.pem
ln -s ../pki/private/ca.key rootCA-key.pem

Select the domain:

DOMAIN="exav2agnwayz4hxbm2elifyn25ivey7pg7glg676nz2udsobbvsihkad.onion"

Then create the certificate:

CAROOT=$HOME/code/easy-rsa/mkcert mkcert "*.${DOMAIN}"

Add the fullchain:

sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' \
  ./_wildcard.${DOMAIN}.pem | \
  cat - ~/code/easy-rsa/pki/ca.crt \
  > ./_wildcard.${DOMAIN}.cert

Certification using Onionmine and Easy RSA

This is an experimental approach for getting the certificate using Onionmine and Easy RSA.

Define a pool:

POOL="mypool"

Create some keys:

git clone --recursive https://gitlab.torproject.org/tpo/onion-services/onionmine.git ~/onionmine
cd onionmine
./bin/provision
./onionmine config ${POOL}
echo onion > pools/${POOL}/filters.lst
./onionmine mine   ${POOL}

After a while, type Ctr C and select one of the create addresses:

DOMAIN="exav2agnwayz4hxbm2elifyn25ivey7pg7glg676nz2udsobbvsihkad.onion"
./onionmine select-candidate ${POOL} ${DOMAIN}

Then create a CSR:

cd ~/onionmine
./onionmine generate-selected-cert ${POOL}

Signing the Onion Service certificate:

cd ~/easy-rsa
./easyrsa import-req ~/onionmine/pools/${POOL}/certs/selected/csr.pem ${DOMAIN}
./easyrsa sign-req server ${DOMAIN}

References

EV certs from custom CAs

Developing using EV certs from custom CAs is unfeasible at this point:

  • It's usually not easy, or maybe even possible, to have EV cert using a custom CA without patching the browser source code:
    • At least for Firefox, "Mozilla considers an intermediate certificate to be capable of issuing EV TLS certificates when all of the following are true. The intermediate certificate: [...] either directly or transitively chains up to a root certificate included in Mozilla's root store with the TLS (Websites) trust bit turned on, and EV enabled [...] has Policy Identifiers containing one or more of: 2.23.140.1.1 (CABF EV OID), 2.5.29.32.0 (anyPolicy OID), the CA's EV OIDs used by Mozilla in ExtendedValidation.cpp, or any Policy OIDs listed in ExtendedValidation.cpp for the CA certificate. (source).
  • Also, since signed certs with custom CAs right now won't work by default on [Tor Browser][] as of 2025-10 (check this issue).