Shell scripts for operating an offline, air-gapped Certificate Authority on OpenBSD using OpenSSL, with revocation status published via a separate OpenBSD OCSP Server. Updates are transferred between the offline CA machine and the OCSP server machine by USB drive.
๐ Language / Sprache / Langue / Idioma / Lรญngua / Lingua / ่ช่จ / ์ธ์ด / เคญเคพเคทเคพ / ะฏะทัะบ / ูุบุฉ / Lugha / ่จ่ช / Lang / Wika / สปลlelo / Gagana / Reo / Taal / Harshe / แแแ / รdรจ / เฆญเฆพเฆทเฆพ / ่ฏญ่จ / Keel / Kieli / Sprรฅk / ะะพะฒะฐ / เธ เธฒเธฉเธฒ / Bahasa / Wika / Bahasa / Basa / ฮฮปฯฯฯฮฑ / Lingua / ืฉืคื / Teanga
| ๐บ๐ธ English | ๐ฉ๐ช Deutsch | ๐ช๐ธ Espaรฑol | ๐ซ๐ท Franรงais | ๐ต๐น Portuguรชs |
| ๐ฎ๐น Italiano | ๐ญ๐ฐ ็น้ซไธญๆ | ๐ฐ๐ท ํ๊ตญ์ด | ๐ฎ๐ณ เคนเคฟเคจเฅเคฆเฅ | ๐ท๐บ ะ ัััะบะธะน |
| ๐ธ๐ฆ ุงูุนุฑุจูุฉ | ๐ Kiswahili | ๐ฏ๐ต ๆฅๆฌ่ช | ๐ญ๐น Kreyรฒl ayisyen | ๐บ สปลlelo Hawaiสปi |
| ๐ Gagana Sฤmoa | ๐ฟ Te Reo Mฤori | ๐ฟ๐ฆ Afrikaans | ๐ณ๐ฑ Nederlands | ๐ Hausa |
| ๐ช๐น แ แแญแ | ๐ Yorรนbรก | ๐ง๐ฉ เฆฌเฆพเฆเฆฒเฆพ | ๐จ๐ณ ็ฎไฝไธญๆ | ๐ช๐ช Eesti |
| ๐ซ๐ฎ Suomi | ๐ธ๐ช Svenska | ๐ณ๐ด Norsk | ๐บ๐ฆ ะฃะบัะฐัะฝััะบะฐ | ๐น๐ญ เธ เธฒเธฉเธฒเนเธเธข |
| ๐ฎ๐ฉ Bahasa Indonesia | ๐ต๐ญ Filipino | ๐ฒ๐พ Bahasa Melayu | ๐ Basa Jawa | ๐ฌ๐ท ฮฮปฮปฮทฮฝฮนฮบฮฌ |
| ๐ Latina | ๐ฎ๐ฑ ืขืืจืืช | ๐ฎ๐ช Gaeilge |
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ USB drive โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Offline CA machine โ โโโโโโโโโโโโโโโโโโโโบ โ OCSP server machine โ
โ (OpenBSD, air-gapped) โ export-to-usb.sh โ (OpenBSD, networked) โ
โ โ โโโโโโโโโโโโโโโโโโโโ โ โ
โ /root/ca/ โ physical carry โ /etc/ocsp/ โ
โ openssl.cnf โ โ index.txt โ
โ certs/ca.cert.pem โ โ *.crl.pem โ
โ intermediate-*/ โ โ *-responder.crt โ
โ index.txt โ โ OcspServer (ASP.NET) โ
โ crl/ โ โ rcctl enable ocspserver โ
โ certs/ โ โ โ
โ ocsp/ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Both machines run OpenBSD. Install OpenSSL if it is not already present:
pkg_add opensslThe OCSP server machine also needs the
OpenBSDOCSPServer installed and
registered as an rc.d service named ocspserver.
All scripts use #!/bin/sh (OpenBSD's ksh-based /bin/sh), standard OpenBSD
utilities (mount_msdos, sha256, rcctl, doas), and openssl(1).
Run all scripts as root via doas.
scripts/
setup-ca.sh Initialize root CA directories and generate root key/cert
create-intermediate-ca.sh Create a named intermediate CA signed by the root CA
create-server-cert.sh Issue a TLS server certificate (mTLS)
create-client-cert.sh Issue a client certificate (mTLS)
revoke-cert.sh Revoke a certificate and regenerate the CRL
export-to-usb.sh Package CA data onto USB for air-gap transfer (CA side)
import-from-usb.sh Import from USB into the OCSP server (OCSP server side)
config/
openssl-root.cnf.template Root CA OpenSSL config template
openssl-intermediate.cnf.template Intermediate CA OpenSSL config template
Prepare your deployment values before running the steps below:
-
Where is the CA going to be?
default:/root/ca
actual: -
What is the org and where is it?
default:My Organization
actual: -
What is the project name?
default:MY PROJECT
actual: -
When is the project versioned?
default:01012027
actual: -
What is the TLD?
default:example.com
actual: -
What is the subdomain?
default:app.
actual: -
What is the email address for the client user(s)?
default:user@example.com
actual: -
Where is the USB thumb drive for transfer?
default:/dev/sd1i
actual:
doas sh scripts/setup-ca.sh /root/ca \
"/C=US/ST=MyState/L=MyCity/O=My Organization/CN=My Organization Root CA"This creates /root/ca/, generates an AES-256-encrypted 4096-bit root key, a
self-signed certificate valid for 20 years, and an OCSP signing certificate for
the root CA.
doas sh scripts/create-intermediate-ca.sh MY-PROJECT 01012027 /root/ca \
"/C=US/ST=MyState/L=MyCity/O=My Organization/CN=My Organization Intermediate CA MY-PROJECT 01012027"Files are created under /root/ca/intermediate-MY-PROJECT-01012027/.
doas sh scripts/create-server-cert.sh MY-PROJECT 01012027 \
app.example.com \
"DNS:app.example.com,DNS:www.example.com" \
/root/caOutputs under the intermediate CA directory:
private/app.example.com.01012027.key.pemโ encrypted private keycerts/app.example.com.01012027.cert.pemโ signed certificatecerts/app.example.com.01012027.server.full.pfxโ PKCS#12 bundle
doas sh scripts/create-client-cert.sh MY-PROJECT 01012027 \
user@example.com /root/caRepeat for each user. Transfer each .full.pfx bundle to the respective user
over a secure channel.
doas sh scripts/revoke-cert.sh MY-PROJECT 01012027 \
certs/client-user@example.com.01012027.cert.pem \
keyCompromise /root/caTo renew the CRL without revoking anything (e.g. on a scheduled basis):
doas sh scripts/revoke-cert.sh MY-PROJECT 01012027 --crl-only /root/caInsert a FAT32-formatted USB drive. Confirm the device:
dmesg | tail -20 # look for "sd1 at ..." lines
disklabel sd1 # identify the FAT32 partition (usually 'i')Then export:
doas sh scripts/export-to-usb.sh MY-PROJECT 01012027 /root/ca /dev/sd1iThe script writes a SHA256 checksum manifest and unmounts the drive safely.
Physically carry the USB drive to the OCSP server machine.
dmesg | tail -20 # confirm USB device name
disklabel sd1
doas sh scripts/import-from-usb.sh /dev/sd1i /etc/ocsp ocspserverThe script verifies checksums, copies updated files to /etc/ocsp/, and
reloads the ocspserver daemon via rcctl. If EnableIndexTxtWatch is true
in appsettings.json, the OCSP server will also pick up index.txt changes
automatically without a reload.
openssl ocsp \
-issuer /etc/ocsp/intermediate-MY-PROJECT-01012027/ca-chain.cert.pem \
-cert /path/to/cert.pem \
-url http://localhost:2560 \
-resp_text| File | Pattern |
|---|---|
| Intermediate CA key | intermediate-PROJECT-DATE.key.pem |
| Intermediate CA cert | intermediate-PROJECT-DATE.cert.pem |
| Certificate chain | ca-chain-PROJECT-DATE.cert.pem |
| Server cert | SERVER_DOMAIN.DATE.cert.pem |
| Server PKCS#12 | SERVER_DOMAIN.DATE.server.full.pfx |
| Client cert | client-USER_EMAIL.DATE.cert.pem |
| Client PKCS#12 | client-USER_EMAIL.DATE.full.pfx |
| CRL | intermediate.crl.pem / intermediate.crl.der |
| OCSP signing cert | INTER_NAME-ocsp.cert.pem |
- The offline CA machine must never be connected to a network.
- Root and intermediate private keys are AES-256 encrypted. Store passphrases in a hardware token or physical vault, separate from the keys themselves.
- Always verify USB drive checksums before importing โ
import-from-usb.shdoes this automatically using OpenBSD'ssha256 -C. - CRLs expire after 30 days by default. Schedule regular CRL renewal:
doas sh scripts/revoke-cert.sh MY-PROJECT DATE --crl-only # then export-to-usb + import-from-usb - OCSP signing certificates expire after 375 days. Renew them by rerunning
create-intermediate-ca.shwith the same arguments; it skips steps that are already complete and only generates a new OCSP cert when needed.