

# Implement certificate revocation for mutual TLS (viewer) with CloudFront Functions and KeyValueStore
<a name="implement-certificate-revocation"></a>

You can use CloudFront Connection Functions with KeyValueStore to implement certificate revocation checking. This lets you maintain a list of revoked certificate serial numbers and check client certificates against this list during the TLS handshake.

To implement certificate revocation, you need these components:
+ A distribution configured with viewer mTLS
+ A KeyValueStore containing revoked certificate serial numbers
+ A Connection Function that queries the KeyValueStore to check certificate status

When a client connects, CloudFront validates the certificate against the trust store, then runs your Connection Function. Your function checks the certificate serial number against the KeyValueStore and allows or denies the connection.

**Topics**
+ [Step 1: Create a KeyValueStore for revoked certificates](create-kvs-revoked-certificates.md)
+ [Step 2: Create the revocation Connection Function](create-revocation-connection-function.md)
+ [Step 3: Test your revocation function](test-revocation-function.md)
+ [Step 4: Associate the function to your distribution](associate-function-distribution.md)
+ [Advanced revocation scenarios](advanced-revocation-scenarios.md)

# Step 1: Create a KeyValueStore for revoked certificates
<a name="create-kvs-revoked-certificates"></a>

Create a KeyValueStore to store revoked certificate serial numbers that your Connection Function can check during mTLS connections.

First, prepare your revoked certificate serial numbers in JSON format:

```
{
  "data": [
    {
      "key": "ABC123DEF456",
      "value": ""
    },
    {
      "key": "789XYZ012GHI", 
      "value": ""
    }
  ]
}
```

Upload this JSON file to an S3 bucket, then create the KeyValueStore:

```
aws s3 cp revoked-serials.json s3://your-bucket-name/revoked-serials.json
aws cloudfront create-key-value-store \
  --name revoked-serials-kvs \
  --import-source '{
    "SourceType": "S3",
    "SourceARN": "arn:aws:s3:::your-bucket-name/revoked-serials.json"
  }'
```

Wait for the KeyValueStore to finish provisioning. Check the status with:

```
aws cloudfront get-key-value-store --name "revoked-serials-kvs"
```

# Step 2: Create the revocation Connection Function
<a name="create-revocation-connection-function"></a>

Create a Connection Function that checks certificate serial numbers against the KeyValueStore to determine if certificates are revoked.

Create a Connection Function that checks certificate serial numbers against the KeyValueStore:

```
aws cloudfront create-connection-function \
  --name "revocation-control" \
  --connection-function-config file://connection-function-config.json \
  --connection-function-code file://connection-function-code.txt
```

The configuration file specifies the KeyValueStore association:

```
{
  "Runtime": "cloudfront-js-2.0",
  "Comment": "A function that implements revocation control via KVS",
  "KeyValueStoreAssociations": {
    "Quantity": 1,
    "Items": [
      {
        "KeyValueStoreArn": "arn:aws:cloudfront::account-id:key-value-store/kvs-id"
      }
    ]
  }
}
```

The Connection Function code checks the KeyValueStore for revoked certificates:

```
import cf from 'cloudfront';

async function connectionHandler(connection) {
    const kvsHandle = cf.kvs();
    
    // Get parsed client serial number from client certificate
    const clientSerialNumber = connection.clientCertInfo.serialNumber;
    
    // Check KVS to see if serial number exists as a key
    const serialNumberExistsInKvs = await kvsHandle.exists(clientSerialNumber);
    
    // Deny connection if serial number exists in KVS
    if (serialNumberExistsInKvs) {
        console.log("Connection denied - certificate revoked");
        return connection.deny();
    }
    
    // Allow connections that don't exist in kvs
    console.log("Connection allowed");
    return connection.allow();
}
```

# Step 3: Test your revocation function
<a name="test-revocation-function"></a>

Use the CloudFront console to test your Connection Function with sample certificates. Navigate to the Connection Function in the console and use the Test tab.

**Test with sample certificates**

1. Paste a sample certificate in PEM format into the test interface

1. Optionally specify a client IP address for testing IP-based logic

1. Choose **Test function** to see the execution results

1. Review the execution logs to verify your function logic

Test with both valid and revoked certificates to ensure your function handles both scenarios correctly. The execution logs show console.log output and any errors that occur during function execution.

# Step 4: Associate the function to your distribution
<a name="associate-function-distribution"></a>

Once you publish your Connection Function, associate it with your mTLS-enabled distribution to activate certificate revocation checking.

You can associate the function from the distribution settings page or from the Connection Function's associated distributions table. Navigate to your distribution settings, scroll to the **Viewer mutual authentication (mTLS)** section, select your Connection Function, and save the changes.

# Advanced revocation scenarios
<a name="advanced-revocation-scenarios"></a>

For more complex certificate revocation requirements, consider these additional configurations:

