Scenario 6.3: Delta CRL

Category: Revocation
Complexity: ⭐⭐⭐⭐ (High)
Prerequisites: Base CRL available
Estimated Time: 20-30 Minutes


Description

This scenario describes the creation of Delta CRLs (RFC 5280 section 5.2.4). Delta CRLs contain only changes since the last base CRL, enabling more efficient updates.

Advantages:

Disadvantages:


Workflow

flowchart TD BASE[Base CRL] --> DELTA1[Delta CRL 1] DELTA1 --> DELTA2[Delta CRL 2] DELTA2 --> DELTA3[Delta CRL 3] DELTA3 --> NEW_BASE[New Base CRL] NEW_BASE --> DELTA4[Delta CRL 4] CLIENT[Client] --> |Download| BASE CLIENT --> |Updates| DELTA1 CLIENT --> |Updates| DELTA2 style BASE fill:#e3f2fd style NEW_BASE fill:#e3f2fd style DELTA1 fill:#fff3e0 style DELTA2 fill:#fff3e0 style DELTA3 fill:#fff3e0 style DELTA4 fill:#fff3e0


Code Example: Create Delta CRL

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using System.Numerics;
 
using var ctx = PqCryptoContext.Initialize();
 
// Load CA
var caCert = ctx.LoadCertificate("intermediate-ca.crt.pem");
var caKey = ctx.LoadPrivateKey("intermediate-ca.key.pem", "CaPassword!");
 
// Load base CRL
var baseCrl = ctx.ParseCrl(File.ReadAllBytes("intermediate-ca-base.crl"));
var baseCrlNumber = baseCrl.CrlNumber;
 
// Delta CRL builder
var deltaCrlBuilder = new CertificateRevocationListBuilder();
 
// Add only NEW revocations since base CRL
var newRevocations = GetRevocationsSince(baseCrl.ThisUpdate);
foreach (var rev in newRevocations)
{
    deltaCrlBuilder.AddEntry(
        rev.SerialNumber,
        rev.RevocationTime,
        rev.Reason
    );
}
 
// Optional: Remove certificates from "Hold"
var removedFromHold = GetRemovedFromHold(baseCrl.ThisUpdate);
foreach (var serial in removedFromHold)
{
    deltaCrlBuilder.AddEntry(
        serial,
        DateTimeOffset.UtcNow,
        X509RevocationReason.RemoveFromCrl  // Code 8
    );
}
 
// Delta CRL extensions
deltaCrlBuilder.AddExtension(
    oid: "2.5.29.27",  // Delta CRL Indicator
    critical: true,
    value: EncodeDeltaCrlIndicator(baseCrlNumber)
);
 
// Generate Delta CRL
byte[] deltaCrlBytes = deltaCrlBuilder.Build(
    issuerCertificate: caCert,
    crlNumber: baseCrlNumber + 10,  // Delta numbers between base numbers
    nextUpdate: DateTimeOffset.UtcNow.AddHours(4),  // More frequent than base
    hashAlgorithm: HashAlgorithmName.SHA256,
    mode: CryptoMode.Hybrid
);
 
File.WriteAllBytes("intermediate-ca-delta.crl", deltaCrlBytes);
 
Console.WriteLine($"Delta CRL created:");
Console.WriteLine($"  Base CRL Number: {baseCrlNumber}");
Console.WriteLine($"  Delta CRL Number: {baseCrlNumber + 10}");
Console.WriteLine($"  New Entries: {newRevocations.Count}");
Console.WriteLine($"  Removed from Hold: {removedFromHold.Count}");

Delta CRL Indicator Extension

private byte[] EncodeDeltaCrlIndicator(BigInteger baseCrlNumber)
{
    // Delta CRL Indicator is simply the base CRL number as INTEGER
    var writer = new AsnWriter(AsnEncodingRules.DER);
    writer.WriteInteger(baseCrlNumber);
    return writer.Encode();
}

Base CRL with Delta CRL Support

// Base CRL must reference Delta CRLs
var baseCrlBuilder = new CertificateRevocationListBuilder();
 
// Add all revoked certificates
foreach (var rev in allRevocations)
{
    baseCrlBuilder.AddEntry(rev.SerialNumber, rev.RevocationTime, rev.Reason);
}
 
// Freshest CRL Extension (points to Delta CRL)
baseCrlBuilder.AddExtension(
    oid: "2.5.29.46",  // Freshest CRL (Delta CRL Distribution Point)
    critical: false,
    value: EncodeFreshestCrl("http://crl.example.com/intermediate-delta.crl")
);
 
