chore(text): source code format
This commit is contained in:
@@ -138,18 +138,16 @@ public final class Guard {
|
|||||||
/**
|
/**
|
||||||
* Executes Guard with an explicit keyring unlock source.
|
* Executes Guard with an explicit keyring unlock source.
|
||||||
*
|
*
|
||||||
* @param args command arguments
|
* @param args command arguments
|
||||||
* @param options dispatcher options
|
* @param options dispatcher options
|
||||||
* @param keyringUnlockProvider destroyable-password provider
|
* @param keyringUnlockProvider destroyable-password provider
|
||||||
* @return process exit code
|
* @return process exit code
|
||||||
* @throws ParseException if parsing fails
|
* @throws ParseException if parsing fails
|
||||||
* @throws IOException if I/O fails
|
* @throws IOException if I/O fails
|
||||||
* @throws GeneralSecurityException if cryptographic processing fails
|
* @throws GeneralSecurityException if cryptographic processing fails
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.NcssCount", "PMD.CognitiveComplexity",
|
@SuppressWarnings({ "PMD.NcssCount", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "PMD.NPathComplexity" })
|
||||||
"PMD.CyclomaticComplexity", "PMD.NPathComplexity" })
|
public static int main(final String[] args, final Options options, KeyringUnlockProvider keyringUnlockProvider)
|
||||||
public static int main(final String[] args, final Options options,
|
|
||||||
KeyringUnlockProvider keyringUnlockProvider)
|
|
||||||
throws ParseException, IOException, GeneralSecurityException {
|
throws ParseException, IOException, GeneralSecurityException {
|
||||||
// ---- operation selection
|
// ---- operation selection
|
||||||
final Option OPT_ENCRYPT = Option.builder("e").longOpt("encrypt").hasArg().argName("in-file")
|
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();
|
.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")
|
final Option OPT_PBKDF2_MAX = Option.builder().longOpt("pbkdf2-max").hasArg().argName("iterations")
|
||||||
.desc("Operational PBKDF2 ceiling; required for password operations").get();
|
.desc("Operational PBKDF2 ceiling; required for password operations").get();
|
||||||
final Option OPT_PBKDF2_HARD_MAX = Option.builder().longOpt("pbkdf2-hard-max").hasArg()
|
final Option OPT_PBKDF2_HARD_MAX = Option.builder().longOpt("pbkdf2-hard-max").hasArg().argName("iterations")
|
||||||
.argName("iterations")
|
|
||||||
.desc("Absolute decoded PBKDF2 safety ceiling; required for password operations").get();
|
.desc("Absolute decoded PBKDF2 safety ceiling; required for password operations").get();
|
||||||
|
|
||||||
// ---- decoys (all types)
|
// ---- decoys (all types)
|
||||||
@@ -260,8 +257,7 @@ public final class Guard {
|
|||||||
final CommandLine cmd = parser.parse(options, args);
|
final CommandLine cmd = parser.parse(options, args);
|
||||||
final boolean passwordOperation = cmd.hasOption(OPT_TO_PSW) || cmd.hasOption(OPT_DECOY_PSW)
|
final boolean passwordOperation = cmd.hasOption(OPT_TO_PSW) || cmd.hasOption(OPT_DECOY_PSW)
|
||||||
|| cmd.hasOption(OPT_DECOY_PSW_RAND) || cmd.hasOption(OPT_PASSWORD);
|
|| cmd.hasOption(OPT_DECOY_PSW_RAND) || cmd.hasOption(OPT_PASSWORD);
|
||||||
final ZeroEchoSession session = createSession(cmd, OPT_PBKDF2_MAX, OPT_PBKDF2_HARD_MAX,
|
final ZeroEchoSession session = createSession(cmd, OPT_PBKDF2_MAX, OPT_PBKDF2_HARD_MAX, passwordOperation);
|
||||||
passwordOperation);
|
|
||||||
|
|
||||||
final boolean encrypt = cmd.hasOption(OPT_ENCRYPT);
|
final boolean encrypt = cmd.hasOption(OPT_ENCRYPT);
|
||||||
final Path inPath = Paths.get(cmd.getOptionValue(encrypt ? OPT_ENCRYPT : OPT_DECRYPT));
|
final Path inPath = Paths.get(cmd.getOptionValue(encrypt ? OPT_ENCRYPT : OPT_DECRYPT));
|
||||||
@@ -359,8 +355,7 @@ public final class Guard {
|
|||||||
|
|
||||||
// envelope builder (new API)
|
// envelope builder (new API)
|
||||||
final MultiRecipientDataSourceBuilder env = MultiRecipientDataSourceBuilder.builder(session)
|
final MultiRecipientDataSourceBuilder env = MultiRecipientDataSourceBuilder.builder(session)
|
||||||
.payloadKeyBytes(cekBytes)
|
.payloadKeyBytes(cekBytes).headerLimits(maxRecipients, maxEntryLen);
|
||||||
.headerLimits(maxRecipients, maxEntryLen);
|
|
||||||
UnlockMaterial borrowedUnlockMaterial = null;
|
UnlockMaterial borrowedUnlockMaterial = null;
|
||||||
try (env) {
|
try (env) {
|
||||||
if (aes != null) {
|
if (aes != null) {
|
||||||
@@ -374,11 +369,10 @@ public final class Guard {
|
|||||||
if (encrypt) {
|
if (encrypt) {
|
||||||
final int iter = Integer.parseInt(cmd.getOptionValue(OPT_PSW_ITER, "200000"));
|
final int iter = Integer.parseInt(cmd.getOptionValue(OPT_PSW_ITER, "200000"));
|
||||||
final int saltLen = Integer.parseInt(cmd.getOptionValue(OPT_PSW_SALT, "16"));
|
final int saltLen = Integer.parseInt(cmd.getOptionValue(OPT_PSW_SALT, "16"));
|
||||||
final int kekLen = RecipientKekSizes.requireSupported(
|
final int kekLen = RecipientKekSizes
|
||||||
Integer.parseInt(cmd.getOptionValue(OPT_PSW_KEK, "32")));
|
.requireSupported(Integer.parseInt(cmd.getOptionValue(OPT_PSW_KEK, "32")));
|
||||||
|
|
||||||
KeyringStore ks = loadKeyringIfPresent(cmd, OPT_KEYRING,
|
KeyringStore ks = loadKeyringIfPresent(cmd, OPT_KEYRING, keyringUnlockProvider);
|
||||||
keyringUnlockProvider);
|
|
||||||
try (ks) {
|
try (ks) {
|
||||||
for (String alias : cmd.getOptionValues(OPT_TO_ALIAS) == null ? new String[0]
|
for (String alias : cmd.getOptionValues(OPT_TO_ALIAS) == null ? new String[0]
|
||||||
: cmd.getOptionValues(OPT_TO_ALIAS)) {
|
: 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");
|
throw new ParseException("Specify exactly one of --priv-alias or --password for decryption");
|
||||||
}
|
}
|
||||||
if (privAlias != null) {
|
if (privAlias != null) {
|
||||||
try (KeyringStore ks = requireKeyring(cmd, OPT_KEYRING,
|
try (KeyringStore ks = requireKeyring(cmd, OPT_KEYRING, keyringUnlockProvider)) {
|
||||||
keyringUnlockProvider)) {
|
|
||||||
final KeyringStore.PrivateWithId pr = ks.getPrivateWithId(privAlias);
|
final KeyringStore.PrivateWithId pr = ks.getPrivateWithId(privAlias);
|
||||||
borrowedUnlockMaterial = new UnlockMaterial.Private(pr.key());
|
borrowedUnlockMaterial = new UnlockMaterial.Private(pr.key());
|
||||||
}
|
}
|
||||||
@@ -474,8 +467,7 @@ public final class Guard {
|
|||||||
boolean hasAbsoluteMaximum = cmd.hasOption(absoluteMaximumOption);
|
boolean hasAbsoluteMaximum = cmd.hasOption(absoluteMaximumOption);
|
||||||
if (!hasOperationalMaximum && !hasAbsoluteMaximum) {
|
if (!hasOperationalMaximum && !hasAbsoluteMaximum) {
|
||||||
if (passwordOperation) {
|
if (passwordOperation) {
|
||||||
throw new ParseException(
|
throw new ParseException("Password operations require --pbkdf2-max and --pbkdf2-hard-max");
|
||||||
"Password operations require --pbkdf2-max and --pbkdf2-hard-max");
|
|
||||||
}
|
}
|
||||||
return new ZeroEchoSession();
|
return new ZeroEchoSession();
|
||||||
}
|
}
|
||||||
@@ -485,11 +477,9 @@ public final class Guard {
|
|||||||
try {
|
try {
|
||||||
int operationalMaximum = Integer.parseInt(cmd.getOptionValue(operationalMaximumOption));
|
int operationalMaximum = Integer.parseInt(cmd.getOptionValue(operationalMaximumOption));
|
||||||
int absoluteMaximum = Integer.parseInt(cmd.getOptionValue(absoluteMaximumOption));
|
int absoluteMaximum = Integer.parseInt(cmd.getOptionValue(absoluteMaximumOption));
|
||||||
return new ZeroEchoSession().withPbkdf2Limits(
|
return new ZeroEchoSession().withPbkdf2Limits(new Pbkdf2Limits(operationalMaximum, absoluteMaximum));
|
||||||
new Pbkdf2Limits(operationalMaximum, absoluteMaximum));
|
|
||||||
} catch (IllegalArgumentException exception) {
|
} catch (IllegalArgumentException exception) {
|
||||||
ParseException parseException =
|
ParseException parseException = new ParseException("Invalid PBKDF2 limits: " + exception.getMessage());
|
||||||
new ParseException("Invalid PBKDF2 limits: " + exception.getMessage());
|
|
||||||
parseException.initCause(exception);
|
parseException.initCause(exception);
|
||||||
throw parseException;
|
throw parseException;
|
||||||
}
|
}
|
||||||
@@ -524,9 +514,9 @@ public final class Guard {
|
|||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* In both cases, the created context is consumed by
|
* In both cases, the created context is consumed by the matching
|
||||||
* the matching {@link MultiRecipientDataSourceBuilder} recipient method and is
|
* {@link MultiRecipientDataSourceBuilder} recipient method and is closed
|
||||||
* closed internally by the resulting content.
|
* internally by the resulting content.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param env target builder to which the recipient is added
|
* @param env target builder to which the recipient is added
|
||||||
@@ -542,8 +532,8 @@ public final class Guard {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.CloseResource", "PMD.UseTryWithResources" })
|
@SuppressWarnings({ "PMD.CloseResource", "PMD.UseTryWithResources" })
|
||||||
private static void addRecipientFromAlias(ZeroEchoSession session, MultiRecipientDataSourceBuilder env,
|
private static void addRecipientFromAlias(ZeroEchoSession session, MultiRecipientDataSourceBuilder env,
|
||||||
KeyringStore ks, String alias,
|
KeyringStore ks, String alias, int kekBytes, int saltLen, boolean decoy)
|
||||||
int kekBytes, int saltLen, boolean decoy) throws GeneralSecurityException, IOException {
|
throws GeneralSecurityException, IOException {
|
||||||
KeyringStore.PublicWithId r = ks.getPublicWithId(alias);
|
KeyringStore.PublicWithId r = ks.getPublicWithId(alias);
|
||||||
final String algId = r.algorithm();
|
final String algId = r.algorithm();
|
||||||
final java.security.PublicKey pub = r.key();
|
final java.security.PublicKey pub = r.key();
|
||||||
@@ -602,16 +592,14 @@ public final class Guard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static KeyringStore loadKeyringIfPresent(CommandLine cmd, Option optKs,
|
private static KeyringStore loadKeyringIfPresent(CommandLine cmd, Option optKs,
|
||||||
KeyringUnlockProvider unlockProvider)
|
KeyringUnlockProvider unlockProvider) throws IOException, GeneralSecurityException {
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
if (!cmd.hasOption(optKs)) {
|
if (!cmd.hasOption(optKs)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return KeyringUnlocks.open(Paths.get(cmd.getOptionValue(optKs)), unlockProvider);
|
return KeyringUnlocks.open(Paths.get(cmd.getOptionValue(optKs)), unlockProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KeyringStore requireKeyring(CommandLine cmd, Option optKs,
|
private static KeyringStore requireKeyring(CommandLine cmd, Option optKs, KeyringUnlockProvider unlockProvider)
|
||||||
KeyringUnlockProvider unlockProvider)
|
|
||||||
throws IOException, ParseException, GeneralSecurityException {
|
throws IOException, ParseException, GeneralSecurityException {
|
||||||
if (!cmd.hasOption(optKs)) {
|
if (!cmd.hasOption(optKs)) {
|
||||||
throw new ParseException("--keyring <file> is required when aliases are used");
|
throw new ParseException("--keyring <file> is required when aliases are used");
|
||||||
|
|||||||
@@ -210,30 +210,27 @@ public final class Kem { // NOPMD
|
|||||||
* @param args command arguments
|
* @param args command arguments
|
||||||
* @param opts command options
|
* @param opts command options
|
||||||
* @return process exit code
|
* @return process exit code
|
||||||
* @throws ParseException if arguments are invalid
|
* @throws ParseException if arguments are invalid
|
||||||
* @throws IOException if I/O fails
|
* @throws IOException if I/O fails
|
||||||
* @throws GeneralSecurityException if cryptographic processing fails
|
* @throws GeneralSecurityException if cryptographic processing fails
|
||||||
*/
|
*/
|
||||||
public static int main(String[] args, Options opts)
|
public static int main(String[] args, Options opts) throws ParseException, IOException, GeneralSecurityException {
|
||||||
throws ParseException, IOException, GeneralSecurityException {
|
|
||||||
return main(args, opts, KeyringUnlocks.console());
|
return main(args, opts, KeyringUnlocks.console());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the KEM command with an explicit keyring unlock source.
|
* Executes the KEM command with an explicit keyring unlock source.
|
||||||
*
|
*
|
||||||
* @param args command arguments
|
* @param args command arguments
|
||||||
* @param opts command options
|
* @param opts command options
|
||||||
* @param unlockProvider keyring password provider
|
* @param unlockProvider keyring password provider
|
||||||
* @return process exit code
|
* @return process exit code
|
||||||
* @throws ParseException if arguments are invalid
|
* @throws ParseException if arguments are invalid
|
||||||
* @throws IOException if I/O fails
|
* @throws IOException if I/O fails
|
||||||
* @throws GeneralSecurityException if cryptographic processing fails
|
* @throws GeneralSecurityException if cryptographic processing fails
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.NcssCount", "PMD.CyclomaticComplexity",
|
@SuppressWarnings({ "PMD.NcssCount", "PMD.CyclomaticComplexity", "PMD.NPathComplexity" })
|
||||||
"PMD.NPathComplexity" })
|
public static int main(String[] args, Options opts, KeyringUnlockProvider unlockProvider)
|
||||||
public static int main(String[] args, Options opts,
|
|
||||||
KeyringUnlockProvider unlockProvider)
|
|
||||||
throws ParseException, IOException, GeneralSecurityException {
|
throws ParseException, IOException, GeneralSecurityException {
|
||||||
ZeroEchoSession session = new ZeroEchoSession();
|
ZeroEchoSession session = new ZeroEchoSession();
|
||||||
defineOptions(opts);
|
defineOptions(opts);
|
||||||
@@ -275,105 +272,105 @@ public final class Kem { // NOPMD
|
|||||||
final Path keyringPath = Path.of(cmd.getOptionValue(OPT_KEYRING.getLongOpt()));
|
final Path keyringPath = Path.of(cmd.getOptionValue(OPT_KEYRING.getLongOpt()));
|
||||||
try (KeyringStore keyring = KeyringUnlocks.open(keyringPath, unlockProvider)) {
|
try (KeyringStore keyring = KeyringUnlocks.open(keyringPath, unlockProvider)) {
|
||||||
|
|
||||||
// Configure KEM envelope
|
// Configure KEM envelope
|
||||||
KemDataContentBuilder kem = KemDataContentBuilder.builder(session).kem(kemId);
|
KemDataContentBuilder kem = KemDataContentBuilder.builder(session).kem(kemId);
|
||||||
if (cmd.hasOption(OPT_DIRECT.getLongOpt())) {
|
if (cmd.hasOption(OPT_DIRECT.getLongOpt())) {
|
||||||
kem = kem.directSecret();
|
kem = kem.directSecret();
|
||||||
} else {
|
} else {
|
||||||
byte[] info = parseOptionalHex(cmd, OPT_HKDF, "ZeroEcho-KEM".getBytes());
|
byte[] info = parseOptionalHex(cmd, OPT_HKDF, "ZeroEcho-KEM".getBytes());
|
||||||
kem = kem.hkdfSha256(info);
|
kem = kem.hkdfSha256(info);
|
||||||
}
|
}
|
||||||
// typed numeric options
|
// typed numeric options
|
||||||
Integer keyBytes = parsedIntOpt(cmd, OPT_KEY_BYTES);
|
Integer keyBytes = parsedIntOpt(cmd, OPT_KEY_BYTES);
|
||||||
if (keyBytes != null) {
|
if (keyBytes != null) {
|
||||||
kem = kem.derivedKeyBytes(keyBytes);
|
kem = kem.derivedKeyBytes(keyBytes);
|
||||||
}
|
}
|
||||||
Integer maxKemCt = parsedIntOpt(cmd, OPT_MAX_KEM_CT);
|
Integer maxKemCt = parsedIntOpt(cmd, OPT_MAX_KEM_CT);
|
||||||
if (maxKemCt != null) {
|
if (maxKemCt != null) {
|
||||||
kem = kem.maxKemCiphertextLen(maxKemCt);
|
kem = kem.maxKemCiphertextLen(maxKemCt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common symmetric knobs
|
// Common symmetric knobs
|
||||||
final byte[] aad = parseHexOpt(cmd, OPT_AAD);
|
final byte[] aad = parseHexOpt(cmd, OPT_AAD);
|
||||||
final boolean wantHeader = cmd.hasOption(OPT_HEADER.getLongOpt());
|
final boolean wantHeader = cmd.hasOption(OPT_HEADER.getLongOpt());
|
||||||
|
|
||||||
// AES payload
|
// AES payload
|
||||||
if (wantAes) {
|
if (wantAes) {
|
||||||
String mode = cmd.getOptionValue(OPT_AES_CIPHER.getLongOpt(), "gcm").toLowerCase(Locale.ROOT);
|
String mode = cmd.getOptionValue(OPT_AES_CIPHER.getLongOpt(), "gcm").toLowerCase(Locale.ROOT);
|
||||||
AesDataContentBuilder aes = AesDataContentBuilder.builder(session);
|
AesDataContentBuilder aes = AesDataContentBuilder.builder(session);
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "gcm" -> {
|
case "gcm" -> {
|
||||||
Integer tagBitsOpt = parsedIntOpt(cmd, OPT_AES_TAG_BITS);
|
Integer tagBitsOpt = parsedIntOpt(cmd, OPT_AES_TAG_BITS);
|
||||||
int tagBits = tagBitsOpt == null ? 128 : tagBitsOpt;
|
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();
|
byte[] iv = parseHexOpt(cmd, OPT_AES_IV);
|
||||||
case "cbc" -> aes = aes.modeCbcPkcs5();
|
if (iv != null) {
|
||||||
default -> throw new ParseException("Unsupported --aes-cipher: " + mode);
|
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
|
// ChaCha payload
|
||||||
if (wantChaCha) {
|
if (wantChaCha) {
|
||||||
ChaChaDataContentBuilder cc = ChaChaDataContentBuilder.builder(session);
|
ChaChaDataContentBuilder cc = ChaChaDataContentBuilder.builder(session);
|
||||||
byte[] nonce = parseHexOpt(cmd, OPT_CHACHA_NONCE);
|
byte[] nonce = parseHexOpt(cmd, OPT_CHACHA_NONCE);
|
||||||
if (nonce != null) {
|
if (nonce != null) {
|
||||||
cc = cc.withDecryptionNonce(nonce);
|
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
|
// Pipeline: source -> kem payload stage
|
||||||
DataContent chain;
|
DataContent chain;
|
||||||
if (encrypt) {
|
if (encrypt) {
|
||||||
String alias = require(cmd, OPT_PUB, "Missing --pub for encryption");
|
String alias = require(cmd, OPT_PUB, "Missing --pub for encryption");
|
||||||
PublicKey recipient = keyring.getPublic(alias);
|
PublicKey recipient = keyring.getPublic(alias);
|
||||||
chain = DataContentChainBuilder.encrypt()
|
chain = DataContentChainBuilder.encrypt()
|
||||||
.add(PlainFileBuilder.builder().url(Path.of(input).toUri().toURL()))
|
.add(PlainFileBuilder.builder().url(Path.of(input).toUri().toURL()))
|
||||||
.add(kem.recipientPublic(recipient)).build();
|
.add(kem.recipientPublic(recipient)).build();
|
||||||
} else {
|
} else {
|
||||||
String alias = require(cmd, OPT_PRIV, "Missing --priv for decryption");
|
String alias = require(cmd, OPT_PRIV, "Missing --priv for decryption");
|
||||||
PrivateKey recipient = keyring.getPrivate(alias);
|
PrivateKey recipient = keyring.getPrivate(alias);
|
||||||
chain = DataContentChainBuilder.decrypt()
|
chain = DataContentChainBuilder.decrypt()
|
||||||
.add(PlainFileBuilder.builder().url(Path.of(input).toUri().toURL()))
|
.add(PlainFileBuilder.builder().url(Path.of(input).toUri().toURL()))
|
||||||
.add(kem.recipientPrivate(recipient)).build();
|
.add(kem.recipientPrivate(recipient)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
try (InputStream in = chain.getStream(); OutputStream out = Files.newOutputStream(output)) {
|
try (InputStream in = chain.getStream(); OutputStream out = Files.newOutputStream(output)) {
|
||||||
in.transferTo(out);
|
in.transferTo(out);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
if (LOG.isLoggable(Level.SEVERE)) {
|
if (LOG.isLoggable(Level.SEVERE)) {
|
||||||
LOG.log(Level.SEVERE, "I/O error", ex);
|
LOG.log(Level.SEVERE, "I/O error", ex);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,16 +192,15 @@ public final class KeyStoreManagement {
|
|||||||
/**
|
/**
|
||||||
* Executes the command with an explicit unlock source.
|
* 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 dispatcherOptions dispatcher options
|
||||||
* @param unlockProvider explicit destroyable-password provider
|
* @param unlockProvider explicit destroyable-password provider
|
||||||
* @return process exit code
|
* @return process exit code
|
||||||
* @throws ParseException if parsing fails
|
* @throws ParseException if parsing fails
|
||||||
* @throws IOException if keyring I/O fails
|
* @throws IOException if keyring I/O fails
|
||||||
* @throws GeneralSecurityException if cryptographic processing fails
|
* @throws GeneralSecurityException if cryptographic processing fails
|
||||||
*/
|
*/
|
||||||
public static int main(final String[] args, final Options dispatcherOptions,
|
public static int main(final String[] args, final Options dispatcherOptions, KeyringUnlockProvider unlockProvider)
|
||||||
KeyringUnlockProvider unlockProvider)
|
|
||||||
throws ParseException, IOException, GeneralSecurityException {
|
throws ParseException, IOException, GeneralSecurityException {
|
||||||
ZeroEchoSession session = new ZeroEchoSession();
|
ZeroEchoSession session = new ZeroEchoSession();
|
||||||
defineOptions(dispatcherOptions);
|
defineOptions(dispatcherOptions);
|
||||||
@@ -218,8 +217,7 @@ public final class KeyStoreManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Path keyringPath = Path.of(cmd.getOptionValue(KEYSTORE_OPTION.getLongOpt()));
|
Path keyringPath = Path.of(cmd.getOptionValue(KEYSTORE_OPTION.getLongOpt()));
|
||||||
try (KeyringStore store = Files.exists(keyringPath)
|
try (KeyringStore store = Files.exists(keyringPath) ? KeyringUnlocks.open(keyringPath, unlockProvider)
|
||||||
? KeyringUnlocks.open(keyringPath, unlockProvider)
|
|
||||||
: KeyringUnlocks.create(keyringPath, unlockProvider)) {
|
: KeyringUnlocks.create(keyringPath, unlockProvider)) {
|
||||||
if (cmd.hasOption(LIST_ALIASES_OPTION.getLongOpt())) {
|
if (cmd.hasOption(LIST_ALIASES_OPTION.getLongOpt())) {
|
||||||
listAliases(store);
|
listAliases(store);
|
||||||
@@ -310,8 +308,8 @@ public final class KeyStoreManagement {
|
|||||||
* @param store keyring store to mutate
|
* @param store keyring store to mutate
|
||||||
* @param cmd parsed command line
|
* @param cmd parsed command line
|
||||||
*/
|
*/
|
||||||
public static void doGenerate(final ZeroEchoSession session, final KeyringStore store,
|
public static void doGenerate(final ZeroEchoSession session, final KeyringStore store, final CommandLine cmd)
|
||||||
final CommandLine cmd) throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
String algId = required(cmd, ALG_OPTION, "--alg is required for --generate");
|
String algId = required(cmd, ALG_OPTION, "--alg is required for --generate");
|
||||||
String aliasBase = required(cmd, ALIAS_OPTION, "--alias is required for --generate");
|
String aliasBase = required(cmd, ALIAS_OPTION, "--alias is required for --generate");
|
||||||
String kind = cmd.getOptionValue(KIND_OPTION.getLongOpt());
|
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,
|
private static void generateSymmetric(ZeroEchoSession session, KeyringStore store, CryptoAlgorithm algorithm,
|
||||||
String algorithmId, String alias, boolean overwrite)
|
String algorithmId, String alias, boolean overwrite) throws IOException, GeneralSecurityException {
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
GeneratedSecret generated = firstGeneratedSecret(session, algorithm, algorithmId);
|
GeneratedSecret generated = firstGeneratedSecret(session, algorithm, algorithmId);
|
||||||
ensureWritable(store, alias, overwrite);
|
ensureWritable(store, alias, overwrite);
|
||||||
store.putSecret(alias, algorithmId, generated.key());
|
store.putSecret(alias, algorithmId, generated.key());
|
||||||
@@ -430,9 +427,7 @@ public final class KeyStoreManagement {
|
|||||||
|
|
||||||
/** Selects the exact key-generation operation requested by the command. */
|
/** Selects the exact key-generation operation requested by the command. */
|
||||||
private enum GenerationKind {
|
private enum GenerationKind {
|
||||||
ASYMMETRIC,
|
ASYMMETRIC, SYMMETRIC, NONE
|
||||||
SYMMETRIC,
|
|
||||||
NONE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String required(CommandLine cmd, Option opt, String message) {
|
private static String required(CommandLine cmd, Option opt, String message) {
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ final class KeyringUnlocks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KeyringPassword acquire(KeyringUnlockProvider provider)
|
private static KeyringPassword acquire(KeyringUnlockProvider provider) throws IOException {
|
||||||
throws IOException {
|
|
||||||
KeyringPassword password = provider.acquire();
|
KeyringPassword password = provider.acquire();
|
||||||
if (password == null) {
|
if (password == null) {
|
||||||
throw new IOException("Keyring unlock provider returned no password");
|
throw new IOException("Keyring unlock provider returned no password");
|
||||||
|
|||||||
@@ -158,24 +158,22 @@ public final class Tag { // NOPMD
|
|||||||
* @throws GeneralSecurityException if a cryptographic error occurs during
|
* @throws GeneralSecurityException if a cryptographic error occurs during
|
||||||
* signature or digest processing
|
* signature or digest processing
|
||||||
*/
|
*/
|
||||||
public static int main(String[] args, Options root)
|
public static int main(String[] args, Options root) throws ParseException, IOException, GeneralSecurityException {
|
||||||
throws ParseException, IOException, GeneralSecurityException {
|
|
||||||
return main(args, root, KeyringUnlocks.console());
|
return main(args, root, KeyringUnlocks.console());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the command with an explicit keyring unlock source.
|
* Executes the command with an explicit keyring unlock source.
|
||||||
*
|
*
|
||||||
* @param args command arguments
|
* @param args command arguments
|
||||||
* @param root root options
|
* @param root root options
|
||||||
* @param unlockProvider keyring password provider
|
* @param unlockProvider keyring password provider
|
||||||
* @return process exit code
|
* @return process exit code
|
||||||
* @throws ParseException if parsing fails
|
* @throws ParseException if parsing fails
|
||||||
* @throws IOException if I/O fails
|
* @throws IOException if I/O fails
|
||||||
* @throws GeneralSecurityException if cryptographic processing fails
|
* @throws GeneralSecurityException if cryptographic processing fails
|
||||||
*/
|
*/
|
||||||
public static int main(String[] args, Options root,
|
public static int main(String[] args, Options root, KeyringUnlockProvider unlockProvider)
|
||||||
KeyringUnlockProvider unlockProvider)
|
|
||||||
throws ParseException, IOException, GeneralSecurityException {
|
throws ParseException, IOException, GeneralSecurityException {
|
||||||
ZeroEchoSession session = new ZeroEchoSession();
|
ZeroEchoSession session = new ZeroEchoSession();
|
||||||
Options opts = root;
|
Options opts = root;
|
||||||
@@ -222,13 +220,13 @@ public final class Tag { // NOPMD
|
|||||||
if (produce) {
|
if (produce) {
|
||||||
String privAlias = require(cli, PRIV_OPT, "signature produce requires --priv <alias>");
|
String privAlias = require(cli, PRIV_OPT, "signature produce requires --priv <alias>");
|
||||||
PrivateKey priv = keyring.getPrivate(privAlias);
|
PrivateKey priv = keyring.getPrivate(privAlias);
|
||||||
tail = new TagTrailerDataContentBuilder<>(
|
tail = new TagTrailerDataContentBuilder<>(TagEngineBuilder.signature(session, alg, priv, spec))
|
||||||
TagEngineBuilder.signature(session, alg, priv, spec)).build(true);
|
.build(true);
|
||||||
} else {
|
} else {
|
||||||
String pubAlias = require(cli, PUB_OPT, "signature verify requires --pub <alias>");
|
String pubAlias = require(cli, PUB_OPT, "signature verify requires --pub <alias>");
|
||||||
PublicKey pub = keyring.getPublic(pubAlias);
|
PublicKey pub = keyring.getPublic(pubAlias);
|
||||||
tail = new TagTrailerDataContentBuilder<>(
|
tail = new TagTrailerDataContentBuilder<>(TagEngineBuilder.signature(session, alg, pub, spec))
|
||||||
TagEngineBuilder.signature(session, alg, pub, spec)).build(false);
|
.build(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // digest
|
} else { // digest
|
||||||
|
|||||||
@@ -124,16 +124,16 @@ public class GuardTest {
|
|||||||
|
|
||||||
// Encrypt
|
// Encrypt
|
||||||
String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--to-psw", password,
|
String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--to-psw", password,
|
||||||
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg",
|
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm",
|
||||||
"aes-gcm", "--tag-bits", Integer.toString(tagBits), "--aad-hex", aadHex };
|
"--tag-bits", Integer.toString(tagBits), "--aad-hex", aadHex };
|
||||||
System.out.println("...encrypt: " + Arrays.toString(encArgs));
|
System.out.println("...encrypt: " + Arrays.toString(encArgs));
|
||||||
int e = Guard.main(encArgs, new Options(), TestKeyringUnlocks.provider());
|
int e = Guard.main(encArgs, new Options(), TestKeyringUnlocks.provider());
|
||||||
assertEquals(0, e, "... encrypt expected exit code 0");
|
assertEquals(0, e, "... encrypt expected exit code 0");
|
||||||
|
|
||||||
// Decrypt (using password)
|
// Decrypt (using password)
|
||||||
String[] decArgs = { "--decrypt", enc.toString(), "--output", dec.toString(), "--password", password,
|
String[] decArgs = { "--decrypt", enc.toString(), "--output", dec.toString(), "--password", password,
|
||||||
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg",
|
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm",
|
||||||
"aes-gcm", "--tag-bits", Integer.toString(tagBits), "--aad-hex", aadHex };
|
"--tag-bits", Integer.toString(tagBits), "--aad-hex", aadHex };
|
||||||
System.out.println("...decrypt: " + Arrays.toString(decArgs));
|
System.out.println("...decrypt: " + Arrays.toString(decArgs));
|
||||||
int d = Guard.main(decArgs, new Options(), TestKeyringUnlocks.provider());
|
int d = Guard.main(decArgs, new Options(), TestKeyringUnlocks.provider());
|
||||||
assertEquals(0, d, "... decrypt expected exit code 0");
|
assertEquals(0, d, "... decrypt expected exit code 0");
|
||||||
@@ -163,10 +163,8 @@ public class GuardTest {
|
|||||||
System.out.println(method);
|
System.out.println(method);
|
||||||
Path input = writeRandom(tmp.resolve("invalid-kek.bin"), 32, 0x4B454B);
|
Path input = writeRandom(tmp.resolve("invalid-kek.bin"), 32, 0x4B454B);
|
||||||
Path output = tmp.resolve("invalid-kek.enc");
|
Path output = tmp.resolve("invalid-kek.enc");
|
||||||
String[] arguments = { "--encrypt", input.toString(), "--output", output.toString(),
|
String[] arguments = { "--encrypt", input.toString(), "--output", output.toString(), "--to-psw", "controlled",
|
||||||
"--to-psw", "controlled", "--to-kek-bytes", "24",
|
"--to-kek-bytes", "24", "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM,
|
||||||
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM,
|
|
||||||
"--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM,
|
|
||||||
"--alg", "aes-gcm" };
|
"--alg", "aes-gcm" };
|
||||||
|
|
||||||
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
|
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
|
||||||
@@ -290,8 +288,8 @@ public class GuardTest {
|
|||||||
// plus 2 random password decoys. Recipients are shuffled by default.
|
// plus 2 random password decoys. Recipients are shuffled by default.
|
||||||
String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--keyring", ring.toString(),
|
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",
|
"--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",
|
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm",
|
||||||
"aes-gcm", "--tag-bits", Integer.toString(tagBits), "--aad-hex", aad };
|
"--tag-bits", Integer.toString(tagBits), "--aad-hex", aad };
|
||||||
System.out.println("...encrypt: " + Arrays.toString(encArgs));
|
System.out.println("...encrypt: " + Arrays.toString(encArgs));
|
||||||
int e = Guard.main(encArgs, new Options(), TestKeyringUnlocks.provider());
|
int e = Guard.main(encArgs, new Options(), TestKeyringUnlocks.provider());
|
||||||
assertEquals(0, e, "... encrypt rc");
|
assertEquals(0, e, "... encrypt rc");
|
||||||
@@ -308,8 +306,8 @@ public class GuardTest {
|
|||||||
|
|
||||||
// Decrypt via password instead of key
|
// Decrypt via password instead of key
|
||||||
String[] decPwd = { "--decrypt", enc.toString(), "--output", dec2.toString(), "--password", password,
|
String[] decPwd = { "--decrypt", enc.toString(), "--output", dec2.toString(), "--password", password,
|
||||||
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg",
|
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm",
|
||||||
"aes-gcm", "--tag-bits", Integer.toString(tagBits), "--aad-hex", aad };
|
"--tag-bits", Integer.toString(tagBits), "--aad-hex", aad };
|
||||||
System.out.println("...decrypt(password): " + Arrays.toString(decPwd));
|
System.out.println("...decrypt(password): " + Arrays.toString(decPwd));
|
||||||
int d2 = Guard.main(decPwd, new Options(), TestKeyringUnlocks.provider());
|
int d2 = Guard.main(decPwd, new Options(), TestKeyringUnlocks.provider());
|
||||||
assertEquals(0, d2, "... decrypt(password) rc");
|
assertEquals(0, d2, "... decrypt(password) rc");
|
||||||
@@ -334,9 +332,9 @@ public class GuardTest {
|
|||||||
Path enc = tmp.resolve("pt-neg.bin.enc");
|
Path enc = tmp.resolve("pt-neg.bin.enc");
|
||||||
String pwd = "x";
|
String pwd = "x";
|
||||||
|
|
||||||
String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--to-psw", pwd,
|
String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--to-psw", pwd, "--pbkdf2-max",
|
||||||
"--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg",
|
TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm", "--tag-bits",
|
||||||
"aes-gcm", "--tag-bits", "128" };
|
"128" };
|
||||||
int e = Guard.main(encArgs, new Options(), TestKeyringUnlocks.provider());
|
int e = Guard.main(encArgs, new Options(), TestKeyringUnlocks.provider());
|
||||||
assertEquals(0, e, "... encrypt rc");
|
assertEquals(0, e, "... encrypt rc");
|
||||||
|
|
||||||
|
|||||||
@@ -165,8 +165,7 @@ public class KemTest {
|
|||||||
KeyAliases aliases = generateKemIntoKeyStore(ring, kemId, "alias-" + shortId(kemId));
|
KeyAliases aliases = generateKemIntoKeyStore(ring, kemId, "alias-" + shortId(kemId));
|
||||||
|
|
||||||
// Sanity: re-open to ensure the file is valid
|
// Sanity: re-open to ensure the file is valid
|
||||||
try (zeroecho.core.storage.KeyringPassword password =
|
try (zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire();
|
||||||
TestKeyringUnlocks.provider().acquire();
|
|
||||||
KeyringStore ks = KeyringStore.open(ring, password)) {
|
KeyringStore ks = KeyringStore.open(ring, password)) {
|
||||||
if (!(ks.contains(aliases.pub) && ks.contains(aliases.prv))) {
|
if (!(ks.contains(aliases.pub) && ks.contains(aliases.prv))) {
|
||||||
throw new IllegalStateException("Keyring does not contain expected aliases for " + kemId);
|
throw new IllegalStateException("Keyring does not contain expected aliases for " + kemId);
|
||||||
@@ -210,15 +209,15 @@ public class KemTest {
|
|||||||
Files.write(plain, content);
|
Files.write(plain, content);
|
||||||
System.out.println("...[" + kemId + "] ChaCha encrypt");
|
System.out.println("...[" + kemId + "] ChaCha encrypt");
|
||||||
int e = Kem.main(new String[] { "--encrypt", plain.toString(), "--output", enc.toString(),
|
int e = Kem.main(new String[] { "--encrypt", plain.toString(), "--output", enc.toString(),
|
||||||
"--keyring", ring.toString(), "--pub", aliases.pub, "--kem", kemId, "--chacha",
|
"--keyring", ring.toString(), "--pub", aliases.pub, "--kem", kemId, "--chacha", "--aad",
|
||||||
"--aad", aadChaCha, "--header" }, new Options(), TestKeyringUnlocks.provider());
|
aadChaCha, "--header" }, new Options(), TestKeyringUnlocks.provider());
|
||||||
if (e != 0) {
|
if (e != 0) {
|
||||||
throw new IllegalStateException("ChaCha encrypt rc=" + e);
|
throw new IllegalStateException("ChaCha encrypt rc=" + e);
|
||||||
}
|
}
|
||||||
System.out.println("...[" + kemId + "] ChaCha decrypt");
|
System.out.println("...[" + kemId + "] ChaCha decrypt");
|
||||||
int d = Kem.main(new String[] { "--decrypt", enc.toString(), "--output", dec.toString(),
|
int d = Kem.main(new String[] { "--decrypt", enc.toString(), "--output", dec.toString(),
|
||||||
"--keyring", ring.toString(), "--priv", aliases.prv, "--kem", kemId, "--chacha",
|
"--keyring", ring.toString(), "--priv", aliases.prv, "--kem", kemId, "--chacha", "--aad",
|
||||||
"--aad", aadChaCha, "--header" }, new Options(), TestKeyringUnlocks.provider());
|
aadChaCha, "--header" }, new Options(), TestKeyringUnlocks.provider());
|
||||||
if (d != 0) {
|
if (d != 0) {
|
||||||
throw new IllegalStateException("ChaCha decrypt rc=" + d);
|
throw new IllegalStateException("ChaCha decrypt rc=" + d);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,8 +139,7 @@ public class KeyStoreManagementTest {
|
|||||||
assertTrue(attempted > 0, "No generation attempts were successful");
|
assertTrue(attempted > 0, "No generation attempts were successful");
|
||||||
|
|
||||||
// Verify by reloading and materializing.
|
// Verify by reloading and materializing.
|
||||||
zeroecho.core.storage.KeyringPassword password =
|
zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire();
|
||||||
TestKeyringUnlocks.provider().acquire();
|
|
||||||
KeyringStore store;
|
KeyringStore store;
|
||||||
try {
|
try {
|
||||||
store = KeyringStore.open(ring, password);
|
store = KeyringStore.open(ring, password);
|
||||||
@@ -197,15 +196,13 @@ public class KeyStoreManagementTest {
|
|||||||
// ---- helpers ----
|
// ---- helpers ----
|
||||||
|
|
||||||
private static boolean hasAsymmetricDefault(CryptoAlgorithm alg) {
|
private static boolean hasAsymmetricDefault(CryptoAlgorithm alg) {
|
||||||
return alg.keyOperations().stream()
|
return alg.keyOperations().stream().anyMatch(
|
||||||
.anyMatch(info -> info.operation() == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE
|
info -> info.operation() == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE && info.defaultSpec() != null);
|
||||||
&& info.defaultSpec() != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean hasSymmetricDefault(CryptoAlgorithm alg) {
|
private static boolean hasSymmetricDefault(CryptoAlgorithm alg) {
|
||||||
return alg.keyOperations().stream()
|
return alg.keyOperations().stream()
|
||||||
.anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE
|
.anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE && info.defaultSpec() != null);
|
||||||
&& info.defaultSpec() != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String sanitize(String id) {
|
private static String sanitize(String id) {
|
||||||
|
|||||||
@@ -127,8 +127,7 @@ public class TagTest {
|
|||||||
Path ring = tmp.resolve("ring-ed25519.txt");
|
Path ring = tmp.resolve("ring-ed25519.txt");
|
||||||
KeyAliases ed = generateIntoKeyStore(ring, "Ed25519", "ed");
|
KeyAliases ed = generateIntoKeyStore(ring, "Ed25519", "ed");
|
||||||
// sanity
|
// sanity
|
||||||
try (zeroecho.core.storage.KeyringPassword password =
|
try (zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire();
|
||||||
TestKeyringUnlocks.provider().acquire();
|
|
||||||
KeyringStore ks = KeyringStore.open(ring, password)) {
|
KeyringStore ks = KeyringStore.open(ring, password)) {
|
||||||
assertTrue(ks.contains(ed.pub) && ks.contains(ed.prv), "missing expected aliases");
|
assertTrue(ks.contains(ed.pub) && ks.contains(ed.prv), "missing expected aliases");
|
||||||
}
|
}
|
||||||
@@ -200,12 +199,18 @@ public class TagTest {
|
|||||||
Files.write(plain, pt);
|
Files.write(plain, pt);
|
||||||
|
|
||||||
// produce
|
// produce
|
||||||
assertEquals(0, Tag.main(new String[] { "--type", "digest", "--mode", "produce", "--alg", "SHA-256", "--in",
|
assertEquals(0,
|
||||||
plain.toString(), "--out", tagged.toString() }, new Options(), TestKeyringUnlocks.provider()));
|
Tag.main(
|
||||||
|
new String[] { "--type", "digest", "--mode", "produce", "--alg", "SHA-256", "--in",
|
||||||
|
plain.toString(), "--out", tagged.toString() },
|
||||||
|
new Options(), TestKeyringUnlocks.provider()));
|
||||||
|
|
||||||
// verify (match)
|
// verify (match)
|
||||||
assertEquals(0, Tag.main(new String[] { "--type", "digest", "--mode", "verify", "--alg", "SHA-256", "--in",
|
assertEquals(0,
|
||||||
tagged.toString(), "--out", recovered.toString() }, new Options(), TestKeyringUnlocks.provider()));
|
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");
|
assertArrayEquals(pt, Files.readAllBytes(recovered), "digest round-trip mismatch");
|
||||||
|
|
||||||
@@ -223,15 +228,21 @@ public class TagTest {
|
|||||||
Files.write(plain, pt);
|
Files.write(plain, pt);
|
||||||
|
|
||||||
// produce
|
// produce
|
||||||
assertEquals(0, Tag.main(new String[] { "--type", "digest", "--mode", "produce", "--alg", "SHA-256", "--in",
|
assertEquals(0,
|
||||||
plain.toString(), "--out", tagged.toString() }, new Options(), TestKeyringUnlocks.provider()));
|
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
|
// corrupt last byte -> break digest
|
||||||
flipLastByte(tagged);
|
flipLastByte(tagged);
|
||||||
|
|
||||||
// verify (mismatch): expect throw + default marker ("digest invalid")
|
// verify (mismatch): expect throw + default marker ("digest invalid")
|
||||||
assertEquals(1, Tag.main(new String[] { "--type", "digest", "--mode", "verify", "--alg", "SHA-256", "--in",
|
assertEquals(1,
|
||||||
tagged.toString(), "--out", out.toString() }, new Options(), TestKeyringUnlocks.provider()));
|
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));
|
assertTrue(Files.notExists(out, LinkOption.NOFOLLOW_LINKS));
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ final class TestKeyringUnlocks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static KeyringUnlockProvider provider() {
|
static KeyringUnlockProvider provider() {
|
||||||
return () -> new KeyringPassword(
|
return () -> new KeyringPassword(new char[] { 't', 'e', 's', 't', '-', 'k', 'e', 'y', 'r', 'i', 'n', 'g' });
|
||||||
new char[] { 't', 'e', 's', 't', '-', 'k', 'e', 'y', 'r', 'i', 'n', 'g' });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,17 +91,17 @@ class JpegExifIntegrationTest {
|
|||||||
|
|
||||||
// AES encryption setup
|
// AES encryption setup
|
||||||
/*
|
/*
|
||||||
* SecretKey key = zeroEchoSession.keyBuilders().symmetric()
|
* SecretKey key = zeroEchoSession.keyBuilders().symmetric() .generate("AES",
|
||||||
* .generate("AES", AesKeyGenSpec.aes256()); AesSpec spec =
|
* AesKeyGenSpec.aes256()); AesSpec spec =
|
||||||
* AesSpec.builder().mode(Mode.GCM).tagLenBits(128).header(null).build();
|
* AesSpec.builder().mode(Mode.GCM).tagLenBits(128).header(null).build();
|
||||||
* EncryptionContext enc = zeroEchoSession.createContext("AES", KeyUsage.ENCRYPT, key,
|
* EncryptionContext enc = zeroEchoSession.createContext("AES",
|
||||||
* spec); CtxInterface session = Ctx.INSTANCE.getContext("aes-ctx-" +
|
* KeyUsage.ENCRYPT, key, spec); CtxInterface session =
|
||||||
* System.nanoTime()); session.put(ConfluxKeys.aad("AES"), aad); ((ContextAware)
|
* Ctx.INSTANCE.getContext("aes-ctx-" + System.nanoTime());
|
||||||
|
* session.put(ConfluxKeys.aad("AES"), aad); ((ContextAware)
|
||||||
* enc).setContext(session);
|
* enc).setContext(session);
|
||||||
*/
|
*/
|
||||||
ZeroEchoSession zeroEchoSession = new ZeroEchoSession();
|
ZeroEchoSession zeroEchoSession = new ZeroEchoSession();
|
||||||
SecretKey key = zeroEchoSession.keyBuilders().symmetric()
|
SecretKey key = zeroEchoSession.keyBuilders().symmetric().generate("AES", AesKeyGenSpec.aes256());
|
||||||
.generate("AES", AesKeyGenSpec.aes256());
|
|
||||||
CtxInterface session = Ctx.INSTANCE.getContext("aes-ctx-" + System.nanoTime());
|
CtxInterface session = Ctx.INSTANCE.getContext("aes-ctx-" + System.nanoTime());
|
||||||
|
|
||||||
byte[] encryptedBytes;
|
byte[] encryptedBytes;
|
||||||
@@ -152,7 +152,8 @@ class JpegExifIntegrationTest {
|
|||||||
// input
|
// input
|
||||||
.add(PlainBytesBuilder.builder().bytes(extractedEncryptedBytes))
|
.add(PlainBytesBuilder.builder().bytes(extractedEncryptedBytes))
|
||||||
// encryption
|
// 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
|
// let us use the default header for AAD and IV
|
||||||
.withHeader().withAad(aad).context(session))
|
.withHeader().withAad(aad).context(session))
|
||||||
// and create the pipeline
|
// and create the pipeline
|
||||||
@@ -164,9 +165,9 @@ class JpegExifIntegrationTest {
|
|||||||
/*
|
/*
|
||||||
* AesSpec spec =
|
* AesSpec spec =
|
||||||
* AesSpec.builder().mode(Mode.GCM).tagLenBits(128).header(null).build();
|
* AesSpec.builder().mode(Mode.GCM).tagLenBits(128).header(null).build();
|
||||||
* EncryptionContext dec1 = zeroEchoSession.createContext("AES", KeyUsage.DECRYPT,
|
* EncryptionContext dec1 = zeroEchoSession.createContext("AES",
|
||||||
* key, spec); ((ContextAware) dec1).setContext(session); // same IV/AAD in ctx
|
* KeyUsage.DECRYPT, key, spec); ((ContextAware) dec1).setContext(session); //
|
||||||
* byte[] pt1 = readAll(dec1.attach(new
|
* same IV/AAD in ctx byte[] pt1 = readAll(dec1.attach(new
|
||||||
* ByteArrayInputStream(extractedEncryptedBytes))); dec1.close();
|
* ByteArrayInputStream(extractedEncryptedBytes))); dec1.close();
|
||||||
*/
|
*/
|
||||||
String decrypted = new String(pt1, StandardCharsets.UTF_8);
|
String decrypted = new String(pt1, StandardCharsets.UTF_8);
|
||||||
|
|||||||
@@ -16,29 +16,31 @@ import zeroecho.core.spec.ContextSpec;
|
|||||||
/**
|
/**
|
||||||
* Immutable value descriptor of one algorithm context capability.
|
* Immutable value descriptor of one algorithm context capability.
|
||||||
*
|
*
|
||||||
* <p>The default specification is resolved once during provider construction.
|
* <p>
|
||||||
* All components therefore have stable value semantics and are safe for
|
* The default specification is resolved once during provider construction. All
|
||||||
* concurrent reads.</p>
|
* components therefore have stable value semantics and are safe for concurrent
|
||||||
|
* reads.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @param algorithmId canonical algorithm identifier
|
* @param algorithmId canonical algorithm identifier
|
||||||
* @param family algorithm family
|
* @param family algorithm family
|
||||||
* @param role supported key usage
|
* @param role supported key usage
|
||||||
* @param contextType produced context type
|
* @param contextType produced context type
|
||||||
* @param keyType accepted key type
|
* @param keyType accepted key type
|
||||||
* @param specType accepted specification type
|
* @param specType accepted specification type
|
||||||
* @param defaultSpec non-null resolved default specification
|
* @param defaultSpec non-null resolved default specification
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
public record Capability(String algorithmId, AlgorithmFamily family, KeyUsage role,
|
public record Capability(String algorithmId, AlgorithmFamily family, KeyUsage role,
|
||||||
Class<? extends CryptoContext> contextType, Class<? extends Key> keyType,
|
Class<? extends CryptoContext> contextType, Class<? extends Key> keyType, Class<? extends ContextSpec> specType,
|
||||||
Class<? extends ContextSpec> specType, ContextSpec defaultSpec) {
|
ContextSpec defaultSpec) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the capability metadata.
|
* Validates the capability metadata.
|
||||||
*
|
*
|
||||||
* @throws NullPointerException if a component is {@code null}
|
* @throws NullPointerException if a component is {@code null}
|
||||||
* @throws IllegalArgumentException if {@code defaultSpec} is incompatible
|
* @throws IllegalArgumentException if {@code defaultSpec} is incompatible with
|
||||||
* with {@code specType}
|
* {@code specType}
|
||||||
*/
|
*/
|
||||||
public Capability {
|
public Capability {
|
||||||
Objects.requireNonNull(algorithmId, "algorithmId must not be null");
|
Objects.requireNonNull(algorithmId, "algorithmId must not be null");
|
||||||
|
|||||||
@@ -106,8 +106,8 @@ import zeroecho.core.spi.SymmetricKeyImporter;
|
|||||||
* <p>
|
* <p>
|
||||||
* <b>Security note:</b> Algorithms must enforce strong validation of keys and
|
* <b>Security note:</b> Algorithms must enforce strong validation of keys and
|
||||||
* specs during registration and
|
* specs during registration and
|
||||||
* {@link #createContext(KeyUsage, Key, ContextSpec)} to
|
* {@link #createContext(KeyUsage, Key, ContextSpec)} to prevent downgrade or
|
||||||
* prevent downgrade or misuse attacks.
|
* misuse attacks.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
@@ -123,16 +123,11 @@ public abstract class CryptoAlgorithm { // NOPMD
|
|||||||
|
|
||||||
private final List<Capability> capabilities = new ArrayList<>();
|
private final List<Capability> capabilities = new ArrayList<>();
|
||||||
private final Map<KeyUsage, List<RoleBinding<?, ?, ?>>> ctxBindings = new EnumMap<>(KeyUsage.class);
|
private final Map<KeyUsage, List<RoleBinding<?, ?, ?>>> ctxBindings = new EnumMap<>(KeyUsage.class);
|
||||||
private final Map<Class<? extends AlgorithmKeySpec>, AsymmetricKeyPairGenerator<?>> keyPairGenerators =
|
private final Map<Class<? extends AlgorithmKeySpec>, AsymmetricKeyPairGenerator<?>> keyPairGenerators = new LinkedHashMap<>();
|
||||||
new LinkedHashMap<>();
|
private final Map<Class<? extends AlgorithmKeySpec>, PublicKeyImporter<?>> publicKeyImporters = new LinkedHashMap<>();
|
||||||
private final Map<Class<? extends AlgorithmKeySpec>, PublicKeyImporter<?>> publicKeyImporters =
|
private final Map<Class<? extends AlgorithmKeySpec>, PrivateKeyImporter<?>> privateKeyImporters = new LinkedHashMap<>();
|
||||||
new LinkedHashMap<>();
|
private final Map<Class<? extends AlgorithmKeySpec>, SymmetricKeyGenerator<?>> symmetricKeyGenerators = new LinkedHashMap<>();
|
||||||
private final Map<Class<? extends AlgorithmKeySpec>, PrivateKeyImporter<?>> privateKeyImporters =
|
private final Map<Class<? extends AlgorithmKeySpec>, SymmetricKeyImporter<?>> symmetricKeyImporters = new LinkedHashMap<>();
|
||||||
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> asymmetricDefaults = new LinkedHashMap<>();
|
||||||
private final Map<Class<? extends AlgorithmKeySpec>, AlgorithmKeySpec> symmetricDefaults = new LinkedHashMap<>();
|
private final Map<Class<? extends AlgorithmKeySpec>, AlgorithmKeySpec> symmetricDefaults = new LinkedHashMap<>();
|
||||||
|
|
||||||
@@ -296,8 +291,9 @@ public abstract class CryptoAlgorithm { // NOPMD
|
|||||||
* <p>
|
* <p>
|
||||||
* Concrete algorithms call this during construction to declare support for
|
* Concrete algorithms call this during construction to declare support for
|
||||||
* specific roles (e.g., {@code ENCRYPT}, {@code VERIFY}). When
|
* specific roles (e.g., {@code ENCRYPT}, {@code VERIFY}). When
|
||||||
* {@link #createContext(KeyUsage, Key, ContextSpec)} is later invoked, the provided
|
* {@link #createContext(KeyUsage, Key, ContextSpec)} is later invoked, the
|
||||||
* {@code key} and optional {@code spec} are matched against these bindings.
|
* provided {@code key} and optional {@code spec} are matched against these
|
||||||
|
* bindings.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param role supported {@link KeyUsage} role
|
* @param role supported {@link KeyUsage} role
|
||||||
@@ -397,8 +393,7 @@ public abstract class CryptoAlgorithm { // NOPMD
|
|||||||
if (rb.accepts(key, spec)) {
|
if (rb.accepts(key, spec)) {
|
||||||
S resolved = (spec != null) ? spec
|
S resolved = (spec != null) ? spec
|
||||||
: Objects.requireNonNull(rb.defaultSpec.get(), "defaultSpec value must not be null");
|
: Objects.requireNonNull(rb.defaultSpec.get(), "defaultSpec value must not be null");
|
||||||
C ctx = Objects.requireNonNull(rb.factory.createContext(key, resolved),
|
C ctx = Objects.requireNonNull(rb.factory.createContext(key, resolved), _id + " factory returned null");
|
||||||
_id + " factory returned null");
|
|
||||||
// Enforce the declared context type contract:
|
// Enforce the declared context type contract:
|
||||||
if (!rb.ctxType.isInstance(ctx)) {
|
if (!rb.ctxType.isInstance(ctx)) {
|
||||||
throw new IllegalStateException(_id + " factory returned " + ctx.getClass().getName()
|
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()));
|
+ (spec == null ? " (default spec)" : " and spec=" + spec.getClass().getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private <S extends AlgorithmKeySpec> S resolveDefault(Class<S> specType,
|
private <S extends AlgorithmKeySpec> S resolveDefault(Class<S> specType, Supplier<? extends S> defaultSpecOrNull) {
|
||||||
Supplier<? extends S> defaultSpecOrNull) {
|
|
||||||
if (defaultSpecOrNull == null) {
|
if (defaultSpecOrNull == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -426,16 +420,18 @@ public abstract class CryptoAlgorithm { // NOPMD
|
|||||||
/**
|
/**
|
||||||
* Registers asymmetric key-pair generation for one exact specification class.
|
* 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
|
* Registered generators must be safe for concurrent invocation after the
|
||||||
* algorithm is published.</p>
|
* algorithm is published.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @param specType exact specification class
|
* @param specType exact specification class
|
||||||
* @param generator non-null generator
|
* @param generator non-null generator
|
||||||
* @param defaultSpecOrNull optional default supplier, evaluated once
|
* @param defaultSpecOrNull optional default supplier, evaluated once
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @throws NullPointerException if a required argument or supplied default is
|
* @throws NullPointerException if a required argument or supplied default
|
||||||
* {@code null}
|
* is {@code null}
|
||||||
* @throws IllegalArgumentException if the supplied default has the wrong type
|
* @throws IllegalArgumentException if the supplied default has the wrong type
|
||||||
*/
|
*/
|
||||||
protected final <S extends AlgorithmKeySpec> void registerAsymmetricKeyPairGenerator(Class<S> specType,
|
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 specType exact specification class
|
||||||
* @param importer non-null importer safe for concurrent invocation
|
* @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}
|
* @throws NullPointerException if an argument is {@code null}
|
||||||
*/
|
*/
|
||||||
protected final <S extends AlgorithmKeySpec> void registerPublicKeyImporter(Class<S> specType,
|
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 specType exact specification class
|
||||||
* @param importer non-null importer safe for concurrent invocation
|
* @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}
|
* @throws NullPointerException if an argument is {@code null}
|
||||||
*/
|
*/
|
||||||
protected final <S extends AlgorithmKeySpec> void registerPrivateKeyImporter(Class<S> specType,
|
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.
|
* 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 specType exact specification class
|
||||||
* @param generator non-null generator safe for concurrent invocation
|
* @param generator non-null generator safe for concurrent invocation
|
||||||
* @param defaultSpecOrNull optional default supplier, evaluated once
|
* @param defaultSpecOrNull optional default supplier, evaluated once
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @throws NullPointerException if a required argument or supplied default is
|
* @throws NullPointerException if a required argument or supplied default
|
||||||
* {@code null}
|
* is {@code null}
|
||||||
* @throws IllegalArgumentException if the supplied default has the wrong type
|
* @throws IllegalArgumentException if the supplied default has the wrong type
|
||||||
*/
|
*/
|
||||||
protected final <S extends AlgorithmKeySpec> void registerSymmetricKeyGenerator(Class<S> specType,
|
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 specType exact specification class
|
||||||
* @param importer non-null importer safe for concurrent invocation
|
* @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}
|
* @throws NullPointerException if an argument is {@code null}
|
||||||
*/
|
*/
|
||||||
protected final <S extends AlgorithmKeySpec> void registerSymmetricKeyImporter(Class<S> specType,
|
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
|
* Returns the asymmetric key-pair generator registered for an exact
|
||||||
* specification class.
|
* 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 specType exact specification class; subclasses are not matched
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return registered generator
|
* @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
|
* @throws IllegalArgumentException if no generator is registered
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -537,12 +537,14 @@ public abstract class CryptoAlgorithm { // NOPMD
|
|||||||
/**
|
/**
|
||||||
* Returns the public-key importer registered for an exact specification class.
|
* 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 specType exact specification class; subclasses are not matched
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return registered importer
|
* @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
|
* @throws IllegalArgumentException if no importer is registered
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -556,15 +558,16 @@ public abstract class CryptoAlgorithm { // NOPMD
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the private-key importer registered for an exact specification
|
* Returns the private-key importer registered for an exact specification class.
|
||||||
* 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 specType exact specification class; subclasses are not matched
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return registered importer
|
* @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
|
* @throws IllegalArgumentException if no importer is registered
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -581,12 +584,14 @@ public abstract class CryptoAlgorithm { // NOPMD
|
|||||||
* Returns the symmetric-key generator registered for an exact specification
|
* Returns the symmetric-key generator registered for an exact specification
|
||||||
* class.
|
* 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 specType exact specification class; subclasses are not matched
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return registered generator
|
* @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
|
* @throws IllegalArgumentException if no generator is registered
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -603,12 +608,14 @@ public abstract class CryptoAlgorithm { // NOPMD
|
|||||||
* Returns the symmetric-key importer registered for an exact specification
|
* Returns the symmetric-key importer registered for an exact specification
|
||||||
* class.
|
* 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 specType exact specification class; subclasses are not matched
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return registered importer
|
* @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
|
* @throws IllegalArgumentException if no importer is registered
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -628,14 +635,12 @@ public abstract class CryptoAlgorithm { // NOPMD
|
|||||||
*/
|
*/
|
||||||
public final List<KeyOperationInfo> keyOperations() {
|
public final List<KeyOperationInfo> keyOperations() {
|
||||||
List<KeyOperationInfo> result = new ArrayList<>();
|
List<KeyOperationInfo> result = new ArrayList<>();
|
||||||
addOperationInfo(result, KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE, keyPairGenerators,
|
addOperationInfo(result, KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE, keyPairGenerators, asymmetricDefaults);
|
||||||
asymmetricDefaults);
|
|
||||||
addOperationInfo(result, KeyOperation.ASYMMETRIC_PUBLIC_IMPORT, publicKeyImporters, Map.of());
|
addOperationInfo(result, KeyOperation.ASYMMETRIC_PUBLIC_IMPORT, publicKeyImporters, Map.of());
|
||||||
addOperationInfo(result, KeyOperation.ASYMMETRIC_PRIVATE_IMPORT, privateKeyImporters, Map.of());
|
addOperationInfo(result, KeyOperation.ASYMMETRIC_PRIVATE_IMPORT, privateKeyImporters, Map.of());
|
||||||
addOperationInfo(result, KeyOperation.SYMMETRIC_GENERATE, symmetricKeyGenerators, symmetricDefaults);
|
addOperationInfo(result, KeyOperation.SYMMETRIC_GENERATE, symmetricKeyGenerators, symmetricDefaults);
|
||||||
addOperationInfo(result, KeyOperation.SYMMETRIC_IMPORT, symmetricKeyImporters, Map.of());
|
addOperationInfo(result, KeyOperation.SYMMETRIC_IMPORT, symmetricKeyImporters, Map.of());
|
||||||
result.sort(Comparator.comparing(KeyOperationInfo::operation)
|
result.sort(Comparator.comparing(KeyOperationInfo::operation).thenComparing(info -> info.specType().getName()));
|
||||||
.thenComparing(info -> info.specType().getName()));
|
|
||||||
return List.copyOf(result);
|
return List.copyOf(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ import java.util.TreeMap;
|
|||||||
/**
|
/**
|
||||||
* Immutable registry of {@link CryptoAlgorithm} providers.
|
* 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.
|
* canonical algorithm identifier, and retained in one immutable registry.
|
||||||
* Runtime policy and auditing belong exclusively to explicitly created
|
* Runtime policy and auditing belong exclusively to explicitly created
|
||||||
* {@link zeroecho.sdk.ZeroEchoSession} instances.</p>
|
* {@link zeroecho.sdk.ZeroEchoSession} instances.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ public final class CryptoCatalog {
|
|||||||
* {@link CryptoAlgorithms}.
|
* {@link CryptoAlgorithms}.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Provider discovery, deterministic ordering, and duplicate checking occur
|
* Provider discovery, deterministic ordering, and duplicate checking occur once
|
||||||
* once in {@code CryptoAlgorithms}. This method neither scans providers nor
|
* in {@code CryptoAlgorithms}. This method neither scans providers nor copies
|
||||||
* copies their collection.
|
* their collection.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @return an immutable {@code CryptoCatalog} with all discovered algorithms
|
* @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.
|
* Serializes the catalog to a compact JSON document.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p> The schema is: </p> <pre>{@code { "algorithms": [ { "id": "AES/GCM",
|
||||||
* The schema is:
|
* "displayName": "AES-GCM", "capabilities": [ { "family": "SYMMETRIC", "role":
|
||||||
* </p>
|
* "ENCRYPT", "contextType": "AeadEncryptContext", "keyType": "SecretKey",
|
||||||
* <pre>{@code
|
* "specType": "AeadSpec", "defaultSpec": "Random nonce, 128-bit tag" } ],
|
||||||
* {
|
* "asymmetricKeyBuilders": [ { "specType": "Ed25519Spec", "defaultKeySpec":
|
||||||
* "algorithms": [
|
* "Ed25519 default" } ], "symmetricKeyBuilders": [ { "specType": "AesKeySpec",
|
||||||
* {
|
* "defaultKeySpec": "AES-256" } ] } ] } }</pre>
|
||||||
* "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>
|
* <p> String values are escaped for quotes and backslashes. The method does not
|
||||||
* String values are escaped for quotes and backslashes. The method does not
|
* attempt to pretty-print; callers can format the output if needed. </p>
|
||||||
* attempt to pretty-print; callers can format the output if needed.
|
|
||||||
* </p>
|
|
||||||
*
|
*
|
||||||
* @return a JSON string describing algorithms, capabilities, and key builders
|
* @return a JSON string describing algorithms, capabilities, and key builders
|
||||||
*/
|
*/
|
||||||
@@ -230,8 +206,7 @@ public final class CryptoCatalog {
|
|||||||
}
|
}
|
||||||
firstOperation = false;
|
firstOperation = false;
|
||||||
sb.append('{').append(jsonField("operation", operation.operation().name())).append(',')
|
sb.append('{').append(jsonField("operation", operation.operation().name())).append(',')
|
||||||
.append(jsonField("specType", operation.specType().getSimpleName()))
|
.append(jsonField("specType", operation.specType().getSimpleName())).append(",\"defaultSpec\":")
|
||||||
.append(",\"defaultSpec\":")
|
|
||||||
.append(operation.defaultSpec() == null ? "null" : jsonString(labelOf(operation.defaultSpec())))
|
.append(operation.defaultSpec() == null ? "null" : jsonString(labelOf(operation.defaultSpec())))
|
||||||
.append('}');
|
.append('}');
|
||||||
}
|
}
|
||||||
@@ -273,9 +248,8 @@ public final class CryptoCatalog {
|
|||||||
}
|
}
|
||||||
sb.append("</capabilities><keyOperations>");
|
sb.append("</capabilities><keyOperations>");
|
||||||
for (KeyOperationInfo operation : a.keyOperations()) {
|
for (KeyOperationInfo operation : a.keyOperations()) {
|
||||||
sb.append("<keyOperation operation=\"").append(operation.operation().name())
|
sb.append("<keyOperation operation=\"").append(operation.operation().name()).append("\" specType=\"")
|
||||||
.append("\" specType=\"").append(esc(operation.specType().getSimpleName()))
|
.append(esc(operation.specType().getSimpleName())).append("\"><defaultSpec>")
|
||||||
.append("\"><defaultSpec>")
|
|
||||||
.append(operation.defaultSpec() == null ? "" : esc(labelOf(operation.defaultSpec())))
|
.append(operation.defaultSpec() == null ? "" : esc(labelOf(operation.defaultSpec())))
|
||||||
.append("</defaultSpec></keyOperation>");
|
.append("</defaultSpec></keyOperation>");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,20 +14,20 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
|||||||
/**
|
/**
|
||||||
* Immutable metadata for one exact key operation.
|
* Immutable metadata for one exact key operation.
|
||||||
*
|
*
|
||||||
* @param operation operation guaranteed by the associated lookup
|
* @param operation operation guaranteed by the associated lookup
|
||||||
* @param specType exact accepted specification type
|
* @param specType exact accepted specification type
|
||||||
* @param defaultSpec resolved generation default, or {@code null} for import
|
* @param defaultSpec resolved generation default, or {@code null} for import
|
||||||
* operations and generators without a default
|
* operations and generators without a default
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
public record KeyOperationInfo(KeyOperation operation,
|
public record KeyOperationInfo(KeyOperation operation, Class<? extends AlgorithmKeySpec> specType,
|
||||||
Class<? extends AlgorithmKeySpec> specType, AlgorithmKeySpec defaultSpec) {
|
AlgorithmKeySpec defaultSpec) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the metadata invariant.
|
* Validates the metadata invariant.
|
||||||
*
|
*
|
||||||
* @throws NullPointerException if {@code operation} or {@code specType} is
|
* @throws NullPointerException if {@code operation} or {@code specType} is
|
||||||
* {@code null}
|
* {@code null}
|
||||||
* @throws IllegalArgumentException if a default is incompatible with
|
* @throws IllegalArgumentException if a default is incompatible with
|
||||||
* {@code specType}, or an import operation
|
* {@code specType}, or an import operation
|
||||||
* declares a default
|
* declares a default
|
||||||
@@ -38,9 +38,9 @@ public record KeyOperationInfo(KeyOperation operation,
|
|||||||
if (defaultSpec != null && !specType.isInstance(defaultSpec)) {
|
if (defaultSpec != null && !specType.isInstance(defaultSpec)) {
|
||||||
throw new IllegalArgumentException("defaultSpec must be an instance of " + specType.getName());
|
throw new IllegalArgumentException("defaultSpec must be an instance of " + specType.getName());
|
||||||
}
|
}
|
||||||
if (defaultSpec != null && (operation == KeyOperation.SYMMETRIC_IMPORT
|
if (defaultSpec != null
|
||||||
|| operation == KeyOperation.ASYMMETRIC_PUBLIC_IMPORT
|
&& (operation == KeyOperation.SYMMETRIC_IMPORT || operation == KeyOperation.ASYMMETRIC_PUBLIC_IMPORT
|
||||||
|| operation == KeyOperation.ASYMMETRIC_PRIVATE_IMPORT)) {
|
|| operation == KeyOperation.ASYMMETRIC_PRIVATE_IMPORT)) {
|
||||||
throw new IllegalArgumentException("import operations cannot declare a default specification");
|
throw new IllegalArgumentException("import operations cannot declare a default specification");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,8 +147,8 @@ public abstract class AbstractCryptoAlgorithm extends CryptoAlgorithm {
|
|||||||
*
|
*
|
||||||
* <h4>Validation</h4> Type checks happen at creation time (via {@code bind})
|
* <h4>Validation</h4> Type checks happen at creation time (via {@code bind})
|
||||||
* and again when
|
* and again when
|
||||||
* {@link CryptoAlgorithm#createContext(KeyUsage, Key, ContextSpec)} is
|
* {@link CryptoAlgorithm#createContext(KeyUsage, Key, ContextSpec)} is called.
|
||||||
* called. If a factory returns a context not assignable to {@code ctxType}, an
|
* If a factory returns a context not assignable to {@code ctxType}, an
|
||||||
* {@link IllegalStateException} will be thrown.
|
* {@link IllegalStateException} will be thrown.
|
||||||
*
|
*
|
||||||
* @param family high-level algorithm family classification
|
* @param family high-level algorithm family classification
|
||||||
|
|||||||
@@ -63,9 +63,10 @@ import zeroecho.core.util.RandomSupport;
|
|||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* IV and optional AAD are exchanged via a {@link conflux.CtxInterface} set with
|
* IV and optional AAD are exchanged via a {@link conflux.CtxInterface} set with
|
||||||
* {@link #setContext(conflux.CtxInterface)}. Encryption always generates a fresh
|
* {@link #setContext(conflux.CtxInterface)}. Encryption always generates a
|
||||||
* IV after atomically claiming the context; a caller-provided IV is never used
|
* fresh IV after atomically claiming the context; a caller-provided IV is never
|
||||||
* for encryption. Decryption requires the IV from the context or encoded header.
|
* used for encryption. Decryption requires the IV from the context or encoded
|
||||||
|
* header.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
@@ -311,10 +312,7 @@ public final class AesCipherContext implements EncryptionContext, ContextAware {
|
|||||||
|
|
||||||
/** Single-use encryption lifecycle states. */
|
/** Single-use encryption lifecycle states. */
|
||||||
private enum OperationState {
|
private enum OperationState {
|
||||||
NEW,
|
NEW, ENCRYPTING, COMPLETED, FAILED
|
||||||
ENCRYPTING,
|
|
||||||
COMPLETED,
|
|
||||||
FAILED
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Marks the owning encryption context terminal as its stream is consumed. */
|
/** Marks the owning encryption context terminal as its stream is consumed. */
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Objects of this type are thread-safe while active and may be destroyed to wipe
|
* Objects of this type are thread-safe while active and may be destroyed to
|
||||||
* their owned key bytes. Access and marshalling fail after destruction.
|
* wipe their owned key bytes. Access and marshalling fail after destruction.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
|
|||||||
@@ -243,8 +243,8 @@ abstract class AbstractChaChaCipherContext<S extends ChaChaBaseSpec> implements
|
|||||||
* Ensures a nonce is available in the context.
|
* Ensures a nonce is available in the context.
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>For encryption, always generates a new nonce after the context has
|
* <li>For encryption, always generates a new nonce after the context has been
|
||||||
* been atomically claimed and stores a copy in the context.</li>
|
* atomically claimed and stores a copy in the context.</li>
|
||||||
* <li>For decryption, validates presence and correct length.</li>
|
* <li>For decryption, validates presence and correct length.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
@@ -283,10 +283,7 @@ abstract class AbstractChaChaCipherContext<S extends ChaChaBaseSpec> implements
|
|||||||
|
|
||||||
/** Single-use encryption lifecycle states. */
|
/** Single-use encryption lifecycle states. */
|
||||||
private enum OperationState {
|
private enum OperationState {
|
||||||
NEW,
|
NEW, ENCRYPTING, COMPLETED, FAILED
|
||||||
ENCRYPTING,
|
|
||||||
COMPLETED,
|
|
||||||
FAILED
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Marks the owning encryption context terminal as its stream is consumed. */
|
/** Marks the owning encryption context terminal as its stream is consumed. */
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
package zeroecho.core.alg.chacha;
|
package zeroecho.core.alg.chacha;
|
||||||
|
|
||||||
import zeroecho.core.util.RandomSupport;
|
import zeroecho.core.util.RandomSupport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <h2>ChaCha20 (stream) algorithm</h2>
|
* <h2>ChaCha20 (stream) algorithm</h2>
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -39,10 +39,10 @@
|
|||||||
* the stream cipher ChaCha20 and the AEAD construction ChaCha20-Poly1305. The
|
* the stream cipher ChaCha20 and the AEAD construction ChaCha20-Poly1305. The
|
||||||
* module contains algorithm descriptors, streaming cipher contexts,
|
* module contains algorithm descriptors, streaming cipher contexts,
|
||||||
* configuration specifications, optional header codecs for runtime parameters,
|
* configuration specifications, optional header codecs for runtime parameters,
|
||||||
* and symmetric key import/generation specifications. Key import
|
* and symmetric key import/generation specifications. Key import specifications
|
||||||
* specifications are destroyable. The design favors safe defaults
|
* are destroyable. The design favors safe defaults (12-byte nonces, 128-bit
|
||||||
* (12-byte nonces, 128-bit AEAD tag), explicit role-to-context binding, and a
|
* AEAD tag), explicit role-to-context binding, and a clear separation between
|
||||||
* clear separation between static configuration and per-operation parameters.
|
* static configuration and per-operation parameters.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Components</h2>
|
* <h2>Components</h2>
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ import zeroecho.core.context.AgreementContext;
|
|||||||
* <h2>Generic JCA-based Key Agreement Context</h2>
|
* <h2>Generic JCA-based Key Agreement Context</h2>
|
||||||
*
|
*
|
||||||
* An {@link AgreementContext} backed by the standard JCA key-agreement API.
|
* An {@link AgreementContext} backed by the standard JCA key-agreement API.
|
||||||
* This class supports elliptic-curve and modern Diffie-Hellman variants
|
* This class supports elliptic-curve and modern Diffie-Hellman variants such as
|
||||||
* such as ECDH, XDH (X25519, X448), and others provided by the runtime or
|
* ECDH, XDH (X25519, X448), and others provided by the runtime or configured
|
||||||
* configured provider.
|
* provider.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Instances of this context are created with a local {@link PrivateKey}, and
|
* Instances of this context are created with a local {@link PrivateKey}, and
|
||||||
|
|||||||
@@ -99,9 +99,9 @@
|
|||||||
* reconstructs public keys from X.509 encodings via
|
* reconstructs public keys from X.509 encodings via
|
||||||
* {@link java.security.KeyFactory}.</li>
|
* {@link java.security.KeyFactory}.</li>
|
||||||
* <li><b>Signature contexts:</b>
|
* <li><b>Signature contexts:</b>
|
||||||
* {@link zeroecho.core.alg.common.eddsa.CommonEdDSASignatureContext}
|
* {@link zeroecho.core.alg.common.eddsa.CommonEdDSASignatureContext} delegates
|
||||||
* delegates all operations to a generic JCA-backed signature adapter, enforcing
|
* all operations to a generic JCA-backed signature adapter, enforcing a fixed
|
||||||
* a fixed tag length for the selected EdDSA variant.</li>
|
* tag length for the selected EdDSA variant.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h2>Design notes</h2>
|
* <h2>Design notes</h2>
|
||||||
|
|||||||
@@ -116,7 +116,8 @@ public final class SignatureInteropProfile { // NOPMD
|
|||||||
* @param keyAlgorithmId ZeroEcho key algorithm identifier used for key
|
* @param keyAlgorithmId ZeroEcho key algorithm identifier used for key
|
||||||
* import and matching, such as {@code RSA}
|
* import and matching, such as {@code RSA}
|
||||||
* @param contextAlgorithmId ZeroEcho context algorithm identifier used
|
* @param contextAlgorithmId ZeroEcho context algorithm identifier used
|
||||||
* with {@code ZeroEchoSession.createContext(...)}
|
* with
|
||||||
|
* {@code ZeroEchoSession.createContext(...)}
|
||||||
* @param contextSpec explicit ZeroEcho context specification
|
* @param contextSpec explicit ZeroEcho context specification
|
||||||
* @param signatureRepresentation signature representation bridge between
|
* @param signatureRepresentation signature representation bridge between
|
||||||
* external bytes and internal ZeroEcho bytes
|
* external bytes and internal ZeroEcho bytes
|
||||||
|
|||||||
@@ -60,9 +60,9 @@
|
|||||||
* configured {@link java.security.Signature}, resolves a fixed tag length (via
|
* configured {@link java.security.Signature}, resolves a fixed tag length (via
|
||||||
* resolvers), and exposes a one-shot {@code wrap(InputStream)} API.
|
* resolvers), and exposes a one-shot {@code wrap(InputStream)} API.
|
||||||
* Verification behavior is controlled by a pluggable comparison approach.</li>
|
* Verification behavior is controlled by a pluggable comparison approach.</li>
|
||||||
* <li><b>SignatureStream</b> - internal passthrough input stream that feeds chunks to
|
* <li><b>SignatureStream</b> - internal passthrough input stream that feeds
|
||||||
* the signature engine, emits the trailer in SIGN mode, and performs final
|
* chunks to the signature engine, emits the trailer in SIGN mode, and performs
|
||||||
* verification in VERIFY mode.</li>
|
* final verification in VERIFY mode.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h2>Length resolution</h2>
|
* <h2>Length resolution</h2>
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ public final class Sha2Sha3Algorithm extends AbstractCryptoAlgorithm {
|
|||||||
MessageDigest md = MessageDigest.getInstance(s.algorithm().jca());
|
MessageDigest md = MessageDigest.getInstance(s.algorithm().jca());
|
||||||
return new JcaDigestContext(this, md, s);
|
return new JcaDigestContext(this, md, s);
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new ProviderFailureException(
|
throw new ProviderFailureException("Failed to initialize MessageDigest " + s.algorithm().jca(),
|
||||||
"Failed to initialize MessageDigest " + s.algorithm().jca(), e);
|
e);
|
||||||
}
|
}
|
||||||
}, DigestSpec::sha256 // default for catalog/tests
|
}, DigestSpec::sha256 // default for catalog/tests
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -160,8 +160,7 @@ public final class EcdhAlgorithm extends AbstractCryptoAlgorithm {
|
|||||||
() -> EcdsaCurveSpec.P256);
|
() -> EcdsaCurveSpec.P256);
|
||||||
|
|
||||||
// Reuse EC builders/importers
|
// Reuse EC builders/importers
|
||||||
registerAsymmetricKeyPairGenerator(EcdhCurveSpec.class, new EcdhKeyGenBuilder(),
|
registerAsymmetricKeyPairGenerator(EcdhCurveSpec.class, new EcdhKeyGenBuilder(), () -> EcdhCurveSpec.P256);
|
||||||
() -> EcdhCurveSpec.P256);
|
|
||||||
registerPublicKeyImporter(EcdsaPublicKeySpec.class, new EcdsaPublicKeyBuilder());
|
registerPublicKeyImporter(EcdsaPublicKeySpec.class, new EcdsaPublicKeyBuilder());
|
||||||
registerPrivateKeyImporter(EcdsaPrivateKeySpec.class, new EcdsaPrivateKeyBuilder());
|
registerPrivateKeyImporter(EcdsaPrivateKeySpec.class, new EcdsaPrivateKeyBuilder());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
|
|||||||
/**
|
/**
|
||||||
* <h2>ECDH Key Pair Generator</h2>
|
* <h2>ECDH Key Pair Generator</h2>
|
||||||
*
|
*
|
||||||
* Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for elliptic curve
|
* Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for
|
||||||
* Diffie-Hellman (ECDH) key pairs.
|
* elliptic curve Diffie-Hellman (ECDH) key pairs.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This builder generates fresh EC key pairs suitable for ECDH key agreement. It
|
* This builder generates fresh EC key pairs suitable for ECDH key agreement. It
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ public final class EcdsaAlgorithm extends AbstractCryptoAlgorithm {
|
|||||||
* <p>
|
* <p>
|
||||||
* On construction, the algorithm declares its supported roles and registers
|
* On construction, the algorithm declares its supported roles and registers
|
||||||
* builders with the {@link CryptoAlgorithm} infrastructure so they can be
|
* builders with the {@link CryptoAlgorithm} infrastructure so they can be
|
||||||
* discovered by the {@link CryptoCatalog} or invoked through the
|
* discovered by the {@link CryptoCatalog} or invoked through the session-bound
|
||||||
* session-bound {@link zeroecho.sdk.KeyBuilders} entry point.
|
* {@link zeroecho.sdk.KeyBuilders} entry point.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public EcdsaAlgorithm() {
|
public EcdsaAlgorithm() {
|
||||||
@@ -134,8 +134,7 @@ public final class EcdsaAlgorithm extends AbstractCryptoAlgorithm {
|
|||||||
}
|
}
|
||||||
}, () -> EcdsaCurveSpec.P256);
|
}, () -> EcdsaCurveSpec.P256);
|
||||||
|
|
||||||
registerAsymmetricKeyPairGenerator(EcdsaCurveSpec.class, new EcdsaKeyGenBuilder(),
|
registerAsymmetricKeyPairGenerator(EcdsaCurveSpec.class, new EcdsaKeyGenBuilder(), () -> EcdsaCurveSpec.P256);
|
||||||
() -> EcdsaCurveSpec.P256);
|
|
||||||
registerPublicKeyImporter(EcdsaPublicKeySpec.class, new EcdsaPublicKeyBuilder());
|
registerPublicKeyImporter(EcdsaPublicKeySpec.class, new EcdsaPublicKeyBuilder());
|
||||||
registerPrivateKeyImporter(EcdsaPrivateKeySpec.class, new EcdsaPrivateKeyBuilder());
|
registerPrivateKeyImporter(EcdsaPrivateKeySpec.class, new EcdsaPrivateKeyBuilder());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,14 +45,14 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
|
|||||||
* <h2>ECDSA Key Pair Generator</h2>
|
* <h2>ECDSA Key Pair Generator</h2>
|
||||||
*
|
*
|
||||||
* Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for
|
* Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for
|
||||||
* {@link EcdsaCurveSpec}.
|
* {@link EcdsaCurveSpec}. This builder is responsible for generating new
|
||||||
* This builder is responsible for generating new elliptic curve key pairs for
|
* elliptic curve key pairs for use with the {@link EcdsaAlgorithm}.
|
||||||
* use with the {@link EcdsaAlgorithm}.
|
|
||||||
*
|
*
|
||||||
* <p>The exact supported operation is
|
* <p>
|
||||||
* {@link #generateKeyPair(EcdsaCurveSpec)}. Public and private import are
|
* The exact supported operation is {@link #generateKeyPair(EcdsaCurveSpec)}.
|
||||||
* registered separately through {@link EcdsaPublicKeyBuilder} and
|
* Public and private import are registered separately through
|
||||||
* {@link EcdsaPrivateKeyBuilder}.</p>
|
* {@link EcdsaPublicKeyBuilder} and {@link EcdsaPrivateKeyBuilder}.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Usage</h2> Typically accessed through the session key-operation API or
|
* <h2>Usage</h2> Typically accessed through the session key-operation API or
|
||||||
* {@link CryptoAlgorithm#asymmetricKeyPairGenerator(Class)}.
|
* {@link CryptoAlgorithm#asymmetricKeyPairGenerator(Class)}.
|
||||||
|
|||||||
@@ -49,9 +49,11 @@ import zeroecho.core.spi.PrivateKeyImporter;
|
|||||||
* {@link EcdsaPrivateKeySpec}. This builder is responsible for importing ECDSA
|
* {@link EcdsaPrivateKeySpec}. This builder is responsible for importing ECDSA
|
||||||
* private keys from encoded representations.
|
* private keys from encoded representations.
|
||||||
*
|
*
|
||||||
* <p>The exact supported operation is
|
* <p>
|
||||||
* {@link #importPrivate(EcdsaPrivateKeySpec)}. Generation and public import are
|
* The exact supported operation is {@link #importPrivate(EcdsaPrivateKeySpec)}.
|
||||||
* registered through their own operation-specific implementations.</p>
|
* Generation and public import are registered through their own
|
||||||
|
* operation-specific implementations.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Encoding</h2> The {@link EcdsaPrivateKeySpec} stores the private key in
|
* <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
|
* PKCS#8 DER format. This builder delegates to a JCA {@link KeyFactory} for the
|
||||||
|
|||||||
@@ -48,9 +48,11 @@ import zeroecho.core.spi.PublicKeyImporter;
|
|||||||
* {@link EcdsaPublicKeySpec}. This builder is responsible for importing ECDSA
|
* {@link EcdsaPublicKeySpec}. This builder is responsible for importing ECDSA
|
||||||
* public keys from X.509 SubjectPublicKeyInfo encodings.
|
* public keys from X.509 SubjectPublicKeyInfo encodings.
|
||||||
*
|
*
|
||||||
* <p>The exact supported operation is
|
* <p>
|
||||||
* {@link #importPublic(EcdsaPublicKeySpec)}. Generation and private import are
|
* The exact supported operation is {@link #importPublic(EcdsaPublicKeySpec)}.
|
||||||
* registered through their own operation-specific implementations.</p>
|
* Generation and private import are registered through their own
|
||||||
|
* operation-specific implementations.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Encoding</h2> The {@link EcdsaPublicKeySpec} stores the public key in
|
* <h2>Encoding</h2> The {@link EcdsaPublicKeySpec} stores the public key in
|
||||||
* standard X.509 DER format. This builder delegates to a JCA {@link KeyFactory}
|
* standard X.509 DER format. This builder delegates to a JCA {@link KeyFactory}
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ import zeroecho.core.alg.common.eddsa.AbstractEdDSAKeyGenBuilder;
|
|||||||
/**
|
/**
|
||||||
* <h2>Key-pair builder for Ed25519</h2>
|
* <h2>Key-pair builder for Ed25519</h2>
|
||||||
*
|
*
|
||||||
* Concrete {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} implementation for
|
* Concrete {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} implementation
|
||||||
* generating Ed25519 key pairs.
|
* for generating Ed25519 key pairs.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This builder delegates to the JCA provider under the canonical algorithm name
|
* This builder delegates to the JCA provider under the canonical algorithm name
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ import zeroecho.core.alg.common.eddsa.AbstractEncodedPrivateKeyBuilder;
|
|||||||
/**
|
/**
|
||||||
* <h2>Private key builder for Ed25519</h2>
|
* <h2>Private key builder for Ed25519</h2>
|
||||||
*
|
*
|
||||||
* Concrete {@link zeroecho.core.spi.PrivateKeyImporter} for importing
|
* Concrete {@link zeroecho.core.spi.PrivateKeyImporter} for importing wrapping
|
||||||
* wrapping Ed25519 private keys.
|
* Ed25519 private keys.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This builder integrates with the JCA under the canonical key factory
|
* This builder integrates with the JCA under the canonical key factory
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ import zeroecho.core.alg.common.eddsa.AbstractEncodedPublicKeyBuilder;
|
|||||||
/**
|
/**
|
||||||
* <h2>Public key builder for Ed25519</h2>
|
* <h2>Public key builder for Ed25519</h2>
|
||||||
*
|
*
|
||||||
* Concrete {@link zeroecho.core.spi.PublicKeyImporter} for importing
|
* Concrete {@link zeroecho.core.spi.PublicKeyImporter} for importing wrapping
|
||||||
* wrapping Ed25519 public keys.
|
* Ed25519 public keys.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This builder integrates with the JCA under the canonical key factory
|
* This builder integrates with the JCA under the canonical key factory
|
||||||
|
|||||||
@@ -66,8 +66,7 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
|||||||
*
|
*
|
||||||
* <h2>Thread-safety</h2> Stateless and safe for concurrent use. Each call to
|
* <h2>Thread-safety</h2> Stateless and safe for concurrent use. Each call to
|
||||||
* {@link zeroecho.core.spi.PrivateKeyImporter#importPrivate(AlgorithmKeySpec)}
|
* {@link zeroecho.core.spi.PrivateKeyImporter#importPrivate(AlgorithmKeySpec)}
|
||||||
* creates a new
|
* creates a new {@link java.security.KeyFactory}.
|
||||||
* {@link java.security.KeyFactory}.
|
|
||||||
*
|
*
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -65,8 +65,7 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
|||||||
*
|
*
|
||||||
* <h2>Thread-safety</h2> Stateless and safe for concurrent use. Each call to
|
* <h2>Thread-safety</h2> Stateless and safe for concurrent use. Each call to
|
||||||
* {@link zeroecho.core.spi.PublicKeyImporter#importPublic(AlgorithmKeySpec)}
|
* {@link zeroecho.core.spi.PublicKeyImporter#importPublic(AlgorithmKeySpec)}
|
||||||
* creates a new
|
* creates a new {@link java.security.KeyFactory}.
|
||||||
* {@link java.security.KeyFactory}.
|
|
||||||
*
|
*
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @see KyberAlgorithm
|
* @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 {
|
public final class KyberKeyGenSpec implements AlgorithmKeySpec, Describable {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
|||||||
* Specification wrapper for a Kyber (ML-KEM) private key encoded in PKCS#8.
|
* Specification wrapper for a Kyber (ML-KEM) private key encoded in PKCS#8.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Instances of this class carry an owned copy of the PKCS#8-encoded private
|
* Instances of this class carry an owned copy of the PKCS#8-encoded private key
|
||||||
* key bytes. They are used with {@link zeroecho.core.CryptoAlgorithm} key
|
* bytes. They are used with {@link zeroecho.core.CryptoAlgorithm} key builders
|
||||||
* builders to import keys into the provider’s native representation.
|
* to import keys into the provider’s native representation.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Encoding</h2>
|
* <h2>Encoding</h2>
|
||||||
|
|||||||
@@ -102,8 +102,7 @@ public record BlockGeometry(int inChunkSize, int outChunkSize, int finalizationO
|
|||||||
"inChunkSize must not exceed outChunkSize: " + inChunkSize + " > " + outChunkSize);
|
"inChunkSize must not exceed outChunkSize: " + inChunkSize + " > " + outChunkSize);
|
||||||
}
|
}
|
||||||
if (finalizationOutputChunks != 0) {
|
if (finalizationOutputChunks != 0) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException("finalizationOutputChunks must be zero: " + finalizationOutputChunks);
|
||||||
"finalizationOutputChunks must be zero: " + finalizationOutputChunks);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
|||||||
* A {@code SaberKeyGenSpec} selects one of the SABER parameter variants
|
* A {@code SaberKeyGenSpec} selects one of the SABER parameter variants
|
||||||
* standardized in round-3 submissions. Each variant balances performance,
|
* standardized in round-3 submissions. Each variant balances performance,
|
||||||
* bandwidth, and security level. This spec is passed to a registered
|
* 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>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Variants</h2> The {@link Variant} enumeration identifies supported SABER
|
* <h2>Variants</h2> The {@link Variant} enumeration identifies supported SABER
|
||||||
|
|||||||
@@ -54,10 +54,12 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
|
|||||||
* Reflection is used to avoid a hard dependency on all parameter variants.
|
* Reflection is used to avoid a hard dependency on all parameter variants.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>The exact supported operation is
|
* <p>
|
||||||
* {@link #generateKeyPair(SphincsPlusKeyGenSpec)}. Public and private import are
|
* The exact supported operation is
|
||||||
* registered separately for {@link SphincsPlusPublicKeySpec} and
|
* {@link #generateKeyPair(SphincsPlusKeyGenSpec)}. Public and private import
|
||||||
* {@link SphincsPlusPrivateKeySpec}.</p>
|
* are registered separately for {@link SphincsPlusPublicKeySpec} and
|
||||||
|
* {@link SphincsPlusPrivateKeySpec}.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Example</h2> <pre>{@code
|
* <h2>Example</h2> <pre>{@code
|
||||||
* SphincsPlusKeyGenSpec spec =
|
* SphincsPlusKeyGenSpec spec =
|
||||||
|
|||||||
@@ -51,9 +51,11 @@ import zeroecho.core.spi.PrivateKeyImporter;
|
|||||||
* pairs but focuses solely on importing private key material.
|
* pairs but focuses solely on importing private key material.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>The exact supported operation is
|
* <p>
|
||||||
|
* The exact supported operation is
|
||||||
* {@link #importPrivate(SphincsPlusPrivateKeySpec)}. Other key operations are
|
* {@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
|
* <h2>Example</h2> <pre>{@code
|
||||||
* // Assuming bytes contain a PKCS#8-encoded SPHINCS+ private key:
|
* // Assuming bytes contain a PKCS#8-encoded SPHINCS+ private key:
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
|||||||
* <p>
|
* <p>
|
||||||
* {@code SphincsPlusPrivateKeySpec} wraps a PKCS#8-encoded SPHINCS+ private key
|
* {@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
|
* 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>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Encoding</h2>
|
* <h2>Encoding</h2>
|
||||||
|
|||||||
@@ -50,9 +50,11 @@ import zeroecho.core.spi.PublicKeyImporter;
|
|||||||
* pairs, but focuses solely on importing public key material.
|
* pairs, but focuses solely on importing public key material.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>The exact supported operation is
|
* <p>
|
||||||
|
* The exact supported operation is
|
||||||
* {@link #importPublic(SphincsPlusPublicKeySpec)}. Other key operations are
|
* {@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
|
* <h2>Example</h2> <pre>{@code
|
||||||
* // Assuming bytes contain an X.509-encoded SPHINCS+ public key:
|
* // Assuming bytes contain an X.509-encoded SPHINCS+ public key:
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
|
|||||||
* <h2>Design and scope</h2>
|
* <h2>Design and scope</h2>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li><b>Generation only:</b> This implementation exposes only the exact
|
* <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.
|
* <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
|
* If a specific provider is required, supply or register one that exposes the
|
||||||
* requested XDH algorithm name.</li>
|
* requested XDH algorithm name.</li>
|
||||||
|
|||||||
@@ -13,10 +13,12 @@ import java.util.Objects;
|
|||||||
/**
|
/**
|
||||||
* Utilities for enforcing the best-effort audit-listener contract.
|
* 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
|
* arguments, because those arguments may refer to sensitive cryptographic
|
||||||
* objects. Cryptographic operation outcomes therefore never depend on an audit
|
* objects. Cryptographic operation outcomes therefore never depend on an audit
|
||||||
* sink's availability.</p>
|
* sink's availability.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
@@ -36,8 +38,8 @@ public final class AuditListeners {
|
|||||||
AuditListener target = Objects.requireNonNull(listener, "listener");
|
AuditListener target = Objects.requireNonNull(listener, "listener");
|
||||||
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
|
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
|
||||||
ClassLoader loader = contextLoader == null ? ClassLoader.getSystemClassLoader() : contextLoader;
|
ClassLoader loader = contextLoader == null ? ClassLoader.getSystemClassLoader() : contextLoader;
|
||||||
return (AuditListener) Proxy.newProxyInstance(loader,
|
return (AuditListener) Proxy.newProxyInstance(loader, new Class<?>[] { AuditListener.class },
|
||||||
new Class<?>[] { AuditListener.class }, (proxy, method, arguments) -> {
|
(proxy, method, arguments) -> {
|
||||||
if (method.getDeclaringClass() == Object.class) {
|
if (method.getDeclaringClass() == Object.class) {
|
||||||
return method.invoke(target, arguments);
|
return method.invoke(target, arguments);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ package zeroecho.core.audit;
|
|||||||
/**
|
/**
|
||||||
* Defines the session-owned automatic auditing strategy.
|
* Defines the session-owned automatic auditing strategy.
|
||||||
*
|
*
|
||||||
* <p>Audit listener failures are best-effort diagnostics and never change the
|
* <p>
|
||||||
* outcome of a cryptographic operation.</p>
|
* Audit listener failures are best-effort diagnostics and never change the
|
||||||
|
* outcome of a cryptographic operation.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -105,8 +105,8 @@ import zeroecho.core.spec.ContextSpec;
|
|||||||
* <li>Counting is performed by decorating the returned {@code InputStream}s; no
|
* <li>Counting is performed by decorating the returned {@code InputStream}s; no
|
||||||
* buffering beyond normal {@code FilterInputStream} forwarding is
|
* buffering beyond normal {@code FilterInputStream} forwarding is
|
||||||
* introduced.</li>
|
* introduced.</li>
|
||||||
* <li>Idempotent wrapping: contexts already wrapped by this utility are returned
|
* <li>Idempotent wrapping: contexts already wrapped by this utility are
|
||||||
* unchanged. Unrelated JDK proxies are wrapped normally.</li>
|
* returned unchanged. Unrelated JDK proxies are wrapped normally.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h2>Usage example</h2> <pre>{@code
|
* <h2>Usage example</h2> <pre>{@code
|
||||||
@@ -190,8 +190,7 @@ public final class AuditedContexts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAuditedProxy(CryptoContext context) {
|
private static boolean isAuditedProxy(CryptoContext context) {
|
||||||
return Proxy.isProxyClass(context.getClass())
|
return Proxy.isProxyClass(context.getClass()) && Proxy.getInvocationHandler(context) instanceof AuditingHandler;
|
||||||
&& Proxy.getInvocationHandler(context) instanceof AuditingHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -242,8 +241,7 @@ public final class AuditedContexts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
safeAudit.onContextCreatedMeta(ctxId, algoId == null ? UNKNOWN : algoId,
|
safeAudit.onContextCreatedMeta(ctxId, algoId == null ? UNKNOWN : algoId,
|
||||||
provider == null ? UNKNOWN : provider,
|
provider == null ? UNKNOWN : provider, role, keyFp, specMeta);
|
||||||
role, keyFp, specMeta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassLoader cl = ctx.getClass().getClassLoader(); // NOPMD
|
ClassLoader cl = ctx.getClass().getClassLoader(); // NOPMD
|
||||||
@@ -720,8 +718,7 @@ public final class AuditedContexts {
|
|||||||
StringBuilder fingerprint = new StringBuilder(16);
|
StringBuilder fingerprint = new StringBuilder(16);
|
||||||
for (int index = 0; index < Math.min(8, digest.length); index++) {
|
for (int index = 0; index < Math.min(8, digest.length); index++) {
|
||||||
int value = digest[index] & 0xff;
|
int value = digest[index] & 0xff;
|
||||||
fingerprint.append(Character.forDigit(value >>> 4, 16))
|
fingerprint.append(Character.forDigit(value >>> 4, 16)).append(Character.forDigit(value & 0x0f, 16));
|
||||||
.append(Character.forDigit(value & 0x0f, 16));
|
|
||||||
}
|
}
|
||||||
return key.getAlgorithm() + ":" + fingerprint;
|
return key.getAlgorithm() + ":" + fingerprint;
|
||||||
} catch (NoSuchAlgorithmException exception) {
|
} catch (NoSuchAlgorithmException exception) {
|
||||||
|
|||||||
@@ -204,9 +204,9 @@ public final class JulAuditListenerStd implements AuditListener {
|
|||||||
* appends a stack trace in addition to the structured summary.
|
* appends a stack trace in addition to the structured summary.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Stack traces may contain provider exception messages or application
|
* Stack traces may contain provider exception messages or application values.
|
||||||
* values. Enabling them is an explicit diagnostic opt-in and requires a
|
* Enabling them is an explicit diagnostic opt-in and requires a suitably
|
||||||
* suitably protected log destination.
|
* protected log destination.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param include true to include stack traces, false to omit them
|
* @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(':');
|
StringBuilder sb = new StringBuilder(key.getAlgorithm()).append(':');
|
||||||
for (int i = 0; i < Math.min(8, digest.length); i++) {
|
for (int i = 0; i < Math.min(8, digest.length); i++) {
|
||||||
int value = digest[i] & 0xff;
|
int value = digest[i] & 0xff;
|
||||||
sb.append(Character.forDigit(value >>> 4, 16))
|
sb.append(Character.forDigit(value >>> 4, 16)).append(Character.forDigit(value & 0x0f, 16));
|
||||||
.append(Character.forDigit(value & 0x0f, 16));
|
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
|||||||
@@ -46,13 +46,11 @@ package zeroecho.core.err;
|
|||||||
* <h2>When it is thrown</h2>
|
* <h2>When it is thrown</h2>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>During
|
* <li>During
|
||||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}
|
||||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}
|
|
||||||
* after policy validation, if the resolved algorithm exposes no bindings for
|
* after policy validation, if the resolved algorithm exposes no bindings for
|
||||||
* the given role.</li>
|
* the given role.</li>
|
||||||
* <li>Directly from
|
* <li>Directly from
|
||||||
* {@link zeroecho.core.CryptoAlgorithm#createContext(zeroecho.core.KeyUsage,
|
* {@link zeroecho.core.CryptoAlgorithm#createContext(zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}
|
||||||
* java.security.Key, zeroecho.core.spec.ContextSpec)}
|
|
||||||
* when no binding exists for the role.</li>
|
* when no binding exists for the role.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -157,8 +157,8 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
|
|||||||
* @param outChunkSize size of output chunks produced by the transform (must be
|
* @param outChunkSize size of output chunks produced by the transform (must be
|
||||||
* > 0)
|
* > 0)
|
||||||
* @param chunks number of chunks buffered at once (must be > 0)
|
* @param chunks number of chunks buffered at once (must be > 0)
|
||||||
* @throws IllegalArgumentException if a size is outside its documented range
|
* @throws IllegalArgumentException if a size is outside its documented range or
|
||||||
* or a buffer size overflows
|
* a buffer size overflows
|
||||||
*/
|
*/
|
||||||
protected AbstractChunkTransformInputStream(InputStream upstream, int inChunkSize, int outChunkSize, int chunks) {
|
protected AbstractChunkTransformInputStream(InputStream upstream, int inChunkSize, int outChunkSize, int chunks) {
|
||||||
super(upstream);
|
super(upstream);
|
||||||
@@ -190,8 +190,8 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
|
|||||||
* steady-state (must be > 0)
|
* steady-state (must be > 0)
|
||||||
* @param finalizationOutputChunks number of extra output chunks reserved for
|
* @param finalizationOutputChunks number of extra output chunks reserved for
|
||||||
* finalization (must be >= 0)
|
* finalization (must be >= 0)
|
||||||
* @throws IllegalArgumentException if a size is outside its documented range
|
* @throws IllegalArgumentException if a size is outside its documented range or
|
||||||
* or a buffer size overflows
|
* a buffer size overflows
|
||||||
*/
|
*/
|
||||||
protected AbstractChunkTransformInputStream(InputStream upstream, int inChunkSize, int outChunkSize, int chunks,
|
protected AbstractChunkTransformInputStream(InputStream upstream, int inChunkSize, int outChunkSize, int chunks,
|
||||||
int finalizationOutputChunks) {
|
int finalizationOutputChunks) {
|
||||||
@@ -261,8 +261,7 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
|
|||||||
// EOF: run finalization exactly once, even if there's no remainder,
|
// EOF: run finalization exactly once, even if there's no remainder,
|
||||||
// and surface any produced bytes (e.g., padding block, GCM tag).
|
// and surface any produced bytes (e.g., padding block, GCM tag).
|
||||||
if (!eofSeen) {
|
if (!eofSeen) {
|
||||||
int finalOut = validateOutputCount(doFinal(inBuf, 0, 0, outBuf, 0), outBuf.length,
|
int finalOut = validateOutputCount(doFinal(inBuf, 0, 0, outBuf, 0), outBuf.length, "finalization");
|
||||||
"finalization");
|
|
||||||
outPtr = 0;
|
outPtr = 0;
|
||||||
outLen = finalOut;
|
outLen = finalOut;
|
||||||
eofSeen = true;
|
eofSeen = true;
|
||||||
@@ -273,8 +272,7 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
|
|||||||
|
|
||||||
// all chunks are aligned to the specified boundary (inChunkSize) -> transform
|
// all chunks are aligned to the specified boundary (inChunkSize) -> transform
|
||||||
// can be simply invoked
|
// can be simply invoked
|
||||||
outLen = validateOutputCount(transform(inBuf, 0, inLen / inChunkSize, outBuf), outBuf.length,
|
outLen = validateOutputCount(transform(inBuf, 0, inLen / inChunkSize, outBuf), outBuf.length, "transformation");
|
||||||
"transformation");
|
|
||||||
outPtr = 0;
|
outPtr = 0;
|
||||||
|
|
||||||
int left = inLen % inChunkSize;
|
int left = inLen % inChunkSize;
|
||||||
@@ -291,8 +289,7 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateGeometry(int inChunkSize, int outChunkSize, int chunks,
|
private static void validateGeometry(int inChunkSize, int outChunkSize, int chunks, int finalizationOutputChunks) {
|
||||||
int finalizationOutputChunks) {
|
|
||||||
if (inChunkSize < MIN_INPUT_CHUNK_SIZE) {
|
if (inChunkSize < MIN_INPUT_CHUNK_SIZE) {
|
||||||
throw new IllegalArgumentException("inChunkSize must be greater than 1");
|
throw new IllegalArgumentException("inChunkSize must be greater than 1");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,13 +53,12 @@ import javax.crypto.Cipher;
|
|||||||
* block; a final partial block (if any) is processed by a single
|
* block; a final partial block (if any) is processed by a single
|
||||||
* {@code doFinal}. This mode is restricted to RSA and ElGamal.</li>
|
* {@code doFinal}. This mode is restricted to RSA and ElGamal.</li>
|
||||||
* <li><b>Left-padded independent block stream</b> - like the independent block
|
* <li><b>Left-padded independent block stream</b> - like the independent block
|
||||||
* stream, but
|
* stream, but left-pads each transformed output block with zeros up to
|
||||||
* left-pads each transformed output block with zeros up to
|
|
||||||
* {@code outChunkSize}. Final blocks must be complete; otherwise an
|
* {@code outChunkSize}. Final blocks must be complete; otherwise an
|
||||||
* {@link IllegalStateException} is thrown.</li>
|
* {@link IllegalStateException} is thrown.</li>
|
||||||
* <li><b>Continuous stream</b> - uses
|
* <li><b>Continuous stream</b> - uses {@code Cipher.update(...)} for bulk bytes
|
||||||
* {@code Cipher.update(...)} for bulk bytes and a single {@code doFinal()} at
|
* and a single {@code doFinal()} at end of stream. This is suitable for
|
||||||
* end of stream. This is suitable for CTR/CFB/OFB/GCM and padding modes.</li>
|
* CTR/CFB/OFB/GCM and padding modes.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h2>Block sizing</h2>
|
* <h2>Block sizing</h2>
|
||||||
@@ -344,7 +343,8 @@ public final class CipherTransformInputStreamBuilder {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @return a new InputStream that transforms bytes on the fly
|
* @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
|
* @throws IllegalArgumentException if independent-block processing is selected
|
||||||
* for an unsupported algorithm or buffer
|
* for an unsupported algorithm or buffer
|
||||||
* geometry is invalid
|
* geometry is invalid
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ final class SmartBlockStream extends AbstractChunkTransformInputStream {
|
|||||||
private static final Logger LOG = Logger.getLogger(SmartBlockStream.class.getName());
|
private static final Logger LOG = Logger.getLogger(SmartBlockStream.class.getName());
|
||||||
|
|
||||||
private final Cipher cipher;
|
private final Cipher cipher;
|
||||||
|
|
||||||
/* package */ SmartBlockStream(InputStream upstream, Cipher cipher, int inChunkSize, int outChunkSize,
|
/* package */ SmartBlockStream(InputStream upstream, Cipher cipher, int inChunkSize, int outChunkSize,
|
||||||
int bufferedBlocks) {
|
int bufferedBlocks) {
|
||||||
super(upstream, inChunkSize, outChunkSize, bufferedBlocks);
|
super(upstream, inChunkSize, outChunkSize, bufferedBlocks);
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ final class SmartPaddedBlockStream extends AbstractChunkTransformInputStream {
|
|||||||
private static final Logger LOG = Logger.getLogger(SmartPaddedBlockStream.class.getName());
|
private static final Logger LOG = Logger.getLogger(SmartPaddedBlockStream.class.getName());
|
||||||
|
|
||||||
private final Cipher cipher;
|
private final Cipher cipher;
|
||||||
|
|
||||||
/* package */ SmartPaddedBlockStream(InputStream upstream, Cipher cipher, int inChunkSize, int outChunkSize,
|
/* package */ SmartPaddedBlockStream(InputStream upstream, Cipher cipher, int inChunkSize, int outChunkSize,
|
||||||
int bufferedBlocks) {
|
int bufferedBlocks) {
|
||||||
super(upstream, inChunkSize, outChunkSize, bufferedBlocks);
|
super(upstream, inChunkSize, outChunkSize, bufferedBlocks);
|
||||||
|
|||||||
@@ -54,12 +54,12 @@
|
|||||||
* then calls {@code onCompleted()} exactly once at EOF.</li>
|
* then calls {@code onCompleted()} exactly once at EOF.</li>
|
||||||
* <li>{@link CipherTransformInputStreamBuilder} - fluent builder that creates
|
* <li>{@link CipherTransformInputStreamBuilder} - fluent builder that creates
|
||||||
* cipher-backed streams for RSA/ElGamal independent-block processing,
|
* cipher-backed streams for RSA/ElGamal independent-block processing,
|
||||||
* left-zero-padded independent blocks, or
|
* left-zero-padded independent blocks, or continuous
|
||||||
* continuous {@code update}+{@code doFinal} streaming.</li>
|
* {@code update}+{@code doFinal} streaming.</li>
|
||||||
* <li>{@link SmartBlockStream}, {@link SmartPaddedBlockStream},
|
* <li>{@link SmartBlockStream}, {@link SmartPaddedBlockStream},
|
||||||
* {@link SmartContinuousBlockStream} - internal cipher-backed stream variants;
|
* {@link SmartContinuousBlockStream} - internal cipher-backed stream variants;
|
||||||
* the first two are restricted to independent RSA or ElGamal blocks
|
* the first two are restricted to independent RSA or ElGamal blocks used by the
|
||||||
* used by the builder.</li>
|
* builder.</li>
|
||||||
* <li>{@link TailStrippingInputStream} - withholds the last N bytes from the
|
* <li>{@link TailStrippingInputStream} - withholds the last N bytes from the
|
||||||
* payload and delivers them to a callback at EOF (useful for tags, checksums,
|
* payload and delivers them to a callback at EOF (useful for tags, checksums,
|
||||||
* or footers).</li>
|
* or footers).</li>
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* <h2>Serialization</h2>
|
* <h2>Serialization</h2>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link #writeTo(Appendable)} outputs each pair as {@code k=v\n}
|
* <li>{@link #writeTo(Appendable)} outputs each pair as {@code k=v\n} lines
|
||||||
* lines without escaping and reports checked I/O failures.</li>
|
* without escaping and reports checked I/O failures.</li>
|
||||||
* <li>{@link #readFrom(java.io.Reader)} parses lines in the same format,
|
* <li>{@link #readFrom(java.io.Reader)} parses lines in the same format,
|
||||||
* ignoring blank lines and comments starting with {@code #}.</li>
|
* ignoring blank lines and comments starting with {@code #}.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
@@ -99,8 +99,7 @@ public final class PairSeq {
|
|||||||
if (kv[elementIndex] == null) {
|
if (kv[elementIndex] == null) {
|
||||||
int pairIndex = elementIndex >>> 1;
|
int pairIndex = elementIndex >>> 1;
|
||||||
String role = (elementIndex & 1) == 0 ? "key" : "value";
|
String role = (elementIndex & 1) == 0 ? "key" : "value";
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException("pair " + pairIndex + " " + role + " must not be null");
|
||||||
"pair " + pairIndex + " " + role + " must not be null");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new PairSeq(kv.clone());
|
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
|
* Appends all pairs to the target as {@code key=value} lines, reporting checked
|
||||||
* checked I/O failures directly.
|
* I/O failures directly.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* No escaping is performed; callers must ensure keys and values do not contain
|
* No escaping is performed; callers must ensure keys and values do not contain
|
||||||
|
|||||||
@@ -101,9 +101,9 @@ import java.util.function.Supplier;
|
|||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <h2>Thread-safety</h2> Instances are immutable and thread-safe. Public
|
* <h2>Thread-safety</h2> Instances are immutable and thread-safe. Public
|
||||||
* accessors are resolved once per runtime class and operation type, then invoked
|
* accessors are resolved once per runtime class and operation type, then
|
||||||
* through cached method handles. The unload-safe {@link ClassValue} caches do
|
* invoked through cached method handles. The unload-safe {@link ClassValue}
|
||||||
* not retain otherwise unreachable class loaders.
|
* caches do not retain otherwise unreachable class loaders.
|
||||||
*
|
*
|
||||||
* @param <T> domain type that follows the marshalling and unmarshalling
|
* @param <T> domain type that follows the marshalling and unmarshalling
|
||||||
* conventions
|
* conventions
|
||||||
@@ -274,8 +274,7 @@ public final class PairSeqCodec<T> implements Codec<T, PairSeq> {
|
|||||||
try {
|
try {
|
||||||
Method method = runtimeType.getMethod("marshal");
|
Method method = runtimeType.getMethod("marshal");
|
||||||
if (!PairSeq.class.isAssignableFrom(method.getReturnType())) {
|
if (!PairSeq.class.isAssignableFrom(method.getReturnType())) {
|
||||||
return new MarshalPlan(null,
|
return new MarshalPlan(null, "marshal() must return PairSeq in " + runtimeType.getName(), null);
|
||||||
"marshal() must return PairSeq in " + runtimeType.getName(), null);
|
|
||||||
}
|
}
|
||||||
MethodHandle handle = MethodHandles.lookup().unreflect(method);
|
MethodHandle handle = MethodHandles.lookup().unreflect(method);
|
||||||
return new MarshalPlan(handle, null, null);
|
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);
|
"static unmarshal(PairSeq) must return " + runtimeType.getName(), null);
|
||||||
}
|
}
|
||||||
MethodHandle handle = MethodHandles.lookup().unreflect(method);
|
MethodHandle handle = MethodHandles.lookup().unreflect(method);
|
||||||
return new UnmarshalPlan(handle,
|
return new UnmarshalPlan(handle, "static unmarshal(PairSeq) failed for " + runtimeType.getName(),
|
||||||
"static unmarshal(PairSeq) failed for " + runtimeType.getName(), null, null);
|
null, null);
|
||||||
}
|
}
|
||||||
} catch (NoSuchMethodException ignored) {
|
} catch (NoSuchMethodException ignored) {
|
||||||
// Resolve the constructor fallback below.
|
// Resolve the constructor fallback below.
|
||||||
} catch (IllegalAccessException exception) {
|
} catch (IllegalAccessException exception) {
|
||||||
return new UnmarshalPlan(null, null,
|
return new UnmarshalPlan(null, null, "static unmarshal(PairSeq) failed for " + runtimeType.getName(),
|
||||||
"static unmarshal(PairSeq) failed for " + runtimeType.getName(), exception);
|
exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.core.spec;
|
package zeroecho.core.spec;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marker interface for algorithm-specific key specifications.
|
* Marker interface for algorithm-specific key specifications.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates asymmetric key pairs for one exact specification type.
|
* 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
|
* @param <S> specification type
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
|
|||||||
@@ -15,11 +15,13 @@ import zeroecho.core.spec.ContextSpec;
|
|||||||
/**
|
/**
|
||||||
* Creates a cryptographic context from a key and a context specification.
|
* 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
|
* 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
|
* expose an I/O failure contract. Factories must be stateless or otherwise safe
|
||||||
* for concurrent invocation; returned contexts retain their own documented
|
* for concurrent invocation; returned contexts retain their own documented
|
||||||
* thread-safety contracts.</p>
|
* thread-safety contracts.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @param <C> context type produced
|
* @param <C> context type produced
|
||||||
* @param <K> key type accepted
|
* @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.
|
* 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
|
* @param spec non-null resolved context specification
|
||||||
* @return a newly created context
|
* @return a newly created context
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ import zeroecho.core.storage.KeyringPassword;
|
|||||||
/**
|
/**
|
||||||
* Supplies a fresh destroyable password for one keyring open operation.
|
* 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
|
* destroy it in a {@code finally} block immediately after the keyring has been
|
||||||
* opened. Implementations must not source passwords from immutable strings,
|
* opened. Implementations must not source passwords from immutable strings,
|
||||||
* process arguments, system properties, environment fallbacks, persistent
|
* process arguments, system properties, environment fallbacks, persistent
|
||||||
* files, or global mutable state.</p>
|
* files, or global mutable state.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface KeyringUnlockProvider {
|
public interface KeyringUnlockProvider {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import java.security.PrivateKey;
|
|||||||
import zeroecho.core.spec.AlgorithmKeySpec;
|
import zeroecho.core.spec.AlgorithmKeySpec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports private keys for one exact specification type. Implementations must be
|
* Imports private keys for one exact specification type. Implementations must
|
||||||
* stateless or otherwise safe for concurrent invocation.
|
* be stateless or otherwise safe for concurrent invocation.
|
||||||
*
|
*
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
|
|||||||
@@ -34,20 +34,26 @@
|
|||||||
/**
|
/**
|
||||||
* Provider contracts for context construction and exact key operations.
|
* 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
|
* 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 SymmetricKeyGenerator}, {@link SymmetricKeyImporter},
|
||||||
* {@link AsymmetricKeyPairGenerator}, {@link PublicKeyImporter}, and
|
* {@link AsymmetricKeyPairGenerator}, {@link PublicKeyImporter}, and
|
||||||
* {@link PrivateKeyImporter}. A provider registers only the operations it
|
* {@link PrivateKeyImporter}. A provider registers only the operations it
|
||||||
* implements, so capability lookup fails before invocation instead of returning
|
* 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
|
* 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
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import java.util.Objects;
|
|||||||
/**
|
/**
|
||||||
* Redacted checked failure raised by encrypted keyring operations.
|
* 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
|
* aliases, key material, ciphertext, and provider-controlled messages are
|
||||||
* deliberately excluded.</p>
|
* deliberately excluded.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public final class KeyringException extends IOException {
|
public final class KeyringException extends IOException {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
@@ -21,17 +23,9 @@ public final class KeyringException extends IOException {
|
|||||||
* Stable keyring failure categories.
|
* Stable keyring failure categories.
|
||||||
*/
|
*/
|
||||||
public enum Code {
|
public enum Code {
|
||||||
KEYRING_ALREADY_OPEN,
|
KEYRING_ALREADY_OPEN, KEYRING_FILESYSTEM_UNSUPPORTED, KEYRING_FORMAT_INVALID, KEYRING_LIMIT_EXCEEDED,
|
||||||
KEYRING_FILESYSTEM_UNSUPPORTED,
|
KEYRING_UNLOCK_FAILED, KEYRING_IO_FAILED, KEYRING_DURABILITY_UNCONFIRMED, KEYRING_CLOSED,
|
||||||
KEYRING_FORMAT_INVALID,
|
KEYRING_NON_EXPORTABLE_KEY, KEYRING_IMPORT_MAPPING_INVALID, KEYRING_IMPORT_METADATA_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
|
KEYRING_KEY_NOT_CANONICALIZABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ interface KeyringFileOperations {
|
|||||||
|
|
||||||
/** Atomic persistence destination. */
|
/** Atomic persistence destination. */
|
||||||
enum Target {
|
enum Target {
|
||||||
MAIN_IMAGE,
|
MAIN_IMAGE, NONCE_RESERVATION
|
||||||
NONCE_RESERVATION
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates one owner-only temporary file beside its destination. */
|
/** Creates one owner-only temporary file beside its destination. */
|
||||||
@@ -57,10 +56,8 @@ final class NioKeyringFileOperations implements KeyringFileOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTemporary(Target target, Path temporary, byte[] image)
|
public void writeTemporary(Target target, Path temporary, byte[] image) throws IOException {
|
||||||
throws IOException {
|
try (FileChannel channel = FileChannel.open(temporary, StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
try (FileChannel channel = FileChannel.open(temporary,
|
|
||||||
StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) {
|
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(image);
|
ByteBuffer buffer = ByteBuffer.wrap(image);
|
||||||
while (buffer.hasRemaining()) {
|
while (buffer.hasRemaining()) {
|
||||||
channel.write(buffer);
|
channel.write(buffer);
|
||||||
@@ -70,17 +67,14 @@ final class NioKeyringFileOperations implements KeyringFileOperations {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forceTemporary(Target target, Path temporary) throws IOException {
|
public void forceTemporary(Target target, Path temporary) throws IOException {
|
||||||
try (FileChannel channel = FileChannel.open(temporary,
|
try (FileChannel channel = FileChannel.open(temporary, StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) {
|
|
||||||
channel.force(true);
|
channel.force(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void atomicReplace(Target target, Path temporary, Path destination)
|
public void atomicReplace(Target target, Path temporary, Path destination) throws IOException {
|
||||||
throws IOException {
|
Files.move(temporary, destination, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||||
Files.move(temporary, destination, StandardCopyOption.ATOMIC_MOVE,
|
|
||||||
StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -71,18 +71,18 @@ import zeroecho.core.spi.SymmetricKeyImporter;
|
|||||||
* Closed trusted mapping from persistent key identities to canonical registered
|
* Closed trusted mapping from persistent key identities to canonical registered
|
||||||
* import operations.
|
* import operations.
|
||||||
*
|
*
|
||||||
* <p>Provider identity is deliberately absent. Standard encoded key material
|
* <p>
|
||||||
* is reconstructed by the current runtime's canonical ZeroEcho importer. The
|
* Provider identity is deliberately absent. Standard encoded key material is
|
||||||
* original JCA provider is neither persisted nor reproduced.</p>
|
* reconstructed by the current runtime's canonical ZeroEcho importer. The
|
||||||
|
* original JCA provider is neither persisted nor reproduced.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
final class KeyringImportRegistry {
|
final class KeyringImportRegistry {
|
||||||
private static final String ALGORITHM_AES = "AES";
|
private static final String ALGORITHM_AES = "AES";
|
||||||
private static final String ALGORITHM_HMAC = "HMAC";
|
private static final String ALGORITHM_HMAC = "HMAC";
|
||||||
private static final String ALGORITHM_CHACHA20 = "CHACHA20";
|
private static final String ALGORITHM_CHACHA20 = "CHACHA20";
|
||||||
private static final String ALGORITHM_CHACHA20_POLY1305 = "CHACHA20-POLY1305";
|
private static final String ALGORITHM_CHACHA20_POLY1305 = "CHACHA20-POLY1305";
|
||||||
private static final Map<Class<? extends AlgorithmKeySpec>,
|
private static final Map<Class<? extends AlgorithmKeySpec>, Function<byte[], ? extends AlgorithmKeySpec>> SPEC_FACTORIES = createSpecFactories();
|
||||||
Function<byte[], ? extends AlgorithmKeySpec>> SPEC_FACTORIES =
|
|
||||||
createSpecFactories();
|
|
||||||
private static final Map<Tuple, PersistentMapping> MAPPINGS = createMappings();
|
private static final Map<Tuple, PersistentMapping> MAPPINGS = createMappings();
|
||||||
|
|
||||||
private KeyringImportRegistry() {
|
private KeyringImportRegistry() {
|
||||||
@@ -93,10 +93,7 @@ final class KeyringImportRegistry {
|
|||||||
*/
|
*/
|
||||||
/* default */
|
/* default */
|
||||||
enum HmacVariant {
|
enum HmacVariant {
|
||||||
NONE(0, null),
|
NONE(0, null), SHA256(1, "HmacSHA256"), SHA384(2, "HmacSHA384"), SHA512(3, "HmacSHA512");
|
||||||
SHA256(1, "HmacSHA256"),
|
|
||||||
SHA384(2, "HmacSHA384"),
|
|
||||||
SHA512(3, "HmacSHA512");
|
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
private final String jcaName;
|
private final String jcaName;
|
||||||
@@ -123,8 +120,7 @@ final class KeyringImportRegistry {
|
|||||||
throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID);
|
throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* default */ static HmacVariant forStoredKey(String algorithmId, Key key)
|
/* default */ static HmacVariant forStoredKey(String algorithmId, Key key) throws KeyringException {
|
||||||
throws KeyringException {
|
|
||||||
if (!ALGORITHM_HMAC.equals(algorithmId)) {
|
if (!ALGORITHM_HMAC.equals(algorithmId)) {
|
||||||
return NONE;
|
return NONE;
|
||||||
}
|
}
|
||||||
@@ -142,21 +138,19 @@ final class KeyringImportRegistry {
|
|||||||
* Immutable description used by the finite importer-matrix test.
|
* Immutable description used by the finite importer-matrix test.
|
||||||
*
|
*
|
||||||
* @param algorithmId canonical ZeroEcho algorithm identifier
|
* @param algorithmId canonical ZeroEcho algorithm identifier
|
||||||
* @param kind key kind
|
* @param kind key kind
|
||||||
* @param encoding standard encoding
|
* @param encoding standard encoding
|
||||||
* @param hmacVariant closed HMAC variant, or {@link HmacVariant#NONE}
|
* @param hmacVariant closed HMAC variant, or {@link HmacVariant#NONE}
|
||||||
* @param specType exact registered importer specification type
|
* @param specType exact registered importer specification type
|
||||||
*/
|
*/
|
||||||
/* default */
|
/* default */
|
||||||
record PersistentMapping(String algorithmId, KeyringStore.Kind kind,
|
record PersistentMapping(String algorithmId, KeyringStore.Kind kind, KeyringStore.Encoding encoding,
|
||||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant,
|
HmacVariant hmacVariant, Class<? extends AlgorithmKeySpec> specType) {
|
||||||
Class<? extends AlgorithmKeySpec> specType) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("PMD.AvoidCatchingGenericException")
|
@SuppressWarnings("PMD.AvoidCatchingGenericException")
|
||||||
/* default */ static Key importKey(String algorithmId, KeyringStore.Kind kind,
|
/* default */ static Key importKey(String algorithmId, KeyringStore.Kind kind, KeyringStore.Encoding encoding,
|
||||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant, byte[] encoded)
|
HmacVariant hmacVariant, byte[] encoded) throws GeneralSecurityException, KeyringException {
|
||||||
throws GeneralSecurityException, KeyringException {
|
|
||||||
PersistentMapping mapping = requireMapping(algorithmId, kind, encoding, hmacVariant);
|
PersistentMapping mapping = requireMapping(algorithmId, kind, encoding, hmacVariant);
|
||||||
CryptoAlgorithm algorithm = requireAlgorithm(mapping.algorithmId);
|
CryptoAlgorithm algorithm = requireAlgorithm(mapping.algorithmId);
|
||||||
AlgorithmKeySpec spec = createSpec(mapping, encoded);
|
AlgorithmKeySpec spec = createSpec(mapping, encoded);
|
||||||
@@ -172,8 +166,7 @@ final class KeyringImportRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* default */ static void validateMapping(String algorithmId, KeyringStore.Kind kind,
|
/* default */ static void validateMapping(String algorithmId, KeyringStore.Kind kind,
|
||||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant)
|
KeyringStore.Encoding encoding, HmacVariant hmacVariant) throws KeyringException {
|
||||||
throws KeyringException {
|
|
||||||
PersistentMapping mapping = requireMapping(algorithmId, kind, encoding, hmacVariant);
|
PersistentMapping mapping = requireMapping(algorithmId, kind, encoding, hmacVariant);
|
||||||
CryptoAlgorithm algorithm = requireAlgorithm(mapping.algorithmId);
|
CryptoAlgorithm algorithm = requireAlgorithm(mapping.algorithmId);
|
||||||
List<KeyOperationInfo> operations = matchingOperations(algorithm, kind);
|
List<KeyOperationInfo> operations = matchingOperations(algorithm, kind);
|
||||||
@@ -184,27 +177,23 @@ final class KeyringImportRegistry {
|
|||||||
|
|
||||||
@SuppressWarnings({ "PMD.PreserveStackTrace", "PMD.AvoidCatchingGenericException" })
|
@SuppressWarnings({ "PMD.PreserveStackTrace", "PMD.AvoidCatchingGenericException" })
|
||||||
/* default */ static void validateCanonical(String algorithmId, KeyringStore.Kind kind,
|
/* default */ static void validateCanonical(String algorithmId, KeyringStore.Kind kind,
|
||||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant, Key source,
|
KeyringStore.Encoding encoding, HmacVariant hmacVariant, Key source, byte[] encoded)
|
||||||
byte[] encoded)
|
|
||||||
throws KeyringException {
|
throws KeyringException {
|
||||||
Key imported = null;
|
Key imported = null;
|
||||||
byte[] canonical = null;
|
byte[] canonical = null;
|
||||||
try {
|
try {
|
||||||
if (!matchesSourceAlgorithm(source, algorithmId, hmacVariant)) {
|
if (!matchesSourceAlgorithm(source, algorithmId, hmacVariant)) {
|
||||||
throw new KeyringException(
|
throw new KeyringException(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
|
||||||
KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
|
|
||||||
}
|
}
|
||||||
imported = importKey(algorithmId, kind, encoding, hmacVariant, encoded);
|
imported = importKey(algorithmId, kind, encoding, hmacVariant, encoded);
|
||||||
canonical = imported.getEncoded();
|
canonical = imported.getEncoded();
|
||||||
if (canonical == null || !matchesFormat(imported.getFormat(), encoding)
|
if (canonical == null || !matchesFormat(imported.getFormat(), encoding)
|
||||||
|| !MessageDigest.isEqual(encoded, canonical)
|
|| !MessageDigest.isEqual(encoded, canonical)
|
||||||
|| !matchesAlgorithm(imported, algorithmId, hmacVariant)) {
|
|| !matchesAlgorithm(imported, algorithmId, hmacVariant)) {
|
||||||
throw new KeyringException(
|
throw new KeyringException(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
|
||||||
KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
|
|
||||||
}
|
}
|
||||||
} catch (GeneralSecurityException | RuntimeException exception) {
|
} catch (GeneralSecurityException | RuntimeException exception) {
|
||||||
throw new KeyringException(
|
throw new KeyringException(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
|
||||||
KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (canonical != null) {
|
if (canonical != null) {
|
||||||
Arrays.fill(canonical, (byte) 0);
|
Arrays.fill(canonical, (byte) 0);
|
||||||
@@ -217,11 +206,9 @@ final class KeyringImportRegistry {
|
|||||||
return List.copyOf(MAPPINGS.values());
|
return List.copyOf(MAPPINGS.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PersistentMapping requireMapping(String algorithmId,
|
private static PersistentMapping requireMapping(String algorithmId, KeyringStore.Kind kind,
|
||||||
KeyringStore.Kind kind, KeyringStore.Encoding encoding,
|
KeyringStore.Encoding encoding, HmacVariant hmacVariant) throws KeyringException {
|
||||||
HmacVariant hmacVariant) throws KeyringException {
|
PersistentMapping mapping = MAPPINGS.get(new Tuple(algorithmId, kind, encoding, hmacVariant));
|
||||||
PersistentMapping mapping = MAPPINGS.get(
|
|
||||||
new Tuple(algorithmId, kind, encoding, hmacVariant));
|
|
||||||
if (mapping == null) {
|
if (mapping == null) {
|
||||||
throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_MAPPING_INVALID);
|
throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_MAPPING_INVALID);
|
||||||
}
|
}
|
||||||
@@ -229,8 +216,7 @@ final class KeyringImportRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("PMD.PreserveStackTrace")
|
@SuppressWarnings("PMD.PreserveStackTrace")
|
||||||
private static CryptoAlgorithm requireAlgorithm(String algorithmId)
|
private static CryptoAlgorithm requireAlgorithm(String algorithmId) throws KeyringException {
|
||||||
throws KeyringException {
|
|
||||||
try {
|
try {
|
||||||
return CryptoAlgorithms.require(algorithmId);
|
return CryptoAlgorithms.require(algorithmId);
|
||||||
} catch (IllegalArgumentException exception) {
|
} catch (IllegalArgumentException exception) {
|
||||||
@@ -238,11 +224,8 @@ final class KeyringImportRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<KeyOperationInfo> matchingOperations(CryptoAlgorithm algorithm,
|
private static List<KeyOperationInfo> matchingOperations(CryptoAlgorithm algorithm, KeyringStore.Kind kind) {
|
||||||
KeyringStore.Kind kind) {
|
return algorithm.keyOperations().stream().filter(info -> info.operation() == operation(kind)).toList();
|
||||||
return algorithm.keyOperations().stream()
|
|
||||||
.filter(info -> info.operation() == operation(kind))
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KeyOperation operation(KeyringStore.Kind kind) {
|
private static KeyOperation operation(KeyringStore.Kind kind) {
|
||||||
@@ -254,25 +237,22 @@ final class KeyringImportRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
private static Key invokeImporter(CryptoAlgorithm algorithm, KeyringStore.Kind kind,
|
private static Key invokeImporter(CryptoAlgorithm algorithm, KeyringStore.Kind kind, AlgorithmKeySpec spec)
|
||||||
AlgorithmKeySpec spec) throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
return switch (kind) {
|
return switch (kind) {
|
||||||
case PUBLIC_KEY -> ((PublicKeyImporter) algorithm.publicKeyImporter(spec.getClass()))
|
case PUBLIC_KEY -> ((PublicKeyImporter) algorithm.publicKeyImporter(spec.getClass())).importPublic(spec);
|
||||||
.importPublic(spec);
|
case PRIVATE_KEY ->
|
||||||
case PRIVATE_KEY -> ((PrivateKeyImporter) algorithm.privateKeyImporter(spec.getClass()))
|
((PrivateKeyImporter) algorithm.privateKeyImporter(spec.getClass())).importPrivate(spec);
|
||||||
.importPrivate(spec);
|
case SECRET_KEY ->
|
||||||
case SECRET_KEY -> ((SymmetricKeyImporter) algorithm.symmetricKeyImporter(spec.getClass()))
|
((SymmetricKeyImporter) algorithm.symmetricKeyImporter(spec.getClass())).importSecret(spec);
|
||||||
.importSecret(spec);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AlgorithmKeySpec createSpec(PersistentMapping mapping, byte[] encoded)
|
private static AlgorithmKeySpec createSpec(PersistentMapping mapping, byte[] encoded) throws KeyringException {
|
||||||
throws KeyringException {
|
|
||||||
if (mapping.specType == HmacKeyImportSpec.class) {
|
if (mapping.specType == HmacKeyImportSpec.class) {
|
||||||
return new HmacKeyImportSpec(mapping.hmacVariant.jcaName, encoded);
|
return new HmacKeyImportSpec(mapping.hmacVariant.jcaName, encoded);
|
||||||
}
|
}
|
||||||
Function<byte[], ? extends AlgorithmKeySpec> factory =
|
Function<byte[], ? extends AlgorithmKeySpec> factory = SPEC_FACTORIES.get(mapping.specType);
|
||||||
SPEC_FACTORIES.get(mapping.specType);
|
|
||||||
AlgorithmKeySpec result = factory == null ? null : factory.apply(encoded);
|
AlgorithmKeySpec result = factory == null ? null : factory.apply(encoded);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_MAPPING_INVALID);
|
throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_MAPPING_INVALID);
|
||||||
@@ -288,31 +268,27 @@ final class KeyringImportRegistry {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean matchesAlgorithm(Key imported, String algorithmId,
|
private static boolean matchesAlgorithm(Key imported, String algorithmId, HmacVariant hmacVariant) {
|
||||||
HmacVariant hmacVariant) {
|
|
||||||
if (ALGORITHM_HMAC.equals(algorithmId)) {
|
if (ALGORITHM_HMAC.equals(algorithmId)) {
|
||||||
return hmacVariant.jcaName.equals(imported.getAlgorithm());
|
return hmacVariant.jcaName.equals(imported.getAlgorithm());
|
||||||
}
|
}
|
||||||
if (ALGORITHM_AES.equals(algorithmId)) {
|
if (ALGORITHM_AES.equals(algorithmId)) {
|
||||||
return ALGORITHM_AES.equals(imported.getAlgorithm());
|
return ALGORITHM_AES.equals(imported.getAlgorithm());
|
||||||
}
|
}
|
||||||
if (ALGORITHM_CHACHA20.equals(algorithmId)
|
if (ALGORITHM_CHACHA20.equals(algorithmId) || ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) {
|
||||||
|| ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) {
|
|
||||||
return "ChaCha20".equals(imported.getAlgorithm());
|
return "ChaCha20".equals(imported.getAlgorithm());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean matchesSourceAlgorithm(Key source, String algorithmId,
|
private static boolean matchesSourceAlgorithm(Key source, String algorithmId, HmacVariant hmacVariant) {
|
||||||
HmacVariant hmacVariant) {
|
|
||||||
if (ALGORITHM_HMAC.equals(algorithmId)) {
|
if (ALGORITHM_HMAC.equals(algorithmId)) {
|
||||||
return hmacVariant.jcaName.equals(source.getAlgorithm());
|
return hmacVariant.jcaName.equals(source.getAlgorithm());
|
||||||
}
|
}
|
||||||
if (ALGORITHM_AES.equals(algorithmId)) {
|
if (ALGORITHM_AES.equals(algorithmId)) {
|
||||||
return ALGORITHM_AES.equals(source.getAlgorithm());
|
return ALGORITHM_AES.equals(source.getAlgorithm());
|
||||||
}
|
}
|
||||||
if (ALGORITHM_CHACHA20.equals(algorithmId)
|
if (ALGORITHM_CHACHA20.equals(algorithmId) || ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) {
|
||||||
|| ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) {
|
|
||||||
return "ChaCha20".equals(source.getAlgorithm());
|
return "ChaCha20".equals(source.getAlgorithm());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -329,10 +305,8 @@ final class KeyringImportRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<Class<? extends AlgorithmKeySpec>,
|
private static Map<Class<? extends AlgorithmKeySpec>, Function<byte[], ? extends AlgorithmKeySpec>> createSpecFactories() {
|
||||||
Function<byte[], ? extends AlgorithmKeySpec>> createSpecFactories() {
|
return Map.ofEntries(Map.entry(AesKeyImportSpec.class, AesKeyImportSpec::fromRaw),
|
||||||
return Map.ofEntries(
|
|
||||||
Map.entry(AesKeyImportSpec.class, AesKeyImportSpec::fromRaw),
|
|
||||||
Map.entry(ChaChaKeyImportSpec.class, ChaChaKeyImportSpec::fromRaw),
|
Map.entry(ChaChaKeyImportSpec.class, ChaChaKeyImportSpec::fromRaw),
|
||||||
Map.entry(BikePublicKeySpec.class, BikePublicKeySpec::new),
|
Map.entry(BikePublicKeySpec.class, BikePublicKeySpec::new),
|
||||||
Map.entry(BikePrivateKeySpec.class, BikePrivateKeySpec::new),
|
Map.entry(BikePrivateKeySpec.class, BikePrivateKeySpec::new),
|
||||||
@@ -380,73 +354,54 @@ final class KeyringImportRegistry {
|
|||||||
addAsymmetric(mappings, "CMCE", CmcePublicKeySpec.class, CmcePrivateKeySpec.class);
|
addAsymmetric(mappings, "CMCE", CmcePublicKeySpec.class, CmcePrivateKeySpec.class);
|
||||||
addAsymmetric(mappings, "DH", DhPublicKeySpec.class, DhPrivateKeySpec.class);
|
addAsymmetric(mappings, "DH", DhPublicKeySpec.class, DhPrivateKeySpec.class);
|
||||||
addAsymmetric(mappings, "ECDSA", EcdsaPublicKeySpec.class, EcdsaPrivateKeySpec.class);
|
addAsymmetric(mappings, "ECDSA", EcdsaPublicKeySpec.class, EcdsaPrivateKeySpec.class);
|
||||||
addAsymmetric(mappings, "Ed25519", Ed25519PublicKeySpec.class,
|
addAsymmetric(mappings, "Ed25519", Ed25519PublicKeySpec.class, Ed25519PrivateKeySpec.class);
|
||||||
Ed25519PrivateKeySpec.class);
|
addAsymmetric(mappings, "Ed448", Ed448PublicKeySpec.class, Ed448PrivateKeySpec.class);
|
||||||
addAsymmetric(mappings, "Ed448", Ed448PublicKeySpec.class,
|
addAsymmetric(mappings, "ElGamal", ElgamalPublicKeySpec.class, ElgamalPrivateKeySpec.class);
|
||||||
Ed448PrivateKeySpec.class);
|
addAsymmetric(mappings, "Frodo", FrodoPublicKeySpec.class, FrodoPrivateKeySpec.class);
|
||||||
addAsymmetric(mappings, "ElGamal", ElgamalPublicKeySpec.class,
|
|
||||||
ElgamalPrivateKeySpec.class);
|
|
||||||
addAsymmetric(mappings, "Frodo", FrodoPublicKeySpec.class,
|
|
||||||
FrodoPrivateKeySpec.class);
|
|
||||||
addAsymmetric(mappings, "HQC", HqcPublicKeySpec.class, HqcPrivateKeySpec.class);
|
addAsymmetric(mappings, "HQC", HqcPublicKeySpec.class, HqcPrivateKeySpec.class);
|
||||||
addAsymmetric(mappings, "ML-KEM", KyberPublicKeySpec.class,
|
addAsymmetric(mappings, "ML-KEM", KyberPublicKeySpec.class, KyberPrivateKeySpec.class);
|
||||||
KyberPrivateKeySpec.class);
|
addAsymmetric(mappings, "ML-DSA", MldsaPublicKeySpec.class, MldsaPrivateKeySpec.class);
|
||||||
addAsymmetric(mappings, "ML-DSA", MldsaPublicKeySpec.class,
|
|
||||||
MldsaPrivateKeySpec.class);
|
|
||||||
addAsymmetric(mappings, "NTRU", NtruPublicKeySpec.class, NtruPrivateKeySpec.class);
|
addAsymmetric(mappings, "NTRU", NtruPublicKeySpec.class, NtruPrivateKeySpec.class);
|
||||||
addAsymmetric(mappings, "NTRULPRime", NtrulPrimePublicKeySpec.class,
|
addAsymmetric(mappings, "NTRULPRime", NtrulPrimePublicKeySpec.class, NtrulPrimePrivateKeySpec.class);
|
||||||
NtrulPrimePrivateKeySpec.class);
|
addAsymmetric(mappings, "SNTRUPrime", SntruPrimePublicKeySpec.class, SntruPrimePrivateKeySpec.class);
|
||||||
addAsymmetric(mappings, "SNTRUPrime", SntruPrimePublicKeySpec.class,
|
|
||||||
SntruPrimePrivateKeySpec.class);
|
|
||||||
addAsymmetric(mappings, "RSA", RsaPublicKeySpec.class, RsaPrivateKeySpec.class);
|
addAsymmetric(mappings, "RSA", RsaPublicKeySpec.class, RsaPrivateKeySpec.class);
|
||||||
addAsymmetric(mappings, "SABER", SaberPublicKeySpec.class,
|
addAsymmetric(mappings, "SABER", SaberPublicKeySpec.class, SaberPrivateKeySpec.class);
|
||||||
SaberPrivateKeySpec.class);
|
addAsymmetric(mappings, "SLH-DSA", SlhDsaPublicKeySpec.class, SlhDsaPrivateKeySpec.class);
|
||||||
addAsymmetric(mappings, "SLH-DSA", SlhDsaPublicKeySpec.class,
|
addAsymmetric(mappings, "SPHINCS+", SphincsPlusPublicKeySpec.class, SphincsPlusPrivateKeySpec.class);
|
||||||
SlhDsaPrivateKeySpec.class);
|
|
||||||
addAsymmetric(mappings, "SPHINCS+", SphincsPlusPublicKeySpec.class,
|
|
||||||
SphincsPlusPrivateKeySpec.class);
|
|
||||||
addAsymmetric(mappings, "Xdh", XdhPublicKeySpec.class, XdhPrivateKeySpec.class);
|
addAsymmetric(mappings, "Xdh", XdhPublicKeySpec.class, XdhPrivateKeySpec.class);
|
||||||
add(mappings, ALGORITHM_AES, KeyringStore.Kind.SECRET_KEY,
|
add(mappings, ALGORITHM_AES, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.NONE,
|
||||||
KeyringStore.Encoding.RAW,
|
AesKeyImportSpec.class);
|
||||||
HmacVariant.NONE, AesKeyImportSpec.class);
|
add(mappings, ALGORITHM_CHACHA20, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.NONE,
|
||||||
add(mappings, ALGORITHM_CHACHA20, KeyringStore.Kind.SECRET_KEY,
|
ChaChaKeyImportSpec.class);
|
||||||
KeyringStore.Encoding.RAW,
|
add(mappings, ALGORITHM_CHACHA20_POLY1305, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW,
|
||||||
HmacVariant.NONE, ChaChaKeyImportSpec.class);
|
HmacVariant.NONE, ChaChaKeyImportSpec.class);
|
||||||
add(mappings, ALGORITHM_CHACHA20_POLY1305, KeyringStore.Kind.SECRET_KEY,
|
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.SHA256,
|
||||||
KeyringStore.Encoding.RAW, HmacVariant.NONE, ChaChaKeyImportSpec.class);
|
HmacKeyImportSpec.class);
|
||||||
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY,
|
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.SHA384,
|
||||||
KeyringStore.Encoding.RAW,
|
HmacKeyImportSpec.class);
|
||||||
HmacVariant.SHA256, HmacKeyImportSpec.class);
|
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.SHA512,
|
||||||
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY,
|
HmacKeyImportSpec.class);
|
||||||
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);
|
return Collections.unmodifiableMap(mappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addAsymmetric(Map<Tuple, PersistentMapping> mappings,
|
private static void addAsymmetric(Map<Tuple, PersistentMapping> mappings, String algorithmId,
|
||||||
String algorithmId, Class<? extends AlgorithmKeySpec> publicSpec,
|
Class<? extends AlgorithmKeySpec> publicSpec, Class<? extends AlgorithmKeySpec> privateSpec) {
|
||||||
Class<? extends AlgorithmKeySpec> privateSpec) {
|
add(mappings, algorithmId, KeyringStore.Kind.PUBLIC_KEY, KeyringStore.Encoding.X509, HmacVariant.NONE,
|
||||||
add(mappings, algorithmId, KeyringStore.Kind.PUBLIC_KEY,
|
publicSpec);
|
||||||
KeyringStore.Encoding.X509, HmacVariant.NONE, publicSpec);
|
add(mappings, algorithmId, KeyringStore.Kind.PRIVATE_KEY, KeyringStore.Encoding.PKCS8, HmacVariant.NONE,
|
||||||
add(mappings, algorithmId, KeyringStore.Kind.PRIVATE_KEY,
|
privateSpec);
|
||||||
KeyringStore.Encoding.PKCS8, HmacVariant.NONE, privateSpec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void add(Map<Tuple, PersistentMapping> mappings, String algorithmId,
|
private static void add(Map<Tuple, PersistentMapping> mappings, String algorithmId, KeyringStore.Kind kind,
|
||||||
KeyringStore.Kind kind, KeyringStore.Encoding encoding,
|
KeyringStore.Encoding encoding, HmacVariant hmacVariant, Class<? extends AlgorithmKeySpec> specType) {
|
||||||
HmacVariant hmacVariant, Class<? extends AlgorithmKeySpec> specType) {
|
|
||||||
Tuple tuple = new Tuple(algorithmId, kind, encoding, hmacVariant);
|
Tuple tuple = new Tuple(algorithmId, kind, encoding, hmacVariant);
|
||||||
PersistentMapping mapping = new PersistentMapping(algorithmId, kind,
|
PersistentMapping mapping = new PersistentMapping(algorithmId, kind, encoding, hmacVariant, specType);
|
||||||
encoding, hmacVariant, specType);
|
|
||||||
if (mappings.put(tuple, mapping) != null) {
|
if (mappings.put(tuple, mapping) != null) {
|
||||||
throw new IllegalStateException("Duplicate persistent key importer tuple");
|
throw new IllegalStateException("Duplicate persistent key importer tuple");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Tuple(String algorithmId, KeyringStore.Kind kind,
|
private record Tuple(String algorithmId, KeyringStore.Kind kind, KeyringStore.Encoding encoding,
|
||||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant) {
|
HmacVariant hmacVariant) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ final class KeyringNonceReservationKdf {
|
|||||||
private static final int STORE_ID_BYTES = 16;
|
private static final int STORE_ID_BYTES = 16;
|
||||||
private static final int OUTPUT_BYTES = 32;
|
private static final int OUTPUT_BYTES = 32;
|
||||||
private static final String HMAC_SHA256 = "HmacSHA256";
|
private static final String HMAC_SHA256 = "HmacSHA256";
|
||||||
private static final String DOMAIN_LABEL =
|
private static final String DOMAIN_LABEL = "zeroecho:keyring:nonce-reservation-mac:v1";
|
||||||
"zeroecho:keyring:nonce-reservation-mac:v1";
|
|
||||||
|
|
||||||
private KeyringNonceReservationKdf() {
|
private KeyringNonceReservationKdf() {
|
||||||
}
|
}
|
||||||
@@ -29,14 +28,13 @@ final class KeyringNonceReservationKdf {
|
|||||||
* Derives the store-specific nonce-reservation MAC key.
|
* Derives the store-specific nonce-reservation MAC key.
|
||||||
*
|
*
|
||||||
* @param masterKey borrowed 256-bit store master 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
|
* @return newly owned 256-bit derived key
|
||||||
* @throws GeneralSecurityException if HMAC-SHA-256 is unavailable
|
* @throws GeneralSecurityException if HMAC-SHA-256 is unavailable
|
||||||
*/
|
*/
|
||||||
/* default */ static byte[] derive(byte[] masterKey, byte[] storeId)
|
/* default */ static byte[] derive(byte[] masterKey, byte[] storeId) throws GeneralSecurityException {
|
||||||
throws GeneralSecurityException {
|
if (masterKey == null || masterKey.length != MASTER_KEY_BYTES || storeId == null
|
||||||
if (masterKey == null || masterKey.length != MASTER_KEY_BYTES
|
|| storeId.length != STORE_ID_BYTES) {
|
||||||
|| storeId == null || storeId.length != STORE_ID_BYTES) {
|
|
||||||
throw new IllegalArgumentException("Invalid keyring derivation input");
|
throw new IllegalArgumentException("Invalid keyring derivation input");
|
||||||
}
|
}
|
||||||
byte[] salt = storeId.clone();
|
byte[] salt = storeId.clone();
|
||||||
@@ -61,8 +59,7 @@ final class KeyringNonceReservationKdf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] hmac(byte[] key, byte[] input)
|
private static byte[] hmac(byte[] key, byte[] input) throws GeneralSecurityException {
|
||||||
throws GeneralSecurityException {
|
|
||||||
Mac mac = Mac.getInstance(HMAC_SHA256);
|
Mac mac = Mac.getInstance(HMAC_SHA256);
|
||||||
mac.init(new SecretKeySpec(key, HMAC_SHA256));
|
mac.init(new SecretKeySpec(key, HMAC_SHA256));
|
||||||
return mac.doFinal(input);
|
return mac.doFinal(input);
|
||||||
|
|||||||
@@ -14,14 +14,18 @@ import javax.security.auth.Destroyable;
|
|||||||
/**
|
/**
|
||||||
* Destroyable owner of a keyring password.
|
* 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
|
* 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
|
* returned copy belongs to the receiver and must be cleared immediately after
|
||||||
* key derivation. This object never creates an immutable password
|
* 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
|
* <p>
|
||||||
* access fail deterministically.</p>
|
* Instances are thread-safe. Destruction is idempotent and makes subsequent
|
||||||
|
* access fail deterministically.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public final class KeyringPassword implements Destroyable, AutoCloseable {
|
public final class KeyringPassword implements Destroyable, AutoCloseable {
|
||||||
private final ReentrantLock lifecycleLock = new ReentrantLock();
|
private final ReentrantLock lifecycleLock = new ReentrantLock();
|
||||||
@@ -32,7 +36,7 @@ public final class KeyringPassword implements Destroyable, AutoCloseable {
|
|||||||
* Creates a password owner.
|
* Creates a password owner.
|
||||||
*
|
*
|
||||||
* @param password password characters, which are defensively copied
|
* @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
|
* @throws IllegalArgumentException if {@code password} is empty
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.UseVarargs")
|
@SuppressWarnings("PMD.UseVarargs")
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ package zeroecho.core.storage;
|
|||||||
* Operational limits applied while opening an encrypted software keyring.
|
* Operational limits applied while opening an encrypted software keyring.
|
||||||
*
|
*
|
||||||
* @param operationalIterationMaximum maximum accepted PBKDF2 iteration count;
|
* @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) {
|
public record KeyringProtection(int operationalIterationMaximum) {
|
||||||
/** Iterations used when a new keyring is created. */
|
/** Iterations used when a new keyring is created. */
|
||||||
@@ -21,8 +22,8 @@ public record KeyringProtection(int operationalIterationMaximum) {
|
|||||||
/**
|
/**
|
||||||
* Validates the operational limit.
|
* Validates the operational limit.
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if the limit is below the creation
|
* @throws IllegalArgumentException if the limit is below the creation setting
|
||||||
* setting or above the absolute operational maximum
|
* or above the absolute operational maximum
|
||||||
*/
|
*/
|
||||||
public KeyringProtection {
|
public KeyringProtection {
|
||||||
if (operationalIterationMaximum < CREATION_ITERATIONS
|
if (operationalIterationMaximum < CREATION_ITERATIONS
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ package zeroecho.core.storage;
|
|||||||
/**
|
/**
|
||||||
* Fills keyring randomness buffers.
|
* Fills keyring randomness buffers.
|
||||||
*
|
*
|
||||||
* <p>This package-private seam supports deterministic format tests; production
|
* <p>
|
||||||
* creation uses the authoritative shared secure random source.</p>
|
* This package-private seam supports deterministic format tests; production
|
||||||
|
* creation uses the authoritative shared secure random source.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
interface KeyringRandomBytes {
|
interface KeyringRandomBytes {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -53,15 +53,15 @@
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Unlock passwords are destroyable, transfer ownership to the receiver, and
|
* Unlock passwords are destroyable, transfer ownership to the receiver, and are
|
||||||
* are destroyed immediately after the master key is unwrapped. The unlocked
|
* destroyed immediately after the master key is unwrapped. The unlocked store
|
||||||
* store retains the master key, its domain-separated nonce-reservation MAC
|
* retains the master key, its domain-separated nonce-reservation MAC key, and
|
||||||
* key, and encrypted entry records; closing the store clears this material.
|
* encrypted entry records; closing the store clears this material. The store
|
||||||
* The store requires a POSIX filesystem on which owner-only permissions can be
|
* requires a POSIX filesystem on which owner-only permissions can be verified.
|
||||||
* verified. A directory-force failure after atomic replacement makes the open
|
* A directory-force failure after atomic replacement makes the open instance
|
||||||
* instance unusable until close and authenticated reopen resolves which
|
* unusable until close and authenticated reopen resolves which complete image
|
||||||
* complete image is current. Non-exportable keys must remain behind an
|
* is current. Non-exportable keys must remain behind an external provider
|
||||||
* external provider reference.
|
* reference.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -124,8 +124,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
|
|||||||
public static TagEngineBuilder<byte[]> digest(final ZeroEchoSession session, final DigestSpec spec) {
|
public static TagEngineBuilder<byte[]> digest(final ZeroEchoSession session, final DigestSpec spec) {
|
||||||
Objects.requireNonNull(session, "session");
|
Objects.requireNonNull(session, "session");
|
||||||
final DigestSpec s = spec == null ? DigestSpec.sha256() : spec;
|
final DigestSpec s = spec == null ? DigestSpec.sha256() : spec;
|
||||||
return new TagEngineBuilder<>(
|
return new TagEngineBuilder<>(() -> session.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE, s));
|
||||||
() -> 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
|
* @return a builder that produces Ed25519 signature engines in SIGN mode
|
||||||
* @throws NullPointerException if {@code privateKey} is {@code null}
|
* @throws NullPointerException if {@code privateKey} is {@code null}
|
||||||
*/
|
*/
|
||||||
public static TagEngineBuilder<Signature> ed25519Sign(final ZeroEchoSession session,
|
public static TagEngineBuilder<Signature> ed25519Sign(final ZeroEchoSession session, final PrivateKey privateKey) {
|
||||||
final PrivateKey privateKey) {
|
|
||||||
Objects.requireNonNull(privateKey, PRIVATE_KEY);
|
Objects.requireNonNull(privateKey, PRIVATE_KEY);
|
||||||
return signature(session, "Ed25519", privateKey, VoidSpec.INSTANCE);
|
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
|
* @return a builder that produces Ed25519 signature engines in VERIFY mode
|
||||||
* @throws NullPointerException if {@code publicKey} is {@code null}
|
* @throws NullPointerException if {@code publicKey} is {@code null}
|
||||||
*/
|
*/
|
||||||
public static TagEngineBuilder<Signature> ed25519Verify(final ZeroEchoSession session,
|
public static TagEngineBuilder<Signature> ed25519Verify(final ZeroEchoSession session, final PublicKey publicKey) {
|
||||||
final PublicKey publicKey) {
|
|
||||||
Objects.requireNonNull(publicKey, PUBLIC_KEY);
|
Objects.requireNonNull(publicKey, PUBLIC_KEY);
|
||||||
return signature(session, "Ed25519", publicKey, VoidSpec.INSTANCE);
|
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,
|
public static TagEngineBuilder<Signature> rsaSign(final ZeroEchoSession session, final PrivateKey privateKey,
|
||||||
final RsaSigSpec spec) {
|
final RsaSigSpec spec) {
|
||||||
Objects.requireNonNull(privateKey, PRIVATE_KEY);
|
Objects.requireNonNull(privateKey, PRIVATE_KEY);
|
||||||
return signature(session, "RSA", privateKey,
|
return signature(session, "RSA", privateKey, spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec);
|
||||||
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,
|
public static TagEngineBuilder<Signature> rsaVerify(final ZeroEchoSession session, final PublicKey publicKey,
|
||||||
final RsaSigSpec spec) {
|
final RsaSigSpec spec) {
|
||||||
Objects.requireNonNull(publicKey, PUBLIC_KEY);
|
Objects.requireNonNull(publicKey, PUBLIC_KEY);
|
||||||
return signature(session, "RSA", publicKey,
|
return signature(session, "RSA", publicKey, spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec);
|
||||||
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
|
* @return a builder that produces SLH-DSA signature engines in SIGN mode
|
||||||
* @throws NullPointerException if {@code privateKey} is {@code null}
|
* @throws NullPointerException if {@code privateKey} is {@code null}
|
||||||
*/
|
*/
|
||||||
public static TagEngineBuilder<Signature> slhDsaSign(final ZeroEchoSession session,
|
public static TagEngineBuilder<Signature> slhDsaSign(final ZeroEchoSession session, final PrivateKey privateKey) {
|
||||||
final PrivateKey privateKey) {
|
|
||||||
Objects.requireNonNull(privateKey, PRIVATE_KEY);
|
Objects.requireNonNull(privateKey, PRIVATE_KEY);
|
||||||
return signature(session, "SLH-DSA", privateKey, VoidSpec.INSTANCE);
|
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
|
* @return a builder that produces SLH-DSA signature engines in VERIFY mode
|
||||||
* @throws NullPointerException if {@code publicKey} is {@code null}
|
* @throws NullPointerException if {@code publicKey} is {@code null}
|
||||||
*/
|
*/
|
||||||
public static TagEngineBuilder<Signature> slhDsaVerify(final ZeroEchoSession session,
|
public static TagEngineBuilder<Signature> slhDsaVerify(final ZeroEchoSession session, final PublicKey publicKey) {
|
||||||
final PublicKey publicKey) {
|
|
||||||
Objects.requireNonNull(publicKey, PUBLIC_KEY);
|
Objects.requireNonNull(publicKey, PUBLIC_KEY);
|
||||||
return signature(session, "SLH-DSA", publicKey, VoidSpec.INSTANCE);
|
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
|
* @return a builder that produces ML-DSA signature engines in SIGN mode
|
||||||
* @throws NullPointerException if {@code privateKey} is {@code null}
|
* @throws NullPointerException if {@code privateKey} is {@code null}
|
||||||
*/
|
*/
|
||||||
public static TagEngineBuilder<Signature> mldsaSign(final ZeroEchoSession session,
|
public static TagEngineBuilder<Signature> mldsaSign(final ZeroEchoSession session, final PrivateKey privateKey) {
|
||||||
final PrivateKey privateKey) {
|
|
||||||
Objects.requireNonNull(privateKey, PRIVATE_KEY);
|
Objects.requireNonNull(privateKey, PRIVATE_KEY);
|
||||||
return signature(session, "ML-DSA", privateKey, VoidSpec.INSTANCE);
|
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
|
* @return a builder that produces ML-DSA signature engines in VERIFY mode
|
||||||
* @throws NullPointerException if {@code publicKey} is {@code null}
|
* @throws NullPointerException if {@code publicKey} is {@code null}
|
||||||
*/
|
*/
|
||||||
public static TagEngineBuilder<Signature> mldsaVerify(final ZeroEchoSession session,
|
public static TagEngineBuilder<Signature> mldsaVerify(final ZeroEchoSession session, final PublicKey publicKey) {
|
||||||
final PublicKey publicKey) {
|
|
||||||
Objects.requireNonNull(publicKey, PUBLIC_KEY);
|
Objects.requireNonNull(publicKey, PUBLIC_KEY);
|
||||||
return signature(session, "ML-DSA", publicKey, VoidSpec.INSTANCE);
|
return signature(session, "ML-DSA", publicKey, VoidSpec.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,11 @@ import zeroecho.core.spi.SymmetricKeyImporter;
|
|||||||
/**
|
/**
|
||||||
* Session-bound entry point for exact key-material operations.
|
* 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
|
* 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
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
@@ -69,13 +71,12 @@ public final class KeyBuilders {
|
|||||||
* Resolves an exact symmetric generator.
|
* Resolves an exact symmetric generator.
|
||||||
*
|
*
|
||||||
* @param algorithmId canonical algorithm identifier
|
* @param algorithmId canonical algorithm identifier
|
||||||
* @param specType exact specification class
|
* @param specType exact specification class
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return guaranteed generator
|
* @return guaranteed generator
|
||||||
* @throws IllegalArgumentException if the capability is absent
|
* @throws IllegalArgumentException if the capability is absent
|
||||||
*/
|
*/
|
||||||
public <S extends AlgorithmKeySpec> SymmetricKeyGenerator<S> generator(String algorithmId,
|
public <S extends AlgorithmKeySpec> SymmetricKeyGenerator<S> generator(String algorithmId, Class<S> specType) {
|
||||||
Class<S> specType) {
|
|
||||||
CryptoAlgorithm algorithm = session.require(algorithmId);
|
CryptoAlgorithm algorithm = session.require(algorithmId);
|
||||||
SymmetricKeyGenerator<S> delegate = algorithm.symmetricKeyGenerator(specType);
|
SymmetricKeyGenerator<S> delegate = algorithm.symmetricKeyGenerator(specType);
|
||||||
return spec -> {
|
return spec -> {
|
||||||
@@ -89,13 +90,12 @@ public final class KeyBuilders {
|
|||||||
* Resolves an exact symmetric importer.
|
* Resolves an exact symmetric importer.
|
||||||
*
|
*
|
||||||
* @param algorithmId canonical algorithm identifier
|
* @param algorithmId canonical algorithm identifier
|
||||||
* @param specType exact specification class
|
* @param specType exact specification class
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return guaranteed importer
|
* @return guaranteed importer
|
||||||
* @throws IllegalArgumentException if the capability is absent
|
* @throws IllegalArgumentException if the capability is absent
|
||||||
*/
|
*/
|
||||||
public <S extends AlgorithmKeySpec> SymmetricKeyImporter<S> importer(String algorithmId,
|
public <S extends AlgorithmKeySpec> SymmetricKeyImporter<S> importer(String algorithmId, Class<S> specType) {
|
||||||
Class<S> specType) {
|
|
||||||
CryptoAlgorithm algorithm = session.require(algorithmId);
|
CryptoAlgorithm algorithm = session.require(algorithmId);
|
||||||
SymmetricKeyImporter<S> delegate = algorithm.symmetricKeyImporter(specType);
|
SymmetricKeyImporter<S> delegate = algorithm.symmetricKeyImporter(specType);
|
||||||
return spec -> {
|
return spec -> {
|
||||||
@@ -109,12 +109,13 @@ public final class KeyBuilders {
|
|||||||
* Generates a symmetric key using the exact runtime specification type.
|
* Generates a symmetric key using the exact runtime specification type.
|
||||||
*
|
*
|
||||||
* @param algorithmId canonical algorithm identifier
|
* @param algorithmId canonical algorithm identifier
|
||||||
* @param spec generation specification
|
* @param spec generation specification
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return generated secret key
|
* @return generated secret key
|
||||||
* @throws java.security.GeneralSecurityException if generation fails
|
* @throws java.security.GeneralSecurityException if generation fails
|
||||||
* @throws IllegalArgumentException if the capability is absent
|
* @throws IllegalArgumentException if the capability is absent
|
||||||
* @throws NullPointerException if {@code spec} is {@code null}
|
* @throws NullPointerException if {@code spec} is
|
||||||
|
* {@code null}
|
||||||
*/
|
*/
|
||||||
public <S extends AlgorithmKeySpec> SecretKey generate(String algorithmId, S spec)
|
public <S extends AlgorithmKeySpec> SecretKey generate(String algorithmId, S spec)
|
||||||
throws java.security.GeneralSecurityException {
|
throws java.security.GeneralSecurityException {
|
||||||
@@ -128,12 +129,13 @@ public final class KeyBuilders {
|
|||||||
* Imports a symmetric key using the exact runtime specification type.
|
* Imports a symmetric key using the exact runtime specification type.
|
||||||
*
|
*
|
||||||
* @param algorithmId canonical algorithm identifier
|
* @param algorithmId canonical algorithm identifier
|
||||||
* @param spec import specification
|
* @param spec import specification
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return imported secret key
|
* @return imported secret key
|
||||||
* @throws java.security.GeneralSecurityException if import fails
|
* @throws java.security.GeneralSecurityException if import fails
|
||||||
* @throws IllegalArgumentException if the capability is absent
|
* @throws IllegalArgumentException if the capability is absent
|
||||||
* @throws NullPointerException if {@code spec} is {@code null}
|
* @throws NullPointerException if {@code spec} is
|
||||||
|
* {@code null}
|
||||||
*/
|
*/
|
||||||
public <S extends AlgorithmKeySpec> SecretKey importKey(String algorithmId, S spec)
|
public <S extends AlgorithmKeySpec> SecretKey importKey(String algorithmId, S spec)
|
||||||
throws java.security.GeneralSecurityException {
|
throws java.security.GeneralSecurityException {
|
||||||
@@ -155,8 +157,8 @@ public final class KeyBuilders {
|
|||||||
* Resolves an exact key-pair generator.
|
* Resolves an exact key-pair generator.
|
||||||
*
|
*
|
||||||
* @param algorithmId canonical algorithm identifier
|
* @param algorithmId canonical algorithm identifier
|
||||||
* @param specType exact specification class
|
* @param specType exact specification class
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return guaranteed generator
|
* @return guaranteed generator
|
||||||
* @throws IllegalArgumentException if the capability is absent
|
* @throws IllegalArgumentException if the capability is absent
|
||||||
*/
|
*/
|
||||||
@@ -175,13 +177,12 @@ public final class KeyBuilders {
|
|||||||
* Resolves an exact public-key importer.
|
* Resolves an exact public-key importer.
|
||||||
*
|
*
|
||||||
* @param algorithmId canonical algorithm identifier
|
* @param algorithmId canonical algorithm identifier
|
||||||
* @param specType exact specification class
|
* @param specType exact specification class
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return guaranteed importer
|
* @return guaranteed importer
|
||||||
* @throws IllegalArgumentException if the capability is absent
|
* @throws IllegalArgumentException if the capability is absent
|
||||||
*/
|
*/
|
||||||
public <S extends AlgorithmKeySpec> PublicKeyImporter<S> publicImporter(String algorithmId,
|
public <S extends AlgorithmKeySpec> PublicKeyImporter<S> publicImporter(String algorithmId, Class<S> specType) {
|
||||||
Class<S> specType) {
|
|
||||||
CryptoAlgorithm algorithm = session.require(algorithmId);
|
CryptoAlgorithm algorithm = session.require(algorithmId);
|
||||||
PublicKeyImporter<S> delegate = algorithm.publicKeyImporter(specType);
|
PublicKeyImporter<S> delegate = algorithm.publicKeyImporter(specType);
|
||||||
return spec -> {
|
return spec -> {
|
||||||
@@ -195,8 +196,8 @@ public final class KeyBuilders {
|
|||||||
* Resolves an exact private-key importer.
|
* Resolves an exact private-key importer.
|
||||||
*
|
*
|
||||||
* @param algorithmId canonical algorithm identifier
|
* @param algorithmId canonical algorithm identifier
|
||||||
* @param specType exact specification class
|
* @param specType exact specification class
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return guaranteed importer
|
* @return guaranteed importer
|
||||||
* @throws IllegalArgumentException if the capability is absent
|
* @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.
|
* Generates a key pair using the exact runtime specification type.
|
||||||
*
|
*
|
||||||
* @param algorithmId canonical algorithm identifier
|
* @param algorithmId canonical algorithm identifier
|
||||||
* @param spec generation specification
|
* @param spec generation specification
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return generated key pair
|
* @return generated key pair
|
||||||
* @throws java.security.GeneralSecurityException if generation fails
|
* @throws java.security.GeneralSecurityException if generation fails
|
||||||
* @throws IllegalArgumentException if the capability is absent
|
* @throws IllegalArgumentException if the capability is absent
|
||||||
* @throws NullPointerException if {@code spec} is {@code null}
|
* @throws NullPointerException if {@code spec} is
|
||||||
|
* {@code null}
|
||||||
*/
|
*/
|
||||||
public <S extends AlgorithmKeySpec> KeyPair generateKeyPair(String algorithmId, S spec)
|
public <S extends AlgorithmKeySpec> KeyPair generateKeyPair(String algorithmId, S spec)
|
||||||
throws java.security.GeneralSecurityException {
|
throws java.security.GeneralSecurityException {
|
||||||
@@ -234,12 +236,13 @@ public final class KeyBuilders {
|
|||||||
* Imports a public key using the exact runtime specification type.
|
* Imports a public key using the exact runtime specification type.
|
||||||
*
|
*
|
||||||
* @param algorithmId canonical algorithm identifier
|
* @param algorithmId canonical algorithm identifier
|
||||||
* @param spec public-key import specification
|
* @param spec public-key import specification
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return imported public key
|
* @return imported public key
|
||||||
* @throws java.security.GeneralSecurityException if import fails
|
* @throws java.security.GeneralSecurityException if import fails
|
||||||
* @throws IllegalArgumentException if the capability is absent
|
* @throws IllegalArgumentException if the capability is absent
|
||||||
* @throws NullPointerException if {@code spec} is {@code null}
|
* @throws NullPointerException if {@code spec} is
|
||||||
|
* {@code null}
|
||||||
*/
|
*/
|
||||||
public <S extends AlgorithmKeySpec> PublicKey importPublic(String algorithmId, S spec)
|
public <S extends AlgorithmKeySpec> PublicKey importPublic(String algorithmId, S spec)
|
||||||
throws java.security.GeneralSecurityException {
|
throws java.security.GeneralSecurityException {
|
||||||
@@ -253,12 +256,13 @@ public final class KeyBuilders {
|
|||||||
* Imports a private key using the exact runtime specification type.
|
* Imports a private key using the exact runtime specification type.
|
||||||
*
|
*
|
||||||
* @param algorithmId canonical algorithm identifier
|
* @param algorithmId canonical algorithm identifier
|
||||||
* @param spec private-key import specification
|
* @param spec private-key import specification
|
||||||
* @param <S> specification type
|
* @param <S> specification type
|
||||||
* @return imported private key
|
* @return imported private key
|
||||||
* @throws java.security.GeneralSecurityException if import fails
|
* @throws java.security.GeneralSecurityException if import fails
|
||||||
* @throws IllegalArgumentException if the capability is absent
|
* @throws IllegalArgumentException if the capability is absent
|
||||||
* @throws NullPointerException if {@code spec} is {@code null}
|
* @throws NullPointerException if {@code spec} is
|
||||||
|
* {@code null}
|
||||||
*/
|
*/
|
||||||
public <S extends AlgorithmKeySpec> PrivateKey importPrivate(String algorithmId, S spec)
|
public <S extends AlgorithmKeySpec> PrivateKey importPrivate(String algorithmId, S spec)
|
||||||
throws java.security.GeneralSecurityException {
|
throws java.security.GeneralSecurityException {
|
||||||
|
|||||||
@@ -8,10 +8,11 @@
|
|||||||
package zeroecho.sdk;
|
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
|
* @param operationalMaximum largest iteration count accepted from trusted
|
||||||
* configuration
|
* local configuration
|
||||||
* @param absoluteDecodedMaximum hard safety ceiling for untrusted decoded data
|
* @param absoluteDecodedMaximum hard safety ceiling for untrusted decoded data
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
@@ -41,8 +42,8 @@ public record Pbkdf2Limits(int operationalMaximum, int absoluteDecodedMaximum) {
|
|||||||
*/
|
*/
|
||||||
public void validateTrusted(int iterations) {
|
public void validateTrusted(int iterations) {
|
||||||
if (iterations < MINIMUM || iterations > operationalMaximum) {
|
if (iterations < MINIMUM || iterations > operationalMaximum) {
|
||||||
throw new IllegalArgumentException("PBKDF2 iterations must be in range " + MINIMUM + ".."
|
throw new IllegalArgumentException(
|
||||||
+ operationalMaximum + ": " + iterations);
|
"PBKDF2 iterations must be in range " + MINIMUM + ".." + operationalMaximum + ": " + iterations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,8 +108,8 @@ public final class ZeroEchoSession {
|
|||||||
this(CryptoPolicy.permissive(), AuditListener.noop(), AuditMode.OFF, null);
|
this(CryptoPolicy.permissive(), AuditListener.noop(), AuditMode.OFF, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ZeroEchoSession(CryptoPolicy<ContextSpec, Key> policy, AuditListener auditListener,
|
private ZeroEchoSession(CryptoPolicy<ContextSpec, Key> policy, AuditListener auditListener, AuditMode auditMode,
|
||||||
AuditMode auditMode, Pbkdf2Limits pbkdf2Limits) {
|
Pbkdf2Limits pbkdf2Limits) {
|
||||||
this.policy = Objects.requireNonNull(policy, "policy must not be null");
|
this.policy = Objects.requireNonNull(policy, "policy must not be null");
|
||||||
this.auditListener = Objects.requireNonNull(auditListener, "auditListener must not be null");
|
this.auditListener = Objects.requireNonNull(auditListener, "auditListener must not be null");
|
||||||
this.auditSink = AuditListeners.bestEffort(auditListener);
|
this.auditSink = AuditListeners.bestEffort(auditListener);
|
||||||
@@ -143,8 +143,7 @@ public final class ZeroEchoSession {
|
|||||||
*/
|
*/
|
||||||
public ZeroEchoSession withAuditListener(AuditListener newAuditListener) {
|
public ZeroEchoSession withAuditListener(AuditListener newAuditListener) {
|
||||||
return new ZeroEchoSession(policy,
|
return new ZeroEchoSession(policy,
|
||||||
Objects.requireNonNull(newAuditListener, "newAuditListener must not be null"), auditMode,
|
Objects.requireNonNull(newAuditListener, "newAuditListener must not be null"), auditMode, pbkdf2Limits);
|
||||||
pbkdf2Limits);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -198,8 +197,8 @@ public final class ZeroEchoSession {
|
|||||||
* Returns the audit listener owned by this session.
|
* Returns the audit listener owned by this session.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The returned listener is the configured strategy, not mutable session
|
* The returned listener is the configured strategy, not mutable session state.
|
||||||
* state. It is exposed to support manual audit mode.
|
* It is exposed to support manual audit mode.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @return the non-null audit listener
|
* @return the non-null audit listener
|
||||||
@@ -254,8 +253,8 @@ public final class ZeroEchoSession {
|
|||||||
* @param id canonical algorithm identifier
|
* @param id canonical algorithm identifier
|
||||||
* @param role intended key usage
|
* @param role intended key usage
|
||||||
* @param key key compatible with the selected algorithm and role
|
* @param key key compatible with the selected algorithm and role
|
||||||
* @param spec optional context specification, or {@code null} for the
|
* @param spec optional context specification, or {@code null} for the algorithm
|
||||||
* algorithm default
|
* default
|
||||||
* @param <C> context type
|
* @param <C> context type
|
||||||
* @param <K> key type
|
* @param <K> key type
|
||||||
* @param <S> context specification type
|
* @param <S> context specification type
|
||||||
@@ -275,8 +274,8 @@ public final class ZeroEchoSession {
|
|||||||
return finishContext(algorithm, context, role, spec);
|
return finishContext(algorithm, context, role, spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <C extends CryptoContext, S extends ContextSpec> C finishContext(CryptoAlgorithm algorithm,
|
private <C extends CryptoContext, S extends ContextSpec> C finishContext(CryptoAlgorithm algorithm, C context,
|
||||||
C context, KeyUsage role, S spec) {
|
KeyUsage role, S spec) {
|
||||||
if (auditMode == AuditMode.OFF) {
|
if (auditMode == AuditMode.OFF) {
|
||||||
notifyContextCreated(algorithm, role, spec);
|
notifyContextCreated(algorithm, role, spec);
|
||||||
return context;
|
return context;
|
||||||
@@ -321,14 +320,15 @@ public final class ZeroEchoSession {
|
|||||||
* Destroys a key and verifies that it entered the destroyed state.
|
* Destroys a key and verifies that it entered the destroyed state.
|
||||||
*
|
*
|
||||||
* @param algorithmId algorithm identifier used as audit metadata
|
* @param algorithmId algorithm identifier used as audit metadata
|
||||||
* @param provider provider name used as audit metadata
|
* @param provider provider name used as audit metadata
|
||||||
* @param key key to destroy; must not be {@code null}
|
* @param key key to destroy; must not be {@code null}
|
||||||
* @return {@code true} only when this call transitions the key to destroyed;
|
* @return {@code true} only when this call transitions the key to destroyed;
|
||||||
* {@code false} for a non-destroyable or already destroyed key
|
* {@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
|
* @throws DestroyFailedException if destruction fails or the key does not
|
||||||
* report itself destroyed afterward
|
* 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 {
|
public boolean destroyKey(String algorithmId, String provider, Key key) throws DestroyFailedException {
|
||||||
Objects.requireNonNull(key, "key must not be null");
|
Objects.requireNonNull(key, "key must not be null");
|
||||||
@@ -352,12 +352,10 @@ public final class ZeroEchoSession {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <S extends ContextSpec> void notifyContextCreated(CryptoAlgorithm algorithm,
|
private <S extends ContextSpec> void notifyContextCreated(CryptoAlgorithm algorithm, KeyUsage role, S spec) {
|
||||||
KeyUsage role, S spec) {
|
Map<String, Object> metadata = spec == null ? Map.of() : Map.of("specType", spec.getClass().getName());
|
||||||
Map<String, Object> metadata = spec == null ? Map.of()
|
auditSink.onContextCreatedMeta(UUID.randomUUID().toString(), algorithm.id(), algorithm.providerName(), role,
|
||||||
: Map.of("specType", spec.getClass().getName());
|
"n/a", metadata);
|
||||||
auditSink.onContextCreatedMeta(UUID.randomUUID().toString(), algorithm.id(), algorithm.providerName(),
|
|
||||||
role, "n/a", metadata);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* default */ void notifyKeyPairGenerated(CryptoAlgorithm algorithm, AlgorithmKeySpec spec, KeyPair keyPair) {
|
/* default */ void notifyKeyPairGenerated(CryptoAlgorithm algorithm, AlgorithmKeySpec spec, KeyPair keyPair) {
|
||||||
|
|||||||
@@ -342,10 +342,8 @@ public final class HybridKexBuilder {
|
|||||||
if (pqcAlgId == null) {
|
if (pqcAlgId == null) {
|
||||||
throw new IllegalStateException("pqc algorithm id must be set");
|
throw new IllegalStateException("pqc algorithm id must be set");
|
||||||
}
|
}
|
||||||
if (classicMode == ClassicMode.CLASSIC_AGREEMENT
|
if (classicMode == ClassicMode.CLASSIC_AGREEMENT && (classicPrivate == null || classicPeerPublic == null)) {
|
||||||
&& (classicPrivate == null || classicPeerPublic == null)) {
|
throw new IllegalStateException("classic private key and peer public must be set for CLASSIC_AGREEMENT");
|
||||||
throw new IllegalStateException(
|
|
||||||
"classic private key and peer public must be set for CLASSIC_AGREEMENT");
|
|
||||||
}
|
}
|
||||||
if (classicMode == ClassicMode.PAIR_MESSAGE && classicKeyPair == null) {
|
if (classicMode == ClassicMode.PAIR_MESSAGE && classicKeyPair == null) {
|
||||||
throw new IllegalStateException("classic key pair must be set for PAIR_MESSAGE");
|
throw new IllegalStateException("classic key pair must be set for PAIR_MESSAGE");
|
||||||
|
|||||||
@@ -77,12 +77,13 @@ import zeroecho.sdk.hybrid.signature.HybridSignatureProfile;
|
|||||||
* <li>{@link #single(ZeroEchoSession)}: constructs a non-hybrid
|
* <li>{@link #single(ZeroEchoSession)}: constructs a non-hybrid
|
||||||
* {@code SignatureContext}.</li>
|
* {@code SignatureContext}.</li>
|
||||||
* <li>{@link #hybrid(ZeroEchoSession)}: constructs a hybrid
|
* <li>{@link #hybrid(ZeroEchoSession)}: constructs a hybrid
|
||||||
* {@code SignatureContext} via
|
* {@code SignatureContext} via {@link HybridSignatureContexts}.</li>
|
||||||
* {@link HybridSignatureContexts}.</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>Context construction is in-memory. Checked I/O failures arise only when a
|
* <p>
|
||||||
* built stream is attached or processed.</p>
|
* Context construction is in-memory. Checked I/O failures arise only when a
|
||||||
|
* built stream is attached or processed.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
@@ -253,8 +254,8 @@ public final class SignatureTrailerDataContentBuilder implements DataContentBuil
|
|||||||
Objects.requireNonNull(algorithmId, "algorithmId");
|
Objects.requireNonNull(algorithmId, "algorithmId");
|
||||||
Objects.requireNonNull(privateKey, "privateKey");
|
Objects.requireNonNull(privateKey, "privateKey");
|
||||||
|
|
||||||
Supplier<TagEngine<Signature>> factory = () -> session.createContext(algorithmId, KeyUsage.SIGN,
|
Supplier<TagEngine<Signature>> factory = () -> session.createContext(algorithmId, KeyUsage.SIGN, privateKey,
|
||||||
privateKey, spec);
|
spec);
|
||||||
|
|
||||||
return core(factory);
|
return core(factory);
|
||||||
}
|
}
|
||||||
@@ -293,8 +294,8 @@ public final class SignatureTrailerDataContentBuilder implements DataContentBuil
|
|||||||
Objects.requireNonNull(algorithmId, "algorithmId");
|
Objects.requireNonNull(algorithmId, "algorithmId");
|
||||||
Objects.requireNonNull(publicKey, "publicKey");
|
Objects.requireNonNull(publicKey, "publicKey");
|
||||||
|
|
||||||
Supplier<TagEngine<Signature>> factory = () -> session.createContext(algorithmId,
|
Supplier<TagEngine<Signature>> factory = () -> session.createContext(algorithmId, KeyUsage.VERIFY,
|
||||||
KeyUsage.VERIFY, publicKey, spec);
|
publicKey, spec);
|
||||||
|
|
||||||
return core(factory);
|
return core(factory);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -567,8 +567,7 @@ public final class ChaChaDataContentBuilder implements DataContentBuilder<DataCo
|
|||||||
* <p>
|
* <p>
|
||||||
* The actual cipher work is delegated to an
|
* The actual cipher work is delegated to an
|
||||||
* {@link zeroecho.core.context.EncryptionContext} created through
|
* {@link zeroecho.core.context.EncryptionContext} created through
|
||||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}.
|
||||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}.
|
|
||||||
* If the created context implements {@code ContextAware}, the configured
|
* If the created context implements {@code ContextAware}, the configured
|
||||||
* context is injected before the stream is attached.
|
* context is injected before the stream is attached.
|
||||||
* </p>
|
* </p>
|
||||||
@@ -638,8 +637,7 @@ public final class ChaChaDataContentBuilder implements DataContentBuilder<DataCo
|
|||||||
* <p>
|
* <p>
|
||||||
* The actual cipher work is delegated to an
|
* The actual cipher work is delegated to an
|
||||||
* {@link zeroecho.core.context.EncryptionContext} created through
|
* {@link zeroecho.core.context.EncryptionContext} created through
|
||||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}.
|
||||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}.
|
|
||||||
* If the created context implements {@code ContextAware}, the configured
|
* If the created context implements {@code ContextAware}, the configured
|
||||||
* context is injected before the stream is attached.
|
* context is injected before the stream is attached.
|
||||||
* </p>
|
* </p>
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ import zeroecho.sdk.content.api.PlainContent;
|
|||||||
*/
|
*/
|
||||||
public final class DigestDataContentBuilder implements DataContentBuilder<PlainContent> {
|
public final class DigestDataContentBuilder implements DataContentBuilder<PlainContent> {
|
||||||
private final ZeroEchoSession session;
|
private final ZeroEchoSession session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OutputMode selects how the digest-computing pipeline presents its result to
|
* OutputMode selects how the digest-computing pipeline presents its result to
|
||||||
* callers.
|
* callers.
|
||||||
|
|||||||
@@ -361,8 +361,7 @@ public final class ElgamalEncDataContentBuilder implements DataContentBuilder<Da
|
|||||||
* {@link ElgamalEncSpec}. When {@link #getStream()} is called, it creates an
|
* {@link ElgamalEncSpec}. When {@link #getStream()} is called, it creates an
|
||||||
* {@link zeroecho.core.context.EncryptionContext} for the "ElGamal" algorithm
|
* {@link zeroecho.core.context.EncryptionContext} for the "ElGamal" algorithm
|
||||||
* in {@link zeroecho.core.KeyUsage#ENCRYPT} role via
|
* in {@link zeroecho.core.KeyUsage#ENCRYPT} role via
|
||||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
||||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
|
||||||
* attaches the upstream stream, and returns a pull-based stream that encrypts
|
* attaches the upstream stream, and returns a pull-based stream that encrypts
|
||||||
* on the fly.
|
* on the fly.
|
||||||
* </p>
|
* </p>
|
||||||
@@ -426,8 +425,7 @@ public final class ElgamalEncDataContentBuilder implements DataContentBuilder<Da
|
|||||||
* {@link ElgamalEncSpec}. When {@link #getStream()} is called, it creates an
|
* {@link ElgamalEncSpec}. When {@link #getStream()} is called, it creates an
|
||||||
* {@link zeroecho.core.context.EncryptionContext} for the "ElGamal" algorithm
|
* {@link zeroecho.core.context.EncryptionContext} for the "ElGamal" algorithm
|
||||||
* in {@link zeroecho.core.KeyUsage#DECRYPT} role via
|
* in {@link zeroecho.core.KeyUsage#DECRYPT} role via
|
||||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
||||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
|
||||||
* attaches the upstream stream, and returns a pull-based stream that decrypts
|
* attaches the upstream stream, and returns a pull-based stream that decrypts
|
||||||
* on the fly.
|
* on the fly.
|
||||||
* </p>
|
* </p>
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ import zeroecho.sdk.content.api.PlainContent;
|
|||||||
public final class HmacDataContentBuilder implements DataContentBuilder<PlainContent> {
|
public final class HmacDataContentBuilder implements DataContentBuilder<PlainContent> {
|
||||||
private static final String ALGORITHM_ID = "HMAC";
|
private static final String ALGORITHM_ID = "HMAC";
|
||||||
private final ZeroEchoSession session;
|
private final ZeroEchoSession session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mode selects whether the pipeline computes an HMAC tag or verifies one.
|
* 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"
|
final String mac = spec.macName(); // e.g., "HmacSHA256"
|
||||||
try {
|
try {
|
||||||
if (genKeyBits != null) {
|
if (genKeyBits != null) {
|
||||||
return session.keyBuilders().symmetric().generate(ALGORITHM_ID,
|
return session.keyBuilders().symmetric().generate(ALGORITHM_ID, new HmacKeyGenSpec(mac, genKeyBits));
|
||||||
new HmacKeyGenSpec(mac, genKeyBits));
|
|
||||||
}
|
}
|
||||||
if (importRaw != null || importHex != null || importBase64 != null) {
|
if (importRaw != null || importHex != null || importBase64 != null) {
|
||||||
HmacKeyImportSpec ispec;
|
HmacKeyImportSpec ispec;
|
||||||
|
|||||||
@@ -345,8 +345,7 @@ public final class RsaEncDataContentBuilder implements DataContentBuilder<DataCo
|
|||||||
* When {@link #getStream()} is invoked, this class creates an
|
* When {@link #getStream()} is invoked, this class creates an
|
||||||
* {@link zeroecho.core.context.EncryptionContext} for the "RSA" algorithm in
|
* {@link zeroecho.core.context.EncryptionContext} for the "RSA" algorithm in
|
||||||
* {@link zeroecho.core.KeyUsage#ENCRYPT} role via
|
* {@link zeroecho.core.KeyUsage#ENCRYPT} role via
|
||||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
||||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
|
||||||
* attaches the upstream stream, and returns a pull-based stream that encrypts
|
* attaches the upstream stream, and returns a pull-based stream that encrypts
|
||||||
* on-the-fly using the configured {@link RsaEncSpec}.
|
* on-the-fly using the configured {@link RsaEncSpec}.
|
||||||
* </p>
|
* </p>
|
||||||
@@ -407,8 +406,7 @@ public final class RsaEncDataContentBuilder implements DataContentBuilder<DataCo
|
|||||||
* When {@link #getStream()} is invoked, this class creates an
|
* When {@link #getStream()} is invoked, this class creates an
|
||||||
* {@link zeroecho.core.context.EncryptionContext} for the "RSA" algorithm in
|
* {@link zeroecho.core.context.EncryptionContext} for the "RSA" algorithm in
|
||||||
* {@link zeroecho.core.KeyUsage#DECRYPT} role via
|
* {@link zeroecho.core.KeyUsage#DECRYPT} role via
|
||||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
||||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
|
||||||
* attaches the upstream stream, and returns a pull-based stream that decrypts
|
* attaches the upstream stream, and returns a pull-based stream that decrypts
|
||||||
* on-the-fly using the configured {@link RsaEncSpec}.
|
* on-the-fly using the configured {@link RsaEncSpec}.
|
||||||
* </p>
|
* </p>
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ import zeroecho.sdk.content.api.PlainContent;
|
|||||||
public final class RsaSigDataContentBuilder implements DataContentBuilder<PlainContent> {
|
public final class RsaSigDataContentBuilder implements DataContentBuilder<PlainContent> {
|
||||||
private static final String ALGORITHM_ID = "RSA";
|
private static final String ALGORITHM_ID = "RSA";
|
||||||
private final ZeroEchoSession session;
|
private final ZeroEchoSession session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mode selects whether the builder signs or verifies.
|
* Mode selects whether the builder signs or verifies.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -70,7 +70,8 @@
|
|||||||
* {@link zeroecho.sdk.builders.alg.ElgamalEncDataContentBuilder}.</li>
|
* {@link zeroecho.sdk.builders.alg.ElgamalEncDataContentBuilder}.</li>
|
||||||
* <li>RSA signatures:
|
* <li>RSA signatures:
|
||||||
* {@link zeroecho.sdk.builders.alg.RsaSigDataContentBuilder}; generic signature
|
* {@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},
|
* <li>MAC and digest: {@link zeroecho.sdk.builders.alg.HmacDataContentBuilder},
|
||||||
* {@link zeroecho.sdk.builders.alg.DigestDataContentBuilder}.</li>
|
* {@link zeroecho.sdk.builders.alg.DigestDataContentBuilder}.</li>
|
||||||
* <li>KEM envelopes: {@link zeroecho.sdk.builders.alg.KemDataContentBuilder}
|
* <li>KEM envelopes: {@link zeroecho.sdk.builders.alg.KemDataContentBuilder}
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ public final class SecretPassword implements SecretContent, Destroyable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a password from a caller-owned character array. The supplied
|
* Constructs a password from a caller-owned character array. The supplied array
|
||||||
* array is cloned and remains owned by the caller.
|
* is cloned and remains owned by the caller.
|
||||||
*
|
*
|
||||||
* @param password password characters; must not be {@code null}
|
* @param password password characters; must not be {@code null}
|
||||||
* @throws NullPointerException if {@code password} is {@code null}
|
* @throws NullPointerException if {@code password} is {@code null}
|
||||||
|
|||||||
@@ -148,8 +148,7 @@ final class Decryptor implements PlainContent, MultiRecipientContent {
|
|||||||
} catch (AEADBadTagException ex) {
|
} catch (AEADBadTagException ex) {
|
||||||
// wrong key/password for that entry, continue scanning
|
// wrong key/password for that entry, continue scanning
|
||||||
if (LOG.isLoggable(Level.FINE)) {
|
if (LOG.isLoggable(Level.FINE)) {
|
||||||
LOG.log(Level.FINE, "recipient authentication failed: {0}",
|
LOG.log(Level.FINE, "recipient authentication failed: {0}", ex.getClass().getSimpleName());
|
||||||
ex.getClass().getSimpleName());
|
|
||||||
}
|
}
|
||||||
} catch (GeneralSecurityException | IOException | IllegalArgumentException ex) {
|
} catch (GeneralSecurityException | IOException | IllegalArgumentException ex) {
|
||||||
// entry not applicable to this opener/material; ignore and continue
|
// 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)
|
private void closeAfterAttempt(InputStream input, boolean transferred, Throwable primary) throws IOException {
|
||||||
throws IOException {
|
|
||||||
IOException cleanupFailure = null;
|
IOException cleanupFailure = null;
|
||||||
if (!transferred) {
|
if (!transferred) {
|
||||||
try {
|
try {
|
||||||
@@ -282,8 +280,7 @@ final class Decryptor implements PlainContent, MultiRecipientContent {
|
|||||||
closeOpeners(openers, primary);
|
closeOpeners(openers, primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* default */ static void closeOpeners(List<RecipientOpener> ownedOpeners, Throwable primary)
|
/* default */ static void closeOpeners(List<RecipientOpener> ownedOpeners, Throwable primary) throws IOException {
|
||||||
throws IOException {
|
|
||||||
IOException cleanupFailure = null;
|
IOException cleanupFailure = null;
|
||||||
for (RecipientOpener opener : ownedOpeners) { // NOPMD - each opener is closed in this loop
|
for (RecipientOpener opener : ownedOpeners) { // NOPMD - each opener is closed in this loop
|
||||||
try {
|
try {
|
||||||
@@ -309,14 +306,13 @@ final class Decryptor implements PlainContent, MultiRecipientContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rejectOversizedCek(byte[] candidate, int fieldIndex, String recipientId,
|
private void rejectOversizedCek(byte[] candidate, int fieldIndex, String recipientId, RecipientOpener opener) {
|
||||||
RecipientOpener opener) {
|
|
||||||
try {
|
try {
|
||||||
if (LOG.isLoggable(Level.WARNING)) {
|
if (LOG.isLoggable(Level.WARNING)) {
|
||||||
LOG.log(Level.WARNING,
|
LOG.log(Level.WARNING,
|
||||||
"Suspicious material in field {0}: {1}/{2} returned length {3}, while {4} is the limit. Ignoring.",
|
"Suspicious material in field {0}: {1}/{2} returned length {3}, while {4} is the limit. Ignoring.",
|
||||||
new Object[] { fieldIndex, recipientId, opener.getClass().getName(),
|
new Object[] { fieldIndex, recipientId, opener.getClass().getName(), candidate.length,
|
||||||
candidate.length, keyBytes });
|
keyBytes });
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
Arrays.fill(candidate, (byte) 0);
|
Arrays.fill(candidate, (byte) 0);
|
||||||
|
|||||||
@@ -84,8 +84,7 @@ final class Encryptor implements EncryptedContent, MultiRecipientContent {
|
|||||||
this.keyBytes = keyBytes;
|
this.keyBytes = keyBytes;
|
||||||
this.maxRecipients = maxRecipients;
|
this.maxRecipients = maxRecipients;
|
||||||
this.maxEntryLen = maxEntryLen;
|
this.maxEntryLen = maxEntryLen;
|
||||||
this.randomBytesFactory = Objects.requireNonNull(randomBytesFactory,
|
this.randomBytesFactory = Objects.requireNonNull(randomBytesFactory, "randomBytesFactory must not be null");
|
||||||
"randomBytesFactory must not be null");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -232,8 +231,7 @@ final class Encryptor implements EncryptedContent, MultiRecipientContent {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* default */ static void closeRecipients(List<Recipient> ownedRecipients, Throwable primary)
|
/* default */ static void closeRecipients(List<Recipient> ownedRecipients, Throwable primary) throws IOException {
|
||||||
throws IOException {
|
|
||||||
IOException cleanupFailure = null;
|
IOException cleanupFailure = null;
|
||||||
for (Recipient recipient : ownedRecipients) {
|
for (Recipient recipient : ownedRecipients) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -82,8 +82,7 @@ public final class KemCtxRecipient implements Recipient, AutoCloseable {
|
|||||||
* @param kekBytes KEK length; exactly 16 or 32 bytes
|
* @param kekBytes KEK length; exactly 16 or 32 bytes
|
||||||
* @param saltLen length of the random salt to apply during HKDF
|
* @param saltLen length of the random salt to apply during HKDF
|
||||||
* @throws NullPointerException if {@code ctx} is {@code null}
|
* @throws NullPointerException if {@code ctx} is {@code null}
|
||||||
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or
|
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32
|
||||||
* 32
|
|
||||||
*/
|
*/
|
||||||
public KemCtxRecipient(KemContext ctx, int kekBytes, int saltLen) {
|
public KemCtxRecipient(KemContext ctx, int kekBytes, int saltLen) {
|
||||||
this(ctx, kekBytes, saltLen, false);
|
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
|
* @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
|
* cannot unwrap a CEK); {@code false} if it is a real recipient
|
||||||
* @throws NullPointerException if {@code ctx} is {@code null}
|
* @throws NullPointerException if {@code ctx} is {@code null}
|
||||||
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or
|
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32
|
||||||
* 32
|
|
||||||
*/
|
*/
|
||||||
public KemCtxRecipient(KemContext ctx, int kekBytes, int saltLen, boolean decoy) {
|
public KemCtxRecipient(KemContext ctx, int kekBytes, int saltLen, boolean decoy) {
|
||||||
int validatedKekBytes = RecipientKekSizes.requireSupported(kekBytes);
|
int validatedKekBytes = RecipientKekSizes.requireSupported(kekBytes);
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ final class KemKeyDerivation {
|
|||||||
* Derives a KEK bound to the KEM algorithm identifier.
|
* Derives a KEK bound to the KEM algorithm identifier.
|
||||||
*
|
*
|
||||||
* @param sharedSecret KEM shared secret
|
* @param sharedSecret KEM shared secret
|
||||||
* @param salt HKDF salt
|
* @param salt HKDF salt
|
||||||
* @param algorithmId canonical KEM algorithm identifier
|
* @param algorithmId canonical KEM algorithm identifier
|
||||||
* @param outputBytes requested KEK size
|
* @param outputBytes requested KEK size
|
||||||
* @return newly allocated KEK bytes
|
* @return newly allocated KEK bytes
|
||||||
* @throws GeneralSecurityException if HKDF fails
|
* @throws GeneralSecurityException if HKDF fails
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -46,17 +46,17 @@ import zeroecho.sdk.content.api.DataContent;
|
|||||||
* resources until processing or explicit cleanup.
|
* resources until processing or explicit cleanup.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Callers must invoke {@link #close()} when a built instance is abandoned before
|
* Callers must invoke {@link #close()} when a built instance is abandoned
|
||||||
* {@link #getStream()} is called. Successful or failed stream construction also
|
* before {@link #getStream()} is called. Successful or failed stream
|
||||||
* releases the owned recipient resources. Unlocking keys and password material
|
* construction also releases the owned recipient resources. Unlocking keys and
|
||||||
* supplied separately remain caller-owned and are never destroyed by this
|
* password material supplied separately remain caller-owned and are never
|
||||||
* content.
|
* destroyed by this content.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Implementations are not thread-safe. Cleanup is idempotent, and content cannot
|
* Implementations are not thread-safe. Cleanup is idempotent, and content
|
||||||
* be used after cleanup. Calling {@link #getStream()} is terminal even when stream
|
* cannot be used after cleanup. Calling {@link #getStream()} is terminal even
|
||||||
* construction fails.
|
* when stream construction fails.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public interface MultiRecipientContent extends DataContent, Destroyable, AutoCloseable {
|
public interface MultiRecipientContent extends DataContent, Destroyable, AutoCloseable {
|
||||||
|
|||||||
@@ -215,8 +215,8 @@ public final class MultiRecipientDataSourceBuilder
|
|||||||
ensureOpen();
|
ensureOpen();
|
||||||
RecipientKekSizes.requireSupported(kekBytes);
|
RecipientKekSizes.requireSupported(kekBytes);
|
||||||
session.pbkdf2Limits().validateTrusted(iterations);
|
session.pbkdf2Limits().validateTrusted(iterations);
|
||||||
this.recipients.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, false,
|
this.recipients
|
||||||
session.pbkdf2Limits()));
|
.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, false, session.pbkdf2Limits()));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,8 +236,7 @@ public final class MultiRecipientDataSourceBuilder
|
|||||||
* @param saltLen HKDF salt length in bytes
|
* @param saltLen HKDF salt length in bytes
|
||||||
* @return this builder
|
* @return this builder
|
||||||
* @throws NullPointerException if {@code kem} is {@code null}
|
* @throws NullPointerException if {@code kem} is {@code null}
|
||||||
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or
|
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32
|
||||||
* 32
|
|
||||||
*/
|
*/
|
||||||
public MultiRecipientDataSourceBuilder addRecipient(KemContext kem, int kekBytes, int saltLen) {
|
public MultiRecipientDataSourceBuilder addRecipient(KemContext kem, int kekBytes, int saltLen) {
|
||||||
ensureOpen();
|
ensureOpen();
|
||||||
@@ -292,8 +291,8 @@ public final class MultiRecipientDataSourceBuilder
|
|||||||
ensureOpen();
|
ensureOpen();
|
||||||
RecipientKekSizes.requireSupported(kekBytes);
|
RecipientKekSizes.requireSupported(kekBytes);
|
||||||
session.pbkdf2Limits().validateTrusted(iterations);
|
session.pbkdf2Limits().validateTrusted(iterations);
|
||||||
this.recipients.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, true,
|
this.recipients
|
||||||
session.pbkdf2Limits()));
|
.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, true, session.pbkdf2Limits()));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,8 +313,7 @@ public final class MultiRecipientDataSourceBuilder
|
|||||||
* @param saltLen HKDF salt length in bytes
|
* @param saltLen HKDF salt length in bytes
|
||||||
* @return this builder
|
* @return this builder
|
||||||
* @throws NullPointerException if {@code kem} is {@code null}
|
* @throws NullPointerException if {@code kem} is {@code null}
|
||||||
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or
|
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32
|
||||||
* 32
|
|
||||||
*/
|
*/
|
||||||
public MultiRecipientDataSourceBuilder addRecipientDecoy(KemContext kem, int kekBytes, int saltLen) {
|
public MultiRecipientDataSourceBuilder addRecipientDecoy(KemContext kem, int kekBytes, int saltLen) {
|
||||||
ensureOpen();
|
ensureOpen();
|
||||||
@@ -384,9 +382,9 @@ public final class MultiRecipientDataSourceBuilder
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The builder takes ownership of the opener. The opener must be reusable
|
* The builder takes ownership of the opener. The opener must be reusable across
|
||||||
* across all recipient entries and is closed after scanning or when the built
|
* all recipient entries and is closed after scanning or when the built content
|
||||||
* content is abandoned.
|
* is abandoned.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param opener reusable opener to add
|
* @param opener reusable opener to add
|
||||||
@@ -479,8 +477,10 @@ public final class MultiRecipientDataSourceBuilder
|
|||||||
/**
|
/**
|
||||||
* Destroys recipient secrets still owned by this builder.
|
* Destroys recipient secrets still owned by this builder.
|
||||||
*
|
*
|
||||||
* <p>Recipients transferred to a successfully built encrypting content object
|
* <p>
|
||||||
* are owned and destroyed by that object instead.</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
|
* @throws DestroyFailedException if recipient cleanup fails
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ public final class PasswordOpener implements RecipientOpener {
|
|||||||
public PasswordOpener(Pbkdf2Limits limits) {
|
public PasswordOpener(Pbkdf2Limits limits) {
|
||||||
this.limits = java.util.Objects.requireNonNull(limits, "limits must not be null");
|
this.limits = java.util.Objects.requireNonNull(limits, "limits must not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to open a password-based recipient entry using a password unlock
|
* Attempts to open a password-based recipient entry using a password unlock
|
||||||
* material.
|
* material.
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ public final class PasswordRecipient implements Recipient, Destroyable, AutoClos
|
|||||||
* <li>The caller should clear the {@code password} array after constructing the
|
* <li>The caller should clear the {@code password} array after constructing the
|
||||||
* recipient to minimize exposure in memory.</li>
|
* recipient to minimize exposure in memory.</li>
|
||||||
* <li>Choose an iteration count appropriate to the target platform to balance
|
* <li>Choose an iteration count appropriate to the target platform to balance
|
||||||
* password-guessing resistance against recipient creation and opening
|
* password-guessing resistance against recipient creation and opening latency.
|
||||||
* latency. Counts below {@value Pbkdf2Limits#MINIMUM} are rejected.</li>
|
* Counts below {@value Pbkdf2Limits#MINIMUM} are rejected.</li>
|
||||||
* <li>Decoy recipients increase confidentiality by hiding the number of real
|
* <li>Decoy recipients increase confidentiality by hiding the number of real
|
||||||
* recipients but cannot successfully unwrap the CEK.</li>
|
* recipients but cannot successfully unwrap the CEK.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ package zeroecho.sdk.guard;
|
|||||||
* Defines the KEK sizes supported by recipient entries without an encoded size
|
* Defines the KEK sizes supported by recipient entries without an encoded size
|
||||||
* discriminator.
|
* 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
|
* 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
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ import zeroecho.core.annotation.Describable;
|
|||||||
/**
|
/**
|
||||||
* Caller-owned session-operation input used to unlock a recipient entry.
|
* 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
|
* 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 {
|
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.
|
* Destroyable password unlocking material backed by an owned character array.
|
||||||
*
|
*
|
||||||
* <p>Construction and access use defensive copies. Destruction is idempotent
|
* <p>
|
||||||
* and prevents subsequent access.</p>
|
* Construction and access use defensive copies. Destruction is idempotent and
|
||||||
|
* prevents subsequent access.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
final class Password implements UnlockMaterial, Destroyable {
|
final class Password implements UnlockMaterial, Destroyable {
|
||||||
private final char[] characters;
|
private final char[] characters;
|
||||||
|
|||||||
@@ -108,8 +108,8 @@
|
|||||||
* generation and recipient entries; the symmetric builder manages algorithm
|
* generation and recipient entries; the symmetric builder manages algorithm
|
||||||
* parameters and payload framing.</li>
|
* parameters and payload framing.</li>
|
||||||
* <li><strong>Reusable opener strategies:</strong> recipients encode entries;
|
* <li><strong>Reusable opener strategies:</strong> recipients encode entries;
|
||||||
* openers attempt every applicable entry and create fresh cryptographic contexts
|
* openers attempt every applicable entry and create fresh cryptographic
|
||||||
* per attempt. Neither carries long-lived secret state.</li>
|
* contexts per attempt. Neither carries long-lived secret state.</li>
|
||||||
* <li><strong>Defensive parsing:</strong> the builder applies limits to the
|
* <li><strong>Defensive parsing:</strong> the builder applies limits to the
|
||||||
* number of recipients and the size of each entry blob; the symmetric stage
|
* number of recipients and the size of each entry blob; the symmetric stage
|
||||||
* applies its own limits to its header and payload.</li>
|
* applies its own limits to its header and payload.</li>
|
||||||
|
|||||||
@@ -196,8 +196,8 @@ public final class HybridDerived {
|
|||||||
* construction.
|
* construction.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param aes AES builder to configure (must not be null)
|
* @param aes AES builder to configure (must not be null)
|
||||||
* @param keyBits AES key size in bits (128/192/256)
|
* @param keyBits AES key size in bits (128/192/256)
|
||||||
* @return the provided builder instance
|
* @return the provided builder instance
|
||||||
* @throws NullPointerException if aes is null
|
* @throws NullPointerException if aes is null
|
||||||
* @throws IllegalArgumentException if keyBits is invalid
|
* @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
|
* Derives a ChaCha key and applies it with optional AAD to the provided ChaCha
|
||||||
* ChaCha builder.
|
* builder.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The returned value is the same builder instance to preserve fluent pipeline
|
* The returned value is the same builder instance to preserve fluent pipeline
|
||||||
* construction.
|
* construction.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param chacha ChaCha builder to configure (must not be null)
|
* @param chacha ChaCha builder to configure (must not be null)
|
||||||
* @param keyBits key size in bits (typically 256)
|
* @param keyBits key size in bits (typically 256)
|
||||||
* @return the provided builder instance
|
* @return the provided builder instance
|
||||||
* @throws NullPointerException if chacha is null
|
* @throws NullPointerException if chacha is null
|
||||||
* @throws IllegalArgumentException if keyBits is invalid
|
* @throws IllegalArgumentException if keyBits is invalid
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user