**Topics**
+ [Convert Certificate Revocation Lists (CRL) to KeyValueStore format](#convert-crl-kvs-format)
+ [Handle multiple Certificate Authorities](#handle-multiple-cas)
+ [Add custom data to connection logs](#add-custom-data-logs)
+ [Manage CRL updates](#manage-crl-updates)
+ [Plan KeyValueStore capacity](#plan-kvs-capacity)

## Convert Certificate Revocation Lists (CRL) to KeyValueStore format
<a name="convert-crl-kvs-format"></a>

If you have a Certificate Revocation List (CRL) file, you can convert it to KeyValueStore JSON format using OpenSSL and jq:

**Convert CRL to KeyValueStore format**

Extract serial numbers from the CRL file:

```
openssl crl -text -noout -in rfc5280_CRL.crl | \
  awk '/Serial Number:/ {print $3}' | \
  cut -d'=' -f2 | \
  sed 's/../&:/g;s/:$//' >> serialnumbers.txt
```

Convert the serial numbers to KeyValueStore JSON format:

```
jq -R -s 'split("\n") | map(select(length > 0)) | {data: map({"key": ., "value": ""})}' \
  serialnumbers.txt >> serialnumbers_kvs.json
```

Upload the formatted file to S3 and create the KeyValueStore as described in Step 1.

## Handle multiple Certificate Authorities
<a name="handle-multiple-cas"></a>

When your TrustStore contains multiple Certificate Authorities (CAs), include the issuer information in your KeyValueStore keys to avoid conflicts between certificates from different CAs that might have the same serial number.

For multi-CA scenarios, use a combination of the issuer's SHA1 hash and the serial number as the key:

```
import cf from 'cloudfront';

async function connectionHandler(connection) {
    const kvsHandle = cf.kvs();
    const clientCert = connection.clientCertInfo;
    
    // Create composite key with issuer hash and serial number
    const issuer = clientCert.issuer.replace(/[^a-zA-Z0-9]/g, '').substring(0, 20);
    const serialno = clientCert.serialNumber;
    const compositeKey = `${issuer}_${serialno}`;
    
    const cert_revoked = await kvsHandle.exists(compositeKey);
    
    if (cert_revoked) {
        console.log(`Blocking revoked cert: ${serialno} from issuer: ${issuer}`);
        connection.deny();
    } else {
        connection.allow();
    }
}
```

**Note**  
Using issuer identifier \$1 serial number creates longer keys, which may reduce the total number of entries you can store in the KeyValueStore.

## Add custom data to connection logs
<a name="add-custom-data-logs"></a>

Connection functions can add custom data to CloudFront connection logs using the logCustomData method. This lets you include revocation check results, certificate information, or other relevant data in your logs.

```
async function connectionHandler(connection) {
    const kvsHandle = cf.kvs();
    const clientSerialNumber = connection.clientCertInfo.serialNumber;
    const serialNumberExistsInKvs = await kvsHandle.exists(clientSerialNumber);
    
    if (serialNumberExistsInKvs) {
        // Log revocation details to connection logs
        connection.logCustomData(`REVOKED:${clientSerialNumber}:DENIED`);
        console.log("Connection denied - certificate revoked");
        return connection.deny();
    }
    
    // Log successful validation
    connection.logCustomData(`VALID:${clientSerialNumber}:ALLOWED`);
    console.log("Connection allowed");
    return connection.allow();
}
```

Custom data is limited to 800 bytes of valid UTF-8 text. If you exceed this limit, CloudFront truncates the data to the nearest valid UTF-8 boundary.

**Note**  
Custom data logging only works when connection logs are enabled for your distribution. If connection logs are not configured, the logCustomData method is a no-op.

## Manage CRL updates
<a name="manage-crl-updates"></a>

Certificate Authorities can issue two types of CRLs:
+ **Full CRLs**: Contain a complete list of all revoked certificates
+ **Delta CRLs**: Only list certificates revoked since the last full CRL

For full CRL updates, create a new KeyValueStore with the updated data and redirect the Connection Function association to the new KeyValueStore. This approach is simpler than calculating differences and performing incremental updates.

For delta CRL updates, use the update-keys command to add new revoked certificates to the existing KeyValueStore:

```
aws cloudfront update-key-value-store \
  --name "revoked-serials-kvs" \
  --if-match "current-etag" \
  --put file://delta-revoked-serials.json
```

## Plan KeyValueStore capacity
<a name="plan-kvs-capacity"></a>

KeyValueStore has a 5 MB size limit and supports up to 10 million key-value pairs. Plan your revocation list capacity based on your key format and data size:
+ **Serial number only**: Efficient storage for simple revocation checking
+ **Issuer identifier \$1 serial number**: Longer keys for multi-CA environments

For large revocation lists, consider implementing a tiered approach where you maintain separate KeyValueStores for different certificate categories or time periods.