feat: Add OCSP support to federated environment

Adds Online Certificate Status Protocol (OCSP) support to the federated Openfire setup:
- Add certificate generation script with full PKI hierarchy
- Add certificate import script for Openfire keystores
- Implement OCSP responder service via Docker compose
- Update documentation with OCSP usage instructions

The -o flag can now be used with start.sh to enable OCSP support.
fix-revocation-examples
Matthew Vivian 2024-11-05 14:41:50 +00:00 committed by Guus der Kinderen
parent 3aac76fdcf
commit 7102765241
5 changed files with 403 additions and 2 deletions

View File

@ -101,6 +101,67 @@ For example:
`docker network connect openfire-testing_openfire-federated-net openfire-testing_xmpp1_1`
## Certificates
By default, the system uses self-signed certificates that have been pre-generated and added
to the identity and trust stores for `xmpp1.localhost.example` and `xmpp2.localhost.example`.
This simplifies the setup process and avoids the need to generate certificates, but the
limitations of this are that the certificates are not trusted by anything other than
our own servers (`xmpp1` and `xmpp2`), and that they do not support OCSP (Online Certificate
Status Protocol).
### OCSP Support
A more fully featured solution is provided by the `-o` option of the `start.sh` script
which enables OCSP support for the Openfire federated environment. When passed the `-o`
option the script will generate a complete certificate hierarchy with OCSP support, and
deploy an OCSP responder service configured to respond to OCSP requests for the server
certificates.
Here's what the script creates:
* Root CA certificate (self-signed)
* Intermediate CA certificate (signed by Root CA)
* Two server certificates with OCSP information (one for each Openfire instance)
* An OCSP responder certificate (for signing OCSP responses)
* Full certificate chains for both servers (server + intermediate + root)
* Certificate database (index.txt) for the OCSP responder to track certificate statuses
All certificates are stored in `./_data/certs/`.
```
Root CA
(Top level trust root)
(Kept offline/secure)
|
v
Intermediate CA
(Day-to-day certificate issuer)
(OCSP configuration point)
|
+--------------------+------------------+
v v v
XMPP1 Cert XMPP2 Cert OCSP Cert
| | (Signs OCSP responses)
v v
XMPP1 Server XMPP2 Server
[keystore] [keystore]
(server's identity) (server's identity)
[truststore] [truststore]
(who to trust) (who to trust)
```
This setup allows certificates to be checked for revocation status making a request to the
OCSP responder:
```bash
openssl ocsp -url http://localhost:8888 \
-issuer _data/certs/ca/intermediate-ca/intermediate.crt \
-CAfile _data/certs/chain1.pem \
-cert _data/certs/server1.crt \
-text
```
## How it's built
To recreate the known good state for the system we first create base Openfire and Postgres containers.

View File

@ -0,0 +1,42 @@
services:
# OCSP (Online Certificate Status Protocol) Responder Service
#
# This service provides real-time certificate validation for the development environment.
# It works with certificates generated by ./scripts/generate-certificates.sh and imported
# by ./scripts/import-certificates.sh.
#
# Configuration Parameters:
# ------------------------
# Port: 8888 - OCSP responder listens for validation requests
# Index File: - Lists all issued certificates (/ca/intermediate-ca/index.txt)
# CA Certificate: - Issuer's certificate (/ca/intermediate-ca/intermediate.crt)
# OCSP Key Pair: - Responder credentials (/ca/ocsp-responder/ocsp.{key,crt})
# Validity Period: - Responses valid for 1 day (-ndays 1)
#
# Test Certificate Status:
# ----------------------
# openssl ocsp -url http://localhost:8888 \
# -issuer _data/certs/ca/intermediate-ca/intermediate.crt \
# -CAfile _data/certs/chain1.pem \
# -cert _data/certs/server1.crt \
# -text # Adds human-readable output
ocsp-responder:
image: alpine:latest
volumes:
- ./_data/certs/ca:/ca
command:
- /bin/sh
- -c
- |
apk add --no-cache openssl &&
openssl ocsp -port 8888 -text \
-index /ca/intermediate-ca/index.txt \
-CA /ca/intermediate-ca/intermediate.crt \
-rkey /ca/ocsp-responder/ocsp.key \
-rsigner /ca/ocsp-responder/ocsp.crt \
-ndays 1
ports:
- "8888:8888"
networks:
openfire-federated-net:
ipv4_address: 172.50.0.30

View File

