refactor!: consolidate crypto architecture and security model

* make ZeroEchoSession the sole policy, audit, and runtime boundary
* replace combined key builders with operation-specific SPI and typed metadata
* remove obsolete pre-release compatibility APIs and global crypto operations
* finalize JCA agreement contexts and replace inheritance with composition
* harden secret lifecycle, key destruction, hybrid KEX, PBKDF2, and audit handling
* standardize PairSeq I/O and introduce immutable validated value types
* migrate app, ext, samples, and required pki integration points
* expand correctness, security, concurrency, and malformed-input coverage

BREAKING CHANGE: removes deprecated pre-release global configuration, legacy
context factories, combined key-builder contracts, String-based password APIs,
unchecked PairSeq writing, BlockGeometry public fields, and other compatibility
facades.
This commit is contained in:
2026-07-28 19:20:30 +02:00
parent 7319aca0db
commit 49dc080c65
298 changed files with 12802 additions and 8763 deletions

View File

@@ -42,6 +42,7 @@ import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Base64;
@@ -61,10 +62,11 @@ import org.apache.commons.cli.ParseException;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.CryptoAlgorithms;
import zeroecho.core.KeyOperation;
import zeroecho.core.KeyOperationInfo;
import zeroecho.core.spec.AlgorithmKeySpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
import zeroecho.core.spi.SymmetricKeyBuilder;
import zeroecho.core.storage.KeyringStore;
import zeroecho.sdk.ZeroEchoSession;
/**
* Command-line utility for managing key material in a text-based keyring store.
@@ -231,6 +233,7 @@ public final class KeyStoreManagement { // NOPMD
* @throws IOException if I/O fails
*/
public static int main(final String[] args, final Options dispatcherOptions) throws ParseException, IOException {
ZeroEchoSession session = new ZeroEchoSession();
defineOptions(dispatcherOptions);
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(dispatcherOptions, args);
@@ -245,14 +248,15 @@ public final class KeyStoreManagement { // NOPMD
}
Path keyringPath = Path.of(cmd.getOptionValue(KEYSTORE_OPTION.getLongOpt()));
KeyringStore store = Files.exists(keyringPath) ? KeyringStore.load(keyringPath) : new KeyringStore();
KeyringStore store = Files.exists(keyringPath) ? KeyringStore.load(session, keyringPath)
: new KeyringStore(session);
if (cmd.hasOption(LIST_ALIASES_OPTION.getLongOpt())) {
listAliases(store);
return 0;
}
if (cmd.hasOption(GENERATE_OPTION.getLongOpt())) {
doGenerate(store, cmd);
doGenerate(session, store, cmd);
store.save(keyringPath);
return 0;
}
@@ -311,8 +315,12 @@ public final class KeyStoreManagement { // NOPMD
Set<String> ids = CryptoAlgorithms.available();
for (String id : ids) {
CryptoAlgorithm a = CryptoAlgorithms.require(id);
boolean hasAsym = !a.asymmetricBuildersInfo().isEmpty();
boolean hasSym = !a.symmetricBuildersInfo().isEmpty();
boolean hasAsym = a.keyOperations().stream()
.anyMatch(info -> info.operation() != KeyOperation.SYMMETRIC_GENERATE
&& info.operation() != KeyOperation.SYMMETRIC_IMPORT);
boolean hasSym = a.keyOperations().stream()
.anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE
|| info.operation() == KeyOperation.SYMMETRIC_IMPORT);
out.printf(Locale.ROOT, "%-12s asym:%s sym:%s%n", id, hasAsym, hasSym);
}
}
@@ -346,7 +354,8 @@ public final class KeyStoreManagement { // NOPMD
* @param store keyring store to mutate
* @param cmd parsed command line
*/
public static void doGenerate(final KeyringStore store, final CommandLine cmd) { // NOPMD
public static void doGenerate(final ZeroEchoSession session, final KeyringStore store,
final CommandLine cmd) {
String algId = required(cmd, ALG_OPTION, "--alg is required for --generate");
String aliasBase = required(cmd, ALIAS_OPTION, "--alias is required for --generate");
String kind = cmd.getOptionValue(KIND_OPTION.getLongOpt());
@@ -355,119 +364,155 @@ public final class KeyStoreManagement { // NOPMD
boolean overwrite = cmd.hasOption(OVERWRITE_OPTION.getLongOpt());
CryptoAlgorithm alg = CryptoAlgorithms.require(algId);
boolean canAsym = !alg.asymmetricBuildersInfo().isEmpty();
boolean canSym = !alg.symmetricBuildersInfo().isEmpty();
boolean canAsym = alg.keyOperations().stream()
.anyMatch(info -> info.operation() == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE);
boolean canSym = alg.keyOperations().stream()
.anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE);
boolean doAsym = "asym".equalsIgnoreCase(kind) || (kind == null && canAsym && !canSym);
boolean doSym = "sym".equalsIgnoreCase(kind) || (kind == null && canSym && !canAsym);
GenerationKind generationKind = selectGenerationKind(kind, canAsym, canSym);
if (generationKind == GenerationKind.ASYMMETRIC) {
generateAsymmetric(session, store, alg, algId, aliasBase, pubSfx, prvSfx, overwrite);
}
if (generationKind == GenerationKind.SYMMETRIC) {
generateSymmetric(session, store, alg, algId, aliasBase, overwrite);
}
}
if (!doAsym && !doSym && canAsym && canSym) {
private static GenerationKind selectGenerationKind(String requestedKind, boolean canAsymmetric,
boolean canSymmetric) {
if ("asym".equalsIgnoreCase(requestedKind) || requestedKind == null && canAsymmetric && !canSymmetric) {
return GenerationKind.ASYMMETRIC;
}
if ("sym".equalsIgnoreCase(requestedKind) || requestedKind == null && canSymmetric && !canAsymmetric) {
return GenerationKind.SYMMETRIC;
}
if (canAsymmetric && canSymmetric) {
throw new IllegalArgumentException("Algorithm supports both; specify --kind sym|asym");
}
return GenerationKind.NONE;
}
if (doAsym) {
KeyPair kp = null;
CryptoAlgorithm.AsymBuilderInfo used = null;
for (CryptoAlgorithm.AsymBuilderInfo bi : alg.asymmetricBuildersInfo()) {
if (bi.defaultKeySpec == null) {
continue;
}
@SuppressWarnings("unchecked")
Class<AlgorithmKeySpec> st = (Class<AlgorithmKeySpec>) bi.specType;
AsymmetricKeyBuilder<AlgorithmKeySpec> b = alg.asymmetricKeyBuilder(st);
try {
kp = b.generateKeyPair((AlgorithmKeySpec) bi.defaultKeySpec);
if (kp != null) {
used = bi;
break;
}
} catch (Throwable ignore) { // NOPMD
}
}
if (kp == null || used == null) {
throw new IllegalStateException("No asymmetric builder with default spec worked for " + algId);
}
Class<?> pubImp = null;
Class<?> prvImp = null;
for (CryptoAlgorithm.AsymBuilderInfo x : alg.asymmetricBuildersInfo()) {
if (looksLikeImportSpecForPublic(x.specType)) {
pubImp = x.specType;
}
if (looksLikeImportSpecForPrivate(x.specType)) {
prvImp = x.specType;
}
}
if (pubImp == null && prvImp == null) {
throw new IllegalStateException("No import spec class found for " + algId + " (asymmetric)");
}
byte[] spki = kp.getPublic() != null ? kp.getPublic().getEncoded() : null;
byte[] pkcs8 = kp.getPrivate() != null ? kp.getPrivate().getEncoded() : null;
AlgorithmKeySpec pubSpec = pubImp != null ? makeImportSpec(pubImp, spki, algId, used.defaultKeySpec) : null;
AlgorithmKeySpec prvSpec = prvImp != null ? makeImportSpec(prvImp, pkcs8, algId, used.defaultKeySpec)
: null;
if (pubImp != null && pubSpec == null) {
throw new IllegalStateException("Cannot construct public import spec for " + algId);
}
if (prvImp != null && prvSpec == null) {
throw new IllegalStateException("Cannot construct private import spec for " + algId);
}
String pubAlias = aliasBase + pubSfx;
String prvAlias = aliasBase + prvSfx;
ensureWritable(store, pubAlias, overwrite);
ensureWritable(store, prvAlias, overwrite);
store.putPublic(pubAlias, algId, pubSpec);
store.putPrivate(prvAlias, algId, prvSpec);
PrintWriter out = new PrintWriter(System.out, true, StandardCharsets.UTF_8); // NOPMD
out.printf("Generated %s -> %s, %s%n", algId, pubAlias, prvAlias);
private static void generateAsymmetric(ZeroEchoSession session, KeyringStore store, CryptoAlgorithm algorithm,
String algorithmId, String aliasBase, String publicSuffix, String privateSuffix, boolean overwrite) {
GeneratedKeyPair generated = firstGeneratedKeyPair(session, algorithm, algorithmId);
Class<?> publicImport = findImportSpecClass(algorithm, KeyOperation.ASYMMETRIC_PUBLIC_IMPORT, true);
Class<?> privateImport = findImportSpecClass(algorithm, KeyOperation.ASYMMETRIC_PRIVATE_IMPORT, false);
if (publicImport == null && privateImport == null) {
throw new IllegalStateException("No import spec class found for " + algorithmId + " (asymmetric)");
}
if (doSym) {
SecretKey sk = null;
CryptoAlgorithm.SymBuilderInfo used = null;
for (CryptoAlgorithm.SymBuilderInfo bi : alg.symmetricBuildersInfo()) {
if (bi.defaultKeySpec() == null) {
continue;
}
@SuppressWarnings("unchecked")
Class<AlgorithmKeySpec> st = (Class<AlgorithmKeySpec>) bi.specType();
SymmetricKeyBuilder<AlgorithmKeySpec> b = alg.symmetricKeyBuilder(st);
try {
sk = b.generateSecret((AlgorithmKeySpec) bi.defaultKeySpec());
if (sk != null) {
used = bi;
break;
}
} catch (Throwable ignore) { // NOPMD
KeyPair pair = generated.pair();
byte[] publicEncoding = pair.getPublic() == null ? null : pair.getPublic().getEncoded();
byte[] privateEncoding = pair.getPrivate() == null ? null : pair.getPrivate().getEncoded();
AlgorithmKeySpec publicSpec = publicImport == null ? null
: makeImportSpec(publicImport, publicEncoding, algorithmId, generated.info().defaultSpec());
AlgorithmKeySpec privateSpec = privateImport == null ? null
: makeImportSpec(privateImport, privateEncoding, algorithmId, generated.info().defaultSpec());
requireImportSpec(publicImport, publicSpec, "public", algorithmId);
requireImportSpec(privateImport, privateSpec, "private", algorithmId);
String publicAlias = aliasBase + publicSuffix;
String privateAlias = aliasBase + privateSuffix;
ensureWritable(store, publicAlias, overwrite);
ensureWritable(store, privateAlias, overwrite);
store.putPublic(publicAlias, algorithmId, publicSpec);
store.putPrivate(privateAlias, algorithmId, privateSpec);
PrintWriter out = new PrintWriter(System.out, true, StandardCharsets.UTF_8); // NOPMD
out.printf("Generated %s -> %s, %s%n", algorithmId, publicAlias, privateAlias);
}
private static GeneratedKeyPair firstGeneratedKeyPair(ZeroEchoSession session, CryptoAlgorithm algorithm,
String algorithmId) {
for (KeyOperationInfo info : algorithm.keyOperations()) {
if (info.operation() != KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE || info.defaultSpec() == null) {
continue;
}
@SuppressWarnings("unchecked")
Class<AlgorithmKeySpec> specType = (Class<AlgorithmKeySpec>) info.specType();
try {
KeyPair pair = session.keyBuilders().asymmetric().keyPairGenerator(algorithmId, specType)
.generateKeyPair(info.defaultSpec());
if (pair != null) {
return new GeneratedKeyPair(pair, info);
}
} catch (GeneralSecurityException | RuntimeException ignored) { // NOPMD
// Try the next registered default specification.
}
if (sk == null || used == null) {
throw new IllegalStateException("No symmetric builder with default spec worked for " + algId);
}
Class<?> impSym = findSymmetricImportSpecClass(alg);
if (impSym == null) {
throw new IllegalStateException("No symmetric import spec class for " + algId);
}
byte[] raw = sk.getEncoded();
AlgorithmKeySpec secSpec = makeImportSpec(impSym, raw, algId, used.defaultKeySpec());
if (secSpec == null) {
throw new IllegalStateException("Cannot construct symmetric import spec for " + algId);
}
ensureWritable(store, aliasBase, overwrite);
store.putSecret(aliasBase, algId, secSpec);
PrintWriter out = new PrintWriter(System.out, true, StandardCharsets.UTF_8); // NOPMD
out.printf("Generated %s -> %s%n", algId, aliasBase);
}
throw new IllegalStateException("No asymmetric builder with default spec worked for " + algorithmId);
}
private static Class<?> findImportSpecClass(CryptoAlgorithm algorithm, KeyOperation operation,
boolean publicImport) {
for (KeyOperationInfo info : algorithm.keyOperations()) {
boolean matchingName = publicImport ? looksLikeImportSpecForPublic(info.specType())
: looksLikeImportSpecForPrivate(info.specType());
if (info.operation() == operation && matchingName) {
return info.specType();
}
}
return null;
}
private static void requireImportSpec(Class<?> specType, AlgorithmKeySpec spec, String kind, String algorithmId) {
if (specType != null && spec == null) {
throw new IllegalStateException("Cannot construct " + kind + " import spec for " + algorithmId);
}
}
private static void generateSymmetric(ZeroEchoSession session, KeyringStore store, CryptoAlgorithm algorithm,
String algorithmId, String alias, boolean overwrite) {
GeneratedSecret generated = firstGeneratedSecret(session, algorithm, algorithmId);
Class<?> importType = findSymmetricImportSpecClass(algorithm);
if (importType == null) {
throw new IllegalStateException("No symmetric import spec class for " + algorithmId);
}
byte[] encoding = generated.key().getEncoded();
AlgorithmKeySpec spec = makeImportSpec(importType, encoding, algorithmId, generated.info().defaultSpec());
if (spec == null) {
throw new IllegalStateException("Cannot construct symmetric import spec for " + algorithmId);
}
ensureWritable(store, alias, overwrite);
store.putSecret(alias, algorithmId, spec);
PrintWriter out = new PrintWriter(System.out, true, StandardCharsets.UTF_8); // NOPMD
out.printf("Generated %s -> %s%n", algorithmId, alias);
}
private static GeneratedSecret firstGeneratedSecret(ZeroEchoSession session, CryptoAlgorithm algorithm,
String algorithmId) {
for (KeyOperationInfo info : algorithm.keyOperations()) {
if (info.operation() != KeyOperation.SYMMETRIC_GENERATE || info.defaultSpec() == null) {
continue;
}
@SuppressWarnings("unchecked")
Class<AlgorithmKeySpec> specType = (Class<AlgorithmKeySpec>) info.specType();
try {
SecretKey key = session.keyBuilders().symmetric().generator(algorithmId, specType)
.generateSecret(info.defaultSpec());
if (key != null) {
return new GeneratedSecret(key, info);
}
} catch (GeneralSecurityException | RuntimeException ignored) { // NOPMD
// Try the next registered default specification.
}
}
throw new IllegalStateException("No symmetric builder with default spec worked for " + algorithmId);
}
private record GeneratedKeyPair(KeyPair pair, KeyOperationInfo info) {
}
private record GeneratedSecret(SecretKey key, KeyOperationInfo info) {
}
/** Selects the exact key-generation operation requested by the command. */
private enum GenerationKind {
ASYMMETRIC,
SYMMETRIC,
NONE
}
/**
@@ -545,19 +590,11 @@ public final class KeyStoreManagement { // NOPMD
}
private static Class<?> findSymmetricImportSpecClass(CryptoAlgorithm alg) {
for (CryptoAlgorithm.SymBuilderInfo x : alg.symmetricBuildersInfo()) {
String n = x.specType().getSimpleName();
if (n.contains("Import") || n.endsWith("KeyImportSpec") || n.endsWith("SecretSpec")) {
for (KeyOperationInfo x : alg.keyOperations()) {
if (x.operation() == KeyOperation.SYMMETRIC_IMPORT) {
return x.specType();
}
}
for (CryptoAlgorithm.SymBuilderInfo x : alg.symmetricBuildersInfo()) {
try {
x.specType().getConstructor(byte[].class);
return x.specType();
} catch (NoSuchMethodException ignored) {
}
}
return null;
}