chore(text): source code format
This commit is contained in:
@@ -16,29 +16,31 @@ import zeroecho.core.spec.ContextSpec;
|
||||
/**
|
||||
* Immutable value descriptor of one algorithm context capability.
|
||||
*
|
||||
* <p>The default specification is resolved once during provider construction.
|
||||
* All components therefore have stable value semantics and are safe for
|
||||
* concurrent reads.</p>
|
||||
* <p>
|
||||
* The default specification is resolved once during provider construction. All
|
||||
* components therefore have stable value semantics and are safe for concurrent
|
||||
* reads.
|
||||
* </p>
|
||||
*
|
||||
* @param algorithmId canonical algorithm identifier
|
||||
* @param family algorithm family
|
||||
* @param role supported key usage
|
||||
* @param family algorithm family
|
||||
* @param role supported key usage
|
||||
* @param contextType produced context type
|
||||
* @param keyType accepted key type
|
||||
* @param specType accepted specification type
|
||||
* @param keyType accepted key type
|
||||
* @param specType accepted specification type
|
||||
* @param defaultSpec non-null resolved default specification
|
||||
* @since 1.0
|
||||
*/
|
||||
public record Capability(String algorithmId, AlgorithmFamily family, KeyUsage role,
|
||||
Class<? extends CryptoContext> contextType, Class<? extends Key> keyType,
|
||||
Class<? extends ContextSpec> specType, ContextSpec defaultSpec) {
|
||||
Class<? extends CryptoContext> contextType, Class<? extends Key> keyType, Class<? extends ContextSpec> specType,
|
||||
ContextSpec defaultSpec) {
|
||||
|
||||
/**
|
||||
* Validates the capability metadata.
|
||||
*
|
||||
* @throws NullPointerException if a component is {@code null}
|
||||
* @throws IllegalArgumentException if {@code defaultSpec} is incompatible
|
||||
* with {@code specType}
|
||||
* @throws NullPointerException if a component is {@code null}
|
||||
* @throws IllegalArgumentException if {@code defaultSpec} is incompatible with
|
||||
* {@code specType}
|
||||
*/
|
||||
public Capability {
|
||||
Objects.requireNonNull(algorithmId, "algorithmId must not be null");
|
||||
|
||||
@@ -106,8 +106,8 @@ import zeroecho.core.spi.SymmetricKeyImporter;
|
||||
* <p>
|
||||
* <b>Security note:</b> Algorithms must enforce strong validation of keys and
|
||||
* specs during registration and
|
||||
* {@link #createContext(KeyUsage, Key, ContextSpec)} to
|
||||
* prevent downgrade or misuse attacks.
|
||||
* {@link #createContext(KeyUsage, Key, ContextSpec)} to prevent downgrade or
|
||||
* misuse attacks.
|
||||
* </p>
|
||||
*
|
||||
* @since 1.0
|
||||
@@ -123,16 +123,11 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
|
||||
private final List<Capability> capabilities = new ArrayList<>();
|
||||
private final Map<KeyUsage, List<RoleBinding<?, ?, ?>>> ctxBindings = new EnumMap<>(KeyUsage.class);
|
||||
private final Map<Class<? extends AlgorithmKeySpec>, AsymmetricKeyPairGenerator<?>> keyPairGenerators =
|
||||
new LinkedHashMap<>();
|
||||
private final Map<Class<? extends AlgorithmKeySpec>, PublicKeyImporter<?>> publicKeyImporters =
|
||||
new LinkedHashMap<>();
|
||||
private final Map<Class<? extends AlgorithmKeySpec>, PrivateKeyImporter<?>> privateKeyImporters =
|
||||
new LinkedHashMap<>();
|
||||
private final Map<Class<? extends AlgorithmKeySpec>, SymmetricKeyGenerator<?>> symmetricKeyGenerators =
|
||||
new LinkedHashMap<>();
|
||||
private final Map<Class<? extends AlgorithmKeySpec>, SymmetricKeyImporter<?>> symmetricKeyImporters =
|
||||
new LinkedHashMap<>();
|
||||
private final Map<Class<? extends AlgorithmKeySpec>, AsymmetricKeyPairGenerator<?>> keyPairGenerators = new LinkedHashMap<>();
|
||||
private final Map<Class<? extends AlgorithmKeySpec>, PublicKeyImporter<?>> publicKeyImporters = new LinkedHashMap<>();
|
||||
private final Map<Class<? extends AlgorithmKeySpec>, PrivateKeyImporter<?>> privateKeyImporters = new LinkedHashMap<>();
|
||||
private final Map<Class<? extends AlgorithmKeySpec>, SymmetricKeyGenerator<?>> symmetricKeyGenerators = new LinkedHashMap<>();
|
||||
private final Map<Class<? extends AlgorithmKeySpec>, SymmetricKeyImporter<?>> symmetricKeyImporters = new LinkedHashMap<>();
|
||||
private final Map<Class<? extends AlgorithmKeySpec>, AlgorithmKeySpec> asymmetricDefaults = new LinkedHashMap<>();
|
||||
private final Map<Class<? extends AlgorithmKeySpec>, AlgorithmKeySpec> symmetricDefaults = new LinkedHashMap<>();
|
||||
|
||||
@@ -296,8 +291,9 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
* <p>
|
||||
* Concrete algorithms call this during construction to declare support for
|
||||
* specific roles (e.g., {@code ENCRYPT}, {@code VERIFY}). When
|
||||
* {@link #createContext(KeyUsage, Key, ContextSpec)} is later invoked, the provided
|
||||
* {@code key} and optional {@code spec} are matched against these bindings.
|
||||
* {@link #createContext(KeyUsage, Key, ContextSpec)} is later invoked, the
|
||||
* provided {@code key} and optional {@code spec} are matched against these
|
||||
* bindings.
|
||||
* </p>
|
||||
*
|
||||
* @param role supported {@link KeyUsage} role
|
||||
@@ -397,8 +393,7 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
if (rb.accepts(key, spec)) {
|
||||
S resolved = (spec != null) ? spec
|
||||
: Objects.requireNonNull(rb.defaultSpec.get(), "defaultSpec value must not be null");
|
||||
C ctx = Objects.requireNonNull(rb.factory.createContext(key, resolved),
|
||||
_id + " factory returned null");
|
||||
C ctx = Objects.requireNonNull(rb.factory.createContext(key, resolved), _id + " factory returned null");
|
||||
// Enforce the declared context type contract:
|
||||
if (!rb.ctxType.isInstance(ctx)) {
|
||||
throw new IllegalStateException(_id + " factory returned " + ctx.getClass().getName()
|
||||
@@ -411,8 +406,7 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
+ (spec == null ? " (default spec)" : " and spec=" + spec.getClass().getName()));
|
||||
}
|
||||
|
||||
private <S extends AlgorithmKeySpec> S resolveDefault(Class<S> specType,
|
||||
Supplier<? extends S> defaultSpecOrNull) {
|
||||
private <S extends AlgorithmKeySpec> S resolveDefault(Class<S> specType, Supplier<? extends S> defaultSpecOrNull) {
|
||||
if (defaultSpecOrNull == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -426,16 +420,18 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
/**
|
||||
* Registers asymmetric key-pair generation for one exact specification class.
|
||||
*
|
||||
* <p>The optional default is resolved and validated during registration.
|
||||
* <p>
|
||||
* The optional default is resolved and validated during registration.
|
||||
* Registered generators must be safe for concurrent invocation after the
|
||||
* algorithm is published.</p>
|
||||
* algorithm is published.
|
||||
* </p>
|
||||
*
|
||||
* @param specType exact specification class
|
||||
* @param generator non-null generator
|
||||
* @param specType exact specification class
|
||||
* @param generator non-null generator
|
||||
* @param defaultSpecOrNull optional default supplier, evaluated once
|
||||
* @param <S> specification type
|
||||
* @throws NullPointerException if a required argument or supplied default is
|
||||
* {@code null}
|
||||
* @param <S> specification type
|
||||
* @throws NullPointerException if a required argument or supplied default
|
||||
* is {@code null}
|
||||
* @throws IllegalArgumentException if the supplied default has the wrong type
|
||||
*/
|
||||
protected final <S extends AlgorithmKeySpec> void registerAsymmetricKeyPairGenerator(Class<S> specType,
|
||||
@@ -450,7 +446,7 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
*
|
||||
* @param specType exact specification class
|
||||
* @param importer non-null importer safe for concurrent invocation
|
||||
* @param <S> specification type
|
||||
* @param <S> specification type
|
||||
* @throws NullPointerException if an argument is {@code null}
|
||||
*/
|
||||
protected final <S extends AlgorithmKeySpec> void registerPublicKeyImporter(Class<S> specType,
|
||||
@@ -464,7 +460,7 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
*
|
||||
* @param specType exact specification class
|
||||
* @param importer non-null importer safe for concurrent invocation
|
||||
* @param <S> specification type
|
||||
* @param <S> specification type
|
||||
* @throws NullPointerException if an argument is {@code null}
|
||||
*/
|
||||
protected final <S extends AlgorithmKeySpec> void registerPrivateKeyImporter(Class<S> specType,
|
||||
@@ -476,14 +472,16 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
/**
|
||||
* Registers symmetric-key generation for one exact specification class.
|
||||
*
|
||||
* <p>The optional default is resolved and validated during registration.</p>
|
||||
* <p>
|
||||
* The optional default is resolved and validated during registration.
|
||||
* </p>
|
||||
*
|
||||
* @param specType exact specification class
|
||||
* @param generator non-null generator safe for concurrent invocation
|
||||
* @param specType exact specification class
|
||||
* @param generator non-null generator safe for concurrent invocation
|
||||
* @param defaultSpecOrNull optional default supplier, evaluated once
|
||||
* @param <S> specification type
|
||||
* @throws NullPointerException if a required argument or supplied default is
|
||||
* {@code null}
|
||||
* @param <S> specification type
|
||||
* @throws NullPointerException if a required argument or supplied default
|
||||
* is {@code null}
|
||||
* @throws IllegalArgumentException if the supplied default has the wrong type
|
||||
*/
|
||||
protected final <S extends AlgorithmKeySpec> void registerSymmetricKeyGenerator(Class<S> specType,
|
||||
@@ -498,7 +496,7 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
*
|
||||
* @param specType exact specification class
|
||||
* @param importer non-null importer safe for concurrent invocation
|
||||
* @param <S> specification type
|
||||
* @param <S> specification type
|
||||
* @throws NullPointerException if an argument is {@code null}
|
||||
*/
|
||||
protected final <S extends AlgorithmKeySpec> void registerSymmetricKeyImporter(Class<S> specType,
|
||||
@@ -515,12 +513,14 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
* Returns the asymmetric key-pair generator registered for an exact
|
||||
* specification class.
|
||||
*
|
||||
* <p>The returned implementation may be shared and invoked concurrently.</p>
|
||||
* <p>
|
||||
* The returned implementation may be shared and invoked concurrently.
|
||||
* </p>
|
||||
*
|
||||
* @param specType exact specification class; subclasses are not matched
|
||||
* @param <S> specification type
|
||||
* @param <S> specification type
|
||||
* @return registered generator
|
||||
* @throws NullPointerException if {@code specType} is {@code null}
|
||||
* @throws NullPointerException if {@code specType} is {@code null}
|
||||
* @throws IllegalArgumentException if no generator is registered
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -537,12 +537,14 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
/**
|
||||
* Returns the public-key importer registered for an exact specification class.
|
||||
*
|
||||
* <p>The returned implementation may be shared and invoked concurrently.</p>
|
||||
* <p>
|
||||
* The returned implementation may be shared and invoked concurrently.
|
||||
* </p>
|
||||
*
|
||||
* @param specType exact specification class; subclasses are not matched
|
||||
* @param <S> specification type
|
||||
* @param <S> specification type
|
||||
* @return registered importer
|
||||
* @throws NullPointerException if {@code specType} is {@code null}
|
||||
* @throws NullPointerException if {@code specType} is {@code null}
|
||||
* @throws IllegalArgumentException if no importer is registered
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -556,15 +558,16 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the private-key importer registered for an exact specification
|
||||
* class.
|
||||
* Returns the private-key importer registered for an exact specification class.
|
||||
*
|
||||
* <p>The returned implementation may be shared and invoked concurrently.</p>
|
||||
* <p>
|
||||
* The returned implementation may be shared and invoked concurrently.
|
||||
* </p>
|
||||
*
|
||||
* @param specType exact specification class; subclasses are not matched
|
||||
* @param <S> specification type
|
||||
* @param <S> specification type
|
||||
* @return registered importer
|
||||
* @throws NullPointerException if {@code specType} is {@code null}
|
||||
* @throws NullPointerException if {@code specType} is {@code null}
|
||||
* @throws IllegalArgumentException if no importer is registered
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -581,12 +584,14 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
* Returns the symmetric-key generator registered for an exact specification
|
||||
* class.
|
||||
*
|
||||
* <p>The returned implementation may be shared and invoked concurrently.</p>
|
||||
* <p>
|
||||
* The returned implementation may be shared and invoked concurrently.
|
||||
* </p>
|
||||
*
|
||||
* @param specType exact specification class; subclasses are not matched
|
||||
* @param <S> specification type
|
||||
* @param <S> specification type
|
||||
* @return registered generator
|
||||
* @throws NullPointerException if {@code specType} is {@code null}
|
||||
* @throws NullPointerException if {@code specType} is {@code null}
|
||||
* @throws IllegalArgumentException if no generator is registered
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -603,12 +608,14 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
* Returns the symmetric-key importer registered for an exact specification
|
||||
* class.
|
||||
*
|
||||
* <p>The returned implementation may be shared and invoked concurrently.</p>
|
||||
* <p>
|
||||
* The returned implementation may be shared and invoked concurrently.
|
||||
* </p>
|
||||
*
|
||||
* @param specType exact specification class; subclasses are not matched
|
||||
* @param <S> specification type
|
||||
* @param <S> specification type
|
||||
* @return registered importer
|
||||
* @throws NullPointerException if {@code specType} is {@code null}
|
||||
* @throws NullPointerException if {@code specType} is {@code null}
|
||||
* @throws IllegalArgumentException if no importer is registered
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -628,14 +635,12 @@ public abstract class CryptoAlgorithm { // NOPMD
|
||||
*/
|
||||
public final List<KeyOperationInfo> keyOperations() {
|
||||
List<KeyOperationInfo> result = new ArrayList<>();
|
||||
addOperationInfo(result, KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE, keyPairGenerators,
|
||||
asymmetricDefaults);
|
||||
addOperationInfo(result, KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE, keyPairGenerators, asymmetricDefaults);
|
||||
addOperationInfo(result, KeyOperation.ASYMMETRIC_PUBLIC_IMPORT, publicKeyImporters, Map.of());
|
||||
addOperationInfo(result, KeyOperation.ASYMMETRIC_PRIVATE_IMPORT, privateKeyImporters, Map.of());
|
||||
addOperationInfo(result, KeyOperation.SYMMETRIC_GENERATE, symmetricKeyGenerators, symmetricDefaults);
|
||||
addOperationInfo(result, KeyOperation.SYMMETRIC_IMPORT, symmetricKeyImporters, Map.of());
|
||||
result.sort(Comparator.comparing(KeyOperationInfo::operation)
|
||||
.thenComparing(info -> info.specType().getName()));
|
||||
result.sort(Comparator.comparing(KeyOperationInfo::operation).thenComparing(info -> info.specType().getName()));
|
||||
return List.copyOf(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,12 @@ import java.util.TreeMap;
|
||||
/**
|
||||
* Immutable registry of {@link CryptoAlgorithm} providers.
|
||||
*
|
||||
* <p>Providers are discovered once through {@link ServiceLoader}, sorted by
|
||||
* <p>
|
||||
* Providers are discovered once through {@link ServiceLoader}, sorted by
|
||||
* canonical algorithm identifier, and retained in one immutable registry.
|
||||
* Runtime policy and auditing belong exclusively to explicitly created
|
||||
* {@link zeroecho.sdk.ZeroEchoSession} instances.</p>
|
||||
* {@link zeroecho.sdk.ZeroEchoSession} instances.
|
||||
* </p>
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
@@ -93,9 +93,9 @@ public final class CryptoCatalog {
|
||||
* {@link CryptoAlgorithms}.
|
||||
*
|
||||
* <p>
|
||||
* Provider discovery, deterministic ordering, and duplicate checking occur
|
||||
* once in {@code CryptoAlgorithms}. This method neither scans providers nor
|
||||
* copies their collection.
|
||||
* Provider discovery, deterministic ordering, and duplicate checking occur once
|
||||
* in {@code CryptoAlgorithms}. This method neither scans providers nor copies
|
||||
* their collection.
|
||||
* </p>
|
||||
*
|
||||
* @return an immutable {@code CryptoCatalog} with all discovered algorithms
|
||||
@@ -158,40 +158,16 @@ public final class CryptoCatalog {
|
||||
/**
|
||||
* Serializes the catalog to a compact JSON document.
|
||||
*
|
||||
* <p>
|
||||
* The schema is:
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
* {
|
||||
* "algorithms": [
|
||||
* {
|
||||
* "id": "AES/GCM",
|
||||
* "displayName": "AES-GCM",
|
||||
* "capabilities": [
|
||||
* {
|
||||
* "family": "SYMMETRIC",
|
||||
* "role": "ENCRYPT",
|
||||
* "contextType": "AeadEncryptContext",
|
||||
* "keyType": "SecretKey",
|
||||
* "specType": "AeadSpec",
|
||||
* "defaultSpec": "Random nonce, 128-bit tag"
|
||||
* }
|
||||
* ],
|
||||
* "asymmetricKeyBuilders": [
|
||||
* { "specType": "Ed25519Spec", "defaultKeySpec": "Ed25519 default" }
|
||||
* ],
|
||||
* "symmetricKeyBuilders": [
|
||||
* { "specType": "AesKeySpec", "defaultKeySpec": "AES-256" }
|
||||
* ]
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }</pre>
|
||||
* <p> The schema is: </p> <pre>{@code { "algorithms": [ { "id": "AES/GCM",
|
||||
* "displayName": "AES-GCM", "capabilities": [ { "family": "SYMMETRIC", "role":
|
||||
* "ENCRYPT", "contextType": "AeadEncryptContext", "keyType": "SecretKey",
|
||||
* "specType": "AeadSpec", "defaultSpec": "Random nonce, 128-bit tag" } ],
|
||||
* "asymmetricKeyBuilders": [ { "specType": "Ed25519Spec", "defaultKeySpec":
|
||||
* "Ed25519 default" } ], "symmetricKeyBuilders": [ { "specType": "AesKeySpec",
|
||||
* "defaultKeySpec": "AES-256" } ] } ] } }</pre>
|
||||
*
|
||||
* <p>
|
||||
* String values are escaped for quotes and backslashes. The method does not
|
||||
* attempt to pretty-print; callers can format the output if needed.
|
||||
* </p>
|
||||
* <p> String values are escaped for quotes and backslashes. The method does not
|
||||
* attempt to pretty-print; callers can format the output if needed. </p>
|
||||
*
|
||||
* @return a JSON string describing algorithms, capabilities, and key builders
|
||||
*/
|
||||
@@ -230,8 +206,7 @@ public final class CryptoCatalog {
|
||||
}
|
||||
firstOperation = false;
|
||||
sb.append('{').append(jsonField("operation", operation.operation().name())).append(',')
|
||||
.append(jsonField("specType", operation.specType().getSimpleName()))
|
||||
.append(",\"defaultSpec\":")
|
||||
.append(jsonField("specType", operation.specType().getSimpleName())).append(",\"defaultSpec\":")
|
||||
.append(operation.defaultSpec() == null ? "null" : jsonString(labelOf(operation.defaultSpec())))
|
||||
.append('}');
|
||||
}
|
||||
@@ -273,9 +248,8 @@ public final class CryptoCatalog {
|
||||
}
|
||||
sb.append("</capabilities><keyOperations>");
|
||||
for (KeyOperationInfo operation : a.keyOperations()) {
|
||||
sb.append("<keyOperation operation=\"").append(operation.operation().name())
|
||||
.append("\" specType=\"").append(esc(operation.specType().getSimpleName()))
|
||||
.append("\"><defaultSpec>")
|
||||
sb.append("<keyOperation operation=\"").append(operation.operation().name()).append("\" specType=\"")
|
||||
.append(esc(operation.specType().getSimpleName())).append("\"><defaultSpec>")
|
||||
.append(operation.defaultSpec() == null ? "" : esc(labelOf(operation.defaultSpec())))
|
||||
.append("</defaultSpec></keyOperation>");
|
||||
}
|
||||
|
||||
@@ -14,20 +14,20 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
||||
/**
|
||||
* Immutable metadata for one exact key operation.
|
||||
*
|
||||
* @param operation operation guaranteed by the associated lookup
|
||||
* @param specType exact accepted specification type
|
||||
* @param operation operation guaranteed by the associated lookup
|
||||
* @param specType exact accepted specification type
|
||||
* @param defaultSpec resolved generation default, or {@code null} for import
|
||||
* operations and generators without a default
|
||||
* @since 1.0
|
||||
*/
|
||||
public record KeyOperationInfo(KeyOperation operation,
|
||||
Class<? extends AlgorithmKeySpec> specType, AlgorithmKeySpec defaultSpec) {
|
||||
public record KeyOperationInfo(KeyOperation operation, Class<? extends AlgorithmKeySpec> specType,
|
||||
AlgorithmKeySpec defaultSpec) {
|
||||
|
||||
/**
|
||||
* Validates the metadata invariant.
|
||||
*
|
||||
* @throws NullPointerException if {@code operation} or {@code specType} is
|
||||
* {@code null}
|
||||
* @throws NullPointerException if {@code operation} or {@code specType} is
|
||||
* {@code null}
|
||||
* @throws IllegalArgumentException if a default is incompatible with
|
||||
* {@code specType}, or an import operation
|
||||
* declares a default
|
||||
@@ -38,9 +38,9 @@ public record KeyOperationInfo(KeyOperation operation,
|
||||
if (defaultSpec != null && !specType.isInstance(defaultSpec)) {
|
||||
throw new IllegalArgumentException("defaultSpec must be an instance of " + specType.getName());
|
||||
}
|
||||
if (defaultSpec != null && (operation == KeyOperation.SYMMETRIC_IMPORT
|
||||
|| operation == KeyOperation.ASYMMETRIC_PUBLIC_IMPORT
|
||||
|| operation == KeyOperation.ASYMMETRIC_PRIVATE_IMPORT)) {
|
||||
if (defaultSpec != null
|
||||
&& (operation == KeyOperation.SYMMETRIC_IMPORT || operation == KeyOperation.ASYMMETRIC_PUBLIC_IMPORT
|
||||
|| operation == KeyOperation.ASYMMETRIC_PRIVATE_IMPORT)) {
|
||||
throw new IllegalArgumentException("import operations cannot declare a default specification");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +147,8 @@ public abstract class AbstractCryptoAlgorithm extends CryptoAlgorithm {
|
||||
*
|
||||
* <h4>Validation</h4> Type checks happen at creation time (via {@code bind})
|
||||
* and again when
|
||||
* {@link CryptoAlgorithm#createContext(KeyUsage, Key, ContextSpec)} is
|
||||
* called. If a factory returns a context not assignable to {@code ctxType}, an
|
||||
* {@link CryptoAlgorithm#createContext(KeyUsage, Key, ContextSpec)} is called.
|
||||
* If a factory returns a context not assignable to {@code ctxType}, an
|
||||
* {@link IllegalStateException} will be thrown.
|
||||
*
|
||||
* @param family high-level algorithm family classification
|
||||
|
||||
@@ -63,9 +63,10 @@ import zeroecho.core.util.RandomSupport;
|
||||
*
|
||||
* <p>
|
||||
* IV and optional AAD are exchanged via a {@link conflux.CtxInterface} set with
|
||||
* {@link #setContext(conflux.CtxInterface)}. Encryption always generates a fresh
|
||||
* IV after atomically claiming the context; a caller-provided IV is never used
|
||||
* for encryption. Decryption requires the IV from the context or encoded header.
|
||||
* {@link #setContext(conflux.CtxInterface)}. Encryption always generates a
|
||||
* fresh IV after atomically claiming the context; a caller-provided IV is never
|
||||
* used for encryption. Decryption requires the IV from the context or encoded
|
||||
* header.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
@@ -311,10 +312,7 @@ public final class AesCipherContext implements EncryptionContext, ContextAware {
|
||||
|
||||
/** Single-use encryption lifecycle states. */
|
||||
private enum OperationState {
|
||||
NEW,
|
||||
ENCRYPTING,
|
||||
COMPLETED,
|
||||
FAILED
|
||||
NEW, ENCRYPTING, COMPLETED, FAILED
|
||||
}
|
||||
|
||||
/** Marks the owning encryption context terminal as its stream is consumed. */
|
||||
|
||||
@@ -60,8 +60,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Objects of this type are thread-safe while active and may be destroyed to wipe
|
||||
* their owned key bytes. Access and marshalling fail after destruction.
|
||||
* Objects of this type are thread-safe while active and may be destroyed to
|
||||
* wipe their owned key bytes. Access and marshalling fail after destruction.
|
||||
* </p>
|
||||
*
|
||||
* @since 1.0
|
||||
|
||||
@@ -243,8 +243,8 @@ abstract class AbstractChaChaCipherContext<S extends ChaChaBaseSpec> implements
|
||||
* Ensures a nonce is available in the context.
|
||||
*
|
||||
* <ul>
|
||||
* <li>For encryption, always generates a new nonce after the context has
|
||||
* been atomically claimed and stores a copy in the context.</li>
|
||||
* <li>For encryption, always generates a new nonce after the context has been
|
||||
* atomically claimed and stores a copy in the context.</li>
|
||||
* <li>For decryption, validates presence and correct length.</li>
|
||||
* </ul>
|
||||
*
|
||||
@@ -283,10 +283,7 @@ abstract class AbstractChaChaCipherContext<S extends ChaChaBaseSpec> implements
|
||||
|
||||
/** Single-use encryption lifecycle states. */
|
||||
private enum OperationState {
|
||||
NEW,
|
||||
ENCRYPTING,
|
||||
COMPLETED,
|
||||
FAILED
|
||||
NEW, ENCRYPTING, COMPLETED, FAILED
|
||||
}
|
||||
|
||||
/** Marks the owning encryption context terminal as its stream is consumed. */
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
package zeroecho.core.alg.chacha;
|
||||
|
||||
import zeroecho.core.util.RandomSupport;
|
||||
|
||||
/**
|
||||
* <h2>ChaCha20 (stream) algorithm</h2>
|
||||
*
|
||||
|
||||
@@ -39,10 +39,10 @@
|
||||
* the stream cipher ChaCha20 and the AEAD construction ChaCha20-Poly1305. The
|
||||
* module contains algorithm descriptors, streaming cipher contexts,
|
||||
* configuration specifications, optional header codecs for runtime parameters,
|
||||
* and symmetric key import/generation specifications. Key import
|
||||
* specifications are destroyable. The design favors safe defaults
|
||||
* (12-byte nonces, 128-bit AEAD tag), explicit role-to-context binding, and a
|
||||
* clear separation between static configuration and per-operation parameters.
|
||||
* and symmetric key import/generation specifications. Key import specifications
|
||||
* are destroyable. The design favors safe defaults (12-byte nonces, 128-bit
|
||||
* AEAD tag), explicit role-to-context binding, and a clear separation between
|
||||
* static configuration and per-operation parameters.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Components</h2>
|
||||
|
||||
@@ -44,9 +44,9 @@ import zeroecho.core.context.AgreementContext;
|
||||
* <h2>Generic JCA-based Key Agreement Context</h2>
|
||||
*
|
||||
* An {@link AgreementContext} backed by the standard JCA key-agreement API.
|
||||
* This class supports elliptic-curve and modern Diffie-Hellman variants
|
||||
* such as ECDH, XDH (X25519, X448), and others provided by the runtime or
|
||||
* configured provider.
|
||||
* This class supports elliptic-curve and modern Diffie-Hellman variants such as
|
||||
* ECDH, XDH (X25519, X448), and others provided by the runtime or configured
|
||||
* provider.
|
||||
*
|
||||
* <p>
|
||||
* Instances of this context are created with a local {@link PrivateKey}, and
|
||||
|
||||
@@ -99,9 +99,9 @@
|
||||
* reconstructs public keys from X.509 encodings via
|
||||
* {@link java.security.KeyFactory}.</li>
|
||||
* <li><b>Signature contexts:</b>
|
||||
* {@link zeroecho.core.alg.common.eddsa.CommonEdDSASignatureContext}
|
||||
* delegates all operations to a generic JCA-backed signature adapter, enforcing
|
||||
* a fixed tag length for the selected EdDSA variant.</li>
|
||||
* {@link zeroecho.core.alg.common.eddsa.CommonEdDSASignatureContext} delegates
|
||||
* all operations to a generic JCA-backed signature adapter, enforcing a fixed
|
||||
* tag length for the selected EdDSA variant.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Design notes</h2>
|
||||
|
||||
@@ -116,7 +116,8 @@ public final class SignatureInteropProfile { // NOPMD
|
||||
* @param keyAlgorithmId ZeroEcho key algorithm identifier used for key
|
||||
* import and matching, such as {@code RSA}
|
||||
* @param contextAlgorithmId ZeroEcho context algorithm identifier used
|
||||
* with {@code ZeroEchoSession.createContext(...)}
|
||||
* with
|
||||
* {@code ZeroEchoSession.createContext(...)}
|
||||
* @param contextSpec explicit ZeroEcho context specification
|
||||
* @param signatureRepresentation signature representation bridge between
|
||||
* external bytes and internal ZeroEcho bytes
|
||||
|
||||
@@ -60,9 +60,9 @@
|
||||
* configured {@link java.security.Signature}, resolves a fixed tag length (via
|
||||
* resolvers), and exposes a one-shot {@code wrap(InputStream)} API.
|
||||
* Verification behavior is controlled by a pluggable comparison approach.</li>
|
||||
* <li><b>SignatureStream</b> - internal passthrough input stream that feeds chunks to
|
||||
* the signature engine, emits the trailer in SIGN mode, and performs final
|
||||
* verification in VERIFY mode.</li>
|
||||
* <li><b>SignatureStream</b> - internal passthrough input stream that feeds
|
||||
* chunks to the signature engine, emits the trailer in SIGN mode, and performs
|
||||
* final verification in VERIFY mode.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Length resolution</h2>
|
||||
|
||||
@@ -117,8 +117,8 @@ public final class Sha2Sha3Algorithm extends AbstractCryptoAlgorithm {
|
||||
MessageDigest md = MessageDigest.getInstance(s.algorithm().jca());
|
||||
return new JcaDigestContext(this, md, s);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new ProviderFailureException(
|
||||
"Failed to initialize MessageDigest " + s.algorithm().jca(), e);
|
||||
throw new ProviderFailureException("Failed to initialize MessageDigest " + s.algorithm().jca(),
|
||||
e);
|
||||
}
|
||||
}, DigestSpec::sha256 // default for catalog/tests
|
||||
);
|
||||
|
||||
@@ -160,8 +160,7 @@ public final class EcdhAlgorithm extends AbstractCryptoAlgorithm {
|
||||
() -> EcdsaCurveSpec.P256);
|
||||
|
||||
// Reuse EC builders/importers
|
||||
registerAsymmetricKeyPairGenerator(EcdhCurveSpec.class, new EcdhKeyGenBuilder(),
|
||||
() -> EcdhCurveSpec.P256);
|
||||
registerAsymmetricKeyPairGenerator(EcdhCurveSpec.class, new EcdhKeyGenBuilder(), () -> EcdhCurveSpec.P256);
|
||||
registerPublicKeyImporter(EcdsaPublicKeySpec.class, new EcdsaPublicKeyBuilder());
|
||||
registerPrivateKeyImporter(EcdsaPrivateKeySpec.class, new EcdsaPrivateKeyBuilder());
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
|
||||
/**
|
||||
* <h2>ECDH Key Pair Generator</h2>
|
||||
*
|
||||
* Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for elliptic curve
|
||||
* Diffie-Hellman (ECDH) key pairs.
|
||||
* Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for
|
||||
* elliptic curve Diffie-Hellman (ECDH) key pairs.
|
||||
*
|
||||
* <p>
|
||||
* This builder generates fresh EC key pairs suitable for ECDH key agreement. It
|
||||
|
||||
@@ -103,8 +103,8 @@ public final class EcdsaAlgorithm extends AbstractCryptoAlgorithm {
|
||||
* <p>
|
||||
* On construction, the algorithm declares its supported roles and registers
|
||||
* builders with the {@link CryptoAlgorithm} infrastructure so they can be
|
||||
* discovered by the {@link CryptoCatalog} or invoked through the
|
||||
* session-bound {@link zeroecho.sdk.KeyBuilders} entry point.
|
||||
* discovered by the {@link CryptoCatalog} or invoked through the session-bound
|
||||
* {@link zeroecho.sdk.KeyBuilders} entry point.
|
||||
* </p>
|
||||
*/
|
||||
public EcdsaAlgorithm() {
|
||||
@@ -134,8 +134,7 @@ public final class EcdsaAlgorithm extends AbstractCryptoAlgorithm {
|
||||
}
|
||||
}, () -> EcdsaCurveSpec.P256);
|
||||
|
||||
registerAsymmetricKeyPairGenerator(EcdsaCurveSpec.class, new EcdsaKeyGenBuilder(),
|
||||
() -> EcdsaCurveSpec.P256);
|
||||
registerAsymmetricKeyPairGenerator(EcdsaCurveSpec.class, new EcdsaKeyGenBuilder(), () -> EcdsaCurveSpec.P256);
|
||||
registerPublicKeyImporter(EcdsaPublicKeySpec.class, new EcdsaPublicKeyBuilder());
|
||||
registerPrivateKeyImporter(EcdsaPrivateKeySpec.class, new EcdsaPrivateKeyBuilder());
|
||||
}
|
||||
|
||||
@@ -45,14 +45,14 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
|
||||
* <h2>ECDSA Key Pair Generator</h2>
|
||||
*
|
||||
* Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for
|
||||
* {@link EcdsaCurveSpec}.
|
||||
* This builder is responsible for generating new elliptic curve key pairs for
|
||||
* use with the {@link EcdsaAlgorithm}.
|
||||
* {@link EcdsaCurveSpec}. This builder is responsible for generating new
|
||||
* elliptic curve key pairs for use with the {@link EcdsaAlgorithm}.
|
||||
*
|
||||
* <p>The exact supported operation is
|
||||
* {@link #generateKeyPair(EcdsaCurveSpec)}. Public and private import are
|
||||
* registered separately through {@link EcdsaPublicKeyBuilder} and
|
||||
* {@link EcdsaPrivateKeyBuilder}.</p>
|
||||
* <p>
|
||||
* The exact supported operation is {@link #generateKeyPair(EcdsaCurveSpec)}.
|
||||
* Public and private import are registered separately through
|
||||
* {@link EcdsaPublicKeyBuilder} and {@link EcdsaPrivateKeyBuilder}.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Usage</h2> Typically accessed through the session key-operation API or
|
||||
* {@link CryptoAlgorithm#asymmetricKeyPairGenerator(Class)}.
|
||||
|
||||
@@ -49,9 +49,11 @@ import zeroecho.core.spi.PrivateKeyImporter;
|
||||
* {@link EcdsaPrivateKeySpec}. This builder is responsible for importing ECDSA
|
||||
* private keys from encoded representations.
|
||||
*
|
||||
* <p>The exact supported operation is
|
||||
* {@link #importPrivate(EcdsaPrivateKeySpec)}. Generation and public import are
|
||||
* registered through their own operation-specific implementations.</p>
|
||||
* <p>
|
||||
* The exact supported operation is {@link #importPrivate(EcdsaPrivateKeySpec)}.
|
||||
* Generation and public import are registered through their own
|
||||
* operation-specific implementations.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Encoding</h2> The {@link EcdsaPrivateKeySpec} stores the private key in
|
||||
* PKCS#8 DER format. This builder delegates to a JCA {@link KeyFactory} for the
|
||||
|
||||
@@ -48,9 +48,11 @@ import zeroecho.core.spi.PublicKeyImporter;
|
||||
* {@link EcdsaPublicKeySpec}. This builder is responsible for importing ECDSA
|
||||
* public keys from X.509 SubjectPublicKeyInfo encodings.
|
||||
*
|
||||
* <p>The exact supported operation is
|
||||
* {@link #importPublic(EcdsaPublicKeySpec)}. Generation and private import are
|
||||
* registered through their own operation-specific implementations.</p>
|
||||
* <p>
|
||||
* The exact supported operation is {@link #importPublic(EcdsaPublicKeySpec)}.
|
||||
* Generation and private import are registered through their own
|
||||
* operation-specific implementations.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Encoding</h2> The {@link EcdsaPublicKeySpec} stores the public key in
|
||||
* standard X.509 DER format. This builder delegates to a JCA {@link KeyFactory}
|
||||
|
||||
@@ -38,8 +38,8 @@ import zeroecho.core.alg.common.eddsa.AbstractEdDSAKeyGenBuilder;
|
||||
/**
|
||||
* <h2>Key-pair builder for Ed25519</h2>
|
||||
*
|
||||
* Concrete {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} implementation for
|
||||
* generating Ed25519 key pairs.
|
||||
* Concrete {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} implementation
|
||||
* for generating Ed25519 key pairs.
|
||||
*
|
||||
* <p>
|
||||
* This builder delegates to the JCA provider under the canonical algorithm name
|
||||
|
||||
@@ -38,8 +38,8 @@ import zeroecho.core.alg.common.eddsa.AbstractEncodedPrivateKeyBuilder;
|
||||
/**
|
||||
* <h2>Private key builder for Ed25519</h2>
|
||||
*
|
||||
* Concrete {@link zeroecho.core.spi.PrivateKeyImporter} for importing
|
||||
* wrapping Ed25519 private keys.
|
||||
* Concrete {@link zeroecho.core.spi.PrivateKeyImporter} for importing wrapping
|
||||
* Ed25519 private keys.
|
||||
*
|
||||
* <p>
|
||||
* This builder integrates with the JCA under the canonical key factory
|
||||
|
||||
@@ -38,8 +38,8 @@ import zeroecho.core.alg.common.eddsa.AbstractEncodedPublicKeyBuilder;
|
||||
/**
|
||||
* <h2>Public key builder for Ed25519</h2>
|
||||
*
|
||||
* Concrete {@link zeroecho.core.spi.PublicKeyImporter} for importing
|
||||
* wrapping Ed25519 public keys.
|
||||
* Concrete {@link zeroecho.core.spi.PublicKeyImporter} for importing wrapping
|
||||
* Ed25519 public keys.
|
||||
*
|
||||
* <p>
|
||||
* This builder integrates with the JCA under the canonical key factory
|
||||
|
||||
@@ -66,8 +66,7 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
||||
*
|
||||
* <h2>Thread-safety</h2> Stateless and safe for concurrent use. Each call to
|
||||
* {@link zeroecho.core.spi.PrivateKeyImporter#importPrivate(AlgorithmKeySpec)}
|
||||
* creates a new
|
||||
* {@link java.security.KeyFactory}.
|
||||
* creates a new {@link java.security.KeyFactory}.
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
@@ -65,8 +65,7 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
||||
*
|
||||
* <h2>Thread-safety</h2> Stateless and safe for concurrent use. Each call to
|
||||
* {@link zeroecho.core.spi.PublicKeyImporter#importPublic(AlgorithmKeySpec)}
|
||||
* creates a new
|
||||
* {@link java.security.KeyFactory}.
|
||||
* creates a new {@link java.security.KeyFactory}.
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
@@ -63,7 +63,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
||||
* </p>
|
||||
*
|
||||
* @see KyberAlgorithm
|
||||
* @see zeroecho.sdk.KeyBuilders.Asymmetric#generateKeyPair(String, AlgorithmKeySpec)
|
||||
* @see zeroecho.sdk.KeyBuilders.Asymmetric#generateKeyPair(String,
|
||||
* AlgorithmKeySpec)
|
||||
*/
|
||||
public final class KyberKeyGenSpec implements AlgorithmKeySpec, Describable {
|
||||
/**
|
||||
|
||||
@@ -47,9 +47,9 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
||||
* Specification wrapper for a Kyber (ML-KEM) private key encoded in PKCS#8.
|
||||
*
|
||||
* <p>
|
||||
* Instances of this class carry an owned copy of the PKCS#8-encoded private
|
||||
* key bytes. They are used with {@link zeroecho.core.CryptoAlgorithm} key
|
||||
* builders to import keys into the provider’s native representation.
|
||||
* Instances of this class carry an owned copy of the PKCS#8-encoded private key
|
||||
* bytes. They are used with {@link zeroecho.core.CryptoAlgorithm} key builders
|
||||
* to import keys into the provider’s native representation.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Encoding</h2>
|
||||
|
||||
@@ -102,8 +102,7 @@ public record BlockGeometry(int inChunkSize, int outChunkSize, int finalizationO
|
||||
"inChunkSize must not exceed outChunkSize: " + inChunkSize + " > " + outChunkSize);
|
||||
}
|
||||
if (finalizationOutputChunks != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"finalizationOutputChunks must be zero: " + finalizationOutputChunks);
|
||||
throw new IllegalArgumentException("finalizationOutputChunks must be zero: " + finalizationOutputChunks);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
||||
* A {@code SaberKeyGenSpec} selects one of the SABER parameter variants
|
||||
* standardized in round-3 submissions. Each variant balances performance,
|
||||
* bandwidth, and security level. This spec is passed to a registered
|
||||
* {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} to generate a SABER key pair.
|
||||
* {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} to generate a SABER key
|
||||
* pair.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Variants</h2> The {@link Variant} enumeration identifies supported SABER
|
||||
|
||||
@@ -54,10 +54,12 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
|
||||
* Reflection is used to avoid a hard dependency on all parameter variants.
|
||||
* </p>
|
||||
*
|
||||
* <p>The exact supported operation is
|
||||
* {@link #generateKeyPair(SphincsPlusKeyGenSpec)}. Public and private import are
|
||||
* registered separately for {@link SphincsPlusPublicKeySpec} and
|
||||
* {@link SphincsPlusPrivateKeySpec}.</p>
|
||||
* <p>
|
||||
* The exact supported operation is
|
||||
* {@link #generateKeyPair(SphincsPlusKeyGenSpec)}. Public and private import
|
||||
* are registered separately for {@link SphincsPlusPublicKeySpec} and
|
||||
* {@link SphincsPlusPrivateKeySpec}.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Example</h2> <pre>{@code
|
||||
* SphincsPlusKeyGenSpec spec =
|
||||
|
||||
@@ -51,9 +51,11 @@ import zeroecho.core.spi.PrivateKeyImporter;
|
||||
* pairs but focuses solely on importing private key material.
|
||||
* </p>
|
||||
*
|
||||
* <p>The exact supported operation is
|
||||
* <p>
|
||||
* The exact supported operation is
|
||||
* {@link #importPrivate(SphincsPlusPrivateKeySpec)}. Other key operations are
|
||||
* registered through their own exact interfaces.</p>
|
||||
* registered through their own exact interfaces.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Example</h2> <pre>{@code
|
||||
* // Assuming bytes contain a PKCS#8-encoded SPHINCS+ private key:
|
||||
|
||||
@@ -48,7 +48,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
||||
* <p>
|
||||
* {@code SphincsPlusPrivateKeySpec} wraps a PKCS#8-encoded SPHINCS+ private key
|
||||
* along with the provider name that should be used for import. It is a simple
|
||||
* destroyable holder designed for use with {@link SphincsPlusPrivateKeyBuilder}.
|
||||
* destroyable holder designed for use with
|
||||
* {@link SphincsPlusPrivateKeyBuilder}.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Encoding</h2>
|
||||
|
||||
@@ -50,9 +50,11 @@ import zeroecho.core.spi.PublicKeyImporter;
|
||||
* pairs, but focuses solely on importing public key material.
|
||||
* </p>
|
||||
*
|
||||
* <p>The exact supported operation is
|
||||
* <p>
|
||||
* The exact supported operation is
|
||||
* {@link #importPublic(SphincsPlusPublicKeySpec)}. Other key operations are
|
||||
* registered through their own exact interfaces.</p>
|
||||
* registered through their own exact interfaces.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Example</h2> <pre>{@code
|
||||
* // Assuming bytes contain an X.509-encoded SPHINCS+ public key:
|
||||
|
||||
@@ -51,7 +51,8 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
|
||||
* <h2>Design and scope</h2>
|
||||
* <ul>
|
||||
* <li><b>Generation only:</b> This implementation exposes only the exact
|
||||
* key-pair generation capability; import operations are registered separately.</li>
|
||||
* key-pair generation capability; import operations are registered
|
||||
* separately.</li>
|
||||
* <li><b>Provider resolution:</b> The default JCA provider selection is used.
|
||||
* If a specific provider is required, supply or register one that exposes the
|
||||
* requested XDH algorithm name.</li>
|
||||
|
||||
@@ -13,10 +13,12 @@ import java.util.Objects;
|
||||
/**
|
||||
* Utilities for enforcing the best-effort audit-listener contract.
|
||||
*
|
||||
* <p>The returned listener suppresses listener failures without logging callback
|
||||
* <p>
|
||||
* The returned listener suppresses listener failures without logging callback
|
||||
* arguments, because those arguments may refer to sensitive cryptographic
|
||||
* objects. Cryptographic operation outcomes therefore never depend on an audit
|
||||
* sink's availability.</p>
|
||||
* sink's availability.
|
||||
* </p>
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
@@ -36,8 +38,8 @@ public final class AuditListeners {
|
||||
AuditListener target = Objects.requireNonNull(listener, "listener");
|
||||
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
|
||||
ClassLoader loader = contextLoader == null ? ClassLoader.getSystemClassLoader() : contextLoader;
|
||||
return (AuditListener) Proxy.newProxyInstance(loader,
|
||||
new Class<?>[] { AuditListener.class }, (proxy, method, arguments) -> {
|
||||
return (AuditListener) Proxy.newProxyInstance(loader, new Class<?>[] { AuditListener.class },
|
||||
(proxy, method, arguments) -> {
|
||||
if (method.getDeclaringClass() == Object.class) {
|
||||
return method.invoke(target, arguments);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ package zeroecho.core.audit;
|
||||
/**
|
||||
* Defines the session-owned automatic auditing strategy.
|
||||
*
|
||||
* <p>Audit listener failures are best-effort diagnostics and never change the
|
||||
* outcome of a cryptographic operation.</p>
|
||||
* <p>
|
||||
* Audit listener failures are best-effort diagnostics and never change the
|
||||
* outcome of a cryptographic operation.
|
||||
* </p>
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
@@ -105,8 +105,8 @@ import zeroecho.core.spec.ContextSpec;
|
||||
* <li>Counting is performed by decorating the returned {@code InputStream}s; no
|
||||
* buffering beyond normal {@code FilterInputStream} forwarding is
|
||||
* introduced.</li>
|
||||
* <li>Idempotent wrapping: contexts already wrapped by this utility are returned
|
||||
* unchanged. Unrelated JDK proxies are wrapped normally.</li>
|
||||
* <li>Idempotent wrapping: contexts already wrapped by this utility are
|
||||
* returned unchanged. Unrelated JDK proxies are wrapped normally.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Usage example</h2> <pre>{@code
|
||||
@@ -190,8 +190,7 @@ public final class AuditedContexts {
|
||||
}
|
||||
|
||||
private static boolean isAuditedProxy(CryptoContext context) {
|
||||
return Proxy.isProxyClass(context.getClass())
|
||||
&& Proxy.getInvocationHandler(context) instanceof AuditingHandler;
|
||||
return Proxy.isProxyClass(context.getClass()) && Proxy.getInvocationHandler(context) instanceof AuditingHandler;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -242,8 +241,7 @@ public final class AuditedContexts {
|
||||
}
|
||||
|
||||
safeAudit.onContextCreatedMeta(ctxId, algoId == null ? UNKNOWN : algoId,
|
||||
provider == null ? UNKNOWN : provider,
|
||||
role, keyFp, specMeta);
|
||||
provider == null ? UNKNOWN : provider, role, keyFp, specMeta);
|
||||
}
|
||||
|
||||
ClassLoader cl = ctx.getClass().getClassLoader(); // NOPMD
|
||||
@@ -720,8 +718,7 @@ public final class AuditedContexts {
|
||||
StringBuilder fingerprint = new StringBuilder(16);
|
||||
for (int index = 0; index < Math.min(8, digest.length); index++) {
|
||||
int value = digest[index] & 0xff;
|
||||
fingerprint.append(Character.forDigit(value >>> 4, 16))
|
||||
.append(Character.forDigit(value & 0x0f, 16));
|
||||
fingerprint.append(Character.forDigit(value >>> 4, 16)).append(Character.forDigit(value & 0x0f, 16));
|
||||
}
|
||||
return key.getAlgorithm() + ":" + fingerprint;
|
||||
} catch (NoSuchAlgorithmException exception) {
|
||||
|
||||
@@ -204,9 +204,9 @@ public final class JulAuditListenerStd implements AuditListener {
|
||||
* appends a stack trace in addition to the structured summary.
|
||||
*
|
||||
* <p>
|
||||
* Stack traces may contain provider exception messages or application
|
||||
* values. Enabling them is an explicit diagnostic opt-in and requires a
|
||||
* suitably protected log destination.
|
||||
* Stack traces may contain provider exception messages or application values.
|
||||
* Enabling them is an explicit diagnostic opt-in and requires a suitably
|
||||
* protected log destination.
|
||||
* </p>
|
||||
*
|
||||
* @param include true to include stack traces, false to omit them
|
||||
@@ -600,8 +600,7 @@ public final class JulAuditListenerStd implements AuditListener {
|
||||
StringBuilder sb = new StringBuilder(key.getAlgorithm()).append(':');
|
||||
for (int i = 0; i < Math.min(8, digest.length); i++) {
|
||||
int value = digest[i] & 0xff;
|
||||
sb.append(Character.forDigit(value >>> 4, 16))
|
||||
.append(Character.forDigit(value & 0x0f, 16));
|
||||
sb.append(Character.forDigit(value >>> 4, 16)).append(Character.forDigit(value & 0x0f, 16));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
|
||||
@@ -46,13 +46,11 @@ package zeroecho.core.err;
|
||||
* <h2>When it is thrown</h2>
|
||||
* <ul>
|
||||
* <li>During
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}
|
||||
* after policy validation, if the resolved algorithm exposes no bindings for
|
||||
* the given role.</li>
|
||||
* <li>Directly from
|
||||
* {@link zeroecho.core.CryptoAlgorithm#createContext(zeroecho.core.KeyUsage,
|
||||
* java.security.Key, zeroecho.core.spec.ContextSpec)}
|
||||
* {@link zeroecho.core.CryptoAlgorithm#createContext(zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}
|
||||
* when no binding exists for the role.</li>
|
||||
* </ul>
|
||||
*
|
||||
|
||||
@@ -157,8 +157,8 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
|
||||
* @param outChunkSize size of output chunks produced by the transform (must be
|
||||
* > 0)
|
||||
* @param chunks number of chunks buffered at once (must be > 0)
|
||||
* @throws IllegalArgumentException if a size is outside its documented range
|
||||
* or a buffer size overflows
|
||||
* @throws IllegalArgumentException if a size is outside its documented range or
|
||||
* a buffer size overflows
|
||||
*/
|
||||
protected AbstractChunkTransformInputStream(InputStream upstream, int inChunkSize, int outChunkSize, int chunks) {
|
||||
super(upstream);
|
||||
@@ -190,8 +190,8 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
|
||||
* steady-state (must be > 0)
|
||||
* @param finalizationOutputChunks number of extra output chunks reserved for
|
||||
* finalization (must be >= 0)
|
||||
* @throws IllegalArgumentException if a size is outside its documented range
|
||||
* or a buffer size overflows
|
||||
* @throws IllegalArgumentException if a size is outside its documented range or
|
||||
* a buffer size overflows
|
||||
*/
|
||||
protected AbstractChunkTransformInputStream(InputStream upstream, int inChunkSize, int outChunkSize, int chunks,
|
||||
int finalizationOutputChunks) {
|
||||
@@ -261,8 +261,7 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
|
||||
// EOF: run finalization exactly once, even if there's no remainder,
|
||||
// and surface any produced bytes (e.g., padding block, GCM tag).
|
||||
if (!eofSeen) {
|
||||
int finalOut = validateOutputCount(doFinal(inBuf, 0, 0, outBuf, 0), outBuf.length,
|
||||
"finalization");
|
||||
int finalOut = validateOutputCount(doFinal(inBuf, 0, 0, outBuf, 0), outBuf.length, "finalization");
|
||||
outPtr = 0;
|
||||
outLen = finalOut;
|
||||
eofSeen = true;
|
||||
@@ -273,8 +272,7 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
|
||||
|
||||
// all chunks are aligned to the specified boundary (inChunkSize) -> transform
|
||||
// can be simply invoked
|
||||
outLen = validateOutputCount(transform(inBuf, 0, inLen / inChunkSize, outBuf), outBuf.length,
|
||||
"transformation");
|
||||
outLen = validateOutputCount(transform(inBuf, 0, inLen / inChunkSize, outBuf), outBuf.length, "transformation");
|
||||
outPtr = 0;
|
||||
|
||||
int left = inLen % inChunkSize;
|
||||
@@ -291,8 +289,7 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void validateGeometry(int inChunkSize, int outChunkSize, int chunks,
|
||||
int finalizationOutputChunks) {
|
||||
private static void validateGeometry(int inChunkSize, int outChunkSize, int chunks, int finalizationOutputChunks) {
|
||||
if (inChunkSize < MIN_INPUT_CHUNK_SIZE) {
|
||||
throw new IllegalArgumentException("inChunkSize must be greater than 1");
|
||||
}
|
||||
|
||||
@@ -53,13 +53,12 @@ import javax.crypto.Cipher;
|
||||
* block; a final partial block (if any) is processed by a single
|
||||
* {@code doFinal}. This mode is restricted to RSA and ElGamal.</li>
|
||||
* <li><b>Left-padded independent block stream</b> - like the independent block
|
||||
* stream, but
|
||||
* left-pads each transformed output block with zeros up to
|
||||
* stream, but left-pads each transformed output block with zeros up to
|
||||
* {@code outChunkSize}. Final blocks must be complete; otherwise an
|
||||
* {@link IllegalStateException} is thrown.</li>
|
||||
* <li><b>Continuous stream</b> - uses
|
||||
* {@code Cipher.update(...)} for bulk bytes and a single {@code doFinal()} at
|
||||
* end of stream. This is suitable for CTR/CFB/OFB/GCM and padding modes.</li>
|
||||
* <li><b>Continuous stream</b> - uses {@code Cipher.update(...)} for bulk bytes
|
||||
* and a single {@code doFinal()} at end of stream. This is suitable for
|
||||
* CTR/CFB/OFB/GCM and padding modes.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Block sizing</h2>
|
||||
@@ -344,7 +343,8 @@ public final class CipherTransformInputStreamBuilder {
|
||||
* </p>
|
||||
*
|
||||
* @return a new InputStream that transforms bytes on the fly
|
||||
* @throws NullPointerException if {@code upstream} or {@code cipher} is null
|
||||
* @throws NullPointerException if {@code upstream} or {@code cipher} is
|
||||
* null
|
||||
* @throws IllegalArgumentException if independent-block processing is selected
|
||||
* for an unsupported algorithm or buffer
|
||||
* geometry is invalid
|
||||
|
||||
@@ -58,6 +58,7 @@ final class SmartBlockStream extends AbstractChunkTransformInputStream {
|
||||
private static final Logger LOG = Logger.getLogger(SmartBlockStream.class.getName());
|
||||
|
||||
private final Cipher cipher;
|
||||
|
||||
/* package */ SmartBlockStream(InputStream upstream, Cipher cipher, int inChunkSize, int outChunkSize,
|
||||
int bufferedBlocks) {
|
||||
super(upstream, inChunkSize, outChunkSize, bufferedBlocks);
|
||||
|
||||
@@ -59,6 +59,7 @@ final class SmartPaddedBlockStream extends AbstractChunkTransformInputStream {
|
||||
private static final Logger LOG = Logger.getLogger(SmartPaddedBlockStream.class.getName());
|
||||
|
||||
private final Cipher cipher;
|
||||
|
||||
/* package */ SmartPaddedBlockStream(InputStream upstream, Cipher cipher, int inChunkSize, int outChunkSize,
|
||||
int bufferedBlocks) {
|
||||
super(upstream, inChunkSize, outChunkSize, bufferedBlocks);
|
||||
|
||||
@@ -54,12 +54,12 @@
|
||||
* then calls {@code onCompleted()} exactly once at EOF.</li>
|
||||
* <li>{@link CipherTransformInputStreamBuilder} - fluent builder that creates
|
||||
* cipher-backed streams for RSA/ElGamal independent-block processing,
|
||||
* left-zero-padded independent blocks, or
|
||||
* continuous {@code update}+{@code doFinal} streaming.</li>
|
||||
* left-zero-padded independent blocks, or continuous
|
||||
* {@code update}+{@code doFinal} streaming.</li>
|
||||
* <li>{@link SmartBlockStream}, {@link SmartPaddedBlockStream},
|
||||
* {@link SmartContinuousBlockStream} - internal cipher-backed stream variants;
|
||||
* the first two are restricted to independent RSA or ElGamal blocks
|
||||
* used by the builder.</li>
|
||||
* the first two are restricted to independent RSA or ElGamal blocks used by the
|
||||
* builder.</li>
|
||||
* <li>{@link TailStrippingInputStream} - withholds the last N bytes from the
|
||||
* payload and delivers them to a callback at EOF (useful for tags, checksums,
|
||||
* or footers).</li>
|
||||
|
||||
@@ -61,8 +61,8 @@ import java.util.List;
|
||||
*
|
||||
* <h2>Serialization</h2>
|
||||
* <ul>
|
||||
* <li>{@link #writeTo(Appendable)} outputs each pair as {@code k=v\n}
|
||||
* lines without escaping and reports checked I/O failures.</li>
|
||||
* <li>{@link #writeTo(Appendable)} outputs each pair as {@code k=v\n} lines
|
||||
* without escaping and reports checked I/O failures.</li>
|
||||
* <li>{@link #readFrom(java.io.Reader)} parses lines in the same format,
|
||||
* ignoring blank lines and comments starting with {@code #}.</li>
|
||||
* </ul>
|
||||
@@ -99,8 +99,7 @@ public final class PairSeq {
|
||||
if (kv[elementIndex] == null) {
|
||||
int pairIndex = elementIndex >>> 1;
|
||||
String role = (elementIndex & 1) == 0 ? "key" : "value";
|
||||
throw new IllegalArgumentException(
|
||||
"pair " + pairIndex + " " + role + " must not be null");
|
||||
throw new IllegalArgumentException("pair " + pairIndex + " " + role + " must not be null");
|
||||
}
|
||||
}
|
||||
return new PairSeq(kv.clone());
|
||||
@@ -201,8 +200,8 @@ public final class PairSeq {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends all pairs to the target as {@code key=value} lines, reporting
|
||||
* checked I/O failures directly.
|
||||
* Appends all pairs to the target as {@code key=value} lines, reporting checked
|
||||
* I/O failures directly.
|
||||
*
|
||||
* <p>
|
||||
* No escaping is performed; callers must ensure keys and values do not contain
|
||||
|
||||
@@ -101,9 +101,9 @@ import java.util.function.Supplier;
|
||||
* }</pre>
|
||||
*
|
||||
* <h2>Thread-safety</h2> Instances are immutable and thread-safe. Public
|
||||
* accessors are resolved once per runtime class and operation type, then invoked
|
||||
* through cached method handles. The unload-safe {@link ClassValue} caches do
|
||||
* not retain otherwise unreachable class loaders.
|
||||
* accessors are resolved once per runtime class and operation type, then
|
||||
* invoked through cached method handles. The unload-safe {@link ClassValue}
|
||||
* caches do not retain otherwise unreachable class loaders.
|
||||
*
|
||||
* @param <T> domain type that follows the marshalling and unmarshalling
|
||||
* conventions
|
||||
@@ -274,8 +274,7 @@ public final class PairSeqCodec<T> implements Codec<T, PairSeq> {
|
||||
try {
|
||||
Method method = runtimeType.getMethod("marshal");
|
||||
if (!PairSeq.class.isAssignableFrom(method.getReturnType())) {
|
||||
return new MarshalPlan(null,
|
||||
"marshal() must return PairSeq in " + runtimeType.getName(), null);
|
||||
return new MarshalPlan(null, "marshal() must return PairSeq in " + runtimeType.getName(), null);
|
||||
}
|
||||
MethodHandle handle = MethodHandles.lookup().unreflect(method);
|
||||
return new MarshalPlan(handle, null, null);
|
||||
@@ -327,14 +326,14 @@ public final class PairSeqCodec<T> implements Codec<T, PairSeq> {
|
||||
"static unmarshal(PairSeq) must return " + runtimeType.getName(), null);
|
||||
}
|
||||
MethodHandle handle = MethodHandles.lookup().unreflect(method);
|
||||
return new UnmarshalPlan(handle,
|
||||
"static unmarshal(PairSeq) failed for " + runtimeType.getName(), null, null);
|
||||
return new UnmarshalPlan(handle, "static unmarshal(PairSeq) failed for " + runtimeType.getName(),
|
||||
null, null);
|
||||
}
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
// Resolve the constructor fallback below.
|
||||
} catch (IllegalAccessException exception) {
|
||||
return new UnmarshalPlan(null, null,
|
||||
"static unmarshal(PairSeq) failed for " + runtimeType.getName(), exception);
|
||||
return new UnmarshalPlan(null, null, "static unmarshal(PairSeq) failed for " + runtimeType.getName(),
|
||||
exception);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
******************************************************************************/
|
||||
package zeroecho.core.spec;
|
||||
|
||||
|
||||
/**
|
||||
* Marker interface for algorithm-specific key specifications.
|
||||
* <p>
|
||||
|
||||
@@ -11,7 +11,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
|
||||
|
||||
/**
|
||||
* Generates asymmetric key pairs for one exact specification type.
|
||||
* Implementations must be stateless or otherwise safe for concurrent invocation.
|
||||
* Implementations must be stateless or otherwise safe for concurrent
|
||||
* invocation.
|
||||
*
|
||||
* @param <S> specification type
|
||||
* @since 1.0
|
||||
|
||||
@@ -15,11 +15,13 @@ import zeroecho.core.spec.ContextSpec;
|
||||
/**
|
||||
* Creates a cryptographic context from a key and a context specification.
|
||||
*
|
||||
* <p>Implementations report provider and parameter failures with unchecked
|
||||
* <p>
|
||||
* Implementations report provider and parameter failures with unchecked
|
||||
* exceptions; context construction is a pure in-memory operation and does not
|
||||
* expose an I/O failure contract. Factories must be stateless or otherwise safe
|
||||
* for concurrent invocation; returned contexts retain their own documented
|
||||
* thread-safety contracts.</p>
|
||||
* thread-safety contracts.
|
||||
* </p>
|
||||
*
|
||||
* @param <C> context type produced
|
||||
* @param <K> key type accepted
|
||||
@@ -31,7 +33,7 @@ public interface ContextFactoryKS<C extends CryptoContext, K extends Key, S exte
|
||||
/**
|
||||
* Creates a context bound to the supplied key and specification.
|
||||
*
|
||||
* @param key non-null key
|
||||
* @param key non-null key
|
||||
* @param spec non-null resolved context specification
|
||||
* @return a newly created context
|
||||
*/
|
||||
|
||||
@@ -11,11 +11,13 @@ import zeroecho.core.storage.KeyringPassword;
|
||||
/**
|
||||
* Supplies a fresh destroyable password for one keyring open operation.
|
||||
*
|
||||
* <p>Ownership of the returned object transfers to the receiver, which must
|
||||
* <p>
|
||||
* Ownership of the returned object transfers to the receiver, which must
|
||||
* destroy it in a {@code finally} block immediately after the keyring has been
|
||||
* opened. Implementations must not source passwords from immutable strings,
|
||||
* process arguments, system properties, environment fallbacks, persistent
|
||||
* files, or global mutable state.</p>
|
||||
* files, or global mutable state.
|
||||
* </p>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface KeyringUnlockProvider {
|
||||
|
||||
@@ -10,8 +10,8 @@ import java.security.PrivateKey;
|
||||
import zeroecho.core.spec.AlgorithmKeySpec;
|
||||
|
||||
/**
|
||||
* Imports private keys for one exact specification type. Implementations must be
|
||||
* stateless or otherwise safe for concurrent invocation.
|
||||
* Imports private keys for one exact specification type. Implementations must
|
||||
* be stateless or otherwise safe for concurrent invocation.
|
||||
*
|
||||
* @param <S> specification type
|
||||
* @since 1.0
|
||||
|
||||
@@ -34,20 +34,26 @@
|
||||
/**
|
||||
* Provider contracts for context construction and exact key operations.
|
||||
*
|
||||
* <p>Algorithms bind each supported role to a {@link ContextFactoryKS}. Context
|
||||
* <p>
|
||||
* Algorithms bind each supported role to a {@link ContextFactoryKS}. Context
|
||||
* construction is an in-memory operation; stream attachment and processing are
|
||||
* responsible for reporting {@link java.io.IOException}.</p>
|
||||
* responsible for reporting {@link java.io.IOException}.
|
||||
* </p>
|
||||
*
|
||||
* <p>Key capabilities are registered independently through
|
||||
* <p>
|
||||
* Key capabilities are registered independently through
|
||||
* {@link SymmetricKeyGenerator}, {@link SymmetricKeyImporter},
|
||||
* {@link AsymmetricKeyPairGenerator}, {@link PublicKeyImporter}, and
|
||||
* {@link PrivateKeyImporter}. A provider registers only the operations it
|
||||
* implements, so capability lookup fails before invocation instead of returning
|
||||
* an object with unsupported methods.</p>
|
||||
* an object with unsupported methods.
|
||||
* </p>
|
||||
*
|
||||
* <p>SPI implementations should be stateless or otherwise safe for concurrent
|
||||
* <p>
|
||||
* SPI implementations should be stateless or otherwise safe for concurrent
|
||||
* lookup and invocation. Created cryptographic contexts remain operation-local
|
||||
* and are not necessarily thread-safe.</p>
|
||||
* and are not necessarily thread-safe.
|
||||
* </p>
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
@@ -10,9 +10,11 @@ import java.util.Objects;
|
||||
/**
|
||||
* Redacted checked failure raised by encrypted keyring operations.
|
||||
*
|
||||
* <p>The public message contains only the stable error code. Filesystem paths,
|
||||
* <p>
|
||||
* The public message contains only the stable error code. Filesystem paths,
|
||||
* aliases, key material, ciphertext, and provider-controlled messages are
|
||||
* deliberately excluded.</p>
|
||||
* deliberately excluded.
|
||||
* </p>
|
||||
*/
|
||||
public final class KeyringException extends IOException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -21,17 +23,9 @@ public final class KeyringException extends IOException {
|
||||
* Stable keyring failure categories.
|
||||
*/
|
||||
public enum Code {
|
||||
KEYRING_ALREADY_OPEN,
|
||||
KEYRING_FILESYSTEM_UNSUPPORTED,
|
||||
KEYRING_FORMAT_INVALID,
|
||||
KEYRING_LIMIT_EXCEEDED,
|
||||
KEYRING_UNLOCK_FAILED,
|
||||
KEYRING_IO_FAILED,
|
||||
KEYRING_DURABILITY_UNCONFIRMED,
|
||||
KEYRING_CLOSED,
|
||||
KEYRING_NON_EXPORTABLE_KEY,
|
||||
KEYRING_IMPORT_MAPPING_INVALID,
|
||||
KEYRING_IMPORT_METADATA_INVALID,
|
||||
KEYRING_ALREADY_OPEN, KEYRING_FILESYSTEM_UNSUPPORTED, KEYRING_FORMAT_INVALID, KEYRING_LIMIT_EXCEEDED,
|
||||
KEYRING_UNLOCK_FAILED, KEYRING_IO_FAILED, KEYRING_DURABILITY_UNCONFIRMED, KEYRING_CLOSED,
|
||||
KEYRING_NON_EXPORTABLE_KEY, KEYRING_IMPORT_MAPPING_INVALID, KEYRING_IMPORT_METADATA_INVALID,
|
||||
KEYRING_KEY_NOT_CANONICALIZABLE
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,7 @@ interface KeyringFileOperations {
|
||||
|
||||
/** Atomic persistence destination. */
|
||||
enum Target {
|
||||
MAIN_IMAGE,
|
||||
NONCE_RESERVATION
|
||||
MAIN_IMAGE, NONCE_RESERVATION
|
||||
}
|
||||
|
||||
/** Creates one owner-only temporary file beside its destination. */
|
||||
@@ -57,10 +56,8 @@ final class NioKeyringFileOperations implements KeyringFileOperations {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTemporary(Target target, Path temporary, byte[] image)
|
||||
throws IOException {
|
||||
try (FileChannel channel = FileChannel.open(temporary,
|
||||
StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) {
|
||||
public void writeTemporary(Target target, Path temporary, byte[] image) throws IOException {
|
||||
try (FileChannel channel = FileChannel.open(temporary, StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(image);
|
||||
while (buffer.hasRemaining()) {
|
||||
channel.write(buffer);
|
||||
@@ -70,17 +67,14 @@ final class NioKeyringFileOperations implements KeyringFileOperations {
|
||||
|
||||
@Override
|
||||
public void forceTemporary(Target target, Path temporary) throws IOException {
|
||||
try (FileChannel channel = FileChannel.open(temporary,
|
||||
StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) {
|
||||
try (FileChannel channel = FileChannel.open(temporary, StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) {
|
||||
channel.force(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void atomicReplace(Target target, Path temporary, Path destination)
|
||||
throws IOException {
|
||||
Files.move(temporary, destination, StandardCopyOption.ATOMIC_MOVE,
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
public void atomicReplace(Target target, Path temporary, Path destination) throws IOException {
|
||||
Files.move(temporary, destination, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -71,18 +71,18 @@ import zeroecho.core.spi.SymmetricKeyImporter;
|
||||
* Closed trusted mapping from persistent key identities to canonical registered
|
||||
* import operations.
|
||||
*
|
||||
* <p>Provider identity is deliberately absent. Standard encoded key material
|
||||
* is reconstructed by the current runtime's canonical ZeroEcho importer. The
|
||||
* original JCA provider is neither persisted nor reproduced.</p>
|
||||
* <p>
|
||||
* Provider identity is deliberately absent. Standard encoded key material is
|
||||
* reconstructed by the current runtime's canonical ZeroEcho importer. The
|
||||
* original JCA provider is neither persisted nor reproduced.
|
||||
* </p>
|
||||
*/
|
||||
final class KeyringImportRegistry {
|
||||
private static final String ALGORITHM_AES = "AES";
|
||||
private static final String ALGORITHM_HMAC = "HMAC";
|
||||
private static final String ALGORITHM_CHACHA20 = "CHACHA20";
|
||||
private static final String ALGORITHM_CHACHA20_POLY1305 = "CHACHA20-POLY1305";
|
||||
private static final Map<Class<? extends AlgorithmKeySpec>,
|
||||
Function<byte[], ? extends AlgorithmKeySpec>> SPEC_FACTORIES =
|
||||
createSpecFactories();
|
||||
private static final Map<Class<? extends AlgorithmKeySpec>, Function<byte[], ? extends AlgorithmKeySpec>> SPEC_FACTORIES = createSpecFactories();
|
||||
private static final Map<Tuple, PersistentMapping> MAPPINGS = createMappings();
|
||||
|
||||
private KeyringImportRegistry() {
|
||||
@@ -93,10 +93,7 @@ final class KeyringImportRegistry {
|
||||
*/
|
||||
/* default */
|
||||
enum HmacVariant {
|
||||
NONE(0, null),
|
||||
SHA256(1, "HmacSHA256"),
|
||||
SHA384(2, "HmacSHA384"),
|
||||
SHA512(3, "HmacSHA512");
|
||||
NONE(0, null), SHA256(1, "HmacSHA256"), SHA384(2, "HmacSHA384"), SHA512(3, "HmacSHA512");
|
||||
|
||||
private final int code;
|
||||
private final String jcaName;
|
||||
@@ -123,8 +120,7 @@ final class KeyringImportRegistry {
|
||||
throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID);
|
||||
}
|
||||
|
||||
/* default */ static HmacVariant forStoredKey(String algorithmId, Key key)
|
||||
throws KeyringException {
|
||||
/* default */ static HmacVariant forStoredKey(String algorithmId, Key key) throws KeyringException {
|
||||
if (!ALGORITHM_HMAC.equals(algorithmId)) {
|
||||
return NONE;
|
||||
}
|
||||
@@ -142,21 +138,19 @@ final class KeyringImportRegistry {
|
||||
* Immutable description used by the finite importer-matrix test.
|
||||
*
|
||||
* @param algorithmId canonical ZeroEcho algorithm identifier
|
||||
* @param kind key kind
|
||||
* @param encoding standard encoding
|
||||
* @param kind key kind
|
||||
* @param encoding standard encoding
|
||||
* @param hmacVariant closed HMAC variant, or {@link HmacVariant#NONE}
|
||||
* @param specType exact registered importer specification type
|
||||
* @param specType exact registered importer specification type
|
||||
*/
|
||||
/* default */
|
||||
record PersistentMapping(String algorithmId, KeyringStore.Kind kind,
|
||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant,
|
||||
Class<? extends AlgorithmKeySpec> specType) {
|
||||
record PersistentMapping(String algorithmId, KeyringStore.Kind kind, KeyringStore.Encoding encoding,
|
||||
HmacVariant hmacVariant, Class<? extends AlgorithmKeySpec> specType) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.AvoidCatchingGenericException")
|
||||
/* default */ static Key importKey(String algorithmId, KeyringStore.Kind kind,
|
||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant, byte[] encoded)
|
||||
throws GeneralSecurityException, KeyringException {
|
||||
/* default */ static Key importKey(String algorithmId, KeyringStore.Kind kind, KeyringStore.Encoding encoding,
|
||||
HmacVariant hmacVariant, byte[] encoded) throws GeneralSecurityException, KeyringException {
|
||||
PersistentMapping mapping = requireMapping(algorithmId, kind, encoding, hmacVariant);
|
||||
CryptoAlgorithm algorithm = requireAlgorithm(mapping.algorithmId);
|
||||
AlgorithmKeySpec spec = createSpec(mapping, encoded);
|
||||
@@ -172,8 +166,7 @@ final class KeyringImportRegistry {
|
||||
}
|
||||
|
||||
/* default */ static void validateMapping(String algorithmId, KeyringStore.Kind kind,
|
||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant)
|
||||
throws KeyringException {
|
||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant) throws KeyringException {
|
||||
PersistentMapping mapping = requireMapping(algorithmId, kind, encoding, hmacVariant);
|
||||
CryptoAlgorithm algorithm = requireAlgorithm(mapping.algorithmId);
|
||||
List<KeyOperationInfo> operations = matchingOperations(algorithm, kind);
|
||||
@@ -184,27 +177,23 @@ final class KeyringImportRegistry {
|
||||
|
||||
@SuppressWarnings({ "PMD.PreserveStackTrace", "PMD.AvoidCatchingGenericException" })
|
||||
/* default */ static void validateCanonical(String algorithmId, KeyringStore.Kind kind,
|
||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant, Key source,
|
||||
byte[] encoded)
|
||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant, Key source, byte[] encoded)
|
||||
throws KeyringException {
|
||||
Key imported = null;
|
||||
byte[] canonical = null;
|
||||
try {
|
||||
if (!matchesSourceAlgorithm(source, algorithmId, hmacVariant)) {
|
||||
throw new KeyringException(
|
||||
KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
|
||||
throw new KeyringException(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
|
||||
}
|
||||
imported = importKey(algorithmId, kind, encoding, hmacVariant, encoded);
|
||||
canonical = imported.getEncoded();
|
||||
if (canonical == null || !matchesFormat(imported.getFormat(), encoding)
|
||||
|| !MessageDigest.isEqual(encoded, canonical)
|
||||
|| !matchesAlgorithm(imported, algorithmId, hmacVariant)) {
|
||||
throw new KeyringException(
|
||||
KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
|
||||
throw new KeyringException(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
|
||||
}
|
||||
} catch (GeneralSecurityException | RuntimeException exception) {
|
||||
throw new KeyringException(
|
||||
KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
|
||||
throw new KeyringException(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE);
|
||||
} finally {
|
||||
if (canonical != null) {
|
||||
Arrays.fill(canonical, (byte) 0);
|
||||
@@ -217,11 +206,9 @@ final class KeyringImportRegistry {
|
||||
return List.copyOf(MAPPINGS.values());
|
||||
}
|
||||
|
||||
private static PersistentMapping requireMapping(String algorithmId,
|
||||
KeyringStore.Kind kind, KeyringStore.Encoding encoding,
|
||||
HmacVariant hmacVariant) throws KeyringException {
|
||||
PersistentMapping mapping = MAPPINGS.get(
|
||||
new Tuple(algorithmId, kind, encoding, hmacVariant));
|
||||
private static PersistentMapping requireMapping(String algorithmId, KeyringStore.Kind kind,
|
||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant) throws KeyringException {
|
||||
PersistentMapping mapping = MAPPINGS.get(new Tuple(algorithmId, kind, encoding, hmacVariant));
|
||||
if (mapping == null) {
|
||||
throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_MAPPING_INVALID);
|
||||
}
|
||||
@@ -229,8 +216,7 @@ final class KeyringImportRegistry {
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.PreserveStackTrace")
|
||||
private static CryptoAlgorithm requireAlgorithm(String algorithmId)
|
||||
throws KeyringException {
|
||||
private static CryptoAlgorithm requireAlgorithm(String algorithmId) throws KeyringException {
|
||||
try {
|
||||
return CryptoAlgorithms.require(algorithmId);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
@@ -238,11 +224,8 @@ final class KeyringImportRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<KeyOperationInfo> matchingOperations(CryptoAlgorithm algorithm,
|
||||
KeyringStore.Kind kind) {
|
||||
return algorithm.keyOperations().stream()
|
||||
.filter(info -> info.operation() == operation(kind))
|
||||
.toList();
|
||||
private static List<KeyOperationInfo> matchingOperations(CryptoAlgorithm algorithm, KeyringStore.Kind kind) {
|
||||
return algorithm.keyOperations().stream().filter(info -> info.operation() == operation(kind)).toList();
|
||||
}
|
||||
|
||||
private static KeyOperation operation(KeyringStore.Kind kind) {
|
||||
@@ -254,25 +237,22 @@ final class KeyringImportRegistry {
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static Key invokeImporter(CryptoAlgorithm algorithm, KeyringStore.Kind kind,
|
||||
AlgorithmKeySpec spec) throws GeneralSecurityException {
|
||||
private static Key invokeImporter(CryptoAlgorithm algorithm, KeyringStore.Kind kind, AlgorithmKeySpec spec)
|
||||
throws GeneralSecurityException {
|
||||
return switch (kind) {
|
||||
case PUBLIC_KEY -> ((PublicKeyImporter) algorithm.publicKeyImporter(spec.getClass()))
|
||||
.importPublic(spec);
|
||||
case PRIVATE_KEY -> ((PrivateKeyImporter) algorithm.privateKeyImporter(spec.getClass()))
|
||||
.importPrivate(spec);
|
||||
case SECRET_KEY -> ((SymmetricKeyImporter) algorithm.symmetricKeyImporter(spec.getClass()))
|
||||
.importSecret(spec);
|
||||
case PUBLIC_KEY -> ((PublicKeyImporter) algorithm.publicKeyImporter(spec.getClass())).importPublic(spec);
|
||||
case PRIVATE_KEY ->
|
||||
((PrivateKeyImporter) algorithm.privateKeyImporter(spec.getClass())).importPrivate(spec);
|
||||
case SECRET_KEY ->
|
||||
((SymmetricKeyImporter) algorithm.symmetricKeyImporter(spec.getClass())).importSecret(spec);
|
||||
};
|
||||
}
|
||||
|
||||
private static AlgorithmKeySpec createSpec(PersistentMapping mapping, byte[] encoded)
|
||||
throws KeyringException {
|
||||
private static AlgorithmKeySpec createSpec(PersistentMapping mapping, byte[] encoded) throws KeyringException {
|
||||
if (mapping.specType == HmacKeyImportSpec.class) {
|
||||
return new HmacKeyImportSpec(mapping.hmacVariant.jcaName, encoded);
|
||||
}
|
||||
Function<byte[], ? extends AlgorithmKeySpec> factory =
|
||||
SPEC_FACTORIES.get(mapping.specType);
|
||||
Function<byte[], ? extends AlgorithmKeySpec> factory = SPEC_FACTORIES.get(mapping.specType);
|
||||
AlgorithmKeySpec result = factory == null ? null : factory.apply(encoded);
|
||||
if (result == null) {
|
||||
throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_MAPPING_INVALID);
|
||||
@@ -288,31 +268,27 @@ final class KeyringImportRegistry {
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean matchesAlgorithm(Key imported, String algorithmId,
|
||||
HmacVariant hmacVariant) {
|
||||
private static boolean matchesAlgorithm(Key imported, String algorithmId, HmacVariant hmacVariant) {
|
||||
if (ALGORITHM_HMAC.equals(algorithmId)) {
|
||||
return hmacVariant.jcaName.equals(imported.getAlgorithm());
|
||||
}
|
||||
if (ALGORITHM_AES.equals(algorithmId)) {
|
||||
return ALGORITHM_AES.equals(imported.getAlgorithm());
|
||||
}
|
||||
if (ALGORITHM_CHACHA20.equals(algorithmId)
|
||||
|| ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) {
|
||||
if (ALGORITHM_CHACHA20.equals(algorithmId) || ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) {
|
||||
return "ChaCha20".equals(imported.getAlgorithm());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean matchesSourceAlgorithm(Key source, String algorithmId,
|
||||
HmacVariant hmacVariant) {
|
||||
private static boolean matchesSourceAlgorithm(Key source, String algorithmId, HmacVariant hmacVariant) {
|
||||
if (ALGORITHM_HMAC.equals(algorithmId)) {
|
||||
return hmacVariant.jcaName.equals(source.getAlgorithm());
|
||||
}
|
||||
if (ALGORITHM_AES.equals(algorithmId)) {
|
||||
return ALGORITHM_AES.equals(source.getAlgorithm());
|
||||
}
|
||||
if (ALGORITHM_CHACHA20.equals(algorithmId)
|
||||
|| ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) {
|
||||
if (ALGORITHM_CHACHA20.equals(algorithmId) || ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) {
|
||||
return "ChaCha20".equals(source.getAlgorithm());
|
||||
}
|
||||
return true;
|
||||
@@ -329,10 +305,8 @@ final class KeyringImportRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<Class<? extends AlgorithmKeySpec>,
|
||||
Function<byte[], ? extends AlgorithmKeySpec>> createSpecFactories() {
|
||||
return Map.ofEntries(
|
||||
Map.entry(AesKeyImportSpec.class, AesKeyImportSpec::fromRaw),
|
||||
private static Map<Class<? extends AlgorithmKeySpec>, Function<byte[], ? extends AlgorithmKeySpec>> createSpecFactories() {
|
||||
return Map.ofEntries(Map.entry(AesKeyImportSpec.class, AesKeyImportSpec::fromRaw),
|
||||
Map.entry(ChaChaKeyImportSpec.class, ChaChaKeyImportSpec::fromRaw),
|
||||
Map.entry(BikePublicKeySpec.class, BikePublicKeySpec::new),
|
||||
Map.entry(BikePrivateKeySpec.class, BikePrivateKeySpec::new),
|
||||
@@ -380,73 +354,54 @@ final class KeyringImportRegistry {
|
||||
addAsymmetric(mappings, "CMCE", CmcePublicKeySpec.class, CmcePrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "DH", DhPublicKeySpec.class, DhPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "ECDSA", EcdsaPublicKeySpec.class, EcdsaPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "Ed25519", Ed25519PublicKeySpec.class,
|
||||
Ed25519PrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "Ed448", Ed448PublicKeySpec.class,
|
||||
Ed448PrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "ElGamal", ElgamalPublicKeySpec.class,
|
||||
ElgamalPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "Frodo", FrodoPublicKeySpec.class,
|
||||
FrodoPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "Ed25519", Ed25519PublicKeySpec.class, Ed25519PrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "Ed448", Ed448PublicKeySpec.class, Ed448PrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "ElGamal", ElgamalPublicKeySpec.class, ElgamalPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "Frodo", FrodoPublicKeySpec.class, FrodoPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "HQC", HqcPublicKeySpec.class, HqcPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "ML-KEM", KyberPublicKeySpec.class,
|
||||
KyberPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "ML-DSA", MldsaPublicKeySpec.class,
|
||||
MldsaPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "ML-KEM", KyberPublicKeySpec.class, KyberPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "ML-DSA", MldsaPublicKeySpec.class, MldsaPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "NTRU", NtruPublicKeySpec.class, NtruPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "NTRULPRime", NtrulPrimePublicKeySpec.class,
|
||||
NtrulPrimePrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "SNTRUPrime", SntruPrimePublicKeySpec.class,
|
||||
SntruPrimePrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "NTRULPRime", NtrulPrimePublicKeySpec.class, NtrulPrimePrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "SNTRUPrime", SntruPrimePublicKeySpec.class, SntruPrimePrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "RSA", RsaPublicKeySpec.class, RsaPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "SABER", SaberPublicKeySpec.class,
|
||||
SaberPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "SLH-DSA", SlhDsaPublicKeySpec.class,
|
||||
SlhDsaPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "SPHINCS+", SphincsPlusPublicKeySpec.class,
|
||||
SphincsPlusPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "SABER", SaberPublicKeySpec.class, SaberPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "SLH-DSA", SlhDsaPublicKeySpec.class, SlhDsaPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "SPHINCS+", SphincsPlusPublicKeySpec.class, SphincsPlusPrivateKeySpec.class);
|
||||
addAsymmetric(mappings, "Xdh", XdhPublicKeySpec.class, XdhPrivateKeySpec.class);
|
||||
add(mappings, ALGORITHM_AES, KeyringStore.Kind.SECRET_KEY,
|
||||
KeyringStore.Encoding.RAW,
|
||||
HmacVariant.NONE, AesKeyImportSpec.class);
|
||||
add(mappings, ALGORITHM_CHACHA20, KeyringStore.Kind.SECRET_KEY,
|
||||
KeyringStore.Encoding.RAW,
|
||||
add(mappings, ALGORITHM_AES, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.NONE,
|
||||
AesKeyImportSpec.class);
|
||||
add(mappings, ALGORITHM_CHACHA20, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.NONE,
|
||||
ChaChaKeyImportSpec.class);
|
||||
add(mappings, ALGORITHM_CHACHA20_POLY1305, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW,
|
||||
HmacVariant.NONE, ChaChaKeyImportSpec.class);
|
||||
add(mappings, ALGORITHM_CHACHA20_POLY1305, KeyringStore.Kind.SECRET_KEY,
|
||||
KeyringStore.Encoding.RAW, HmacVariant.NONE, ChaChaKeyImportSpec.class);
|
||||
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY,
|
||||
KeyringStore.Encoding.RAW,
|
||||
HmacVariant.SHA256, HmacKeyImportSpec.class);
|
||||
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY,
|
||||
KeyringStore.Encoding.RAW,
|
||||
HmacVariant.SHA384, HmacKeyImportSpec.class);
|
||||
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY,
|
||||
KeyringStore.Encoding.RAW,
|
||||
HmacVariant.SHA512, HmacKeyImportSpec.class);
|
||||
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.SHA256,
|
||||
HmacKeyImportSpec.class);
|
||||
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.SHA384,
|
||||
HmacKeyImportSpec.class);
|
||||
add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.SHA512,
|
||||
HmacKeyImportSpec.class);
|
||||
return Collections.unmodifiableMap(mappings);
|
||||
}
|
||||
|
||||
private static void addAsymmetric(Map<Tuple, PersistentMapping> mappings,
|
||||
String algorithmId, Class<? extends AlgorithmKeySpec> publicSpec,
|
||||
Class<? extends AlgorithmKeySpec> privateSpec) {
|
||||
add(mappings, algorithmId, KeyringStore.Kind.PUBLIC_KEY,
|
||||
KeyringStore.Encoding.X509, HmacVariant.NONE, publicSpec);
|
||||
add(mappings, algorithmId, KeyringStore.Kind.PRIVATE_KEY,
|
||||
KeyringStore.Encoding.PKCS8, HmacVariant.NONE, privateSpec);
|
||||
private static void addAsymmetric(Map<Tuple, PersistentMapping> mappings, String algorithmId,
|
||||
Class<? extends AlgorithmKeySpec> publicSpec, Class<? extends AlgorithmKeySpec> privateSpec) {
|
||||
add(mappings, algorithmId, KeyringStore.Kind.PUBLIC_KEY, KeyringStore.Encoding.X509, HmacVariant.NONE,
|
||||
publicSpec);
|
||||
add(mappings, algorithmId, KeyringStore.Kind.PRIVATE_KEY, KeyringStore.Encoding.PKCS8, HmacVariant.NONE,
|
||||
privateSpec);
|
||||
}
|
||||
|
||||
private static void add(Map<Tuple, PersistentMapping> mappings, String algorithmId,
|
||||
KeyringStore.Kind kind, KeyringStore.Encoding encoding,
|
||||
HmacVariant hmacVariant, Class<? extends AlgorithmKeySpec> specType) {
|
||||
private static void add(Map<Tuple, PersistentMapping> mappings, String algorithmId, KeyringStore.Kind kind,
|
||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant, Class<? extends AlgorithmKeySpec> specType) {
|
||||
Tuple tuple = new Tuple(algorithmId, kind, encoding, hmacVariant);
|
||||
PersistentMapping mapping = new PersistentMapping(algorithmId, kind,
|
||||
encoding, hmacVariant, specType);
|
||||
PersistentMapping mapping = new PersistentMapping(algorithmId, kind, encoding, hmacVariant, specType);
|
||||
if (mappings.put(tuple, mapping) != null) {
|
||||
throw new IllegalStateException("Duplicate persistent key importer tuple");
|
||||
}
|
||||
}
|
||||
|
||||
private record Tuple(String algorithmId, KeyringStore.Kind kind,
|
||||
KeyringStore.Encoding encoding, HmacVariant hmacVariant) {
|
||||
private record Tuple(String algorithmId, KeyringStore.Kind kind, KeyringStore.Encoding encoding,
|
||||
HmacVariant hmacVariant) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,7 @@ final class KeyringNonceReservationKdf {
|
||||
private static final int STORE_ID_BYTES = 16;
|
||||
private static final int OUTPUT_BYTES = 32;
|
||||
private static final String HMAC_SHA256 = "HmacSHA256";
|
||||
private static final String DOMAIN_LABEL =
|
||||
"zeroecho:keyring:nonce-reservation-mac:v1";
|
||||
private static final String DOMAIN_LABEL = "zeroecho:keyring:nonce-reservation-mac:v1";
|
||||
|
||||
private KeyringNonceReservationKdf() {
|
||||
}
|
||||
@@ -29,14 +28,13 @@ final class KeyringNonceReservationKdf {
|
||||
* Derives the store-specific nonce-reservation MAC key.
|
||||
*
|
||||
* @param masterKey borrowed 256-bit store master key
|
||||
* @param storeId borrowed canonical 128-bit binary store UUID
|
||||
* @param storeId borrowed canonical 128-bit binary store UUID
|
||||
* @return newly owned 256-bit derived key
|
||||
* @throws GeneralSecurityException if HMAC-SHA-256 is unavailable
|
||||
*/
|
||||
/* default */ static byte[] derive(byte[] masterKey, byte[] storeId)
|
||||
throws GeneralSecurityException {
|
||||
if (masterKey == null || masterKey.length != MASTER_KEY_BYTES
|
||||
|| storeId == null || storeId.length != STORE_ID_BYTES) {
|
||||
/* default */ static byte[] derive(byte[] masterKey, byte[] storeId) throws GeneralSecurityException {
|
||||
if (masterKey == null || masterKey.length != MASTER_KEY_BYTES || storeId == null
|
||||
|| storeId.length != STORE_ID_BYTES) {
|
||||
throw new IllegalArgumentException("Invalid keyring derivation input");
|
||||
}
|
||||
byte[] salt = storeId.clone();
|
||||
@@ -61,8 +59,7 @@ final class KeyringNonceReservationKdf {
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] hmac(byte[] key, byte[] input)
|
||||
throws GeneralSecurityException {
|
||||
private static byte[] hmac(byte[] key, byte[] input) throws GeneralSecurityException {
|
||||
Mac mac = Mac.getInstance(HMAC_SHA256);
|
||||
mac.init(new SecretKeySpec(key, HMAC_SHA256));
|
||||
return mac.doFinal(input);
|
||||
|
||||
@@ -14,14 +14,18 @@ import javax.security.auth.Destroyable;
|
||||
/**
|
||||
* Destroyable owner of a keyring password.
|
||||
*
|
||||
* <p>The constructor and {@link #copy()} use defensive copies. Callers retain
|
||||
* <p>
|
||||
* The constructor and {@link #copy()} use defensive copies. Callers retain
|
||||
* ownership of the array supplied to the constructor and must clear it. The
|
||||
* returned copy belongs to the receiver and must be cleared immediately after
|
||||
* key derivation. This object never creates an immutable password
|
||||
* {@link String}.</p>
|
||||
* {@link String}.
|
||||
* </p>
|
||||
*
|
||||
* <p>Instances are thread-safe. Destruction is idempotent and makes subsequent
|
||||
* access fail deterministically.</p>
|
||||
* <p>
|
||||
* Instances are thread-safe. Destruction is idempotent and makes subsequent
|
||||
* access fail deterministically.
|
||||
* </p>
|
||||
*/
|
||||
public final class KeyringPassword implements Destroyable, AutoCloseable {
|
||||
private final ReentrantLock lifecycleLock = new ReentrantLock();
|
||||
@@ -32,7 +36,7 @@ public final class KeyringPassword implements Destroyable, AutoCloseable {
|
||||
* Creates a password owner.
|
||||
*
|
||||
* @param password password characters, which are defensively copied
|
||||
* @throws NullPointerException if {@code password} is {@code null}
|
||||
* @throws NullPointerException if {@code password} is {@code null}
|
||||
* @throws IllegalArgumentException if {@code password} is empty
|
||||
*/
|
||||
@SuppressWarnings("PMD.UseVarargs")
|
||||
|
||||
@@ -8,7 +8,8 @@ package zeroecho.core.storage;
|
||||
* Operational limits applied while opening an encrypted software keyring.
|
||||
*
|
||||
* @param operationalIterationMaximum maximum accepted PBKDF2 iteration count;
|
||||
* it may restrict but never exceed the absolute decoded maximum
|
||||
* it may restrict but never exceed the
|
||||
* absolute decoded maximum
|
||||
*/
|
||||
public record KeyringProtection(int operationalIterationMaximum) {
|
||||
/** Iterations used when a new keyring is created. */
|
||||
@@ -21,8 +22,8 @@ public record KeyringProtection(int operationalIterationMaximum) {
|
||||
/**
|
||||
* Validates the operational limit.
|
||||
*
|
||||
* @throws IllegalArgumentException if the limit is below the creation
|
||||
* setting or above the absolute operational maximum
|
||||
* @throws IllegalArgumentException if the limit is below the creation setting
|
||||
* or above the absolute operational maximum
|
||||
*/
|
||||
public KeyringProtection {
|
||||
if (operationalIterationMaximum < CREATION_ITERATIONS
|
||||
|
||||
@@ -7,8 +7,10 @@ package zeroecho.core.storage;
|
||||
/**
|
||||
* Fills keyring randomness buffers.
|
||||
*
|
||||
* <p>This package-private seam supports deterministic format tests; production
|
||||
* creation uses the authoritative shared secure random source.</p>
|
||||
* <p>
|
||||
* This package-private seam supports deterministic format tests; production
|
||||
* creation uses the authoritative shared secure random source.
|
||||
* </p>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface KeyringRandomBytes {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,15 +53,15 @@
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Unlock passwords are destroyable, transfer ownership to the receiver, and
|
||||
* are destroyed immediately after the master key is unwrapped. The unlocked
|
||||
* store retains the master key, its domain-separated nonce-reservation MAC
|
||||
* key, and encrypted entry records; closing the store clears this material.
|
||||
* The store requires a POSIX filesystem on which owner-only permissions can be
|
||||
* verified. A directory-force failure after atomic replacement makes the open
|
||||
* instance unusable until close and authenticated reopen resolves which
|
||||
* complete image is current. Non-exportable keys must remain behind an
|
||||
* external provider reference.
|
||||
* Unlock passwords are destroyable, transfer ownership to the receiver, and are
|
||||
* destroyed immediately after the master key is unwrapped. The unlocked store
|
||||
* retains the master key, its domain-separated nonce-reservation MAC key, and
|
||||
* encrypted entry records; closing the store clears this material. The store
|
||||
* requires a POSIX filesystem on which owner-only permissions can be verified.
|
||||
* A directory-force failure after atomic replacement makes the open instance
|
||||
* unusable until close and authenticated reopen resolves which complete image
|
||||
* is current. Non-exportable keys must remain behind an external provider
|
||||
* reference.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
|
||||
@@ -124,8 +124,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
|
||||
public static TagEngineBuilder<byte[]> digest(final ZeroEchoSession session, final DigestSpec spec) {
|
||||
Objects.requireNonNull(session, "session");
|
||||
final DigestSpec s = spec == null ? DigestSpec.sha256() : spec;
|
||||
return new TagEngineBuilder<>(
|
||||
() -> session.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE, s));
|
||||
return new TagEngineBuilder<>(() -> session.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE, s));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,8 +192,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
|
||||
* @return a builder that produces Ed25519 signature engines in SIGN mode
|
||||
* @throws NullPointerException if {@code privateKey} is {@code null}
|
||||
*/
|
||||
public static TagEngineBuilder<Signature> ed25519Sign(final ZeroEchoSession session,
|
||||
final PrivateKey privateKey) {
|
||||
public static TagEngineBuilder<Signature> ed25519Sign(final ZeroEchoSession session, final PrivateKey privateKey) {
|
||||
Objects.requireNonNull(privateKey, PRIVATE_KEY);
|
||||
return signature(session, "Ed25519", privateKey, VoidSpec.INSTANCE);
|
||||
}
|
||||
@@ -206,8 +204,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
|
||||
* @return a builder that produces Ed25519 signature engines in VERIFY mode
|
||||
* @throws NullPointerException if {@code publicKey} is {@code null}
|
||||
*/
|
||||
public static TagEngineBuilder<Signature> ed25519Verify(final ZeroEchoSession session,
|
||||
final PublicKey publicKey) {
|
||||
public static TagEngineBuilder<Signature> ed25519Verify(final ZeroEchoSession session, final PublicKey publicKey) {
|
||||
Objects.requireNonNull(publicKey, PUBLIC_KEY);
|
||||
return signature(session, "Ed25519", publicKey, VoidSpec.INSTANCE);
|
||||
}
|
||||
@@ -229,8 +226,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
|
||||
public static TagEngineBuilder<Signature> rsaSign(final ZeroEchoSession session, final PrivateKey privateKey,
|
||||
final RsaSigSpec spec) {
|
||||
Objects.requireNonNull(privateKey, PRIVATE_KEY);
|
||||
return signature(session, "RSA", privateKey,
|
||||
spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec);
|
||||
return signature(session, "RSA", privateKey, spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,8 +246,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
|
||||
public static TagEngineBuilder<Signature> rsaVerify(final ZeroEchoSession session, final PublicKey publicKey,
|
||||
final RsaSigSpec spec) {
|
||||
Objects.requireNonNull(publicKey, PUBLIC_KEY);
|
||||
return signature(session, "RSA", publicKey,
|
||||
spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec);
|
||||
return signature(session, "RSA", publicKey, spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,8 +364,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
|
||||
* @return a builder that produces SLH-DSA signature engines in SIGN mode
|
||||
* @throws NullPointerException if {@code privateKey} is {@code null}
|
||||
*/
|
||||
public static TagEngineBuilder<Signature> slhDsaSign(final ZeroEchoSession session,
|
||||
final PrivateKey privateKey) {
|
||||
public static TagEngineBuilder<Signature> slhDsaSign(final ZeroEchoSession session, final PrivateKey privateKey) {
|
||||
Objects.requireNonNull(privateKey, PRIVATE_KEY);
|
||||
return signature(session, "SLH-DSA", privateKey, VoidSpec.INSTANCE);
|
||||
}
|
||||
@@ -388,8 +382,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
|
||||
* @return a builder that produces SLH-DSA signature engines in VERIFY mode
|
||||
* @throws NullPointerException if {@code publicKey} is {@code null}
|
||||
*/
|
||||
public static TagEngineBuilder<Signature> slhDsaVerify(final ZeroEchoSession session,
|
||||
final PublicKey publicKey) {
|
||||
public static TagEngineBuilder<Signature> slhDsaVerify(final ZeroEchoSession session, final PublicKey publicKey) {
|
||||
Objects.requireNonNull(publicKey, PUBLIC_KEY);
|
||||
return signature(session, "SLH-DSA", publicKey, VoidSpec.INSTANCE);
|
||||
}
|
||||
@@ -408,8 +401,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
|
||||
* @return a builder that produces ML-DSA signature engines in SIGN mode
|
||||
* @throws NullPointerException if {@code privateKey} is {@code null}
|
||||
*/
|
||||
public static TagEngineBuilder<Signature> mldsaSign(final ZeroEchoSession session,
|
||||
final PrivateKey privateKey) {
|
||||
public static TagEngineBuilder<Signature> mldsaSign(final ZeroEchoSession session, final PrivateKey privateKey) {
|
||||
Objects.requireNonNull(privateKey, PRIVATE_KEY);
|
||||
return signature(session, "ML-DSA", privateKey, VoidSpec.INSTANCE);
|
||||
}
|
||||
@@ -428,8 +420,7 @@ public final class TagEngineBuilder<T> implements Supplier<TagEngine<T>> {
|
||||
* @return a builder that produces ML-DSA signature engines in VERIFY mode
|
||||
* @throws NullPointerException if {@code publicKey} is {@code null}
|
||||
*/
|
||||
public static TagEngineBuilder<Signature> mldsaVerify(final ZeroEchoSession session,
|
||||
final PublicKey publicKey) {
|
||||
public static TagEngineBuilder<Signature> mldsaVerify(final ZeroEchoSession session, final PublicKey publicKey) {
|
||||
Objects.requireNonNull(publicKey, PUBLIC_KEY);
|
||||
return signature(session, "ML-DSA", publicKey, VoidSpec.INSTANCE);
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@ import zeroecho.core.spi.SymmetricKeyImporter;
|
||||
/**
|
||||
* Session-bound entry point for exact key-material operations.
|
||||
*
|
||||
* <p>Capability lookup fails before an operation object is returned. Returned
|
||||
* <p>
|
||||
* Capability lookup fails before an operation object is returned. Returned
|
||||
* objects guarantee the requested operation and report successful execution to
|
||||
* the owning session's audit listener on a best-effort basis.</p>
|
||||
* the owning session's audit listener on a best-effort basis.
|
||||
* </p>
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
@@ -69,13 +71,12 @@ public final class KeyBuilders {
|
||||
* Resolves an exact symmetric generator.
|
||||
*
|
||||
* @param algorithmId canonical algorithm identifier
|
||||
* @param specType exact specification class
|
||||
* @param <S> specification type
|
||||
* @param specType exact specification class
|
||||
* @param <S> specification type
|
||||
* @return guaranteed generator
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
*/
|
||||
public <S extends AlgorithmKeySpec> SymmetricKeyGenerator<S> generator(String algorithmId,
|
||||
Class<S> specType) {
|
||||
public <S extends AlgorithmKeySpec> SymmetricKeyGenerator<S> generator(String algorithmId, Class<S> specType) {
|
||||
CryptoAlgorithm algorithm = session.require(algorithmId);
|
||||
SymmetricKeyGenerator<S> delegate = algorithm.symmetricKeyGenerator(specType);
|
||||
return spec -> {
|
||||
@@ -89,13 +90,12 @@ public final class KeyBuilders {
|
||||
* Resolves an exact symmetric importer.
|
||||
*
|
||||
* @param algorithmId canonical algorithm identifier
|
||||
* @param specType exact specification class
|
||||
* @param <S> specification type
|
||||
* @param specType exact specification class
|
||||
* @param <S> specification type
|
||||
* @return guaranteed importer
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
*/
|
||||
public <S extends AlgorithmKeySpec> SymmetricKeyImporter<S> importer(String algorithmId,
|
||||
Class<S> specType) {
|
||||
public <S extends AlgorithmKeySpec> SymmetricKeyImporter<S> importer(String algorithmId, Class<S> specType) {
|
||||
CryptoAlgorithm algorithm = session.require(algorithmId);
|
||||
SymmetricKeyImporter<S> delegate = algorithm.symmetricKeyImporter(specType);
|
||||
return spec -> {
|
||||
@@ -109,12 +109,13 @@ public final class KeyBuilders {
|
||||
* Generates a symmetric key using the exact runtime specification type.
|
||||
*
|
||||
* @param algorithmId canonical algorithm identifier
|
||||
* @param spec generation specification
|
||||
* @param <S> specification type
|
||||
* @param spec generation specification
|
||||
* @param <S> specification type
|
||||
* @return generated secret key
|
||||
* @throws java.security.GeneralSecurityException if generation fails
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
* @throws NullPointerException if {@code spec} is {@code null}
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
* @throws NullPointerException if {@code spec} is
|
||||
* {@code null}
|
||||
*/
|
||||
public <S extends AlgorithmKeySpec> SecretKey generate(String algorithmId, S spec)
|
||||
throws java.security.GeneralSecurityException {
|
||||
@@ -128,12 +129,13 @@ public final class KeyBuilders {
|
||||
* Imports a symmetric key using the exact runtime specification type.
|
||||
*
|
||||
* @param algorithmId canonical algorithm identifier
|
||||
* @param spec import specification
|
||||
* @param <S> specification type
|
||||
* @param spec import specification
|
||||
* @param <S> specification type
|
||||
* @return imported secret key
|
||||
* @throws java.security.GeneralSecurityException if import fails
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
* @throws NullPointerException if {@code spec} is {@code null}
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
* @throws NullPointerException if {@code spec} is
|
||||
* {@code null}
|
||||
*/
|
||||
public <S extends AlgorithmKeySpec> SecretKey importKey(String algorithmId, S spec)
|
||||
throws java.security.GeneralSecurityException {
|
||||
@@ -155,8 +157,8 @@ public final class KeyBuilders {
|
||||
* Resolves an exact key-pair generator.
|
||||
*
|
||||
* @param algorithmId canonical algorithm identifier
|
||||
* @param specType exact specification class
|
||||
* @param <S> specification type
|
||||
* @param specType exact specification class
|
||||
* @param <S> specification type
|
||||
* @return guaranteed generator
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
*/
|
||||
@@ -175,13 +177,12 @@ public final class KeyBuilders {
|
||||
* Resolves an exact public-key importer.
|
||||
*
|
||||
* @param algorithmId canonical algorithm identifier
|
||||
* @param specType exact specification class
|
||||
* @param <S> specification type
|
||||
* @param specType exact specification class
|
||||
* @param <S> specification type
|
||||
* @return guaranteed importer
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
*/
|
||||
public <S extends AlgorithmKeySpec> PublicKeyImporter<S> publicImporter(String algorithmId,
|
||||
Class<S> specType) {
|
||||
public <S extends AlgorithmKeySpec> PublicKeyImporter<S> publicImporter(String algorithmId, Class<S> specType) {
|
||||
CryptoAlgorithm algorithm = session.require(algorithmId);
|
||||
PublicKeyImporter<S> delegate = algorithm.publicKeyImporter(specType);
|
||||
return spec -> {
|
||||
@@ -195,8 +196,8 @@ public final class KeyBuilders {
|
||||
* Resolves an exact private-key importer.
|
||||
*
|
||||
* @param algorithmId canonical algorithm identifier
|
||||
* @param specType exact specification class
|
||||
* @param <S> specification type
|
||||
* @param specType exact specification class
|
||||
* @param <S> specification type
|
||||
* @return guaranteed importer
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
*/
|
||||
@@ -215,12 +216,13 @@ public final class KeyBuilders {
|
||||
* Generates a key pair using the exact runtime specification type.
|
||||
*
|
||||
* @param algorithmId canonical algorithm identifier
|
||||
* @param spec generation specification
|
||||
* @param <S> specification type
|
||||
* @param spec generation specification
|
||||
* @param <S> specification type
|
||||
* @return generated key pair
|
||||
* @throws java.security.GeneralSecurityException if generation fails
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
* @throws NullPointerException if {@code spec} is {@code null}
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
* @throws NullPointerException if {@code spec} is
|
||||
* {@code null}
|
||||
*/
|
||||
public <S extends AlgorithmKeySpec> KeyPair generateKeyPair(String algorithmId, S spec)
|
||||
throws java.security.GeneralSecurityException {
|
||||
@@ -234,12 +236,13 @@ public final class KeyBuilders {
|
||||
* Imports a public key using the exact runtime specification type.
|
||||
*
|
||||
* @param algorithmId canonical algorithm identifier
|
||||
* @param spec public-key import specification
|
||||
* @param <S> specification type
|
||||
* @param spec public-key import specification
|
||||
* @param <S> specification type
|
||||
* @return imported public key
|
||||
* @throws java.security.GeneralSecurityException if import fails
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
* @throws NullPointerException if {@code spec} is {@code null}
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
* @throws NullPointerException if {@code spec} is
|
||||
* {@code null}
|
||||
*/
|
||||
public <S extends AlgorithmKeySpec> PublicKey importPublic(String algorithmId, S spec)
|
||||
throws java.security.GeneralSecurityException {
|
||||
@@ -253,12 +256,13 @@ public final class KeyBuilders {
|
||||
* Imports a private key using the exact runtime specification type.
|
||||
*
|
||||
* @param algorithmId canonical algorithm identifier
|
||||
* @param spec private-key import specification
|
||||
* @param <S> specification type
|
||||
* @param spec private-key import specification
|
||||
* @param <S> specification type
|
||||
* @return imported private key
|
||||
* @throws java.security.GeneralSecurityException if import fails
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
* @throws NullPointerException if {@code spec} is {@code null}
|
||||
* @throws IllegalArgumentException if the capability is absent
|
||||
* @throws NullPointerException if {@code spec} is
|
||||
* {@code null}
|
||||
*/
|
||||
public <S extends AlgorithmKeySpec> PrivateKey importPrivate(String algorithmId, S spec)
|
||||
throws java.security.GeneralSecurityException {
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
package zeroecho.sdk;
|
||||
|
||||
/**
|
||||
* Explicit PBKDF2 work-factor limits for trusted configuration and decoded data.
|
||||
* Explicit PBKDF2 work-factor limits for trusted configuration and decoded
|
||||
* data.
|
||||
*
|
||||
* @param operationalMaximum largest iteration count accepted from trusted local
|
||||
* configuration
|
||||
* @param operationalMaximum largest iteration count accepted from trusted
|
||||
* local configuration
|
||||
* @param absoluteDecodedMaximum hard safety ceiling for untrusted decoded data
|
||||
* @since 1.0
|
||||
*/
|
||||
@@ -41,8 +42,8 @@ public record Pbkdf2Limits(int operationalMaximum, int absoluteDecodedMaximum) {
|
||||
*/
|
||||
public void validateTrusted(int iterations) {
|
||||
if (iterations < MINIMUM || iterations > operationalMaximum) {
|
||||
throw new IllegalArgumentException("PBKDF2 iterations must be in range " + MINIMUM + ".."
|
||||
+ operationalMaximum + ": " + iterations);
|
||||
throw new IllegalArgumentException(
|
||||
"PBKDF2 iterations must be in range " + MINIMUM + ".." + operationalMaximum + ": " + iterations);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,8 +108,8 @@ public final class ZeroEchoSession {
|
||||
this(CryptoPolicy.permissive(), AuditListener.noop(), AuditMode.OFF, null);
|
||||
}
|
||||
|
||||
private ZeroEchoSession(CryptoPolicy<ContextSpec, Key> policy, AuditListener auditListener,
|
||||
AuditMode auditMode, Pbkdf2Limits pbkdf2Limits) {
|
||||
private ZeroEchoSession(CryptoPolicy<ContextSpec, Key> policy, AuditListener auditListener, AuditMode auditMode,
|
||||
Pbkdf2Limits pbkdf2Limits) {
|
||||
this.policy = Objects.requireNonNull(policy, "policy must not be null");
|
||||
this.auditListener = Objects.requireNonNull(auditListener, "auditListener must not be null");
|
||||
this.auditSink = AuditListeners.bestEffort(auditListener);
|
||||
@@ -143,8 +143,7 @@ public final class ZeroEchoSession {
|
||||
*/
|
||||
public ZeroEchoSession withAuditListener(AuditListener newAuditListener) {
|
||||
return new ZeroEchoSession(policy,
|
||||
Objects.requireNonNull(newAuditListener, "newAuditListener must not be null"), auditMode,
|
||||
pbkdf2Limits);
|
||||
Objects.requireNonNull(newAuditListener, "newAuditListener must not be null"), auditMode, pbkdf2Limits);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,8 +197,8 @@ public final class ZeroEchoSession {
|
||||
* Returns the audit listener owned by this session.
|
||||
*
|
||||
* <p>
|
||||
* The returned listener is the configured strategy, not mutable session
|
||||
* state. It is exposed to support manual audit mode.
|
||||
* The returned listener is the configured strategy, not mutable session state.
|
||||
* It is exposed to support manual audit mode.
|
||||
* </p>
|
||||
*
|
||||
* @return the non-null audit listener
|
||||
@@ -254,8 +253,8 @@ public final class ZeroEchoSession {
|
||||
* @param id canonical algorithm identifier
|
||||
* @param role intended key usage
|
||||
* @param key key compatible with the selected algorithm and role
|
||||
* @param spec optional context specification, or {@code null} for the
|
||||
* algorithm default
|
||||
* @param spec optional context specification, or {@code null} for the algorithm
|
||||
* default
|
||||
* @param <C> context type
|
||||
* @param <K> key type
|
||||
* @param <S> context specification type
|
||||
@@ -275,8 +274,8 @@ public final class ZeroEchoSession {
|
||||
return finishContext(algorithm, context, role, spec);
|
||||
}
|
||||
|
||||
private <C extends CryptoContext, S extends ContextSpec> C finishContext(CryptoAlgorithm algorithm,
|
||||
C context, KeyUsage role, S spec) {
|
||||
private <C extends CryptoContext, S extends ContextSpec> C finishContext(CryptoAlgorithm algorithm, C context,
|
||||
KeyUsage role, S spec) {
|
||||
if (auditMode == AuditMode.OFF) {
|
||||
notifyContextCreated(algorithm, role, spec);
|
||||
return context;
|
||||
@@ -321,14 +320,15 @@ public final class ZeroEchoSession {
|
||||
* Destroys a key and verifies that it entered the destroyed state.
|
||||
*
|
||||
* @param algorithmId algorithm identifier used as audit metadata
|
||||
* @param provider provider name used as audit metadata
|
||||
* @param key key to destroy; must not be {@code null}
|
||||
* @param provider provider name used as audit metadata
|
||||
* @param key key to destroy; must not be {@code null}
|
||||
* @return {@code true} only when this call transitions the key to destroyed;
|
||||
* {@code false} for a non-destroyable or already destroyed key
|
||||
* @throws NullPointerException if {@code key} is {@code null}
|
||||
* @throws NullPointerException if {@code key} is {@code null}
|
||||
* @throws DestroyFailedException if destruction fails or the key does not
|
||||
* report itself destroyed afterward
|
||||
* @throws RuntimeException if the key's lifecycle implementation throws one
|
||||
* @throws RuntimeException if the key's lifecycle implementation throws
|
||||
* one
|
||||
*/
|
||||
public boolean destroyKey(String algorithmId, String provider, Key key) throws DestroyFailedException {
|
||||
Objects.requireNonNull(key, "key must not be null");
|
||||
@@ -352,12 +352,10 @@ public final class ZeroEchoSession {
|
||||
return true;
|
||||
}
|
||||
|
||||
private <S extends ContextSpec> void notifyContextCreated(CryptoAlgorithm algorithm,
|
||||
KeyUsage role, S spec) {
|
||||
Map<String, Object> metadata = spec == null ? Map.of()
|
||||
: Map.of("specType", spec.getClass().getName());
|
||||
auditSink.onContextCreatedMeta(UUID.randomUUID().toString(), algorithm.id(), algorithm.providerName(),
|
||||
role, "n/a", metadata);
|
||||
private <S extends ContextSpec> void notifyContextCreated(CryptoAlgorithm algorithm, KeyUsage role, S spec) {
|
||||
Map<String, Object> metadata = spec == null ? Map.of() : Map.of("specType", spec.getClass().getName());
|
||||
auditSink.onContextCreatedMeta(UUID.randomUUID().toString(), algorithm.id(), algorithm.providerName(), role,
|
||||
"n/a", metadata);
|
||||
}
|
||||
|
||||
/* default */ void notifyKeyPairGenerated(CryptoAlgorithm algorithm, AlgorithmKeySpec spec, KeyPair keyPair) {
|
||||
|
||||
@@ -342,10 +342,8 @@ public final class HybridKexBuilder {
|
||||
if (pqcAlgId == null) {
|
||||
throw new IllegalStateException("pqc algorithm id must be set");
|
||||
}
|
||||
if (classicMode == ClassicMode.CLASSIC_AGREEMENT
|
||||
&& (classicPrivate == null || classicPeerPublic == null)) {
|
||||
throw new IllegalStateException(
|
||||
"classic private key and peer public must be set for CLASSIC_AGREEMENT");
|
||||
if (classicMode == ClassicMode.CLASSIC_AGREEMENT && (classicPrivate == null || classicPeerPublic == null)) {
|
||||
throw new IllegalStateException("classic private key and peer public must be set for CLASSIC_AGREEMENT");
|
||||
}
|
||||
if (classicMode == ClassicMode.PAIR_MESSAGE && classicKeyPair == null) {
|
||||
throw new IllegalStateException("classic key pair must be set for PAIR_MESSAGE");
|
||||
|
||||
@@ -77,12 +77,13 @@ import zeroecho.sdk.hybrid.signature.HybridSignatureProfile;
|
||||
* <li>{@link #single(ZeroEchoSession)}: constructs a non-hybrid
|
||||
* {@code SignatureContext}.</li>
|
||||
* <li>{@link #hybrid(ZeroEchoSession)}: constructs a hybrid
|
||||
* {@code SignatureContext} via
|
||||
* {@link HybridSignatureContexts}.</li>
|
||||
* {@code SignatureContext} via {@link HybridSignatureContexts}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Context construction is in-memory. Checked I/O failures arise only when a
|
||||
* built stream is attached or processed.</p>
|
||||
* <p>
|
||||
* Context construction is in-memory. Checked I/O failures arise only when a
|
||||
* built stream is attached or processed.
|
||||
* </p>
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
@@ -253,8 +254,8 @@ public final class SignatureTrailerDataContentBuilder implements DataContentBuil
|
||||
Objects.requireNonNull(algorithmId, "algorithmId");
|
||||
Objects.requireNonNull(privateKey, "privateKey");
|
||||
|
||||
Supplier<TagEngine<Signature>> factory = () -> session.createContext(algorithmId, KeyUsage.SIGN,
|
||||
privateKey, spec);
|
||||
Supplier<TagEngine<Signature>> factory = () -> session.createContext(algorithmId, KeyUsage.SIGN, privateKey,
|
||||
spec);
|
||||
|
||||
return core(factory);
|
||||
}
|
||||
@@ -293,8 +294,8 @@ public final class SignatureTrailerDataContentBuilder implements DataContentBuil
|
||||
Objects.requireNonNull(algorithmId, "algorithmId");
|
||||
Objects.requireNonNull(publicKey, "publicKey");
|
||||
|
||||
Supplier<TagEngine<Signature>> factory = () -> session.createContext(algorithmId,
|
||||
KeyUsage.VERIFY, publicKey, spec);
|
||||
Supplier<TagEngine<Signature>> factory = () -> session.createContext(algorithmId, KeyUsage.VERIFY,
|
||||
publicKey, spec);
|
||||
|
||||
return core(factory);
|
||||
}
|
||||
|
||||
@@ -567,8 +567,7 @@ public final class ChaChaDataContentBuilder implements DataContentBuilder<DataCo
|
||||
* <p>
|
||||
* The actual cipher work is delegated to an
|
||||
* {@link zeroecho.core.context.EncryptionContext} created through
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}.
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}.
|
||||
* If the created context implements {@code ContextAware}, the configured
|
||||
* context is injected before the stream is attached.
|
||||
* </p>
|
||||
@@ -638,8 +637,7 @@ public final class ChaChaDataContentBuilder implements DataContentBuilder<DataCo
|
||||
* <p>
|
||||
* The actual cipher work is delegated to an
|
||||
* {@link zeroecho.core.context.EncryptionContext} created through
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}.
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}.
|
||||
* If the created context implements {@code ContextAware}, the configured
|
||||
* context is injected before the stream is attached.
|
||||
* </p>
|
||||
|
||||
@@ -113,6 +113,7 @@ import zeroecho.sdk.content.api.PlainContent;
|
||||
*/
|
||||
public final class DigestDataContentBuilder implements DataContentBuilder<PlainContent> {
|
||||
private final ZeroEchoSession session;
|
||||
|
||||
/**
|
||||
* OutputMode selects how the digest-computing pipeline presents its result to
|
||||
* callers.
|
||||
|
||||
@@ -361,8 +361,7 @@ public final class ElgamalEncDataContentBuilder implements DataContentBuilder<Da
|
||||
* {@link ElgamalEncSpec}. When {@link #getStream()} is called, it creates an
|
||||
* {@link zeroecho.core.context.EncryptionContext} for the "ElGamal" algorithm
|
||||
* in {@link zeroecho.core.KeyUsage#ENCRYPT} role via
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
||||
* attaches the upstream stream, and returns a pull-based stream that encrypts
|
||||
* on the fly.
|
||||
* </p>
|
||||
@@ -426,8 +425,7 @@ public final class ElgamalEncDataContentBuilder implements DataContentBuilder<Da
|
||||
* {@link ElgamalEncSpec}. When {@link #getStream()} is called, it creates an
|
||||
* {@link zeroecho.core.context.EncryptionContext} for the "ElGamal" algorithm
|
||||
* in {@link zeroecho.core.KeyUsage#DECRYPT} role via
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
||||
* attaches the upstream stream, and returns a pull-based stream that decrypts
|
||||
* on the fly.
|
||||
* </p>
|
||||
|
||||
@@ -91,6 +91,7 @@ import zeroecho.sdk.content.api.PlainContent;
|
||||
public final class HmacDataContentBuilder implements DataContentBuilder<PlainContent> {
|
||||
private static final String ALGORITHM_ID = "HMAC";
|
||||
private final ZeroEchoSession session;
|
||||
|
||||
/**
|
||||
* Mode selects whether the pipeline computes an HMAC tag or verifies one.
|
||||
*
|
||||
@@ -622,8 +623,7 @@ public final class HmacDataContentBuilder implements DataContentBuilder<PlainCon
|
||||
final String mac = spec.macName(); // e.g., "HmacSHA256"
|
||||
try {
|
||||
if (genKeyBits != null) {
|
||||
return session.keyBuilders().symmetric().generate(ALGORITHM_ID,
|
||||
new HmacKeyGenSpec(mac, genKeyBits));
|
||||
return session.keyBuilders().symmetric().generate(ALGORITHM_ID, new HmacKeyGenSpec(mac, genKeyBits));
|
||||
}
|
||||
if (importRaw != null || importHex != null || importBase64 != null) {
|
||||
HmacKeyImportSpec ispec;
|
||||
|
||||
@@ -345,8 +345,7 @@ public final class RsaEncDataContentBuilder implements DataContentBuilder<DataCo
|
||||
* When {@link #getStream()} is invoked, this class creates an
|
||||
* {@link zeroecho.core.context.EncryptionContext} for the "RSA" algorithm in
|
||||
* {@link zeroecho.core.KeyUsage#ENCRYPT} role via
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
||||
* attaches the upstream stream, and returns a pull-based stream that encrypts
|
||||
* on-the-fly using the configured {@link RsaEncSpec}.
|
||||
* </p>
|
||||
@@ -407,8 +406,7 @@ public final class RsaEncDataContentBuilder implements DataContentBuilder<DataCo
|
||||
* When {@link #getStream()} is invoked, this class creates an
|
||||
* {@link zeroecho.core.context.EncryptionContext} for the "RSA" algorithm in
|
||||
* {@link zeroecho.core.KeyUsage#DECRYPT} role via
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String,
|
||||
* zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
||||
* {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)},
|
||||
* attaches the upstream stream, and returns a pull-based stream that decrypts
|
||||
* on-the-fly using the configured {@link RsaEncSpec}.
|
||||
* </p>
|
||||
|
||||
@@ -109,6 +109,7 @@ import zeroecho.sdk.content.api.PlainContent;
|
||||
public final class RsaSigDataContentBuilder implements DataContentBuilder<PlainContent> {
|
||||
private static final String ALGORITHM_ID = "RSA";
|
||||
private final ZeroEchoSession session;
|
||||
|
||||
/**
|
||||
* Mode selects whether the builder signs or verifies.
|
||||
*/
|
||||
|
||||
@@ -70,7 +70,8 @@
|
||||
* {@link zeroecho.sdk.builders.alg.ElgamalEncDataContentBuilder}.</li>
|
||||
* <li>RSA signatures:
|
||||
* {@link zeroecho.sdk.builders.alg.RsaSigDataContentBuilder}; generic signature
|
||||
* trailers use {@link zeroecho.sdk.builders.SignatureTrailerDataContentBuilder}.</li>
|
||||
* trailers use
|
||||
* {@link zeroecho.sdk.builders.SignatureTrailerDataContentBuilder}.</li>
|
||||
* <li>MAC and digest: {@link zeroecho.sdk.builders.alg.HmacDataContentBuilder},
|
||||
* {@link zeroecho.sdk.builders.alg.DigestDataContentBuilder}.</li>
|
||||
* <li>KEM envelopes: {@link zeroecho.sdk.builders.alg.KemDataContentBuilder}
|
||||
|
||||
@@ -85,8 +85,8 @@ public final class SecretPassword implements SecretContent, Destroyable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a password from a caller-owned character array. The supplied
|
||||
* array is cloned and remains owned by the caller.
|
||||
* Constructs a password from a caller-owned character array. The supplied array
|
||||
* is cloned and remains owned by the caller.
|
||||
*
|
||||
* @param password password characters; must not be {@code null}
|
||||
* @throws NullPointerException if {@code password} is {@code null}
|
||||
|
||||
@@ -148,8 +148,7 @@ final class Decryptor implements PlainContent, MultiRecipientContent {
|
||||
} catch (AEADBadTagException ex) {
|
||||
// wrong key/password for that entry, continue scanning
|
||||
if (LOG.isLoggable(Level.FINE)) {
|
||||
LOG.log(Level.FINE, "recipient authentication failed: {0}",
|
||||
ex.getClass().getSimpleName());
|
||||
LOG.log(Level.FINE, "recipient authentication failed: {0}", ex.getClass().getSimpleName());
|
||||
}
|
||||
} catch (GeneralSecurityException | IOException | IllegalArgumentException ex) {
|
||||
// entry not applicable to this opener/material; ignore and continue
|
||||
@@ -198,8 +197,7 @@ final class Decryptor implements PlainContent, MultiRecipientContent {
|
||||
}
|
||||
}
|
||||
|
||||
private void closeAfterAttempt(InputStream input, boolean transferred, Throwable primary)
|
||||
throws IOException {
|
||||
private void closeAfterAttempt(InputStream input, boolean transferred, Throwable primary) throws IOException {
|
||||
IOException cleanupFailure = null;
|
||||
if (!transferred) {
|
||||
try {
|
||||
@@ -282,8 +280,7 @@ final class Decryptor implements PlainContent, MultiRecipientContent {
|
||||
closeOpeners(openers, primary);
|
||||
}
|
||||
|
||||
/* default */ static void closeOpeners(List<RecipientOpener> ownedOpeners, Throwable primary)
|
||||
throws IOException {
|
||||
/* default */ static void closeOpeners(List<RecipientOpener> ownedOpeners, Throwable primary) throws IOException {
|
||||
IOException cleanupFailure = null;
|
||||
for (RecipientOpener opener : ownedOpeners) { // NOPMD - each opener is closed in this loop
|
||||
try {
|
||||
@@ -309,14 +306,13 @@ final class Decryptor implements PlainContent, MultiRecipientContent {
|
||||
}
|
||||
}
|
||||
|
||||
private void rejectOversizedCek(byte[] candidate, int fieldIndex, String recipientId,
|
||||
RecipientOpener opener) {
|
||||
private void rejectOversizedCek(byte[] candidate, int fieldIndex, String recipientId, RecipientOpener opener) {
|
||||
try {
|
||||
if (LOG.isLoggable(Level.WARNING)) {
|
||||
LOG.log(Level.WARNING,
|
||||
"Suspicious material in field {0}: {1}/{2} returned length {3}, while {4} is the limit. Ignoring.",
|
||||
new Object[] { fieldIndex, recipientId, opener.getClass().getName(),
|
||||
candidate.length, keyBytes });
|
||||
new Object[] { fieldIndex, recipientId, opener.getClass().getName(), candidate.length,
|
||||
keyBytes });
|
||||
}
|
||||
} finally {
|
||||
Arrays.fill(candidate, (byte) 0);
|
||||
|
||||
@@ -84,8 +84,7 @@ final class Encryptor implements EncryptedContent, MultiRecipientContent {
|
||||
this.keyBytes = keyBytes;
|
||||
this.maxRecipients = maxRecipients;
|
||||
this.maxEntryLen = maxEntryLen;
|
||||
this.randomBytesFactory = Objects.requireNonNull(randomBytesFactory,
|
||||
"randomBytesFactory must not be null");
|
||||
this.randomBytesFactory = Objects.requireNonNull(randomBytesFactory, "randomBytesFactory must not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,8 +231,7 @@ final class Encryptor implements EncryptedContent, MultiRecipientContent {
|
||||
return key;
|
||||
}
|
||||
|
||||
/* default */ static void closeRecipients(List<Recipient> ownedRecipients, Throwable primary)
|
||||
throws IOException {
|
||||
/* default */ static void closeRecipients(List<Recipient> ownedRecipients, Throwable primary) throws IOException {
|
||||
IOException cleanupFailure = null;
|
||||
for (Recipient recipient : ownedRecipients) {
|
||||
try {
|
||||
|
||||
@@ -82,8 +82,7 @@ public final class KemCtxRecipient implements Recipient, AutoCloseable {
|
||||
* @param kekBytes KEK length; exactly 16 or 32 bytes
|
||||
* @param saltLen length of the random salt to apply during HKDF
|
||||
* @throws NullPointerException if {@code ctx} is {@code null}
|
||||
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or
|
||||
* 32
|
||||
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32
|
||||
*/
|
||||
public KemCtxRecipient(KemContext ctx, int kekBytes, int saltLen) {
|
||||
this(ctx, kekBytes, saltLen, false);
|
||||
@@ -107,8 +106,7 @@ public final class KemCtxRecipient implements Recipient, AutoCloseable {
|
||||
* @param decoy {@code true} if this recipient is a decoy (fake entry that
|
||||
* cannot unwrap a CEK); {@code false} if it is a real recipient
|
||||
* @throws NullPointerException if {@code ctx} is {@code null}
|
||||
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or
|
||||
* 32
|
||||
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32
|
||||
*/
|
||||
public KemCtxRecipient(KemContext ctx, int kekBytes, int saltLen, boolean decoy) {
|
||||
int validatedKekBytes = RecipientKekSizes.requireSupported(kekBytes);
|
||||
|
||||
@@ -23,9 +23,9 @@ final class KemKeyDerivation {
|
||||
* Derives a KEK bound to the KEM algorithm identifier.
|
||||
*
|
||||
* @param sharedSecret KEM shared secret
|
||||
* @param salt HKDF salt
|
||||
* @param algorithmId canonical KEM algorithm identifier
|
||||
* @param outputBytes requested KEK size
|
||||
* @param salt HKDF salt
|
||||
* @param algorithmId canonical KEM algorithm identifier
|
||||
* @param outputBytes requested KEK size
|
||||
* @return newly allocated KEK bytes
|
||||
* @throws GeneralSecurityException if HKDF fails
|
||||
*/
|
||||
|
||||
@@ -46,17 +46,17 @@ import zeroecho.sdk.content.api.DataContent;
|
||||
* resources until processing or explicit cleanup.
|
||||
*
|
||||
* <p>
|
||||
* Callers must invoke {@link #close()} when a built instance is abandoned before
|
||||
* {@link #getStream()} is called. Successful or failed stream construction also
|
||||
* releases the owned recipient resources. Unlocking keys and password material
|
||||
* supplied separately remain caller-owned and are never destroyed by this
|
||||
* content.
|
||||
* Callers must invoke {@link #close()} when a built instance is abandoned
|
||||
* before {@link #getStream()} is called. Successful or failed stream
|
||||
* construction also releases the owned recipient resources. Unlocking keys and
|
||||
* password material supplied separately remain caller-owned and are never
|
||||
* destroyed by this content.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Implementations are not thread-safe. Cleanup is idempotent, and content cannot
|
||||
* be used after cleanup. Calling {@link #getStream()} is terminal even when stream
|
||||
* construction fails.
|
||||
* Implementations are not thread-safe. Cleanup is idempotent, and content
|
||||
* cannot be used after cleanup. Calling {@link #getStream()} is terminal even
|
||||
* when stream construction fails.
|
||||
* </p>
|
||||
*/
|
||||
public interface MultiRecipientContent extends DataContent, Destroyable, AutoCloseable {
|
||||
|
||||
@@ -215,8 +215,8 @@ public final class MultiRecipientDataSourceBuilder
|
||||
ensureOpen();
|
||||
RecipientKekSizes.requireSupported(kekBytes);
|
||||
session.pbkdf2Limits().validateTrusted(iterations);
|
||||
this.recipients.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, false,
|
||||
session.pbkdf2Limits()));
|
||||
this.recipients
|
||||
.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, false, session.pbkdf2Limits()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -236,8 +236,7 @@ public final class MultiRecipientDataSourceBuilder
|
||||
* @param saltLen HKDF salt length in bytes
|
||||
* @return this builder
|
||||
* @throws NullPointerException if {@code kem} is {@code null}
|
||||
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or
|
||||
* 32
|
||||
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32
|
||||
*/
|
||||
public MultiRecipientDataSourceBuilder addRecipient(KemContext kem, int kekBytes, int saltLen) {
|
||||
ensureOpen();
|
||||
@@ -292,8 +291,8 @@ public final class MultiRecipientDataSourceBuilder
|
||||
ensureOpen();
|
||||
RecipientKekSizes.requireSupported(kekBytes);
|
||||
session.pbkdf2Limits().validateTrusted(iterations);
|
||||
this.recipients.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, true,
|
||||
session.pbkdf2Limits()));
|
||||
this.recipients
|
||||
.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, true, session.pbkdf2Limits()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -314,8 +313,7 @@ public final class MultiRecipientDataSourceBuilder
|
||||
* @param saltLen HKDF salt length in bytes
|
||||
* @return this builder
|
||||
* @throws NullPointerException if {@code kem} is {@code null}
|
||||
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or
|
||||
* 32
|
||||
* @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32
|
||||
*/
|
||||
public MultiRecipientDataSourceBuilder addRecipientDecoy(KemContext kem, int kekBytes, int saltLen) {
|
||||
ensureOpen();
|
||||
@@ -384,9 +382,9 @@ public final class MultiRecipientDataSourceBuilder
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The builder takes ownership of the opener. The opener must be reusable
|
||||
* across all recipient entries and is closed after scanning or when the built
|
||||
* content is abandoned.
|
||||
* The builder takes ownership of the opener. The opener must be reusable across
|
||||
* all recipient entries and is closed after scanning or when the built content
|
||||
* is abandoned.
|
||||
* </p>
|
||||
*
|
||||
* @param opener reusable opener to add
|
||||
@@ -479,8 +477,10 @@ public final class MultiRecipientDataSourceBuilder
|
||||
/**
|
||||
* Destroys recipient secrets still owned by this builder.
|
||||
*
|
||||
* <p>Recipients transferred to a successfully built encrypting content object
|
||||
* are owned and destroyed by that object instead.</p>
|
||||
* <p>
|
||||
* Recipients transferred to a successfully built encrypting content object are
|
||||
* owned and destroyed by that object instead.
|
||||
* </p>
|
||||
*
|
||||
* @throws DestroyFailedException if recipient cleanup fails
|
||||
*/
|
||||
|
||||
@@ -58,6 +58,7 @@ public final class PasswordOpener implements RecipientOpener {
|
||||
public PasswordOpener(Pbkdf2Limits limits) {
|
||||
this.limits = java.util.Objects.requireNonNull(limits, "limits must not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to open a password-based recipient entry using a password unlock
|
||||
* material.
|
||||
|
||||
@@ -76,8 +76,8 @@ public final class PasswordRecipient implements Recipient, Destroyable, AutoClos
|
||||
* <li>The caller should clear the {@code password} array after constructing the
|
||||
* recipient to minimize exposure in memory.</li>
|
||||
* <li>Choose an iteration count appropriate to the target platform to balance
|
||||
* password-guessing resistance against recipient creation and opening
|
||||
* latency. Counts below {@value Pbkdf2Limits#MINIMUM} are rejected.</li>
|
||||
* password-guessing resistance against recipient creation and opening latency.
|
||||
* Counts below {@value Pbkdf2Limits#MINIMUM} are rejected.</li>
|
||||
* <li>Decoy recipients increase confidentiality by hiding the number of real
|
||||
* recipients but cannot successfully unwrap the CEK.</li>
|
||||
* </ul>
|
||||
|
||||
@@ -8,9 +8,11 @@ package zeroecho.sdk.guard;
|
||||
* Defines the KEK sizes supported by recipient entries without an encoded size
|
||||
* discriminator.
|
||||
*
|
||||
* <p>The current recipient format permits AES-128 and AES-256 wrapping only.
|
||||
* <p>
|
||||
* The current recipient format permits AES-128 and AES-256 wrapping only.
|
||||
* Validation must occur before an entry is registered so every emitted entry
|
||||
* remains openable by the corresponding recipient opener.</p>
|
||||
* remains openable by the corresponding recipient opener.
|
||||
* </p>
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
@@ -18,9 +18,11 @@ import zeroecho.core.annotation.Describable;
|
||||
/**
|
||||
* Caller-owned session-operation input used to unlock a recipient entry.
|
||||
*
|
||||
* <p>Components accepting an {@code UnlockMaterial} borrow it and do not destroy
|
||||
* <p>
|
||||
* Components accepting an {@code UnlockMaterial} borrow it and do not destroy
|
||||
* it. The caller must keep it usable until the operation completes and destroy
|
||||
* password material afterwards.</p>
|
||||
* password material afterwards.
|
||||
* </p>
|
||||
*/
|
||||
public sealed interface UnlockMaterial extends Describable {
|
||||
/**
|
||||
@@ -44,8 +46,10 @@ public sealed interface UnlockMaterial extends Describable {
|
||||
/**
|
||||
* Destroyable password unlocking material backed by an owned character array.
|
||||
*
|
||||
* <p>Construction and access use defensive copies. Destruction is idempotent
|
||||
* and prevents subsequent access.</p>
|
||||
* <p>
|
||||
* Construction and access use defensive copies. Destruction is idempotent and
|
||||
* prevents subsequent access.
|
||||
* </p>
|
||||
*/
|
||||
final class Password implements UnlockMaterial, Destroyable {
|
||||
private final char[] characters;
|
||||
|
||||
@@ -108,8 +108,8 @@
|
||||
* generation and recipient entries; the symmetric builder manages algorithm
|
||||
* parameters and payload framing.</li>
|
||||
* <li><strong>Reusable opener strategies:</strong> recipients encode entries;
|
||||
* openers attempt every applicable entry and create fresh cryptographic contexts
|
||||
* per attempt. Neither carries long-lived secret state.</li>
|
||||
* openers attempt every applicable entry and create fresh cryptographic
|
||||
* contexts per attempt. Neither carries long-lived secret state.</li>
|
||||
* <li><strong>Defensive parsing:</strong> the builder applies limits to the
|
||||
* number of recipients and the size of each entry blob; the symmetric stage
|
||||
* applies its own limits to its header and payload.</li>
|
||||
|
||||
@@ -196,8 +196,8 @@ public final class HybridDerived {
|
||||
* construction.
|
||||
* </p>
|
||||
*
|
||||
* @param aes AES builder to configure (must not be null)
|
||||
* @param keyBits AES key size in bits (128/192/256)
|
||||
* @param aes AES builder to configure (must not be null)
|
||||
* @param keyBits AES key size in bits (128/192/256)
|
||||
* @return the provided builder instance
|
||||
* @throws NullPointerException if aes is null
|
||||
* @throws IllegalArgumentException if keyBits is invalid
|
||||
@@ -225,16 +225,16 @@ public final class HybridDerived {
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a ChaCha key and applies it with optional AAD to the provided
|
||||
* ChaCha builder.
|
||||
* Derives a ChaCha key and applies it with optional AAD to the provided ChaCha
|
||||
* builder.
|
||||
*
|
||||
* <p>
|
||||
* The returned value is the same builder instance to preserve fluent pipeline
|
||||
* construction.
|
||||
* </p>
|
||||
*
|
||||
* @param chacha ChaCha builder to configure (must not be null)
|
||||
* @param keyBits key size in bits (typically 256)
|
||||
* @param chacha ChaCha builder to configure (must not be null)
|
||||
* @param keyBits key size in bits (typically 256)
|
||||
* @return the provided builder instance
|
||||
* @throws NullPointerException if chacha is null
|
||||
* @throws IllegalArgumentException if keyBits is invalid
|
||||
|
||||
@@ -150,8 +150,8 @@ public final class HybridKexContext implements MessageAgreementContext {
|
||||
this(profile, classic, pqc, Kdf::hkdfSha256);
|
||||
}
|
||||
|
||||
/* default */ HybridKexContext(HybridKexProfile profile, AgreementContext classic,
|
||||
MessageAgreementContext pqc, SecretDeriver secretDeriver) {
|
||||
/* default */ HybridKexContext(HybridKexProfile profile, AgreementContext classic, MessageAgreementContext pqc,
|
||||
SecretDeriver secretDeriver) {
|
||||
this.profile = Objects.requireNonNull(profile, "profile");
|
||||
this.classic = Objects.requireNonNull(classic, "classic");
|
||||
this.pqc = Objects.requireNonNull(pqc, "pqc");
|
||||
@@ -358,15 +358,14 @@ public final class HybridKexContext implements MessageAgreementContext {
|
||||
/**
|
||||
* Derives the final output from owned temporary hybrid input.
|
||||
*
|
||||
* @param ikm combined component secrets
|
||||
* @param salt HKDF salt
|
||||
* @param info HKDF context information
|
||||
* @param ikm combined component secrets
|
||||
* @param salt HKDF salt
|
||||
* @param info HKDF context information
|
||||
* @param outputLength requested output length
|
||||
* @return derived output transferred to the caller
|
||||
* @throws GeneralSecurityException if derivation fails
|
||||
*/
|
||||
byte[] derive(byte[] ikm, byte[] salt, byte[] info, int outputLength)
|
||||
throws GeneralSecurityException;
|
||||
byte[] derive(byte[] ikm, byte[] salt, byte[] info, int outputLength) throws GeneralSecurityException;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,9 +83,11 @@ import zeroecho.sdk.ZeroEchoSession;
|
||||
* </p>
|
||||
*
|
||||
* <h2>Error handling</h2>
|
||||
* <p>Context construction is in-memory and reports configuration or provider
|
||||
* failures through the security exception model of {@link ZeroEchoSession}.
|
||||
* I/O failures remain associated with later stream processing.</p>
|
||||
* <p>
|
||||
* Context construction is in-memory and reports configuration or provider
|
||||
* failures through the security exception model of {@link ZeroEchoSession}. I/O
|
||||
* failures remain associated with later stream processing.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Thread safety</h2>
|
||||
* <p>
|
||||
@@ -158,8 +160,7 @@ public final class HybridKexContexts {
|
||||
AgreementContext classic = null;
|
||||
MessageAgreementContext pqc = null;
|
||||
try {
|
||||
classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicInitiatorPrivate,
|
||||
classicSpec);
|
||||
classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicInitiatorPrivate, classicSpec);
|
||||
classic.setPeerPublic(classicPeerPublic);
|
||||
pqc = session.createContext(pqcAlgId, KeyUsage.AGREEMENT, pqcPeerPublic, pqcSpec);
|
||||
return new HybridKexContext(profile, classic, pqc);
|
||||
@@ -222,8 +223,7 @@ public final class HybridKexContexts {
|
||||
AgreementContext classic = null;
|
||||
MessageAgreementContext pqc = null;
|
||||
try {
|
||||
classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicResponderPrivate,
|
||||
classicSpec);
|
||||
classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicResponderPrivate, classicSpec);
|
||||
classic.setPeerPublic(classicPeerPublic);
|
||||
pqc = session.createContext(pqcAlgId, KeyUsage.AGREEMENT, pqcResponderPrivate, pqcSpec);
|
||||
return new HybridKexContext(profile, classic, pqc);
|
||||
@@ -262,9 +262,8 @@ public final class HybridKexContexts {
|
||||
* @throws NullPointerException if any required argument is {@code null}
|
||||
*/
|
||||
public static HybridKexContext initiatorPairMessage(ZeroEchoSession session, HybridKexProfile profile,
|
||||
String classicAlgId,
|
||||
zeroecho.core.alg.common.agreement.KeyPairKey classicInitiatorKeyPair, ContextSpec classicSpec,
|
||||
String pqcAlgId, PublicKey pqcPeerPublic, ContextSpec pqcSpec) {
|
||||
String classicAlgId, zeroecho.core.alg.common.agreement.KeyPairKey classicInitiatorKeyPair,
|
||||
ContextSpec classicSpec, String pqcAlgId, PublicKey pqcPeerPublic, ContextSpec pqcSpec) {
|
||||
|
||||
Objects.requireNonNull(session, "session");
|
||||
Objects.requireNonNull(profile, "profile");
|
||||
@@ -276,8 +275,7 @@ public final class HybridKexContexts {
|
||||
MessageAgreementContext classic = null;
|
||||
MessageAgreementContext pqc = null;
|
||||
try {
|
||||
classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicInitiatorKeyPair,
|
||||
classicSpec);
|
||||
classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicInitiatorKeyPair, classicSpec);
|
||||
pqc = session.createContext(pqcAlgId, KeyUsage.AGREEMENT, pqcPeerPublic, pqcSpec);
|
||||
return new HybridKexContext(profile, classic, pqc);
|
||||
} catch (RuntimeException | Error failure) { // NOPMD - close partial construction
|
||||
@@ -312,9 +310,8 @@ public final class HybridKexContexts {
|
||||
* @throws NullPointerException if any required argument is {@code null}
|
||||
*/
|
||||
public static HybridKexContext responderPairMessage(ZeroEchoSession session, HybridKexProfile profile,
|
||||
String classicAlgId,
|
||||
zeroecho.core.alg.common.agreement.KeyPairKey classicResponderKeyPair, ContextSpec classicSpec,
|
||||
String pqcAlgId, PrivateKey pqcResponderPrivate, ContextSpec pqcSpec) {
|
||||
String classicAlgId, zeroecho.core.alg.common.agreement.KeyPairKey classicResponderKeyPair,
|
||||
ContextSpec classicSpec, String pqcAlgId, PrivateKey pqcResponderPrivate, ContextSpec pqcSpec) {
|
||||
|
||||
Objects.requireNonNull(session, "session");
|
||||
Objects.requireNonNull(profile, "profile");
|
||||
@@ -326,8 +323,7 @@ public final class HybridKexContexts {
|
||||
MessageAgreementContext classic = null;
|
||||
MessageAgreementContext pqc = null;
|
||||
try {
|
||||
classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicResponderKeyPair,
|
||||
classicSpec);
|
||||
classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicResponderKeyPair, classicSpec);
|
||||
pqc = session.createContext(pqcAlgId, KeyUsage.AGREEMENT, pqcResponderPrivate, pqcSpec);
|
||||
return new HybridKexContext(profile, classic, pqc);
|
||||
} catch (RuntimeException | Error failure) { // NOPMD - close partial construction
|
||||
|
||||
@@ -155,8 +155,10 @@ public final class HybridKexExporter implements Destroyable, AutoCloseable {
|
||||
/**
|
||||
* Overwrites the exporter root secret and salt.
|
||||
*
|
||||
* <p>Destruction is idempotent. All subsequent export or diagnostic access
|
||||
* fails with {@link IllegalStateException}.</p>
|
||||
* <p>
|
||||
* Destruction is idempotent. All subsequent export or diagnostic access fails
|
||||
* with {@link IllegalStateException}.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
@@ -118,8 +118,7 @@ final class HybridSignatureContext implements SignatureContext {
|
||||
* @throws IllegalArgumentException if {@code maxBufferedBytes <= 0}
|
||||
*/
|
||||
/* default */ HybridSignatureContext(ZeroEchoSession session, HybridSignatureProfile profile,
|
||||
PrivateKey classicPrivate,
|
||||
PrivateKey pqcPrivate, int maxBufferedBytes) {
|
||||
PrivateKey classicPrivate, PrivateKey pqcPrivate, int maxBufferedBytes) {
|
||||
this.session = Objects.requireNonNull(session, "session");
|
||||
this.profile = Objects.requireNonNull(profile, "profile");
|
||||
this.classicPrivate = Objects.requireNonNull(classicPrivate, "classicPrivate");
|
||||
@@ -151,8 +150,7 @@ final class HybridSignatureContext implements SignatureContext {
|
||||
* @throws IllegalArgumentException if {@code maxBufferedBytes <= 0}
|
||||
*/
|
||||
/* default */ HybridSignatureContext(ZeroEchoSession session, HybridSignatureProfile profile,
|
||||
PublicKey classicPublic,
|
||||
PublicKey pqcPublic, int maxBufferedBytes) {
|
||||
PublicKey classicPublic, PublicKey pqcPublic, int maxBufferedBytes) {
|
||||
this.session = Objects.requireNonNull(session, "session");
|
||||
this.profile = Objects.requireNonNull(profile, "profile");
|
||||
this.classicPublic = Objects.requireNonNull(classicPublic, "classicPublic");
|
||||
|
||||
@@ -69,8 +69,7 @@ public final class HybridSignatureContexts {
|
||||
* @since 1.0
|
||||
*/
|
||||
public static SignatureContext sign(ZeroEchoSession session, HybridSignatureProfile profile,
|
||||
PrivateKey classicPrivate,
|
||||
PrivateKey pqcPrivate, int maxBufferedBytes) {
|
||||
PrivateKey classicPrivate, PrivateKey pqcPrivate, int maxBufferedBytes) {
|
||||
Objects.requireNonNull(session, "session");
|
||||
Objects.requireNonNull(profile, "profile");
|
||||
Objects.requireNonNull(classicPrivate, "classicPrivate");
|
||||
|
||||
@@ -35,7 +35,6 @@ package zeroecho.sdk.util;
|
||||
|
||||
import zeroecho.core.util.RandomSupport;
|
||||
|
||||
|
||||
/**
|
||||
* Utility class for generating random passwords and secure random byte arrays.
|
||||
* <p>
|
||||
|
||||
@@ -74,9 +74,8 @@ class CapabilityValueSemanticsTest {
|
||||
void nullAndIncompatibleDefaultsAreRejectedAtConstruction() {
|
||||
System.out.println("nullAndIncompatibleDefaultsAreRejectedAtConstruction");
|
||||
assertThrows(NullPointerException.class, () -> capability(() -> null));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> new Capability("DIGEST", AlgorithmFamily.DIGEST, KeyUsage.DIGEST, DigestContext.class,
|
||||
NullKey.class, TestSpec.class, new OtherSpec()));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Capability("DIGEST", AlgorithmFamily.DIGEST,
|
||||
KeyUsage.DIGEST, DigestContext.class, NullKey.class, TestSpec.class, new OtherSpec()));
|
||||
|
||||
System.out.println("...invalidDefaultsRejected=true");
|
||||
System.out.println("nullAndIncompatibleDefaultsAreRejectedAtConstruction...ok");
|
||||
@@ -121,8 +120,7 @@ class CapabilityValueSemanticsTest {
|
||||
(key, spec) -> {
|
||||
runtimeSpecs.add(spec);
|
||||
return mock(DigestContext.class);
|
||||
},
|
||||
() -> new TestSpec(Integer.toString(evaluations.incrementAndGet())));
|
||||
}, () -> new TestSpec(Integer.toString(evaluations.incrementAndGet())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,9 +105,10 @@ public class CatalogContractTest {
|
||||
Logger jul = Logger.getLogger("zeroecho.audit");
|
||||
jul.setLevel(Level.FINE); // see PROGRESS at FINE
|
||||
|
||||
session = new ZeroEchoSession().withAuditListener(JulAuditListenerStd.builder().logger(jul)
|
||||
.infoLevel(Level.INFO).warnLevel(Level.WARNING).progressLevel(Level.FINE)
|
||||
.includeStackTraces(true).build()).withAuditMode(AuditMode.WRAP);
|
||||
session = new ZeroEchoSession()
|
||||
.withAuditListener(JulAuditListenerStd.builder().logger(jul).infoLevel(Level.INFO)
|
||||
.warnLevel(Level.WARNING).progressLevel(Level.FINE).includeStackTraces(true).build())
|
||||
.withAuditMode(AuditMode.WRAP);
|
||||
|
||||
dump("");
|
||||
dump("zeroecho.core.audit");
|
||||
@@ -178,8 +179,7 @@ public class CatalogContractTest {
|
||||
.anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE);
|
||||
|
||||
// SIGN/VERIFY (asymmetric)
|
||||
if (alg.roles().contains(KeyUsage.SIGN) && alg.roles().contains(KeyUsage.VERIFY)
|
||||
&& hasAsym) {
|
||||
if (alg.roles().contains(KeyUsage.SIGN) && alg.roles().contains(KeyUsage.VERIFY) && hasAsym) {
|
||||
trySignVerify(id, msg);
|
||||
System.out.println();
|
||||
}
|
||||
@@ -193,8 +193,7 @@ public class CatalogContractTest {
|
||||
}
|
||||
|
||||
// KEM
|
||||
if (alg.roles().contains(KeyUsage.ENCAPSULATE) && alg.roles().contains(KeyUsage.DECAPSULATE)
|
||||
&& hasAsym) {
|
||||
if (alg.roles().contains(KeyUsage.ENCAPSULATE) && alg.roles().contains(KeyUsage.DECAPSULATE) && hasAsym) {
|
||||
tryKem(id, msg);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
@@ -74,10 +74,10 @@ import zeroecho.core.tag.TagEngine;
|
||||
*
|
||||
* <p>
|
||||
* These tests focus on the internal audit wrapping path used by
|
||||
* {@link AuditedContexts#wrap(CryptoContext, AuditListener, KeyUsage)}.
|
||||
* They verify that representative context types are wrapped as audited JDK
|
||||
* proxies and that the resulting wrapper preserves the expected basic
|
||||
* delegation behavior.
|
||||
* {@link AuditedContexts#wrap(CryptoContext, AuditListener, KeyUsage)}. They
|
||||
* verify that representative context types are wrapped as audited JDK proxies
|
||||
* and that the resulting wrapper preserves the expected basic delegation
|
||||
* behavior.
|
||||
* </p>
|
||||
*/
|
||||
class CryptoAlgorithmsAuditWrapTest {
|
||||
|
||||
@@ -27,7 +27,8 @@ import zeroecho.core.context.DigestContext;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
|
||||
/**
|
||||
* Verifies authoritative registry ownership and explicitly scoped runtime state.
|
||||
* Verifies authoritative registry ownership and explicitly scoped runtime
|
||||
* state.
|
||||
*/
|
||||
class CryptoArchitectureTest {
|
||||
|
||||
@@ -51,8 +52,8 @@ class CryptoArchitectureTest {
|
||||
System.out.println("explicitPolicyOrder");
|
||||
List<String> events = new ArrayList<>();
|
||||
AuditListener listener = policyOrderListener(events);
|
||||
ZeroEchoSession allowed = new ZeroEchoSession().withAuditListener(listener).withPolicy(
|
||||
(id, role, key, spec) -> events.add("policy"));
|
||||
ZeroEchoSession allowed = new ZeroEchoSession().withAuditListener(listener)
|
||||
.withPolicy((id, role, key, spec) -> events.add("policy"));
|
||||
try (DigestContext context = allowed.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE)) {
|
||||
assertSame(CryptoAlgorithms.require("DIGEST"), context.algorithm());
|
||||
}
|
||||
@@ -60,19 +61,18 @@ class CryptoArchitectureTest {
|
||||
|
||||
events.clear();
|
||||
IllegalArgumentException denial = new IllegalArgumentException("controlled denial");
|
||||
ZeroEchoSession denied = new ZeroEchoSession().withAuditListener(listener).withPolicy(
|
||||
(id, role, key, spec) -> {
|
||||
events.add("policy");
|
||||
throw denial;
|
||||
});
|
||||
ZeroEchoSession denied = new ZeroEchoSession().withAuditListener(listener).withPolicy((id, role, key, spec) -> {
|
||||
events.add("policy");
|
||||
throw denial;
|
||||
});
|
||||
assertSame(denial, assertThrows(IllegalArgumentException.class,
|
||||
() -> denied.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE)));
|
||||
assertEquals(List.of("policy"), events);
|
||||
|
||||
events.clear();
|
||||
IllegalStateException failure = new IllegalStateException("controlled policy failure");
|
||||
ZeroEchoSession failing = new ZeroEchoSession().withAuditListener(listener).withPolicy(
|
||||
(id, role, key, spec) -> {
|
||||
ZeroEchoSession failing = new ZeroEchoSession().withAuditListener(listener)
|
||||
.withPolicy((id, role, key, spec) -> {
|
||||
events.add("policy");
|
||||
throw failure;
|
||||
});
|
||||
@@ -91,8 +91,7 @@ class CryptoArchitectureTest {
|
||||
AtomicInteger secondEvents = new AtomicInteger();
|
||||
AuditListener firstListener = contextListener(firstEvents);
|
||||
AuditListener secondListener = contextListener(secondEvents);
|
||||
ZeroEchoSession wrapped = new ZeroEchoSession().withAuditListener(firstListener)
|
||||
.withAuditMode(AuditMode.WRAP);
|
||||
ZeroEchoSession wrapped = new ZeroEchoSession().withAuditListener(firstListener).withAuditMode(AuditMode.WRAP);
|
||||
ZeroEchoSession direct = new ZeroEchoSession().withAuditListener(secondListener);
|
||||
|
||||
try (DigestContext wrappedContext = wrapped.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE)) {
|
||||
|
||||
@@ -58,15 +58,12 @@ class SecretSpecLifecycleTest {
|
||||
new SpecCase("zeroecho.core.alg.kyber.KyberPrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR),
|
||||
new SpecCase("zeroecho.core.alg.mldsa.MldsaPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR),
|
||||
new SpecCase("zeroecho.core.alg.ntru.NtruPrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR),
|
||||
new SpecCase("zeroecho.core.alg.ntruprime.NtrulPrimePrivateKeySpec", "pkcs8", 8,
|
||||
Factory.CONSTRUCTOR),
|
||||
new SpecCase("zeroecho.core.alg.ntruprime.SntruPrimePrivateKeySpec", "pkcs8", 8,
|
||||
Factory.CONSTRUCTOR),
|
||||
new SpecCase("zeroecho.core.alg.ntruprime.NtrulPrimePrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR),
|
||||
new SpecCase("zeroecho.core.alg.ntruprime.SntruPrimePrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR),
|
||||
new SpecCase("zeroecho.core.alg.rsa.RsaPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR),
|
||||
new SpecCase("zeroecho.core.alg.saber.SaberPrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR),
|
||||
new SpecCase("zeroecho.core.alg.slhdsa.SlhDsaPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR),
|
||||
new SpecCase("zeroecho.core.alg.sphincsplus.SphincsPlusPrivateKeySpec", "encoded", 8,
|
||||
Factory.CONSTRUCTOR),
|
||||
new SpecCase("zeroecho.core.alg.sphincsplus.SphincsPlusPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR),
|
||||
new SpecCase("zeroecho.core.alg.xdh.XdhPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR));
|
||||
|
||||
@Test
|
||||
@@ -131,14 +128,14 @@ class SecretSpecLifecycleTest {
|
||||
() -> AesKeyImportSpec.unmarshal(PairSeq.of("k.b64", aesKey, "k.b64", "%")));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ChaChaKeyImportSpec.unmarshal(PairSeq.of("k.b64", chachaKey, "k.b64", "%")));
|
||||
assertThrows(IllegalArgumentException.class, () -> HmacKeyImportSpec.unmarshal(
|
||||
PairSeq.of("mac", "HmacSHA256", "k.b64", encodedPrivateKey, "k.b64", "%")));
|
||||
assertThrows(IllegalArgumentException.class, () -> MldsaPrivateKeySpec.unmarshal(
|
||||
PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%")));
|
||||
assertThrows(IllegalArgumentException.class, () -> SlhDsaPrivateKeySpec.unmarshal(
|
||||
PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%")));
|
||||
assertThrows(IllegalArgumentException.class, () -> SphincsPlusPrivateKeySpec.unmarshal(
|
||||
PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%")));
|
||||
assertThrows(IllegalArgumentException.class, () -> HmacKeyImportSpec
|
||||
.unmarshal(PairSeq.of("mac", "HmacSHA256", "k.b64", encodedPrivateKey, "k.b64", "%")));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> MldsaPrivateKeySpec.unmarshal(PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%")));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> SlhDsaPrivateKeySpec.unmarshal(PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%")));
|
||||
assertThrows(IllegalArgumentException.class, () -> SphincsPlusPrivateKeySpec
|
||||
.unmarshal(PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%")));
|
||||
System.out.println("...cases=6");
|
||||
System.out.println("unmarshalCleansUpWhenMalformedDataFollowsValidSecretMaterial...ok");
|
||||
}
|
||||
@@ -195,7 +192,8 @@ class SecretSpecLifecycleTest {
|
||||
generator.initialize(2048);
|
||||
KeyPair pair = generator.generateKeyPair();
|
||||
RsaPrivateKeySpec rsaSpec = new RsaPrivateKeySpec(pair.getPrivate().getEncoded());
|
||||
PrivateKey imported = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().importPrivate("RSA", rsaSpec);
|
||||
PrivateKey imported = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().importPrivate("RSA",
|
||||
rsaSpec);
|
||||
assertArrayEquals(pair.getPrivate().getEncoded(), imported.getEncoded());
|
||||
assertFalse(rsaSpec.isDestroyed());
|
||||
assertArrayEquals(pair.getPrivate().getEncoded(), rsaSpec.encoded());
|
||||
@@ -221,9 +219,7 @@ class SecretSpecLifecycleTest {
|
||||
}
|
||||
|
||||
private enum Factory {
|
||||
CONSTRUCTOR,
|
||||
STATIC_RAW,
|
||||
HMAC
|
||||
CONSTRUCTOR, STATIC_RAW, HMAC
|
||||
}
|
||||
|
||||
private record SpecCase(String className, String accessor, int length, Factory factory) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user