refactor!: consolidate crypto architecture and security model
* make ZeroEchoSession the sole policy, audit, and runtime boundary * replace combined key builders with operation-specific SPI and typed metadata * remove obsolete pre-release compatibility APIs and global crypto operations * finalize JCA agreement contexts and replace inheritance with composition * harden secret lifecycle, key destruction, hybrid KEX, PBKDF2, and audit handling * standardize PairSeq I/O and introduce immutable validated value types * migrate app, ext, samples, and required pki integration points * expand correctness, security, concurrency, and malformed-input coverage BREAKING CHANGE: removes deprecated pre-release global configuration, legacy context factories, combined key-builder contracts, String-based password APIs, unchecked PairSeq writing, BlockGeometry public fields, and other compatibility facades.
This commit is contained in:
@@ -40,7 +40,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -52,17 +52,21 @@ import org.apache.commons.cli.OptionGroup;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
|
||||
import zeroecho.core.CryptoAlgorithms;
|
||||
import zeroecho.core.KeyUsage;
|
||||
import zeroecho.core.context.EncryptionContext;
|
||||
import zeroecho.core.context.KemContext;
|
||||
import zeroecho.core.err.UnsupportedRoleException;
|
||||
import zeroecho.core.storage.KeyringStore;
|
||||
import zeroecho.sdk.Pbkdf2Limits;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
import zeroecho.sdk.builders.alg.AesDataContentBuilder;
|
||||
import zeroecho.sdk.builders.alg.ChaChaDataContentBuilder;
|
||||
import zeroecho.sdk.builders.core.PlainFileBuilder;
|
||||
import zeroecho.sdk.content.api.DataContent;
|
||||
import zeroecho.sdk.guard.MultiRecipientContent;
|
||||
import zeroecho.sdk.guard.MultiRecipientDataSourceBuilder;
|
||||
import zeroecho.sdk.guard.RecipientKekSizes;
|
||||
import zeroecho.sdk.guard.UnlockMaterial;
|
||||
import zeroecho.sdk.util.RandomSupport;
|
||||
|
||||
/**
|
||||
* Guard is a unified subcommand that encrypts and decrypts using a
|
||||
@@ -97,6 +101,8 @@ import zeroecho.sdk.guard.UnlockMaterial;
|
||||
* ZeroEcho -G --encrypt in.bin --ks keys.txt \
|
||||
* --to-alias alice --to-alias bob \
|
||||
* --decoy-psw-rand 2 \
|
||||
* --pbkdf2-max "$PBKDF2_POLICY_MAX" \
|
||||
* --pbkdf2-hard-max "$PBKDF2_HARD_MAX" \
|
||||
* --alg aes-gcm --tag-bits 128 --aad-hex 01ff
|
||||
*
|
||||
* # Decrypt with private key (payload is ChaCha20-Poly1305)
|
||||
@@ -124,7 +130,6 @@ public final class Guard {
|
||||
*/
|
||||
public static int main(final String[] args, final Options options) // NOPMD
|
||||
throws ParseException, IOException, GeneralSecurityException {
|
||||
|
||||
// ---- operation selection
|
||||
final Option OPT_ENCRYPT = Option.builder("e").longOpt("encrypt").hasArg().argName("in-file")
|
||||
.desc("Encrypt the given file").get();
|
||||
@@ -177,7 +182,12 @@ public final class Guard {
|
||||
final Option OPT_PSW_SALT = Option.builder().longOpt("to-salt-len").hasArg().argName("bytes")
|
||||
.desc("PBKDF2 salt length for password recipients (default 16)").get();
|
||||
final Option OPT_PSW_KEK = Option.builder().longOpt("to-kek-bytes").hasArg().argName("bytes")
|
||||
.desc("Derived KEK length for password recipients (default 32)").get();
|
||||
.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")
|
||||
.desc("Absolute decoded PBKDF2 safety ceiling; required for password operations").get();
|
||||
|
||||
// ---- decoys (all types)
|
||||
final Option OPT_DECOY_ALIAS = Option.builder().longOpt("decoy-alias").hasArg().argName("alias")
|
||||
@@ -215,6 +225,8 @@ public final class Guard {
|
||||
options.addOption(OPT_PSW_ITER);
|
||||
options.addOption(OPT_PSW_SALT);
|
||||
options.addOption(OPT_PSW_KEK);
|
||||
options.addOption(OPT_PBKDF2_MAX);
|
||||
options.addOption(OPT_PBKDF2_HARD_MAX);
|
||||
|
||||
options.addOption(OPT_DECOY_ALIAS);
|
||||
options.addOption(OPT_DECOY_PSW);
|
||||
@@ -225,6 +237,10 @@ public final class Guard {
|
||||
|
||||
final CommandLineParser parser = new DefaultParser();
|
||||
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 boolean encrypt = cmd.hasOption(OPT_ENCRYPT);
|
||||
final Path inPath = Paths.get(cmd.getOptionValue(encrypt ? OPT_ENCRYPT : OPT_DECRYPT));
|
||||
@@ -249,7 +265,7 @@ public final class Guard {
|
||||
final ChaChaDataContentBuilder chacha;
|
||||
switch (alg) {
|
||||
case "aes-gcm" -> {
|
||||
aes = AesDataContentBuilder.builder().modeGcm(tagBits);
|
||||
aes = AesDataContentBuilder.builder(session).modeGcm(tagBits);
|
||||
if (header) {
|
||||
aes.withHeader();
|
||||
}
|
||||
@@ -259,28 +275,28 @@ public final class Guard {
|
||||
chacha = null;
|
||||
}
|
||||
case "aes-ctr" -> {
|
||||
aes = AesDataContentBuilder.builder().modeCtr();
|
||||
aes = AesDataContentBuilder.builder(session).modeCtr();
|
||||
if (header) {
|
||||
aes.withHeader();
|
||||
}
|
||||
chacha = null;
|
||||
}
|
||||
case "aes-cbc-pkcs7" -> {
|
||||
aes = AesDataContentBuilder.builder().modeCbcPkcs5();
|
||||
aes = AesDataContentBuilder.builder(session).modeCbcPkcs5();
|
||||
if (header) {
|
||||
aes.withHeader();
|
||||
}
|
||||
chacha = null;
|
||||
}
|
||||
case "aes-cbc-nopad" -> {
|
||||
aes = AesDataContentBuilder.builder().modeCbcNoPadding();
|
||||
aes = AesDataContentBuilder.builder(session).modeCbcNoPadding();
|
||||
if (header) {
|
||||
aes.withHeader();
|
||||
}
|
||||
chacha = null;
|
||||
}
|
||||
case "chacha-aead" -> {
|
||||
chacha = ChaChaDataContentBuilder.builder();
|
||||
chacha = ChaChaDataContentBuilder.builder(session);
|
||||
// selecting AEAD: if the user did not supply AAD, pass empty to pick AEAD
|
||||
chacha.withAad(aad != null ? aad : new byte[0]);
|
||||
if (header) {
|
||||
@@ -302,7 +318,7 @@ public final class Guard {
|
||||
aes = null;
|
||||
}
|
||||
case "chacha-stream" -> {
|
||||
chacha = ChaChaDataContentBuilder.builder();
|
||||
chacha = ChaChaDataContentBuilder.builder(session);
|
||||
if (header) {
|
||||
chacha.withHeader();
|
||||
}
|
||||
@@ -321,71 +337,93 @@ public final class Guard {
|
||||
}
|
||||
|
||||
// envelope builder (new API)
|
||||
final MultiRecipientDataSourceBuilder env = new MultiRecipientDataSourceBuilder().payloadKeyBytes(cekBytes)
|
||||
final MultiRecipientDataSourceBuilder env = MultiRecipientDataSourceBuilder.builder(session)
|
||||
.payloadKeyBytes(cekBytes)
|
||||
.headerLimits(maxRecipients, maxEntryLen);
|
||||
if (aes != null) {
|
||||
env.withAes(aes);
|
||||
} else {
|
||||
env.withChaCha(chacha);
|
||||
}
|
||||
// shuffle on by default
|
||||
if (shuffle) {
|
||||
env.shuffle();
|
||||
}
|
||||
// recipients and decoys only apply on encrypt
|
||||
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 = Integer.parseInt(cmd.getOptionValue(OPT_PSW_KEK, "32"));
|
||||
|
||||
final KeyringStore ks = loadKeyringIfPresent(cmd, OPT_KEYRING);
|
||||
// real recipients by alias
|
||||
for (String alias : cmd.getOptionValues(OPT_TO_ALIAS) == null ? new String[0]
|
||||
: cmd.getOptionValues(OPT_TO_ALIAS)) {
|
||||
addRecipientFromAlias(env, ks, alias, kekLen, saltLen, false);
|
||||
}
|
||||
// real password recipients
|
||||
for (String psw : cmd.getOptionValues(OPT_TO_PSW) == null ? new String[0]
|
||||
: cmd.getOptionValues(OPT_TO_PSW)) {
|
||||
env.addPasswordRecipient(psw.toCharArray(), iter, saltLen, kekLen);
|
||||
}
|
||||
// decoys by alias (key types)
|
||||
for (String alias : cmd.getOptionValues(OPT_DECOY_ALIAS) == null ? new String[0]
|
||||
: cmd.getOptionValues(OPT_DECOY_ALIAS)) {
|
||||
addRecipientFromAlias(env, ks, alias, kekLen, saltLen, true);
|
||||
}
|
||||
// decoy passwords (explicit)
|
||||
for (String psw : cmd.getOptionValues(OPT_DECOY_PSW) == null ? new String[0]
|
||||
: cmd.getOptionValues(OPT_DECOY_PSW)) {
|
||||
env.addPasswordRecipientDecoy(psw.toCharArray(), iter, saltLen, kekLen);
|
||||
}
|
||||
// decoy passwords (random)
|
||||
final int rndCount = Integer.parseInt(cmd.getOptionValue(OPT_DECOY_PSW_RAND, "0"));
|
||||
for (int i = 0; i < rndCount; i++) {
|
||||
env.addPasswordRecipientDecoy(randomPassword(), iter, saltLen, kekLen);
|
||||
}
|
||||
} else {
|
||||
// unlock material for decrypt
|
||||
final String privAlias = cmd.getOptionValue(OPT_PRIV_ALIAS);
|
||||
final String password = cmd.getOptionValue(OPT_PASSWORD);
|
||||
|
||||
if ((privAlias == null && password == null) || (privAlias != null && password != null)) {
|
||||
throw new ParseException("Specify exactly one of --priv-alias or --password for decryption");
|
||||
}
|
||||
if (privAlias != null) {
|
||||
final KeyringStore ks = requireKeyring(cmd, OPT_KEYRING);
|
||||
final KeyringStore.PrivateWithId pr = ks.getPrivateWithId(privAlias);
|
||||
env.unlockWith(new UnlockMaterial.Private(pr.key()));
|
||||
UnlockMaterial borrowedUnlockMaterial = null;
|
||||
try (env) {
|
||||
if (aes != null) {
|
||||
env.withAes(aes);
|
||||
} else {
|
||||
env.unlockWith(new UnlockMaterial.Password(password.toCharArray()));
|
||||
env.withChaCha(chacha);
|
||||
}
|
||||
}
|
||||
if (shuffle) {
|
||||
env.shuffle();
|
||||
}
|
||||
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")));
|
||||
|
||||
// connect upstream and run
|
||||
final DataContent content = env.build(encrypt); // env installs default openers on decrypt if none were added
|
||||
content.setInput(PlainFileBuilder.builder().url(inPath.toUri().toURL()).build(encrypt));
|
||||
try (InputStream in = content.getStream(); OutputStream out = Files.newOutputStream(outPath)) {
|
||||
in.transferTo(out);
|
||||
final KeyringStore ks = loadKeyringIfPresent(session, cmd, OPT_KEYRING);
|
||||
for (String alias : cmd.getOptionValues(OPT_TO_ALIAS) == null ? new String[0]
|
||||
: cmd.getOptionValues(OPT_TO_ALIAS)) {
|
||||
addRecipientFromAlias(session, env, ks, alias, kekLen, saltLen, false);
|
||||
}
|
||||
for (String psw : cmd.getOptionValues(OPT_TO_PSW) == null ? new String[0]
|
||||
: cmd.getOptionValues(OPT_TO_PSW)) {
|
||||
char[] passwordChars = psw.toCharArray();
|
||||
try {
|
||||
env.addPasswordRecipient(passwordChars, iter, saltLen, kekLen);
|
||||
} finally {
|
||||
Arrays.fill(passwordChars, '\0');
|
||||
}
|
||||
}
|
||||
for (String alias : cmd.getOptionValues(OPT_DECOY_ALIAS) == null ? new String[0]
|
||||
: cmd.getOptionValues(OPT_DECOY_ALIAS)) {
|
||||
addRecipientFromAlias(session, env, ks, alias, kekLen, saltLen, true);
|
||||
}
|
||||
for (String psw : cmd.getOptionValues(OPT_DECOY_PSW) == null ? new String[0]
|
||||
: cmd.getOptionValues(OPT_DECOY_PSW)) {
|
||||
char[] passwordChars = psw.toCharArray();
|
||||
try {
|
||||
env.addPasswordRecipientDecoy(passwordChars, iter, saltLen, kekLen);
|
||||
} finally {
|
||||
Arrays.fill(passwordChars, '\0');
|
||||
}
|
||||
}
|
||||
final int rndCount = Integer.parseInt(cmd.getOptionValue(OPT_DECOY_PSW_RAND, "0"));
|
||||
for (int i = 0; i < rndCount; i++) {
|
||||
char[] passwordChars = randomPassword();
|
||||
try {
|
||||
env.addPasswordRecipientDecoy(passwordChars, iter, saltLen, kekLen);
|
||||
} finally {
|
||||
Arrays.fill(passwordChars, '\0');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final String privAlias = cmd.getOptionValue(OPT_PRIV_ALIAS);
|
||||
final String password = cmd.getOptionValue(OPT_PASSWORD);
|
||||
|
||||
if ((privAlias == null && password == null) || (privAlias != null && password != null)) {
|
||||
throw new ParseException("Specify exactly one of --priv-alias or --password for decryption");
|
||||
}
|
||||
if (privAlias != null) {
|
||||
final KeyringStore ks = requireKeyring(session, cmd, OPT_KEYRING);
|
||||
final KeyringStore.PrivateWithId pr = ks.getPrivateWithId(privAlias);
|
||||
borrowedUnlockMaterial = new UnlockMaterial.Private(pr.key());
|
||||
} else {
|
||||
char[] passwordChars = password.toCharArray();
|
||||
try {
|
||||
borrowedUnlockMaterial = new UnlockMaterial.Password(passwordChars);
|
||||
} finally {
|
||||
Arrays.fill(passwordChars, '\0');
|
||||
}
|
||||
}
|
||||
env.unlockWith(borrowedUnlockMaterial);
|
||||
}
|
||||
|
||||
try (MultiRecipientContent content = env.build(encrypt)) {
|
||||
content.setInput(PlainFileBuilder.builder().url(inPath.toUri().toURL()).build(encrypt));
|
||||
try (InputStream in = content.getStream(); OutputStream out = Files.newOutputStream(outPath)) {
|
||||
in.transferTo(out);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (borrowedUnlockMaterial instanceof UnlockMaterial.Password passwordMaterial) {
|
||||
passwordMaterial.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -404,6 +442,33 @@ public final class Guard {
|
||||
return Paths.get(s + suffix);
|
||||
}
|
||||
|
||||
private static ZeroEchoSession createSession(CommandLine cmd, Option operationalMaximumOption,
|
||||
Option absoluteMaximumOption, boolean passwordOperation) throws ParseException {
|
||||
boolean hasOperationalMaximum = cmd.hasOption(operationalMaximumOption);
|
||||
boolean hasAbsoluteMaximum = cmd.hasOption(absoluteMaximumOption);
|
||||
if (!hasOperationalMaximum && !hasAbsoluteMaximum) {
|
||||
if (passwordOperation) {
|
||||
throw new ParseException(
|
||||
"Password operations require --pbkdf2-max and --pbkdf2-hard-max");
|
||||
}
|
||||
return new ZeroEchoSession();
|
||||
}
|
||||
if (!hasOperationalMaximum || !hasAbsoluteMaximum) {
|
||||
throw new ParseException("--pbkdf2-max and --pbkdf2-hard-max must be specified together");
|
||||
}
|
||||
try {
|
||||
int operationalMaximum = Integer.parseInt(cmd.getOptionValue(operationalMaximumOption));
|
||||
int absoluteMaximum = Integer.parseInt(cmd.getOptionValue(absoluteMaximumOption));
|
||||
return new ZeroEchoSession().withPbkdf2Limits(
|
||||
new Pbkdf2Limits(operationalMaximum, absoluteMaximum));
|
||||
} catch (IllegalArgumentException exception) {
|
||||
ParseException parseException =
|
||||
new ParseException("Invalid PBKDF2 limits: " + exception.getMessage());
|
||||
parseException.initCause(exception);
|
||||
throw parseException;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] parseHex(String s) throws ParseException {
|
||||
try {
|
||||
return HexFormat.of().parseHex(s);
|
||||
@@ -434,15 +499,14 @@ public final class Guard {
|
||||
*
|
||||
* <p>
|
||||
* In both cases, the created context is consumed by
|
||||
* {@link MultiRecipientDataSourceBuilder#addRecipient(Object)} and is closed
|
||||
* internally by the builder.
|
||||
* 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
|
||||
* @param ks keyring store that provides public keys by alias
|
||||
* @param alias alias name of the recipient's public key in the keyring
|
||||
* @param kekBytes desired length in bytes of the key-encryption key when using
|
||||
* KEM
|
||||
* @param kekBytes key-encryption key length; exactly 16 or 32 bytes
|
||||
* @param saltLen salt length in bytes when using KEM
|
||||
* @param decoy whether the recipient is a decoy
|
||||
* @throws GeneralSecurityException if the algorithm does not support the
|
||||
@@ -450,29 +514,52 @@ public final class Guard {
|
||||
* @throws IOException if context creation or builder operations
|
||||
* require I/O and fail
|
||||
*/
|
||||
private static void addRecipientFromAlias(MultiRecipientDataSourceBuilder env, KeyringStore ks, String alias,
|
||||
@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.PublicWithId r = ks.getPublicWithId(alias);
|
||||
final String algId = r.algorithm();
|
||||
final java.security.PublicKey pub = r.key();
|
||||
|
||||
// Try KEM first
|
||||
try (KemContext kem = CryptoAlgorithms.create(algId, KeyUsage.ENCAPSULATE, pub)) {
|
||||
if (decoy) {
|
||||
env.addRecipientDecoy(kem, kekBytes, saltLen); // builder closes context
|
||||
} else {
|
||||
env.addRecipient(kem, kekBytes, saltLen); // builder closes context
|
||||
}
|
||||
KemContext kem;
|
||||
try {
|
||||
kem = session.createContext(algId, KeyUsage.ENCAPSULATE, pub);
|
||||
} catch (UnsupportedRoleException notKem) {
|
||||
addEncryptionRecipient(session, env, algId, pub, decoy);
|
||||
return;
|
||||
} catch (Exception notKem) { // NOPMD
|
||||
// fall back to public-key encryption
|
||||
}
|
||||
|
||||
try (EncryptionContext enc = CryptoAlgorithms.create(algId, KeyUsage.ENCRYPT, pub)) {
|
||||
boolean transferred = false;
|
||||
try {
|
||||
if (decoy) {
|
||||
env.addRecipientDecoy(enc); // builder closes context
|
||||
env.addRecipientDecoy(kem, kekBytes, saltLen);
|
||||
} else {
|
||||
env.addRecipient(enc); // builder closes context
|
||||
env.addRecipient(kem, kekBytes, saltLen);
|
||||
}
|
||||
transferred = true;
|
||||
} finally {
|
||||
if (!transferred) {
|
||||
kem.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "PMD.CloseResource", "PMD.UseTryWithResources" })
|
||||
private static void addEncryptionRecipient(ZeroEchoSession session, MultiRecipientDataSourceBuilder env,
|
||||
String algorithmId, java.security.PublicKey publicKey, boolean decoy) throws IOException {
|
||||
EncryptionContext encryption = session.createContext(algorithmId, KeyUsage.ENCRYPT, publicKey);
|
||||
boolean transferred = false;
|
||||
try {
|
||||
if (decoy) {
|
||||
env.addRecipientDecoy(encryption);
|
||||
} else {
|
||||
env.addRecipient(encryption);
|
||||
}
|
||||
transferred = true;
|
||||
} finally {
|
||||
if (!transferred) {
|
||||
encryption.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -480,26 +567,27 @@ public final class Guard {
|
||||
private static char[] randomPassword() {
|
||||
// simple random alnum for decoy purposes only
|
||||
final String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
final SecureRandom rnd = new SecureRandom();
|
||||
final int len = 16 + rnd.nextInt(17); // 16..32
|
||||
final int len = 16 + RandomSupport.nextInt(17); // 16..32
|
||||
final char[] out = new char[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
out[i] = alphabet.charAt(rnd.nextInt(alphabet.length()));
|
||||
out[i] = alphabet.charAt(RandomSupport.nextInt(alphabet.length()));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static KeyringStore loadKeyringIfPresent(CommandLine cmd, Option optKs) throws IOException {
|
||||
private static KeyringStore loadKeyringIfPresent(ZeroEchoSession session, CommandLine cmd, Option optKs)
|
||||
throws IOException {
|
||||
if (!cmd.hasOption(optKs)) {
|
||||
return new KeyringStore();
|
||||
return new KeyringStore(session);
|
||||
}
|
||||
return KeyringStore.load(Paths.get(cmd.getOptionValue(optKs)));
|
||||
return KeyringStore.load(session, Paths.get(cmd.getOptionValue(optKs)));
|
||||
}
|
||||
|
||||
private static KeyringStore requireKeyring(CommandLine cmd, Option optKs) throws IOException, ParseException {
|
||||
private static KeyringStore requireKeyring(ZeroEchoSession session, CommandLine cmd, Option optKs)
|
||||
throws IOException, ParseException {
|
||||
if (!cmd.hasOption(optKs)) {
|
||||
throw new ParseException("--keyring <file> is required when aliases are used");
|
||||
}
|
||||
return KeyringStore.load(Paths.get(cmd.getOptionValue(optKs)));
|
||||
return KeyringStore.load(session, Paths.get(cmd.getOptionValue(optKs)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ import zeroecho.core.storage.KeyringStore;
|
||||
import zeroecho.sdk.builders.alg.AesDataContentBuilder;
|
||||
import zeroecho.sdk.builders.alg.ChaChaDataContentBuilder;
|
||||
import zeroecho.sdk.builders.alg.KemDataContentBuilder;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
import zeroecho.sdk.builders.core.DataContentChainBuilder;
|
||||
import zeroecho.sdk.builders.core.PlainFileBuilder;
|
||||
import zeroecho.sdk.content.api.DataContent;
|
||||
@@ -203,6 +204,7 @@ public final class Kem { // NOPMD
|
||||
}
|
||||
|
||||
public static int main(String[] args, Options opts) throws ParseException, IOException, GeneralSecurityException { // NOPMD
|
||||
ZeroEchoSession session = new ZeroEchoSession();
|
||||
defineOptions(opts);
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
CommandLine cmd = parser.parse(opts, args);
|
||||
@@ -240,10 +242,10 @@ public final class Kem { // NOPMD
|
||||
|
||||
final String kemId = cmd.getOptionValue(OPT_KEM.getLongOpt());
|
||||
final Path keyringPath = Path.of(cmd.getOptionValue(OPT_KEYRING.getLongOpt()));
|
||||
final KeyringStore keyring = KeyringStore.load(keyringPath);
|
||||
final KeyringStore keyring = KeyringStore.load(session, keyringPath);
|
||||
|
||||
// Configure KEM envelope
|
||||
KemDataContentBuilder kem = KemDataContentBuilder.builder().kem(kemId);
|
||||
KemDataContentBuilder kem = KemDataContentBuilder.builder(session).kem(kemId);
|
||||
if (cmd.hasOption(OPT_DIRECT.getLongOpt())) {
|
||||
kem = kem.directSecret();
|
||||
} else {
|
||||
@@ -267,7 +269,7 @@ public final class Kem { // NOPMD
|
||||
// AES payload
|
||||
if (wantAes) {
|
||||
String mode = cmd.getOptionValue(OPT_AES_CIPHER.getLongOpt(), "gcm").toLowerCase(Locale.ROOT);
|
||||
AesDataContentBuilder aes = AesDataContentBuilder.builder();
|
||||
AesDataContentBuilder aes = AesDataContentBuilder.builder(session);
|
||||
switch (mode) {
|
||||
case "gcm" -> {
|
||||
Integer tagBitsOpt = parsedIntOpt(cmd, OPT_AES_TAG_BITS);
|
||||
@@ -294,7 +296,7 @@ public final class Kem { // NOPMD
|
||||
|
||||
// ChaCha payload
|
||||
if (wantChaCha) {
|
||||
ChaChaDataContentBuilder cc = ChaChaDataContentBuilder.builder();
|
||||
ChaChaDataContentBuilder cc = ChaChaDataContentBuilder.builder(session);
|
||||
byte[] nonce = parseHexOpt(cmd, OPT_CHACHA_NONCE);
|
||||
if (nonce != null) {
|
||||
cc = cc.withNonce(nonce);
|
||||
|
||||
@@ -42,6 +42,7 @@ import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
@@ -61,10 +62,11 @@ import org.apache.commons.cli.ParseException;
|
||||
|
||||
import zeroecho.core.CryptoAlgorithm;
|
||||
import zeroecho.core.CryptoAlgorithms;
|
||||
import zeroecho.core.KeyOperation;
|
||||
import zeroecho.core.KeyOperationInfo;
|
||||
import zeroecho.core.spec.AlgorithmKeySpec;
|
||||
import zeroecho.core.spi.AsymmetricKeyBuilder;
|
||||
import zeroecho.core.spi.SymmetricKeyBuilder;
|
||||
import zeroecho.core.storage.KeyringStore;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
|
||||
/**
|
||||
* Command-line utility for managing key material in a text-based keyring store.
|
||||
@@ -231,6 +233,7 @@ public final class KeyStoreManagement { // NOPMD
|
||||
* @throws IOException if I/O fails
|
||||
*/
|
||||
public static int main(final String[] args, final Options dispatcherOptions) throws ParseException, IOException {
|
||||
ZeroEchoSession session = new ZeroEchoSession();
|
||||
defineOptions(dispatcherOptions);
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
CommandLine cmd = parser.parse(dispatcherOptions, args);
|
||||
@@ -245,14 +248,15 @@ public final class KeyStoreManagement { // NOPMD
|
||||
}
|
||||
|
||||
Path keyringPath = Path.of(cmd.getOptionValue(KEYSTORE_OPTION.getLongOpt()));
|
||||
KeyringStore store = Files.exists(keyringPath) ? KeyringStore.load(keyringPath) : new KeyringStore();
|
||||
KeyringStore store = Files.exists(keyringPath) ? KeyringStore.load(session, keyringPath)
|
||||
: new KeyringStore(session);
|
||||
|
||||
if (cmd.hasOption(LIST_ALIASES_OPTION.getLongOpt())) {
|
||||
listAliases(store);
|
||||
return 0;
|
||||
}
|
||||
if (cmd.hasOption(GENERATE_OPTION.getLongOpt())) {
|
||||
doGenerate(store, cmd);
|
||||
doGenerate(session, store, cmd);
|
||||
store.save(keyringPath);
|
||||
return 0;
|
||||
}
|
||||
@@ -311,8 +315,12 @@ public final class KeyStoreManagement { // NOPMD
|
||||
Set<String> ids = CryptoAlgorithms.available();
|
||||
for (String id : ids) {
|
||||
CryptoAlgorithm a = CryptoAlgorithms.require(id);
|
||||
boolean hasAsym = !a.asymmetricBuildersInfo().isEmpty();
|
||||
boolean hasSym = !a.symmetricBuildersInfo().isEmpty();
|
||||
boolean hasAsym = a.keyOperations().stream()
|
||||
.anyMatch(info -> info.operation() != KeyOperation.SYMMETRIC_GENERATE
|
||||
&& info.operation() != KeyOperation.SYMMETRIC_IMPORT);
|
||||
boolean hasSym = a.keyOperations().stream()
|
||||
.anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE
|
||||
|| info.operation() == KeyOperation.SYMMETRIC_IMPORT);
|
||||
out.printf(Locale.ROOT, "%-12s asym:%s sym:%s%n", id, hasAsym, hasSym);
|
||||
}
|
||||
}
|
||||
@@ -346,7 +354,8 @@ public final class KeyStoreManagement { // NOPMD
|
||||
* @param store keyring store to mutate
|
||||
* @param cmd parsed command line
|
||||
*/
|
||||
public static void doGenerate(final KeyringStore store, final CommandLine cmd) { // NOPMD
|
||||
public static void doGenerate(final ZeroEchoSession session, final KeyringStore store,
|
||||
final CommandLine cmd) {
|
||||
String algId = required(cmd, ALG_OPTION, "--alg is required for --generate");
|
||||
String aliasBase = required(cmd, ALIAS_OPTION, "--alias is required for --generate");
|
||||
String kind = cmd.getOptionValue(KIND_OPTION.getLongOpt());
|
||||
@@ -355,119 +364,155 @@ public final class KeyStoreManagement { // NOPMD
|
||||
boolean overwrite = cmd.hasOption(OVERWRITE_OPTION.getLongOpt());
|
||||
|
||||
CryptoAlgorithm alg = CryptoAlgorithms.require(algId);
|
||||
boolean canAsym = !alg.asymmetricBuildersInfo().isEmpty();
|
||||
boolean canSym = !alg.symmetricBuildersInfo().isEmpty();
|
||||
boolean canAsym = alg.keyOperations().stream()
|
||||
.anyMatch(info -> info.operation() == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE);
|
||||
boolean canSym = alg.keyOperations().stream()
|
||||
.anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE);
|
||||
|
||||
boolean doAsym = "asym".equalsIgnoreCase(kind) || (kind == null && canAsym && !canSym);
|
||||
boolean doSym = "sym".equalsIgnoreCase(kind) || (kind == null && canSym && !canAsym);
|
||||
GenerationKind generationKind = selectGenerationKind(kind, canAsym, canSym);
|
||||
if (generationKind == GenerationKind.ASYMMETRIC) {
|
||||
generateAsymmetric(session, store, alg, algId, aliasBase, pubSfx, prvSfx, overwrite);
|
||||
}
|
||||
if (generationKind == GenerationKind.SYMMETRIC) {
|
||||
generateSymmetric(session, store, alg, algId, aliasBase, overwrite);
|
||||
}
|
||||
}
|
||||
|
||||
if (!doAsym && !doSym && canAsym && canSym) {
|
||||
private static GenerationKind selectGenerationKind(String requestedKind, boolean canAsymmetric,
|
||||
boolean canSymmetric) {
|
||||
if ("asym".equalsIgnoreCase(requestedKind) || requestedKind == null && canAsymmetric && !canSymmetric) {
|
||||
return GenerationKind.ASYMMETRIC;
|
||||
}
|
||||
if ("sym".equalsIgnoreCase(requestedKind) || requestedKind == null && canSymmetric && !canAsymmetric) {
|
||||
return GenerationKind.SYMMETRIC;
|
||||
}
|
||||
if (canAsymmetric && canSymmetric) {
|
||||
throw new IllegalArgumentException("Algorithm supports both; specify --kind sym|asym");
|
||||
}
|
||||
return GenerationKind.NONE;
|
||||
}
|
||||
|
||||
if (doAsym) {
|
||||
KeyPair kp = null;
|
||||
CryptoAlgorithm.AsymBuilderInfo used = null;
|
||||
for (CryptoAlgorithm.AsymBuilderInfo bi : alg.asymmetricBuildersInfo()) {
|
||||
if (bi.defaultKeySpec == null) {
|
||||
continue;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<AlgorithmKeySpec> st = (Class<AlgorithmKeySpec>) bi.specType;
|
||||
AsymmetricKeyBuilder<AlgorithmKeySpec> b = alg.asymmetricKeyBuilder(st);
|
||||
try {
|
||||
kp = b.generateKeyPair((AlgorithmKeySpec) bi.defaultKeySpec);
|
||||
if (kp != null) {
|
||||
used = bi;
|
||||
break;
|
||||
}
|
||||
} catch (Throwable ignore) { // NOPMD
|
||||
}
|
||||
}
|
||||
if (kp == null || used == null) {
|
||||
throw new IllegalStateException("No asymmetric builder with default spec worked for " + algId);
|
||||
}
|
||||
|
||||
Class<?> pubImp = null;
|
||||
Class<?> prvImp = null;
|
||||
for (CryptoAlgorithm.AsymBuilderInfo x : alg.asymmetricBuildersInfo()) {
|
||||
if (looksLikeImportSpecForPublic(x.specType)) {
|
||||
pubImp = x.specType;
|
||||
}
|
||||
if (looksLikeImportSpecForPrivate(x.specType)) {
|
||||
prvImp = x.specType;
|
||||
}
|
||||
}
|
||||
if (pubImp == null && prvImp == null) {
|
||||
throw new IllegalStateException("No import spec class found for " + algId + " (asymmetric)");
|
||||
}
|
||||
|
||||
byte[] spki = kp.getPublic() != null ? kp.getPublic().getEncoded() : null;
|
||||
byte[] pkcs8 = kp.getPrivate() != null ? kp.getPrivate().getEncoded() : null;
|
||||
|
||||
AlgorithmKeySpec pubSpec = pubImp != null ? makeImportSpec(pubImp, spki, algId, used.defaultKeySpec) : null;
|
||||
AlgorithmKeySpec prvSpec = prvImp != null ? makeImportSpec(prvImp, pkcs8, algId, used.defaultKeySpec)
|
||||
: null;
|
||||
|
||||
if (pubImp != null && pubSpec == null) {
|
||||
throw new IllegalStateException("Cannot construct public import spec for " + algId);
|
||||
}
|
||||
if (prvImp != null && prvSpec == null) {
|
||||
throw new IllegalStateException("Cannot construct private import spec for " + algId);
|
||||
}
|
||||
|
||||
String pubAlias = aliasBase + pubSfx;
|
||||
String prvAlias = aliasBase + prvSfx;
|
||||
ensureWritable(store, pubAlias, overwrite);
|
||||
ensureWritable(store, prvAlias, overwrite);
|
||||
|
||||
store.putPublic(pubAlias, algId, pubSpec);
|
||||
store.putPrivate(prvAlias, algId, prvSpec);
|
||||
|
||||
PrintWriter out = new PrintWriter(System.out, true, StandardCharsets.UTF_8); // NOPMD
|
||||
out.printf("Generated %s -> %s, %s%n", algId, pubAlias, prvAlias);
|
||||
private static void generateAsymmetric(ZeroEchoSession session, KeyringStore store, CryptoAlgorithm algorithm,
|
||||
String algorithmId, String aliasBase, String publicSuffix, String privateSuffix, boolean overwrite) {
|
||||
GeneratedKeyPair generated = firstGeneratedKeyPair(session, algorithm, algorithmId);
|
||||
Class<?> publicImport = findImportSpecClass(algorithm, KeyOperation.ASYMMETRIC_PUBLIC_IMPORT, true);
|
||||
Class<?> privateImport = findImportSpecClass(algorithm, KeyOperation.ASYMMETRIC_PRIVATE_IMPORT, false);
|
||||
if (publicImport == null && privateImport == null) {
|
||||
throw new IllegalStateException("No import spec class found for " + algorithmId + " (asymmetric)");
|
||||
}
|
||||
|
||||
if (doSym) {
|
||||
SecretKey sk = null;
|
||||
CryptoAlgorithm.SymBuilderInfo used = null;
|
||||
for (CryptoAlgorithm.SymBuilderInfo bi : alg.symmetricBuildersInfo()) {
|
||||
if (bi.defaultKeySpec() == null) {
|
||||
continue;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<AlgorithmKeySpec> st = (Class<AlgorithmKeySpec>) bi.specType();
|
||||
SymmetricKeyBuilder<AlgorithmKeySpec> b = alg.symmetricKeyBuilder(st);
|
||||
try {
|
||||
sk = b.generateSecret((AlgorithmKeySpec) bi.defaultKeySpec());
|
||||
if (sk != null) {
|
||||
used = bi;
|
||||
break;
|
||||
}
|
||||
} catch (Throwable ignore) { // NOPMD
|
||||
KeyPair pair = generated.pair();
|
||||
byte[] publicEncoding = pair.getPublic() == null ? null : pair.getPublic().getEncoded();
|
||||
byte[] privateEncoding = pair.getPrivate() == null ? null : pair.getPrivate().getEncoded();
|
||||
AlgorithmKeySpec publicSpec = publicImport == null ? null
|
||||
: makeImportSpec(publicImport, publicEncoding, algorithmId, generated.info().defaultSpec());
|
||||
AlgorithmKeySpec privateSpec = privateImport == null ? null
|
||||
: makeImportSpec(privateImport, privateEncoding, algorithmId, generated.info().defaultSpec());
|
||||
requireImportSpec(publicImport, publicSpec, "public", algorithmId);
|
||||
requireImportSpec(privateImport, privateSpec, "private", algorithmId);
|
||||
|
||||
String publicAlias = aliasBase + publicSuffix;
|
||||
String privateAlias = aliasBase + privateSuffix;
|
||||
ensureWritable(store, publicAlias, overwrite);
|
||||
ensureWritable(store, privateAlias, overwrite);
|
||||
store.putPublic(publicAlias, algorithmId, publicSpec);
|
||||
store.putPrivate(privateAlias, algorithmId, privateSpec);
|
||||
|
||||
PrintWriter out = new PrintWriter(System.out, true, StandardCharsets.UTF_8); // NOPMD
|
||||
out.printf("Generated %s -> %s, %s%n", algorithmId, publicAlias, privateAlias);
|
||||
}
|
||||
|
||||
private static GeneratedKeyPair firstGeneratedKeyPair(ZeroEchoSession session, CryptoAlgorithm algorithm,
|
||||
String algorithmId) {
|
||||
for (KeyOperationInfo info : algorithm.keyOperations()) {
|
||||
if (info.operation() != KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE || info.defaultSpec() == null) {
|
||||
continue;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<AlgorithmKeySpec> specType = (Class<AlgorithmKeySpec>) info.specType();
|
||||
try {
|
||||
KeyPair pair = session.keyBuilders().asymmetric().keyPairGenerator(algorithmId, specType)
|
||||
.generateKeyPair(info.defaultSpec());
|
||||
if (pair != null) {
|
||||
return new GeneratedKeyPair(pair, info);
|
||||
}
|
||||
} catch (GeneralSecurityException | RuntimeException ignored) { // NOPMD
|
||||
// Try the next registered default specification.
|
||||
}
|
||||
if (sk == null || used == null) {
|
||||
throw new IllegalStateException("No symmetric builder with default spec worked for " + algId);
|
||||
}
|
||||
|
||||
Class<?> impSym = findSymmetricImportSpecClass(alg);
|
||||
if (impSym == null) {
|
||||
throw new IllegalStateException("No symmetric import spec class for " + algId);
|
||||
}
|
||||
|
||||
byte[] raw = sk.getEncoded();
|
||||
AlgorithmKeySpec secSpec = makeImportSpec(impSym, raw, algId, used.defaultKeySpec());
|
||||
if (secSpec == null) {
|
||||
throw new IllegalStateException("Cannot construct symmetric import spec for " + algId);
|
||||
}
|
||||
|
||||
ensureWritable(store, aliasBase, overwrite);
|
||||
store.putSecret(aliasBase, algId, secSpec);
|
||||
|
||||
PrintWriter out = new PrintWriter(System.out, true, StandardCharsets.UTF_8); // NOPMD
|
||||
out.printf("Generated %s -> %s%n", algId, aliasBase);
|
||||
}
|
||||
throw new IllegalStateException("No asymmetric builder with default spec worked for " + algorithmId);
|
||||
}
|
||||
|
||||
private static Class<?> findImportSpecClass(CryptoAlgorithm algorithm, KeyOperation operation,
|
||||
boolean publicImport) {
|
||||
for (KeyOperationInfo info : algorithm.keyOperations()) {
|
||||
boolean matchingName = publicImport ? looksLikeImportSpecForPublic(info.specType())
|
||||
: looksLikeImportSpecForPrivate(info.specType());
|
||||
if (info.operation() == operation && matchingName) {
|
||||
return info.specType();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void requireImportSpec(Class<?> specType, AlgorithmKeySpec spec, String kind, String algorithmId) {
|
||||
if (specType != null && spec == null) {
|
||||
throw new IllegalStateException("Cannot construct " + kind + " import spec for " + algorithmId);
|
||||
}
|
||||
}
|
||||
|
||||
private static void generateSymmetric(ZeroEchoSession session, KeyringStore store, CryptoAlgorithm algorithm,
|
||||
String algorithmId, String alias, boolean overwrite) {
|
||||
GeneratedSecret generated = firstGeneratedSecret(session, algorithm, algorithmId);
|
||||
Class<?> importType = findSymmetricImportSpecClass(algorithm);
|
||||
if (importType == null) {
|
||||
throw new IllegalStateException("No symmetric import spec class for " + algorithmId);
|
||||
}
|
||||
|
||||
byte[] encoding = generated.key().getEncoded();
|
||||
AlgorithmKeySpec spec = makeImportSpec(importType, encoding, algorithmId, generated.info().defaultSpec());
|
||||
if (spec == null) {
|
||||
throw new IllegalStateException("Cannot construct symmetric import spec for " + algorithmId);
|
||||
}
|
||||
ensureWritable(store, alias, overwrite);
|
||||
store.putSecret(alias, algorithmId, spec);
|
||||
|
||||
PrintWriter out = new PrintWriter(System.out, true, StandardCharsets.UTF_8); // NOPMD
|
||||
out.printf("Generated %s -> %s%n", algorithmId, alias);
|
||||
}
|
||||
|
||||
private static GeneratedSecret firstGeneratedSecret(ZeroEchoSession session, CryptoAlgorithm algorithm,
|
||||
String algorithmId) {
|
||||
for (KeyOperationInfo info : algorithm.keyOperations()) {
|
||||
if (info.operation() != KeyOperation.SYMMETRIC_GENERATE || info.defaultSpec() == null) {
|
||||
continue;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<AlgorithmKeySpec> specType = (Class<AlgorithmKeySpec>) info.specType();
|
||||
try {
|
||||
SecretKey key = session.keyBuilders().symmetric().generator(algorithmId, specType)
|
||||
.generateSecret(info.defaultSpec());
|
||||
if (key != null) {
|
||||
return new GeneratedSecret(key, info);
|
||||
}
|
||||
} catch (GeneralSecurityException | RuntimeException ignored) { // NOPMD
|
||||
// Try the next registered default specification.
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("No symmetric builder with default spec worked for " + algorithmId);
|
||||
}
|
||||
|
||||
private record GeneratedKeyPair(KeyPair pair, KeyOperationInfo info) {
|
||||
}
|
||||
|
||||
private record GeneratedSecret(SecretKey key, KeyOperationInfo info) {
|
||||
}
|
||||
|
||||
/** Selects the exact key-generation operation requested by the command. */
|
||||
private enum GenerationKind {
|
||||
ASYMMETRIC,
|
||||
SYMMETRIC,
|
||||
NONE
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -545,19 +590,11 @@ public final class KeyStoreManagement { // NOPMD
|
||||
}
|
||||
|
||||
private static Class<?> findSymmetricImportSpecClass(CryptoAlgorithm alg) {
|
||||
for (CryptoAlgorithm.SymBuilderInfo x : alg.symmetricBuildersInfo()) {
|
||||
String n = x.specType().getSimpleName();
|
||||
if (n.contains("Import") || n.endsWith("KeyImportSpec") || n.endsWith("SecretSpec")) {
|
||||
for (KeyOperationInfo x : alg.keyOperations()) {
|
||||
if (x.operation() == KeyOperation.SYMMETRIC_IMPORT) {
|
||||
return x.specType();
|
||||
}
|
||||
}
|
||||
for (CryptoAlgorithm.SymBuilderInfo x : alg.symmetricBuildersInfo()) {
|
||||
try {
|
||||
x.specType().getConstructor(byte[].class);
|
||||
return x.specType();
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ import zeroecho.core.spec.VoidSpec;
|
||||
import zeroecho.core.storage.KeyringStore;
|
||||
import zeroecho.core.tag.TagEngineBuilder;
|
||||
import zeroecho.sdk.builders.TagTrailerDataContentBuilder;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
import zeroecho.sdk.content.api.DataContent;
|
||||
import zeroecho.sdk.content.api.PlainContent;
|
||||
import zeroecho.sdk.content.builtin.PlainFile;
|
||||
@@ -157,6 +158,7 @@ public final class Tag { // NOPMD
|
||||
* signature or digest processing
|
||||
*/
|
||||
public static int main(String[] args, Options root) throws ParseException, IOException, GeneralSecurityException {
|
||||
ZeroEchoSession session = new ZeroEchoSession();
|
||||
Options opts = root;
|
||||
opts.addOption(TYPE_OPT);
|
||||
opts.addOption(MODE_OPT);
|
||||
@@ -196,21 +198,23 @@ public final class Tag { // NOPMD
|
||||
|
||||
if (TYPE_SIGNATURE.equals(type)) {
|
||||
String ksPath = require(cli, KS_OPT, "--ks <file> is required for --type signature");
|
||||
KeyringStore keyring = KeyringStore.load(Path.of(ksPath));
|
||||
KeyringStore keyring = KeyringStore.load(session, Path.of(ksPath));
|
||||
ContextSpec spec = VoidSpec.INSTANCE;
|
||||
|
||||
if (produce) {
|
||||
String privAlias = require(cli, PRIV_OPT, "signature produce requires --priv <alias>");
|
||||
PrivateKey priv = keyring.getPrivate(privAlias);
|
||||
tail = new TagTrailerDataContentBuilder<>(TagEngineBuilder.signature(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(alg, pub, spec)).build(false);
|
||||
tail = new TagTrailerDataContentBuilder<>(TagEngineBuilder.signature(session, alg, pub, spec))
|
||||
.build(false);
|
||||
}
|
||||
} else { // digest
|
||||
DigestSpec spec = parseDigest(alg);
|
||||
tail = new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(spec)).build(produce);
|
||||
tail = new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(session, spec)).build(produce);
|
||||
}
|
||||
|
||||
tail.setInput(source);
|
||||
|
||||
Reference in New Issue
Block a user