Client certificate authentication

Client certificate authentication is an optional feature in Media Gateway and can be used only if HTTPS is enabled (see HTTPS). Only signed by CA client certificates can be used. Certificates should be in PEM format. CRLs are supported but they are not mandatory.

The server uses a store with trusted X509 certificates and CRLs to verify peer certificates. The store automatically (without a server restart) loads certificates and CRLs from the specified directory. Certificates and CRLs should be added to the directory in accordance with X509_LOOKUP_hash_dir method requirements. If CRLs are enabled for each certificate at least one CRL must be in the directory. The CRL may contain no revoked certificates. A new CRL must be added when the previous CRL is expired.

Prerequisites

  • Docker

  • Docker Compose

  • openssl

  • curl

Configuring Media Gateway

To enable client certificate authentication in Media Gateway both server and client configuration should be updated.

server with enabled CRLs
"tls": {
    // see HTTPS
    "peers": {
        "lookup_hash_directory" : "/etc/certs/lookup-hash-dir",
        "crl_enabled": true
    }
}
server with disabled CRLs
"tls": {
    // see HTTPS
    "peers": {
        "lookup_hash_directory" : "/etc/certs/lookup-hash-dir",
        "crl_enabled": false
    }
}
client
"tls": {
    // see HTTPS
    "identity": {
        "certificate": "client.crt",
        "key": "client.key"
    }
}

where

  • /etc/certs/lookup-hash-dir is a directory with CA certificates and CRLs.

  • client.crt is a file with a client certificate in PEM format.

  • client.key is a file with a PEM encoded PKCS #8 formatted client key.

Generating certificates and CRLs

This section describes how to generate certificates and CLRs signed by a private CA using OpenSSL. Provided instructions specifies the minimum required information only. For production usage see OpenSSL documentation.

Creating a private CA

Create directories

CA_DIR="$(pwd)/ca"

mkdir "${CA_DIR}" "${CA_DIR}/certs" "${CA_DIR}/crl"

Prepare a CA database

touch "${CA_DIR}/index.txt"

echo 01 > "${CA_DIR}/serial"

echo 1000 > "${CA_DIR}/crlnumber"

Prepare a CA configuration file

echo "[ ca ]

default_ca      = CA_default

[ CA_default ]

dir             = ${CA_DIR}
certificate     = \$dir/ca.crt
private_key     = \$dir/ca.key
database        = \$dir/index.txt
new_certs_dir   = \$dir/certs
serial          = \$dir/serial
crl_dir         = \$dir/crl
crl             = \$dir/crl/ca.crl
crlnumber       = \$dir/crlnumber

x509_extensions = v3_ca
crl_extensions  = crl_ext

name_opt        = ca_default
cert_opt        = ca_default

default_days     = 365
default_crl_days = 30
default_md       = default
preserve         = no
policy           = policy_any

[ policy_any ]
countryName            = optional
stateOrProvinceName    = optional
organizationName       = optional
organizationalUnitName = optional
commonName             = supplied
emailAddress           = optional

####################################################################

[ req ]
default_bits       = 2048
default_keyfile    = privkey.pem
distinguished_name = req_distinguished_name
attributes         = req_attributes
x509_extensions    = v3_ca

[ req_distinguished_name ]
countryName                    = Country Name (2 letter code)
countryName_default            = US
countryName_min                = 2
countryName_max                = 2
stateOrProvinceName            = State or Province Name (full name)
stateOrProvinceName_default    =
localityName                   = Locality Name (eg, city)
localityName+default           =
0.organizationName             = Organization Name (eg, company)
0.organizationName_default     =
organizationalUnitName         = Organizational Unit Name (eg, section)
organizationalUnitName_default =
commonName                     = Common Name (e.g. server FQDN or YOUR name)
commonName_max                 = 64
emailAddress                   = Email Address
emailAddress_max               = 64

[ req_attributes ]
challengePassword     = A challenge password
challengePassword_min = 4
challengePassword_max = 20
unstructuredName      = An optional company name

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ crl_ext ]
authorityKeyIdentifier=keyid:always
" > "${CA_DIR}/ca.conf"

Generate a CA private key and certificate

openssl genpkey -algorithm RSA -out "${CA_DIR}/ca.key"