@ -0,0 +1,160 @@
#!/bin/bash
# OCSP server configuration
# Defines where the OCSP responder will be accessible in the Docker network
OCSP_URL="http://172.50.0.30:8888"
# Base directory for all certificate-related files
# All paths in this script will be relative to this directory
CERT_DIR="./_data/certs"
# Create the PKI directory structure:
# We only need:
# - root-ca: Root Certificate Authority files
# - intermediate-ca: Intermediate Certificate Authority files
# - ocsp-responder: OCSP responder certificates
mkdir -p "${CERT_DIR}/ca/root-ca/private" \
"${CERT_DIR}/ca/intermediate-ca/private" \
"${CERT_DIR}/ca/ocsp-responder"
chmod 700 "${CERT_DIR}/ca/root-ca/private" "${CERT_DIR}/ca/intermediate-ca/private"
# Initialize the certificate databases and serial number counters
# index.txt acts as a database of all certificates
# serial defines the next certificate serial number
echo -n > "${CERT_DIR}/ca/intermediate-ca/index.txt"
echo 1000 > "${CERT_DIR}/ca/intermediate-ca/serial"
# Generate the Root CA private key and certificate
# This is the top-level certificate authority that signs the intermediate CA
openssl genrsa -out "${CERT_DIR}/ca/root-ca/private/ca.key" 4096
openssl req -new -x509 -key "${CERT_DIR}/ca/root-ca/private/ca.key" \
-out "${CERT_DIR}/ca/root-ca/ca.crt" -days 3650 \
-subj "/C=GB/ST=London/L=London/O=Test Openfire/CN=Test Openfire Root CA"
# Generate the Intermediate CA private key and certificate signing request (CSR)
# The intermediate CA will be used to sign the server and OCSP responder certificates
openssl genrsa -out "${CERT_DIR}/ca/intermediate-ca/private/intermediate.key" 4096
openssl req -new -key "${CERT_DIR}/ca/intermediate-ca/private/intermediate.key" \
-out "${CERT_DIR}/ca/intermediate-ca/intermediate.csr" \
-subj "/C=GB/ST=London/L=London/O=Test Openfire/CN=Test Openfire Intermediate CA"
# Sign the Intermediate CA certificate with the Root CA
# The certificate includes:
# - CA capabilities (basicConstraints)
# - Permission to sign certificates (keyUsage)
# - Location of the OCSP responder (authorityInfoAccess)
openssl x509 -req -in "${CERT_DIR}/ca/intermediate-ca/intermediate.csr" \
-CA "${CERT_DIR}/ca/root-ca/ca.crt" \
-CAkey "${CERT_DIR}/ca/root-ca/private/ca.key" -CAcreateserial \
-out "${CERT_DIR}/ca/intermediate-ca/intermediate.crt" -days 1825 \
-extfile <(printf 'basicConstraints=critical,CA:true,pathlen:0\nkeyUsage=critical,digitalSignature,keyCertSign,cRLSign\nauthorityInfoAccess=OCSP;URI:%s' "$OCSP_URL")
# Function to generate a server certificate and add it to the certificate database
# Parameters:
# $1: instance number (1 or 2 for xmpp1/xmpp2)
generate_server_cert() {
local instance=$1
# Generate server private key and CSR
openssl genrsa -out "${CERT_DIR}/server${instance}.key" 2048
# Create OpenSSL config file for SAN support
cat > "${CERT_DIR}/server${instance}.cnf" << EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
C = GB
ST = London
L = London
O = Test Openfire
CN = xmpp${instance}.localhost.example
[v3_req]
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = xmpp${instance}.localhost.example
DNS.2 = *.xmpp${instance}.localhost.example
EOF
# Generate CSR with the config file
openssl req -new -key "${CERT_DIR}/server${instance}.key" \
-out "${CERT_DIR}/server${instance}.csr" \
-config "${CERT_DIR}/server${instance}.cnf"
# Create OpenSSL config for certificate signing
cat > "${CERT_DIR}/server${instance}_sign.cnf" << EOF
basicConstraints = critical,CA:false
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
authorityInfoAccess = OCSP;URI:${OCSP_URL}
[alt_names]
DNS.1 = xmpp${instance}.localhost.example
DNS.2 = *.xmpp${instance}.localhost.example
EOF
# Sign the server certificate with the Intermediate CA
openssl x509 -req -in "${CERT_DIR}/server${instance}.csr" \
-CA "${CERT_DIR}/ca/intermediate-ca/intermediate.crt" \
-CAkey "${CERT_DIR}/ca/intermediate-ca/private/intermediate.key" -CAcreateserial \
-out "${CERT_DIR}/server${instance}.crt" -days 365 \
-extfile "${CERT_DIR}/server${instance}_sign.cnf"
# Create the full certificate chain
cat "${CERT_DIR}/server${instance}.crt" "${CERT_DIR}/ca/intermediate-ca/intermediate.crt" "${CERT_DIR}/ca/root-ca/ca.crt" > "${CERT_DIR}/chain${instance}.pem"
# Add to certificate database
SERIAL=$(openssl x509 -in "${CERT_DIR}/server${instance}.crt" -noout -serial | cut -d'=' -f2)
SUBJECT=$(openssl x509 -in "${CERT_DIR}/server${instance}.crt" -noout -subject | cut -d'=' -f2-)
printf 'V\t%s\t\t%s\tunknown\t%s\n' "$(date -u +%y%m%d%H%M%SZ)" "$SERIAL" "$SUBJECT" >> "${CERT_DIR}/ca/intermediate-ca/index.txt"
# Clean up temporary config files
rm -f "${CERT_DIR}/server${instance}.cnf" "${CERT_DIR}/server${instance}_sign.cnf"
# Display the certificate's subject and SANs for verification
echo "Certificate generated for instance ${instance}:"
openssl x509 -in "${CERT_DIR}/server${instance}.crt" -noout -subject -ext subjectAltName
}
# Generate the OCSP responder certificate
# This certificate will be used to sign OCSP responses
openssl genrsa -out "${CERT_DIR}/ca/ocsp-responder/ocsp.key" 2048
openssl req -new -key "${CERT_DIR}/ca/ocsp-responder/ocsp.key" \
-out "${CERT_DIR}/ca/ocsp-responder/ocsp.csr" \
-subj "/C=GB/ST=London/L=London/O=Test Openfire/CN=ocsp.example.com"
# Sign the OCSP responder certificate
# The certificate includes:
# - Non-CA status (basicConstraints)
# - OCSP signing capability (extendedKeyUsage)
openssl x509 -req -in "${CERT_DIR}/ca/ocsp-responder/ocsp.csr" \
-CA "${CERT_DIR}/ca/intermediate-ca/intermediate.crt" \
-CAkey "${CERT_DIR}/ca/intermediate-ca/private/intermediate.key" -CAcreateserial \
-out "${CERT_DIR}/ca/ocsp-responder/ocsp.crt" -days 365 \
-extfile <(printf 'basicConstraints=critical,CA:false\nkeyUsage=critical,digitalSignature\nextendedKeyUsage=OCSPSigning')
# Generate certificates for both Openfire instances
generate_server_cert 1
generate_server_cert 2
# Verify the certificate chain for all generated certificates
# This ensures that all certificates are properly signed and trusted
echo "Verifying certificates..."
openssl verify -CAfile "${CERT_DIR}/ca/root-ca/ca.crt" "${CERT_DIR}/ca/intermediate-ca/intermediate.crt"
openssl verify -CAfile <(cat "${CERT_DIR}/ca/root-ca/ca.crt" "${CERT_DIR}/ca/intermediate-ca/intermediate.crt") "${CERT_DIR}/server1.crt"
openssl verify -CAfile <(cat "${CERT_DIR}/ca/root-ca/ca.crt" "${CERT_DIR}/ca/intermediate-ca/intermediate.crt") "${CERT_DIR}/server2.crt"
openssl verify -CAfile <(cat "${CERT_DIR}/ca/root-ca/ca.crt" "${CERT_DIR}/ca/intermediate-ca/intermediate.crt") "${CERT_DIR}/ca/ocsp-responder/ocsp.crt"
printf '\nCertificate generation complete.'
printf '\nOCSP URL configured as: %s\n\n' "$OCSP_URL"

