mTLS Setup

Setting up keys and certificates

Overview

You'll need to:

  1. Generate a private key and Certificate Signing Request (CSR)
  2. Obtain an Organization Validated (OV) SSL certificate from a recognized authority

Prerequisites

  • OpenSSL installed on your system
  • Administrative access to your domain's DNS
  • Company registration documents (for OV validation)

Step 1: Generate Private Key and CSR

1.1 Generate Private Key

# Generate a 4096-bit RSA private key
openssl genrsa -out server.key 4096

# IMPORTANT: Keep this key secure! Never share it or commit it to version control
chmod 600 server.key

1.2 Generate Certificate Signing Request (CSR)

# Generate the CSR using your private key
openssl req -new -key server.key -out server.csr

You'll be prompted for the following information:

Country Name (2 letter code) [AU]: BE
State or Province Name (full name) [Some-State]: Brabant Wallon
Locality Name (eg, city) []: Mont-Saint-Guibert
Organization Name (eg, company) [Internet Widgits Pty Ltd]: Your Company Legal Name
Organizational Unit Name (eg, section) []: IT Department
Common Name (e.g. server FQDN or YOUR name) []: webhook.yourcompany.com
Email Address []: [email protected]

# Optional fields (press Enter to skip):
A challenge password []:
An optional company name []:

⚠️ Critical Information:

  • Common Name (CN): Must be your exact domain name (e.g., webhook.yourcompany.com)
  • Organization Name: Must be your legal company name (will be verified)
  • Email: Use an email address that can receive validation emails

1.3 Verify Your CSR

# Check CSR contents
openssl req -in server.csr -noout -text

Step 2: Obtain OV SSL Certificate

2.1 Purchase Certificate

  1. Visit a OV certificate reseller (we generally use GlobalSign: GlobalSign SSL Certificates, if you would like to use another one, no problem: just let us know)
  2. Select "Organization Validated (OV) SSL"
  3. Choose certificate duration (1-2 years recommended)
  4. Create an account or log in

2.2 Submit Your CSR

  1. Copy the entire contents of your CSR file:
    cat server.csr
  2. Paste the CSR submission form (including the BEGIN/END lines)
  3. Complete the order form with company information

2.3 Complete Organization Validation

Normally the certificate authority will verify:

  • Domain ownership (via DNS, email, or HTTP file)
  • Organization legitimacy (business registration, phone verification)

Domain Validation Options:

When using GlobalSign as an example:

Option A - DNS Validation (Recommended)

# Add TXT record to your DNS:
Host: _globalsign-domain-verification
Value: globalsign-verification=XXXXXXXXXXXXX

Option B - HTTP File Validation

# Create verification file at:
http://webhook.yourcompany.com/.well-known/pki-validation/globalsign.txt

Option C - Email Validation

  • GlobalSign sends email to: admin@, webmaster@, or postmaster@ your domain

2.4 Download Certificates

After approval (typically 1-3 business days), you'll receive:

  • server.crt - Your server certificate
  • intermediate.crt - intermediate certificate
  • Installation instructions

Setting up a mTLS client for calling our services

Step 1: Install Certificates

1.1 Create Certificate Bundle

# Combine certificates in correct order
cat server.crt intermediate.crt > server-bundle.crt

# Verify the certificate chain
openssl verify -CAfile intermediate.crt server.crt

1.2 Test Certificate

# Check certificate details
openssl x509 -in server-bundle.crt -text -noout | grep -A2 "Subject:"

# Test certificate and key match
openssl x509 -noout -modulus -in server-bundle.crt | openssl md5
openssl rsa -noout -modulus -in server.key | openssl md5
# Both commands should return the same MD5 hash

Step 2: Provide us the necessary verification info

  • The necessary certificate(s) to add to our trust store
  • The full Subject to verify against

Step 3 (Optional): Example of calling our service

(Coming soon)

Setting up a mTLS server for webhook reception

Step 1: Obtain client certificate

You can obtain the four OV certificates to be added to you trust store here: https://support.globalsign.com/ca-certificates/intermediate-certificates/organizationssl-intermediate-certificates

Step 2: (Optional) Example of webhook server doing client verification

