chore(text): source code format

This commit is contained in:
2026-07-30 20:28:43 +02:00
parent bc68e0433c
commit dedd16f584
262 changed files with 4867 additions and 5717 deletions

View File

@@ -138,18 +138,16 @@ public final class Guard {
/**
* Executes Guard with an explicit keyring unlock source.
*
* @param args command arguments
* @param options dispatcher options
* @param args command arguments
* @param options dispatcher options
* @param keyringUnlockProvider destroyable-password provider
* @return process exit code
* @throws ParseException if parsing fails
* @throws IOException if I/O fails
* @throws ParseException if parsing fails
* @throws IOException if I/O fails
* @throws GeneralSecurityException if cryptographic processing fails
*/
@SuppressWarnings({ "PMD.NcssCount", "PMD.CognitiveComplexity",
"PMD.CyclomaticComplexity", "PMD.NPathComplexity" })
public static int main(final String[] args, final Options options,
KeyringUnlockProvider keyringUnlockProvider)
@SuppressWarnings({ "PMD.NcssCount", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "PMD.NPathComplexity" })
public static int main(final String[] args, final Options options, KeyringUnlockProvider keyringUnlockProvider)
throws ParseException, IOException, GeneralSecurityException {
// ---- operation selection
final Option OPT_ENCRYPT = Option.builder("e").longOpt("encrypt").hasArg().argName("in-file")
@@ -206,8 +204,7 @@ public final class Guard {
.desc("Recipient KEK length: exactly 16 or 32 bytes (default 32)").get();
final Option OPT_PBKDF2_MAX = Option.builder().longOpt("pbkdf2-max").hasArg().argName("iterations")
.desc("Operational PBKDF2 ceiling; required for password operations").get();
final Option OPT_PBKDF2_HARD_MAX = Option.builder().longOpt("pbkdf2-hard-max").hasArg()
.argName("iterations")
final Option OPT_PBKDF2_HARD_MAX = Option.builder().longOpt("pbkdf2-hard-max").hasArg().argName("iterations")
.desc("Absolute decoded PBKDF2 safety ceiling; required for password operations").get();
// ---- decoys (all types)
@@ -260,8 +257,7 @@ public final class Guard {
final CommandLine cmd = parser.parse(options, args);
final boolean passwordOperation = cmd.hasOption(OPT_TO_PSW) || cmd.hasOption(OPT_DECOY_PSW)
|| cmd.hasOption(OPT_DECOY_PSW_RAND) || cmd.hasOption(OPT_PASSWORD);
final ZeroEchoSession session = createSession(cmd, OPT_PBKDF2_MAX, OPT_PBKDF2_HARD_MAX,
passwordOperation);
final ZeroEchoSession session = createSession(cmd, OPT_PBKDF2_MAX, OPT_PBKDF2_HARD_MAX, passwordOperation);
final boolean encrypt = cmd.hasOption(OPT_ENCRYPT);
final Path inPath = Paths.get(cmd.getOptionValue(encrypt ? OPT_ENCRYPT : OPT_DECRYPT));
@@ -359,8 +355,7 @@ public final class Guard {
// envelope builder (new API)
final MultiRecipientDataSourceBuilder env = MultiRecipientDataSourceBuilder.builder(session)
.payloadKeyBytes(cekBytes)
.headerLimits(maxRecipients, maxEntryLen);
.payloadKeyBytes(cekBytes).headerLimits(maxRecipients, maxEntryLen);
UnlockMaterial borrowedUnlockMaterial = null;
try (env) {
if (aes != null) {
@@ -374,11 +369,10 @@ public final class Guard {
if (encrypt) {
final int iter = Integer.parseInt(cmd.getOptionValue(OPT_PSW_ITER, "200000"));
final int saltLen = Integer.parseInt(cmd.getOptionValue(OPT_PSW_SALT, "16"));
final int kekLen = RecipientKekSizes.requireSupported(
Integer.parseInt(cmd.getOptionValue(OPT_PSW_KEK, "32")));
final int kekLen = RecipientKekSizes
.requireSupported(Integer.parseInt(cmd.getOptionValue(OPT_PSW_KEK, "32")));
KeyringStore ks = loadKeyringIfPresent(cmd, OPT_KEYRING,
keyringUnlockProvider);
KeyringStore ks = loadKeyringIfPresent(cmd, OPT_KEYRING, keyringUnlockProvider);
try (ks) {
for (String alias : cmd.getOptionValues(OPT_TO_ALIAS) == null ? new String[0]
: cmd.getOptionValues(OPT_TO_ALIAS)) {
@@ -424,8 +418,7 @@ public final class Guard {
throw new ParseException("Specify exactly one of --priv-alias or --password for decryption");
}
if (privAlias != null) {
try (KeyringStore ks = requireKeyring(cmd, OPT_KEYRING,
keyringUnlockProvider)) {
try (KeyringStore ks = requireKeyring(cmd, OPT_KEYRING, keyringUnlockProvider)) {
final KeyringStore.PrivateWithId pr = ks.getPrivateWithId(privAlias);
borrowedUnlockMaterial = new UnlockMaterial.Private(pr.key());
}
@@ -474,8 +467,7 @@ public final class Guard {
boolean hasAbsoluteMaximum = cmd.hasOption(absoluteMaximumOption);
if (!hasOperationalMaximum && !hasAbsoluteMaximum) {
if (passwordOperation) {
throw new ParseException(
"Password operations require --pbkdf2-max and --pbkdf2-hard-max");
throw new ParseException("Password operations require --pbkdf2-max and --pbkdf2-hard-max");
}
return new ZeroEchoSession();
}
@@ -485,11 +477,9 @@ public final class Guard {
try {
int operationalMaximum = Integer.parseInt(cmd.getOptionValue(operationalMaximumOption));
int absoluteMaximum = Integer.parseInt(cmd.getOptionValue(absoluteMaximumOption));
return new ZeroEchoSession().withPbkdf2Limits(
new Pbkdf2Limits(operationalMaximum, absoluteMaximum));
return new ZeroEchoSession().withPbkdf2Limits(new Pbkdf2Limits(operationalMaximum, absoluteMaximum));
} catch (IllegalArgumentException exception) {
ParseException parseException =
new ParseException("Invalid PBKDF2 limits: " + exception.getMessage());
ParseException parseException = new ParseException("Invalid PBKDF2 limits: " + exception.getMessage());
parseException.initCause(exception);
throw parseException;
}
@@ -524,9 +514,9 @@ public final class Guard {
* </ul>
*
* <p>
* In both cases, the created context is consumed by
* the matching {@link MultiRecipientDataSourceBuilder} recipient method and is
* closed internally by the resulting content.
* In both cases, the created context is consumed by the matching
* {@link MultiRecipientDataSourceBuilder} recipient method and is closed
* internally by the resulting content.
* </p>
*
* @param env target builder to which the recipient is added
@@ -542,8 +532,8 @@ public final class Guard {
*/
@SuppressWarnings({ "PMD.CloseResource", "PMD.UseTryWithResources" })
private static void addRecipientFromAlias(ZeroEchoSession session, MultiRecipientDataSourceBuilder env,
KeyringStore ks, String alias,
int kekBytes, int saltLen, boolean decoy) throws GeneralSecurityException, IOException {
KeyringStore ks, String alias, int kekBytes, int saltLen, boolean decoy)
throws GeneralSecurityException, IOException {
KeyringStore.PublicWithId r = ks.getPublicWithId(alias);
final String algId = r.algorithm();
final java.security.PublicKey pub = r.key();
@@ -602,16 +592,14 @@ public final class Guard {
}
private static KeyringStore loadKeyringIfPresent(CommandLine cmd, Option optKs,
KeyringUnlockProvider unlockProvider)
throws IOException, GeneralSecurityException {
KeyringUnlockProvider unlockProvider) throws IOException, GeneralSecurityException {
if (!cmd.hasOption(optKs)) {
return null;
}
return KeyringUnlocks.open(Paths.get(cmd.getOptionValue(optKs)), unlockProvider);
}
private static KeyringStore requireKeyring(CommandLine cmd, Option optKs,
KeyringUnlockProvider unlockProvider)
private static KeyringStore requireKeyring(CommandLine cmd, Option optKs, KeyringUnlockProvider unlockProvider)
throws IOException, ParseException, GeneralSecurityException {
if (!cmd.hasOption(optKs)) {
throw new ParseException("--keyring <file> is required when aliases are used");

View File

@@ -210,30 +210,27 @@ public final class Kem { // NOPMD
* @param args command arguments
* @param opts command options
* @return process exit code
* @throws ParseException if arguments are invalid
* @throws IOException if I/O fails
* @throws ParseException if arguments are invalid
* @throws IOException if I/O fails
* @throws GeneralSecurityException if cryptographic processing fails
*/
public static int main(String[] args, Options opts)
throws ParseException, IOException, GeneralSecurityException {
public static int main(String[] args, Options opts) throws ParseException, IOException, GeneralSecurityException {
return main(args, opts, KeyringUnlocks.console());
}
/**
* Executes the KEM command with an explicit keyring unlock source.
*
* @param args command arguments
* @param opts command options
* @param args command arguments
* @param opts command options
* @param unlockProvider keyring password provider
* @return process exit code
* @throws ParseException if arguments are invalid
* @throws IOException if I/O fails
* @throws ParseException if arguments are invalid
* @throws IOException if I/O fails
* @throws GeneralSecurityException if cryptographic processing fails
*/
@SuppressWarnings({ "PMD.NcssCount", "PMD.CyclomaticComplexity",
"PMD.NPathComplexity" })
public static int main(String[] args, Options opts,
KeyringUnlockProvider unlockProvider)
@SuppressWarnings({ "PMD.NcssCount", "PMD.CyclomaticComplexity", "PMD.NPathComplexity" })
public static int main(String[] args, Options opts, KeyringUnlockProvider unlockProvider)
throws ParseException, IOException, GeneralSecurityException {
ZeroEchoSession session = new ZeroEchoSession();
defineOptions(opts);
@@ -275,105 +272,105 @@ public final class Kem { // NOPMD
final Path keyringPath = Path.of(cmd.getOptionValue(OPT_KEYRING.getLongOpt()));
try (KeyringStore keyring = KeyringUnlocks.open(keyringPath, unlockProvider)) {
// Configure KEM envelope
KemDataContentBuilder kem = KemDataContentBuilder.builder(session).kem(kemId);
if (cmd.hasOption(OPT_DIRECT.getLongOpt())) {
kem = kem.directSecret();
} else {
byte[] info = parseOptionalHex(cmd, OPT_HKDF, "ZeroEcho-KEM".getBytes());
kem = kem.hkdfSha256(info);
}
// typed numeric options
Integer keyBytes = parsedIntOpt(cmd, OPT_KEY_BYTES);
if (keyBytes != null) {
kem = kem.derivedKeyBytes(keyBytes);
}
Integer maxKemCt = parsedIntOpt(cmd, OPT_MAX_KEM_CT);
if (maxKemCt != null) {
kem = kem.maxKemCiphertextLen(maxKemCt);
}
// Configure KEM envelope
KemDataContentBuilder kem = KemDataContentBuilder.builder(session).kem(kemId);
if (cmd.hasOption(OPT_DIRECT.getLongOpt())) {
kem = kem.directSecret();
} else {
byte[] info = parseOptionalHex(cmd, OPT_HKDF, "ZeroEcho-KEM".getBytes());
kem = kem.hkdfSha256(info);
}
// typed numeric options
Integer keyBytes = parsedIntOpt(cmd, OPT_KEY_BYTES);
if (keyBytes != null) {
kem = kem.derivedKeyBytes(keyBytes);
}
Integer maxKemCt = parsedIntOpt(cmd, OPT_MAX_KEM_CT);
if (maxKemCt != null) {
kem = kem.maxKemCiphertextLen(maxKemCt);
}
// Common symmetric knobs
final byte[] aad = parseHexOpt(cmd, OPT_AAD);
final boolean wantHeader = cmd.hasOption(OPT_HEADER.getLongOpt());
// Common symmetric knobs
final byte[] aad = parseHexOpt(cmd, OPT_AAD);
final boolean wantHeader = cmd.hasOption(OPT_HEADER.getLongOpt());
// AES payload
if (wantAes) {
String mode = cmd.getOptionValue(OPT_AES_CIPHER.getLongOpt(), "gcm").toLowerCase(Locale.ROOT);
AesDataContentBuilder aes = AesDataContentBuilder.builder(session);
switch (mode) {
case "gcm" -> {
Integer tagBitsOpt = parsedIntOpt(cmd, OPT_AES_TAG_BITS);
int tagBits = tagBitsOpt == null ? 128 : tagBitsOpt;
// AES payload
if (wantAes) {
String mode = cmd.getOptionValue(OPT_AES_CIPHER.getLongOpt(), "gcm").toLowerCase(Locale.ROOT);
AesDataContentBuilder aes = AesDataContentBuilder.builder(session);
switch (mode) {
case "gcm" -> {
Integer tagBitsOpt = parsedIntOpt(cmd, OPT_AES_TAG_BITS);
int tagBits = tagBitsOpt == null ? 128 : tagBitsOpt;
aes = aes.modeGcm(tagBits);
aes = aes.modeGcm(tagBits);
}
case "ctr" -> aes = aes.modeCtr();
case "cbc" -> aes = aes.modeCbcPkcs5();
default -> throw new ParseException("Unsupported --aes-cipher: " + mode);
}
case "ctr" -> aes = aes.modeCtr();
case "cbc" -> aes = aes.modeCbcPkcs5();
default -> throw new ParseException("Unsupported --aes-cipher: " + mode);
byte[] iv = parseHexOpt(cmd, OPT_AES_IV);
if (iv != null) {
aes = aes.withDecryptionIv(iv);
}
if (aad != null && aad.length > 0) {
aes = aes.withAad(aad);
}
if (wantHeader) {
aes = aes.withHeader();
}
kem = kem.withAes(aes);
}
byte[] iv = parseHexOpt(cmd, OPT_AES_IV);
if (iv != null) {
aes = aes.withDecryptionIv(iv);
}
if (aad != null && aad.length > 0) {
aes = aes.withAad(aad);
}
if (wantHeader) {
aes = aes.withHeader();
}
kem = kem.withAes(aes);
}
// ChaCha payload
if (wantChaCha) {
ChaChaDataContentBuilder cc = ChaChaDataContentBuilder.builder(session);
byte[] nonce = parseHexOpt(cmd, OPT_CHACHA_NONCE);
if (nonce != null) {
cc = cc.withDecryptionNonce(nonce);
// ChaCha payload
if (wantChaCha) {
ChaChaDataContentBuilder cc = ChaChaDataContentBuilder.builder(session);
byte[] nonce = parseHexOpt(cmd, OPT_CHACHA_NONCE);
if (nonce != null) {
cc = cc.withDecryptionNonce(nonce);
}
// counter is an integer, not bytes; use typed parsed option
Integer counter = parsedIntOpt(cmd, OPT_CHACHA_COUNTER);
if (counter != null) {
cc = cc.withCounter(counter);
}
Integer initial = parsedIntOpt(cmd, OPT_CHACHA_INITIAL);
if (initial != null) {
cc = cc.initialCounter(initial);
}
if (aad != null && aad.length > 0) {
cc = cc.withAad(aad); // selects AEAD
}
if (wantHeader) {
cc = cc.withHeader();
}
kem = kem.withChaCha(cc);
}
// counter is an integer, not bytes; use typed parsed option
Integer counter = parsedIntOpt(cmd, OPT_CHACHA_COUNTER);
if (counter != null) {
cc = cc.withCounter(counter);
}
Integer initial = parsedIntOpt(cmd, OPT_CHACHA_INITIAL);
if (initial != null) {
cc = cc.initialCounter(initial);
}
if (aad != null && aad.length > 0) {
cc = cc.withAad(aad); // selects AEAD
}
if (wantHeader) {
cc = cc.withHeader();
}
kem = kem.withChaCha(cc);
}
// Pipeline: source -> kem payload stage
DataContent chain;
if (encrypt) {
String alias = require(cmd, OPT_PUB, "Missing --pub for encryption");
PublicKey recipient = keyring.getPublic(alias);
chain = DataContentChainBuilder.encrypt()
.add(PlainFileBuilder.builder().url(Path.of(input).toUri().toURL()))
.add(kem.recipientPublic(recipient)).build();
} else {
String alias = require(cmd, OPT_PRIV, "Missing --priv for decryption");
PrivateKey recipient = keyring.getPrivate(alias);
chain = DataContentChainBuilder.decrypt()
.add(PlainFileBuilder.builder().url(Path.of(input).toUri().toURL()))
.add(kem.recipientPrivate(recipient)).build();
}
try (InputStream in = chain.getStream(); OutputStream out = Files.newOutputStream(output)) {
in.transferTo(out);
} catch (IOException ex) {
if (LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE, "I/O error", ex);
// Pipeline: source -> kem payload stage
DataContent chain;
if (encrypt) {
String alias = require(cmd, OPT_PUB, "Missing --pub for encryption");
PublicKey recipient = keyring.getPublic(alias);
chain = DataContentChainBuilder.encrypt()
.add(PlainFileBuilder.builder().url(Path.of(input).toUri().toURL()))
.add(kem.recipientPublic(recipient)).build();
} else {
String alias = require(cmd, OPT_PRIV, "Missing --priv for decryption");
PrivateKey recipient = keyring.getPrivate(alias);
chain = DataContentChainBuilder.decrypt()
.add(PlainFileBuilder.builder().url(Path.of(input).toUri().toURL()))
.add(kem.recipientPrivate(recipient)).build();
}
try (InputStream in = chain.getStream(); OutputStream out = Files.newOutputStream(output)) {
in.transferTo(out);
} catch (IOException ex) {
if (LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE, "I/O error", ex);
}
return 1;
}
return 1;
}
return 0;
}
}

View File

@@ -192,16 +192,15 @@ public final class KeyStoreManagement {
/**
* Executes the command with an explicit unlock source.
*
* @param args arguments passed by the application dispatcher
* @param args arguments passed by the application dispatcher
* @param dispatcherOptions dispatcher options
* @param unlockProvider explicit destroyable-password provider
* @param unlockProvider explicit destroyable-password provider
* @return process exit code
* @throws ParseException if parsing fails
* @throws IOException if keyring I/O fails
* @throws ParseException if parsing fails
* @throws IOException if keyring I/O fails
* @throws GeneralSecurityException if cryptographic processing fails
*/
public static int main(final String[] args, final Options dispatcherOptions,
KeyringUnlockProvider unlockProvider)
public static int main(final String[] args, final Options dispatcherOptions, KeyringUnlockProvider unlockProvider)
throws ParseException, IOException, GeneralSecurityException {
ZeroEchoSession session = new ZeroEchoSession();
defineOptions(dispatcherOptions);
@@ -218,8 +217,7 @@ public final class KeyStoreManagement {
}
Path keyringPath = Path.of(cmd.getOptionValue(KEYSTORE_OPTION.getLongOpt()));
try (KeyringStore store = Files.exists(keyringPath)
? KeyringUnlocks.open(keyringPath, unlockProvider)
try (KeyringStore store = Files.exists(keyringPath) ? KeyringUnlocks.open(keyringPath, unlockProvider)
: KeyringUnlocks.create(keyringPath, unlockProvider)) {
if (cmd.hasOption(LIST_ALIASES_OPTION.getLongOpt())) {
listAliases(store);
@@ -310,8 +308,8 @@ public final class KeyStoreManagement {
* @param store keyring store to mutate
* @param cmd parsed command line
*/
public static void doGenerate(final ZeroEchoSession session, final KeyringStore store,
final CommandLine cmd) throws IOException, GeneralSecurityException {
public static void doGenerate(final ZeroEchoSession session, final KeyringStore store, final CommandLine cmd)
throws IOException, GeneralSecurityException {
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());
@@ -391,8 +389,7 @@ public final class KeyStoreManagement {
}
private static void generateSymmetric(ZeroEchoSession session, KeyringStore store, CryptoAlgorithm algorithm,
String algorithmId, String alias, boolean overwrite)
throws IOException, GeneralSecurityException {
String algorithmId, String alias, boolean overwrite) throws IOException, GeneralSecurityException {
GeneratedSecret generated = firstGeneratedSecret(session, algorithm, algorithmId);
ensureWritable(store, alias, overwrite);
store.putSecret(alias, algorithmId, generated.key());
@@ -430,9 +427,7 @@ public final class KeyStoreManagement {
/** Selects the exact key-generation operation requested by the command. */
private enum GenerationKind {
ASYMMETRIC,
SYMMETRIC,
NONE
ASYMMETRIC, SYMMETRIC, NONE
}
private static String required(CommandLine cmd, Option opt, String message) {

View File

@@ -53,8 +53,7 @@ final class KeyringUnlocks {
}
}
private static KeyringPassword acquire(KeyringUnlockProvider provider)
throws IOException {
private static KeyringPassword acquire(KeyringUnlockProvider provider) throws IOException {
KeyringPassword password = provider.acquire();
if (password == null) {
throw new IOException("Keyring unlock provider returned no password");

View File

@@ -158,24 +158,22 @@ public final class Tag { // NOPMD
* @throws GeneralSecurityException if a cryptographic error occurs during
* signature or digest processing
*/
public static int main(String[] args, Options root)
throws ParseException, IOException, GeneralSecurityException {
public static int main(String[] args, Options root) throws ParseException, IOException, GeneralSecurityException {
return main(args, root, KeyringUnlocks.console());
}
/**
* Executes the command with an explicit keyring unlock source.
*
* @param args command arguments
* @param root root options
* @param args command arguments
* @param root root options
* @param unlockProvider keyring password provider
* @return process exit code
* @throws ParseException if parsing fails
* @throws IOException if I/O fails
* @throws ParseException if parsing fails
* @throws IOException if I/O fails
* @throws GeneralSecurityException if cryptographic processing fails
*/
public static int main(String[] args, Options root,
KeyringUnlockProvider unlockProvider)
public static int main(String[] args, Options root, KeyringUnlockProvider unlockProvider)
throws ParseException, IOException, GeneralSecurityException {
ZeroEchoSession session = new ZeroEchoSession();
Options opts = root;
@@ -222,13 +220,13 @@ public final class Tag { // NOPMD
if (produce) {
String privAlias = require(cli, PRIV_OPT, "signature produce requires --priv <alias>");
PrivateKey priv = keyring.getPrivate(privAlias);
tail = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.signature(session, alg, priv, spec)).build(true);
tail = new TagTrailerDataContentBuilder<>(TagEngineBuilder.signature(session, alg, priv, spec))
.build(true);
} else {
String pubAlias = require(cli, PUB_OPT, "signature verify requires --pub <alias>");
PublicKey pub = keyring.getPublic(pubAlias);
tail = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.signature(session, alg, pub, spec)).build(false);
tail = new TagTrailerDataContentBuilder<>(TagEngineBuilder.signature(session, alg, pub, spec))
.build(false);
}
}
} else { // digest

View File

@@ -124,16 +124,16 @@ public class GuardTest {
// Encrypt
String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--to-psw", password,
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg",
"aes-gcm", "--tag-bits", Integer.toString(tagBits), "--aad-hex", aadHex };
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm",
"--tag-bits", Integer.toString(tagBits), "--aad-hex", aadHex };
System.out.println("...encrypt: " + Arrays.toString(encArgs));
int e = Guard.main(encArgs, new Options(), TestKeyringUnlocks.provider());
assertEquals(0, e, "... encrypt expected exit code 0");
// Decrypt (using password)
String[] decArgs = { "--decrypt", enc.toString(), "--output", dec.toString(), "--password", password,
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg",
"aes-gcm", "--tag-bits", Integer.toString(tagBits), "--aad-hex", aadHex };
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm",
"--tag-bits", Integer.toString(tagBits), "--aad-hex", aadHex };
System.out.println("...decrypt: " + Arrays.toString(decArgs));
int d = Guard.main(decArgs, new Options(), TestKeyringUnlocks.provider());
assertEquals(0, d, "... decrypt expected exit code 0");
@@ -163,10 +163,8 @@ public class GuardTest {
System.out.println(method);
Path input = writeRandom(tmp.resolve("invalid-kek.bin"), 32, 0x4B454B);
Path output = tmp.resolve("invalid-kek.enc");
String[] arguments = { "--encrypt", input.toString(), "--output", output.toString(),
"--to-psw", "controlled", "--to-kek-bytes", "24",
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM,
"--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM,
String[] arguments = { "--encrypt", input.toString(), "--output", output.toString(), "--to-psw", "controlled",
"--to-kek-bytes", "24", "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM,
"--alg", "aes-gcm" };
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
@@ -290,8 +288,8 @@ public class GuardTest {
// plus 2 random password decoys. Recipients are shuffled by default.
String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--keyring", ring.toString(),
"--to-alias", rsa.pub, "--to-psw", password, "--decoy-alias", elg.pub, "--decoy-psw-rand", "2",
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg",
"aes-gcm", "--tag-bits", Integer.toString(tagBits), "--aad-hex", aad };
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm",
"--tag-bits", Integer.toString(tagBits), "--aad-hex", aad };
System.out.println("...encrypt: " + Arrays.toString(encArgs));
int e = Guard.main(encArgs, new Options(), TestKeyringUnlocks.provider());
assertEquals(0, e, "... encrypt rc");
@@ -308,8 +306,8 @@ public class GuardTest {
// Decrypt via password instead of key
String[] decPwd = { "--decrypt", enc.toString(), "--output", dec2.toString(), "--password", password,
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg",
"aes-gcm", "--tag-bits", Integer.toString(tagBits), "--aad-hex", aad };
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm",
"--tag-bits", Integer.toString(tagBits), "--aad-hex", aad };
System.out.println("...decrypt(password): " + Arrays.toString(decPwd));
int d2 = Guard.main(decPwd, new Options(), TestKeyringUnlocks.provider());
assertEquals(0, d2, "... decrypt(password) rc");
@@ -334,9 +332,9 @@ public class GuardTest {
Path enc = tmp.resolve("pt-neg.bin.enc");
String pwd = "x";
String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--to-psw", pwd,
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg",
"aes-gcm", "--tag-bits", "128" };
String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--to-psw", pwd, "--pbkdf2-max",
TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm", "--tag-bits",
"128" };
int e = Guard.main(encArgs, new Options(), TestKeyringUnlocks.provider());
assertEquals(0, e, "... encrypt rc");

View File

@@ -165,8 +165,7 @@ public class KemTest {
KeyAliases aliases = generateKemIntoKeyStore(ring, kemId, "alias-" + shortId(kemId));
// Sanity: re-open to ensure the file is valid
try (zeroecho.core.storage.KeyringPassword password =
TestKeyringUnlocks.provider().acquire();
try (zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire();
KeyringStore ks = KeyringStore.open(ring, password)) {
if (!(ks.contains(aliases.pub) && ks.contains(aliases.prv))) {
throw new IllegalStateException("Keyring does not contain expected aliases for " + kemId);
@@ -210,15 +209,15 @@ public class KemTest {
Files.write(plain, content);
System.out.println("...[" + kemId + "] ChaCha encrypt");
int e = Kem.main(new String[] { "--encrypt", plain.toString(), "--output", enc.toString(),
"--keyring", ring.toString(), "--pub", aliases.pub, "--kem", kemId, "--chacha",
"--aad", aadChaCha, "--header" }, new Options(), TestKeyringUnlocks.provider());
"--keyring", ring.toString(), "--pub", aliases.pub, "--kem", kemId, "--chacha", "--aad",
aadChaCha, "--header" }, new Options(), TestKeyringUnlocks.provider());
if (e != 0) {
throw new IllegalStateException("ChaCha encrypt rc=" + e);
}
System.out.println("...[" + kemId + "] ChaCha decrypt");
int d = Kem.main(new String[] { "--decrypt", enc.toString(), "--output", dec.toString(),
"--keyring", ring.toString(), "--priv", aliases.prv, "--kem", kemId, "--chacha",
"--aad", aadChaCha, "--header" }, new Options(), TestKeyringUnlocks.provider());
"--keyring", ring.toString(), "--priv", aliases.prv, "--kem", kemId, "--chacha", "--aad",
aadChaCha, "--header" }, new Options(), TestKeyringUnlocks.provider());
if (d != 0) {
throw new IllegalStateException("ChaCha decrypt rc=" + d);
}

View File

@@ -139,8 +139,7 @@ public class KeyStoreManagementTest {
assertTrue(attempted > 0, "No generation attempts were successful");
// Verify by reloading and materializing.
zeroecho.core.storage.KeyringPassword password =
TestKeyringUnlocks.provider().acquire();
zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire();
KeyringStore store;
try {
store = KeyringStore.open(ring, password);
@@ -197,15 +196,13 @@ public class KeyStoreManagementTest {
// ---- helpers ----
private static boolean hasAsymmetricDefault(CryptoAlgorithm alg) {
return alg.keyOperations().stream()
.anyMatch(info -> info.operation() == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE
&& info.defaultSpec() != null);
return alg.keyOperations().stream().anyMatch(
info -> info.operation() == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE && info.defaultSpec() != null);
}
private static boolean hasSymmetricDefault(CryptoAlgorithm alg) {
return alg.keyOperations().stream()
.anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE
&& info.defaultSpec() != null);
.anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE && info.defaultSpec() != null);
}
private static String sanitize(String id) {

View File

@@ -127,8 +127,7 @@ public class TagTest {
Path ring = tmp.resolve("ring-ed25519.txt");
KeyAliases ed = generateIntoKeyStore(ring, "Ed25519", "ed");
// sanity
try (zeroecho.core.storage.KeyringPassword password =
TestKeyringUnlocks.provider().acquire();
try (zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire();
KeyringStore ks = KeyringStore.open(ring, password)) {
assertTrue(ks.contains(ed.pub) && ks.contains(ed.prv), "missing expected aliases");
}
@@ -200,12 +199,18 @@ public class TagTest {
Files.write(plain, pt);
// produce
assertEquals(0, Tag.main(new String[] { "--type", "digest", "--mode", "produce", "--alg", "SHA-256", "--in",
plain.toString(), "--out", tagged.toString() }, new Options(), TestKeyringUnlocks.provider()));
assertEquals(0,
Tag.main(
new String[] { "--type", "digest", "--mode", "produce", "--alg", "SHA-256", "--in",
plain.toString(), "--out", tagged.toString() },
new Options(), TestKeyringUnlocks.provider()));
// verify (match)
assertEquals(0, Tag.main(new String[] { "--type", "digest", "--mode", "verify", "--alg", "SHA-256", "--in",
tagged.toString(), "--out", recovered.toString() }, new Options(), TestKeyringUnlocks.provider()));
assertEquals(0,
Tag.main(
new String[] { "--type", "digest", "--mode", "verify", "--alg", "SHA-256", "--in",
tagged.toString(), "--out", recovered.toString() },
new Options(), TestKeyringUnlocks.provider()));
assertArrayEquals(pt, Files.readAllBytes(recovered), "digest round-trip mismatch");
@@ -223,15 +228,21 @@ public class TagTest {
Files.write(plain, pt);
// produce
assertEquals(0, Tag.main(new String[] { "--type", "digest", "--mode", "produce", "--alg", "SHA-256", "--in",
plain.toString(), "--out", tagged.toString() }, new Options(), TestKeyringUnlocks.provider()));
assertEquals(0,
Tag.main(
new String[] { "--type", "digest", "--mode", "produce", "--alg", "SHA-256", "--in",
plain.toString(), "--out", tagged.toString() },
new Options(), TestKeyringUnlocks.provider()));
// corrupt last byte -> break digest
flipLastByte(tagged);
// verify (mismatch): expect throw + default marker ("digest invalid")
assertEquals(1, Tag.main(new String[] { "--type", "digest", "--mode", "verify", "--alg", "SHA-256", "--in",
tagged.toString(), "--out", out.toString() }, new Options(), TestKeyringUnlocks.provider()));
assertEquals(1,
Tag.main(
new String[] { "--type", "digest", "--mode", "verify", "--alg", "SHA-256", "--in",
tagged.toString(), "--out", out.toString() },
new Options(), TestKeyringUnlocks.provider()));
assertTrue(Files.notExists(out, LinkOption.NOFOLLOW_LINKS));

View File

@@ -8,7 +8,6 @@ final class TestKeyringUnlocks {
}
static KeyringUnlockProvider provider() {
return () -> new KeyringPassword(
new char[] { 't', 'e', 's', 't', '-', 'k', 'e', 'y', 'r', 'i', 'n', 'g' });
return () -> new KeyringPassword(new char[] { 't', 'e', 's', 't', '-', 'k', 'e', 'y', 'r', 'i', 'n', 'g' });
}
}

View File

@@ -91,17 +91,17 @@ class JpegExifIntegrationTest {
// AES encryption setup
/*
* SecretKey key = zeroEchoSession.keyBuilders().symmetric()
* .generate("AES", AesKeyGenSpec.aes256()); AesSpec spec =
* SecretKey key = zeroEchoSession.keyBuilders().symmetric() .generate("AES",
* AesKeyGenSpec.aes256()); AesSpec spec =
* AesSpec.builder().mode(Mode.GCM).tagLenBits(128).header(null).build();
* EncryptionContext enc = zeroEchoSession.createContext("AES", KeyUsage.ENCRYPT, key,
* spec); CtxInterface session = Ctx.INSTANCE.getContext("aes-ctx-" +
* System.nanoTime()); session.put(ConfluxKeys.aad("AES"), aad); ((ContextAware)
* EncryptionContext enc = zeroEchoSession.createContext("AES",
* KeyUsage.ENCRYPT, key, spec); CtxInterface session =
* Ctx.INSTANCE.getContext("aes-ctx-" + System.nanoTime());
* session.put(ConfluxKeys.aad("AES"), aad); ((ContextAware)
* enc).setContext(session);
*/
ZeroEchoSession zeroEchoSession = new ZeroEchoSession();
SecretKey key = zeroEchoSession.keyBuilders().symmetric()
.generate("AES", AesKeyGenSpec.aes256());
SecretKey key = zeroEchoSession.keyBuilders().symmetric().generate("AES", AesKeyGenSpec.aes256());
CtxInterface session = Ctx.INSTANCE.getContext("aes-ctx-" + System.nanoTime());
byte[] encryptedBytes;
@@ -152,7 +152,8 @@ class JpegExifIntegrationTest {
// input
.add(PlainBytesBuilder.builder().bytes(extractedEncryptedBytes))
// encryption
.add(AesDataContentBuilder.builder(zeroEchoSession).importKeyRaw(key.getEncoded()).spec(AesSpec.gcm128(null))
.add(AesDataContentBuilder.builder(zeroEchoSession).importKeyRaw(key.getEncoded())
.spec(AesSpec.gcm128(null))
// let us use the default header for AAD and IV
.withHeader().withAad(aad).context(session))
// and create the pipeline
@@ -164,9 +165,9 @@ class JpegExifIntegrationTest {
/*
* AesSpec spec =
* AesSpec.builder().mode(Mode.GCM).tagLenBits(128).header(null).build();
* EncryptionContext dec1 = zeroEchoSession.createContext("AES", KeyUsage.DECRYPT,
* key, spec); ((ContextAware) dec1).setContext(session); // same IV/AAD in ctx
* byte[] pt1 = readAll(dec1.attach(new
* EncryptionContext dec1 = zeroEchoSession.createContext("AES",
* KeyUsage.DECRYPT, key, spec); ((ContextAware) dec1).setContext(session); //
* same IV/AAD in ctx byte[] pt1 = readAll(dec1.attach(new
* ByteArrayInputStream(extractedEncryptedBytes))); dec1.close();
*/
String decrypted = new String(pt1, StandardCharsets.UTF_8);

View File

@@ -16,29 +16,31 @@ import zeroecho.core.spec.ContextSpec;
/**
* Immutable value descriptor of one algorithm context capability.
*
* <p>The default specification is resolved once during provider construction.
* All components therefore have stable value semantics and are safe for
* concurrent reads.</p>
* <p>
* The default specification is resolved once during provider construction. All
* components therefore have stable value semantics and are safe for concurrent
* reads.
* </p>
*
* @param algorithmId canonical algorithm identifier
* @param family algorithm family
* @param role supported key usage
* @param family algorithm family
* @param role supported key usage
* @param contextType produced context type
* @param keyType accepted key type
* @param specType accepted specification type
* @param keyType accepted key type
* @param specType accepted specification type
* @param defaultSpec non-null resolved default specification
* @since 1.0
*/
public record Capability(String algorithmId, AlgorithmFamily family, KeyUsage role,
Class<? extends CryptoContext> contextType, Class<? extends Key> keyType,
Class<? extends ContextSpec> specType, ContextSpec defaultSpec) {
Class<? extends CryptoContext> contextType, Class<? extends Key> keyType, Class<? extends ContextSpec> specType,
ContextSpec defaultSpec) {
/**
* Validates the capability metadata.
*
* @throws NullPointerException if a component is {@code null}
* @throws IllegalArgumentException if {@code defaultSpec} is incompatible
* with {@code specType}
* @throws NullPointerException if a component is {@code null}
* @throws IllegalArgumentException if {@code defaultSpec} is incompatible with
* {@code specType}
*/
public Capability {
Objects.requireNonNull(algorithmId, "algorithmId must not be null");

View File

@@ -106,8 +106,8 @@ import zeroecho.core.spi.SymmetricKeyImporter;
* <p>
* <b>Security note:</b> Algorithms must enforce strong validation of keys and
* specs during registration and
* {@link #createContext(KeyUsage, Key, ContextSpec)} to
* prevent downgrade or misuse attacks.
* {@link #createContext(KeyUsage, Key, ContextSpec)} to prevent downgrade or
* misuse attacks.
* </p>
*
* @since 1.0
@@ -123,16 +123,11 @@ public abstract class CryptoAlgorithm { // NOPMD
private final List<Capability> capabilities = new ArrayList<>();
private final Map<KeyUsage, List<RoleBinding<?, ?, ?>>> ctxBindings = new EnumMap<>(KeyUsage.class);
private final Map<Class<? extends AlgorithmKeySpec>, AsymmetricKeyPairGenerator<?>> keyPairGenerators =
new LinkedHashMap<>();
private final Map<Class<? extends AlgorithmKeySpec>, PublicKeyImporter<?>> publicKeyImporters =
new LinkedHashMap<>();
private final Map<Class<? extends AlgorithmKeySpec>, PrivateKeyImporter<?>> privateKeyImporters =
new LinkedHashMap<>();
private final Map<Class<? extends AlgorithmKeySpec>, SymmetricKeyGenerator<?>> symmetricKeyGenerators =
new LinkedHashMap<>();
private final Map<Class<? extends AlgorithmKeySpec>, SymmetricKeyImporter<?>> symmetricKeyImporters =
new LinkedHashMap<>();
private final Map<Class<? extends AlgorithmKeySpec>, AsymmetricKeyPairGenerator<?>> keyPairGenerators = new LinkedHashMap<>();
private final Map<Class<? extends AlgorithmKeySpec>, PublicKeyImporter<?>> publicKeyImporters = new LinkedHashMap<>();
private final Map<Class<? extends AlgorithmKeySpec>, PrivateKeyImporter<?>> privateKeyImporters = new LinkedHashMap<>();
private final Map<Class<? extends AlgorithmKeySpec>, SymmetricKeyGenerator<?>> symmetricKeyGenerators = new LinkedHashMap<>();
private final Map<Class<? extends AlgorithmKeySpec>, SymmetricKeyImporter<?>> symmetricKeyImporters = new LinkedHashMap<>();
private final Map<Class<? extends AlgorithmKeySpec>, AlgorithmKeySpec> asymmetricDefaults = new LinkedHashMap<>();
private final Map<Class<? extends AlgorithmKeySpec>, AlgorithmKeySpec> symmetricDefaults = new LinkedHashMap<>();
@@ -296,8 +291,9 @@ public abstract class CryptoAlgorithm { // NOPMD
* <p>
* Concrete algorithms call this during construction to declare support for
* specific roles (e.g., {@code ENCRYPT}, {@code VERIFY}). When
* {@link #createContext(KeyUsage, Key, ContextSpec)} is later invoked, the provided
* {@code key} and optional {@code spec} are matched against these bindings.
* {@link #createContext(KeyUsage, Key, ContextSpec)} is later invoked, the
* provided {@code key} and optional {@code spec} are matched against these
* bindings.
* </p>
*
* @param role supported {@link KeyUsage} role
@@ -397,8 +393,7 @@ public abstract class CryptoAlgorithm { // NOPMD
if (rb.accepts(key, spec)) {
S resolved = (spec != null) ? spec
: Objects.requireNonNull(rb.defaultSpec.get(), "defaultSpec value must not be null");
C ctx = Objects.requireNonNull(rb.factory.createContext(key, resolved),
_id + " factory returned null");
C ctx = Objects.requireNonNull(rb.factory.createContext(key, resolved), _id + " factory returned null");
// Enforce the declared context type contract:
if (!rb.ctxType.isInstance(ctx)) {
throw new IllegalStateException(_id + " factory returned " + ctx.getClass().getName()
@@ -411,8 +406,7 @@ public abstract class CryptoAlgorithm { // NOPMD
+ (spec == null ? " (default spec)" : " and spec=" + spec.getClass().getName()));
}
private <S extends AlgorithmKeySpec> S resolveDefault(Class<S> specType,
Supplier<? extends S> defaultSpecOrNull) {
private <S extends AlgorithmKeySpec> S resolveDefault(Class<S> specType, Supplier<? extends S> defaultSpecOrNull) {
if (defaultSpecOrNull == null) {
return null;
}
@@ -426,16 +420,18 @@ public abstract class CryptoAlgorithm { // NOPMD
/**
* Registers asymmetric key-pair generation for one exact specification class.
*
* <p>The optional default is resolved and validated during registration.
* <p>
* The optional default is resolved and validated during registration.
* Registered generators must be safe for concurrent invocation after the
* algorithm is published.</p>
* algorithm is published.
* </p>
*
* @param specType exact specification class
* @param generator non-null generator
* @param specType exact specification class
* @param generator non-null generator
* @param defaultSpecOrNull optional default supplier, evaluated once
* @param <S> specification type
* @throws NullPointerException if a required argument or supplied default is
* {@code null}
* @param <S> specification type
* @throws NullPointerException if a required argument or supplied default
* is {@code null}
* @throws IllegalArgumentException if the supplied default has the wrong type
*/
protected final <S extends AlgorithmKeySpec> void registerAsymmetricKeyPairGenerator(Class<S> specType,
@@ -450,7 +446,7 @@ public abstract class CryptoAlgorithm { // NOPMD
*
* @param specType exact specification class
* @param importer non-null importer safe for concurrent invocation
* @param <S> specification type
* @param <S> specification type
* @throws NullPointerException if an argument is {@code null}
*/
protected final <S extends AlgorithmKeySpec> void registerPublicKeyImporter(Class<S> specType,
@@ -464,7 +460,7 @@ public abstract class CryptoAlgorithm { // NOPMD
*
* @param specType exact specification class
* @param importer non-null importer safe for concurrent invocation
* @param <S> specification type
* @param <S> specification type
* @throws NullPointerException if an argument is {@code null}
*/
protected final <S extends AlgorithmKeySpec> void registerPrivateKeyImporter(Class<S> specType,
@@ -476,14 +472,16 @@ public abstract class CryptoAlgorithm { // NOPMD
/**
* Registers symmetric-key generation for one exact specification class.
*
* <p>The optional default is resolved and validated during registration.</p>
* <p>
* The optional default is resolved and validated during registration.
* </p>
*
* @param specType exact specification class
* @param generator non-null generator safe for concurrent invocation
* @param specType exact specification class
* @param generator non-null generator safe for concurrent invocation
* @param defaultSpecOrNull optional default supplier, evaluated once
* @param <S> specification type
* @throws NullPointerException if a required argument or supplied default is
* {@code null}
* @param <S> specification type
* @throws NullPointerException if a required argument or supplied default
* is {@code null}
* @throws IllegalArgumentException if the supplied default has the wrong type
*/
protected final <S extends AlgorithmKeySpec> void registerSymmetricKeyGenerator(Class<S> specType,
@@ -498,7 +496,7 @@ public abstract class CryptoAlgorithm { // NOPMD
*
* @param specType exact specification class
* @param importer non-null importer safe for concurrent invocation
* @param <S> specification type
* @param <S> specification type
* @throws NullPointerException if an argument is {@code null}
*/
protected final <S extends AlgorithmKeySpec> void registerSymmetricKeyImporter(Class<S> specType,
@@ -515,12 +513,14 @@ public abstract class CryptoAlgorithm { // NOPMD
* Returns the asymmetric key-pair generator registered for an exact
* specification class.
*
* <p>The returned implementation may be shared and invoked concurrently.</p>
* <p>
* The returned implementation may be shared and invoked concurrently.
* </p>
*
* @param specType exact specification class; subclasses are not matched
* @param <S> specification type
* @param <S> specification type
* @return registered generator
* @throws NullPointerException if {@code specType} is {@code null}
* @throws NullPointerException if {@code specType} is {@code null}
* @throws IllegalArgumentException if no generator is registered
*/
@SuppressWarnings("unchecked")
@@ -537,12 +537,14 @@ public abstract class CryptoAlgorithm { // NOPMD
/**
* Returns the public-key importer registered for an exact specification class.
*
* <p>The returned implementation may be shared and invoked concurrently.</p>
* <p>
* The returned implementation may be shared and invoked concurrently.
* </p>
*
* @param specType exact specification class; subclasses are not matched
* @param <S> specification type
* @param <S> specification type
* @return registered importer
* @throws NullPointerException if {@code specType} is {@code null}
* @throws NullPointerException if {@code specType} is {@code null}
* @throws IllegalArgumentException if no importer is registered
*/
@SuppressWarnings("unchecked")
@@ -556,15 +558,16 @@ public abstract class CryptoAlgorithm { // NOPMD
}
/**
* Returns the private-key importer registered for an exact specification
* class.
* Returns the private-key importer registered for an exact specification class.
*
* <p>The returned implementation may be shared and invoked concurrently.</p>
* <p>
* The returned implementation may be shared and invoked concurrently.
* </p>
*
* @param specType exact specification class; subclasses are not matched
* @param <S> specification type
* @param <S> specification type
* @return registered importer
* @throws NullPointerException if {@code specType} is {@code null}
* @throws NullPointerException if {@code specType} is {@code null}
* @throws IllegalArgumentException if no importer is registered
*/
@SuppressWarnings("unchecked")
@@ -581,12 +584,14 @@ public abstract class CryptoAlgorithm { // NOPMD
* Returns the symmetric-key generator registered for an exact specification
* class.
*
* <p>The returned implementation may be shared and invoked concurrently.</p>
* <p>
* The returned implementation may be shared and invoked concurrently.
* </p>
*
* @param specType exact specification class; subclasses are not matched
* @param <S> specification type
* @param <S> specification type
* @return registered generator
* @throws NullPointerException if {@code specType} is {@code null}
* @throws NullPointerException if {@code specType} is {@code null}
* @throws IllegalArgumentException if no generator is registered
*/
@SuppressWarnings("unchecked")
@@ -603,12 +608,14 @@ public abstract class CryptoAlgorithm { // NOPMD
* Returns the symmetric-key importer registered for an exact specification
* class.
*
* <p>The returned implementation may be shared and invoked concurrently.</p>
* <p>
* The returned implementation may be shared and invoked concurrently.
* </p>
*
* @param specType exact specification class; subclasses are not matched
* @param <S> specification type
* @param <S> specification type
* @return registered importer
* @throws NullPointerException if {@code specType} is {@code null}
* @throws NullPointerException if {@code specType} is {@code null}
* @throws IllegalArgumentException if no importer is registered
*/
@SuppressWarnings("unchecked")
@@ -628,14 +635,12 @@ public abstract class CryptoAlgorithm { // NOPMD
*/
public final List<KeyOperationInfo> keyOperations() {
List<KeyOperationInfo> result = new ArrayList<>();
addOperationInfo(result, KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE, keyPairGenerators,
asymmetricDefaults);
addOperationInfo(result, KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE, keyPairGenerators, asymmetricDefaults);
addOperationInfo(result, KeyOperation.ASYMMETRIC_PUBLIC_IMPORT, publicKeyImporters, Map.of());
addOperationInfo(result, KeyOperation.ASYMMETRIC_PRIVATE_IMPORT, privateKeyImporters, Map.of());
addOperationInfo(result, KeyOperation.SYMMETRIC_GENERATE, symmetricKeyGenerators, symmetricDefaults);
addOperationInfo(result, KeyOperation.SYMMETRIC_IMPORT, symmetricKeyImporters, Map.of());
result.sort(Comparator.comparing(KeyOperationInfo::operation)
.thenComparing(info -> info.specType().getName()));
result.sort(Comparator.comparing(KeyOperationInfo::operation).thenComparing(info -> info.specType().getName()));
return List.copyOf(result);
}

View File

@@ -17,10 +17,12 @@ import java.util.TreeMap;
/**
* Immutable registry of {@link CryptoAlgorithm} providers.
*
* <p>Providers are discovered once through {@link ServiceLoader}, sorted by
* <p>
* Providers are discovered once through {@link ServiceLoader}, sorted by
* canonical algorithm identifier, and retained in one immutable registry.
* Runtime policy and auditing belong exclusively to explicitly created
* {@link zeroecho.sdk.ZeroEchoSession} instances.</p>
* {@link zeroecho.sdk.ZeroEchoSession} instances.
* </p>
*
* @since 1.0
*/

View File

@@ -93,9 +93,9 @@ public final class CryptoCatalog {
* {@link CryptoAlgorithms}.
*
* <p>
* Provider discovery, deterministic ordering, and duplicate checking occur
* once in {@code CryptoAlgorithms}. This method neither scans providers nor
* copies their collection.
* Provider discovery, deterministic ordering, and duplicate checking occur once
* in {@code CryptoAlgorithms}. This method neither scans providers nor copies
* their collection.
* </p>
*
* @return an immutable {@code CryptoCatalog} with all discovered algorithms
@@ -158,40 +158,16 @@ public final class CryptoCatalog {
/**
* Serializes the catalog to a compact JSON document.
*
* <p>
* The schema is:
* </p>
* <pre>{@code
* {
* "algorithms": [
* {
* "id": "AES/GCM",
* "displayName": "AES-GCM",
* "capabilities": [
* {
* "family": "SYMMETRIC",
* "role": "ENCRYPT",
* "contextType": "AeadEncryptContext",
* "keyType": "SecretKey",
* "specType": "AeadSpec",
* "defaultSpec": "Random nonce, 128-bit tag"
* }
* ],
* "asymmetricKeyBuilders": [
* { "specType": "Ed25519Spec", "defaultKeySpec": "Ed25519 default" }
* ],
* "symmetricKeyBuilders": [
* { "specType": "AesKeySpec", "defaultKeySpec": "AES-256" }
* ]
* }
* ]
* }
* }</pre>
* <p> The schema is: </p> <pre>{@code { "algorithms": [ { "id": "AES/GCM",
* "displayName": "AES-GCM", "capabilities": [ { "family": "SYMMETRIC", "role":
* "ENCRYPT", "contextType": "AeadEncryptContext", "keyType": "SecretKey",
* "specType": "AeadSpec", "defaultSpec": "Random nonce, 128-bit tag" } ],
* "asymmetricKeyBuilders": [ { "specType": "Ed25519Spec", "defaultKeySpec":
* "Ed25519 default" } ], "symmetricKeyBuilders": [ { "specType": "AesKeySpec",
* "defaultKeySpec": "AES-256" } ] } ] } }</pre>
*
* <p>
* String values are escaped for quotes and backslashes. The method does not
* attempt to pretty-print; callers can format the output if needed.
* </p>
* <p> String values are escaped for quotes and backslashes. The method does not
* attempt to pretty-print; callers can format the output if needed. </p>
*
* @return a JSON string describing algorithms, capabilities, and key builders
*/
@@ -230,8 +206,7 @@ public final class CryptoCatalog {
}
firstOperation = false;
sb.append('{').append(jsonField("operation", operation.operation().name())).append(',')
.append(jsonField("specType", operation.specType().getSimpleName()))
.append(",\"defaultSpec\":")
.append(jsonField("specType", operation.specType().getSimpleName())).append(",\"defaultSpec\":")
.append(operation.defaultSpec() == null ? "null" : jsonString(labelOf(operation.defaultSpec())))
.append('}');
}
@@ -273,9 +248,8 @@ public final class CryptoCatalog {
}
sb.append("</capabilities><keyOperations>");
for (KeyOperationInfo operation : a.keyOperations()) {
sb.append("<keyOperation operation=\"").append(operation.operation().name())
.append("\" specType=\"").append(esc(operation.specType().getSimpleName()))
.append("\"><defaultSpec>")
sb.append("<keyOperation operation=\"").append(operation.operation().name()).append("\" specType=\"")
.append(esc(operation.specType().getSimpleName())).append("\"><defaultSpec>")
.append(operation.defaultSpec() == null ? "" : esc(labelOf(operation.defaultSpec())))
.append("</defaultSpec></keyOperation>");
}

View File

@@ -14,20 +14,20 @@ import zeroecho.core.spec.AlgorithmKeySpec;
/**
* Immutable metadata for one exact key operation.
*
* @param operation operation guaranteed by the associated lookup
* @param specType exact accepted specification type
* @param operation operation guaranteed by the associated lookup
* @param specType exact accepted specification type
* @param defaultSpec resolved generation default, or {@code null} for import
* operations and generators without a default
* @since 1.0
*/
public record KeyOperationInfo(KeyOperation operation,
Class<? extends AlgorithmKeySpec> specType, AlgorithmKeySpec defaultSpec) {
public record KeyOperationInfo(KeyOperation operation, Class<? extends AlgorithmKeySpec> specType,
AlgorithmKeySpec defaultSpec) {
/**
* Validates the metadata invariant.
*
* @throws NullPointerException if {@code operation} or {@code specType} is
* {@code null}
* @throws NullPointerException if {@code operation} or {@code specType} is
* {@code null}
* @throws IllegalArgumentException if a default is incompatible with
* {@code specType}, or an import operation
* declares a default
@@ -38,9 +38,9 @@ public record KeyOperationInfo(KeyOperation operation,
if (defaultSpec != null && !specType.isInstance(defaultSpec)) {
throw new IllegalArgumentException("defaultSpec must be an instance of " + specType.getName());
}
if (defaultSpec != null && (operation == KeyOperation.SYMMETRIC_IMPORT
|| operation == KeyOperation.ASYMMETRIC_PUBLIC_IMPORT
|| operation == KeyOperation.ASYMMETRIC_PRIVATE_IMPORT)) {
if (defaultSpec != null
&& (operation == KeyOperation.SYMMETRIC_IMPORT || operation == KeyOperation.ASYMMETRIC_PUBLIC_IMPORT
|| operation == KeyOperation.ASYMMETRIC_PRIVATE_IMPORT)) {
throw new IllegalArgumentException("import operations cannot declare a default specification");
}
}

View File

@@ -147,8 +147,8 @@ public abstract class AbstractCryptoAlgorithm extends CryptoAlgorithm {
*
* <h4>Validation</h4> Type checks happen at creation time (via {@code bind})
* and again when
* {@link CryptoAlgorithm#createContext(KeyUsage, Key, ContextSpec)} is
* called. If a factory returns a context not assignable to {@code ctxType}, an
* {@link CryptoAlgorithm#createContext(KeyUsage, Key, ContextSpec)} is called.
* If a factory returns a context not assignable to {@code ctxType}, an
* {@link IllegalStateException} will be thrown.
*
* @param family high-level algorithm family classification

View File

@@ -63,9 +63,10 @@ import zeroecho.core.util.RandomSupport;
*
* <p>
* IV and optional AAD are exchanged via a {@link conflux.CtxInterface} set with
* {@link #setContext(conflux.CtxInterface)}. Encryption always generates a fresh
* IV after atomically claiming the context; a caller-provided IV is never used
* for encryption. Decryption requires the IV from the context or encoded header.
* {@link #setContext(conflux.CtxInterface)}. Encryption always generates a
* fresh IV after atomically claiming the context; a caller-provided IV is never
* used for encryption. Decryption requires the IV from the context or encoded
* header.
* </p>
*
* <p>
@@ -311,10 +312,7 @@ public final class AesCipherContext implements EncryptionContext, ContextAware {
/** Single-use encryption lifecycle states. */
private enum OperationState {
NEW,
ENCRYPTING,
COMPLETED,
FAILED
NEW, ENCRYPTING, COMPLETED, FAILED
}
/** Marks the owning encryption context terminal as its stream is consumed. */

View File

@@ -60,8 +60,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
* </p>
*
* <p>
* Objects of this type are thread-safe while active and may be destroyed to wipe
* their owned key bytes. Access and marshalling fail after destruction.
* Objects of this type are thread-safe while active and may be destroyed to
* wipe their owned key bytes. Access and marshalling fail after destruction.
* </p>
*
* @since 1.0

View File

@@ -243,8 +243,8 @@ abstract class AbstractChaChaCipherContext<S extends ChaChaBaseSpec> implements
* Ensures a nonce is available in the context.
*
* <ul>
* <li>For encryption, always generates a new nonce after the context has
* been atomically claimed and stores a copy in the context.</li>
* <li>For encryption, always generates a new nonce after the context has been
* atomically claimed and stores a copy in the context.</li>
* <li>For decryption, validates presence and correct length.</li>
* </ul>
*
@@ -283,10 +283,7 @@ abstract class AbstractChaChaCipherContext<S extends ChaChaBaseSpec> implements
/** Single-use encryption lifecycle states. */
private enum OperationState {
NEW,
ENCRYPTING,
COMPLETED,
FAILED
NEW, ENCRYPTING, COMPLETED, FAILED
}
/** Marks the owning encryption context terminal as its stream is consumed. */

View File

@@ -34,6 +34,7 @@
package zeroecho.core.alg.chacha;
import zeroecho.core.util.RandomSupport;
/**
* <h2>ChaCha20 (stream) algorithm</h2>
*

View File

@@ -39,10 +39,10 @@
* the stream cipher ChaCha20 and the AEAD construction ChaCha20-Poly1305. The
* module contains algorithm descriptors, streaming cipher contexts,
* configuration specifications, optional header codecs for runtime parameters,
* and symmetric key import/generation specifications. Key import
* specifications are destroyable. The design favors safe defaults
* (12-byte nonces, 128-bit AEAD tag), explicit role-to-context binding, and a
* clear separation between static configuration and per-operation parameters.
* and symmetric key import/generation specifications. Key import specifications
* are destroyable. The design favors safe defaults (12-byte nonces, 128-bit
* AEAD tag), explicit role-to-context binding, and a clear separation between
* static configuration and per-operation parameters.
* </p>
*
* <h2>Components</h2>

View File

@@ -44,9 +44,9 @@ import zeroecho.core.context.AgreementContext;
* <h2>Generic JCA-based Key Agreement Context</h2>
*
* An {@link AgreementContext} backed by the standard JCA key-agreement API.
* This class supports elliptic-curve and modern Diffie-Hellman variants
* such as ECDH, XDH (X25519, X448), and others provided by the runtime or
* configured provider.
* This class supports elliptic-curve and modern Diffie-Hellman variants such as
* ECDH, XDH (X25519, X448), and others provided by the runtime or configured
* provider.
*
* <p>
* Instances of this context are created with a local {@link PrivateKey}, and

View File

@@ -99,9 +99,9 @@
* reconstructs public keys from X.509 encodings via
* {@link java.security.KeyFactory}.</li>
* <li><b>Signature contexts:</b>
* {@link zeroecho.core.alg.common.eddsa.CommonEdDSASignatureContext}
* delegates all operations to a generic JCA-backed signature adapter, enforcing
* a fixed tag length for the selected EdDSA variant.</li>
* {@link zeroecho.core.alg.common.eddsa.CommonEdDSASignatureContext} delegates
* all operations to a generic JCA-backed signature adapter, enforcing a fixed
* tag length for the selected EdDSA variant.</li>
* </ul>
*
* <h2>Design notes</h2>

View File

@@ -116,7 +116,8 @@ public final class SignatureInteropProfile { // NOPMD
* @param keyAlgorithmId ZeroEcho key algorithm identifier used for key
* import and matching, such as {@code RSA}
* @param contextAlgorithmId ZeroEcho context algorithm identifier used
* with {@code ZeroEchoSession.createContext(...)}
* with
* {@code ZeroEchoSession.createContext(...)}
* @param contextSpec explicit ZeroEcho context specification
* @param signatureRepresentation signature representation bridge between
* external bytes and internal ZeroEcho bytes

View File

@@ -60,9 +60,9 @@
* configured {@link java.security.Signature}, resolves a fixed tag length (via
* resolvers), and exposes a one-shot {@code wrap(InputStream)} API.
* Verification behavior is controlled by a pluggable comparison approach.</li>
* <li><b>SignatureStream</b> - internal passthrough input stream that feeds chunks to
* the signature engine, emits the trailer in SIGN mode, and performs final
* verification in VERIFY mode.</li>
* <li><b>SignatureStream</b> - internal passthrough input stream that feeds
* chunks to the signature engine, emits the trailer in SIGN mode, and performs
* final verification in VERIFY mode.</li>
* </ul>
*
* <h2>Length resolution</h2>

View File

@@ -117,8 +117,8 @@ public final class Sha2Sha3Algorithm extends AbstractCryptoAlgorithm {
MessageDigest md = MessageDigest.getInstance(s.algorithm().jca());
return new JcaDigestContext(this, md, s);
} catch (GeneralSecurityException e) {
throw new ProviderFailureException(
"Failed to initialize MessageDigest " + s.algorithm().jca(), e);
throw new ProviderFailureException("Failed to initialize MessageDigest " + s.algorithm().jca(),
e);
}
}, DigestSpec::sha256 // default for catalog/tests
);

View File

@@ -160,8 +160,7 @@ public final class EcdhAlgorithm extends AbstractCryptoAlgorithm {
() -> EcdsaCurveSpec.P256);
// Reuse EC builders/importers
registerAsymmetricKeyPairGenerator(EcdhCurveSpec.class, new EcdhKeyGenBuilder(),
() -> EcdhCurveSpec.P256);
registerAsymmetricKeyPairGenerator(EcdhCurveSpec.class, new EcdhKeyGenBuilder(), () -> EcdhCurveSpec.P256);
registerPublicKeyImporter(EcdsaPublicKeySpec.class, new EcdsaPublicKeyBuilder());
registerPrivateKeyImporter(EcdsaPrivateKeySpec.class, new EcdsaPrivateKeyBuilder());
}

View File

@@ -45,8 +45,8 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
/**
* <h2>ECDH Key Pair Generator</h2>
*
* Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for elliptic curve
* Diffie-Hellman (ECDH) key pairs.
* Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for
* elliptic curve Diffie-Hellman (ECDH) key pairs.
*
* <p>
* This builder generates fresh EC key pairs suitable for ECDH key agreement. It

View File

@@ -103,8 +103,8 @@ public final class EcdsaAlgorithm extends AbstractCryptoAlgorithm {
* <p>
* On construction, the algorithm declares its supported roles and registers
* builders with the {@link CryptoAlgorithm} infrastructure so they can be
* discovered by the {@link CryptoCatalog} or invoked through the
* session-bound {@link zeroecho.sdk.KeyBuilders} entry point.
* discovered by the {@link CryptoCatalog} or invoked through the session-bound
* {@link zeroecho.sdk.KeyBuilders} entry point.
* </p>
*/
public EcdsaAlgorithm() {
@@ -134,8 +134,7 @@ public final class EcdsaAlgorithm extends AbstractCryptoAlgorithm {
}
}, () -> EcdsaCurveSpec.P256);
registerAsymmetricKeyPairGenerator(EcdsaCurveSpec.class, new EcdsaKeyGenBuilder(),
() -> EcdsaCurveSpec.P256);
registerAsymmetricKeyPairGenerator(EcdsaCurveSpec.class, new EcdsaKeyGenBuilder(), () -> EcdsaCurveSpec.P256);
registerPublicKeyImporter(EcdsaPublicKeySpec.class, new EcdsaPublicKeyBuilder());
registerPrivateKeyImporter(EcdsaPrivateKeySpec.class, new EcdsaPrivateKeyBuilder());
}

View File

@@ -45,14 +45,14 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
* <h2>ECDSA Key Pair Generator</h2>
*
* Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for
* {@link EcdsaCurveSpec}.
* This builder is responsible for generating new elliptic curve key pairs for
* use with the {@link EcdsaAlgorithm}.
* {@link EcdsaCurveSpec}. This builder is responsible for generating new
* elliptic curve key pairs for use with the {@link EcdsaAlgorithm}.
*
* <p>The exact supported operation is
* {@link #generateKeyPair(EcdsaCurveSpec)}. Public and private import are
* registered separately through {@link EcdsaPublicKeyBuilder} and
* {@link EcdsaPrivateKeyBuilder}.</p>
* <p>
* The exact supported operation is {@link #generateKeyPair(EcdsaCurveSpec)}.
* Public and private import are registered separately through
* {@link EcdsaPublicKeyBuilder} and {@link EcdsaPrivateKeyBuilder}.
* </p>
*
* <h2>Usage</h2> Typically accessed through the session key-operation API or
* {@link CryptoAlgorithm#asymmetricKeyPairGenerator(Class)}.

View File

@@ -49,9 +49,11 @@ import zeroecho.core.spi.PrivateKeyImporter;
* {@link EcdsaPrivateKeySpec}. This builder is responsible for importing ECDSA
* private keys from encoded representations.
*
* <p>The exact supported operation is
* {@link #importPrivate(EcdsaPrivateKeySpec)}. Generation and public import are
* registered through their own operation-specific implementations.</p>
* <p>
* The exact supported operation is {@link #importPrivate(EcdsaPrivateKeySpec)}.
* Generation and public import are registered through their own
* operation-specific implementations.
* </p>
*
* <h2>Encoding</h2> The {@link EcdsaPrivateKeySpec} stores the private key in
* PKCS#8 DER format. This builder delegates to a JCA {@link KeyFactory} for the

View File

@@ -48,9 +48,11 @@ import zeroecho.core.spi.PublicKeyImporter;
* {@link EcdsaPublicKeySpec}. This builder is responsible for importing ECDSA
* public keys from X.509 SubjectPublicKeyInfo encodings.
*
* <p>The exact supported operation is
* {@link #importPublic(EcdsaPublicKeySpec)}. Generation and private import are
* registered through their own operation-specific implementations.</p>
* <p>
* The exact supported operation is {@link #importPublic(EcdsaPublicKeySpec)}.
* Generation and private import are registered through their own
* operation-specific implementations.
* </p>
*
* <h2>Encoding</h2> The {@link EcdsaPublicKeySpec} stores the public key in
* standard X.509 DER format. This builder delegates to a JCA {@link KeyFactory}

View File

@@ -38,8 +38,8 @@ import zeroecho.core.alg.common.eddsa.AbstractEdDSAKeyGenBuilder;
/**
* <h2>Key-pair builder for Ed25519</h2>
*
* Concrete {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} implementation for
* generating Ed25519 key pairs.
* Concrete {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} implementation
* for generating Ed25519 key pairs.
*
* <p>
* This builder delegates to the JCA provider under the canonical algorithm name

View File

@@ -38,8 +38,8 @@ import zeroecho.core.alg.common.eddsa.AbstractEncodedPrivateKeyBuilder;
/**
* <h2>Private key builder for Ed25519</h2>
*
* Concrete {@link zeroecho.core.spi.PrivateKeyImporter} for importing
* wrapping Ed25519 private keys.
* Concrete {@link zeroecho.core.spi.PrivateKeyImporter} for importing wrapping
* Ed25519 private keys.
*
* <p>
* This builder integrates with the JCA under the canonical key factory

View File

@@ -38,8 +38,8 @@ import zeroecho.core.alg.common.eddsa.AbstractEncodedPublicKeyBuilder;
/**
* <h2>Public key builder for Ed25519</h2>
*
* Concrete {@link zeroecho.core.spi.PublicKeyImporter} for importing
* wrapping Ed25519 public keys.
* Concrete {@link zeroecho.core.spi.PublicKeyImporter} for importing wrapping
* Ed25519 public keys.
*
* <p>
* This builder integrates with the JCA under the canonical key factory

View File

@@ -66,8 +66,7 @@ import zeroecho.core.spec.AlgorithmKeySpec;
*
* <h2>Thread-safety</h2> Stateless and safe for concurrent use. Each call to
* {@link zeroecho.core.spi.PrivateKeyImporter#importPrivate(AlgorithmKeySpec)}
* creates a new
* {@link java.security.KeyFactory}.
* creates a new {@link java.security.KeyFactory}.
*
* @since 1.0
*/

View File

@@ -65,8 +65,7 @@ import zeroecho.core.spec.AlgorithmKeySpec;
*
* <h2>Thread-safety</h2> Stateless and safe for concurrent use. Each call to
* {@link zeroecho.core.spi.PublicKeyImporter#importPublic(AlgorithmKeySpec)}
* creates a new
* {@link java.security.KeyFactory}.
* creates a new {@link java.security.KeyFactory}.
*
* @since 1.0
*/

View File

@@ -63,7 +63,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
* </p>
*
* @see KyberAlgorithm
* @see zeroecho.sdk.KeyBuilders.Asymmetric#generateKeyPair(String, AlgorithmKeySpec)
* @see zeroecho.sdk.KeyBuilders.Asymmetric#generateKeyPair(String,
* AlgorithmKeySpec)
*/
public final class KyberKeyGenSpec implements AlgorithmKeySpec, Describable {
/**

View File

@@ -47,9 +47,9 @@ import zeroecho.core.spec.AlgorithmKeySpec;
* Specification wrapper for a Kyber (ML-KEM) private key encoded in PKCS#8.
*
* <p>
* Instances of this class carry an owned copy of the PKCS#8-encoded private
* key bytes. They are used with {@link zeroecho.core.CryptoAlgorithm} key
* builders to import keys into the providers native representation.
* Instances of this class carry an owned copy of the PKCS#8-encoded private key
* bytes. They are used with {@link zeroecho.core.CryptoAlgorithm} key builders
* to import keys into the providers native representation.
* </p>
*
* <h2>Encoding</h2>

View File

@@ -102,8 +102,7 @@ public record BlockGeometry(int inChunkSize, int outChunkSize, int finalizationO
"inChunkSize must not exceed outChunkSize: " + inChunkSize + " > " + outChunkSize);
}
if (finalizationOutputChunks != 0) {
throw new IllegalArgumentException(
"finalizationOutputChunks must be zero: " + finalizationOutputChunks);
throw new IllegalArgumentException("finalizationOutputChunks must be zero: " + finalizationOutputChunks);
}
}

View File

@@ -43,7 +43,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
* A {@code SaberKeyGenSpec} selects one of the SABER parameter variants
* standardized in round-3 submissions. Each variant balances performance,
* bandwidth, and security level. This spec is passed to a registered
* {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} to generate a SABER key pair.
* {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} to generate a SABER key
* pair.
* </p>
*
* <h2>Variants</h2> The {@link Variant} enumeration identifies supported SABER

View File

@@ -54,10 +54,12 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
* Reflection is used to avoid a hard dependency on all parameter variants.
* </p>
*
* <p>The exact supported operation is
* {@link #generateKeyPair(SphincsPlusKeyGenSpec)}. Public and private import are
* registered separately for {@link SphincsPlusPublicKeySpec} and
* {@link SphincsPlusPrivateKeySpec}.</p>
* <p>
* The exact supported operation is
* {@link #generateKeyPair(SphincsPlusKeyGenSpec)}. Public and private import
* are registered separately for {@link SphincsPlusPublicKeySpec} and
* {@link SphincsPlusPrivateKeySpec}.
* </p>
*
* <h2>Example</h2> <pre>{@code
* SphincsPlusKeyGenSpec spec =

View File

@@ -51,9 +51,11 @@ import zeroecho.core.spi.PrivateKeyImporter;
* pairs but focuses solely on importing private key material.
* </p>
*
* <p>The exact supported operation is
* <p>
* The exact supported operation is
* {@link #importPrivate(SphincsPlusPrivateKeySpec)}. Other key operations are
* registered through their own exact interfaces.</p>
* registered through their own exact interfaces.
* </p>
*
* <h2>Example</h2> <pre>{@code
* // Assuming bytes contain a PKCS#8-encoded SPHINCS+ private key:

View File

@@ -48,7 +48,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
* <p>
* {@code SphincsPlusPrivateKeySpec} wraps a PKCS#8-encoded SPHINCS+ private key
* along with the provider name that should be used for import. It is a simple
* destroyable holder designed for use with {@link SphincsPlusPrivateKeyBuilder}.
* destroyable holder designed for use with
* {@link SphincsPlusPrivateKeyBuilder}.
* </p>
*
* <h2>Encoding</h2>

View File

@@ -50,9 +50,11 @@ import zeroecho.core.spi.PublicKeyImporter;
* pairs, but focuses solely on importing public key material.
* </p>
*
* <p>The exact supported operation is
* <p>
* The exact supported operation is
* {@link #importPublic(SphincsPlusPublicKeySpec)}. Other key operations are
* registered through their own exact interfaces.</p>
* registered through their own exact interfaces.
* </p>
*
* <h2>Example</h2> <pre>{@code
* // Assuming bytes contain an X.509-encoded SPHINCS+ public key:

View File

@@ -51,7 +51,8 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
* <h2>Design and scope</h2>
* <ul>
* <li><b>Generation only:</b> This implementation exposes only the exact
* key-pair generation capability; import operations are registered separately.</li>
* key-pair generation capability; import operations are registered
* separately.</li>
* <li><b>Provider resolution:</b> The default JCA provider selection is used.
* If a specific provider is required, supply or register one that exposes the
* requested XDH algorithm name.</li>

View File

@@ -13,10 +13,12 @@ import java.util.Objects;
/**
* Utilities for enforcing the best-effort audit-listener contract.
*
* <p>The returned listener suppresses listener failures without logging callback
* <p>
* The returned listener suppresses listener failures without logging callback
* arguments, because those arguments may refer to sensitive cryptographic
* objects. Cryptographic operation outcomes therefore never depend on an audit
* sink's availability.</p>
* sink's availability.
* </p>
*
* @since 1.0
*/
@@ -36,8 +38,8 @@ public final class AuditListeners {
AuditListener target = Objects.requireNonNull(listener, "listener");
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
ClassLoader loader = contextLoader == null ? ClassLoader.getSystemClassLoader() : contextLoader;
return (AuditListener) Proxy.newProxyInstance(loader,
new Class<?>[] { AuditListener.class }, (proxy, method, arguments) -> {
return (AuditListener) Proxy.newProxyInstance(loader, new Class<?>[] { AuditListener.class },
(proxy, method, arguments) -> {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(target, arguments);
}

View File

@@ -10,8 +10,10 @@ package zeroecho.core.audit;
/**
* Defines the session-owned automatic auditing strategy.
*
* <p>Audit listener failures are best-effort diagnostics and never change the
* outcome of a cryptographic operation.</p>
* <p>
* Audit listener failures are best-effort diagnostics and never change the
* outcome of a cryptographic operation.
* </p>
*
* @since 1.0
*/

View File

@@ -105,8 +105,8 @@ import zeroecho.core.spec.ContextSpec;
* <li>Counting is performed by decorating the returned {@code InputStream}s; no
* buffering beyond normal {@code FilterInputStream} forwarding is
* introduced.</li>
* <li>Idempotent wrapping: contexts already wrapped by this utility are returned
* unchanged. Unrelated JDK proxies are wrapped normally.</li>
* <li>Idempotent wrapping: contexts already wrapped by this utility are
* returned unchanged. Unrelated JDK proxies are wrapped normally.</li>
* </ul>
*
* <h2>Usage example</h2> <pre>{@code
@@ -190,8 +190,7 @@ public final class AuditedContexts {
}
private static boolean isAuditedProxy(CryptoContext context) {
return Proxy.isProxyClass(context.getClass())
&& Proxy.getInvocationHandler(context) instanceof AuditingHandler;
return Proxy.isProxyClass(context.getClass()) && Proxy.getInvocationHandler(context) instanceof AuditingHandler;
}
@SuppressWarnings("unchecked")
@@ -242,8 +241,7 @@ public final class AuditedContexts {
}
safeAudit.onContextCreatedMeta(ctxId, algoId == null ? UNKNOWN : algoId,
provider == null ? UNKNOWN : provider,
role, keyFp, specMeta);
provider == null ? UNKNOWN : provider, role, keyFp, specMeta);
}
ClassLoader cl = ctx.getClass().getClassLoader(); // NOPMD
@@ -720,8 +718,7 @@ public final class AuditedContexts {
StringBuilder fingerprint = new StringBuilder(16);
for (int index = 0; index < Math.min(8, digest.length); index++) {
int value = digest[index] & 0xff;
fingerprint.append(Character.forDigit(value >>> 4, 16))
.append(Character.forDigit(value & 0x0f, 16));
fingerprint.append(Character.forDigit(value >>> 4, 16)).append(Character.forDigit(value & 0x0f, 16));
}
return key.getAlgorithm() + ":" + fingerprint;
} catch (NoSuchAlgorithmException exception) {

View File

@@ -204,9 +204,9 @@ public final class JulAuditListenerStd implements AuditListener {
* appends a stack trace in addition to the structured summary.
*
* <p>
* Stack traces may contain provider exception messages or application
* values. Enabling them is an explicit diagnostic opt-in and requires a
* suitably protected log destination.
* Stack traces may contain provider exception messages or application values.
* Enabling them is an explicit diagnostic opt-in and requires a suitably
* protected log destination.
* </p>
*
* @param include true to include stack traces, false to omit them
@@ -600,8 +600,7 @@ public final class JulAuditListenerStd implements AuditListener {
StringBuilder sb = new StringBuilder(key.getAlgorithm()).append(':');
for (int i = 0; i < Math.min(8, digest.length); i++) {
int value = digest[i] & 0xff;
sb.append(Character.forDigit(value >>> 4, 16))
.append(Character.forDigit(value & 0x0f, 16));
sb.append(Character.forDigit(value >>> 4, 16)).append(Character.forDigit(value & 0x0f, 16));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {

View File

@@ -46,13 +46,11 @@ package zeroecho.core.err;
* <h2>When it is thrown</h2>
* <ul>
* <li>During
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}
* after policy validation, if the resolved algorithm exposes no bindings for
* the given role.</li>
* <li>Directly from
* {@link zeroecho.core.CryptoAlgorithm#createContext(zeroecho.core.KeyUsage,
* java.security.Key, zeroecho.core.spec.ContextSpec)}
* {@link zeroecho.core.CryptoAlgorithm#createContext(zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}
* when no binding exists for the role.</li>
* </ul>
*

View File

@@ -157,8 +157,8 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
* @param outChunkSize size of output chunks produced by the transform (must be
* > 0)
* @param chunks number of chunks buffered at once (must be > 0)
* @throws IllegalArgumentException if a size is outside its documented range
* or a buffer size overflows
* @throws IllegalArgumentException if a size is outside its documented range or
* a buffer size overflows
*/
protected AbstractChunkTransformInputStream(InputStream upstream, int inChunkSize, int outChunkSize, int chunks) {
super(upstream);
@@ -190,8 +190,8 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
* steady-state (must be > 0)
* @param finalizationOutputChunks number of extra output chunks reserved for
* finalization (must be >= 0)
* @throws IllegalArgumentException if a size is outside its documented range
* or a buffer size overflows
* @throws IllegalArgumentException if a size is outside its documented range or
* a buffer size overflows
*/
protected AbstractChunkTransformInputStream(InputStream upstream, int inChunkSize, int outChunkSize, int chunks,
int finalizationOutputChunks) {
@@ -261,8 +261,7 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
// EOF: run finalization exactly once, even if there's no remainder,
// and surface any produced bytes (e.g., padding block, GCM tag).
if (!eofSeen) {
int finalOut = validateOutputCount(doFinal(inBuf, 0, 0, outBuf, 0), outBuf.length,
"finalization");
int finalOut = validateOutputCount(doFinal(inBuf, 0, 0, outBuf, 0), outBuf.length, "finalization");
outPtr = 0;
outLen = finalOut;
eofSeen = true;
@@ -273,8 +272,7 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
// all chunks are aligned to the specified boundary (inChunkSize) -> transform
// can be simply invoked
outLen = validateOutputCount(transform(inBuf, 0, inLen / inChunkSize, outBuf), outBuf.length,
"transformation");
outLen = validateOutputCount(transform(inBuf, 0, inLen / inChunkSize, outBuf), outBuf.length, "transformation");
outPtr = 0;
int left = inLen % inChunkSize;
@@ -291,8 +289,7 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
return true;
}
private static void validateGeometry(int inChunkSize, int outChunkSize, int chunks,
int finalizationOutputChunks) {
private static void validateGeometry(int inChunkSize, int outChunkSize, int chunks, int finalizationOutputChunks) {
if (inChunkSize < MIN_INPUT_CHUNK_SIZE) {
throw new IllegalArgumentException("inChunkSize must be greater than 1");
}

View File

@@ -53,13 +53,12 @@ import javax.crypto.Cipher;
* block; a final partial block (if any) is processed by a single
* {@code doFinal}. This mode is restricted to RSA and ElGamal.</li>
* <li><b>Left-padded independent block stream</b> - like the independent block
* stream, but
* left-pads each transformed output block with zeros up to
* stream, but left-pads each transformed output block with zeros up to
* {@code outChunkSize}. Final blocks must be complete; otherwise an
* {@link IllegalStateException} is thrown.</li>
* <li><b>Continuous stream</b> - uses
* {@code Cipher.update(...)} for bulk bytes and a single {@code doFinal()} at
* end of stream. This is suitable for CTR/CFB/OFB/GCM and padding modes.</li>
* <li><b>Continuous stream</b> - uses {@code Cipher.update(...)} for bulk bytes
* and a single {@code doFinal()} at end of stream. This is suitable for
* CTR/CFB/OFB/GCM and padding modes.</li>
* </ul>
*
* <h2>Block sizing</h2>
@@ -344,7 +343,8 @@ public final class CipherTransformInputStreamBuilder {
* </p>
*
* @return a new InputStream that transforms bytes on the fly
* @throws NullPointerException if {@code upstream} or {@code cipher} is null
* @throws NullPointerException if {@code upstream} or {@code cipher} is
* null
* @throws IllegalArgumentException if independent-block processing is selected
* for an unsupported algorithm or buffer
* geometry is invalid

View File

@@ -58,6 +58,7 @@ final class SmartBlockStream extends AbstractChunkTransformInputStream {
private static final Logger LOG = Logger.getLogger(SmartBlockStream.class.getName());
private final Cipher cipher;
/* package */ SmartBlockStream(InputStream upstream, Cipher cipher, int inChunkSize, int outChunkSize,
int bufferedBlocks) {
super(upstream, inChunkSize, outChunkSize, bufferedBlocks);

View File

@@ -59,6 +59,7 @@ final class SmartPaddedBlockStream extends AbstractChunkTransformInputStream {
private static final Logger LOG = Logger.getLogger(SmartPaddedBlockStream.class.getName());
private final Cipher cipher;
/* package */ SmartPaddedBlockStream(InputStream upstream, Cipher cipher, int inChunkSize, int outChunkSize,
int bufferedBlocks) {
super(upstream, inChunkSize, outChunkSize, bufferedBlocks);

View File

@@ -54,12 +54,12 @@
* then calls {@code onCompleted()} exactly once at EOF.</li>
* <li>{@link CipherTransformInputStreamBuilder} - fluent builder that creates
* cipher-backed streams for RSA/ElGamal independent-block processing,
* left-zero-padded independent blocks, or
* continuous {@code update}+{@code doFinal} streaming.</li>
* left-zero-padded independent blocks, or continuous
* {@code update}+{@code doFinal} streaming.</li>
* <li>{@link SmartBlockStream}, {@link SmartPaddedBlockStream},
* {@link SmartContinuousBlockStream} - internal cipher-backed stream variants;
* the first two are restricted to independent RSA or ElGamal blocks
* used by the builder.</li>
* the first two are restricted to independent RSA or ElGamal blocks used by the
* builder.</li>
* <li>{@link TailStrippingInputStream} - withholds the last N bytes from the
* payload and delivers them to a callback at EOF (useful for tags, checksums,
* or footers).</li>

View File

@@ -61,8 +61,8 @@ import java.util.List;
*
* <h2>Serialization</h2>
* <ul>
* <li>{@link #writeTo(Appendable)} outputs each pair as {@code k=v\n}
* lines without escaping and reports checked I/O failures.</li>
* <li>{@link #writeTo(Appendable)} outputs each pair as {@code k=v\n} lines
* without escaping and reports checked I/O failures.</li>
* <li>{@link #readFrom(java.io.Reader)} parses lines in the same format,
* ignoring blank lines and comments starting with {@code #}.</li>
* </ul>
@@ -99,8 +99,7 @@ public final class PairSeq {
if (kv[elementIndex] == null) {
int pairIndex = elementIndex >>> 1;
String role = (elementIndex & 1) == 0 ? "key" : "value";
throw new IllegalArgumentException(
"pair " + pairIndex + " " + role + " must not be null");
throw new IllegalArgumentException("pair " + pairIndex + " " + role + " must not be null");
}
}
return new PairSeq(kv.clone());
@@ -201,8 +200,8 @@ public final class PairSeq {
}
/**
* Appends all pairs to the target as {@code key=value} lines, reporting
* checked I/O failures directly.
* Appends all pairs to the target as {@code key=value} lines, reporting checked
* I/O failures directly.
*
* <p>
* No escaping is performed; callers must ensure keys and values do not contain

View File

@@ -101,9 +101,9 @@ import java.util.function.Supplier;
* }</pre>
*
* <h2>Thread-safety</h2> Instances are immutable and thread-safe. Public
* accessors are resolved once per runtime class and operation type, then invoked
* through cached method handles. The unload-safe {@link ClassValue} caches do
* not retain otherwise unreachable class loaders.
* accessors are resolved once per runtime class and operation type, then
* invoked through cached method handles. The unload-safe {@link ClassValue}
* caches do not retain otherwise unreachable class loaders.
*
* @param <T> domain type that follows the marshalling and unmarshalling
* conventions
@@ -274,8 +274,7 @@ public final class PairSeqCodec<T> implements Codec<T, PairSeq> {
try {
Method method = runtimeType.getMethod("marshal");
if (!PairSeq.class.isAssignableFrom(method.getReturnType())) {
return new MarshalPlan(null,
"marshal() must return PairSeq in " + runtimeType.getName(), null);
return new MarshalPlan(null, "marshal() must return PairSeq in " + runtimeType.getName(), null);
}
MethodHandle handle = MethodHandles.lookup().unreflect(method);
return new MarshalPlan(handle, null, null);
@@ -327,14 +326,14 @@ public final class PairSeqCodec<T> implements Codec<T, PairSeq> {
"static unmarshal(PairSeq) must return " + runtimeType.getName(), null);
}
MethodHandle handle = MethodHandles.lookup().unreflect(method);
return new UnmarshalPlan(handle,
"static unmarshal(PairSeq) failed for " + runtimeType.getName(), null, null);
return new UnmarshalPlan(handle, "static unmarshal(PairSeq) failed for " + runtimeType.getName(),
null, null);
}
} catch (NoSuchMethodException ignored) {
// Resolve the constructor fallback below.
} catch (IllegalAccessException exception) {
return new UnmarshalPlan(null, null,
"static unmarshal(PairSeq) failed for " + runtimeType.getName(), exception);
return new UnmarshalPlan(null, null, "static unmarshal(PairSeq) failed for " + runtimeType.getName(),
exception);
}
try {

View File

@@ -33,7 +33,6 @@
******************************************************************************/
package zeroecho.core.spec;
/**
* Marker interface for algorithm-specific key specifications.
* <p>

View File

@@ -11,7 +11,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
/**
* Generates asymmetric key pairs for one exact specification type.
* Implementations must be stateless or otherwise safe for concurrent invocation.
* Implementations must be stateless or otherwise safe for concurrent
* invocation.
*
* @param <S> specification type
* @since 1.0

View File

@@ -15,11 +15,13 @@ import zeroecho.core.spec.ContextSpec;
/**
* Creates a cryptographic context from a key and a context specification.
*
* <p>Implementations report provider and parameter failures with unchecked
* <p>
* Implementations report provider and parameter failures with unchecked
* exceptions; context construction is a pure in-memory operation and does not
* expose an I/O failure contract. Factories must be stateless or otherwise safe
* for concurrent invocation; returned contexts retain their own documented
* thread-safety contracts.</p>
* thread-safety contracts.
* </p>
*
* @param <C> context type produced
* @param <K> key type accepted
@@ -31,7 +33,7 @@ public interface ContextFactoryKS<C extends CryptoContext, K extends Key, S exte
/**
* Creates a context bound to the supplied key and specification.
*
* @param key non-null key
* @param key non-null key
* @param spec non-null resolved context specification
* @return a newly created context
*/

View File

@@ -11,11 +11,13 @@ import zeroecho.core.storage.KeyringPassword;
/**
* Supplies a fresh destroyable password for one keyring open operation.
*
* <p>Ownership of the returned object transfers to the receiver, which must
* <p>
* Ownership of the returned object transfers to the receiver, which must
* destroy it in a {@code finally} block immediately after the keyring has been
* opened. Implementations must not source passwords from immutable strings,
* process arguments, system properties, environment fallbacks, persistent
* files, or global mutable state.</p>
* files, or global mutable state.
* </p>
*/
@FunctionalInterface
public interface KeyringUnlockProvider {

View File

@@ -10,8 +10,8 @@ import java.security.PrivateKey;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* Imports private keys for one exact specification type. Implementations must be
* stateless or otherwise safe for concurrent invocation.
* Imports private keys for one exact specification type. Implementations must
* be stateless or otherwise safe for concurrent invocation.
*
* @param <S> specification type
* @since 1.0

View File

@@ -34,20 +34,26 @@
/**
* Provider contracts for context construction and exact key operations.
*
* <p>Algorithms bind each supported role to a {@link ContextFactoryKS}. Context
* <p>
* Algorithms bind each supported role to a {@link ContextFactoryKS}. Context
* construction is an in-memory operation; stream attachment and processing are
* responsible for reporting {@link java.io.IOException}.</p>
* responsible for reporting {@link java.io.IOException}.
* </p>
*
* <p>Key capabilities are registered independently through
* <p>
* Key capabilities are registered independently through
* {@link SymmetricKeyGenerator}, {@link SymmetricKeyImporter},
* {@link AsymmetricKeyPairGenerator}, {@link PublicKeyImporter}, and
* {@link PrivateKeyImporter}. A provider registers only the operations it
* implements, so capability lookup fails before invocation instead of returning
* an object with unsupported methods.</p>
* an object with unsupported methods.
* </p>
*
* <p>SPI implementations should be stateless or otherwise safe for concurrent
* <p>
* SPI implementations should be stateless or otherwise safe for concurrent
* lookup and invocation. Created cryptographic contexts remain operation-local
* and are not necessarily thread-safe.</p>
* and are not necessarily thread-safe.
* </p>
*
* @since 1.0
*/

View File

@@ -10,9 +10,11 @@ import java.util.Objects;
/**
* Redacted checked failure raised by encrypted keyring operations.
*
* <p>The public message contains only the stable error code. Filesystem paths,
* <p>
* The public message contains only the stable error code. Filesystem paths,
* aliases, key material, ciphertext, and provider-controlled messages are
* deliberately excluded.</p>
* deliberately excluded.
* </p>
*/
public final class KeyringException extends IOException {
private static final long serialVersionUID = 1L;
@@ -21,17 +23,9 @@ public final class KeyringException extends IOException {
* Stable keyring failure categories.
*/
public enum Code {
KEYRING_ALREADY_OPEN,
KEYRING_FILESYSTEM_UNSUPPORTED,
KEYRING_FORMAT_INVALID,
KEYRING_LIMIT_EXCEEDED,
KEYRING_UNLOCK_FAILED,
KEYRING_IO_FAILED,
KEYRING_DURABILITY_UNCONFIRMED,
KEYRING_CLOSED,
KEYRING_NON_EXPORTABLE_KEY,
KEYRING_IMPORT_MAPPING_INVALID,
KEYRING_IMPORT_METADATA_INVALID,
KEYRING_ALREADY_OPEN, KEYRING_FILESYSTEM_UNSUPPORTED, KEYRING_FORMAT_INVALID, KEYRING_LIMIT_EXCEEDED,
KEYRING_UNLOCK_FAILED, KEYRING_IO_FAILED, KEYRING_DURABILITY_UNCONFIRMED, KEYRING_CLOSED,
KEYRING_NON_EXPORTABLE_KEY, KEYRING_IMPORT_MAPPING_INVALID, KEYRING_IMPORT_METADATA_INVALID,
KEYRING_KEY_NOT_CANONICALIZABLE
}

View File

@@ -24,8 +24,7 @@ interface KeyringFileOperations {
/** Atomic persistence destination. */
enum Target {
MAIN_IMAGE,
NONCE_RESERVATION
MAIN_IMAGE, NONCE_RESERVATION
}
/** Creates one owner-only temporary file beside its destination. */
@@ -57,10 +56,8 @@ final class NioKeyringFileOperations implements KeyringFileOperations {
}
@Override
public void writeTemporary(Target target, Path temporary, byte[] image)
throws IOException {
try (FileChannel channel = FileChannel.open(temporary,
StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) {
public void writeTemporary(Target target, Path temporary, byte[] image) throws IOException {
try (FileChannel channel = FileChannel.open(temporary, StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) {
ByteBuffer buffer = ByteBuffer.wrap(image);
while (buffer.hasRemaining()) {
channel.write(buffer);
@@ -70,17 +67,14 @@ final class NioKeyringFileOperations implements KeyringFileOperations {
@Override
public void forceTemporary(Target target, Path temporary) throws IOException {
try (FileChannel channel = FileChannel.open(temporary,
StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) {
try (FileChannel channel = FileChannel.open(temporary, StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) {
channel.force(true);
}
}
@Override
public void atomicReplace(Target target, Path temporary, Path destination)
throws IOException {
Files.move(temporary, destination, StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);
public void atomicReplace(Target target, Path temporary, Path destination) throws IOException {
Files.move(temporary, destination, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
}
@Override

View File

@@ -71,18 +71,18 @@ import zeroecho.core.spi.SymmetricKeyImporter;
* Closed trusted mapping from persistent key identities to canonical registered
* import operations.
*
* <p>Provider identity is deliberately absent. Standard encoded key material
* is reconstructed by the current runtime's canonical ZeroEcho importer. The
* original JCA provider is neither persisted nor reproduced.</p>
* <p>
* Provider identity is deliberately absent. Standard encoded key material is
* reconstructed by the current runtime's canonical ZeroEcho importer. The
* original JCA provider is neither persisted nor reproduced.
* </p>
*/
final class KeyringImportRegistry {
private static final String ALGORITHM_AES = "AES";
private static final String ALGORITHM_HMAC = "HMAC";
private static final String ALGORITHM_CHACHA20 = "CHACHA20";
private static final String ALGORITHM_CHACHA20_POLY1305 = "CHACHA20-POLY1305";
private static final Map<Class<? extends AlgorithmKeySpec>,
Function<byte[], ? extends AlgorithmKeySpec>> SPEC_FACTORIES =
createSpecFactories();
private static final Map<Class<? extends AlgorithmKeySpec>, Function<byte[], ? extends AlgorithmKeySpec>> SPEC_FACTORIES = createSpecFactories();
private static final Map<Tuple, PersistentMapping> MAPPINGS = createMappings();
private KeyringImportRegistry() {
@@ -93,10 +93,7 @@ final class KeyringImportRegistry {
*/
/* default */
enum HmacVariant {
NONE(0, null),
SHA256(1, "HmacSHA256"),
SHA384(2, "HmacSHA384"),
SHA512(3, "HmacSHA512");
NONE(0, null), SHA256(1, "HmacSHA256"), SHA384(2, "HmacSHA384"), SHA512(3, "HmacSHA512");
private final int code;
private final String jcaName;
@@ -123,8 +120,7 @@ final class KeyringImportRegistry {
throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID);
}
/* default */ static HmacVariant forStoredKey(String algorithmId, Key key)
throws KeyringException {
/* default */ static HmacVariant forStoredKey(String algorithmId, Key key) throws KeyringException {
if (!ALGORITHM_HMAC.equals(algorithmId)) {
return NONE;
}
@@ -142,21 +138,19 @@ final class KeyringImportRegistry {
* Immutable description used by the finite importer-matrix test.
*
* @param algorithmId canonical ZeroEcho algorithm identifier
* @param kind key kind
* @param encoding standard encoding
* @param kind key kind
* @param encoding standard encoding
* @param hmacVariant closed HMAC variant, or {@link HmacVariant#NONE}
* @param specType exact registered importer specification type
* @param specType exact registered importer specification type
*/
/* default */
record PersistentMapping(String algorithmId, KeyringStore.Kind kind,
KeyringStore.Encoding encoding, HmacVariant hmacVariant,
Class<? extends AlgorithmKeySpec> specType) {
record PersistentMapping(String algorithmId, KeyringStore.Kind kind, KeyringStore.Encoding encoding,
HmacVariant hmacVariant, Class<? extends AlgorithmKeySpec> specType) {
}
@SuppressWarnings("PMD.AvoidCatchingGenericException")
/* default */ static Key importKey(String algorithmId, KeyringStore.Kind kind,
KeyringStore.Encoding encoding, HmacVariant hmacVariant, byte[] encoded)
throws GeneralSecurityException, KeyringException {
/* default */ static Key importKey(String algorithmId, KeyringStore.Kind kind, KeyringStore.Encoding encoding,
HmacVariant hmacVariant, byte[] encoded) throws GeneralSecurityException, KeyringException {
PersistentMapping mapping = requireMapping(algorithmId, kind, encoding, hmacVariant);
CryptoAlgorithm algorithm = requireAlgorithm(mapping.algorithmId);
AlgorithmKeySpec spec = createSpec(mapping, encoded);
@@ -172,8 +166,7 @@ final class KeyringImportRegistry {
}
/* default */ static void validateMapping(String algorithmId, KeyringStore.Kind kind,
KeyringStore.Encoding encoding, HmacVariant hmacVariant)
throws KeyringException {
KeyringStore.Encoding encoding, HmacVariant hmacVariant) throws KeyringException {
PersistentMapping mapping = requireMapping(algorithmId, kind, encoding, hmacVariant);
CryptoAlgorithm algorithm = requireAlgorithm(mapping.algorithmId);
List<KeyOperationInfo> operations = matchingOperations(algorithm, kind);
@@ -184,27 +177,23 @@ final class KeyringImportRegistry {
@SuppressWarnings({ "PMD.PreserveStackTrace", "PMD.AvoidCatchingGenericException" })
/* default */ static void validateCanonical(String algorithmId, KeyringStore.Kind kind,
KeyringStore.Encoding encoding, HmacVariant hmacVariant, Key source,
byte[] encoded)
KeyringStore.Encoding encoding, HmacVariant hmacVariant, Key source, byte[] encoded)
throws KeyringException {
Key imported = null;
byte[] canonical = null;
try {
if (!matchesSourceAlgorithm(source, algorithmId, hmacVariant)) {
throw new KeyringException(
KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
throw new KeyringException(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
}
imported = importKey(algorithmId, kind, encoding, hmacVariant, encoded);
canonical = imported.getEncoded();
if (canonical == null || !matchesFormat(imported.getFormat(), encoding)
|| !MessageDigest.isEqual(encoded, canonical)
|| !matchesAlgorithm(imported, algorithmId, hmacVariant)) {
throw new KeyringException(
KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
throw new KeyringException(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
}
} catch (GeneralSecurityException | RuntimeException exception) {
throw new KeyringException(
KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
throw new KeyringException(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
} finally {
if (canonical != null) {
Arrays.fill(canonical, (byte) 0);
@@ -217,11 +206,9 @@ final class KeyringImportRegistry {
return List.copyOf(MAPPINGS.values());
}
private static PersistentMapping requireMapping(String algorithmId,
KeyringStore.Kind kind, KeyringStore.Encoding encoding,
HmacVariant hmacVariant) throws KeyringException {
PersistentMapping mapping = MAPPINGS.get(
new Tuple(algorithmId, kind, encoding, hmacVariant));
private static PersistentMapping requireMapping(String algorithmId, KeyringStore.Kind kind,
KeyringStore.Encoding encoding, HmacVariant hmacVariant) throws KeyringException {
PersistentMapping mapping = MAPPINGS.get(new Tuple(algorithmId, kind, encoding, hmacVariant));
if (mapping == null) {
throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_MAPPING_INVALID);
}
@@ -229,8 +216,7 @@ final class KeyringImportRegistry {
}
@SuppressWarnings("PMD.PreserveStackTrace")
private static CryptoAlgorithm requireAlgorithm(String algorithmId)
throws KeyringException {
private static CryptoAlgorithm requireAlgorithm(String algorithmId) throws KeyringException {
try {
return CryptoAlgorithms.require(algorithmId);
} catch (IllegalArgumentException exception) {
@@ -238,11 +224,8 @@ final class KeyringImportRegistry {
}
}
private static List<KeyOperationInfo> matchingOperations(CryptoAlgorithm algorithm,
KeyringStore.Kind kind) {
return algorithm.keyOperations().stream()
.filter(info -> info.operation() == operation(kind))
.toList();
private static List<KeyOperationInfo> matchingOperations(CryptoAlgorithm algorithm, KeyringStore.Kind kind) {
return algorithm.keyOperations().stream().filter(info -> info.operation() == operation(kind)).toList();
}
private static KeyOperation operation(KeyringStore.Kind kind) {
@@ -254,25 +237,22 @@ final class KeyringImportRegistry {
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static Key invokeImporter(CryptoAlgorithm algorithm, KeyringStore.Kind kind,
AlgorithmKeySpec spec) throws GeneralSecurityException {
private static Key invokeImporter(CryptoAlgorithm algorithm, KeyringStore.Kind kind, AlgorithmKeySpec spec)
throws GeneralSecurityException {
return switch (kind) {
case PUBLIC_KEY -> ((PublicKeyImporter) algorithm.publicKeyImporter(spec.getClass()))
.importPublic(spec);
case PRIVATE_KEY -> ((PrivateKeyImporter) algorithm.privateKeyImporter(spec.getClass()))
.importPrivate(spec);
case SECRET_KEY -> ((SymmetricKeyImporter) algorithm.symmetricKeyImporter(spec.getClass()))
.importSecret(spec);
case PUBLIC_KEY -> ((PublicKeyImporter) algorithm.publicKeyImporter(spec.getClass())).importPublic(spec);
case PRIVATE_KEY ->
((PrivateKeyImporter) algorithm.privateKeyImporter(spec.getClass())).importPrivate(spec);
case SECRET_KEY ->
((SymmetricKeyImporter) algorithm.symmetricKeyImporter(spec.getClass())).importSecret(spec);
};
}
private static AlgorithmKeySpec createSpec(PersistentMapping mapping, byte[] encoded)
throws KeyringException {
private static AlgorithmKeySpec createSpec(PersistentMapping mapping, byte[] encoded) throws KeyringException {
if (mapping.specType == HmacKeyImportSpec.class) {
return new HmacKeyImportSpec(mapping.hmacVariant.jcaName, encoded);
}
Function<byte[], ? extends AlgorithmKeySpec> factory =
SPEC_FACTORIES.get(mapping.specType);
Function<byte[], ? extends AlgorithmKeySpec> factory = SPEC_FACTORIES.get(mapping.specType);
AlgorithmKeySpec result = factory == null ? null : factory.apply(encoded);
if (result == null) {
throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_MAPPING_INVALID);
@@ -288,31 +268,27 @@ final class KeyringImportRegistry {
};
}
private static boolean matchesAlgorithm(Key imported, String algorithmId,
HmacVariant hmacVariant) {
private static boolean matchesAlgorithm(Key imported, String algorithmId, HmacVariant hmacVariant) {
if (ALGORITHM_HMAC.equals(algorithmId)) {
return hmacVariant.jcaName.equals(imported.getAlgorithm());
}
if (ALGORITHM_AES.equals(algorithmId)) {
return ALGORITHM_AES.equals(imported.getAlgorithm());
}
if (ALGORITHM_CHACHA20.equals(algorithmId)
|| ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) {
if (ALGORITHM_CHACHA20.equals(algorithmId) || ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) {
return "ChaCha20".equals(imported.getAlgorithm());
}
return true;
}
private static boolean matchesSourceAlgorithm(Key source, String algorithmId,
HmacVariant hmacVariant) {
private static boolean matchesSourceAlgorithm(Key source, String algorithmId, HmacVariant hmacVariant) {
if (ALGORITHM_HMAC.equals(algorithmId)) {
return hmacVariant.jcaName.equals(source.getAlgorithm());
}
if (ALGORITHM_AES.equals(algorithmId)) {
return ALGORITHM_AES.equals(source.getAlgorithm());
}
if (ALGORITHM_CHACHA20.equals(algorithmId)
|| ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) {
if (ALGORITHM_CHACHA20.equals(algorithmId) || ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) {
return "ChaCha20".equals(source.getAlgorithm());
}
return true;
@@ -329,10 +305,8 @@ final class KeyringImportRegistry {
}
}
private static Map<Class<? extends AlgorithmKeySpec>,
Function<byte[], ? extends AlgorithmKeySpec>> createSpecFactories() {
return Map.ofEntries(
Map.entry(AesKeyImportSpec.class, AesKeyImportSpec::fromRaw),
private static Map<Class<? extends AlgorithmKeySpec>, Function<byte[], ? extends AlgorithmKeySpec>> createSpecFactories() {
return Map.ofEntries(Map.entry(AesKeyImportSpec.class, AesKeyImportSpec::fromRaw),
Map.entry(ChaChaKeyImportSpec.class, ChaChaKeyImportSpec::fromRaw),
Map.entry(BikePublicKeySpec.class, BikePublicKeySpec::new),
Map.entry(BikePrivateKeySpec.class, BikePrivateKeySpec::new),
@@ -380,73 +354,54 @@ final class KeyringImportRegistry {
addAsymmetric(mappings, "CMCE", CmcePublicKeySpec.class, CmcePrivateKeySpec.class);
addAsymmetric(mappings, "DH", DhPublicKeySpec.class, DhPrivateKeySpec.class);
addAsymmetric(mappings, "ECDSA", EcdsaPublicKeySpec.class, EcdsaPrivateKeySpec.class);
addAsymmetric(mappings, "Ed25519", Ed25519PublicKeySpec.class,
Ed25519PrivateKeySpec.class);
addAsymmetric(mappings, "Ed448", Ed448PublicKeySpec.class,
Ed448PrivateKeySpec.class);
addAsymmetric(mappings, "ElGamal", ElgamalPublicKeySpec.class,
ElgamalPrivateKeySpec.class);
addAsymmetric(mappings, "Frodo", FrodoPublicKeySpec.class,
FrodoPrivateKeySpec.class);
addAsymmetric(mappings, "Ed25519", Ed25519PublicKeySpec.class, Ed25519PrivateKeySpec.class);
addAsymmetric(mappings, "Ed448", Ed448PublicKeySpec.class, Ed448PrivateKeySpec.class);
addAsymmetric(mappings, "ElGamal", ElgamalPublicKeySpec.class, ElgamalPrivateKeySpec.class);
addAsymmetric(mappings, "Frodo", FrodoPublicKeySpec.class, FrodoPrivateKeySpec.class);
addAsymmetric(mappings, "HQC", HqcPublicKeySpec.class, HqcPrivateKeySpec.class);
addAsymmetric(mappings, "ML-KEM", KyberPublicKeySpec.class,
KyberPrivateKeySpec.class);
addAsymmetric(mappings, "ML-DSA", MldsaPublicKeySpec.class,
MldsaPrivateKeySpec.class);
addAsymmetric(mappings, "ML-KEM", KyberPublicKeySpec.class, KyberPrivateKeySpec.class);
addAsymmetric(mappings, "ML-DSA", MldsaPublicKeySpec.class, MldsaPrivateKeySpec.class);
addAsymmetric(mappings, "NTRU", NtruPublicKeySpec.class, NtruPrivateKeySpec.class);
addAsymmetric(mappings, "NTRULPRime", NtrulPrimePublicKeySpec.class,
NtrulPrimePrivateKeySpec.class);
addAsymmetric(mappings, "SNTRUPrime", SntruPrimePublicKeySpec.class,
SntruPrimePrivateKeySpec.class);
addAsymmetric(mappings, "NTRULPRime", NtrulPrimePublicKeySpec.class, NtrulPrimePrivateKeySpec.class);
addAsymmetric(mappings, "SNTRUPrime", SntruPrimePublicKeySpec.class, SntruPrimePrivateKeySpec.class);
addAsymmetric(mappings, "RSA", RsaPublicKeySpec.class, RsaPrivateKeySpec.class);
addAsymmetric(mappings, "SABER", SaberPublicKeySpec.class,
SaberPrivateKeySpec.class);
addAsymmetric(mappings, "SLH-DSA", SlhDsaPublicKeySpec.class,
SlhDsaPrivateKeySpec.class);
addAsymmetric(mappings, "SPHINCS+", SphincsPlusPublicKeySpec.class,
SphincsPlusPrivateKeySpec.class);
addAsymmetric(mappings, "SABER", SaberPublicKeySpec.class, SaberPrivateKeySpec.class);
addAsymmetric(mappings, "SLH-DSA", SlhDsaPublicKeySpec.class, SlhDsaPrivateKeySpec.class);
addAsymmetric(mappings, "SPHINCS+", SphincsPlusPublicKeySpec.class, SphincsPlusPrivateKeySpec.class);
addAsymmetric(mappings, "Xdh", XdhPublicKeySpec.class, XdhPrivateKeySpec.class);
add(mappings, ALGORITHM_AES, KeyringStore.Kind.SECRET_KEY,
KeyringStore.Encoding.RAW,
HmacVariant.NONE, AesKeyImportSpec.class);
add(mappings, ALGORITHM_CHACHA20, KeyringStore.Kind.SECRET_KEY,
KeyringStore.Encoding.RAW,
add(mappings, ALGORITHM_AES, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.NONE,
AesKeyImportSpec.class);
add(mappings, ALGORITHM_CHACHA20, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.NONE,
ChaChaKeyImportSpec.class);
add(mappings, ALGORITHM_CHACHA20_POLY1305, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW,
HmacVariant.NONE, ChaChaKeyImportSpec.class);
add(mappings, ALGORITHM_CHACHA20_POLY1305, KeyringStore.Kind.SECRET_KEY,
KeyringStore.Encoding.RAW, HmacVariant.NONE, ChaChaKeyImportSpec.class);
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY,
KeyringStore.Encoding.RAW,
HmacVariant.SHA256, HmacKeyImportSpec.class);
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY,
KeyringStore.Encoding.RAW,
HmacVariant.SHA384, HmacKeyImportSpec.class);
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY,
KeyringStore.Encoding.RAW,
HmacVariant.SHA512, HmacKeyImportSpec.class);
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.SHA256,
HmacKeyImportSpec.class);
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.SHA384,
HmacKeyImportSpec.class);
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.SHA512,
HmacKeyImportSpec.class);
return Collections.unmodifiableMap(mappings);
}
private static void addAsymmetric(Map<Tuple, PersistentMapping> mappings,
String algorithmId, Class<? extends AlgorithmKeySpec> publicSpec,
Class<? extends AlgorithmKeySpec> privateSpec) {
add(mappings, algorithmId, KeyringStore.Kind.PUBLIC_KEY,
KeyringStore.Encoding.X509, HmacVariant.NONE, publicSpec);
add(mappings, algorithmId, KeyringStore.Kind.PRIVATE_KEY,
KeyringStore.Encoding.PKCS8, HmacVariant.NONE, privateSpec);
private static void addAsymmetric(Map<Tuple, PersistentMapping> mappings, String algorithmId,
Class<? extends AlgorithmKeySpec> publicSpec, Class<? extends AlgorithmKeySpec> privateSpec) {
add(mappings, algorithmId, KeyringStore.Kind.PUBLIC_KEY, KeyringStore.Encoding.X509, HmacVariant.NONE,
publicSpec);
add(mappings, algorithmId, KeyringStore.Kind.PRIVATE_KEY, KeyringStore.Encoding.PKCS8, HmacVariant.NONE,
privateSpec);
}
private static void add(Map<Tuple, PersistentMapping> mappings, String algorithmId,
KeyringStore.Kind kind, KeyringStore.Encoding encoding,
HmacVariant hmacVariant, Class<? extends AlgorithmKeySpec> specType) {
private static void add(Map<Tuple, PersistentMapping> mappings, String algorithmId, KeyringStore.Kind kind,
KeyringStore.Encoding encoding, HmacVariant hmacVariant, Class<? extends AlgorithmKeySpec> specType) {
Tuple tuple = new Tuple(algorithmId, kind, encoding, hmacVariant);
PersistentMapping mapping = new PersistentMapping(algorithmId, kind,
encoding, hmacVariant, specType);
PersistentMapping mapping = new PersistentMapping(algorithmId, kind, encoding, hmacVariant, specType);
if (mappings.put(tuple, mapping) != null) {
throw new IllegalStateException("Duplicate persistent key importer tuple");
}
}
private record Tuple(String algorithmId, KeyringStore.Kind kind,
KeyringStore.Encoding encoding, HmacVariant hmacVariant) {
private record Tuple(String algorithmId, KeyringStore.Kind kind, KeyringStore.Encoding encoding,
HmacVariant hmacVariant) {
}
}

View File

@@ -19,8 +19,7 @@ final class KeyringNonceReservationKdf {
private static final int STORE_ID_BYTES = 16;
private static final int OUTPUT_BYTES = 32;
private static final String HMAC_SHA256 = "HmacSHA256";
private static final String DOMAIN_LABEL =
"zeroecho:keyring:nonce-reservation-mac:v1";
private static final String DOMAIN_LABEL = "zeroecho:keyring:nonce-reservation-mac:v1";
private KeyringNonceReservationKdf() {
}
@@ -29,14 +28,13 @@ final class KeyringNonceReservationKdf {
* Derives the store-specific nonce-reservation MAC key.
*
* @param masterKey borrowed 256-bit store master key
* @param storeId borrowed canonical 128-bit binary store UUID
* @param storeId borrowed canonical 128-bit binary store UUID
* @return newly owned 256-bit derived key
* @throws GeneralSecurityException if HMAC-SHA-256 is unavailable
*/
/* default */ static byte[] derive(byte[] masterKey, byte[] storeId)
throws GeneralSecurityException {
if (masterKey == null || masterKey.length != MASTER_KEY_BYTES
|| storeId == null || storeId.length != STORE_ID_BYTES) {
/* default */ static byte[] derive(byte[] masterKey, byte[] storeId) throws GeneralSecurityException {
if (masterKey == null || masterKey.length != MASTER_KEY_BYTES || storeId == null
|| storeId.length != STORE_ID_BYTES) {
throw new IllegalArgumentException("Invalid keyring derivation input");
}
byte[] salt = storeId.clone();
@@ -61,8 +59,7 @@ final class KeyringNonceReservationKdf {
}
}
private static byte[] hmac(byte[] key, byte[] input)
throws GeneralSecurityException {
private static byte[] hmac(byte[] key, byte[] input) throws GeneralSecurityException {
Mac mac = Mac.getInstance(HMAC_SHA256);
mac.init(new SecretKeySpec(key, HMAC_SHA256));
return mac.doFinal(input);

View File

@@ -14,14 +14,18 @@ import javax.security.auth.Destroyable;
/**
* Destroyable owner of a keyring password.
*
* <p>The constructor and {@link #copy()} use defensive copies. Callers retain
* <p>
* The constructor and {@link #copy()} use defensive copies. Callers retain
* ownership of the array supplied to the constructor and must clear it. The
* returned copy belongs to the receiver and must be cleared immediately after
* key derivation. This object never creates an immutable password
* {@link String}.</p>
* {@link String}.
* </p>
*
* <p>Instances are thread-safe. Destruction is idempotent and makes subsequent
* access fail deterministically.</p>
* <p>
* Instances are thread-safe. Destruction is idempotent and makes subsequent
* access fail deterministically.
* </p>
*/
public final class KeyringPassword implements Destroyable, AutoCloseable {
private final ReentrantLock lifecycleLock = new ReentrantLock();
@@ -32,7 +36,7 @@ public final class KeyringPassword implements Destroyable, AutoCloseable {
* Creates a password owner.
*
* @param password password characters, which are defensively copied
* @throws NullPointerException if {@code password} is {@code null}
* @throws NullPointerException if {@code password} is {@code null}
* @throws IllegalArgumentException if {@code password} is empty
*/
@SuppressWarnings("PMD.UseVarargs")

View File

@@ -8,7 +8,8 @@ package zeroecho.core.storage;
* Operational limits applied while opening an encrypted software keyring.
*
* @param operationalIterationMaximum maximum accepted PBKDF2 iteration count;
* it may restrict but never exceed the absolute decoded maximum
* it may restrict but never exceed the
* absolute decoded maximum
*/
public record KeyringProtection(int operationalIterationMaximum) {
/** Iterations used when a new keyring is created. */
@@ -21,8 +22,8 @@ public record KeyringProtection(int operationalIterationMaximum) {
/**
* Validates the operational limit.
*
* @throws IllegalArgumentException if the limit is below the creation
* setting or above the absolute operational maximum
* @throws IllegalArgumentException if the limit is below the creation setting
* or above the absolute operational maximum
*/
public KeyringProtection {
if (operationalIterationMaximum < CREATION_ITERATIONS

View File

@@ -7,8 +7,10 @@ package zeroecho.core.storage;
/**
* Fills keyring randomness buffers.
*
* <p>This package-private seam supports deterministic format tests; production
* creation uses the authoritative shared secure random source.</p>
* <p>
* This package-private seam supports deterministic format tests; production
* creation uses the authoritative shared secure random source.
* </p>
*/
@FunctionalInterface
interface KeyringRandomBytes {

File diff suppressed because it is too large Load Diff

View File

@@ -53,15 +53,15 @@
* </p>
*
* <p>
* Unlock passwords are destroyable, transfer ownership to the receiver, and
* are destroyed immediately after the master key is unwrapped. The unlocked
* store retains the master key, its domain-separated nonce-reservation MAC
* key, and encrypted entry records; closing the store clears this material.
* The store requires a POSIX filesystem on which owner-only permissions can be
* verified. A directory-force failure after atomic replacement makes the open
* instance unusable until close and authenticated reopen resolves which
* complete image is current. Non-exportable keys must remain behind an
* external provider reference.
* Unlock passwords are destroyable, transfer ownership to the receiver, and are
* destroyed immediately after the master key is unwrapped. The unlocked store
* retains the master key, its domain-separated nonce-reservation MAC key, and
* encrypted entry records; closing the store clears this material. The store
* requires a POSIX filesystem on which owner-only permissions can be verified.
* A directory-force failure after atomic replacement makes the open instance
* unusable until close and authenticated reopen resolves which complete image
* is current. Non-exportable keys must remain behind an external provider
* reference.
* </p>
*
* <p>

View File

@@ -124,8 +124,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
public static TagEngineBuilder<byte[]> digest(final ZeroEchoSession session, final DigestSpec spec) {
Objects.requireNonNull(session, "session");
final DigestSpec s = spec == null ? DigestSpec.sha256() : spec;
return new TagEngineBuilder<>(
() -> session.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE, s));
return new TagEngineBuilder<>(() -> session.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE, s));
}
/**
@@ -193,8 +192,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
* @return a builder that produces Ed25519 signature engines in SIGN mode
* @throws NullPointerException if {@code privateKey} is {@code null}
*/
public static TagEngineBuilder<Signature> ed25519Sign(final ZeroEchoSession session,
final PrivateKey privateKey) {
public static TagEngineBuilder<Signature> ed25519Sign(final ZeroEchoSession session, final PrivateKey privateKey) {
Objects.requireNonNull(privateKey, PRIVATE_KEY);
return signature(session, "Ed25519", privateKey, VoidSpec.INSTANCE);
}
@@ -206,8 +204,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
* @return a builder that produces Ed25519 signature engines in VERIFY mode
* @throws NullPointerException if {@code publicKey} is {@code null}
*/
public static TagEngineBuilder<Signature> ed25519Verify(final ZeroEchoSession session,
final PublicKey publicKey) {
public static TagEngineBuilder<Signature> ed25519Verify(final ZeroEchoSession session, final PublicKey publicKey) {
Objects.requireNonNull(publicKey, PUBLIC_KEY);
return signature(session, "Ed25519", publicKey, VoidSpec.INSTANCE);
}
@@ -229,8 +226,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
public static TagEngineBuilder<Signature> rsaSign(final ZeroEchoSession session, final PrivateKey privateKey,
final RsaSigSpec spec) {
Objects.requireNonNull(privateKey, PRIVATE_KEY);
return signature(session, "RSA", privateKey,
spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec);
return signature(session, "RSA", privateKey, spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec);
}
/**
@@ -250,8 +246,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
public static TagEngineBuilder<Signature> rsaVerify(final ZeroEchoSession session, final PublicKey publicKey,
final RsaSigSpec spec) {
Objects.requireNonNull(publicKey, PUBLIC_KEY);
return signature(session, "RSA", publicKey,
spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec);
return signature(session, "RSA", publicKey, spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec);
}
/**
@@ -369,8 +364,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
* @return a builder that produces SLH-DSA signature engines in SIGN mode
* @throws NullPointerException if {@code privateKey} is {@code null}
*/
public static TagEngineBuilder<Signature> slhDsaSign(final ZeroEchoSession session,
final PrivateKey privateKey) {
public static TagEngineBuilder<Signature> slhDsaSign(final ZeroEchoSession session, final PrivateKey privateKey) {
Objects.requireNonNull(privateKey, PRIVATE_KEY);
return signature(session, "SLH-DSA", privateKey, VoidSpec.INSTANCE);
}
@@ -388,8 +382,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
* @return a builder that produces SLH-DSA signature engines in VERIFY mode
* @throws NullPointerException if {@code publicKey} is {@code null}
*/
public static TagEngineBuilder<Signature> slhDsaVerify(final ZeroEchoSession session,
final PublicKey publicKey) {
public static TagEngineBuilder<Signature> slhDsaVerify(final ZeroEchoSession session, final PublicKey publicKey) {
Objects.requireNonNull(publicKey, PUBLIC_KEY);
return signature(session, "SLH-DSA", publicKey, VoidSpec.INSTANCE);
}
@@ -408,8 +401,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
* @return a builder that produces ML-DSA signature engines in SIGN mode
* @throws NullPointerException if {@code privateKey} is {@code null}
*/
public static TagEngineBuilder<Signature> mldsaSign(final ZeroEchoSession session,
final PrivateKey privateKey) {
public static TagEngineBuilder<Signature> mldsaSign(final ZeroEchoSession session, final PrivateKey privateKey) {
Objects.requireNonNull(privateKey, PRIVATE_KEY);
return signature(session, "ML-DSA", privateKey, VoidSpec.INSTANCE);
}
@@ -428,8 +420,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
* @return a builder that produces ML-DSA signature engines in VERIFY mode
* @throws NullPointerException if {@code publicKey} is {@code null}
*/
public static TagEngineBuilder<Signature> mldsaVerify(final ZeroEchoSession session,
final PublicKey publicKey) {
public static TagEngineBuilder<Signature> mldsaVerify(final ZeroEchoSession session, final PublicKey publicKey) {
Objects.requireNonNull(publicKey, PUBLIC_KEY);
return signature(session, "ML-DSA", publicKey, VoidSpec.INSTANCE);
}

View File

@@ -25,9 +25,11 @@ import zeroecho.core.spi.SymmetricKeyImporter;
/**
* Session-bound entry point for exact key-material operations.
*
* <p>Capability lookup fails before an operation object is returned. Returned
* <p>
* Capability lookup fails before an operation object is returned. Returned
* objects guarantee the requested operation and report successful execution to
* the owning session's audit listener on a best-effort basis.</p>
* the owning session's audit listener on a best-effort basis.
* </p>
*
* @since 1.0
*/
@@ -69,13 +71,12 @@ public final class KeyBuilders {
* Resolves an exact symmetric generator.
*
* @param algorithmId canonical algorithm identifier
* @param specType exact specification class
* @param <S> specification type
* @param specType exact specification class
* @param <S> specification type
* @return guaranteed generator
* @throws IllegalArgumentException if the capability is absent
*/
public <S extends AlgorithmKeySpec> SymmetricKeyGenerator<S> generator(String algorithmId,
Class<S> specType) {
public <S extends AlgorithmKeySpec> SymmetricKeyGenerator<S> generator(String algorithmId, Class<S> specType) {
CryptoAlgorithm algorithm = session.require(algorithmId);
SymmetricKeyGenerator<S> delegate = algorithm.symmetricKeyGenerator(specType);
return spec -> {
@@ -89,13 +90,12 @@ public final class KeyBuilders {
* Resolves an exact symmetric importer.
*
* @param algorithmId canonical algorithm identifier
* @param specType exact specification class
* @param <S> specification type
* @param specType exact specification class
* @param <S> specification type
* @return guaranteed importer
* @throws IllegalArgumentException if the capability is absent
*/
public <S extends AlgorithmKeySpec> SymmetricKeyImporter<S> importer(String algorithmId,
Class<S> specType) {
public <S extends AlgorithmKeySpec> SymmetricKeyImporter<S> importer(String algorithmId, Class<S> specType) {
CryptoAlgorithm algorithm = session.require(algorithmId);
SymmetricKeyImporter<S> delegate = algorithm.symmetricKeyImporter(specType);
return spec -> {
@@ -109,12 +109,13 @@ public final class KeyBuilders {
* Generates a symmetric key using the exact runtime specification type.
*
* @param algorithmId canonical algorithm identifier
* @param spec generation specification
* @param <S> specification type
* @param spec generation specification
* @param <S> specification type
* @return generated secret key
* @throws java.security.GeneralSecurityException if generation fails
* @throws IllegalArgumentException if the capability is absent
* @throws NullPointerException if {@code spec} is {@code null}
* @throws IllegalArgumentException if the capability is absent
* @throws NullPointerException if {@code spec} is
* {@code null}
*/
public <S extends AlgorithmKeySpec> SecretKey generate(String algorithmId, S spec)
throws java.security.GeneralSecurityException {
@@ -128,12 +129,13 @@ public final class KeyBuilders {
* Imports a symmetric key using the exact runtime specification type.
*
* @param algorithmId canonical algorithm identifier
* @param spec import specification
* @param <S> specification type
* @param spec import specification
* @param <S> specification type
* @return imported secret key
* @throws java.security.GeneralSecurityException if import fails
* @throws IllegalArgumentException if the capability is absent
* @throws NullPointerException if {@code spec} is {@code null}
* @throws IllegalArgumentException if the capability is absent
* @throws NullPointerException if {@code spec} is
* {@code null}
*/
public <S extends AlgorithmKeySpec> SecretKey importKey(String algorithmId, S spec)
throws java.security.GeneralSecurityException {
@@ -155,8 +157,8 @@ public final class KeyBuilders {
* Resolves an exact key-pair generator.
*
* @param algorithmId canonical algorithm identifier
* @param specType exact specification class
* @param <S> specification type
* @param specType exact specification class
* @param <S> specification type
* @return guaranteed generator
* @throws IllegalArgumentException if the capability is absent
*/
@@ -175,13 +177,12 @@ public final class KeyBuilders {
* Resolves an exact public-key importer.
*
* @param algorithmId canonical algorithm identifier
* @param specType exact specification class
* @param <S> specification type
* @param specType exact specification class
* @param <S> specification type
* @return guaranteed importer
* @throws IllegalArgumentException if the capability is absent
*/
public <S extends AlgorithmKeySpec> PublicKeyImporter<S> publicImporter(String algorithmId,
Class<S> specType) {
public <S extends AlgorithmKeySpec> PublicKeyImporter<S> publicImporter(String algorithmId, Class<S> specType) {
CryptoAlgorithm algorithm = session.require(algorithmId);
PublicKeyImporter<S> delegate = algorithm.publicKeyImporter(specType);
return spec -> {
@@ -195,8 +196,8 @@ public final class KeyBuilders {
* Resolves an exact private-key importer.
*
* @param algorithmId canonical algorithm identifier
* @param specType exact specification class
* @param <S> specification type
* @param specType exact specification class
* @param <S> specification type
* @return guaranteed importer
* @throws IllegalArgumentException if the capability is absent
*/
@@ -215,12 +216,13 @@ public final class KeyBuilders {
* Generates a key pair using the exact runtime specification type.
*
* @param algorithmId canonical algorithm identifier
* @param spec generation specification
* @param <S> specification type
* @param spec generation specification
* @param <S> specification type
* @return generated key pair
* @throws java.security.GeneralSecurityException if generation fails
* @throws IllegalArgumentException if the capability is absent
* @throws NullPointerException if {@code spec} is {@code null}
* @throws IllegalArgumentException if the capability is absent
* @throws NullPointerException if {@code spec} is
* {@code null}
*/
public <S extends AlgorithmKeySpec> KeyPair generateKeyPair(String algorithmId, S spec)
throws java.security.GeneralSecurityException {
@@ -234,12 +236,13 @@ public final class KeyBuilders {
* Imports a public key using the exact runtime specification type.
*
* @param algorithmId canonical algorithm identifier
* @param spec public-key import specification
* @param <S> specification type
* @param spec public-key import specification
* @param <S> specification type
* @return imported public key
* @throws java.security.GeneralSecurityException if import fails
* @throws IllegalArgumentException if the capability is absent
* @throws NullPointerException if {@code spec} is {@code null}
* @throws IllegalArgumentException if the capability is absent
* @throws NullPointerException if {@code spec} is
* {@code null}
*/
public <S extends AlgorithmKeySpec> PublicKey importPublic(String algorithmId, S spec)
throws java.security.GeneralSecurityException {
@@ -253,12 +256,13 @@ public final class KeyBuilders {
* Imports a private key using the exact runtime specification type.
*
* @param algorithmId canonical algorithm identifier
* @param spec private-key import specification
* @param <S> specification type
* @param spec private-key import specification
* @param <S> specification type
* @return imported private key
* @throws java.security.GeneralSecurityException if import fails
* @throws IllegalArgumentException if the capability is absent
* @throws NullPointerException if {@code spec} is {@code null}
* @throws IllegalArgumentException if the capability is absent
* @throws NullPointerException if {@code spec} is
* {@code null}
*/
public <S extends AlgorithmKeySpec> PrivateKey importPrivate(String algorithmId, S spec)
throws java.security.GeneralSecurityException {

View File

@@ -8,10 +8,11 @@
package zeroecho.sdk;
/**
* Explicit PBKDF2 work-factor limits for trusted configuration and decoded data.
* Explicit PBKDF2 work-factor limits for trusted configuration and decoded
* data.
*
* @param operationalMaximum largest iteration count accepted from trusted local
* configuration
* @param operationalMaximum largest iteration count accepted from trusted
* local configuration
* @param absoluteDecodedMaximum hard safety ceiling for untrusted decoded data
* @since 1.0
*/
@@ -41,8 +42,8 @@ public record Pbkdf2Limits(int operationalMaximum, int absoluteDecodedMaximum) {
*/
public void validateTrusted(int iterations) {
if (iterations < MINIMUM || iterations > operationalMaximum) {
throw new IllegalArgumentException("PBKDF2 iterations must be in range " + MINIMUM + ".."
+ operationalMaximum + ": " + iterations);
throw new IllegalArgumentException(
"PBKDF2 iterations must be in range " + MINIMUM + ".." + operationalMaximum + ": " + iterations);
}
}

View File

@@ -108,8 +108,8 @@ public final class ZeroEchoSession {
this(CryptoPolicy.permissive(), AuditListener.noop(), AuditMode.OFF, null);
}
private ZeroEchoSession(CryptoPolicy<ContextSpec, Key> policy, AuditListener auditListener,
AuditMode auditMode, Pbkdf2Limits pbkdf2Limits) {
private ZeroEchoSession(CryptoPolicy<ContextSpec, Key> policy, AuditListener auditListener, AuditMode auditMode,
Pbkdf2Limits pbkdf2Limits) {
this.policy = Objects.requireNonNull(policy, "policy must not be null");
this.auditListener = Objects.requireNonNull(auditListener, "auditListener must not be null");
this.auditSink = AuditListeners.bestEffort(auditListener);
@@ -143,8 +143,7 @@ public final class ZeroEchoSession {
*/
public ZeroEchoSession withAuditListener(AuditListener newAuditListener) {
return new ZeroEchoSession(policy,
Objects.requireNonNull(newAuditListener, "newAuditListener must not be null"), auditMode,
pbkdf2Limits);
Objects.requireNonNull(newAuditListener, "newAuditListener must not be null"), auditMode, pbkdf2Limits);
}
/**
@@ -198,8 +197,8 @@ public final class ZeroEchoSession {
* Returns the audit listener owned by this session.
*
* <p>
* The returned listener is the configured strategy, not mutable session
* state. It is exposed to support manual audit mode.
* The returned listener is the configured strategy, not mutable session state.
* It is exposed to support manual audit mode.
* </p>
*
* @return the non-null audit listener
@@ -254,8 +253,8 @@ public final class ZeroEchoSession {
* @param id canonical algorithm identifier
* @param role intended key usage
* @param key key compatible with the selected algorithm and role
* @param spec optional context specification, or {@code null} for the
* algorithm default
* @param spec optional context specification, or {@code null} for the algorithm
* default
* @param <C> context type
* @param <K> key type
* @param <S> context specification type
@@ -275,8 +274,8 @@ public final class ZeroEchoSession {
return finishContext(algorithm, context, role, spec);
}
private <C extends CryptoContext, S extends ContextSpec> C finishContext(CryptoAlgorithm algorithm,
C context, KeyUsage role, S spec) {
private <C extends CryptoContext, S extends ContextSpec> C finishContext(CryptoAlgorithm algorithm, C context,
KeyUsage role, S spec) {
if (auditMode == AuditMode.OFF) {
notifyContextCreated(algorithm, role, spec);
return context;
@@ -321,14 +320,15 @@ public final class ZeroEchoSession {
* Destroys a key and verifies that it entered the destroyed state.
*
* @param algorithmId algorithm identifier used as audit metadata
* @param provider provider name used as audit metadata
* @param key key to destroy; must not be {@code null}
* @param provider provider name used as audit metadata
* @param key key to destroy; must not be {@code null}
* @return {@code true} only when this call transitions the key to destroyed;
* {@code false} for a non-destroyable or already destroyed key
* @throws NullPointerException if {@code key} is {@code null}
* @throws NullPointerException if {@code key} is {@code null}
* @throws DestroyFailedException if destruction fails or the key does not
* report itself destroyed afterward
* @throws RuntimeException if the key's lifecycle implementation throws one
* @throws RuntimeException if the key's lifecycle implementation throws
* one
*/
public boolean destroyKey(String algorithmId, String provider, Key key) throws DestroyFailedException {
Objects.requireNonNull(key, "key must not be null");
@@ -352,12 +352,10 @@ public final class ZeroEchoSession {
return true;
}
private <S extends ContextSpec> void notifyContextCreated(CryptoAlgorithm algorithm,
KeyUsage role, S spec) {
Map<String, Object> metadata = spec == null ? Map.of()
: Map.of("specType", spec.getClass().getName());
auditSink.onContextCreatedMeta(UUID.randomUUID().toString(), algorithm.id(), algorithm.providerName(),
role, "n/a", metadata);
private <S extends ContextSpec> void notifyContextCreated(CryptoAlgorithm algorithm, KeyUsage role, S spec) {
Map<String, Object> metadata = spec == null ? Map.of() : Map.of("specType", spec.getClass().getName());
auditSink.onContextCreatedMeta(UUID.randomUUID().toString(), algorithm.id(), algorithm.providerName(), role,
"n/a", metadata);
}
/* default */ void notifyKeyPairGenerated(CryptoAlgorithm algorithm, AlgorithmKeySpec spec, KeyPair keyPair) {

View File

@@ -342,10 +342,8 @@ public final class HybridKexBuilder {
if (pqcAlgId == null) {
throw new IllegalStateException("pqc algorithm id must be set");
}
if (classicMode == ClassicMode.CLASSIC_AGREEMENT
&& (classicPrivate == null || classicPeerPublic == null)) {
throw new IllegalStateException(
"classic private key and peer public must be set for CLASSIC_AGREEMENT");
if (classicMode == ClassicMode.CLASSIC_AGREEMENT && (classicPrivate == null || classicPeerPublic == null)) {
throw new IllegalStateException("classic private key and peer public must be set for CLASSIC_AGREEMENT");
}
if (classicMode == ClassicMode.PAIR_MESSAGE && classicKeyPair == null) {
throw new IllegalStateException("classic key pair must be set for PAIR_MESSAGE");

View File

@@ -77,12 +77,13 @@ import zeroecho.sdk.hybrid.signature.HybridSignatureProfile;
* <li>{@link #single(ZeroEchoSession)}: constructs a non-hybrid
* {@code SignatureContext}.</li>
* <li>{@link #hybrid(ZeroEchoSession)}: constructs a hybrid
* {@code SignatureContext} via
* {@link HybridSignatureContexts}.</li>
* {@code SignatureContext} via {@link HybridSignatureContexts}.</li>
* </ul>
*
* <p>Context construction is in-memory. Checked I/O failures arise only when a
* built stream is attached or processed.</p>
* <p>
* Context construction is in-memory. Checked I/O failures arise only when a
* built stream is attached or processed.
* </p>
*
* @since 1.0
*/
@@ -253,8 +254,8 @@ public final class SignatureTrailerDataContentBuilder implements DataContentBuil
Objects.requireNonNull(algorithmId, "algorithmId");
Objects.requireNonNull(privateKey, "privateKey");
Supplier<TagEngine<Signature>> factory = () -> session.createContext(algorithmId, KeyUsage.SIGN,
privateKey, spec);
Supplier<TagEngine<Signature>> factory = () -> session.createContext(algorithmId, KeyUsage.SIGN, privateKey,
spec);
return core(factory);
}
@@ -293,8 +294,8 @@ public final class SignatureTrailerDataContentBuilder implements DataContentBuil
Objects.requireNonNull(algorithmId, "algorithmId");
Objects.requireNonNull(publicKey, "publicKey");
Supplier<TagEngine<Signature>> factory = () -> session.createContext(algorithmId,
KeyUsage.VERIFY, publicKey, spec);
Supplier<TagEngine<Signature>> factory = () -> session.createContext(algorithmId, KeyUsage.VERIFY,
publicKey, spec);
return core(factory);
}

View File

@@ -567,8 +567,7 @@ public final class ChaChaDataContentBuilder implements DataContentBuilder<DataCo
* <p>
* The actual cipher work is delegated to an
* {@link zeroecho.core.context.EncryptionContext} created through
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}.
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}.
* If the created context implements {@code ContextAware}, the configured
* context is injected before the stream is attached.
* </p>
@@ -638,8 +637,7 @@ public final class ChaChaDataContentBuilder implements DataContentBuilder<DataCo
* <p>
* The actual cipher work is delegated to an
* {@link zeroecho.core.context.EncryptionContext} created through
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}.
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}.
* If the created context implements {@code ContextAware}, the configured
* context is injected before the stream is attached.
* </p>

View File

@@ -113,6 +113,7 @@ import zeroecho.sdk.content.api.PlainContent;
*/
public final class DigestDataContentBuilder implements DataContentBuilder<PlainContent> {
private final ZeroEchoSession session;
/**
* OutputMode selects how the digest-computing pipeline presents its result to
* callers.

View File

@@ -361,8 +361,7 @@ public final class ElgamalEncDataContentBuilder implements DataContentBuilder<Da
* {@link ElgamalEncSpec}. When {@link #getStream()} is called, it creates an
* {@link zeroecho.core.context.EncryptionContext} for the "ElGamal" algorithm
* in {@link zeroecho.core.KeyUsage#ENCRYPT} role via
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
* attaches the upstream stream, and returns a pull-based stream that encrypts
* on the fly.
* </p>
@@ -426,8 +425,7 @@ public final class ElgamalEncDataContentBuilder implements DataContentBuilder<Da
* {@link ElgamalEncSpec}. When {@link #getStream()} is called, it creates an
* {@link zeroecho.core.context.EncryptionContext} for the "ElGamal" algorithm
* in {@link zeroecho.core.KeyUsage#DECRYPT} role via
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
* attaches the upstream stream, and returns a pull-based stream that decrypts
* on the fly.
* </p>

View File

@@ -91,6 +91,7 @@ import zeroecho.sdk.content.api.PlainContent;
public final class HmacDataContentBuilder implements DataContentBuilder<PlainContent> {
private static final String ALGORITHM_ID = "HMAC";
private final ZeroEchoSession session;
/**
* Mode selects whether the pipeline computes an HMAC tag or verifies one.
*
@@ -622,8 +623,7 @@ public final class HmacDataContentBuilder implements DataContentBuilder<PlainCon
final String mac = spec.macName(); // e.g., "HmacSHA256"
try {
if (genKeyBits != null) {
return session.keyBuilders().symmetric().generate(ALGORITHM_ID,
new HmacKeyGenSpec(mac, genKeyBits));
return session.keyBuilders().symmetric().generate(ALGORITHM_ID, new HmacKeyGenSpec(mac, genKeyBits));
}
if (importRaw != null || importHex != null || importBase64 != null) {
HmacKeyImportSpec ispec;

View File

@@ -345,8 +345,7 @@ public final class RsaEncDataContentBuilder implements DataContentBuilder<DataCo
* When {@link #getStream()} is invoked, this class creates an
* {@link zeroecho.core.context.EncryptionContext} for the "RSA" algorithm in
* {@link zeroecho.core.KeyUsage#ENCRYPT} role via
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
* attaches the upstream stream, and returns a pull-based stream that encrypts
* on-the-fly using the configured {@link RsaEncSpec}.
* </p>
@@ -407,8 +406,7 @@ public final class RsaEncDataContentBuilder implements DataContentBuilder<DataCo
* When {@link #getStream()} is invoked, this class creates an
* {@link zeroecho.core.context.EncryptionContext} for the "RSA" algorithm in
* {@link zeroecho.core.KeyUsage#DECRYPT} role via
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
* attaches the upstream stream, and returns a pull-based stream that decrypts
* on-the-fly using the configured {@link RsaEncSpec}.
* </p>

View File

@@ -109,6 +109,7 @@ import zeroecho.sdk.content.api.PlainContent;
public final class RsaSigDataContentBuilder implements DataContentBuilder<PlainContent> {
private static final String ALGORITHM_ID = "RSA";
private final ZeroEchoSession session;
/**
* Mode selects whether the builder signs or verifies.
*/

View File

@@ -70,7 +70,8 @@
* {@link zeroecho.sdk.builders.alg.ElgamalEncDataContentBuilder}.</li>
* <li>RSA signatures:
* {@link zeroecho.sdk.builders.alg.RsaSigDataContentBuilder}; generic signature
* trailers use {@link zeroecho.sdk.builders.SignatureTrailerDataContentBuilder}.</li>
* trailers use
* {@link zeroecho.sdk.builders.SignatureTrailerDataContentBuilder}.</li>
* <li>MAC and digest: {@link zeroecho.sdk.builders.alg.HmacDataContentBuilder},
* {@link zeroecho.sdk.builders.alg.DigestDataContentBuilder}.</li>
* <li>KEM envelopes: {@link zeroecho.sdk.builders.alg.KemDataContentBuilder}

View File

@@ -85,8 +85,8 @@ public final class SecretPassword implements SecretContent, Destroyable {
}
/**
* Constructs a password from a caller-owned character array. The supplied
* array is cloned and remains owned by the caller.
* Constructs a password from a caller-owned character array. The supplied array
* is cloned and remains owned by the caller.
*
* @param password password characters; must not be {@code null}
* @throws NullPointerException if {@code password} is {@code null}

View File

@@ -148,8 +148,7 @@ final class Decryptor implements PlainContent, MultiRecipientContent {
} catch (AEADBadTagException ex) {
// wrong key/password for that entry, continue scanning
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "recipient authentication failed: {0}",
ex.getClass().getSimpleName());
LOG.log(Level.FINE, "recipient authentication failed: {0}", ex.getClass().getSimpleName());
}
} catch (GeneralSecurityException | IOException | IllegalArgumentException ex) {
// entry not applicable to this opener/material; ignore and continue
@@ -198,8 +197,7 @@ final class Decryptor implements PlainContent, MultiRecipientContent {
}
}
private void closeAfterAttempt(InputStream input, boolean transferred, Throwable primary)
throws IOException {
private void closeAfterAttempt(InputStream input, boolean transferred, Throwable primary) throws IOException {
IOException cleanupFailure = null;
if (!transferred) {
try {
@@ -282,8 +280,7 @@ final class Decryptor implements PlainContent, MultiRecipientContent {
closeOpeners(openers, primary);
}
/* default */ static void closeOpeners(List<RecipientOpener> ownedOpeners, Throwable primary)
throws IOException {
/* default */ static void closeOpeners(List<RecipientOpener> ownedOpeners, Throwable primary) throws IOException {
IOException cleanupFailure = null;
for (RecipientOpener opener : ownedOpeners) { // NOPMD - each opener is closed in this loop
try {
@@ -309,14 +306,13 @@ final class Decryptor implements PlainContent, MultiRecipientContent {
}
}
private void rejectOversizedCek(byte[] candidate, int fieldIndex, String recipientId,
RecipientOpener opener) {
private void rejectOversizedCek(byte[] candidate, int fieldIndex, String recipientId, RecipientOpener opener) {
try {
if (LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING,
"Suspicious material in field {0}: {1}/{2} returned length {3}, while {4} is the limit. Ignoring.",
new Object[] { fieldIndex, recipientId, opener.getClass().getName(),
candidate.length, keyBytes });
new Object[] { fieldIndex, recipientId, opener.getClass().getName(), candidate.length,
keyBytes });
}
} finally {
Arrays.fill(candidate, (byte) 0);

View File

@@ -84,8 +84,7 @@ final class Encryptor implements EncryptedContent, MultiRecipientContent {
this.keyBytes = keyBytes;
this.maxRecipients = maxRecipients;
this.maxEntryLen = maxEntryLen;
this.randomBytesFactory = Objects.requireNonNull(randomBytesFactory,
"randomBytesFactory must not be null");
this.randomBytesFactory = Objects.requireNonNull(randomBytesFactory, "randomBytesFactory must not be null");
}
/**
@@ -232,8 +231,7 @@ final class Encryptor implements EncryptedContent, MultiRecipientContent {
return key;
}
/* default */ static void closeRecipients(List<Recipient> ownedRecipients, Throwable primary)
throws IOException {
/* default */ static void closeRecipients(List<Recipient> ownedRecipients, Throwable primary) throws IOException {
IOException cleanupFailure = null;
for (Recipient recipient : ownedRecipients) {
try {

View File

@@ -82,8 +82,7 @@ public final class KemCtxRecipient implements Recipient, AutoCloseable {
* @param kekBytes KEK length; exactly 16 or 32 bytes
* @param saltLen length of the random salt to apply during HKDF
* @throws NullPointerException if {@code ctx} is {@code null}
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or
* 32
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32
*/
public KemCtxRecipient(KemContext ctx, int kekBytes, int saltLen) {
this(ctx, kekBytes, saltLen, false);
@@ -107,8 +106,7 @@ public final class KemCtxRecipient implements Recipient, AutoCloseable {
* @param decoy {@code true} if this recipient is a decoy (fake entry that
* cannot unwrap a CEK); {@code false} if it is a real recipient
* @throws NullPointerException if {@code ctx} is {@code null}
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or
* 32
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32
*/
public KemCtxRecipient(KemContext ctx, int kekBytes, int saltLen, boolean decoy) {
int validatedKekBytes = RecipientKekSizes.requireSupported(kekBytes);

View File

@@ -23,9 +23,9 @@ final class KemKeyDerivation {
* Derives a KEK bound to the KEM algorithm identifier.
*
* @param sharedSecret KEM shared secret
* @param salt HKDF salt
* @param algorithmId canonical KEM algorithm identifier
* @param outputBytes requested KEK size
* @param salt HKDF salt
* @param algorithmId canonical KEM algorithm identifier
* @param outputBytes requested KEK size
* @return newly allocated KEK bytes
* @throws GeneralSecurityException if HKDF fails
*/

View File

@@ -46,17 +46,17 @@ import zeroecho.sdk.content.api.DataContent;
* resources until processing or explicit cleanup.
*
* <p>
* Callers must invoke {@link #close()} when a built instance is abandoned before
* {@link #getStream()} is called. Successful or failed stream construction also
* releases the owned recipient resources. Unlocking keys and password material
* supplied separately remain caller-owned and are never destroyed by this
* content.
* Callers must invoke {@link #close()} when a built instance is abandoned
* before {@link #getStream()} is called. Successful or failed stream
* construction also releases the owned recipient resources. Unlocking keys and
* password material supplied separately remain caller-owned and are never
* destroyed by this content.
* </p>
*
* <p>
* Implementations are not thread-safe. Cleanup is idempotent, and content cannot
* be used after cleanup. Calling {@link #getStream()} is terminal even when stream
* construction fails.
* Implementations are not thread-safe. Cleanup is idempotent, and content
* cannot be used after cleanup. Calling {@link #getStream()} is terminal even
* when stream construction fails.
* </p>
*/
public interface MultiRecipientContent extends DataContent, Destroyable, AutoCloseable {

View File

@@ -215,8 +215,8 @@ public final class MultiRecipientDataSourceBuilder
ensureOpen();
RecipientKekSizes.requireSupported(kekBytes);
session.pbkdf2Limits().validateTrusted(iterations);
this.recipients.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, false,
session.pbkdf2Limits()));
this.recipients
.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, false, session.pbkdf2Limits()));
return this;
}
@@ -236,8 +236,7 @@ public final class MultiRecipientDataSourceBuilder
* @param saltLen HKDF salt length in bytes
* @return this builder
* @throws NullPointerException if {@code kem} is {@code null}
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or
* 32
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32
*/
public MultiRecipientDataSourceBuilder addRecipient(KemContext kem, int kekBytes, int saltLen) {
ensureOpen();
@@ -292,8 +291,8 @@ public final class MultiRecipientDataSourceBuilder
ensureOpen();
RecipientKekSizes.requireSupported(kekBytes);
session.pbkdf2Limits().validateTrusted(iterations);
this.recipients.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, true,
session.pbkdf2Limits()));
this.recipients
.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, true, session.pbkdf2Limits()));
return this;
}
@@ -314,8 +313,7 @@ public final class MultiRecipientDataSourceBuilder
* @param saltLen HKDF salt length in bytes
* @return this builder
* @throws NullPointerException if {@code kem} is {@code null}
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or
* 32
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32
*/
public MultiRecipientDataSourceBuilder addRecipientDecoy(KemContext kem, int kekBytes, int saltLen) {
ensureOpen();
@@ -384,9 +382,9 @@ public final class MultiRecipientDataSourceBuilder
* </p>
*
* <p>
* The builder takes ownership of the opener. The opener must be reusable
* across all recipient entries and is closed after scanning or when the built
* content is abandoned.
* The builder takes ownership of the opener. The opener must be reusable across
* all recipient entries and is closed after scanning or when the built content
* is abandoned.
* </p>
*
* @param opener reusable opener to add
@@ -479,8 +477,10 @@ public final class MultiRecipientDataSourceBuilder
/**
* Destroys recipient secrets still owned by this builder.
*
* <p>Recipients transferred to a successfully built encrypting content object
* are owned and destroyed by that object instead.</p>
* <p>
* Recipients transferred to a successfully built encrypting content object are
* owned and destroyed by that object instead.
* </p>
*
* @throws DestroyFailedException if recipient cleanup fails
*/

View File

@@ -58,6 +58,7 @@ public final class PasswordOpener implements RecipientOpener {
public PasswordOpener(Pbkdf2Limits limits) {
this.limits = java.util.Objects.requireNonNull(limits, "limits must not be null");
}
/**
* Attempts to open a password-based recipient entry using a password unlock
* material.

View File

@@ -76,8 +76,8 @@ public final class PasswordRecipient implements Recipient, Destroyable, AutoClos
* <li>The caller should clear the {@code password} array after constructing the
* recipient to minimize exposure in memory.</li>
* <li>Choose an iteration count appropriate to the target platform to balance
* password-guessing resistance against recipient creation and opening
* latency. Counts below {@value Pbkdf2Limits#MINIMUM} are rejected.</li>
* password-guessing resistance against recipient creation and opening latency.
* Counts below {@value Pbkdf2Limits#MINIMUM} are rejected.</li>
* <li>Decoy recipients increase confidentiality by hiding the number of real
* recipients but cannot successfully unwrap the CEK.</li>
* </ul>

View File

@@ -8,9 +8,11 @@ package zeroecho.sdk.guard;
* Defines the KEK sizes supported by recipient entries without an encoded size
* discriminator.
*
* <p>The current recipient format permits AES-128 and AES-256 wrapping only.
* <p>
* The current recipient format permits AES-128 and AES-256 wrapping only.
* Validation must occur before an entry is registered so every emitted entry
* remains openable by the corresponding recipient opener.</p>
* remains openable by the corresponding recipient opener.
* </p>
*
* @since 1.0
*/

View File

@@ -18,9 +18,11 @@ import zeroecho.core.annotation.Describable;
/**
* Caller-owned session-operation input used to unlock a recipient entry.
*
* <p>Components accepting an {@code UnlockMaterial} borrow it and do not destroy
* <p>
* Components accepting an {@code UnlockMaterial} borrow it and do not destroy
* it. The caller must keep it usable until the operation completes and destroy
* password material afterwards.</p>
* password material afterwards.
* </p>
*/
public sealed interface UnlockMaterial extends Describable {
/**
@@ -44,8 +46,10 @@ public sealed interface UnlockMaterial extends Describable {
/**
* Destroyable password unlocking material backed by an owned character array.
*
* <p>Construction and access use defensive copies. Destruction is idempotent
* and prevents subsequent access.</p>
* <p>
* Construction and access use defensive copies. Destruction is idempotent and
* prevents subsequent access.
* </p>
*/
final class Password implements UnlockMaterial, Destroyable {
private final char[] characters;

View File

@@ -108,8 +108,8 @@
* generation and recipient entries; the symmetric builder manages algorithm
* parameters and payload framing.</li>
* <li><strong>Reusable opener strategies:</strong> recipients encode entries;
* openers attempt every applicable entry and create fresh cryptographic contexts
* per attempt. Neither carries long-lived secret state.</li>
* openers attempt every applicable entry and create fresh cryptographic
* contexts per attempt. Neither carries long-lived secret state.</li>
* <li><strong>Defensive parsing:</strong> the builder applies limits to the
* number of recipients and the size of each entry blob; the symmetric stage
* applies its own limits to its header and payload.</li>

View File

@@ -196,8 +196,8 @@ public final class HybridDerived {
* construction.
* </p>
*
* @param aes AES builder to configure (must not be null)
* @param keyBits AES key size in bits (128/192/256)
* @param aes AES builder to configure (must not be null)
* @param keyBits AES key size in bits (128/192/256)
* @return the provided builder instance
* @throws NullPointerException if aes is null
* @throws IllegalArgumentException if keyBits is invalid
@@ -225,16 +225,16 @@ public final class HybridDerived {
}
/**
* Derives a ChaCha key and applies it with optional AAD to the provided
* ChaCha builder.
* Derives a ChaCha key and applies it with optional AAD to the provided ChaCha
* builder.
*
* <p>
* The returned value is the same builder instance to preserve fluent pipeline
* construction.
* </p>
*
* @param chacha ChaCha builder to configure (must not be null)
* @param keyBits key size in bits (typically 256)
* @param chacha ChaCha builder to configure (must not be null)
* @param keyBits key size in bits (typically 256)
* @return the provided builder instance
* @throws NullPointerException if chacha is null
* @throws IllegalArgumentException if keyBits is invalid

Some files were not shown because too many files have changed in this diff Show More