openssl req -new -x509 -days 365 -config "${CA_DIR}/ca.conf" -key "${CA_DIR}/ca.key" -out "${CA_DIR}/ca.crt" -subj "/CN=media-gateway-ca"

Generating a server certificate

Generate a private key and certificate signing request

openssl genpkey -algorithm RSA -out "${CA_DIR}/certs/server.key"

openssl req -new -key "${CA_DIR}/certs/server.key" -out "${CA_DIR}/certs/server.csr" -subj "/CN=media-gateway-server"

If the client connects to the server by IP generate a certificate with IP subject alternative name. Otherwise generate a certificate with DNS subject alternative name.

In commands below replace 192.168.0.108 and media-gateway-server with your values.

IP SAN
export HOST_IP="192.168.0.108"

openssl ca -config "${CA_DIR}/ca.conf" -in "${CA_DIR}/certs/server.csr" -out "${CA_DIR}/certs/server.crt" -extfile <(echo "basicConstraints=CA:FALSE
nsComment=\"OpenSSL Generated Certificate\"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=IP:${HOST_IP}")
DNS SAN
export MEDIA_GATEWAY_SERVER_DNS="media-gateway-server"

openssl ca -config "${CA_DIR}/ca.conf" -in "${CA_DIR}/certs/server.csr" -out "${CA_DIR}/certs/server.crt" -extfile <(echo "basicConstraints=CA:FALSE
nsComment=\"OpenSSL Generated Certificate\"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=DNS:${MEDIA_GATEWAY_SERVER_DNS}")

Generating a client certificate

Generate a private key, certificate signing request and a certificate

openssl genpkey -algorithm RSA -out "${CA_DIR}/certs/client.key"

openssl req -new -key "${CA_DIR}/certs/client.key" -out "${CA_DIR}/certs/client.csr" -subj "/CN=media-gateway-client"

