Scenario 4.3: Certificate Archival

Category: Certificate Management
Complexity: ⭐⭐⭐ (Medium)
Prerequisites: Expired/revoked certificates
Estimated Time: 15-20 Minutes


Description

This scenario describes the secure archival of certificates and associated keys. Archival is necessary for:

  • Compliance retention requirements (GDPR, NIS2)
  • Forensic traceability
  • Decryption of historical data
  • Audit evidence

Important: Encryption keys must be archived if encrypted data still exists. Signature keys should NOT be archived (only certificates).


Workflow

flowchart TD CERT[Certificate Expired/Revoked] --> CLASSIFY{Key Type?} CLASSIFY -->|Encryption| ARCHIVE_KEY[Archive Key] CLASSIFY -->|Signature| DESTROY_KEY[Destroy Key] ARCHIVE_KEY --> ENCRYPT[Store Encrypted] DESTROY_KEY --> ARCHIVE_CERT[Archive Certificate Only] ENCRYPT --> STORE[Archive Storage] ARCHIVE_CERT --> STORE STORE --> LOG[Audit Log] style ENCRYPT fill:#fff3e0 style STORE fill:#e8f5e9


Code Example (C#)

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
 
using var ctx = PqCryptoContext.Initialize();
 
// Load certificates to archive
var expiredCert = ctx.LoadCertificate("old-server.crt.pem");
var expiredKey = ctx.LoadPrivateKey("old-server.key.pem", "OldPassword!");
 
// Archive structure
var archive = new CertificateArchive
{
    Certificate = expiredCert,
    ArchivedAt = DateTime.UtcNow,
    OriginalPath = "old-server.crt.pem",
    Reason = ArchiveReason.Expired,
    RetentionUntil = DateTime.UtcNow.AddYears(10),
    Metadata = new Dictionary<string, string>
    {
        ["Subject"] = expiredCert.Subject,
        ["SerialNumber"] = expiredCert.SerialNumber,
        ["NotBefore"] = expiredCert.NotBefore.ToString("O"),
        ["NotAfter"] = expiredCert.NotAfter.ToString("O"),
        ["Thumbprint"] = expiredCert.Thumbprint,
        ["KeyUsage"] = GetKeyUsageString(expiredCert)
    }
};
 
// Archive key only for encryption certificates
if (HasKeyEncipherment(expiredCert))
{
    // Store key encrypted
    var encryptedKey = ctx.EncryptPrivateKey(
        expiredKey,
        archivePassword: "ArchivePassword!SecureVault2024",
        algorithm: PbeAlgorithm.Aes256Cbc,
        prf: PbePrf.HmacSha256,
        iterations: 100000
    );
    archive.EncryptedPrivateKey = encryptedKey;
}
 
// Archive as JSON
var archivePath = $"archive/{expiredCert.SerialNumber}.json";
File.WriteAllText(archivePath, JsonSerializer.Serialize(archive, new JsonSerializerOptions
{
    WriteIndented = true
}));
 
// Audit log
Console.WriteLine($"Archived: {expiredCert.Subject}");
Console.WriteLine($"  Serial: {expiredCert.SerialNumber}");
Console.WriteLine($"  Expiry: {expiredCert.NotAfter:yyyy-MM-dd}");
Console.WriteLine($"  Archive: {archivePath}");
Console.WriteLine($"  Retention until: {archive.RetentionUntil:yyyy-MM-dd}");

Archive Data Structure

public class CertificateArchive
{
    public string Version { get; } = "1.0";
    public DateTime ArchivedAt { get; set; }
    public X509Certificate2 Certificate { get; set; }
    public byte[]? EncryptedPrivateKey { get; set; }  // Only for encryption certs
    public string OriginalPath { get; set; }
    public ArchiveReason Reason { get; set; }
    public DateTime RetentionUntil { get; set; }
    public Dictionary<string, string> Metadata { get; set; }
    public string? Notes { get; set; }
}
 
public enum ArchiveReason
{
    Expired,
    Revoked,
    Superseded,
    KeyCompromise,
    CessationOfOperation,
    PolicyChange
}

Industry-Specific Retention Periods

Industry Regulation Retention Period Archive Keys?
Financial Sector SOX, PCI 10 years Yes (encryption)
Healthcare GDPR, HIPAA 30 years Yes (patient data)
Energy NIS2 10 years Yes (metering data)
Public Sector Archive laws 30+ years Depending on data type
Standard IT GDPR 6 years No (certificate only)

Create Archive Directory

// Structured archive directory
public class ArchiveDirectory
{
    private readonly string _basePath;
 
    public ArchiveDirectory(string basePath)
    {
        _basePath = basePath;
        Directory.CreateDirectory(Path.Combine(basePath, "certificates"));
        Directory.CreateDirectory(Path.Combine(basePath, "keys"));
        Directory.CreateDirectory(Path.Combine(basePath, "metadata"));
    }
 
    public void Archive(CertificateArchive archive)
    {
        var serial = archive.Certificate.SerialNumber;
        var year = archive.Certificate.NotAfter.Year;
 
        // Certificate (PEM)
        var certPath = Path.Combine(_basePath, "certificates", $"{year}", $"{serial}.crt.pem");
        Directory.CreateDirectory(Path.GetDirectoryName(certPath)!);
        archive.Certificate.ToPemFile(certPath);
 
        // Key (if present)
        if (archive.EncryptedPrivateKey != null)
        {
            var keyPath = Path.Combine(_basePath, "keys", $"{year}", $"{serial}.key.enc");
            Directory.CreateDirectory(Path.GetDirectoryName(keyPath)!);
            File.WriteAllBytes(keyPath, archive.EncryptedPrivateKey);
        }
 
        // Metadata
        var metaPath = Path.Combine(_basePath, "metadata", $"{year}", $"{serial}.json");
        Directory.CreateDirectory(Path.GetDirectoryName(metaPath)!);
        File.WriteAllText(metaPath, JsonSerializer.Serialize(archive.Metadata));
    }
}

// Search archived certificates
public IEnumerable<CertificateArchive> SearchArchive(
    string basePath,
    string? subjectFilter = null,
    DateTime? expiredAfter = null)
{
    var metadataFiles = Directory.GetFiles(
        Path.Combine(basePath, "metadata"),
        "*.json",
        SearchOption.AllDirectories
    );
 
    foreach (var file in metadataFiles)
    {
        var metadata = JsonSerializer.Deserialize<Dictionary<string, string>>(
            File.ReadAllText(file)
        );
 
        if (subjectFilter != null &&
            !metadata["Subject"].Contains(subjectFilter, StringComparison.OrdinalIgnoreCase))
            continue;
 
        if (expiredAfter.HasValue &&
            DateTime.Parse(metadata["NotAfter"]) < expiredAfter.Value)
            continue;
 
        var serial = Path.GetFileNameWithoutExtension(file);
        var year = Path.GetFileName(Path.GetDirectoryName(file));
 
        yield return LoadArchive(basePath, year, serial);
    }
}

Relationship Scenario Description
Prerequisite 4.4 Backup Backup before archival
Related 11.5 Key Destruction Delete signature keys
Related 6.4 Revoke Before archival

« ← 4.2 Rekey | ↑ Management Overview | 4.4 Backup → »


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

Zuletzt geändert: on 2026/01/30 at 06:54 AM