feat(site-runtime): CertStoreActor exports trusted certs (thumbprint+DER) for node reconciliation (UA1 groundwork)

This commit is contained in:
Joseph Doherty
2026-07-09 01:15:51 -04:00
parent 667d863e58
commit 39976772d9
3 changed files with 87 additions and 0 deletions
@@ -51,6 +51,7 @@ public class CertStoreActor : ReceiveActor
Receive<WriteCertToLocalStore>(HandleWrite);
Receive<RemoveCertFromLocalStore>(HandleRemove);
Receive<ListLocalCerts>(_ => HandleList());
Receive<ExportLocalTrustedCerts>(_ => HandleExport());
}
/// <summary>
@@ -133,6 +134,47 @@ public class CertStoreActor : ReceiveActor
}
}
/// <summary>
/// Exports the trusted-peer store (thumbprint + raw DER) so the Deployment
/// Manager singleton can reconcile the trusted set onto a (re)joining node.
/// Trusted store only — rejected certs are never pushed to peers.
/// </summary>
private void HandleExport()
{
try
{
var certs = new List<ExportedCert>();
if (Directory.Exists(_trustedStoreDir))
{
foreach (var file in Directory.EnumerateFiles(_trustedStoreDir)
.Where(f => f.EndsWith(".der", StringComparison.OrdinalIgnoreCase)
|| f.EndsWith(".crt", StringComparison.OrdinalIgnoreCase)))
{
try
{
var der = File.ReadAllBytes(file);
// Load only to derive the canonical thumbprint (the store
// filename); the DER bytes shipped are the raw file bytes.
using var cert = X509CertificateLoader.LoadCertificate(der);
certs.Add(new ExportedCert(cert.Thumbprint, der));
}
catch (Exception ex)
{
// A malformed file must not abort the whole export.
_log.Warning(ex, "Skipping unreadable certificate file {File} during export", file);
}
}
}
Sender.Tell(new LocalCertExport(true, null, certs));
}
catch (Exception ex)
{
_log.Warning(ex, "Failed to export trusted certificates from PKI store");
Sender.Tell(new LocalCertExport(false, ex.Message, Array.Empty<ExportedCert>()));
}
}
private IEnumerable<TrustedCertInfo> EnumerateStore(string storeDir, bool rejected)
{
if (!Directory.Exists(storeDir))