Create a file named webhook-server.go:

package main

import (
	"crypto/tls"
	"crypto/x509"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"time"
)

func webhookHandler(w http.ResponseWriter, r *http.Request) {
	log.Printf("Received %s request from %s", r.Method, r.RemoteAddr)

	if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
		w.WriteHeader(http.StatusUnauthorized)
		io.WriteString(w, "mTLS authentication not provided")
		return
	}

	cert := r.TLS.PeerCertificates[0]
	if strings.Compare("cn=,...", cert.Subject.String()) != 0 {
		w.WriteHeader(http.StatusForbidden)
		io.WriteString(w, "mTLS authentication failed")
		return
	}

	w.WriteHeader(http.StatusOK)
	io.WriteString(w, "Webhook received successfully\n")
}

func loadAndAppendCerts(paths []string, certPool *x509.CertPool) {
	for _, path := range paths {
		cert, err := os.ReadFile(path)
		if err != nil {
			log.Fatalf("Error loading cert from path %s: %v", path, err)
		}
		if !certPool.AppendCertsFromPEM(cert) {
			log.Fatalf("Unable to add cert from path %s to cert pool", path)
		}
	}
}

func main() {

	// Define routes
	handler := http.NewServeMux()
	handler.HandleFunc("/webhook", webhookHandler)
	handler.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		io.WriteString(w, "OK\n")
	})

	// Configure TLS
	tlsConfig := &tls.Config{
		MinVersion: tls.VersionTLS12,
	}

	// Load all the necessary certs
	caCertPool := x509.NewCertPool()
	certPaths := []string{"client-ca1.pem", "client-ca2.pem"}
	loadAndAppendCerts(certPaths, caCertPool)

	// Activate cryptographic security of the client certs
	tlsConfig.ClientCAs = caCertPool
	tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert

	// Configure the server
	server := &http.Server{
		Addr:         ":8443",
		Handler:      handler,
		TLSConfig:    tlsConfig,
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 15 * time.Second,
	}

	log.Println("Starting HTTPS webhook server on :8443")
	log.Println("Endpoints:")
	log.Println("  - https://localhost:8443/webhook")
	log.Println("  - https://localhost:8443/health")

	// Start the server with its server certs
	if err := server.ListenAndServeTLS("server-bundle.crt", "server.key"); err != nil {
		log.Fatalf("Server failed: %v", err)
	}
}

You can run the server:

# Install Go if not already installed
# Download from https://golang.org/dl/

# Run the webhook server
go run webhook-server.go

3. Setup your webhook configuration

3.1 Manually enable mTLS

Contact support via https://support.digiteal.eu or email us at [email protected] asking us to enable all webhooks to be received via mTLS and we will manually set a flag in our system enabling this

3.2 Add the url in the webhook config

If the mTLS port is different form the default port, please specify it in the request:

curl --request POST \
     --url https://api-test.digiteal.eu/api/v1/webhook \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "type": "PEPPOL_INVOICE_RECEIVED",
  "url": "https://api.mycompany.org:8443"
}
'

4. End-to-end test

General troubleshooting

Certificate Issues

# Verify certificate is valid
openssl x509 -in server-bundle.crt -text -noout

# Check certificate expiration
openssl x509 -in server-bundle.crt -noout -enddate

# Test SSL connection
openssl s_client -connect webhook.yourcompany.com:8443 -servername webhook.yourcompany.com

Common Problems

IssueSolution
"x509: certificate signed by unknown authority"Ensure you're using the bundled certificate with intermediate CA
"tls: private key does not match public key"Regenerate CSR with the correct private key
Connection refusedCheck firewall rules for port 8443
Certificate name mismatchEnsure CN in certificate matches your domain

Certificate Renewal

⚠️ Important: OV SSL certificates typically expire after 1-2 years.

Set Renewal Reminder

# Check expiration date
openssl x509 -in server-bundle.crt -noout -enddate

# Add to calendar 30 days before expiration

Renewal Process

  1. Generate new CSR (can reuse existing private key)
  2. Submit renewal request to your certificate authority
  3. Complete validation (usually faster for renewals)
  4. Replace certificates and restart server