~~NOTOC~~
====== Scenario 12.3: PKCS#7 Chain Export ======
**Category:** [[.:start|Import/Export]] \\
**Complexity:** ** (Low) \\
**Prerequisites:** Certificate chain \\
**Estimated Time:** 10-15 minutes
----
===== Description =====
This scenario describes **export and import of certificate chains in PKCS#7 format**. PKCS#7 (also CMS - Cryptographic Message Syntax) is ideal for distributing certificate chains without private keys.
**PKCS#7 Properties:**
* **Content:** Certificates only (no private keys!)
* **Usage:** Chain distribution, S/MIME
* **Extensions:** .p7b, .p7c
* **Encoding:** DER (Binary) or PEM (Base64)
----
===== Workflow =====
flowchart TD
subgraph Input
ROOT[Root CA]
INT[Intermediate CA]
EE[End-Entity]
end
subgraph PKCS7["PKCS#7 Container"]
CERTS[SignedData.Certificates]
end
ROOT --> CERTS
INT --> CERTS
EE --> CERTS
PKCS7 --> DER[.p7b Binary]
PKCS7 --> PEM[.p7b PEM]
----
===== Code Example: Export Chain as PKCS#7 =====
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
public class Pkcs7ChainExporter
{
public byte[] ExportChain(X509Certificate2Collection certificates)
{
// SignedCms without signature (certificates only)
var content = new ContentInfo(Array.Empty());
var signedCms = new SignedCms(content, detached: true);
// Add certificates
foreach (var cert in certificates)
{
signedCms.Certificates.Add(cert);
}
// Export as PKCS#7 (DER)
return signedCms.Encode();
}
public void ExportToFile(
X509Certificate2Collection certificates,
string outputPath,
bool asPem = false)
{
var p7bBytes = ExportChain(certificates);
if (asPem)
{
// PEM format
var pem = new StringBuilder();
pem.AppendLine("-----BEGIN PKCS7-----");
pem.AppendLine(Convert.ToBase64String(p7bBytes, Base64FormattingOptions.InsertLineBreaks));
pem.AppendLine("-----END PKCS7-----");
File.WriteAllText(outputPath, pem.ToString());
}
else
{
// Binary (DER)
File.WriteAllBytes(outputPath, p7bBytes);
}
Console.WriteLine($"PKCS#7 exported: {outputPath} ({certificates.Count} certificates)");
}
}
----
===== Code Example: Create and Export Full Chain =====
using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
public class FullChainExporter
{
public void ExportFullChain(
X509Certificate2 endEntity,
string outputPath)
{
using var ctx = PqCryptoContext.Initialize();
// Build chain
using var chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;
chain.Build(endEntity);
// Collect all certificates
var collection = new X509Certificate2Collection();
collection.Add(endEntity);
foreach (var element in chain.ChainElements)
{
if (element.Certificate.Thumbprint != endEntity.Thumbprint)
{
collection.Add(element.Certificate);
}
}
// Export as PKCS#7
var exporter = new Pkcs7ChainExporter();
exporter.ExportToFile(collection, outputPath);
// Output details
Console.WriteLine("Chain exported:");
foreach (var cert in collection)
{
Console.WriteLine($" [{cert.Thumbprint.Substring(0, 8)}] {cert.Subject}");
}
}
}
----
===== Code Example: Import PKCS#7 =====
public class Pkcs7ChainImporter
{
public X509Certificate2Collection ImportChain(byte[] p7bBytes)
{
var collection = new X509Certificate2Collection();
collection.Import(p7bBytes);
Console.WriteLine($"{collection.Count} certificates imported");
return collection;
}
public X509Certificate2Collection ImportFromFile(string filePath)
{
byte[] data;
var content = File.ReadAllText(filePath);
if (content.Contains("-----BEGIN PKCS7-----"))
{
// PEM format
var base64 = Regex.Match(
content,
@"-----BEGIN PKCS7-----(.*?)-----END PKCS7-----",
RegexOptions.Singleline
).Groups[1].Value.Trim();
data = Convert.FromBase64String(base64);
}
else
{
// Binary (DER)
data = File.ReadAllBytes(filePath);
}
return ImportChain(data);
}
public void InstallChainToStore(
string p7bPath,
StoreName storeName = StoreName.CertificateAuthority,
StoreLocation location = StoreLocation.LocalMachine)
{
var certs = ImportFromFile(p7bPath);
using var store = new X509Store(storeName, location);
store.Open(OpenFlags.ReadWrite);
foreach (var cert in certs)
{
// Check if self-signed (Root)
var targetStore = cert.Subject == cert.Issuer
? new X509Store(StoreName.Root, location)
: store;
if (targetStore.Name != store.Name)
{
targetStore.Open(OpenFlags.ReadWrite);
}
// Check if already present
var existing = targetStore.Certificates.Find(
X509FindType.FindByThumbprint,
cert.Thumbprint,
validOnly: false
);
if (existing.Count == 0)
{
targetStore.Add(cert);
Console.WriteLine($"Installed: {cert.Subject}");
}
if (targetStore.Name != store.Name)
{
targetStore.Close();
}
}
store.Close();
}
}
----
===== Code Example: CA Bundle for Webserver =====
public class CaBundleCreator
{
public void CreateCaBundle(
X509Certificate2 rootCa,
X509Certificate2Collection intermediateCas,
string outputPath)
{
var collection = new X509Certificate2Collection();
// Intermediates first (closest to server)
foreach (var intermediate in intermediateCas.OrderByDescending(c => c.NotAfter))
{
collection.Add(intermediate);
}
// Root last
collection.Add(rootCa);
var exporter = new Pkcs7ChainExporter();
exporter.ExportToFile(collection, outputPath);
Console.WriteLine("CA Bundle created:");
Console.WriteLine($" Intermediates: {intermediateCas.Count}");
Console.WriteLine($" Root: {rootCa.Subject}");
}
public void CreateNginxCaBundle(
X509Certificate2 serverCert,
X509Certificate2Collection chain,
string outputPath)
{
// Nginx expects PEM chain (server first, then intermediates)
var sb = new StringBuilder();
// Server certificate
sb.AppendLine(ToPem(serverCert));
// Intermediates (not root!)
foreach (var cert in chain.Where(c => c.Subject != c.Issuer))
{
sb.AppendLine(ToPem(cert));
}
File.WriteAllText(outputPath, sb.ToString());
Console.WriteLine($"Nginx CA Bundle: {outputPath}");
}
private string ToPem(X509Certificate2 cert)
{
var sb = new StringBuilder();
sb.AppendLine("-----BEGIN CERTIFICATE-----");
sb.AppendLine(Convert.ToBase64String(cert.RawData, Base64FormattingOptions.InsertLineBreaks));
sb.AppendLine("-----END CERTIFICATE-----");
return sb.ToString();
}
}
----
===== PKCS#7 with OpenSSL =====
# Create PKCS#7 from multiple certificates
openssl crl2pkcs7 -nocrl \
-certfile root-ca.pem \
-certfile intermediate-ca.pem \
-certfile server.pem \
-out chain.p7b \
-outform DER
# PKCS#7 as PEM
openssl crl2pkcs7 -nocrl \
-certfile chain.pem \
-out chain.p7b \
-outform PEM
# Inspect PKCS#7
openssl pkcs7 -in chain.p7b -print_certs -noout
# Extract certificates from PKCS#7
openssl pkcs7 -in chain.p7b -print_certs -out extracted.pem
# PKCS#7 information
openssl pkcs7 -in chain.p7b -inform DER -text
----
===== Usage in Various Systems =====
^ System ^ PKCS#7 Usage ^ Format ^
| **Windows** | Intermediate CA Store | .p7b (DER) |
| **IIS** | SSL Certificate Chain | .p7b |
| **Java** | Trust Store Import | .p7b (DER) |
| **S/MIME** | Email Encryption | Part of message |
| **Code Signing** | Timestamp + Chain | Embedded |
----
===== Related Scenarios =====
^ Relationship ^ Scenario ^ Description ^
| **Alternative** | [[.:pem_export|12.1 PEM Export]] | Chain as PEM |
| **Related** | [[.:pfx_export|12.2 PFX Export]] | With Private Key |
| **Prerequisite** | [[en:int:pqcrypt:szenarien:pki:ca_hierarchie|1.3 CA Hierarchy]] | Build chain |
| **Related** | [[en:int:pqcrypt:szenarien:validierung:chain_building|5.1 Chain Building]] | Validate chain |
----
<< [[.:pfx_export|<- 12.2 PFX Export]] | [[.:start|^ Import/Export]] | [[.:interop|12.4 Interoperability ->]] >>
{{tag>scenario import export pkcs7 chain p7b}}
----
//Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//