View File

@ -0,0 +1,126 @@
#!/bin/bash
# Default password for all keystores and truststores
# This is Openfire's default password - changing it would require updating security.xml
KEYSTORE_PASSWORD="changeit"
# Base directory where certificates were generated
# This should match the CERT_DIR from the certificate generation script
CERT_DIR="./_data/certs"
# Function to import certificates and set up keystores/truststores for one Openfire instance
# Parameters:
# $1: instance number (1 or 2 for xmpp1/xmpp2)
import_certificates() {
local instance=$1
# Directory where Openfire expects to find its certificates
local conf_dir="_data/xmpp/${instance}/conf/security"
echo "Importing certificates for Openfire instance ${instance}..."
# Ensure the security directory exists
mkdir -p "${conf_dir}"
# Remove any existing keystore to start fresh
rm -f "${conf_dir}/keystore"
echo "Creating new keystore for instance ${instance}"
# Create a new empty keystore by generating and immediately deleting a temporary keypair
# This ensures the keystore is properly initialized with the correct format
keytool -genkeypair \
-keystore "${conf_dir}/keystore" \
-storepass "${KEYSTORE_PASSWORD}" \
-keypass "${KEYSTORE_PASSWORD}" \
-alias "default" \
-dname "CN=temporary" \
-keyalg RSA \
-validity 1
# Remove the temporary keypair, leaving an empty keystore
keytool -delete \
-alias "default" \
-keystore "${conf_dir}/keystore" \
-storepass "${KEYSTORE_PASSWORD}"
# Import the server's certificate and private key
# First convert them to PKCS12 format which can be imported into a Java keystore
echo "Importing server certificate and private key..."
openssl pkcs12 -export \
-in "${CERT_DIR}/server${instance}.crt" \
-inkey "${CERT_DIR}/server${instance}.key" \
-chain \
-CAfile "${CERT_DIR}/chain${instance}.pem" \
-name "xmpp${instance}.localhost.example" \
-out "${CERT_DIR}/server${instance}.p12" \
-password "pass:${KEYSTORE_PASSWORD}"
# Import the PKCS12 file into the keystore
keytool -importkeystore \
-deststorepass "${KEYSTORE_PASSWORD}" \
-destkeypass "${KEYSTORE_PASSWORD}" \
-destkeystore "${conf_dir}/keystore" \
-srckeystore "${CERT_DIR}/server${instance}.p12" \
-srcstoretype PKCS12 \
-srcstorepass "${KEYSTORE_PASSWORD}" \
-alias "xmpp${instance}.localhost.example"
# Create or update truststore
# truststore - used by the server to verify other servers
if [ ! -f "${conf_dir}/truststore" ]; then
echo "Creating new truststore for instance ${instance}"
# Initialize a new truststore with a dummy entry
keytool -importpass \
-keystore "${conf_dir}/truststore" \
-storepass "${KEYSTORE_PASSWORD}" \
-storetype JKS \
-alias "init" \
-noprompt
fi
# Add our CA certificates to truststore
# We preserve existing entries to maintain trust for other certificates
echo "Adding CA certificates to truststore..."
# Import root CA if not already present
# grep -q checks if the alias already exists in the store
if ! keytool -list -keystore "${conf_dir}/truststore" -storepass "${KEYSTORE_PASSWORD}" | grep -q "root-ca"; then
keytool -import -noprompt \
-keystore "${conf_dir}/truststore" \
-storepass "${KEYSTORE_PASSWORD}" \
-alias "root-ca" \
-file "${CERT_DIR}/ca/root-ca/ca.crt"
echo "Added root CA to truststore"
else
echo "Root CA already exists in truststore"
fi
# Import intermediate CA if not already present
if ! keytool -list -keystore "${conf_dir}/truststore" -storepass "${KEYSTORE_PASSWORD}" | grep -q "intermediate-ca"; then
keytool -import -noprompt \
-keystore "${conf_dir}/truststore" \
-storepass "${KEYSTORE_PASSWORD}" \
-alias "intermediate-ca" \
-file "${CERT_DIR}/ca/intermediate-ca/intermediate.crt"
echo "Added intermediate CA to truststore"
else
echo "Intermediate CA already exists in truststore"
fi
echo "Certificate import completed for instance ${instance}"
}
# Main script execution
echo "Starting certificate import process..."
# Import certificates for both Openfire instances
import_certificates 1
import_certificates 2
# Clean up temporary PKCS12 files that were created during the import process
rm -f "${CERT_DIR}"/*.p12
# Display commands that can be used to verify the keystore contents
echo -e "\nTo verify the keystores, run:"
echo "keytool -list -keystore _data/xmpp/1/conf/security/keystore -storepass ${KEYSTORE_PASSWORD}"
echo "keytool -list -keystore _data/xmpp/1/conf/security/truststore -storepass ${KEYSTORE_PASSWORD}"

View File

@ -1,8 +1,9 @@
#!/bin/bash
usage() { echo "Usage: $0 [-n openfire-tag] [-6] [-h]
usage() { echo "Usage: $0 [-n openfire-tag] [-6] [-o] [-h]
-n openfire-tag Launches all Openfire instances with the specified tag. This overrides the value in .env
-6 Replace standard IPv4-based bridge networking with IPv6.
-o Enable OCSP support, generates compatible certificates, & deploys associated OCSP responder
-h Show this helpful information
"; exit 0; }
@ -20,7 +21,7 @@ source "$SCRIPTPATH/../_common/functions.sh"
check_deps
while getopts n:6h o; do
while getopts n:6oh o; do
case "$o" in
n)
if [[ $OPTARG =~ " " ]]; then
@ -34,6 +35,10 @@ while getopts n:6h o; do
echo "Using IPv6"
NETWORK_COMPOSE_FILE="docker-compose-network-dualstack.yml"
;;
o)
echo "Enabling OCSP support"
export ENABLE_OCSP=true
;;
h)
usage
;;
@ -60,5 +65,12 @@ mkdir _data
cp -r xmpp _data/
cp -r plugins _data/
if [ "$ENABLE_OCSP" = true ]; then
echo "Enabling OCSP support"
"$SCRIPTPATH"/scripts/generate-certificates.sh
"$SCRIPTPATH"/scripts/import-certificates.sh
COMPOSE_FILE_COMMAND+=("-f" "docker-compose-ocsp-responder.yml")
fi
"${COMPOSE_FILE_COMMAND[@]}" up -d || popd
popd