security(pki): enforce configuration-driven certificate profiles

* add strict versioned JSON profile documents and canonical serialization
* package validated built-in end-entity profile templates
* persist immutable profile versions with canonical hashes
* require explicit atomic profile activation
* resolve issuance profiles only from validated active store references
* enforce deny-by-default subject and SAN policies
* support typed DNS, IP, URI, and RFC822 SAN identities
* remove requester control over certificate serials and reserved fields
* bind issued credentials to exact profile ID, version, and hash
* enforce closed end-entity and CA credential profile-binding variants
* validate issued DER against the complete approved profile
* add real SAN issuance, CSR rejection, malicious-backend, lifecycle, snapshot, and redaction coverage

BREAKING CHANGE: replaces direct and mutable pre-release profile handling with strict JSON import,
immutable version persistence, explicit activation, and exact credential profile bindings.
This commit is contained in:
2026-07-30 17:15:33 +02:00
parent f06b25fa39
commit 9e40e8b5a2
72 changed files with 9258 additions and 596 deletions

View File

@@ -51,12 +51,14 @@ import zeroecho.pki.api.Encoding;
import zeroecho.pki.api.IssuanceService;
import zeroecho.pki.api.KeyRef;
import zeroecho.pki.api.RevocationService;
import zeroecho.pki.api.ProfileService;
import zeroecho.pki.api.StatusObjectService;
import zeroecho.pki.api.credential.EffectiveCredentialStatusResolver;
import zeroecho.pki.impl.core.DefaultCaService;
import zeroecho.pki.impl.core.DefaultCertificationRequestService;
import zeroecho.pki.impl.core.DefaultIssuanceService;
import zeroecho.pki.impl.core.DefaultRevocationService;
import zeroecho.pki.impl.core.DefaultProfileService;
import zeroecho.pki.impl.core.DefaultStatusObjectService;
import zeroecho.pki.impl.core.StoreBackedEffectiveCredentialStatusResolver;
import zeroecho.pki.impl.core.async.PkiSigningBus;
@@ -92,6 +94,7 @@ public final class PkiTestRuntime implements AutoCloseable {
private final CredentialFramework framework;
private final CredentialIssuerBackend issuerBackend;
private final EffectiveCredentialStatusResolver statusResolver;
private final ProfileService profileService;
private final CaService caService;
private final CertificationRequestService certificationRequestService;
@@ -113,12 +116,15 @@ public final class PkiTestRuntime implements AutoCloseable {
this.issuerBackend = issuerBackend;
Clock clock = Clock.systemUTC();
this.statusResolver = new StoreBackedEffectiveCredentialStatusResolver(store, clock);
this.profileService = new DefaultProfileService(store, clock, auditSink);
this.publicKeysByKeyRef = publicKeysByKeyRef;
this.publicKeyResolveHook = () -> {
};
this.certificationRequestService = new DefaultCertificationRequestService(store, framework);
this.issuanceService = new DefaultIssuanceService(store, framework, issuerBackend, auditSink, statusResolver);
importAndActivate(H7ProfileDocuments.defaultProfile());
this.issuanceService = new DefaultIssuanceService(store, framework, issuerBackend, auditSink, statusResolver,
profileService, clock);
this.revocationService = new DefaultRevocationService(store, clock, auditSink);
this.statusObjectService = new DefaultStatusObjectService(store, framework, auditSink, statusResolver);
@@ -286,6 +292,19 @@ public final class PkiTestRuntime implements AutoCloseable {
return statusResolver;
}
/** @return production profile lifecycle service */
public ProfileService profileService() {
return profileService;
}
/**
* Imports and activates one strict JSON test document through production APIs.
*/
public void importAndActivate(byte[] document) {
zeroecho.pki.api.profile.CertificateProfileRef reference = profileService.importProfile(document);
profileService.activateProfile(reference.profileId(), reference.profileVersion());
}
public CaService caService() {
return caService;
}
@@ -319,7 +338,7 @@ public final class PkiTestRuntime implements AutoCloseable {
public IssuanceService issuanceService(CredentialIssuerBackend backend,
EffectiveCredentialStatusResolver resolver) {
return new DefaultIssuanceService(store, framework, Objects.requireNonNull(backend, "backend"), auditSink,
Objects.requireNonNull(resolver, "resolver"));
Objects.requireNonNull(resolver, "resolver"), profileService, Clock.systemUTC());
}
public RevocationService revocationService() {