byte[] baseCrlBytes = baseCrlBuilder.Build(
    issuerCertificate: caCert,
    crlNumber: BigInteger.Parse("1000"),
    nextUpdate: DateTimeOffset.UtcNow.AddDays(7),  // Longer validity
    hashAlgorithm: HashAlgorithmName.SHA256,
    mode: CryptoMode.Hybrid
);
 
File.WriteAllBytes("intermediate-ca-base.crl", baseCrlBytes);

Client-Side Delta CRL Processing

public class DeltaCrlProcessor
{
    public CombinedRevocationList CombineCrls(
        byte[] baseCrlBytes,
        byte[] deltaCrlBytes,
        PqCryptoContext ctx)
    {
        var baseCrl = ctx.ParseCrl(baseCrlBytes);
        var deltaCrl = ctx.ParseCrl(deltaCrlBytes);
 
        // Check if delta matches base
        var deltaIndicator = GetDeltaCrlIndicator(deltaCrl);
        if (deltaIndicator != baseCrl.CrlNumber)
        {
            throw new InvalidOperationException(
                $"Delta CRL (Indicator: {deltaIndicator}) does not match base CRL ({baseCrl.CrlNumber})"
            );
        }
 
        // Create combined list
        var combined = new CombinedRevocationList
        {
            BaseCrlNumber = baseCrl.CrlNumber,
            DeltaCrlNumber = deltaCrl.CrlNumber,
            ThisUpdate = deltaCrl.ThisUpdate,  // Delta is more current
            NextUpdate = deltaCrl.NextUpdate,
            Entries = new Dictionary<string, RevocationEntry>()
        };
 
        // Copy base entries
        foreach (var entry in baseCrl.Entries)
        {
            combined.Entries[entry.SerialNumber] = entry;
        }
 
        // Apply delta entries
        foreach (var entry in deltaCrl.Entries)
        {
            if (entry.Reason == X509RevocationReason.RemoveFromCrl)
            {
                // Remove from CRL (hold released)
                combined.Entries.Remove(entry.SerialNumber);
            }
            else
            {
                // Add or update
                combined.Entries[entry.SerialNumber] = entry;
            }
        }
 
        return combined;
    }
 
    public bool IsRevoked(string serialNumber, CombinedRevocationList crl)
    {
        return crl.Entries.ContainsKey(serialNumber);
    }
}

Automated Delta CRL Cycle

public class DeltaCrlScheduler
{
    private readonly TimeSpan _baseCrlInterval = TimeSpan.FromDays(7);
    private readonly TimeSpan _deltaCrlInterval = TimeSpan.FromHours(4);
    private BigInteger _currentBaseCrlNumber = 1000;
    private BigInteger _currentDeltaNumber = 0;
 
    public async Task RunScheduler(CancellationToken cancellationToken)
    {
        var lastBaseCrl = DateTimeOffset.UtcNow;
 
        while (!cancellationToken.IsCancellationRequested)
        {
            if (DateTimeOffset.UtcNow - lastBaseCrl >= _baseCrlInterval)
            {
                // Time for new base CRL
                await CreateBaseCrl();
                lastBaseCrl = DateTimeOffset.UtcNow;
                _currentBaseCrlNumber += 100;
                _currentDeltaNumber = 0;
            }
            else
            {
                // Create Delta CRL
                await CreateDeltaCrl();
                _currentDeltaNumber++;
            }
 
            await Task.Delay(_deltaCrlInterval, cancellationToken);
        }
    }
 
    private async Task CreateBaseCrl()
    {
        Console.WriteLine($"Creating base CRL #{_currentBaseCrlNumber}");
        // ... Base CRL logic
    }
 
    private async Task CreateDeltaCrl()
    {
        var deltaCrlNumber = _currentBaseCrlNumber + _currentDeltaNumber;
        Console.WriteLine($"Creating Delta CRL #{deltaCrlNumber} (Base: {_currentBaseCrlNumber})");
        // ... Delta CRL logic
    }
}

Industry-Specific Delta CRL Cycles

Industry Base CRL Delta CRL Recommendation
WebPKI 7 days 4 hours Optional, OCSP preferred
Enterprise 24 hours 1 hour Recommended
Financial Sector 12 hours 15 minutes Mandatory
Energy/SCADA 7 days 24 hours Depending on connectivity

Relationship Scenario Description
Prerequisite 6.1 Create CRL Base CRL
Alternative 6.2 OCSP Responder Real-time status
Related 5.3 Revocation Check Client verification

« ← 6.2 OCSP Responder | ↑ Revocation Overview | 6.4 Revoke Certificate → »


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional