Integrate CredentialFrameworkProvider bootstrap SPI and harden provider config validation
feat: add SPI-based CredentialFrameworkProvider resolution to PkiBootstrap via ServiceLoader feat: add PkiBootstrap.openCredentialFramework() for provider-driven credential framework initialization feat: register BcX509CredentialFrameworkProvider in META-INF/services feat: introduce ConfigurableProvider.validateConfig(ProviderConfig) as a standard provider-side validation hook fix: move generic backendId consistency validation into the default ConfigurableProvider validation routine fix: enforce provider-local configuration validation from allocate() so direct provider use remains safe outside bootstrap fix: add provider-specific validateConfig implementations for bootstrap-managed providers based on consumed configuration keys fix: report unknown provider configuration keys through provider-local JUL warning logs without exposing values fix: fail fast on malformed consumed configuration values instead of silently falling back where invalid input would mask operator error fix: extend PkiBootstrapTest to cover CredentialFrameworkProvider bootstrap path fix: extend PkiBootstrapTest to cover async and crypto.workflow initialization paths whose prefixed properties are cleared in test setup fix: add negative bootstrap/provider validation coverage for backend mismatch and invalid configured values docs: expand JavaDoc and package-level documentation for CredentialFrameworkProvider bootstrap wiring, ServiceLoader usage, and configuration validation behavior chore: keep PkiBootstrap independent from implementation-specific BC framework classes and preserve provider autonomy over validation and diagnostics Closes #3 spent @2h
This commit is contained in:
@@ -37,6 +37,8 @@ import java.nio.file.Path;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.api.PkiId;
|
import zeroecho.pki.api.PkiId;
|
||||||
import zeroecho.pki.api.audit.Principal;
|
import zeroecho.pki.api.audit.Principal;
|
||||||
@@ -71,6 +73,7 @@ import zeroecho.pki.util.async.impl.DurableAsyncBus;
|
|||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public final class FileBackedAsyncBusProvider implements AsyncBusProvider {
|
public final class FileBackedAsyncBusProvider implements AsyncBusProvider {
|
||||||
|
private static final Logger LOG = Logger.getLogger(FileBackedAsyncBusProvider.class.getName());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration key for the log file path.
|
* Configuration key for the log file path.
|
||||||
@@ -87,13 +90,29 @@ public final class FileBackedAsyncBusProvider implements AsyncBusProvider {
|
|||||||
return Set.of(KEY_LOG_PATH);
|
return Set.of(KEY_LOG_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the durable file-backed async bus provider.
|
||||||
|
*
|
||||||
|
* @param config provider configuration (never {@code null})
|
||||||
|
* @throws IllegalArgumentException if the configuration is invalid
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void validateConfig(final ProviderConfig config) {
|
||||||
|
AsyncBusProvider.super.validateConfig(config);
|
||||||
|
Map<String, String> props = config.properties();
|
||||||
|
String logPath = props.getOrDefault(KEY_LOG_PATH, Path.of("pki-async").resolve("async.log").toString());
|
||||||
|
Path.of(logPath);
|
||||||
|
for (String key : props.keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown async bus configuration key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AsyncBus<PkiId, Principal, String, Object> allocate(ProviderConfig config) {
|
public AsyncBus<PkiId, Principal, String, Object> allocate(ProviderConfig config) {
|
||||||
Objects.requireNonNull(config, "config");
|
Objects.requireNonNull(config, "config");
|
||||||
|
validateConfig(config);
|
||||||
if (!id().equals(config.backendId())) {
|
|
||||||
throw new IllegalArgumentException("ProviderConfig backendId mismatch.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> props = config.properties();
|
Map<String, String> props = config.properties();
|
||||||
String logPath = props.getOrDefault(KEY_LOG_PATH, Path.of("pki-async").resolve("async.log").toString());
|
String logPath = props.getOrDefault(KEY_LOG_PATH, Path.of("pki-async").resolve("async.log").toString());
|
||||||
|
|||||||
@@ -36,15 +36,26 @@ package zeroecho.pki.impl.audit;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
import zeroecho.pki.spi.audit.AuditSink;
|
import zeroecho.pki.spi.audit.AuditSink;
|
||||||
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* {@link AuditSinkProvider} for the file-backed audit sink.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Supported configuration keys:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code root} - audit storage root directory (required)</li>
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class FileAuditSinkProvider implements AuditSinkProvider {
|
public class FileAuditSinkProvider implements AuditSinkProvider {
|
||||||
|
private static final Logger LOG = Logger.getLogger(FileAuditSinkProvider.class.getName());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String id() {
|
public String id() {
|
||||||
return "file";
|
return "file";
|
||||||
@@ -55,9 +66,29 @@ public class FileAuditSinkProvider implements AuditSinkProvider {
|
|||||||
return Set.of("root");
|
return Set.of("root");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the file-backed audit sink provider.
|
||||||
|
*
|
||||||
|
* @param config provider configuration (never {@code null})
|
||||||
|
* @throws IllegalArgumentException if the configuration is incomplete or
|
||||||
|
* invalid
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void validateConfig(final ProviderConfig config) {
|
||||||
|
AuditSinkProvider.super.validateConfig(config);
|
||||||
|
String rootString = config.require("root");
|
||||||
|
Path.of(rootString);
|
||||||
|
for (String key : config.properties().keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown audit sink configuration key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuditSink allocate(ProviderConfig config) {
|
public AuditSink allocate(ProviderConfig config) {
|
||||||
Objects.requireNonNull(config, "config");
|
Objects.requireNonNull(config, "config");
|
||||||
|
validateConfig(config);
|
||||||
|
|
||||||
String rootString = config.require("root");
|
String rootString = config.require("root");
|
||||||
Path root = Path.of(rootString);
|
Path root = Path.of(rootString);
|
||||||
|
|||||||
@@ -35,15 +35,27 @@ package zeroecho.pki.impl.audit;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
import zeroecho.pki.spi.audit.AuditSink;
|
import zeroecho.pki.spi.audit.AuditSink;
|
||||||
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* {@link AuditSinkProvider} for the in-memory audit sink.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Supported configuration keys:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code size} - maximum retained event count (optional, positive
|
||||||
|
* integer)</li>
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class InMemoryAuditSinkProvider implements AuditSinkProvider {
|
public class InMemoryAuditSinkProvider implements AuditSinkProvider {
|
||||||
|
private static final Logger LOG = Logger.getLogger(InMemoryAuditSinkProvider.class.getName());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String id() {
|
public String id() {
|
||||||
return "memory";
|
return "memory";
|
||||||
@@ -54,25 +66,54 @@ public class InMemoryAuditSinkProvider implements AuditSinkProvider {
|
|||||||
return Set.of("size");
|
return Set.of("size");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the in-memory audit sink provider.
|
||||||
|
*
|
||||||
|
* @param config provider configuration (never {@code null})
|
||||||
|
* @throws IllegalArgumentException if the configuration contains an invalid
|
||||||
|
* {@code size} value
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public AuditSink allocate(ProviderConfig config) {
|
public void validateConfig(final ProviderConfig config) {
|
||||||
|
AuditSinkProvider.super.validateConfig(config);
|
||||||
Optional<String> sizeStr = config.get("size");
|
Optional<String> sizeStr = config.get("size");
|
||||||
if (sizeStr.isPresent()) {
|
if (sizeStr.isPresent()) {
|
||||||
return new InMemoryAuditSink(parseIntOrDefault(sizeStr.get(), InMemoryAuditSink.DEFAULT_MAX_EVENTS));
|
int size = parseInt(sizeStr.get(), "size");
|
||||||
|
if (size <= 0) {
|
||||||
|
throw new IllegalArgumentException("Configuration key 'size' must be positive.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String key : config.properties().keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown audit sink configuration key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuditSink allocate(ProviderConfig config) {
|
||||||
|
validateConfig(config);
|
||||||
|
Optional<String> sizeStr = config.get("size");
|
||||||
|
if (sizeStr.isPresent()) {
|
||||||
|
return new InMemoryAuditSink(parseInt(sizeStr.get(), "size"));
|
||||||
} else {
|
} else {
|
||||||
return new InMemoryAuditSink();
|
return new InMemoryAuditSink();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int parseIntOrDefault(String value, int defaultValue) {
|
/**
|
||||||
if (value == null) {
|
* Parses an integer configuration value.
|
||||||
return defaultValue;
|
*
|
||||||
}
|
* @param value raw configuration value
|
||||||
|
* @param keyName configuration key name
|
||||||
|
* @return parsed integer value
|
||||||
|
* @throws IllegalArgumentException if the value is not a valid integer
|
||||||
|
*/
|
||||||
|
private static int parseInt(final String value, final String keyName) {
|
||||||
try {
|
try {
|
||||||
return Integer.parseInt(value);
|
return Integer.parseInt(value);
|
||||||
} catch (NumberFormatException ex) {
|
} catch (NumberFormatException ex) {
|
||||||
return defaultValue;
|
throw new IllegalArgumentException("Configuration key '" + keyName + "' must be an integer.", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,15 +35,23 @@ package zeroecho.pki.impl.audit;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
import zeroecho.pki.spi.audit.AuditSink;
|
import zeroecho.pki.spi.audit.AuditSink;
|
||||||
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* {@link AuditSinkProvider} for the standard-output audit sink.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This provider accepts no provider-specific configuration keys.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class StdoutAuditSinkProvider implements AuditSinkProvider {
|
public class StdoutAuditSinkProvider implements AuditSinkProvider {
|
||||||
|
private static final Logger LOG = Logger.getLogger(StdoutAuditSinkProvider.class.getName());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String id() {
|
public String id() {
|
||||||
return "stdout";
|
return "stdout";
|
||||||
@@ -54,8 +62,24 @@ public class StdoutAuditSinkProvider implements AuditSinkProvider {
|
|||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the standard-output audit sink provider.
|
||||||
|
*
|
||||||
|
* @param config provider configuration (never {@code null})
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void validateConfig(final ProviderConfig config) {
|
||||||
|
AuditSinkProvider.super.validateConfig(config);
|
||||||
|
for (String key : config.properties().keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown audit sink configuration key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuditSink allocate(ProviderConfig config) {
|
public AuditSink allocate(ProviderConfig config) {
|
||||||
|
validateConfig(config);
|
||||||
return new StdoutAuditSink();
|
return new StdoutAuditSink();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ package zeroecho.pki.impl.crypto.zeroecholib;
|
|||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
import zeroecho.pki.spi.crypto.SignatureWorkflow;
|
import zeroecho.pki.spi.crypto.SignatureWorkflow;
|
||||||
@@ -61,6 +63,7 @@ import zeroecho.pki.spi.crypto.SignatureWorkflowProvider;
|
|||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWorkflowProvider {
|
public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWorkflowProvider {
|
||||||
|
private static final Logger LOG = Logger.getLogger(ZeroEchoLibSignatureWorkflowProvider.class.getName());
|
||||||
|
|
||||||
private static final String KEY_KEYRING_PATH = "keyringPath";
|
private static final String KEY_KEYRING_PATH = "keyringPath";
|
||||||
private static final String KEY_KEYREF_PREFIX = "keyRefPrefix";
|
private static final String KEY_KEYREF_PREFIX = "keyRefPrefix";
|
||||||
@@ -76,17 +79,39 @@ public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWork
|
|||||||
return Set.of(KEY_KEYRING_PATH, KEY_KEYREF_PREFIX, KEY_REQUIRE_SUFFIX);
|
return Set.of(KEY_KEYRING_PATH, KEY_KEYREF_PREFIX, KEY_REQUIRE_SUFFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the ZeroEcho-lib signature workflow provider.
|
||||||
|
*
|
||||||
|
* @param config provider configuration (never {@code null})
|
||||||
|
* @throws IllegalArgumentException if the configuration is incomplete or
|
||||||
|
* invalid
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public SignatureWorkflow allocate(ProviderConfig config) {
|
public void validateConfig(final ProviderConfig config) {
|
||||||
if (config == null) {
|
SignatureWorkflowProvider.super.validateConfig(config);
|
||||||
throw new IllegalArgumentException("config must not be null");
|
String keyringPath = config.require(KEY_KEYRING_PATH);
|
||||||
}
|
Path.of(keyringPath);
|
||||||
|
config.get(KEY_KEYREF_PREFIX).ifPresent(value -> {
|
||||||
// Defensive hardening: fail fast if miswired config reaches this provider.
|
if (value.isBlank()) {
|
||||||
if (!id().equals(config.backendId())) {
|
throw new IllegalArgumentException("Configuration key '" + KEY_KEYREF_PREFIX + "' must not be blank.");
|
||||||
throw new IllegalArgumentException("ProviderConfig backendId mismatch.");
|
}
|
||||||
|
});
|
||||||
|
config.get(KEY_REQUIRE_SUFFIX).ifPresent(value -> {
|
||||||
|
if (!("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value))) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Configuration key '" + KEY_REQUIRE_SUFFIX + "' must be 'true' or 'false'.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (String key : config.properties().keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown signature workflow configuration key: " + key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SignatureWorkflow allocate(final ProviderConfig config) {
|
||||||
|
validateConfig(config);
|
||||||
String keyringPath = config.require(KEY_KEYRING_PATH);
|
String keyringPath = config.require(KEY_KEYRING_PATH);
|
||||||
String prefix = config.get(KEY_KEYREF_PREFIX).orElse("zeroecho-lib:");
|
String prefix = config.get(KEY_KEYREF_PREFIX).orElse("zeroecho-lib:");
|
||||||
boolean requireSuffix = config.get(KEY_REQUIRE_SUFFIX).map(Boolean::parseBoolean).orElse(Boolean.TRUE);
|
boolean requireSuffix = config.get(KEY_REQUIRE_SUFFIX).map(Boolean::parseBoolean).orElse(Boolean.TRUE);
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
package zeroecho.pki.impl.framework.x509.bc;
|
package zeroecho.pki.impl.framework.x509.bc;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
import zeroecho.pki.spi.framework.CredentialFramework;
|
import zeroecho.pki.spi.framework.CredentialFramework;
|
||||||
@@ -73,7 +75,9 @@ import zeroecho.pki.spi.framework.CredentialFrameworkProvider;
|
|||||||
* The current implementation does not consume any provider-specific
|
* The current implementation does not consume any provider-specific
|
||||||
* configuration keys beyond backend selection through
|
* configuration keys beyond backend selection through
|
||||||
* {@link ProviderConfig#backendId()}. Consequently, {@link #supportedKeys()}
|
* {@link ProviderConfig#backendId()}. Consequently, {@link #supportedKeys()}
|
||||||
* returns an empty set.
|
* returns an empty set and bootstrap will reject any
|
||||||
|
* {@code zeroecho.pki.framework.*} configuration keys other than the backend
|
||||||
|
* selector itself.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Thread-safety</h2>
|
* <h2>Thread-safety</h2>
|
||||||
@@ -83,6 +87,8 @@ import zeroecho.pki.spi.framework.CredentialFrameworkProvider;
|
|||||||
*/
|
*/
|
||||||
public final class BcX509CredentialFrameworkProvider implements CredentialFrameworkProvider {
|
public final class BcX509CredentialFrameworkProvider implements CredentialFrameworkProvider {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(BcX509CredentialFrameworkProvider.class.getName());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the stable provider identifier used to select this X.509 framework
|
* Returns the stable provider identifier used to select this X.509 framework
|
||||||
* backend.
|
* backend.
|
||||||
@@ -110,6 +116,32 @@ public final class BcX509CredentialFrameworkProvider implements CredentialFramew
|
|||||||
return Set.of();
|
return Set.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the Bouncy Castle backed X.509 credential
|
||||||
|
* framework provider.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This provider currently defines no provider-specific keys. Unknown keys are
|
||||||
|
* ignored for forward compatibility and are reported only by name through JUL
|
||||||
|
* warning output. The backend id must match {@link #id()}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param config provider configuration used to select this backend; must not be
|
||||||
|
* {@code null}
|
||||||
|
* @throws IllegalArgumentException if the backend id does not match this
|
||||||
|
* provider identifier
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void validateConfig(final ProviderConfig config) {
|
||||||
|
CredentialFrameworkProvider.super.validateConfig(config);
|
||||||
|
|
||||||
|
for (String key : config.properties().keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown credential framework configuration key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates a new Bouncy Castle backed X.509 credential framework instance.
|
* Allocates a new Bouncy Castle backed X.509 credential framework instance.
|
||||||
*
|
*
|
||||||
@@ -129,12 +161,7 @@ public final class BcX509CredentialFrameworkProvider implements CredentialFramew
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public CredentialFramework allocate(ProviderConfig config) {
|
public CredentialFramework allocate(ProviderConfig config) {
|
||||||
if (config == null) {
|
validateConfig(config);
|
||||||
throw new IllegalArgumentException("config must not be null");
|
|
||||||
}
|
|
||||||
if (!id().equals(config.backendId())) {
|
|
||||||
throw new IllegalArgumentException("ProviderConfig backendId mismatch");
|
|
||||||
}
|
|
||||||
return new BcX509CredentialFramework();
|
return new BcX509CredentialFramework();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,9 @@
|
|||||||
* issuance and status-object generation remain intentionally unsupported until
|
* issuance and status-object generation remain intentionally unsupported until
|
||||||
* concrete backend components are supplied. This allows bootstrap and runtime
|
* concrete backend components are supplied. This allows bootstrap and runtime
|
||||||
* composition to separate framework selection from full cryptographic and PKI
|
* composition to separate framework selection from full cryptographic and PKI
|
||||||
* service wiring.
|
* service wiring. The package is exposed to runtime composition only through
|
||||||
|
* the framework SPI and its ServiceLoader provider; bootstrap code must not
|
||||||
|
* depend on these implementation classes directly.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Security considerations</h2>
|
* <h2>Security considerations</h2>
|
||||||
|
|||||||
@@ -36,14 +36,15 @@ package zeroecho.pki.impl.fs;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
|
||||||
import zeroecho.pki.spi.store.PkiStore;
|
import zeroecho.pki.spi.store.PkiStore;
|
||||||
import zeroecho.pki.spi.store.PkiStoreProvider;
|
import zeroecho.pki.spi.store.PkiStoreProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link AuditSinkProvider} for the filesystem-backed {@link PkiStore}.
|
* {@link PkiStoreProvider} for the filesystem-backed {@link PkiStore}.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Supported configuration keys:
|
* Supported configuration keys:
|
||||||
@@ -53,6 +54,7 @@ import zeroecho.pki.spi.store.PkiStoreProvider;
|
|||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public final class FilesystemPkiStoreProvider implements PkiStoreProvider {
|
public final class FilesystemPkiStoreProvider implements PkiStoreProvider {
|
||||||
|
private static final Logger LOG = Logger.getLogger(FilesystemPkiStoreProvider.class.getName());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public no-arg constructor required by {@link java.util.ServiceLoader}.
|
* Public no-arg constructor required by {@link java.util.ServiceLoader}.
|
||||||
@@ -71,9 +73,41 @@ public final class FilesystemPkiStoreProvider implements PkiStoreProvider {
|
|||||||
return Set.of("root");
|
return Set.of("root");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the filesystem-backed PKI store provider.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Required configuration:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code root}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Unknown keys are ignored for forward compatibility and are reported by key
|
||||||
|
* name only.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param config provider configuration (never {@code null})
|
||||||
|
* @throws IllegalArgumentException if the configuration is incomplete or
|
||||||
|
* invalid
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public PkiStore allocate(ProviderConfig config) {
|
public void validateConfig(final ProviderConfig config) {
|
||||||
|
PkiStoreProvider.super.validateConfig(config);
|
||||||
|
String rootString = config.require("root");
|
||||||
|
Path.of(rootString);
|
||||||
|
for (String key : config.properties().keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown PKI store configuration key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PkiStore allocate(final ProviderConfig config) {
|
||||||
Objects.requireNonNull(config, "config");
|
Objects.requireNonNull(config, "config");
|
||||||
|
validateConfig(config);
|
||||||
|
|
||||||
String rootString = config.require("root");
|
String rootString = config.require("root");
|
||||||
Path root = Path.of(rootString);
|
Path root = Path.of(rootString);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.pki.spi;
|
package zeroecho.pki.spi;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,8 +60,10 @@ import java.util.Set;
|
|||||||
* Configuration is provided as string properties through
|
* Configuration is provided as string properties through
|
||||||
* {@link ProviderConfig}. Providers must validate required keys and reject
|
* {@link ProviderConfig}. Providers must validate required keys and reject
|
||||||
* invalid configurations with {@link IllegalArgumentException}. Unknown keys
|
* invalid configurations with {@link IllegalArgumentException}. Unknown keys
|
||||||
* must be ignored for forward compatibility. {@link #supportedKeys()} is
|
* must be ignored for forward compatibility, but may be reported through
|
||||||
* informational and may be used for diagnostics or help output.
|
* provider-local diagnostic logging that names keys only.
|
||||||
|
* {@link #supportedKeys()} is informational and may be used for diagnostics or
|
||||||
|
* help output.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Security</h2>
|
* <h2>Security</h2>
|
||||||
@@ -94,6 +97,37 @@ public interface ConfigurableProvider<T> {
|
|||||||
*/
|
*/
|
||||||
Set<String> supportedKeys();
|
Set<String> supportedKeys();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the provided configuration before allocation.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Implementations should use this hook to enforce backend-specific invariants
|
||||||
|
* such as required keys and key formats. The default implementation already
|
||||||
|
* verifies that {@link ProviderConfig#backendId()} matches {@link #id()}.
|
||||||
|
* Unknown keys should normally be ignored for forward compatibility, but may be
|
||||||
|
* reported through provider-local logging that names keys only and never logs
|
||||||
|
* values.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The default implementation performs a null check and verifies that the
|
||||||
|
* supplied backend id matches {@link #id()}. Providers with additional
|
||||||
|
* requirements should override this method, invoke the default implementation,
|
||||||
|
* and then validate their own keys.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param config configuration to validate (never {@code null})
|
||||||
|
* @throws NullPointerException if {@code config} is {@code null}
|
||||||
|
* @throws IllegalArgumentException if the configuration is invalid
|
||||||
|
*/
|
||||||
|
default void validateConfig(final ProviderConfig config) {
|
||||||
|
Objects.requireNonNull(config, "config");
|
||||||
|
if (!id().equals(config.backendId())) {
|
||||||
|
throw new IllegalArgumentException("ProviderConfig backendId mismatch.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates a new instance using the provided configuration.
|
* Allocates a new instance using the provided configuration.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ import zeroecho.pki.spi.audit.AuditSink;
|
|||||||
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
||||||
import zeroecho.pki.spi.crypto.SignatureWorkflow;
|
import zeroecho.pki.spi.crypto.SignatureWorkflow;
|
||||||
import zeroecho.pki.spi.crypto.SignatureWorkflowProvider;
|
import zeroecho.pki.spi.crypto.SignatureWorkflowProvider;
|
||||||
|
import zeroecho.pki.spi.framework.CredentialFramework;
|
||||||
|
import zeroecho.pki.spi.framework.CredentialFrameworkProvider;
|
||||||
import zeroecho.pki.spi.store.PkiStore;
|
import zeroecho.pki.spi.store.PkiStore;
|
||||||
import zeroecho.pki.spi.store.PkiStoreProvider;
|
import zeroecho.pki.spi.store.PkiStoreProvider;
|
||||||
import zeroecho.pki.util.async.AsyncBus;
|
import zeroecho.pki.util.async.AsyncBus;
|
||||||
@@ -58,7 +60,7 @@ import zeroecho.pki.util.async.AsyncBus;
|
|||||||
* <p>
|
* <p>
|
||||||
* This class provides deterministic selection and instantiation rules for
|
* This class provides deterministic selection and instantiation rules for
|
||||||
* components discovered via {@link java.util.ServiceLoader}. It is designed to
|
* components discovered via {@link java.util.ServiceLoader}. It is designed to
|
||||||
* scale as more SPIs are introduced (audit, publish, framework integrations,
|
* scale as more SPIs are introduced (audit, publish, credential frameworks,
|
||||||
* crypto/workflows, async orchestration, etc.).
|
* crypto/workflows, async orchestration, etc.).
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
@@ -77,6 +79,10 @@ import zeroecho.pki.util.async.AsyncBus;
|
|||||||
* <li>Select async bus provider: {@code -Dzeroecho.pki.async=<id>}</li>
|
* <li>Select async bus provider: {@code -Dzeroecho.pki.async=<id>}</li>
|
||||||
* <li>Configure async bus provider:
|
* <li>Configure async bus provider:
|
||||||
* {@code -Dzeroecho.pki.async.<key>=<value>}</li>
|
* {@code -Dzeroecho.pki.async.<key>=<value>}</li>
|
||||||
|
* <li>Select credential framework provider:
|
||||||
|
* {@code -Dzeroecho.pki.framework=<id>}</li>
|
||||||
|
* <li>Configure credential framework provider:
|
||||||
|
* {@code -Dzeroecho.pki.framework.<key>=<value>}</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
@@ -100,6 +106,9 @@ public final class PkiBootstrap {
|
|||||||
private static final String PROP_ASYNC_BACKEND = "zeroecho.pki.async";
|
private static final String PROP_ASYNC_BACKEND = "zeroecho.pki.async";
|
||||||
private static final String PROP_ASYNC_PREFIX = "zeroecho.pki.async.";
|
private static final String PROP_ASYNC_PREFIX = "zeroecho.pki.async.";
|
||||||
|
|
||||||
|
private static final String PROP_FRAMEWORK_BACKEND = "zeroecho.pki.framework";
|
||||||
|
private static final String PROP_FRAMEWORK_PREFIX = "zeroecho.pki.framework.";
|
||||||
|
|
||||||
private PkiBootstrap() {
|
private PkiBootstrap() {
|
||||||
throw new AssertionError("No instances.");
|
throw new AssertionError("No instances.");
|
||||||
}
|
}
|
||||||
@@ -264,6 +273,54 @@ public final class PkiBootstrap {
|
|||||||
return provider.allocate(config);
|
return provider.allocate(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a {@link CredentialFramework} using {@link CredentialFrameworkProvider}
|
||||||
|
* discovered via ServiceLoader.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Provider selection is deterministic and fail-fast:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>If {@code -Dzeroecho.pki.framework=<id>} is specified, the provider
|
||||||
|
* with the matching id is selected.</li>
|
||||||
|
* <li>If no id is specified and exactly one provider exists, that provider is
|
||||||
|
* selected.</li>
|
||||||
|
* <li>If multiple providers exist and no id is specified, bootstrap fails as
|
||||||
|
* configuration is ambiguous.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Configuration properties are read from {@link System#getProperties()} using
|
||||||
|
* the prefix {@code zeroecho.pki.framework.}. Values are treated as sensitive
|
||||||
|
* and are never logged; only keys may be logged.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return credential framework (never {@code null})
|
||||||
|
* @throws IllegalArgumentException if unsupported configuration keys are
|
||||||
|
* provided
|
||||||
|
* @throws IllegalStateException if provider selection fails
|
||||||
|
*/
|
||||||
|
public static CredentialFramework openCredentialFramework() {
|
||||||
|
String requestedId = System.getProperty(PROP_FRAMEWORK_BACKEND);
|
||||||
|
|
||||||
|
CredentialFrameworkProvider provider = SpiSelector.select(CredentialFrameworkProvider.class, requestedId,
|
||||||
|
new SpiSelector.ProviderId<>() {
|
||||||
|
@Override
|
||||||
|
public String id(CredentialFrameworkProvider p) {
|
||||||
|
return p.id();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, String> props = SpiSystemProperties.readPrefixed(PROP_FRAMEWORK_PREFIX);
|
||||||
|
ProviderConfig config = new ProviderConfig(provider.id(), props);
|
||||||
|
|
||||||
|
if (LOG.isLoggable(Level.INFO)) {
|
||||||
|
LOG.info("Selected credential framework provider: " + provider.id() + " (keys: " + props.keySet() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.allocate(config);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs provider help information (supported keys) for diagnostics.
|
* Logs provider help information (supported keys) for diagnostics.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -36,8 +36,8 @@
|
|||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This package centralizes deterministic provider selection and a minimal
|
* This package centralizes deterministic provider selection and a minimal
|
||||||
* configuration convention for multiple SPIs (store, audit, publication, and
|
* configuration convention for multiple SPIs (store, audit, publication,
|
||||||
* future components).
|
* credential frameworks, and future components).
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -33,8 +33,6 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.pki.spi.framework;
|
package zeroecho.pki.spi.framework;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import zeroecho.pki.spi.ConfigurableProvider;
|
import zeroecho.pki.spi.ConfigurableProvider;
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
|
|
||||||
@@ -44,6 +42,7 @@ import zeroecho.pki.spi.ProviderConfig;
|
|||||||
* <p>
|
* <p>
|
||||||
* The PKI runtime selects a framework provider using
|
* The PKI runtime selects a framework provider using
|
||||||
* {@link java.util.ServiceLoader} and instantiates it through
|
* {@link java.util.ServiceLoader} and instantiates it through
|
||||||
|
* {@link #validateConfig(ProviderConfig)} and
|
||||||
* {@link #allocate(ProviderConfig)}. Provider selection is performed by
|
* {@link #allocate(ProviderConfig)}. Provider selection is performed by
|
||||||
* {@code PkiBootstrap} using configuration properties (similarly to store,
|
* {@code PkiBootstrap} using configuration properties (similarly to store,
|
||||||
* audit, async bus, and signature workflow providers).
|
* audit, async bus, and signature workflow providers).
|
||||||
@@ -65,7 +64,9 @@ public interface CredentialFrameworkProvider extends ConfigurableProvider<Creden
|
|||||||
* <p>
|
* <p>
|
||||||
* Implementations must validate that {@link ProviderConfig#backendId()} matches
|
* Implementations must validate that {@link ProviderConfig#backendId()} matches
|
||||||
* {@link #id()}. A mismatch must be reported as
|
* {@link #id()}. A mismatch must be reported as
|
||||||
* {@link IllegalArgumentException}.
|
* {@link IllegalArgumentException}. Implementations should invoke
|
||||||
|
* {@link #validateConfig(ProviderConfig)} from this method so that the same
|
||||||
|
* enforcement applies even when a provider is used outside bootstrap.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param config provider configuration (never {@code null})
|
* @param config provider configuration (never {@code null})
|
||||||
@@ -78,24 +79,4 @@ public interface CredentialFrameworkProvider extends ConfigurableProvider<Creden
|
|||||||
@Override
|
@Override
|
||||||
CredentialFramework allocate(ProviderConfig config);
|
CredentialFramework allocate(ProviderConfig config);
|
||||||
|
|
||||||
/**
|
|
||||||
* Enforces that the provided configuration is intended for this provider.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* This helper is intended for defensive checks inside provider implementations.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param provider provider instance (never {@code null})
|
|
||||||
* @param config provider configuration (never {@code null})
|
|
||||||
* @throws NullPointerException if any argument is {@code null}
|
|
||||||
* @throws IllegalArgumentException if the backend id does not match
|
|
||||||
*/
|
|
||||||
static void requireIdMatch(final CredentialFrameworkProvider provider, final ProviderConfig config) {
|
|
||||||
Objects.requireNonNull(provider, "provider");
|
|
||||||
Objects.requireNonNull(config, "config");
|
|
||||||
|
|
||||||
if (!provider.id().equals(config.backendId())) {
|
|
||||||
throw new IllegalArgumentException("ProviderConfig backendId mismatch.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,5 +40,13 @@
|
|||||||
* framework-specific fields. Framework-specific types must not leak into the
|
* framework-specific fields. Framework-specific types must not leak into the
|
||||||
* public API.
|
* public API.
|
||||||
* </p>
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Runtime selection and allocation of concrete framework implementations is
|
||||||
|
* performed through {@link java.util.ServiceLoader} and the
|
||||||
|
* {@code CredentialFrameworkProvider} SPI. Bootstrap configuration follows the
|
||||||
|
* same deterministic, fail-fast conventions as the other PKI SPIs.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
package zeroecho.pki.spi.framework;
|
package zeroecho.pki.spi.framework;
|
||||||
|
|||||||
@@ -36,9 +36,9 @@
|
|||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* SPIs are used to plug in persistence backends, audit sinks, publishing
|
* SPIs are used to plug in persistence backends, audit sinks, publishing
|
||||||
* targets, and framework integrations while keeping the public PKI API
|
* targets, credential frameworks, and other integrations while keeping the
|
||||||
* framework-agnostic. Implementations are typically discovered via
|
* public PKI API framework-agnostic. Implementations are typically discovered
|
||||||
* {@link java.util.ServiceLoader}.
|
* via {@link java.util.ServiceLoader}.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
zeroecho.pki.impl.framework.x509.bc.BcX509CredentialFrameworkProvider
|
||||||
@@ -35,6 +35,7 @@ package zeroecho.pki.spi.bootstrap;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
@@ -44,8 +45,13 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import zeroecho.pki.api.PkiId;
|
||||||
|
import zeroecho.pki.api.audit.Principal;
|
||||||
import zeroecho.pki.spi.audit.AuditSink;
|
import zeroecho.pki.spi.audit.AuditSink;
|
||||||
|
import zeroecho.pki.spi.crypto.SignatureWorkflow;
|
||||||
|
import zeroecho.pki.spi.framework.CredentialFramework;
|
||||||
import zeroecho.pki.spi.store.PkiStore;
|
import zeroecho.pki.spi.store.PkiStore;
|
||||||
|
import zeroecho.pki.util.async.AsyncBus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JUnit 5 tests for {@link PkiBootstrap}.
|
* JUnit 5 tests for {@link PkiBootstrap}.
|
||||||
@@ -75,9 +81,15 @@ public final class PkiBootstrapTest {
|
|||||||
|
|
||||||
clearPrefix("zeroecho.pki.store.");
|
clearPrefix("zeroecho.pki.store.");
|
||||||
clearPrefix("zeroecho.pki.audit.");
|
clearPrefix("zeroecho.pki.audit.");
|
||||||
|
clearPrefix("zeroecho.pki.framework.");
|
||||||
|
clearPrefix("zeroecho.pki.async.");
|
||||||
|
clearPrefix("zeroecho.pki.crypto.workflow.");
|
||||||
|
|
||||||
System.clearProperty("zeroecho.pki.store");
|
System.clearProperty("zeroecho.pki.store");
|
||||||
System.clearProperty("zeroecho.pki.audit");
|
System.clearProperty("zeroecho.pki.audit");
|
||||||
|
System.clearProperty("zeroecho.pki.framework");
|
||||||
|
System.clearProperty("zeroecho.pki.async");
|
||||||
|
System.clearProperty("zeroecho.pki.crypto.workflow");
|
||||||
|
|
||||||
System.out.println("...tempDir=" + this.tempDir);
|
System.out.println("...tempDir=" + this.tempDir);
|
||||||
System.out.println("...ok");
|
System.out.println("...ok");
|
||||||
@@ -162,6 +174,125 @@ public final class PkiBootstrapTest {
|
|||||||
System.out.println("...ok");
|
System.out.println("...ok");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openAudit_memory_rejectsInvalidSize() {
|
||||||
|
System.out.println("openAudit_memory_rejectsInvalidSize");
|
||||||
|
|
||||||
|
System.setProperty("zeroecho.pki.audit", "memory");
|
||||||
|
System.setProperty("zeroecho.pki.audit.size", "abc");
|
||||||
|
|
||||||
|
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> PkiBootstrap.openAudit());
|
||||||
|
System.out.println("...message=" + exception.getMessage());
|
||||||
|
assertEquals("Configuration key 'size' must be an integer.", exception.getMessage());
|
||||||
|
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openCredentialFramework_explicitX509BcProvider() {
|
||||||
|
System.out.println("openCredentialFramework_explicitX509BcProvider");
|
||||||
|
|
||||||
|
System.setProperty("zeroecho.pki.framework", "x509-bc");
|
||||||
|
|
||||||
|
CredentialFramework framework = PkiBootstrap.openCredentialFramework();
|
||||||
|
assertNotNull(framework);
|
||||||
|
|
||||||
|
String frameworkClassName = framework.getClass().getName();
|
||||||
|
System.out.println("...frameworkClass=" + frameworkClassName);
|
||||||
|
|
||||||
|
assertEquals("zeroecho.pki.impl.framework.x509.bc.BcX509CredentialFramework", frameworkClassName);
|
||||||
|
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openCredentialFramework_x509Bc_byDefault() {
|
||||||
|
System.out.println("openCredentialFramework_x509Bc_byDefault");
|
||||||
|
|
||||||
|
CredentialFramework framework = PkiBootstrap.openCredentialFramework();
|
||||||
|
assertNotNull(framework);
|
||||||
|
|
||||||
|
String frameworkClassName = framework.getClass().getName();
|
||||||
|
System.out.println("...frameworkClass=" + frameworkClassName);
|
||||||
|
|
||||||
|
assertEquals("zeroecho.pki.impl.framework.x509.bc.BcX509CredentialFramework", frameworkClassName);
|
||||||
|
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openCredentialFramework_x509Bc_ignoresUnknownKey() {
|
||||||
|
System.out.println("openCredentialFramework_x509Bc_ignoresUnknownKey");
|
||||||
|
|
||||||
|
System.setProperty("zeroecho.pki.framework", "x509-bc");
|
||||||
|
System.setProperty("zeroecho.pki.framework.unused", "value");
|
||||||
|
|
||||||
|
CredentialFramework framework = PkiBootstrap.openCredentialFramework();
|
||||||
|
assertNotNull(framework);
|
||||||
|
|
||||||
|
String frameworkClassName = framework.getClass().getName();
|
||||||
|
System.out.println("...frameworkClass=" + frameworkClassName);
|
||||||
|
|
||||||
|
assertEquals("zeroecho.pki.impl.framework.x509.bc.BcX509CredentialFramework", frameworkClassName);
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openAsync_file_usesTempLogPath() {
|
||||||
|
System.out.println("openAsync_file_usesTempLogPath");
|
||||||
|
|
||||||
|
System.setProperty("zeroecho.pki.async", "file");
|
||||||
|
System.setProperty("zeroecho.pki.async.logPath", this.tempDir.resolve("async").resolve("async.log").toString());
|
||||||
|
|
||||||
|
AsyncBus<PkiId, Principal, String, Object> bus = PkiBootstrap.openAsyncBus();
|
||||||
|
assertNotNull(bus);
|
||||||
|
|
||||||
|
String busClassName = bus.getClass().getName();
|
||||||
|
System.out.println("...asyncClass=" + busClassName);
|
||||||
|
|
||||||
|
assertEquals("zeroecho.pki.util.async.impl.DurableAsyncBus", busClassName);
|
||||||
|
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openSignatureWorkflow_zeroEchoLib_usesConfiguredKeyringPath() {
|
||||||
|
System.out.println("openSignatureWorkflow_zeroEchoLib_usesConfiguredKeyringPath");
|
||||||
|
|
||||||
|
System.setProperty("zeroecho.pki.crypto.workflow", "zeroecho-lib");
|
||||||
|
System.setProperty("zeroecho.pki.crypto.workflow.keyringPath",
|
||||||
|
this.tempDir.resolve("workflow").resolve("keyring.zek").toString());
|
||||||
|
System.setProperty("zeroecho.pki.crypto.workflow.keyRefPrefix", "test-prefix:");
|
||||||
|
System.setProperty("zeroecho.pki.crypto.workflow.requireComponentSuffix", "false");
|
||||||
|
|
||||||
|
SignatureWorkflow workflow = PkiBootstrap.openSignatureWorkflow();
|
||||||
|
assertNotNull(workflow);
|
||||||
|
|
||||||
|
String workflowClassName = workflow.getClass().getName();
|
||||||
|
System.out.println("...workflowClass=" + workflowClassName);
|
||||||
|
|
||||||
|
assertEquals("zeroecho.pki.impl.crypto.zeroecholib.ZeroEchoLibSignatureWorkflow", workflowClassName);
|
||||||
|
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void providerValidation_rejectsBackendIdMismatch() {
|
||||||
|
System.out.println("providerValidation_rejectsBackendIdMismatch");
|
||||||
|
|
||||||
|
zeroecho.pki.impl.fs.FilesystemPkiStoreProvider provider = new zeroecho.pki.impl.fs.FilesystemPkiStoreProvider();
|
||||||
|
zeroecho.pki.spi.ProviderConfig config = new zeroecho.pki.spi.ProviderConfig("other",
|
||||||
|
java.util.Map.of("root", this.tempDir.resolve("store").toString()));
|
||||||
|
|
||||||
|
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> provider.validateConfig(config));
|
||||||
|
System.out.println("...message=" + exception.getMessage());
|
||||||
|
assertEquals("ProviderConfig backendId mismatch.", exception.getMessage());
|
||||||
|
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
private static void clearPrefix(String prefix) {
|
private static void clearPrefix(String prefix) {
|
||||||
Properties props = System.getProperties();
|
Properties props = System.getProperties();
|
||||||
for (Object keyObj : props.keySet().toArray()) {
|
for (Object keyObj : props.keySet().toArray()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user