~~NOTOC~~
====== Scenario 4.3: Certificate Archival ======
**Category:** [[.:start|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
{
["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 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));
}
}
----
===== Archive Search =====
// Search archived certificates
public IEnumerable 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>(
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);
}
}
----
===== Related Scenarios =====
^ Relationship ^ Scenario ^ Description ^
| **Prerequisite** | [[.:backup|4.4 Backup]] | Backup before archival |
| **Related** | [[en:int:pqcrypt:szenarien:schluessel:vernichtung|11.5 Key Destruction]] | Delete signature keys |
| **Related** | [[en:int:pqcrypt:szenarien:widerruf:zertifikat_widerrufen|6.4 Revoke]] | Before archival |
----
<< [[.:rekey|← 4.2 Rekey]] | [[.:start|↑ Management Overview]] | [[.:backup|4.4 Backup →]] >>
{{tag>scenario management archival compliance retention}}
----
//Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//