openssl ca -config "${CA_DIR}/ca.conf" -in "${CA_DIR}/certs/client.csr" -out "${CA_DIR}/certs/client.crt" -extfile <(echo 'basicConstraints=CA:FALSE
nsComment="OpenSSL Generated Certificate"
subjectKeyIdentifier=hash
keyUsage=critical,nonRepudiation,digitalSignature,keyEncipherment
extendedKeyUsage=clientAuth
authorityKeyIdentifier=keyid,issuer')

Preparing X509 lookup hash directory

OpenSSL documentation

X509_LOOKUP_hash_dir is a more advanced method, which loads certificates and CRLs on demand, and caches them in memory once they are loaded. As of OpenSSL 1.0.0, it also checks for newer CRLs upon each lookup, so that newer CRLs are as soon as they appear in the directory.

The directory should contain one certificate or CRL per file in PEM format, with a filename of the form hash.N for a certificate, or hash.rN for a CRL. The hash is the value returned by the X509_NAME_hash(3) function applied to the subject name for certificates or issuer name for CRLs. The hash can also be obtained via the -hash option of the x509(1) or crl(1) commands.

The .N or .rN suffix is a sequence number that starts at zero, and is incremented consecutively for each certificate or CRL with the same hash value. Gaps in the sequence numbers are not supported, it is assumed that there are no more objects with the same hash beyond the first missing number in the sequence.

Sequence numbers make it possible for the directory to contain multiple certificates with same subject name hash value. For example, it is possible to have in the store several certificates with same subject or several CRLs with same issuer (and, for example, different validity period).

When checking for new CRLs once one CRL for given hash value is loaded, hash_dir lookup method checks only for certificates with sequence number greater than that of the already cached CRL.

Create a directory

mkdir "${CA_DIR}/lookup-hash-dir"

Add the CA certificate to the directory

CA_HASH=$(openssl x509 -in "${CA_DIR}/ca.crt" -subject_hash -noout)

cp "${CA_DIR}/ca.crt" "${CA_DIR}/lookup-hash-dir/$CA_HASH.0"

If CRLs are used generate and add an empty CRL

openssl ca -config "${CA_DIR}/ca.conf" -gencrl -out "${CA_DIR}/crl/ca.crl"

CRL_HASH=$(openssl crl -in "${CA_DIR}/crl/ca.crl" -hash -noout)

cp "${CA_DIR}/crl/ca.crl" "${CA_DIR}/lookup-hash-dir/$CRL_HASH.r0"

Revoking certificates

Omit this section if CRLs are not used or Media Gateway has not been launched.

Revoke a client certificate

openssl ca -config "${CA_DIR}/ca.conf" -revoke "${CA_DIR}/certs/client.crt"

Generate a new CRL and update X509 lookup hash directory.

Warning

The sequence number N in the filename of the form hash.rN must be increased each time.

openssl ca -config "${CA_DIR}/ca.conf" -gencrl -out "${CA_DIR}/crl/ca.crl"

CRL_HASH=$(openssl crl -in "${CA_DIR}/crl/ca.crl" -hash -noout)

cp "${CA_DIR}/crl/ca.crl" "${CA_DIR}/lookup-hash-dir/$CRL_HASH.r1"

Testing

Server

To test the server only a certificate with IP SAN is used.

Prepare the configuration file with enabled CRLs

cat << EOF > media-gateway-server.json
{
    "ip": "0.0.0.0",
    "port": 8080,
    "tls": {
        "identity": {
            "certificate": "/etc/certs/server.crt",
            "key": "/etc/certs/server.key"
        },
        "peers": {
            "lookup_hash_directory": "/etc/certs/lookup-hash-dir",
            "crl_enabled": true
        }
    },
    "out_stream": {
        "url": "pub+bind:ipc:///tmp/server",
        "send_timeout": {
            "secs": 1,
            "nanos": 0
        },
        "send_retries": 3,
        "receive_timeout": {
            "secs": 1,
            "nanos": 0
        },
        "receive_retries": 3,
        "send_hwm": 1000,
        "receive_hwm": 1000,
        "inflight_ops": 100
    }
}
EOF

Launch the server (change the value of MEDIA_GATEWAY_PORT in the command below if required)

x86_64
export MEDIA_GATEWAY_PORT=8080

docker run -d \
    -v $(pwd)/media-gateway-server.json:/opt/etc/custom_config.json \
    -v ${CA_DIR}/certs/server.key:/etc/certs/server.key \
    -v ${CA_DIR}/certs/server.crt:/etc/certs/server.crt \
    -v ${CA_DIR}/lookup-hash-dir:/etc/certs/lookup-hash-dir \
    -p ${MEDIA_GATEWAY_PORT}:8080 \
    --name media-gateway-server \
    ghcr.io/insight-platform/media-gateway-server-x86:latest \
    /opt/etc/custom_config.json
ARM64
export MEDIA_GATEWAY_PORT=8080

docker run -d \
    -v $(pwd)/media-gateway-server.json:/opt/etc/custom_config.json \
    -v ${CA_DIR}/certs/server.key:/etc/certs/server.key \
    -v ${CA_DIR}/certs/server.crt:/etc/certs/server.crt \
    -v ${CA_DIR}/lookup-hash-dir:/etc/certs/lookup-hash-dir \
    -p ${MEDIA_GATEWAY_PORT}:8080 \
    --name media-gateway-server \
    ghcr.io/insight-platform/media-gateway-server-arm64:latest \
    /opt/etc/custom_config.json

Send the request to the server

curl --cacert "${CA_DIR}/ca.crt" --cert "${CA_DIR}/certs/client.crt" --key "${CA_DIR}/certs/client.key" -v https://$HOST_IP:$MEDIA_GATEWAY_PORT/health

HTTP response with 200 OK status code and the body as below should be returned.

{"status": "healthy"}

Revoke the client certificate using the section and send the request to the server again. An error response with the message as below should be returned.

error:0A000414:SSL routines::sslv3 alert certificate revoked

Clean up after testing

docker stop media-gateway-server

docker rm media-gateway-server

rm -rf ca media-gateway-server.json

e2e

To test both server and client based on Usage example

  • generate a certificate with DNS SAN

  • update server_config.json and client_config.json in the downloaded archive as described above and in HTTPS guide

  • add volumes for media-gateway-client` (key and certificate files) and media-gateway-server (key and certificate files) in docker-compose-x86.yaml and docker-compose-arm64.yaml in the downloaded archive

Clean up after testing

rm -rf ca