Initial commit (history reset)

This commit is contained in:
2025-09-16 23:14:24 +02:00
commit 2cc988925a
396 changed files with 71058 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core;
/**
* High-level classification of cryptographic algorithms.
* <p>
* Each {@code AlgorithmFamily} groups primitives with similar lifecycle
* constraints, key properties, and safe usage patterns.
*
* <h2>Families</h2>
* <ul>
* <li>{@link #ASYMMETRIC}: Public-key algorithms such as signature schemes
* (Ed25519, RSA) or public-key encryption.</li>
* <li>{@link #SYMMETRIC}: Shared-key algorithms such as block/stream ciphers
* and message authentication codes.</li>
* <li>{@link #KEM}: Key encapsulation mechanisms, including post-quantum
* schemes.</li>
* <li>{@link #DIGEST}: Unkeyed hash functions and extendable-output functions
* (e.g., SHA-2, SHA-3, BLAKE3).</li>
* <li>{@link #AGREEMENT}: Key-agreement schemes (e.g., X25519, ECDH), distinct
* from KEMs but with similar goals.</li>
* </ul>
*
* <p>
* <b>Usage:</b> Libraries and protocols can branch on this classification to
* enforce correct API surfaces (e.g., demanding nonces for symmetric AEAD, or
* key pairs for asymmetric operations).
* </p>
*
* @since 1.0
*/
public enum AlgorithmFamily {
/** Public-key primitives (signatures, RSA, etc.). */
ASYMMETRIC,
/** Shared-key primitives (ciphers, MACs). */
SYMMETRIC,
/** Key encapsulation mechanisms (encapsulate/decapsulate). */
KEM,
/** Unkeyed hash functions or XOFs. */
DIGEST,
/** Key-agreement schemes such as ECDH/X25519. */
AGREEMENT
}

View File

@@ -0,0 +1,106 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core;
import java.security.Key;
import java.util.Objects;
import java.util.function.Supplier;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.context.CryptoContext;
import zeroecho.core.spec.ContextSpec;
import zeroecho.core.spi.ContextConstructorKS;
/**
* Immutable descriptor of an algorithm capability.
*
* <p>
* A {@code Capability} describes one role supported by a
* {@link CryptoAlgorithm}, including:
* </p>
* <ul>
* <li>the algorithm identifier,</li>
* <li>its high-level {@link AlgorithmFamily},</li>
* <li>the {@link KeyUsage} role (e.g., ENCRYPT, VERIFY),</li>
* <li>the expected {@link CryptoContext} type,</li>
* <li>the accepted {@link Key} type,</li>
* <li>the accepted {@link ContextSpec} type, and</li>
* <li>a supplier for a default spec.</li>
* </ul>
*
* <h2>Purpose</h2> Capabilities allow discovery, inspection, and documentation
* of what an algorithm can do. Higher layers (e.g., protocol builders,
* registries, tooling) can enumerate capabilities via
* {@link CryptoAlgorithm#listCapabilities()} and adapt automatically.
*
* <p>
* Each capability corresponds to a call to
* {@link AbstractCryptoAlgorithm#capability(AlgorithmFamily, KeyUsage, Class, Class, Class, ContextConstructorKS, Supplier)}.
* </p>
*
* <h2>Thread-safety</h2> {@code Capability} instances are immutable and safe to
* share across threads.
*
* @since 1.0
*/
public record Capability(String algorithmId, AlgorithmFamily family, KeyUsage role,
Class<? extends CryptoContext> contextType, Class<? extends Key> keyType, Class<? extends ContextSpec> specType,
Supplier<? extends ContextSpec> defaultSpec) {
/**
* Creates a new capability descriptor.
*
* @param algorithmId identifier of the algorithm this capability belongs to
* @param family high-level algorithm family classification
* @param role supported {@link KeyUsage} role
* @param contextType expected {@link CryptoContext} type for this role
* @param keyType accepted {@link Key} type for this role
* @param specType accepted {@link ContextSpec} type for this role
* @param defaultSpec supplier of a default spec (used when {@code null} is
* passed)
* @throws NullPointerException if any argument is {@code null}
*/
public Capability(String algorithmId, AlgorithmFamily family, KeyUsage role,
Class<? extends CryptoContext> contextType, Class<? extends Key> keyType,
Class<? extends ContextSpec> specType, Supplier<? extends ContextSpec> defaultSpec) {
this.algorithmId = Objects.requireNonNull(algorithmId, "algorithmId must not be null");
this.family = Objects.requireNonNull(family, "family must not be null");
this.role = Objects.requireNonNull(role, "role must not be null");
this.contextType = Objects.requireNonNull(contextType, "contextType must not be null");
this.keyType = Objects.requireNonNull(keyType, "keyType must not be null");
this.specType = Objects.requireNonNull(specType, "specType must not be null");
this.defaultSpec = Objects.requireNonNull(defaultSpec, "defaultSpec must not be null");
}
}

View File

@@ -0,0 +1,115 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Helper routines for catalog selection by family and roles.
*
* <h2>Purpose</h2> This final static nested class provides reusable filtering
* helpers over {@code CryptoAlgorithms} that can be shared by other CLI
* utilities. The selection logic iterates the discovered algorithm identifiers
* and checks metadata exposed by {@code CryptoAlgorithm}.
*/
public final class CatalogSelector {
private CatalogSelector() {
// no instances
}
/**
* Returns algorithm ids that belong to the given family and contain all
* required roles.
*
* @param family required {@link AlgorithmFamily}
* @param requireAllRoles set of {@link KeyUsage} roles that must be supported
* @return list of matching algorithm ids in discovery order
* @throws NullPointerException if {@code family} or {@code requireAllRoles} is
* null
*/
public static List<String> selectByFamilyAndRoles(AlgorithmFamily family, Collection<KeyUsage> requireAllRoles) {
Objects.requireNonNull(family, "family");
Objects.requireNonNull(requireAllRoles, "requireAllRoles");
List<String> out = new ArrayList<>();
Set<String> ids = CryptoAlgorithms.available();
for (String id : ids) {
CryptoAlgorithm alg = CryptoAlgorithms.require(id);
boolean familyMatch = alg.listCapabilities().stream().anyMatch(c -> c.family() == family);
if (!familyMatch) {
continue;
}
if (!alg.roles().containsAll(requireAllRoles)) {
continue;
}
out.add(id);
}
return out;
}
/**
* Returns algorithm ids that belong to the given family, regardless of roles.
*
* @param family required {@link AlgorithmFamily}
* @return list of matching algorithm ids in discovery order
*/
public static List<String> selectByFamily(AlgorithmFamily family) {
return selectByFamilyAndRoles(family, EnumSet.noneOf(KeyUsage.class));
}
/**
* Returns algorithm ids that contain all the given roles, regardless of family.
*
* @param requireAllRoles set of roles to be present
* @return list of matching algorithm ids in discovery order
*/
public static List<String> selectByRoles(Collection<KeyUsage> requireAllRoles) {
Objects.requireNonNull(requireAllRoles, "requireAllRoles");
List<String> out = new ArrayList<>();
Set<String> ids = CryptoAlgorithms.available();
for (String id : ids) {
CryptoAlgorithm alg = CryptoAlgorithms.require(id);
if (alg.roles().containsAll(requireAllRoles)) {
out.add(id);
}
}
return out;
}
}

View File

@@ -0,0 +1,148 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core;
import conflux.Key;
/**
* Shared typed keys for ephemeral cryptographic parameters.
*
* <p>
* {@code ConfluxKeys} provides strongly typed, namespaced keys for common
* transient values such as IVs, nonces, AAD, and authentication tags. These
* keys are typically used with a keyvalue parameter store (e.g., a
* {@code Map<Key<?>,Object>} or a dedicated context object) to exchange
* per-operation metadata between algorithms and higher layers.
* </p>
*
* <h2>Design goals</h2>
* <ul>
* <li><b>Type safety</b>: each key carries its value type (e.g.,
* {@code Key<byte[]>} vs {@code Key<Integer>}).</li>
* <li><b>Namespacing</b>: all keys include the algorithm identifier in their
* name, preventing collisions when multiple algorithms share a context.</li>
* <li><b>Consistency</b>: avoids ad-hoc string constants; discoverable via
* {@link CryptoAlgorithm#listCapabilities()} and related APIs.</li>
* </ul>
*
* <p>
* Instances are created via static factories; this class cannot be
* instantiated.
* </p>
*
* @since 1.0
*/
public final class ConfluxKeys {
final private static String PREFIX = "crypto.";
private ConfluxKeys() {
}
/**
* Returns a typed key for the initialization vector (IV) of a given algorithm.
*
* <p>
* IVs are required by block cipher modes such as CBC or GCM. Each call produces
* a key namespaced as {@code "crypto.<algoId>.iv"}.
* </p>
*
* @param algoId canonical algorithm identifier
* @return key for IV values, of type {@code byte[]}
*/
public static Key<byte[]> iv(String algoId) {
return Key.of(PREFIX + algoId + ".iv", byte[].class);
}
/**
* Returns a typed key for additional authenticated data (AAD).
*
* <p>
* Used in AEAD schemes such as AES-GCM to bind unencrypted headers into the
* authentication tag. Namespaced as {@code "crypto.<algoId>.aad"}.
* </p>
*
* @param algoId canonical algorithm identifier
* @return key for AAD values, of type {@code byte[]}
*/
public static Key<byte[]> aad(String algoId) {
return Key.of(PREFIX + algoId + ".aad", byte[].class);
}
/**
* Returns a typed key for a nonce value of a given algorithm.
*
* <p>
* Nonces are required by stream ciphers and AEAD modes to ensure uniqueness per
* key. Namespaced as {@code "crypto.<algoId>.nonce"}.
* </p>
*
* @param algoId canonical algorithm identifier
* @return key for nonce values, of type {@code byte[]}
*/
public static Key<byte[]> nonce(String algoId) {
return Key.of(PREFIX + algoId + ".nonce", byte[].class);
}
/**
* Returns a typed key for the authentication tag of a given algorithm.
*
* <p>
* AEAD modes output a tag that must be preserved for decryption/verification.
* Namespaced as {@code "crypto.<algoId>.tag"}.
* </p>
*
* @param algoId canonical algorithm identifier
* @return key for authentication tag values, of type {@code byte[]}
*/
public static Key<byte[]> tag(String algoId) {
return Key.of(PREFIX + algoId + ".tag", byte[].class);
}
/**
* Returns a typed key for the number of authentication tag bits.
*
* <p>
* Some AEAD constructions allow truncated tags (e.g., 96-bit or 64-bit). This
* key represents the chosen bit-length. Namespaced as
* {@code "crypto.<algoId>.tagBits"}.
* </p>
*
* @param algoId canonical algorithm identifier
* @return key for tag length values, of type {@code Integer}
*/
public static Key<Integer> tagBits(String algoId) {
return Key.of(PREFIX + algoId + ".tagBits", Integer.class);
}
}

View File

@@ -0,0 +1,846 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import javax.crypto.SecretKey;
import zeroecho.core.context.CryptoContext;
import zeroecho.core.err.UnsupportedRoleException;
import zeroecho.core.err.UnsupportedSpecException;
import zeroecho.core.spec.AlgorithmKeySpec;
import zeroecho.core.spec.ContextSpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
import zeroecho.core.spi.ContextConstructorKS;
import zeroecho.core.spi.SymmetricKeyBuilder;
/**
* Abstract base class for all cryptographic algorithm definitions in ZeroEcho.
* <p>
* A {@code CryptoAlgorithm} declares:
* <ul>
* <li>Metadata: an identifier, display name, provider, and priority.</li>
* <li>Capabilities: declared features such as AEAD, streaming, or deterministic
* signatures.</li>
* <li>Roles: supported {@link KeyUsage} operations (e.g., ENCRYPT, SIGN) bound
* to concrete {@link CryptoContext} constructors.</li>
* <li>Key builders: factories for symmetric and asymmetric key material via
* {@link SymmetricKeyBuilder} and {@link AsymmetricKeyBuilder}.</li>
* </ul>
*
* <h2>Metadata</h2> Each algorithm instance is uniquely identified by
* {@link #id()}, a canonical string such as {@code "AES/GCM/NOPADDING"} or
* {@code "Ed25519"}. A human-readable {@link #displayName()} is provided for
* logs and diagnostics.
*
* <h2>Capabilities</h2> Algorithms may declare extra {@link Capability} flags
* that can be inspected by higher layers. This allows adaptive protocols to
* choose the right primitive (e.g., preferring AEAD over raw block ciphers).
*
* <h2>Roles and contexts</h2> Each algorithm may support multiple
* {@link KeyUsage} roles. For each role, the algorithm binds a key type,
* context type, and optional {@link ContextSpec}. When
* {@link #create(KeyUsage, Key, ContextSpec)} is called:
* <ol>
* <li>The binding for the role is located.</li>
* <li>The supplied key and spec are validated against the expected types.</li>
* <li>A new {@link CryptoContext} is constructed using the registered
* factory.</li>
* </ol>
*
* <h2>Key builders</h2>
* <ul>
* <li>Asymmetric builders: registered via {@link #registerAsymmetricKeyBuilder}
* and accessed through {@link #asymmetricKeyBuilder(Class)} or convenience
* methods like {@link #generateKeyPair(AlgorithmKeySpec)}.</li>
* <li>Symmetric builders: registered via {@link #registerSymmetricKeyBuilder}
* and accessed through {@link #symmetricKeyBuilder(Class)} or convenience
* methods like {@link #generateSecret(AlgorithmKeySpec)}.</li>
* </ul>
*
* <h2>Provider model</h2> Each algorithm belongs to a {@code providerName},
* allowing multiple providers (e.g., JCA, BouncyCastle, ZeroEcho-native) to
* coexist. {@link #priority()} can be used to prefer one provider over another
* when resolving duplicates.
*
* <h2>Thread safety</h2> {@code CryptoAlgorithm} instances are immutable once
* constructed and are safe to share across threads. The created
* {@link CryptoContext} instances, however, are not necessarily thread-safe.
*
* <p>
* <b>Security note:</b> Algorithms must enforce strong validation of keys and
* specs during registration and {@link #create(KeyUsage, Key, ContextSpec)} to
* prevent downgrade or misuse attacks.
* </p>
*
* @since 1.0
*/
public abstract class CryptoAlgorithm { // NOPMD
private final String _id;
private final String _displayName;
private final int _priority;
private final String _providerName;
private final List<Capability> capabilities = new ArrayList<>();
private final Map<KeyUsage, List<RoleBinding<?, ?, ?>>> ctxBindings = new EnumMap<>(KeyUsage.class);
private final Map<Class<? extends AlgorithmKeySpec>, AsymEntry<?>> asymBuilders = new HashMap<>();
private final Map<Class<? extends AlgorithmKeySpec>, SymEntry<?>> symBuilders = new HashMap<>();
/**
* Create a new algorithm with default priority and provider.
*
* @param id unique canonical identifier
* @param displayName human-readable name
*/
protected CryptoAlgorithm(String id, String displayName) {
this(id, displayName, 0, "default");
}
/**
* Create a new algorithm with default priority and a named provider.
*
* @param id unique canonical identifier
* @param displayName human-readable name
* @param providerName provider or implementation source
*/
protected CryptoAlgorithm(String id, String displayName, String providerName) {
this(id, displayName, 0, providerName);
}
/**
* Create a new algorithm with explicit metadata.
*
* @param id unique canonical identifier
* @param displayName human-readable name
* @param priority preference when multiple providers offer the same
* algorithm
* @param providerName provider or implementation source
*/
protected CryptoAlgorithm(String id, String displayName, int priority, String providerName) {
this._id = Objects.requireNonNull(id, "id must not be null");
this._displayName = Objects.requireNonNull(displayName, "displayName must not be null");
this._priority = priority;
this._providerName = Objects.requireNonNull(providerName, "providerName must not be null");
}
/**
* Returns the canonical identifier of this algorithm.
* <p>
* The identifier is a stable, implementation-independent string such as
* {@code "AES/GCM/NOPADDING"} or {@code "Ed25519"}. It is suitable for
* persistence in configuration files, protocol negotiation, or audit logs.
* <p>
* Unlike {@link #displayName()}, the identifier is not localized and should be
* treated as a primary key across providers.
*
* @return canonical, provider-independent algorithm identifier
*/
public final String id() {
return _id;
}
/**
* Returns a human-readable display name for this algorithm.
* <p>
* This name is intended for logs, error messages, and user interfaces. Unlike
* {@link #id()}, the display name may vary by provider and is not guaranteed to
* be stable across versions.
*
* @return human-friendly algorithm name
*/
public final String displayName() {
return _displayName;
}
/**
* Returns the priority of this algorithm within its provider.
* <p>
* When multiple providers expose the same {@link #id()}, the priority is used
* as a tiebreaker. Higher values indicate stronger preference.
* <p>
* Priority values are advisory; applications may still override selection based
* on policy.
*
* @return numeric provider preference (higher means more preferred)
*/
public int priority() {
return _priority;
}
/**
* Returns the provider that supplies this algorithm implementation.
* <p>
* Typical values include {@code "default"}, {@code "JCA"},
* {@code "BouncyCastle"}, or a project-specific label.
* <p>
* Provider names allow coexistence of multiple implementations of the same
* algorithm identifier.
*
* @return provider or implementation source name
*/
public String providerName() {
return _providerName;
}
/**
* Adds a capability flag to this algorithm.
*
* <p>
* Intended for use by concrete subclasses during construction to advertise
* features (e.g., AEAD support, deterministic signatures). Adding capabilities
* after publication is discouraged as callers may have already inspected them.
* </p>
*
* @param capability non-null capability to add
* @throws NullPointerException if {@code capability} is {@code null}
*/
protected final void addCapability(Capability capability) {
capabilities.add(Objects.requireNonNull(capability, "capability must not be null"));
}
/**
* Returns an immutable view of the algorithms declared capabilities.
*
* @return unmodifiable list of capability flags
*/
public final List<Capability> listCapabilities() {
return Collections.unmodifiableList(capabilities);
}
/**
* Internal binding record connecting a role to its required types and
* constructor.
* <p>
* For a given {@link KeyUsage} role, the binding specifies the expected
* {@link CryptoContext} type, the accepted {@link Key} type, the optional
* {@link ContextSpec} type, a constructor factory, and a default spec supplier.
*
* @param <C> context type
* @param <K> key type
* @param <S> spec type
*/
private static final class RoleBinding<C extends CryptoContext, K extends Key, S extends ContextSpec> {
private final Class<C> ctxType;
private final Class<K> keyType;
private final Class<S> specType;
private final ContextConstructorKS<C, K, S> ctor;
private final Supplier<? extends S> defaultSpec;
private RoleBinding(Class<C> ctxType, Class<K> keyType, Class<S> specType, ContextConstructorKS<C, K, S> ctor,
Supplier<? extends S> defaultSpec) {
this.ctxType = ctxType;
this.keyType = keyType;
this.specType = specType;
this.ctor = ctor;
this.defaultSpec = defaultSpec;
}
private boolean accepts(Key key, ContextSpec spec) {
return keyType.isInstance(key) && (spec == null || specType.isInstance(spec));
}
}
/**
* Binds a role to a concrete context factory and its expected key/spec types.
*
* <p>
* Concrete algorithms call this during construction to declare support for
* specific roles (e.g., {@code ENCRYPT}, {@code VERIFY}). When
* {@link #create(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
* @param ctxType context type to be returned by the factory
* @param keyType key type accepted by the factory
* @param specType spec type accepted by the factory (may be a marker type)
* @param factory constructor that creates a context for (key, spec)
* @param defaultSpec default spec supplier used when {@code spec} is
* {@code null}
* @param <C> context type
* @param <K> key type
* @param <S> spec type
* @throws NullPointerException if any class or factory argument is {@code null}
*/
protected final <C extends CryptoContext, K extends Key, S extends ContextSpec> void bind(KeyUsage role,
Class<C> ctxType, Class<K> keyType, Class<S> specType, ContextConstructorKS<C, K, S> factory,
Supplier<? extends S> defaultSpec) {
ctxBindings.computeIfAbsent(role, r -> new ArrayList<>())
.add(new RoleBinding<>(ctxType, keyType, specType, factory, defaultSpec));
}
/**
* Returns whether this algorithm supports the given role.
*
* @param role a {@link KeyUsage} role
* @return {@code true} if a binding exists, otherwise {@code false}
*/
public final boolean supports(KeyUsage role) {
return ctxBindings.containsKey(role);
}
/**
* Returns the set of roles supported by this algorithm.
*
* @return unmodifiable set of supported {@link KeyUsage} values
*/
public final Set<KeyUsage> roles() {
return Collections.unmodifiableSet(ctxBindings.keySet());
}
/**
* Creates a new {@link CryptoContext} for the given role, key, and optional
* spec.
*
* <p>
* Resolution proceeds as follows:
* </p>
* <ol>
* <li>Locate bindings for {@code role}; if none exist, throw
* {@link UnsupportedRoleException}.</li>
* <li>For each binding, check that {@code key} is an instance of the required
* key type and {@code spec} is either {@code null} or an instance of the
* required spec type.</li>
* <li>If matched, resolve the effective spec: use the provided {@code spec} or
* obtain one from the bindings {@code defaultSpec} supplier.</li>
* <li>Invoke the factory to create a context and verify the returned type
* matches the declared {@code ctxType}.</li>
* </ol>
*
* @param role the intended {@link KeyUsage} for the created context
* @param key key instance compatible with the binding
* @param spec optional context spec; if {@code null}, the bindings default is
* used
* @param <C> context type
* @param <K> key type
* @param <S> spec type
* @return a newly constructed context suitable for the requested role
* @throws UnsupportedRoleException if the algorithm does not support
* {@code role}
* @throws UnsupportedSpecException if no binding accepts the provided key/spec
* @throws IllegalStateException if the factory returns an unexpected context
* type
* @throws IOException if the factory encounters I/O while
* constructing the context
*/
@SuppressWarnings("unchecked")
public final <C extends CryptoContext, K extends Key, S extends ContextSpec> C create(KeyUsage role, K key, S spec)
throws IOException {
List<RoleBinding<?, ?, ?>> list = ctxBindings.get(role);
if (list == null || list.isEmpty()) {
throw new UnsupportedRoleException(_id + " does not support role " + role);
}
for (RoleBinding<?, ?, ?> rb0 : list) {
RoleBinding<C, K, S> rb = (RoleBinding<C, K, S>) rb0;
if (rb.accepts(key, spec)) {
S resolved = (spec != null) ? spec : rb.defaultSpec.get();
C ctx = rb.ctor.create(key, resolved);
// Enforce the declared context type contract:
if (!rb.ctxType.isInstance(ctx)) {
throw new IllegalStateException(_id + " factory returned " + ctx.getClass().getName()
+ " but capability declares " + rb.ctxType.getName());
}
return ctx;
}
}
throw new UnsupportedSpecException(_id + " cannot create for " + role + " with key=" + key.getClass().getName()
+ (spec == null ? " (default spec)" : " and spec=" + spec.getClass().getName()));
}
/**
* Immutable descriptor for an asymmetric builder registered with this
* algorithm.
* <p>
* Used for discovery and documentation (e.g., tool UIs).
* </p>
*/
public static final class AsymBuilderInfo {
public final Class<? extends AlgorithmKeySpec> specType;
public final Object defaultKeySpec;
private AsymBuilderInfo(Class<? extends AlgorithmKeySpec> specType, Object defaultKeySpec) {
this.specType = specType;
this.defaultKeySpec = defaultKeySpec;
}
}
/**
* Internal entry binding a registered asymmetric key builder to its default key
* specification supplier.
*
* <p>
* Each {@code AsymEntry} is keyed by a specific {@link AlgorithmKeySpec}
* subtype. It holds the {@link AsymmetricKeyBuilder} instance capable of
* generating or importing keys for that spec, and an optional supplier that
* provides a safe default spec (if the algorithm wants to support "generate
* with defaults").
* </p>
*
* <h2>Usage</h2>
* <ul>
* <li>Created during calls to
* {@link #registerAsymmetricKeyBuilder(Class, AsymmetricKeyBuilder, Supplier)}.</li>
* <li>Looked up later by {@link #asymmetricKeyBuilder(Class)} and used by
* key-generation/import convenience methods such as
* {@link #generateKeyPair(AlgorithmKeySpec)}.</li>
* </ul>
*
* <h2>Thread-safety</h2> Immutable once constructed; safe to share between
* threads.
*
* @param <S> the type of {@link AlgorithmKeySpec} handled by this entry
*/
private record AsymEntry<S extends AlgorithmKeySpec>(AsymmetricKeyBuilder<S> builder,
Supplier<? extends S> defaultKeySpec) {
/**
* Creates a new binding between a key builder and its optional default spec.
*
* @throws NullPointerException if {@code builder} is {@code null}
*/
AsymEntry {
Objects.requireNonNull(builder, "builder must not be null");
}
}
/**
* Registers an asymmetric key builder for a specific spec type.
*
* <p>
* Concrete algorithms call this during construction. The {@code specType} acts
* as a key for later lookup and must be unique within this algorithm.
* </p>
*
* @param specType the spec class accepted by {@code builder}
* @param builder builder that can generate/import keys for
* {@code specType}
* @param defaultKeySpecOrNull optional supplier for a default spec (may be
* {@code null})
* @param <S> spec type
* @throws NullPointerException if {@code specType} or {@code builder} is
* {@code null}
*/
protected final <S extends AlgorithmKeySpec> void registerAsymmetricKeyBuilder(Class<S> specType,
AsymmetricKeyBuilder<S> builder, Supplier<? extends S> defaultKeySpecOrNull) {
Objects.requireNonNull(specType, "specType must not be null");
asymBuilders.put(specType, new AsymEntry<>(builder, defaultKeySpecOrNull));
}
/**
* Returns the asymmetric key builder associated with the given spec type.
*
* @param specType spec class used as a lookup key
* @param <S> spec type
* @return the registered {@link AsymmetricKeyBuilder}
* @throws IllegalArgumentException if no builder is registered for
* {@code specType}
*/
@SuppressWarnings("unchecked")
public final <S extends AlgorithmKeySpec> AsymmetricKeyBuilder<S> asymmetricKeyBuilder(Class<S> specType) {
AsymEntry<?> e = asymBuilders.get(specType);
if (e == null) {
throw new IllegalArgumentException(_id + " has no asymmetric key builder for " + specType.getName());
}
return (AsymmetricKeyBuilder<S>) e.builder;
}
/**
* Returns metadata about all registered asymmetric builders.
*
* <p>
* The default spec value is best-effort; suppliers may throw, in which case
* {@code defaultKeySpec} is reported as {@code null}.
* </p>
*
* @return immutable list of {@link AsymBuilderInfo} descriptors
*/
public final List<AsymBuilderInfo> asymmetricBuildersInfo() {
List<AsymBuilderInfo> out = new ArrayList<>();
for (Map.Entry<Class<? extends AlgorithmKeySpec>, AsymEntry<?>> e : asymBuilders.entrySet()) {
Object def = null;
if (e.getValue().defaultKeySpec != null) {
try {
def = e.getValue().defaultKeySpec.get();
} catch (Throwable t) { // NOPMD
def = null;
}
}
out.add(new AsymBuilderInfo(e.getKey(), def));
}
return Collections.unmodifiableList(out);
}
/**
* Immutable descriptor for a symmetric key builder registered with this
* algorithm.
*
* <p>
* Each {@code SymBuilderInfo} describes the specification type that a
* {@link SymmetricKeyBuilder} can handle, along with an optional default
* specification object. These descriptors are used for discovery and
* documentation purposes, for example when rendering catalog information in
* tooling or UIs.
* </p>
*
* <h2>Usage</h2>
* <ul>
* <li>Produced by {@link #symmetricBuildersInfo()}.</li>
* <li>Displayed to clients for inspection and documentation, but not used
* directly in cryptographic operations.</li>
* </ul>
*
* <h2>Thread-safety</h2> Being a {@code record}, this type is immutable and
* safe to share between threads.
*
* @param specType the specification type supported by the builder
* @param defaultKeySpec an optional default key specification instance, or
* {@code null} if no default is provided
*/
public record SymBuilderInfo(Class<? extends AlgorithmKeySpec> specType, Object defaultKeySpec) {
}
/**
* Internal entry binding a registered symmetric key builder to its optional
* default key specification supplier.
*
* <p>
* Each {@code SymEntry} is keyed by a specific {@link AlgorithmKeySpec}
* subtype. It holds the {@link SymmetricKeyBuilder} instance capable of
* generating or importing keys for that spec, and a supplier that may produce a
* default spec when none is provided explicitly.
* </p>
*
* <h2>Usage</h2>
* <ul>
* <li>Created during calls to
* {@link #registerSymmetricKeyBuilder(Class, SymmetricKeyBuilder, Supplier)}.</li>
* <li>Looked up internally when methods such as
* {@link #generateSecret(AlgorithmKeySpec)} or
* {@link #importSecret(AlgorithmKeySpec)} are invoked.</li>
* </ul>
*
* <h2>Thread-safety</h2> Immutable and thread-safe by design as a
* {@code record}.
*
* @param builder the builder instance that can create or import keys;
* must not be {@code null}
* @param defaultKeySpec supplier for a default specification, or {@code null}
* if no sensible default exists
* @param <S> the type of {@link AlgorithmKeySpec} handled by this
* entry
*/
private record SymEntry<S extends AlgorithmKeySpec>(SymmetricKeyBuilder<S> builder,
Supplier<? extends S> defaultKeySpec) {
/**
* Compact constructor that enforces non-null builder.
*
* @throws NullPointerException if {@code builder} is {@code null}
*/
SymEntry {
Objects.requireNonNull(builder, "builder must not be null");
}
}
/**
* Registers a symmetric key builder for a specific spec type.
*
* @param specType the spec class accepted by {@code builder}
* @param builder builder that can generate/import keys for
* {@code specType}
* @param defaultKeySpecOrNull optional supplier for a default spec (may be
* {@code null})
* @param <S> spec type
* @throws NullPointerException if {@code specType} or {@code builder} is
* {@code null}
*/
protected final <S extends AlgorithmKeySpec> void registerSymmetricKeyBuilder(Class<S> specType,
SymmetricKeyBuilder<S> builder, Supplier<? extends S> defaultKeySpecOrNull) {
Objects.requireNonNull(specType, "specType must not be null");
symBuilders.put(specType, new SymEntry<>(builder, defaultKeySpecOrNull));
}
/**
* Returns the symmetric key builder associated with the given spec type.
*
* @param specType spec class used as a lookup key
* @param <S> spec type
* @return the registered {@link SymmetricKeyBuilder}
* @throws IllegalArgumentException if no builder is registered for
* {@code specType}
*/
@SuppressWarnings("unchecked")
public final <S extends AlgorithmKeySpec> SymmetricKeyBuilder<S> symmetricKeyBuilder(Class<S> specType) {
SymEntry<?> e = symBuilders.get(specType);
if (e == null) {
throw new IllegalArgumentException(_id + " has no symmetric key builder for " + specType.getName());
}
return (SymmetricKeyBuilder<S>) e.builder;
}
/**
* Returns metadata about all registered symmetric builders.
*
* <p>
* The default spec value is best-effort; suppliers may throw, in which case
* {@code defaultKeySpec} is reported as {@code null}.
* </p>
*
* @return immutable list of {@link SymBuilderInfo} descriptors
*/
public final List<SymBuilderInfo> symmetricBuildersInfo() {
List<SymBuilderInfo> out = new ArrayList<>();
for (Map.Entry<Class<? extends AlgorithmKeySpec>, SymEntry<?>> e : symBuilders.entrySet()) {
Object def = null;
if (e.getValue().defaultKeySpec != null) {
try {
def = e.getValue().defaultKeySpec.get();
} catch (Throwable t) { // NOPMD
def = null;
}
}
out.add(new SymBuilderInfo(e.getKey(), def));
}
return Collections.unmodifiableList(out);
}
/**
* Generates a fresh symmetric {@link SecretKey} using the registered builder
* for {@code spec}.
*
* @param spec algorithm-specific key specification (must match a registered
* symmetric builder)
* @param <S> spec type
* @return newly generated secret key
* @throws NullPointerException if {@code spec} is {@code null}
* @throws IllegalArgumentException if no symmetric builder is registered for
* {@code spec.getClass()}
* @throws GeneralSecurityException if key generation fails or parameters are
* unsupported
*/
@SuppressWarnings("unchecked")
public final <S extends AlgorithmKeySpec> SecretKey generateSecret(S spec) throws GeneralSecurityException {
Objects.requireNonNull(spec, "spec must not be null");
SymmetricKeyBuilder<S> b = symmetricKeyBuilder((Class<S>) spec.getClass());
return b.generateSecret(spec);
}
/**
* Imports an existing symmetric {@link SecretKey} using the registered builder
* for {@code spec}.
*
* @param spec algorithm-specific key specification including raw
* material/format
* @param <S> spec type
* @return wrapped secret key validated against the spec
* @throws NullPointerException if {@code spec} is {@code null}
* @throws IllegalArgumentException if no symmetric builder is registered for
* {@code spec.getClass()}
* @throws GeneralSecurityException if the material is invalid or does not match
* the algorithm
*/
@SuppressWarnings("unchecked")
public final <S extends AlgorithmKeySpec> SecretKey importSecret(S spec) throws GeneralSecurityException {
Objects.requireNonNull(spec, "spec must not be null");
SymmetricKeyBuilder<S> b = symmetricKeyBuilder((Class<S>) spec.getClass());
return b.importSecret(spec);
}
/**
* Attempts to generate a {@link KeyPair} using the given asymmetric builder's
* default key spec. This method is fully generic and avoids raw types by
* capturing the concrete spec type parameter.
*
* @param specType the spec class label used for diagnostics
* @param entry the typed asymmetric builder entry
* @param <S> concrete {@link AlgorithmKeySpec} type
* @return a freshly generated key pair
* @throws GeneralSecurityException if the supplier or builder fails
*/
private <S extends AlgorithmKeySpec> KeyPair tryGenerateWithDefault(Class<? extends AlgorithmKeySpec> specType,
AsymEntry<S> entry) throws GeneralSecurityException {
if (entry.defaultKeySpec == null) {
throw new GeneralSecurityException("no default spec supplier");
}
final S spec;
try {
spec = entry.defaultKeySpec.get();
} catch (Throwable t) { // NOPMD
throw new GeneralSecurityException("defaultSpec supplier failed for " + specType.getSimpleName() + ": "
+ t.getClass().getSimpleName() + ": " + t.getMessage(), t);
}
if (spec == null) {
throw new GeneralSecurityException("defaultSpec supplier returned null for " + specType.getSimpleName());
}
// No raw types here: S is captured from entry.
return entry.builder.generateKeyPair(spec);
}
/**
* Generates a fresh {@link KeyPair} using the first asymmetric builder that
* successfully provides a default key specification.
*
* <p>
* This convenience method iterates over all registered asymmetric key builders
* that declare a non-null default {@link AlgorithmKeySpec} supplier. For each,
* it attempts to obtain the default spec and generate a key pair. If a builder
* fails (e.g., the builder only supports import or rejects the parameters), the
* method records the failure and continues with the next candidate.
* </p>
*
* <h4>Example</h4> <pre>{@code
* CryptoAlgorithm algo = CryptoAlgorithms.require("Ed25519");
* KeyPair kp = algo.generateKeyPair();
* }</pre>
*
* @return a newly generated key pair using a default spec from one of the
* registered asymmetric builders
* @throws IllegalStateException if no builder declares a default spec
* supplier
* @throws GeneralSecurityException if all candidate builders fail to generate a
* key pair; the exception message details
* individual causes
*/
public final KeyPair generateKeyPair() throws GeneralSecurityException {
StringBuilder reasons = new StringBuilder(128);
boolean attempted = false;
for (Map.Entry<Class<? extends AlgorithmKeySpec>, AsymEntry<?>> e : asymBuilders.entrySet()) {
AsymEntry<?> entry = e.getValue();
if (entry.defaultKeySpec == null) {
continue;
}
attempted = true;
try {
// Wildcard capture lets the compiler infer <S> without casts.
return tryGenerateWithDefault(e.getKey(), entry);
} catch (GeneralSecurityException ex) {
reasons.append(" - ").append(e.getKey().getSimpleName()).append(": ")
.append(ex.getClass().getSimpleName()).append(": ").append(String.valueOf(ex.getMessage()))
.append('\n');
// keep trying other builders
}
}
if (!attempted) {
throw new IllegalStateException(_id + " has no default asymmetric key spec");
}
throw new GeneralSecurityException(
_id + " failed to generate a default key pair. Reasons:\n" + reasons.toString().trim());
}
/**
* Generates a fresh {@link KeyPair} using the registered asymmetric builder for
* {@code spec}.
*
* @param spec algorithm-specific key specification (must match a registered
* asymmetric builder)
* @param <S> spec type
* @return newly generated key pair
* @throws NullPointerException if {@code spec} is {@code null}
* @throws IllegalArgumentException if no asymmetric builder is registered for
* {@code spec.getClass()}
* @throws GeneralSecurityException if key generation fails or parameters are
* unsupported
*/
@SuppressWarnings("unchecked")
public final <S extends AlgorithmKeySpec> KeyPair generateKeyPair(S spec) throws GeneralSecurityException {
Objects.requireNonNull(spec, "spec must not be null");
AsymmetricKeyBuilder<S> b = asymmetricKeyBuilder((Class<S>) spec.getClass());
return b.generateKeyPair(spec);
}
/**
* Imports a {@link PublicKey} using the registered asymmetric builder for
* {@code spec}.
*
* @param spec algorithm-specific key specification including encoded public
* material/format
* @param <S> spec type
* @return wrapped public key validated against the spec
* @throws NullPointerException if {@code spec} is {@code null}
* @throws IllegalArgumentException if no asymmetric builder is registered for
* {@code spec.getClass()}
* @throws GeneralSecurityException if the material is invalid or does not match
* the algorithm
*/
@SuppressWarnings("unchecked")
public final <S extends AlgorithmKeySpec> PublicKey importPublic(S spec) throws GeneralSecurityException {
Objects.requireNonNull(spec, "spec must not be null");
AsymmetricKeyBuilder<S> b = asymmetricKeyBuilder((Class<S>) spec.getClass());
return b.importPublic(spec);
}
/**
* Imports a {@link PrivateKey} using the registered asymmetric builder for
* {@code spec}.
*
* @param spec algorithm-specific key specification including encoded private
* material/format
* @param <S> spec type
* @return wrapped private key validated against the spec
* @throws NullPointerException if {@code spec} is {@code null}
* @throws IllegalArgumentException if no asymmetric builder is registered for
* {@code spec.getClass()}
* @throws GeneralSecurityException if the material is invalid or does not match
* the algorithm
*/
@SuppressWarnings("unchecked")
public final <S extends AlgorithmKeySpec> PrivateKey importPrivate(S spec) throws GeneralSecurityException {
Objects.requireNonNull(spec, "spec must not be null");
AsymmetricKeyBuilder<S> b = asymmetricKeyBuilder((Class<S>) spec.getClass());
return b.importPrivate(spec);
}
}

View File

@@ -0,0 +1,609 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import javax.crypto.SecretKey;
import zeroecho.core.audit.AuditListener;
import zeroecho.core.audit.AuditedContexts;
import zeroecho.core.context.CryptoContext;
import zeroecho.core.context.DigestContext;
import zeroecho.core.context.EncryptionContext;
import zeroecho.core.context.KemContext;
import zeroecho.core.context.MacContext;
import zeroecho.core.context.SignatureContext;
import zeroecho.core.err.UnsupportedRoleException;
import zeroecho.core.err.UnsupportedSpecException;
import zeroecho.core.policy.CryptoPolicy;
import zeroecho.core.spec.AlgorithmKeySpec;
import zeroecho.core.spec.ContextSpec;
/**
* Static façade and registry for {@link CryptoAlgorithm} providers.
*
* <p>
* {@code CryptoAlgorithms} discovers algorithms via {@link ServiceLoader} and
* exposes:
* </p>
* <ul>
* <li>a registry from canonical algorithm id to implementation,</li>
* <li>policy hooks that validate requested operations before contexts are
* created,</li>
* <li>global audit wiring (listener + wrapping mode), and</li>
* <li>convenience methods for context creation and key generation/import.</li>
* </ul>
*
* <h2>Discovery &amp; identity</h2> Implementations register themselves using
* the Java SPI for {@link CryptoAlgorithm}. If multiple providers advertise the
* same {@linkplain CryptoAlgorithm#id() id}, the registry throws at startup to
* avoid ambiguous resolution.
*
* <h2>Policy</h2> The active {@link CryptoPolicy} is consulted before any
* context is created. Policies can deny weak parameters, enforce key-usage
* separation, or restrict algorithms. If {@link #setPolicy(CryptoPolicy)} is
* never called or is set to {@code null}, a permissive policy is used.
*
* <h2>Auditing</h2> All key lifecycle events and context creation can be
* reported to a global {@link AuditListener}. The {@link AuditMode} determines
* whether contexts are wrapped with auditing proxies or relied upon to emit
* events directly.
*
* <h2>Thread-safety</h2> The registry map and global hooks are safe to read
* concurrently. Hooks are backed by {@code volatile} fields and can be swapped
* at runtime; there is no global lock.
*
* @since 1.0
*/
public final class CryptoAlgorithms {
private static final Map<String, CryptoAlgorithm> BY_ID;
private static volatile CryptoPolicy<ContextSpec, Key> POLICY = CryptoPolicy.permissive(); // NOPMD
private static volatile AuditListener AUDIT = AuditListener.noop(); // NOPMD
private static volatile AuditMode AUDIT_MODE = AuditMode.OFF; // NOPMD
private CryptoAlgorithms() {
}
static {
Map<String, CryptoAlgorithm> m = new HashMap<>();
for (CryptoAlgorithm a : ServiceLoader.load(CryptoAlgorithm.class)) {
CryptoAlgorithm prev = m.put(a.id(), a);
if (prev != null) {
throw new IllegalStateException("Duplicate algorithm id: " + a.id());
}
}
BY_ID = Collections.unmodifiableMap(m);
}
/**
* Returns the set of available algorithm identifiers discovered via
* {@link ServiceLoader}.
*
* <p>
* The returned set is backed by an unmodifiable registry snapshot. Use these
* identifiers with {@link #require(String)} or the convenience methods below.
* </p>
*
* @return unmodifiable set of canonical algorithm ids
*/
public static Set<String> available() {
return BY_ID.keySet();
}
/**
* Looks up an algorithm implementation by its canonical identifier.
*
* <p>
* If the id is unknown, an {@link IllegalArgumentException} is thrown. This
* method is preferred over direct access to ensure consistent error handling
* and to centralize future selection logic.
* </p>
*
* @param id canonical algorithm identifier (e.g., {@code "AES/GCM"} or
* {@code "Ed25519"})
* @return the corresponding {@link CryptoAlgorithm} implementation
* @throws IllegalArgumentException if no algorithm is registered under
* {@code id}
*/
public static CryptoAlgorithm require(String id) {
CryptoAlgorithm a = BY_ID.get(id);
if (a == null) {
throw new IllegalArgumentException("Unknown algorithm id: " + id);
}
return a;
}
/**
* Sets the global cryptographic policy applied before any context creation.
*
* <p>
* Pass {@code null} to revert to a permissive policy. Policies should be fast
* and side-effect free; they are invoked on every
* {@link #create(String, KeyUsage, Key, ContextSpec)} call.
* </p>
*
* @param p policy to install, or {@code null} to use
* {@link CryptoPolicy#permissive()}
*/
public static void setPolicy(CryptoPolicy<ContextSpec, Key> p) {
POLICY = (p == null ? CryptoPolicy.<ContextSpec, Key>permissive() : p);
}
/**
* Sets the global {@link AuditListener}.
*
* <p>
* Pass {@code null} to disable custom auditing (a no-op listener will be
* installed). The listener may be invoked by context proxies (in
* {@link AuditMode#WRAP}) and by the convenience key factory methods below.
* </p>
*
* @param l listener instance or {@code null} for a no-op listener
*/
public static void setAuditListener(AuditListener l) {
AUDIT = (l == null ? AuditListener.noop() : l);
}
/**
* Returns the current global {@link AuditListener}.
*
* @return the active audit listener (never {@code null})
*/
public static AuditListener audit() {
return AUDIT;
}
/**
* Declares how auditing is applied to cryptographic contexts.
*
* <p>
* The {@code AuditMode} controls whether contexts created by
* {@link CryptoAlgorithms#create(String, KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}
* are wrapped in auditing proxies or whether auditing is delegated entirely to
* the caller.
* </p>
*
* <h2>Modes</h2>
* <ul>
* <li>{@link #OFF} - No automatic wrapping of contexts (default). Only explicit
* events triggered at creation are emitted; no per-operation auditing is
* injected.</li>
*
* <li>{@link #WRAP} - Supported contexts are wrapped in dynamic proxies that
* emit additional stream-level and per-operation auditing events. Creation
* events originate from the proxy rather than the factory method.</li>
*
* <li>{@link #MANUAL} - No automatic wrapping and no automatic event emission.
* The caller is fully responsible for invoking audit methods (e.g.,
* {@link CryptoAlgorithms#audit()}) at the appropriate times.</li>
* </ul>
*
* @since 1.0
*/
public enum AuditMode {
/**
* No automatic wrapping of contexts (default).
*
* <p>
* Only explicit events emitted here (e.g.,
* {@link AuditListener#onContextCreated}) are sent to the listener;
* stream-level or per-operation auditing is not injected.
* </p>
*/
OFF,
/**
* Wraps supported contexts in dynamic proxies that emit stream-level auditing.
*
* <p>
* In this mode, creation events are emitted by the proxy rather than here, and
* subsequent operations (e.g., updates, finalization) may also be audited
* depending on the proxy implementation.
* </p>
*/
WRAP,
/**
* No wrapping and no automatic events.
*
* <p>
* The caller is responsible for emitting all relevant audit events via the
* {@link #audit()} listener.
* </p>
*/
MANUAL
}
/**
* Sets the auditing mode for subsequently created contexts.
*
* <p>
* Passing {@code null} resets the mode to {@link AuditMode#OFF}.
* </p>
*
* @param mode desired auditing strategy or {@code null} for {@code OFF}
*/
public static void setAuditMode(AuditMode mode) {
AUDIT_MODE = (mode == null ? AuditMode.OFF : mode);
}
/**
* Returns the current auditing mode.
*
* @return active {@link AuditMode}; never {@code null}
*/
public static AuditMode getAuditMode() {
return AUDIT_MODE;
}
/**
* Creates a {@link CryptoContext} for the given algorithm id and role, applying
* policy validation and optional auditing/wrapping.
*
* <p>
* Flow:
* </p>
* <ol>
* <li>Policy validation via
* {@link CryptoPolicy#validate(String, KeyUsage, Key, ContextSpec)}.</li>
* <li>Algorithm resolution via {@link #require(String)} and context
* construction via
* {@link CryptoAlgorithm#create(KeyUsage, Key, ContextSpec)}.</li>
* <li>Auditing behavior based on {@link #getAuditMode()}:
* <ul>
* <li>{@link AuditMode#OFF}/{@link AuditMode#MANUAL}: emit a creation event
* immediately via
* {@link AuditListener#onContextCreated(String, String, KeyUsage, Key, ContextSpec)}.</li>
* <li>{@link AuditMode#WRAP}: return a proxy (where supported) that emits
* creation and stream-level events; unknown context types are returned
* unwrapped.</li>
* </ul>
* </li>
* </ol>
*
* @param id canonical algorithm identifier
* @param role desired {@link KeyUsage} (e.g., ENCRYPT, VERIFY)
* @param key key instance for the role
* @param spec optional context specification; may be {@code null} to use
* algorithm defaults
* @param <C> context type
* @param <K> key type
* @param <S> spec type
* @return a context ready for use; may be a proxy if {@link AuditMode#WRAP} is
* active
* @throws IOException if the underlying algorithm fails to create
* a context
* @throws IllegalArgumentException if {@code id} is unknown
* @throws UnsupportedRoleException if the algorithm does not support
* {@code role}
* @throws UnsupportedSpecException if the provided key/spec are incompatible
* with the role
*/
public static <C extends CryptoContext, K extends Key, S extends ContextSpec> C create(String id, KeyUsage role,
K key, S spec) throws IOException {
POLICY.validate(id, role, key, spec);
CryptoAlgorithm algo = require(id);
C ctx = algo.create(role, key, spec);
// In WRAP mode, the proxy will emit creation metadata/events.
if (AUDIT_MODE != AuditMode.WRAP) {
AUDIT.onContextCreated(algo.id(), algo.providerName(), role, key, spec);
}
if (AUDIT_MODE == AuditMode.WRAP) {
final AuditListener listener = AUDIT; // pass through the global listener
if (ctx instanceof SignatureContext) {
@SuppressWarnings("unchecked")
C out = (C) AuditedContexts.wrap(ctx, listener, role);
return out;
} else if (ctx instanceof EncryptionContext) {
@SuppressWarnings("unchecked")
C out = (C) AuditedContexts.wrap(ctx, listener, role);
return out;
} else if (ctx instanceof KemContext) {
@SuppressWarnings("unchecked")
C out = (C) AuditedContexts.wrap(ctx, listener, role);
return out;
} else if (ctx instanceof DigestContext) {
@SuppressWarnings("unchecked")
C out = (C) AuditedContexts.wrap(ctx, listener, role);
return out;
} else if (ctx instanceof MacContext) {
@SuppressWarnings("unchecked")
C out = (C) AuditedContexts.wrap(ctx, listener, role);
return out;
}
// Unknown context type: return as-is (no wrapping).
}
return ctx;
}
/**
* Creates a {@link CryptoContext} using the algorithms default spec for the
* role.
*
* <p>
* Equivalent to {@code create(id, role, key, null)}.
* </p>
*
* @param id canonical algorithm identifier
* @param role desired {@link KeyUsage}
* @param key key instance for the role
* @param <C> context type
* @param <K> key type
* @return a context ready for use
* @throws IOException if the underlying algorithm fails to create
* a context
* @throws IllegalArgumentException if {@code id} is unknown
* @throws UnsupportedRoleException if the algorithm does not support
* {@code role}
*/
public static <C extends CryptoContext, K extends Key> C create(String id, KeyUsage role, K key)
throws IOException {
return create(id, role, key, null);
}
/**
* Generates a fresh asymmetric {@link KeyPair} for the given algorithm id and
* spec.
*
* <p>
* Emits
* {@link AuditListener#onKeyGenerated(String, String, AlgorithmKeySpec, KeyPair)}
* on success.
* </p>
*
* @param id canonical algorithm identifier
* @param spec algorithm-specific key specification
* @param <S> spec type
* @return newly generated key pair
* @throws GeneralSecurityException if key generation fails
* @throws IllegalArgumentException if {@code id} is unknown or the spec is
* unsupported
*/
public static <S extends AlgorithmKeySpec> KeyPair keyPair(String id, S spec) throws GeneralSecurityException {
CryptoAlgorithm algo = require(id);
@SuppressWarnings("unchecked")
KeyPair kp = algo.asymmetricKeyBuilder((Class<S>) spec.getClass()).generateKeyPair(spec);
AUDIT.onKeyGenerated(algo.id(), algo.providerName(), spec, kp);
return kp;
}
/**
* Imports a {@link PublicKey} using the algorithms registered asymmetric
* builder.
*
* <p>
* Emits {@link AuditListener#onKeyBuilt(String, String, AlgorithmKeySpec, Key)}
* on success.
* </p>
*
* @param id canonical algorithm identifier
* @param spec algorithm-specific key specification containing encoded public
* material
* @param <S> spec type
* @return imported public key
* @throws GeneralSecurityException if import fails or material is invalid
* @throws IllegalArgumentException if {@code id} is unknown or the spec is
* unsupported
*/
public static <S extends AlgorithmKeySpec> PublicKey publicKey(String id, S spec) throws GeneralSecurityException {
CryptoAlgorithm algo = require(id);
@SuppressWarnings("unchecked")
PublicKey k = algo.asymmetricKeyBuilder((Class<S>) spec.getClass()).importPublic(spec);
AUDIT.onKeyBuilt(algo.id(), algo.providerName(), spec, k);
return k;
}
/**
* Imports a {@link PrivateKey} using the algorithms registered asymmetric
* builder.
*
* <p>
* Emits {@link AuditListener#onKeyBuilt(String, String, AlgorithmKeySpec, Key)}
* on success.
* </p>
*
* @param id canonical algorithm identifier
* @param spec algorithm-specific key specification containing encoded private
* material
* @param <S> spec type
* @return imported private key
* @throws GeneralSecurityException if import fails or material is invalid
* @throws IllegalArgumentException if {@code id} is unknown or the spec is
* unsupported
*/
public static <S extends AlgorithmKeySpec> PrivateKey privateKey(String id, S spec)
throws GeneralSecurityException {
CryptoAlgorithm algo = require(id);
@SuppressWarnings("unchecked")
PrivateKey k = algo.asymmetricKeyBuilder((Class<S>) spec.getClass()).importPrivate(spec);
AUDIT.onKeyBuilt(algo.id(), algo.providerName(), spec, k);
return k;
}
/**
* Imports a symmetric {@link SecretKey} using the algorithms registered
* builder.
*
* <p>
* Emits {@link AuditListener#onKeyBuilt(String, String, AlgorithmKeySpec, Key)}
* on success.
* </p>
*
* @param id canonical algorithm identifier
* @param spec algorithm-specific key specification containing raw/encoded
* material
* @param <S> spec type
* @return imported secret key
* @throws GeneralSecurityException if import fails or material is invalid
* @throws IllegalArgumentException if {@code id} is unknown or the spec is
* unsupported
*/
public static <S extends AlgorithmKeySpec> SecretKey secretKey(String id, S spec) throws GeneralSecurityException {
CryptoAlgorithm algo = require(id);
@SuppressWarnings("unchecked")
SecretKey k = algo.symmetricKeyBuilder((Class<S>) spec.getClass()).importSecret(spec);
AUDIT.onKeyBuilt(algo.id(), algo.providerName(), spec, k);
return k;
}
/**
* Attempts to destroy a key via the JDK {@code Destroyable} interface.
*
* <p>
* If destruction succeeds,
* {@link AuditListener#onKeyDestroyed(String, String, Key)} is emitted. Any
* exceptions from {@code destroy()} are swallowed; the method returns
* {@code false} when destruction did not occur.
* </p>
*
* @param algoId algorithm identifier used for audit metadata
* @param provider provider name used for audit metadata
* @param key key to destroy
* @return {@code true} if the key reported destroyed, {@code false} otherwise
*/
public static boolean destroyKey(String algoId, String provider, Key key) {
boolean destroyed = false;
try {
if (key instanceof javax.security.auth.Destroyable) {
javax.security.auth.Destroyable d = (javax.security.auth.Destroyable) key;
if (!d.isDestroyed()) {
d.destroy();
destroyed = true;
}
}
} catch (Exception ignored) { // NOPMD
// swallow and report via audit only if destroyed
}
if (destroyed) {
AUDIT.onKeyDestroyed(algoId, provider, key);
}
return destroyed;
}
/**
* Convenience wrapper for
* {@link CryptoAlgorithm#generateSecret(AlgorithmKeySpec)}.
*
* @param id canonical algorithm identifier
* @param spec algorithm-specific key specification
* @param <S> spec type
* @return newly generated secret key
* @throws GeneralSecurityException if key generation fails
* @throws IllegalArgumentException if {@code id} is unknown
*/
public static <S extends AlgorithmKeySpec> SecretKey generateSecret(String id, S spec)
throws GeneralSecurityException {
return require(id).generateSecret(spec);
}
/**
* Convenience wrapper for
* {@link CryptoAlgorithm#generateKeyPair(AlgorithmKeySpec)}.
*
* @param id canonical algorithm identifier
* @param spec algorithm-specific key specification
* @param <S> spec type
* @return newly generated key pair
* @throws GeneralSecurityException if key generation fails
* @throws IllegalArgumentException if {@code id} is unknown
*/
public static <S extends AlgorithmKeySpec> KeyPair generateKeyPair(String id, S spec)
throws GeneralSecurityException {
return require(id).generateKeyPair(spec);
}
/**
* Convenience wrapper for
* {@link CryptoAlgorithm#importPublic(AlgorithmKeySpec)}.
*
* @param id canonical algorithm identifier
* @param spec algorithm-specific key specification
* @param <S> spec type
* @return imported public key
* @throws GeneralSecurityException if import fails
* @throws IllegalArgumentException if {@code id} is unknown
*/
public static <S extends AlgorithmKeySpec> PublicKey importPublic(String id, S spec)
throws GeneralSecurityException {
return require(id).importPublic(spec);
}
/**
* Convenience wrapper for
* {@link CryptoAlgorithm#importPrivate(AlgorithmKeySpec)}.
*
* @param id canonical algorithm identifier
* @param spec algorithm-specific key specification
* @param <S> spec type
* @return imported private key
* @throws GeneralSecurityException if import fails
* @throws IllegalArgumentException if {@code id} is unknown
*/
public static <S extends AlgorithmKeySpec> PrivateKey importPrivate(String id, S spec)
throws GeneralSecurityException {
return require(id).importPrivate(spec);
}
/**
* Convenience wrapper for
* {@link CryptoAlgorithm#importSecret(AlgorithmKeySpec)}.
*
* @param id canonical algorithm identifier
* @param spec algorithm-specific key specification
* @param <S> spec type
* @return imported secret key
* @throws GeneralSecurityException if import fails
* @throws IllegalArgumentException if {@code id} is unknown
*/
public static <S extends AlgorithmKeySpec> SecretKey importSecret(String id, S spec)
throws GeneralSecurityException {
return require(id).importSecret(spec);
}
}

View File

@@ -0,0 +1,322 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import zeroecho.core.annotation.Describable;
import zeroecho.core.annotation.DisplayName;
/**
* Immutable snapshot of discovered {@link CryptoAlgorithm} implementations,
* with utilities to validate and serialize the catalog.
*
* <p>
* {@code CryptoCatalog} is a lightweight registry built at a point in time via
* {@link #load()}. It collects algorithms published through the Java SPI for
* {@link CryptoAlgorithm}, ensures identifier uniqueness, and exposes:
* </p>
*
* <ul>
* <li>{@link #validate()} — sanity checks that each algorithm publishes at
* least one capability or key builder,</li>
* <li>{@link #toJson()} — a compact JSON description of algorithms,
* capabilities, and registered key builders, and</li>
* <li>{@link #toXml()} — an XML rendition of the same data.</li>
* </ul>
*
* <h2>Identity and uniqueness</h2> Algorithm ids are treated as canonical keys.
* If two providers expose the same {@linkplain CryptoAlgorithm#id() id}, the
* catalog build fails with {@link IllegalStateException}.
*
* <h2>Immutability &amp; thread-safety</h2> After construction, the internal
* map is unmodifiable and safe to share across threads. This class performs no
* lazy discovery or on-demand mutation.
*
* <h2>Serialization formats</h2>
* <ul>
* <li><b>JSON</b>: produced by {@link #toJson()} as a single object with an
* {@code algorithms} array; trivial string escaping is applied.</li>
* <li><b>XML</b>: produced by {@link #toXml()} with a {@code <cryptoCatalog>}
* root; {@code &amp;&lt;&gt;} escaping is applied to element text and
* attributes.</li>
* </ul>
*
* <p>
* <b>Note:</b> Default spec / key-spec values shown in outputs are derived from
* {@code Supplier}s registered by algorithms. Suppliers may compute labels or
* return lightweight descriptors; their intent is documentation, not
* roundtripping.
* </p>
*
* @since 1.0
*/
public final class CryptoCatalog {
private final Map<String, CryptoAlgorithm> algos;
private CryptoCatalog(Map<String, CryptoAlgorithm> algos) {
this.algos = algos;
}
/**
* Discovers {@link CryptoAlgorithm} implementations via {@link ServiceLoader}
* and returns an immutable catalog snapshot.
*
* <p>
* During loading, algorithm ids are checked for uniqueness. A duplicate id
* results in an {@link IllegalStateException} to prevent ambiguous resolution.
* </p>
*
* @return an immutable {@code CryptoCatalog} with all discovered algorithms
* @throws IllegalStateException if two providers declare the same algorithm id
*/
public static CryptoCatalog load() {
Map<String, CryptoAlgorithm> m = new HashMap<>();
ServiceLoader.load(CryptoAlgorithm.class).forEach(a -> {
CryptoAlgorithm prev = m.put(a.id(), a);
if (prev != null) {
throw new IllegalStateException("Duplicate algorithm id: " + a.id());
}
});
return new CryptoCatalog(Collections.unmodifiableMap(m));
}
/**
* Verifies that each algorithm in this catalog exposes at least one capability
* or key builder.
*
* <p>
* This is a sanity check for provider completeness. If an algorithm publishes
* neither {@link CryptoAlgorithm#listCapabilities() capabilities} nor
* asymmetric/symmetric key builders, the validation fails.
* </p>
*
* @throws IllegalStateException if any algorithm has no capabilities and no key
* builders
*/
public void validate() {
StringBuilder sb = null;
for (CryptoAlgorithm a : algos.values()) {
boolean hasCaps = !a.listCapabilities().isEmpty();
boolean hasAsym = !a.asymmetricBuildersInfo().isEmpty();
boolean hasSym = !a.symmetricBuildersInfo().isEmpty();
if (!hasCaps && !hasAsym && !hasSym) {
if (sb == null) {
sb = new StringBuilder(50 /* minimal record size */ * 6 /* suggested avg of error records */); // NOPMD
}
sb.append("Algorithm ").append(a.id()).append(" has no capabilities nor key builders.\n");
}
}
if (sb != null) {
throw new IllegalStateException(sb.toString());
}
}
private static String labelOf(Object o) {
if (o == null) {
return "null";
}
if (o instanceof Describable) {
return ((Describable) o).description();
}
Class<?> c = o instanceof Class<?> ? (Class<?>) o : o.getClass();
DisplayName dn = c.getAnnotation(DisplayName.class);
if (dn != null) {
return dn.value();
}
return c.getSimpleName();
}
/**
* 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>
* 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
*/
public String toJson() {
StringBuilder sb = new StringBuilder(4096);
sb.append("{\"algorithms\":[");
boolean firstAlgo = true;
for (CryptoAlgorithm a : algos.values()) {
if (!firstAlgo) {
sb.append(',');
}
firstAlgo = false;
sb.append('{').append(jsonField("id", a.id())).append(',').append(jsonField("displayName", a.displayName()))
.append(",\"capabilities\":[");
boolean fc = true;
for (Capability cap : a.listCapabilities()) {
if (!fc) {
sb.append(',');
}
fc = false;
sb.append('{').append(jsonField("family", cap.family().name())).append(',')
.append(jsonField("role", cap.role().name())).append(',')
.append(jsonField("contextType", cap.contextType().getSimpleName())).append(',')
.append(jsonField("keyType", cap.keyType().getSimpleName())).append(',')
.append(jsonField("specType", cap.specType().getSimpleName())).append(",\"defaultSpec\":")
.append(cap.defaultSpec() == null ? "null" : jsonString(labelOf(cap.defaultSpec().get())))
.append('}');
}
sb.append("],\"asymmetricKeyBuilders\":[");
boolean fa = true;
for (CryptoAlgorithm.AsymBuilderInfo kb : a.asymmetricBuildersInfo()) {
if (!fa) {
sb.append(',');
}
fa = false;
sb.append('{').append(jsonField("specType", kb.specType.getSimpleName())).append(",\"defaultKeySpec\":")
.append(kb.defaultKeySpec == null ? "null" : jsonString(labelOf(kb.defaultKeySpec)))
.append('}');
}
sb.append("],\"symmetricKeyBuilders\":[");
boolean fs = true;
for (CryptoAlgorithm.SymBuilderInfo kb : a.symmetricBuildersInfo()) {
if (!fs) {
sb.append(',');
}
fs = false;
sb.append('{').append(jsonField("specType", kb.specType().getSimpleName()))
.append(",\"defaultKeySpec\":")
.append(kb.defaultKeySpec() == null ? "null" : jsonString(labelOf(kb.defaultKeySpec())))
.append('}');
}
sb.append("]}");
}
sb.append("]}");
return sb.toString();
}
/**
* Serializes the catalog to an XML document.
*
* <p>
* The root element is {@code <cryptoCatalog>}. Each algorithm becomes an
* {@code <algorithm>} element with {@code id} and {@code name} attributes, plus
* nested sections for {@code <capabilities>}, {@code <asymmetricKeyBuilders>},
* and {@code <symmetricKeyBuilders>}.
* </p>
*
* <p>
* Element text and attribute values are escaped for {@code &amp;, &lt;, &gt;}.
* </p>
*
* @return an XML string describing algorithms, capabilities, and key builders
*/
public String toXml() {
StringBuilder sb = new StringBuilder(4096);
sb.append("<cryptoCatalog>");
for (CryptoAlgorithm a : algos.values()) {
sb.append("<algorithm id=\"").append(esc(a.id())).append("\" name=\"").append(esc(a.displayName()))
.append("\"><capabilities>");
for (Capability cap : a.listCapabilities()) {
sb.append("<capability family=\"").append(cap.family().name()).append("\" role=\"")
.append(cap.role().name()).append("\"><contextType>")
.append(esc(cap.contextType().getSimpleName())).append("</contextType><keyType>")
.append(esc(cap.keyType().getSimpleName())).append("</keyType><specType>")
.append(esc(cap.specType().getSimpleName())).append("</specType><defaultSpec>")
.append(esc(labelOf(cap.defaultSpec().get()))).append("</defaultSpec></capability>");
}
sb.append("</capabilities><asymmetricKeyBuilders>");
for (CryptoAlgorithm.AsymBuilderInfo kb : a.asymmetricBuildersInfo()) {
sb.append("<keyBuilder specType=\"").append(esc(kb.specType.getSimpleName()))
.append("\"><defaultKeySpec>")
.append(kb.defaultKeySpec == null ? "" : esc(labelOf(kb.defaultKeySpec)))
.append("</defaultKeySpec></keyBuilder>");
}
sb.append("</asymmetricKeyBuilders><symmetricKeyBuilders>");
for (CryptoAlgorithm.SymBuilderInfo kb : a.symmetricBuildersInfo()) {
sb.append("<keyBuilder specType=\"").append(esc(kb.specType().getSimpleName()))
.append("\"><defaultKeySpec>")
.append(kb.defaultKeySpec() == null ? "" : esc(labelOf(kb.defaultKeySpec())))
.append("</defaultKeySpec></keyBuilder>");
}
sb.append("</symmetricKeyBuilders></algorithm>");
}
sb.append("</cryptoCatalog>");
return sb.toString();
}
private static String jsonField(String name, String value) {
return jsonString(name) + ":" + jsonString(value);
}
private static String jsonString(String v) {
return v == null ? "null" : "\"" + v.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
}
private static String esc(String v) {
return v == null ? "" : v.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
}
}

View File

@@ -0,0 +1,86 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core;
/**
* Declares the intended purpose(s) of a cryptographic key.
* <p>
* Keys should be bound to a specific {@code KeyUsage} at generation or import
* time. Enforcing usage prevents accidental cross-purposes — e.g., reusing a
* signing key for encryption, or using a MAC key for key agreement — which can
* lead to serious vulnerabilities.
*
* <h2>Typical usages</h2>
* <ul>
* <li>{@link #SIGN}/{@link #VERIFY}: digital signature schemes (Ed25519, ECDSA,
* RSA-PSS).</li>
* <li>{@link #ENCRYPT}/{@link #DECRYPT}: confidentiality with symmetric or
* asymmetric encryption.</li>
* <li>{@link #ENCAPSULATE}/{@link #DECAPSULATE}: key encapsulation mechanisms
* (KEM).</li>
* <li>{@link #MAC}: keyed message authentication (e.g., HMAC, KMAC).</li>
* <li>{@link #DIGEST}: unkeyed hashing (e.g., SHA-256, SHA3-512).</li>
* <li>{@link #AGREEMENT}: key agreement (e.g., X25519, ECDH) to derive shared
* secrets.</li>
* </ul>
*
* <p>
* <b>Security note:</b> Many real-world breaches trace back to cryptographic
* keys being used outside their intended purpose. Libraries should validate
* {@code KeyUsage} before performing operations.
* </p>
*
* @since 1.0
*/
public enum KeyUsage {
/** Create digital signatures. */
SIGN,
/** Verify digital signatures. */
VERIFY,
/** Encrypt data for confidentiality. */
ENCRYPT,
/** Decrypt data that was encrypted under the matching key/parameters. */
DECRYPT,
/** Encapsulate a shared secret in a KEM flow. */
ENCAPSULATE,
/** Decapsulate a shared secret in a KEM flow. */
DECAPSULATE,
/** Compute a keyed message authentication code (e.g., HMAC, KMAC). */
MAC,
/** Compute an unkeyed hash (e.g., SHA-256), pipeline-friendly. */
DIGEST,
/** Perform key agreement (e.g., X25519, ECDH). */
AGREEMENT
}

View File

@@ -0,0 +1,185 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core;
import java.security.Key;
import java.util.Objects;
/**
* Sentinel {@link Key} representing the intentional absence of cryptographic
* key material.
*
* <p>
* {@code NullKey} satisfies APIs that require a {@code Key} reference even when
* no secret exists (e.g., unkeyed digests or placeholder capabilities). It
* contains no sensitive data and is safe to log and share.
* </p>
*
* <h2>Intended uses</h2>
* <ul>
* <li>Unkeyed operations such as {@code DIGEST} roles where keys are
* semantically undefined but a {@code Key} parameter is part of a common
* interface.</li>
* <li>Testing and scaffolding where a non-null {@code Key} is required to
* exercise type and flow without provisioning secrets.</li>
* <li>Metadata publication for capabilities that dont depend on key
* material.</li>
* </ul>
*
* <h2>Security properties</h2>
* <ul>
* <li>Contains no secret; {@link #getEncoded()} returns an empty byte
* array.</li>
* <li>Algorithm and format are the literal string {@code "NONE"}.</li>
* <li>All instances are equal by type; prefer the {@link #INSTANCE}
* singleton.</li>
* </ul>
*
* <h2>Thread-safety</h2> Immutable; the singleton instance is safe to reuse
* across threads.
*
* @since 1.0
*/
public final class NullKey implements Key {
private static final long serialVersionUID = -3423524955655163523L;
/**
* Singleton instance for all uses where a {@link Key} is required but no key
* exists.
*
* <p>
* Use this constant rather than constructing new instances. Equality for
* {@code NullKey} is defined by type, so all instances compare equal; the
* singleton avoids unnecessary allocations and clarifies intent.
* </p>
*/
public static final NullKey INSTANCE = new NullKey();
private NullKey() {
}
/**
* Returns the algorithm identifier for this sentinel key.
*
* <p>
* The value is the literal string {@code "NONE"} to signal that no real
* cryptographic algorithm is associated with this key.
* </p>
*
* @return the string {@code "NONE"}
*/
@Override
public String getAlgorithm() {
return "NONE";
}
/**
* Returns the primary encoding format for this sentinel key.
*
* <p>
* The value is the literal string {@code "NONE"}; {@link #getEncoded()} returns
* an empty byte array.
* </p>
*
* @return the string {@code "NONE"}
*/
@Override
public String getFormat() {
return "NONE";
}
/**
* Returns the key in its primary encoding.
*
* <p>
* For {@code NullKey}, this is an empty byte array because no material exists.
* Callers MAY rely on this to avoid special-casing unkeyed flows.
* </p>
*
* @return a new zero-length byte array
*/
@Override
public byte[] getEncoded() {
return new byte[0];
}
/**
* Compares this object to another for equality.
*
* <p>
* All {@code NullKey} instances are considered equal regardless of identity;
* equality is defined by <em>type</em> rather than state. Prefer comparing
* against {@link #INSTANCE} when intent matters, but this method ensures
* generic collections and caches behave as expected.
* </p>
*
* @param obj the reference object with which to compare
* @return {@code true} if {@code obj} is a {@code NullKey}; {@code false}
* otherwise
*/
@Override
public boolean equals(Object obj) {
return obj instanceof NullKey;
}
/**
* Returns a stable hash code consistent with {@link #equals(Object)}.
*
* <p>
* Derived from a fixed class-based token so that all {@code NullKey} instances
* hash identically, matching the type-based equality semantics.
* </p>
*
* @return a stable hash code value
*/
@Override
public int hashCode() {
return Objects.hash("NullKey");
}
/**
* Returns a diagnostic string for logs and debugging.
*
* <p>
* The returned value is the literal {@code "NullKey"}. It contains no secrets
* and is safe for inclusion in logs and error messages.
* </p>
*
* @return the string {@code "NullKey"}
*/
@Override
public String toString() {
return "NullKey";
}
}

View File

@@ -0,0 +1,57 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import conflux.CtxInterface;
/**
* Optional SPI to write/read a small header that carries runtime params (e.g.,
* IV, tag length, AAD hash). If used, encryption will prepend the header and
* decryption will parse it before initializing the cipher.
*/
public interface SymmetricHeaderCodec {
/** Write header to {@code out}, using/recording params from {@code ctx}. */
void writeHeader(OutputStream out, CryptoAlgorithm algorithm, CtxInterface ctx) throws IOException;
/**
* Read header from {@code in}, populate params in {@code ctx}, and return an
* InputStream positioned immediately after the header.
*/
InputStream readHeader(InputStream in, CryptoAlgorithm algorithm, CtxInterface ctx) throws IOException;
}

View File

@@ -0,0 +1,178 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg;
import java.security.Key;
import java.util.function.Supplier;
import zeroecho.core.AlgorithmFamily;
import zeroecho.core.Capability;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.KeyUsage;
import zeroecho.core.context.CryptoContext;
import zeroecho.core.spec.ContextSpec;
import zeroecho.core.spi.ContextConstructorKS;
/**
* Convenience base class for concrete {@link CryptoAlgorithm} implementations.
*
* <p>
* {@code AbstractCryptoAlgorithm} streamlines two common tasks during algorithm
* construction:
* </p>
*
* <ol>
* <li><b>Binding roles to runtime factories</b> via
* {@link #capability(AlgorithmFamily, KeyUsage, Class, Class, Class, ContextConstructorKS, Supplier)},
* which registers a {@link KeyUsage role} together with its expected
* {@link CryptoContext} type, accepted {@link Key} type, optional
* {@link ContextSpec} type, the constructor factory, and a default spec
* supplier.</li>
* <li><b>Publishing descriptive capabilities</b> by automatically creating and
* adding a {@link Capability} record that higher layers can inspect for feature
* discovery, documentation, or automated selection.</li>
* </ol>
*
* <h2>Typical usage</h2> <pre>{@code
* public final class AesGcmAlgorithm extends AbstractCryptoAlgorithm {
* public AesGcmAlgorithm() {
* super("AES/GCM", "AES-GCM", "JCA");
*
* capability(
* AlgorithmFamily.SYMMETRIC,
* KeyUsage.ENCRYPT,
* AeadEncryptContext.class,
* javax.crypto.SecretKey.class,
* AeadSpec.class,
* (key, spec) -> new JcaAesGcmEncryptContext(key, spec),
* AeadSpec::withRandomNonce);
*
* capability(
* AlgorithmFamily.SYMMETRIC,
* KeyUsage.DECRYPT,
* AeadDecryptContext.class,
* javax.crypto.SecretKey.class,
* AeadSpec.class,
* (key, spec) -> new JcaAesGcmDecryptContext(key, spec),
* AeadSpec::withRandomNonce);
* }
* }
* }</pre>
*
* <h2>Thread-safety</h2> Instances are immutable once constructed and safe to
* share across threads. The contexts created by registered factories are not
* necessarily thread-safe.
*
* @since 1.0
*/
public abstract class AbstractCryptoAlgorithm extends CryptoAlgorithm {
/**
* Constructs an algorithm with default priority ({@code 0}) and provider
* {@code "default"}.
*
* @param id canonical, provider-independent identifier (e.g.,
* {@code "AES/GCM"})
* @param displayName human-friendly name for logs and diagnostics
* @throws NullPointerException if {@code id} or {@code displayName} is
* {@code null}
*/
protected AbstractCryptoAlgorithm(String id, String displayName) {
super(id, displayName);
}
/**
* Constructs an algorithm with default priority ({@code 0}) and an explicit
* provider name.
*
* @param id canonical, provider-independent identifier
* @param displayName human-friendly name for logs and diagnostics
* @param providerName provider label (e.g., {@code "JCA"}, {@code "BC"},
* {@code "default"})
* @throws NullPointerException if any argument is {@code null}
*/
protected AbstractCryptoAlgorithm(String id, String displayName, String providerName) {
super(id, displayName, providerName);
}
/**
* Declares a capability for this algorithm and binds a runtime factory for the
* given role.
*
* <p>
* This single call performs two actions atomically:
* </p>
* <ul>
* <li><b>Runtime binding:</b> delegates to
* {@link CryptoAlgorithm#bind(KeyUsage, Class, Class, Class, ContextConstructorKS, Supplier)}
* so that {@link CryptoAlgorithm#create(KeyUsage, Key, ContextSpec)} can
* construct the appropriate {@link CryptoContext} when invoked.</li>
* <li><b>Metadata publication:</b> creates a {@link Capability} describing this
* role (algorithm id, {@link AlgorithmFamily family}, role, context/key/spec
* types, and default spec supplier) and adds it to the algorithms advertised
* capabilities, discoverable via
* {@link CryptoAlgorithm#listCapabilities()}.</li>
* </ul>
*
* <h4>Validation</h4> Type checks happen at creation time (via {@code bind})
* and again when {@link CryptoAlgorithm#create(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
* @param role supported {@link KeyUsage} role (e.g., {@code ENCRYPT},
* {@code VERIFY})
* @param ctxType concrete {@link CryptoContext} type constructed by
* {@code factory}
* @param keyType accepted {@link Key} type for this role
* @param specType accepted {@link ContextSpec} type (may be a marker type)
* @param factory constructor that builds a context for (key, spec)
* @param defaultSpec default spec supplier used when callers pass {@code null}
* spec
* @param <C> context type
* @param <K> key type
* @param <S> spec type
* @throws NullPointerException if any class/factory/supplier argument is
* {@code null}
*/
protected <C extends CryptoContext, K extends Key, S extends ContextSpec> void capability(AlgorithmFamily family,
KeyUsage role, Class<C> ctxType, Class<K> keyType, Class<S> specType, ContextConstructorKS<C, K, S> factory,
Supplier<? extends S> defaultSpec) {
// bind runtime factory
bind(role, ctxType, keyType, specType, factory, defaultSpec);
// publish metadata
addCapability(new Capability(id(), family, role, ctxType, keyType, specType, defaultSpec));
}
}

View File

@@ -0,0 +1,134 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.aes;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import zeroecho.core.AlgorithmFamily;
import zeroecho.core.KeyUsage;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.context.EncryptionContext;
import zeroecho.core.spec.VoidSpec;
import zeroecho.core.spi.SymmetricKeyBuilder;
/**
* AES algorithm registration and capability wiring.
*
* <p>
* Registers streaming ENCRYPT/DECRYPT contexts for the {@code "AES"} family,
* provides secure defaults (GCM/128), and exposes key builders for generation
* and import.
* </p>
*
* <ul>
* <li>Capabilities:
* <ul>
* <li>ENCRYPT/DECRYPT → {@link AesCipherContext} with {@link AesSpec}</li>
* <li>VoidSpec fallback → {@code AesSpec.gcm128(null)}</li>
* </ul>
* </li>
* <li>Key builders:
* <ul>
* <li>{@link AesKeyGenSpec} → JCA {@link javax.crypto.KeyGenerator}</li>
* <li>{@link AesKeyImportSpec} → {@link javax.crypto.spec.SecretKeySpec}</li>
* </ul>
* </li>
* </ul>
*
* <h2>Threadsafety</h2> Registration is static and threadsafe. Produced
* contexts are stateful and not threadsafe.
*
* @since 1.0
*/
public final class AesAlgorithm extends AbstractCryptoAlgorithm {
/**
* Constructs the AES algorithm descriptor and performs capability registration.
*/
public AesAlgorithm() {
super("AES", "AES (CBC/GCM/CTR)");
// Context capabilities
capability(AlgorithmFamily.SYMMETRIC, KeyUsage.ENCRYPT, EncryptionContext.class, SecretKey.class, AesSpec.class,
(SecretKey k, AesSpec s) -> new AesCipherContext(this, k, true, s, new SecureRandom()),
() -> AesSpec.gcm128(null));
capability(AlgorithmFamily.SYMMETRIC, KeyUsage.DECRYPT, EncryptionContext.class, SecretKey.class, AesSpec.class,
(SecretKey k, AesSpec s) -> new AesCipherContext(this, k, false, s, new SecureRandom()),
() -> AesSpec.gcm128(null));
capability(AlgorithmFamily.SYMMETRIC, KeyUsage.ENCRYPT, EncryptionContext.class, SecretKey.class,
VoidSpec.class, (SecretKey k, VoidSpec s) -> new AesCipherContext(this, k, true, AesSpec.gcm128(null),
new SecureRandom()),
() -> VoidSpec.INSTANCE);
capability(AlgorithmFamily.SYMMETRIC, KeyUsage.DECRYPT, EncryptionContext.class, SecretKey.class,
VoidSpec.class, (SecretKey k, VoidSpec s) -> new AesCipherContext(this, k, false, AesSpec.gcm128(null),
new SecureRandom()),
() -> VoidSpec.INSTANCE);
// Secret generation builder (AesKeyGenSpec)
registerSymmetricKeyBuilder(AesKeyGenSpec.class, new SymmetricKeyBuilder<>() {
@Override
public SecretKey generateSecret(AesKeyGenSpec spec) throws GeneralSecurityException {
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(spec.keySizeBits(), new SecureRandom());
return kg.generateKey();
}
@Override
public SecretKey importSecret(AesKeyGenSpec spec) {
throw new UnsupportedOperationException("Use AesKeyImportSpec for importing AES keys");
}
}, AesKeyGenSpec::aes256);
// Secret import builder (AesKeyImportSpec)
registerSymmetricKeyBuilder(AesKeyImportSpec.class, new SymmetricKeyBuilder<>() {
@Override
public SecretKey generateSecret(AesKeyImportSpec spec) {
throw new UnsupportedOperationException("Use AesKeyGenSpec to generate AES keys");
}
@Override
public SecretKey importSecret(AesKeyImportSpec spec) {
return new SecretKeySpec(spec.key(), "AES");
}
}, null);
}
}

View File

@@ -0,0 +1,296 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.aes;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import conflux.CtxInterface;
import zeroecho.core.ConfluxKeys;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.SymmetricHeaderCodec;
import zeroecho.core.context.EncryptionContext;
import zeroecho.core.err.ProviderFailureException;
import zeroecho.core.io.CipherTransformInputStreamBuilder;
import zeroecho.core.spi.ContextAware;
import zeroecho.core.util.Strings;
/**
* Streaming AES cipher context for GCM / CBC / CTR.
*
* <p>
* IV and optional AAD are exchanged via a {@link conflux.CtxInterface} set with
* {@link #setContext(conflux.CtxInterface)}. On ENCRYPT, a fresh IV is
* generated if absent and stored back; on DECRYPT, IV must be present (from
* context or header).
* </p>
*
* <p>
* When a {@link zeroecho.core.SymmetricHeaderCodec} is embedded in the
* {@link AesSpec} and a context is present, the context reads/writes a minimal
* header carrying IV, tag bits, and an optional AAD hash.
* </p>
*
* @since 1.0
*/
public final class AesCipherContext implements EncryptionContext, ContextAware {
private static final Logger LOG = Logger.getLogger(AesCipherContext.class.getName());
private static final int AES_BLOCK = 16;
private static final int GCM_DEFAULT_IV_BYTES = 12;
private final CryptoAlgorithm algorithm;
private final SecretKey key;
private final boolean encrypt;
private final AesSpec spec;
private final SecureRandom rnd;
private volatile CtxInterface ctx; // NOPMD
/**
* Creates a new AES streaming context.
*
* @param algorithm the owning algorithm descriptor (ID {@code "AES"}); not null
* @param key the AES {@link SecretKey}; not null
* @param encrypt whether this context encrypts ({@code true}) or decrypts
* ({@code false})
* @param spec static AES settings (mode/padding and GCM tag bits); not
* null
* @param rnd secure random source; if null, a default is created
* @throws NullPointerException if any required parameter is null
* @throws IllegalArgumentException if {@code spec} is inconsistent (e.g., GCM
* without NOPADDING)
*/
public AesCipherContext(CryptoAlgorithm algorithm, SecretKey key, boolean encrypt, AesSpec spec, SecureRandom rnd) {
this.algorithm = Objects.requireNonNull(algorithm, "algorithm must not be null");
this.key = Objects.requireNonNull(key, "secret key must not be null");
this.encrypt = encrypt;
this.spec = Objects.requireNonNull(spec, "spec must not be null");
this.rnd = (rnd != null ? rnd : new SecureRandom());
}
/**
* Returns the algorithm descriptor (ID {@code "AES"}).
*
* @return the algorithm
*/
@Override
public CryptoAlgorithm algorithm() {
return algorithm;
}
/**
* Returns the key bound to this context.
*
* @return the secret key
*/
@Override
public java.security.Key key() {
return key;
}
/**
* Sets the Conflux session context used to exchange IV/AAD/tagBits and to
* enable header I/O.
*
* @param context the context; may be {@code null} to disable context features
*/
@Override
public void setContext(CtxInterface context) {
this.ctx = context;
}
/**
* Returns the currently set Conflux context (possibly {@code null}).
*
* @return the context or {@code null}
*/
@Override
public CtxInterface context() {
return ctx;
}
/**
* Attaches this context to an upstream stream and returns a transforming
* stream.
* <ul>
* <li>Decrypt + header → reads header first, hydrates context, then
* transforms</li>
* <li>Transform → initializes JCA cipher with IV/AAD (GCM), staging final
* output if necessary</li>
* <li>Encrypt + header → prepends header after IV is determined</li>
* </ul>
*
* @param upstream the source stream; not null
* @return a stream that yields ciphertext (encrypt) or plaintext (decrypt)
* @throws IOException on I/O or provider errors; includes IV length mismatches
* and missing IV for decrypt
*/
@Override
public InputStream attach(InputStream upstream) throws IOException {
Objects.requireNonNull(upstream, "upstream must not be null");
try {
// If both spec.header() and ctx are present, let this context read/write the
// header.
final SymmetricHeaderCodec header = spec.header();
final boolean activeHeader = (ctx != null) && header != null;
InputStream in = upstream;
if (!encrypt && activeHeader) {
// DECRYPT: parse header first; hydrate ctx (IV / tag bits / AAD check).
LOG.log(Level.FINE, "decryption: reading header for {0}", algorithm);
in = header.readHeader(in, algorithm, ctx);
}
Cipher cipher = Cipher.getInstance(jcaTransform(spec));
initCipher(cipher); // consumes IV/AAD from ctx if present; generates IV on ENCRYPT and may store it
// back; sets tagBits for GCM
InputStream out = // new Stream(in, cipher, spec);
CipherTransformInputStreamBuilder.builder().withCipher(cipher).withUpstream(in)
.withUpdateStreaming().withInputBlockSize(AES_BLOCK).withOutputBlockSize(AES_BLOCK)
.withFinalizationOutputChunks(2).build();
if (encrypt && activeHeader) {
// ENCRYPT: after IV exists in ctx, prepend header
LOG.log(Level.FINE, "encryption: reading header for {0}", algorithm);
java.io.ByteArrayOutputStream hdr = new java.io.ByteArrayOutputStream(64);
header.writeHeader(hdr, algorithm, ctx);
out = new java.io.SequenceInputStream(new java.io.ByteArrayInputStream(hdr.toByteArray()), out);
}
return out;
} catch (GeneralSecurityException e) {
throw new ProviderFailureException("AES attach/init failed", e);
}
}
@Override
public void close() {
/* no-op; stream finalizes itself */
}
// ---- internals ----
private static String jcaTransform(AesSpec s) {
return switch (s.mode()) {
case GCM -> "AES/GCM/NOPADDING";
case CTR -> "AES/CTR/NOPADDING";
case CBC -> "AES/CBC/" + s.padding().name();
};
}
private void initCipher(Cipher cipher) throws GeneralSecurityException, IOException { // NOPMD
final String id = algorithm.id();
byte[] iv = getCtxBytes(ConfluxKeys.iv(id));
final int ivLen = (spec.mode() == AesSpec.Mode.GCM) ? GCM_DEFAULT_IV_BYTES : AES_BLOCK;
if (encrypt) {
if (iv == null) {
iv = new byte[ivLen];
rnd.nextBytes(iv);
putCtxBytes(ConfluxKeys.iv(id), iv);
} else if (iv.length != ivLen) {
throw new IOException("IV length mismatch: expected " + ivLen + " bytes, got " + iv.length);
}
} else {
if (iv == null) {
throw new IOException("IV not found in context for AES " + spec.mode() + " decryption");
}
if (iv.length != ivLen) {
throw new IOException("IV length mismatch: expected " + ivLen + " bytes, got " + iv.length);
}
}
switch (spec.mode()) {
case GCM: {
int tagBits = 0;
if (ctx != null) {
Integer val = ctx.get(ConfluxKeys.tagBits(id));
if (val != null) {
tagBits = val;
}
}
// tagBits was not in ctx => spec settings are applied
if (tagBits < 1) { // NOPMD
tagBits = spec.tagLenBits() > 0 ? spec.tagLenBits() : 128;
}
GCMParameterSpec gps = new GCMParameterSpec(tagBits, iv);
cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, gps);
byte[] aad = getCtxBytes(ConfluxKeys.aad(id));
if (aad == null) {
// AAD should be defined empty
aad = new byte[0];
putCtxBytes(ConfluxKeys.aad(id), aad);
}
cipher.updateAAD(aad);
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "GCM setup: tagBits={0} iv={1} aad={2}",
new Object[] { tagBits, Strings.toShortHexString(iv), Strings.toShortHexString(aad) });
}
break;
}
case CTR:
case CBC: {
cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
break;
}
}
}
private byte[] getCtxBytes(conflux.Key<byte[]> key) {
if (ctx == null || key == null) {
return null; // NOPMD
}
return ctx.get(key);
}
private void putCtxBytes(conflux.Key<byte[]> key, byte[] value) {
if (ctx != null && key != null) {
ctx.put(key, value);
}
}
}

View File

@@ -0,0 +1,174 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.aes;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import conflux.CtxInterface;
import zeroecho.core.ConfluxKeys;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.SymmetricHeaderCodec;
import zeroecho.core.io.Util;
import zeroecho.core.util.Strings;
/**
* Minimal AES header codec that persists runtime parameters only:
* <pre>IV | tagBits(pack7) | aadFlag(1) | [aadHash(32)]</pre>
*
* <ul>
* <li><b>IV</b>: 12 bytes for GCM; 16 bytes for CBC/CTR.</li>
* <li><b>tagBits</b>: 0 for nonGCM; otherwise 96..128.</li>
* <li><b>aadFlag</b>: 1 when AAD is present; the header then includes a SHA256
* of the AAD.</li>
* </ul>
*
* <p>
* No algorithm magic is written. On decrypt, the codec hydrates the Conflux
* context with IV and tag bits and enforces AAD consistency if an AAD hash is
* present.
* </p>
*
* @since 1.0
*/
public final class AesHeaderCodec implements SymmetricHeaderCodec {
private static final Logger LOG = Logger.getLogger(AesHeaderCodec.class.getName());
/**
* Writes the header to {@code out} using values from the provided context.
*
* @param out destination stream
* @param algorithm owning algorithm (ID {@code "AES"})
* @param ctx source of IV, optional tag bits, and optional AAD
* @throws IOException if IV is missing or an I/O error occurs
*/
@Override
public void writeHeader(OutputStream out, CryptoAlgorithm algorithm, CtxInterface ctx) throws IOException {
final String id = algorithm.id(); // e.g., "AES"
LOG.log(Level.FINE, "writeHeader={0}", id);
byte[] iv = ctx.get(ConfluxKeys.iv(id));
if (iv == null) {
throw new IOException("AesHeaderCodec: IV missing in Ctx");
}
// Optional AAD (hash only)
byte[] aad = ctx.get(ConfluxKeys.aad(id));
byte[] aadHash = (aad == null || aad.length == 0) ? null : sha256(aad);
// Optional tag bits hint for GCM (store if caller put it there)
Integer tb = ctx.get(ConfluxKeys.tagBits(id));
int tagBits = tb == null ? 0 : tb;
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "{4} header aad={0} aadHash={1} tagBits={2} iv={3}",
new Object[] { Strings.toShortHexString(aad), Strings.toShortHexString(aadHash), tagBits,
Strings.toShortHexString(iv), id });
}
Util.write(out, iv); // IV
Util.writePack7I(out, tagBits); // 0 for non-GCM
if (aadHash == null) {
out.write(0);
} else {
out.write(1);
out.write(aadHash); // 32 bytes
}
out.flush();
}
/**
* Reads a header from {@code in}, updates the context with IV (and tag bits),
* and verifies AAD if present.
*
* @param in source stream positioned at header
* @param algorithm owning algorithm (ID {@code "AES"})
* @param ctx destination for IV/tag bits and AAD verification
* @return the same {@code InputStream}, positioned after the header
* @throws IOException on malformed header, missing AAD when required, or AAD
* hash mismatch
*/
@Override
public InputStream readHeader(InputStream in, CryptoAlgorithm algorithm, CtxInterface ctx) throws IOException {
final String id = algorithm.id();
byte[] iv = Util.read(in, 32);
int tagBits = Util.readPack7I(in);
int aadFlag = in.read();
byte[] aadHash = null;
if (aadFlag == 1) { // NOPMD
aadHash = in.readNBytes(32);
}
// Hydrate Ctx
ctx.put(ConfluxKeys.iv(id), iv);
if (tagBits != 0) {
ctx.put(ConfluxKeys.tagBits(id), tagBits);
}
byte[] aad = ctx.get(ConfluxKeys.aad(id));
if (aadHash != null) {
if (aad == null || aad.length == 0) {
throw new IOException("AES header expects AAD, but none provided in Ctx");
}
if (!Arrays.equals(aadHash, sha256(aad))) {
throw new IOException("AES header: AAD hash mismatch");
}
}
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "{4} header aad={0} aadHash={1} tagBits={2} iv={3}",
new Object[] { Strings.toShortHexString(aad), Strings.toShortHexString(aadHash), tagBits,
Strings.toShortHexString(iv), id });
}
return in; // positioned after header
}
private static byte[] sha256(byte[] a) throws IOException {
try {
return MessageDigest.getInstance("SHA-256").digest(a);
} catch (NoSuchAlgorithmException e) {
throw new IOException("SHA-256 unavailable", e);
}
}
}

View File

@@ -0,0 +1,107 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.aes;
import zeroecho.core.annotation.Describable;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* Key generation parameters for AES.
*
* <p>
* Instances of this record specify the size of an AES key in bits. Valid values
* are 128, 192, and 256. A {@code AesKeyGenSpec} is consumed by the registered
* AES key builder to create a new {@link javax.crypto.SecretKey}.
* </p>
*
* <p>
* Objects of this type are immutable and thread-safe.
* </p>
*
* @param keySizeBits the AES key size in bits (128, 192, or 256)
*
* @since 1.0
*/
public record AesKeyGenSpec(int keySizeBits) implements AlgorithmKeySpec, Describable {
/**
* Creates a new spec and validates the key size.
*
* @throws IllegalArgumentException if {@code keySizeBits} is not one of 128,
* 192, or 256
*/
public AesKeyGenSpec {
if (keySizeBits != 128 && keySizeBits != 192 && keySizeBits != 256) {
throw new IllegalArgumentException("AES keySizeBits must be 128/192/256, got " + keySizeBits);
}
}
/**
* Constructs a spec for generating a 128-bit AES key.
*
* @return a specification for AES-128
*/
public static AesKeyGenSpec aes128() {
return new AesKeyGenSpec(128);
}
/**
* Constructs a spec for generating a 192-bit AES key.
*
* @return a specification for AES-192
*/
public static AesKeyGenSpec aes192() {
return new AesKeyGenSpec(192);
}
/**
* Constructs a spec for generating a 256-bit AES key.
*
* @return a specification for AES-256
*/
public static AesKeyGenSpec aes256() {
return new AesKeyGenSpec(256);
}
/**
* Provides a short textual description of the key size. For example,
* {@code "256bits"}.
*
* @return a human-readable description
*/
@Override
public String description() {
return keySizeBits() + "bits";
}
}

View File

@@ -0,0 +1,164 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.aes;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.HexFormat;
import java.util.Objects;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* Specification for importing an existing AES key.
*
* <p>
* This class wraps raw key material (16, 24, or 32 bytes) for use with the AES
* algorithm. Factory methods support construction from raw bytes, hex strings,
* or Base64-encoded strings. The key material is defensively copied to maintain
* immutability.
* </p>
*
* <p>
* Instances are consumed by the AES key builder to produce a
* {@link javax.crypto.SecretKey}.
* </p>
*
* <p>
* Objects of this type are immutable and thread-safe.
* </p>
*
* @since 1.0
*/
public final class AesKeyImportSpec implements AlgorithmKeySpec {
private final byte[] key;
private AesKeyImportSpec(byte[] key) {
Objects.requireNonNull(key, "key must not be null");
int len = key.length;
if (len != 16 && len != 24 && len != 32) {
throw new IllegalArgumentException("AES key must be 16/24/32 bytes, got " + len);
}
this.key = Arrays.copyOf(key, len);
}
/**
* Creates a specification from raw key bytes.
*
* @param key a 16, 24, or 32 byte array
* @return an import specification
* @throws IllegalArgumentException if the length is invalid
*/
public static AesKeyImportSpec fromRaw(byte[] key) {
return new AesKeyImportSpec(key);
}
/**
* Creates a specification from a hex-encoded string.
*
* @param hex a string of 32, 48, or 64 hex characters
* @return an import specification
* @throws NullPointerException if {@code hex} is null
* @throws IllegalArgumentException if decoded length is invalid
*/
public static AesKeyImportSpec fromHex(String hex) {
Objects.requireNonNull(hex, "hex must not be null");
return fromRaw(HexFormat.of().parseHex(hex));
}
/**
* Creates a specification from a Base64-encoded string.
*
* @param b64 Base64 text without padding
* @return an import specification
* @throws NullPointerException if {@code b64} is null
* @throws IllegalArgumentException if decoded length is invalid
*/
public static AesKeyImportSpec fromBase64(String b64) {
Objects.requireNonNull(b64, "base64 must not be null");
return fromRaw(Base64.getDecoder().decode(b64));
}
/**
* Returns a defensive copy of the key bytes.
*
* @return the raw key material
*/
public byte[] key() {
return Arrays.copyOf(key, key.length);
}
/**
* Serializes this specification into a key/value sequence, encoding the key in
* Base64.
*
* @param spec the specification to marshal
* @return a sequence containing the key data
*/
public static PairSeq marshal(AesKeyImportSpec spec) {
String k = Base64.getEncoder().withoutPadding().encodeToString(spec.key);
return PairSeq.of("type", "AES-KEY", "k.b64", k);
}
/**
* Reconstructs a specification from a key/value sequence. Accepts keys under
* {@code k.b64}, {@code k.hex}, or {@code k.raw}.
*
* @param p the sequence to parse
* @return a reconstructed import specification
* @throws IllegalArgumentException if no key is present
*/
public static AesKeyImportSpec unmarshal(PairSeq p) {
byte[] out = null;
PairSeq.Cursor cur = p.cursor();
while (cur.next()) {
String k = cur.key();
String v = cur.value();
switch (k) {
case "k.b64" -> out = Base64.getDecoder().decode(v);
case "k.hex" -> out = HexFormat.of().parseHex(v);
case "k.raw" -> out = v.getBytes(StandardCharsets.ISO_8859_1);
default -> {
/* ignore */ }
}
}
if (out == null) {
throw new IllegalArgumentException("AES key missing (k.b64 / k.hex / k.raw)");
}
return new AesKeyImportSpec(out);
}
}

View File

@@ -0,0 +1,362 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.aes;
import java.util.Objects;
import zeroecho.core.SymmetricHeaderCodec;
import zeroecho.core.annotation.Describable;
import zeroecho.core.spec.ContextSpec;
/**
* Static configuration for AES encryption and decryption.
*
* <p>
* An {@code AesSpec} captures compiletime choices for the AES transform: the
* block mode, padding scheme (CBC only), and—when using GCM—the authentication
* tag length in bits. It may optionally embed a
* {@link zeroecho.core.SymmetricHeaderCodec} that governs how runtime
* parameters (IV/nonce, tag bits, AAD hash) are written to and read from the
* data stream. No IV/nonce or AAD bytes are stored in this type; those are
* runtime values exchanged via a Conflux session context.
* </p>
*
* <h2>Design</h2>
* <ul>
* <li><b>Immutability:</b> instances are immutable and threadsafe.</li>
* <li><b>Validation:</b> GCM requires {@code NOPADDING} and a tag length
* between 96 and 128 bits (inclusive) in 8bit increments; nonGCM modes must
* not specify a tag length.</li>
* <li><b>Header codec:</b> when present, the codec persists IV and GCM
* parameters inband, and can bind AAD via a hash. The cipher context hydrates
* the runtime context from this header during decryption, and writes it during
* encryption.</li>
* <li><b>Runtime parameters:</b> IV sizes are dictated by mode (12 bytes for
* GCM; 16 for CBC/CTR) and are generated or required at runtime by the cipher
* context, not by this spec.</li>
* </ul>
*
* @since 1.0
*/
public final class AesSpec implements ContextSpec, Describable {
/**
* AES block modes supported by this implementation.
*
* <p>
* Mode selection determines IV length and security properties. GCM is an AEAD
* mode; CBC and CTR provide confidentiality only and must be combined with a
* MAC (EncryptthenMAC) when integrity/authenticity are required.
* </p>
*/
public enum Mode {
/**
* Cipher Block Chaining (CBC).
*
* <p>
* Requires a 16byte IV. Supports {@link Padding#PKCS5PADDING} and
* {@link Padding#NOPADDING}. Not an AEAD mode; pair with a MAC for integrity.
* </p>
*/
CBC,
/**
* Galois/Counter Mode (GCM), an authenticated encryption mode (AEAD).
*
* <p>
* Requires a 12byte IV and {@link Padding#NOPADDING}. The tag length must be
* 96128 bits (multiple of 8). Supports Additional Authenticated Data (AAD).
* Recommended default.
* </p>
*/
GCM,
/**
* Counter mode (CTR).
*
* <p>
* Uses a 16byte nonce/IV and {@link Padding#NOPADDING}. Not an AEAD mode; pair
* with a MAC for integrity.
* </p>
*/
CTR
}
/**
* Padding schemes for CBC mode.
*
* <p>
* GCM and CTR always use {@link #NOPADDING}.
* </p>
*/
public enum Padding {
/**
* No padding.
*
* <p>
* Use only when the plaintext length is an exact multiple of the AES block size
* (16 bytes). Applicable to CBC; mandatory for GCM/CTR.
* </p>
*/
NOPADDING,
/**
* PKCS#5 padding (recommended for CBC) available in the Standard JDK21+.
*/
PKCS5PADDING
} // CBC only
private final Mode mode;
private final Padding padding; // CBC: PKCS5 or NOPADDING; GCM/CTR: NOPADDING
private final int tagLenBits; // GCM only (96..128 step 8). 0 for non-GCM.
private final SymmetricHeaderCodec header;
private AesSpec(Mode mode, Padding padding, int tagLenBits, SymmetricHeaderCodec header) {
this.mode = Objects.requireNonNull(mode, "mode must not be null");
this.padding = Objects.requireNonNull(padding, "padding must not be null");
if (mode == Mode.GCM && padding != Padding.NOPADDING) {
throw new IllegalArgumentException("GCM must use NOPADDING");
}
if (mode == Mode.GCM) {
if (tagLenBits % 8 != 0 || tagLenBits < 96 || tagLenBits > 128) {
throw new IllegalArgumentException("GCM tagLenBits must be 96..128 in steps of 8");
}
} else if (tagLenBits != 0) {
throw new IllegalArgumentException("tagLenBits applies to GCM only");
}
this.tagLenBits = tagLenBits;
this.header = header; // may be null
}
/**
* Returns a new builder with safe defaults ({@link Mode#GCM},
* {@link Padding#NOPADDING}, 128bit tag, no header).
*
* @return a fresh builder for {@link AesSpec}
*/
public static Builder builder() {
return new Builder();
}
/**
* Builder for {@link AesSpec}.
*
* <p>
* Not threadsafe. The resulting {@code AesSpec} is immutable.
* </p>
*/
public static final class Builder {
private Mode mode = Mode.GCM;
private Padding padding = Padding.NOPADDING;
private int tagLenBits = 128; // only used for GCM
private SymmetricHeaderCodec header;
/**
* Selects the AES mode.
*
* @param m the block mode ({@link Mode#CBC}, {@link Mode#GCM}, or
* {@link Mode#CTR})
* @return this builder
* @throws NullPointerException if {@code m} is {@code null}
*/
public Builder mode(Mode m) {
this.mode = Objects.requireNonNull(m);
return this;
}
/**
* Selects the padding scheme for CBC.
*
* <p>
* Ignored for GCM and CTR, which always use {@link Padding#NOPADDING}.
* </p>
*
* @param p the padding scheme
* @return this builder
* @throws NullPointerException if {@code p} is {@code null}
*/
public Builder padding(Padding p) {
this.padding = Objects.requireNonNull(p);
return this;
}
/**
* Sets the GCM authentication tag length in bits.
*
* <p>
* Valid only when the mode is {@link Mode#GCM}. The value must be 96, 104, 112,
* 120, or 128.
* </p>
*
* @param t the tag length in bits
* @return this builder
*/
public Builder tagLenBits(int t) {
this.tagLenBits = t;
return this;
}
/**
* Installs a header codec used by the cipher context to persist and recover
* runtime parameters (e.g., IV, tag length, AAD hash).
*
* <p>
* When {@code null}, no header is written or parsed.
* </p>
*
* @param codec the codec to embed, or {@code null} to disable
* @return this builder
*/
public Builder header(SymmetricHeaderCodec codec) {
this.header = codec;
return this;
}
/**
* Builds a validated {@link AesSpec}.
*
* <p>
* Validation rules:
* </p>
* <ul>
* <li>If mode is {@link Mode#GCM}, padding must be {@link Padding#NOPADDING}
* and {@code tagLenBits} must be 96128 in steps of 8.</li>
* <li>If mode is not GCM, {@code tagLenBits} must be 0.</li>
* </ul>
*
* @return an immutable specification
* @throws IllegalArgumentException if parameters are inconsistent
*/
public AesSpec build() {
int t = (mode == Mode.GCM) ? tagLenBits : 0;
return new AesSpec(mode, padding, t, header);
}
}
/**
* Creates a GCM specification with a 128bit authentication tag and the
* provided header codec.
*
* @param header the header codec to embed, or {@code null} for none
* @return a specification for {@code AES/GCM/NOPADDING} with a 128bit tag
*/
public static AesSpec gcm128(SymmetricHeaderCodec header) {
return builder().mode(Mode.GCM).tagLenBits(128).header(header).build();
}
/**
* Creates a CBC specification using PKCS#7 padding and the provided header.
*
* @param header the header codec to embed, or {@code null} for none
* @return a specification for {@code AES/CBC/PKCS7Padding}
*/
public static AesSpec cbcPkcs7(SymmetricHeaderCodec header) {
return builder().mode(Mode.CBC).padding(Padding.PKCS5PADDING).header(header).build();
}
/**
* Creates a CTR specification with no padding and the provided header.
*
* @param header the header codec to embed, or {@code null} for none
* @return a specification for {@code AES/CTR/NOPADDING}
*/
public static AesSpec ctr(SymmetricHeaderCodec header) {
return builder().mode(Mode.CTR).padding(Padding.NOPADDING).header(header).build();
}
/**
* Returns the selected AES mode.
*
* <p>
* This value determines IV length expectations and whether AAD and tag length
* apply (GCM only).
* </p>
*
* @return the block mode used by this specification
*/
public Mode mode() {
return mode;
}
/**
* Returns the selected padding scheme.
*
* <p>
* Relevant only for CBC; GCM and CTR always operate with no padding.
* </p>
*
* @return the padding scheme (never {@code null})
*/
public Padding padding() {
return padding;
}
/**
* Returns the authentication tag length in bits.
*
* <p>
* For GCM, this is one of 96, 104, 112, 120, or 128. For nonGCM modes, this
* method returns {@code 0}.
* </p>
*
* @return the GCM tag length in bits, or {@code 0} if not applicable
*/
public int tagLenBits() {
return tagLenBits;
}
/**
* Returns the embedded header codec, if any.
*
* <p>
* When nonnull, the cipher context writes the header during encryption (after
* the IV is chosen) and reads it during decryption to hydrate the runtime
* context.
* </p>
*
* @return the header codec, or {@code null} if no header is used
*/
public SymmetricHeaderCodec header() {
return header;
}
/**
* Produces a compact humanreadable description of this specification, such as
* {@code "AES-GCM(tag=128)"} or {@code "AES-CBC/PKCS7Padding"}.
*
* @return a descriptive string for diagnostics and logs
*/
@Override
public String description() {
return "AES-" + mode + (mode == Mode.CBC ? "/" + padding : "")
+ (mode == Mode.GCM ? "(tag=" + tagLenBits + ")" : "");
}
}

View File

@@ -0,0 +1,94 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* AES algorithm implementation and runtime wiring.
*
* <p>
* This package provides the AES capability set for the core layer, including
* the algorithm descriptor, a streaming cipher context for GCM / CBC / CTR,
* static configuration, a minimal header codec for runtime parameters, and key
* import/generation specifications. The design favors safe defaults (GCM with a
* 128-bit tag), explicit role-to-context binding, and clear separation between
* static configuration and per-operation parameters.
* </p>
*
* <h2>Components</h2>
* <ul>
* <li><b>Algorithm descriptor:</b> {@link AesAlgorithm} registers ENCRYPT and
* DECRYPT roles, installs default specifications, and exposes symmetric key
* builders for generation and import. Capabilities are published for discovery
* by higher layers.</li>
* <li><b>Streaming context:</b> {@link AesCipherContext} implements the runtime
* transform over {@code InputStream}, handling IV creation and validation,
* optional AAD, tag length (GCM), and mode-specific initialization.</li>
* <li><b>Static configuration:</b> {@link AesSpec} captures compile-time
* choices (mode, padding, tag length for GCM) and may embed a header codec used
* by the context to persist runtime parameters in-band.</li>
* <li><b>Header codec:</b> {@link AesHeaderCodec} writes/reads a compact header
* containing IV, a tag-length hint for GCM, and an optional AAD hash used to
* verify that decrypt-time AAD matches encrypt-time AAD.</li>
* <li><b>Key specifications:</b> {@link AesKeyGenSpec} defines key-size
* parameters for generation, and {@link AesKeyImportSpec} wraps existing AES
* keys supplied as raw bytes, hex, or Base64.</li>
* </ul>
*
* <h2>Runtime parameters and context exchange</h2>
* <p>
* The streaming context exchanges ephemeral parameters (IV/nonce, GCM tag bits,
* and optional AAD) via a Conflux session context. When a header codec is
* present and a session context is set, encryption prepends a minimal header
* and decryption reads it first to hydrate the session context before
* initializing the cipher.
* </p>
*
* <h2>Safety and validation</h2>
* <ul>
* <li>GCM requires no padding and a tag length between 96 and 128 bits in 8-bit
* steps; non-GCM modes must not specify a tag length.</li>
* <li>IV length is enforced by mode (12 bytes for GCM; 16 bytes for CBC/CTR),
* and decryption fails if IV is missing or has an unexpected size.</li>
* <li>When an AAD hash is present in the header, decryption enforces
* consistency with the caller-supplied AAD.</li>
* </ul>
*
* <h2>Thread-safety</h2>
* <ul>
* <li>Algorithm descriptors are immutable and safe to share.</li>
* <li>Streaming contexts are stateful and not thread-safe.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.aes;

View File

@@ -0,0 +1,219 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.bike;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
import org.bouncycastle.pqc.jcajce.spec.BIKEParameterSpec;
import zeroecho.core.AlgorithmFamily;
import zeroecho.core.KeyUsage;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.alg.common.agreement.KemMessageAgreementAdapter;
import zeroecho.core.context.KemContext;
import zeroecho.core.context.MessageAgreementContext;
import zeroecho.core.spec.VoidSpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>Integration of BIKE (Bit Flipping Key Encapsulation) algorithm</h2>
*
* Declares BIKE as a {@link zeroecho.core.alg.AbstractCryptoAlgorithm} and
* registers its supported roles, contexts, and key builders within the ZeroEcho
* framework.
*
* <h3>Capabilities</h3>
* <ul>
* <li><b>KEM</b> - encapsulation and decapsulation of shared secrets using
* public and private keys.</li>
* <li><b>Agreement</b> - initiator and responder flows for
* {@link MessageAgreementContext} built on top of BIKE KEM.</li>
* <li><b>Asymmetric key builders</b> - generation and import of BIKE keys from
* {@link BikeKeyGenSpec}, {@link BikePublicKeySpec}, and
* {@link BikePrivateKeySpec}.</li>
* </ul>
*
* <h3>Provider</h3> Uses the BouncyCastle Post-Quantum provider
* ({@code BCPQC}). The provider must be registered in the JCA {@link Security}
* before use.
*
* <h3>Example</h3> <pre>{@code
* BikeAlgorithm bike = new BikeAlgorithm();
*
* // Generate a key pair
* KeyPair kp = bike.asymmetricKeyBuilder(BikeKeyGenSpec.class)
* .generateKeyPair(BikeKeyGenSpec.bike256());
*
* // Encapsulation using recipient's public key
* KemContext kemEnc = bike.create(KeyUsage.ENCAPSULATE, kp.getPublic(), VoidSpec.INSTANCE);
*
* // Decapsulation using private key
* KemContext kemDec = bike.create(KeyUsage.DECAPSULATE, kp.getPrivate(), VoidSpec.INSTANCE);
* }</pre>
*
* @since 1.0
*/
public final class BikeAlgorithm extends AbstractCryptoAlgorithm {
/**
* Constructs and registers the BIKE algorithm with all its roles and key
* builders.
*
* <p>
* Registers:
* </p>
* <ul>
* <li>KEM (encapsulate/decapsulate)</li>
* <li>Agreement (initiator/responder)</li>
* <li>Asymmetric key builders for BIKE key specifications</li>
* </ul>
*/
public BikeAlgorithm() {
super("BIKE", "BIKE", BouncyCastlePQCProvider.PROVIDER_NAME);
capability(AlgorithmFamily.KEM, KeyUsage.ENCAPSULATE, KemContext.class, PublicKey.class, VoidSpec.class,
(PublicKey k, VoidSpec s) -> new BikeKemContext(this, k), () -> VoidSpec.INSTANCE);
capability(AlgorithmFamily.KEM, KeyUsage.DECAPSULATE, KemContext.class, PrivateKey.class, VoidSpec.class,
(PrivateKey k, VoidSpec s) -> new BikeKemContext(this, k), () -> VoidSpec.INSTANCE);
// AGREEMENT (initiator): Alice has Bob's public key → encapsulate
capability(AlgorithmFamily.AGREEMENT, KeyUsage.AGREEMENT, MessageAgreementContext.class, // ← return your
// existing KemContext
PublicKey.class, // ← initiator uses recipient's public key
VoidSpec.class, // ← must implement ContextSpec
(PublicKey recipient, VoidSpec spec) -> {
// create a context bound to recipient public key for encapsulation
return KemMessageAgreementAdapter.builder().upon(new BikeKemContext(this, recipient)).asInitiator()
.build();
}, () -> VoidSpec.INSTANCE // default
);
// AGREEMENT (responder): Bob has his private key → decapsulate
capability(AlgorithmFamily.AGREEMENT, KeyUsage.AGREEMENT, MessageAgreementContext.class, // ← same KemContext
// type
PrivateKey.class, // ← responder uses their private key
VoidSpec.class, (PrivateKey myPriv, VoidSpec spec) -> {
return KemMessageAgreementAdapter.builder().upon(new BikeKemContext(this, myPriv)).asResponder()
.build();
}, () -> VoidSpec.INSTANCE);
registerAsymmetricKeyBuilder(BikeKeyGenSpec.class, new AsymmetricKeyBuilder<>() {
@Override
public KeyPair generateKeyPair(BikeKeyGenSpec spec) throws GeneralSecurityException {
ensureProvider();
KeyPairGenerator kpg = KeyPairGenerator.getInstance("BIKE", providerName());
BIKEParameterSpec params = switch (spec.variant()) {
case BIKE_128 -> BIKEParameterSpec.bike128;
case BIKE_192 -> BIKEParameterSpec.bike192;
case BIKE_256 -> BIKEParameterSpec.bike256;
};
kpg.initialize(params, new SecureRandom());
return kpg.generateKeyPair();
}
@Override
public PublicKey importPublic(BikeKeyGenSpec spec) {
throw new UnsupportedOperationException();
}
@Override
public PrivateKey importPrivate(BikeKeyGenSpec spec) {
throw new UnsupportedOperationException();
}
}, BikeKeyGenSpec::bike256);
registerAsymmetricKeyBuilder(BikePublicKeySpec.class, new AsymmetricKeyBuilder<>() {
@Override
public KeyPair generateKeyPair(BikePublicKeySpec spec) {
throw new UnsupportedOperationException();
}
@Override
public PublicKey importPublic(BikePublicKeySpec spec) throws GeneralSecurityException {
ensureProvider();
KeyFactory kf = KeyFactory.getInstance("BIKE", providerName());
return kf.generatePublic(new X509EncodedKeySpec(spec.x509()));
}
@Override
public PrivateKey importPrivate(BikePublicKeySpec spec) {
throw new UnsupportedOperationException();
}
}, null);
registerAsymmetricKeyBuilder(BikePrivateKeySpec.class, new AsymmetricKeyBuilder<>() {
@Override
public KeyPair generateKeyPair(BikePrivateKeySpec spec) {
throw new UnsupportedOperationException();
}
@Override
public PublicKey importPublic(BikePrivateKeySpec spec) {
throw new UnsupportedOperationException();
}
@Override
public PrivateKey importPrivate(BikePrivateKeySpec spec) throws GeneralSecurityException {
ensureProvider();
KeyFactory kf = KeyFactory.getInstance("BIKE", providerName());
return kf.generatePrivate(new PKCS8EncodedKeySpec(spec.pkcs8()));
}
}, null);
}
/**
* Ensures that the BouncyCastle Post-Quantum provider is available.
*
* @throws NoSuchProviderException if the provider is not registered in
* {@link Security}
*/
private static void ensureProvider() throws NoSuchProviderException {
Provider p = Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME);
if (p == null) {
throw new NoSuchProviderException("BCPQC provider not registered");
}
}
}

View File

@@ -0,0 +1,193 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.bike;
import java.io.IOException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.Objects;
import javax.security.auth.DestroyFailedException;
import org.bouncycastle.crypto.SecretWithEncapsulation;
import org.bouncycastle.pqc.crypto.bike.BIKEKEMExtractor;
import org.bouncycastle.pqc.crypto.bike.BIKEKEMGenerator;
import org.bouncycastle.pqc.crypto.bike.BIKEPrivateKeyParameters;
import org.bouncycastle.pqc.crypto.bike.BIKEPublicKeyParameters;
import org.bouncycastle.pqc.crypto.util.PrivateKeyFactory;
import org.bouncycastle.pqc.crypto.util.PublicKeyFactory;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.context.KemContext;
/**
* <h2>BIKE Key Encapsulation Mechanism context</h2>
*
* Implements {@link zeroecho.core.context.KemContext} for the BIKE (Bit
* Flipping Key Encapsulation) post-quantum algorithm. Encapsulation and
* decapsulation flows are separated by constructor:
* <ul>
* <li>Construct with a {@link java.security.PublicKey} to create an
* <b>encapsulation</b> context.</li>
* <li>Construct with a {@link java.security.PrivateKey} to create a
* <b>decapsulation</b> context.</li>
* </ul>
*
* <h3>Usage</h3> <pre>{@code
* BikeKemContext enc = new BikeKemContext(algo, recipientPubKey);
* KemResult r = enc.encapsulate();
*
* BikeKemContext dec = new BikeKemContext(algo, myPrivateKey);
* byte[] secret = dec.decapsulate(r.ciphertext());
* }</pre>
*
* @since 1.0
*/
public final class BikeKemContext implements KemContext {
private final CryptoAlgorithm algorithm;
private final Key key;
private final boolean encapsulate;
/**
* Creates an encapsulation context bound to the recipient's public key.
*
* @param algorithm parent algorithm instance
* @param k recipient's public key
* @throws NullPointerException if any argument is null
*/
public BikeKemContext(CryptoAlgorithm algorithm, PublicKey k) {
this.algorithm = Objects.requireNonNull(algorithm);
this.key = Objects.requireNonNull(k);
this.encapsulate = true;
}
/**
* Creates a decapsulation context bound to the holder's private key.
*
* @param algorithm parent algorithm instance
* @param k private key for decapsulation
* @throws NullPointerException if any argument is null
*/
public BikeKemContext(CryptoAlgorithm algorithm, PrivateKey k) {
this.algorithm = Objects.requireNonNull(algorithm);
this.key = Objects.requireNonNull(k);
this.encapsulate = false;
}
/**
* Returns the algorithm that created this context.
*
* @return parent algorithm
*/
@Override
public CryptoAlgorithm algorithm() {
return algorithm;
}
/**
* Returns the underlying key bound to this context.
*
* @return public or private key depending on mode
*/
@Override
public Key key() {
return key;
}
/**
* Releases resources associated with this context.
* <p>
* No-op for BIKE; provided for API symmetry.
* </p>
*/
@Override
public void close() {
// empty
}
/**
* Generates a new shared secret and ciphertext using the recipient's public
* key.
*
* @return encapsulation result containing ciphertext and shared secret
* @throws IOException if encapsulation fails
* @throws IllegalStateException if this context is not initialized for
* encapsulation
*/
@Override
public KemResult encapsulate() throws IOException {
if (!encapsulate) {
throw new IllegalStateException("Not initialized for ENCAPSULATE");
}
try {
final BIKEPublicKeyParameters keyParam = (BIKEPublicKeyParameters) PublicKeyFactory
.createKey(key.getEncoded());
BIKEKEMGenerator gen = new BIKEKEMGenerator(new SecureRandom());
SecretWithEncapsulation res = gen.generateEncapsulated(keyParam);
byte[] secret = res.getSecret();
byte[] ct = res.getEncapsulation();
res.destroy();
return new KemResult(ct, secret);
} catch (DestroyFailedException e) {
throw new IOException("BIKE encapsulate failed", e);
}
}
/**
* Recovers the shared secret from a ciphertext using the holder's private key.
*
* @param ciphertext encapsulated key material
* @return recovered shared secret bytes
* @throws IOException if decapsulation fails
* @throws IllegalStateException if this context is not initialized for
* decapsulation
*/
@Override
public byte[] decapsulate(byte[] ciphertext) throws IOException {
if (encapsulate) {
throw new IllegalStateException("Not initialized for DECAPSULATE");
}
try {
final BIKEPrivateKeyParameters keyParam = (BIKEPrivateKeyParameters) PrivateKeyFactory
.createKey(key.getEncoded());
BIKEKEMExtractor ex = new BIKEKEMExtractor(keyParam);
return ex.extractSecret(ciphertext);
} catch (Exception e) { // NOPMD
throw new IOException("BIKE decapsulate failed", e);
}
}
}

View File

@@ -0,0 +1,132 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.bike;
import zeroecho.core.annotation.Describable;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>Specification for BIKE key generation</h2>
*
* Defines algorithm parameters for generating BIKE key pairs. Encapsulates a
* chosen {@link Variant} (BIKE-128, BIKE-192, BIKE-256).
*
* <h3>Usage</h3> <pre>{@code
* // Generate a BIKE-192 key pair
* KeyPair kp = bikeAlgorithm.asymmetricKeyBuilder(BikeKeyGenSpec.class)
* .generateKeyPair(BikeKeyGenSpec.bike192());
* }</pre>
*
* @since 1.0
*/
public final class BikeKeyGenSpec implements AlgorithmKeySpec, Describable {
/**
* Available BIKE parameter sets.
*/
public enum Variant {
/** BIKE with 128-bit security strength. */
BIKE_128,
/** BIKE with 192-bit security strength. */
BIKE_192,
/** BIKE with 256-bit security strength. */
BIKE_256
}
private final Variant variant;
private BikeKeyGenSpec(Variant v) {
this.variant = v;
}
/**
* Creates a new specification for the given BIKE variant.
*
* @param v variant to use
* @return a new key generation spec
*/
public static BikeKeyGenSpec of(Variant v) {
return new BikeKeyGenSpec(v);
}
/**
* Returns a specification for BIKE-128.
*
* @return BIKE-128 key generation spec
*/
public static BikeKeyGenSpec bike128() {
return new BikeKeyGenSpec(Variant.BIKE_128);
}
/**
* Returns a specification for BIKE-192.
*
* @return BIKE-192 key generation spec
*/
public static BikeKeyGenSpec bike192() {
return new BikeKeyGenSpec(Variant.BIKE_192);
}
/**
* Returns a specification for BIKE-256.
*
* @return BIKE-256 key generation spec
*/
public static BikeKeyGenSpec bike256() {
return new BikeKeyGenSpec(Variant.BIKE_256);
}
/**
* Returns the BIKE variant.
*
* @return configured variant
*/
public Variant variant() {
return variant;
}
/**
* Returns a human-readable description of this spec.
*
* <p>
* Implements {@link Describable} by returning the variant name.
* </p>
*
* @return description string
*/
@Override
public String description() {
return variant.toString();
}
}

View File

@@ -0,0 +1,136 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.bike;
import java.util.Base64;
import java.util.Objects;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.marshal.PairSeq.Cursor;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>Specification for a BIKE private key</h2>
*
* Wraps a BIKE private key encoded in standard PKCS#8 format. Provides
* marshalling and unmarshalling support for interchange.
*
* <h3>Usage</h3> <pre>{@code
* // Import a BIKE private key
* BikePrivateKeySpec spec = new BikePrivateKeySpec(pkcs8Bytes);
* PrivateKey key = bikeAlgorithm.importPrivate(spec);
*
* // Marshal for storage or transport
* PairSeq seq = BikePrivateKeySpec.marshal(spec);
*
* // Reconstruct from encoded representation
* BikePrivateKeySpec restored = BikePrivateKeySpec.unmarshal(seq);
* }</pre>
*
* @since 1.0
*/
public final class BikePrivateKeySpec implements AlgorithmKeySpec {
private static final String PKCS8_B64 = "pkcs8.b64";
private final byte[] pkcs8;
/**
* Constructs a new spec from a PKCS#8 encoded private key.
*
* @param pkcs8Der PKCS#8 bytes (DER encoded)
* @throws NullPointerException if {@code pkcs8Der} is null
*/
public BikePrivateKeySpec(byte[] pkcs8Der) {
this.pkcs8 = Objects.requireNonNull(pkcs8Der).clone();
}
/**
* Returns a defensive copy of the PKCS#8 encoded key.
*
* @return cloned PKCS#8 bytes
*/
public byte[] pkcs8() {
return pkcs8.clone();
}
/**
* Serializes the spec to a {@link PairSeq} with base64 encoding.
*
* <p>
* Fields:
* </p>
* <ul>
* <li>{@code type} = "BikePrivateKeySpec"</li>
* <li>{@code pkcs8.b64} = base64 of encoded key (no padding)</li>
* </ul>
*
* @param spec private key specification
* @return serialized key representation
*/
public static PairSeq marshal(BikePrivateKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.pkcs8);
return PairSeq.of("type", "BikePrivateKeySpec", PKCS8_B64, b64);
}
/**
* Deserializes a spec from a {@link PairSeq}.
*
* @param p serialized representation containing {@code pkcs8.b64}
* @return reconstructed private key spec
* @throws IllegalArgumentException if required field is missing
*/
public static BikePrivateKeySpec unmarshal(PairSeq p) {
String b64 = null;
for (Cursor cur = p.cursor(); cur.next();) {
if (PKCS8_B64.equals(cur.key())) {
b64 = cur.value();
}
}
if (b64 == null) {
throw new IllegalArgumentException("BikePrivateKeySpec: missing pkcs8.b64");
}
return new BikePrivateKeySpec(Base64.getDecoder().decode(b64));
}
/**
* Returns a diagnostic string including encoded length.
*
* @return human-readable description
*/
@Override
public String toString() {
return "BikePrivateKeySpec[len=" + pkcs8.length + "]";
}
}

View File

@@ -0,0 +1,136 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.bike;
import java.util.Base64;
import java.util.Objects;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.marshal.PairSeq.Cursor;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>Specification for a BIKE public key</h2>
*
* Wraps a BIKE public key encoded in standard X.509 format. Provides
* marshalling and unmarshalling support for serialization.
*
* <h3>Usage</h3> <pre>{@code
* // Import a BIKE public key
* BikePublicKeySpec spec = new BikePublicKeySpec(x509Bytes);
* PublicKey key = bikeAlgorithm.importPublic(spec);
*
* // Marshal for transport or storage
* PairSeq seq = BikePublicKeySpec.marshal(spec);
*
* // Reconstruct from encoded representation
* BikePublicKeySpec restored = BikePublicKeySpec.unmarshal(seq);
* }</pre>
*
* @since 1.0
*/
public final class BikePublicKeySpec implements AlgorithmKeySpec {
private static final String X509_B64 = "x509.b64";
private final byte[] x509;
/**
* Constructs a new spec from an X.509 encoded public key.
*
* @param x509Der X.509 bytes (DER encoded)
* @throws NullPointerException if {@code x509Der} is null
*/
public BikePublicKeySpec(byte[] x509Der) {
this.x509 = Objects.requireNonNull(x509Der).clone();
}
/**
* Returns a defensive copy of the X.509 encoded key.
*
* @return cloned X.509 bytes
*/
public byte[] x509() {
return x509.clone();
}
/**
* Serializes the spec to a {@link PairSeq} with base64 encoding.
*
* <p>
* Fields:
* </p>
* <ul>
* <li>{@code type} = "BikePublicKeySpec"</li>
* <li>{@code x509.b64} = base64 of encoded key (no padding)</li>
* </ul>
*
* @param spec public key specification
* @return serialized representation
*/
public static PairSeq marshal(BikePublicKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.x509);
return PairSeq.of("type", "BikePublicKeySpec", X509_B64, b64);
}
/**
* Deserializes a spec from a {@link PairSeq}.
*
* @param p serialized representation containing {@code x509.b64}
* @return reconstructed public key spec
* @throws IllegalArgumentException if required field is missing
*/
public static BikePublicKeySpec unmarshal(PairSeq p) {
String b64 = null;
for (Cursor cur = p.cursor(); cur.next();) {
if (X509_B64.equals(cur.key())) {
b64 = cur.value();
}
}
if (b64 == null) {
throw new IllegalArgumentException("BikePublicKeySpec: missing x509.b64");
}
return new BikePublicKeySpec(Base64.getDecoder().decode(b64));
}
/**
* Returns a diagnostic string including encoded length.
*
* @return human-readable description
*/
@Override
public String toString() {
return "BikePublicKeySpec[len=" + x509.length + "]";
}
}

View File

@@ -0,0 +1,88 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* BIKE post-quantum key encapsulation and related utilities.
*
* <p>
* This package integrates the BIKE (Bit Flipping Key Encapsulation) algorithm
* into the core layer. It defines the algorithm descriptor, the runtime context
* for encapsulation and decapsulation, and key specifications for generation
* and import. The implementation relies on a Post-Quantum JCA provider and
* focuses on safe, explicit wiring of roles, contexts, and key builders.
* </p>
*
* <h2>Scope and responsibilities</h2>
* <ul>
* <li>Expose a concrete algorithm descriptor that registers BIKE roles and key
* builders.</li>
* <li>Provide a runtime context that performs encapsulation and
* decapsulation.</li>
* <li>Define key specifications for generation and for importing encoded
* keys.</li>
* </ul>
*
* <h2>Components</h2>
* <ul>
* <li><b>Algorithm descriptor:</b> {@link BikeAlgorithm} declares KEM roles for
* encapsulation and decapsulation, wires a message-agreement adapter built on
* KEM, and registers asymmetric key builders for BIKE key specs.</li>
* <li><b>Runtime context:</b> {@link BikeKemContext} implements the key
* encapsulation mechanism and separates encapsulation from decapsulation by
* constructor selection.</li>
* <li><b>Key generation spec:</b> {@link BikeKeyGenSpec} selects a BIKE variant
* and is used by the key-pair builder.</li>
* <li><b>Key import specs:</b> {@link BikePublicKeySpec} and
* {@link BikePrivateKeySpec} wrap X.509 and PKCS#8 encodings and support
* marshalling for transport and storage.</li>
* </ul>
*
* <h2>Provider requirements</h2>
* <p>
* BIKE operations require a Post-Quantum JCA provider. The algorithm descriptor
* expects the BouncyCastle PQC provider to be present and may validate provider
* availability during key operations. Applications must ensure the provider is
* installed before use.
* </p>
*
* <h2>Thread-safety</h2>
* <ul>
* <li>Algorithm descriptors are immutable and safe to share across
* threads.</li>
* <li>Runtime contexts are stateful and not thread-safe.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.bike;

View File

@@ -0,0 +1,123 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.spi.SymmetricKeyBuilder;
/**
* <h2>Abstract base for ChaCha family algorithms</h2>
*
* Provides common registration logic for ChaCha20-based algorithms within the
* ZeroEcho framework. Extends {@link zeroecho.core.alg.AbstractCryptoAlgorithm}
* and installs symmetric key builders for both key generation and key import.
*
* <h3>Registered key builders</h3>
* <ul>
* <li>{@link zeroecho.core.alg.chacha.ChaChaKeyGenSpec} - generates new random
* ChaCha keys of configurable size (default 256-bit).</li>
* <li>{@link zeroecho.core.alg.chacha.ChaChaKeyImportSpec} - imports externally
* supplied ChaCha keys as {@link javax.crypto.SecretKey} instances.</li>
* </ul>
*
* <h3>Notes</h3>
* <ul>
* <li>Generation uses {@link javax.crypto.KeyGenerator} with algorithm
* {@code "ChaCha20"}.</li>
* <li>Import wraps the raw key material with
* {@link javax.crypto.spec.SecretKeySpec}.</li>
* <li>Attempts to generate a key via {@code ChaChaKeyImportSpec} or import via
* {@code ChaChaKeyGenSpec} will throw
* {@link UnsupportedOperationException}.</li>
* </ul>
*
* <h3>Example</h3> <pre>{@code
* AbstractChaChaAlgorithm algo = ...;
*
* // Generate a fresh 256-bit key
* SecretKey key = algo.generateSecret(ChaChaKeyGenSpec.chacha256());
*
* // Import an existing key
* SecretKey imported = algo.importSecret(new ChaChaKeyImportSpec(rawBytes));
* }</pre>
*
* @since 1.0
*/
abstract class AbstractChaChaAlgorithm extends AbstractCryptoAlgorithm {
/**
* Constructs a ChaCha-based algorithm definition and registers symmetric key
* builders for generation and import.
*
* @param id canonical algorithm identifier (e.g., "ChaCha20/Poly1305")
* @param title human-readable name for diagnostics
*/
protected AbstractChaChaAlgorithm(String id, String title) {
super(id, title);
// register once for both algorithms (same 256-bit key)
registerSymmetricKeyBuilder(ChaChaKeyGenSpec.class, new SymmetricKeyBuilder<>() {
@Override
public SecretKey generateSecret(ChaChaKeyGenSpec spec) throws GeneralSecurityException {
KeyGenerator kg = KeyGenerator.getInstance("ChaCha20");
kg.init(spec.keySizeBits(), new SecureRandom());
return kg.generateKey();
}
@Override
public SecretKey importSecret(ChaChaKeyGenSpec spec) {
throw new UnsupportedOperationException("Use ChaChaKeyImportSpec for importing ChaCha keys");
}
}, ChaChaKeyGenSpec::chacha256);
registerSymmetricKeyBuilder(ChaChaKeyImportSpec.class, new SymmetricKeyBuilder<>() {
@Override
public SecretKey generateSecret(ChaChaKeyImportSpec spec) {
throw new UnsupportedOperationException("Use ChaChaKeyGenSpec to generate ChaCha keys");
}
@Override
public SecretKey importSecret(ChaChaKeyImportSpec spec) {
return new SecretKeySpec(spec.key(), "ChaCha20");
}
}, null);
}
}

View File

@@ -0,0 +1,263 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import conflux.CtxInterface;
import zeroecho.core.ConfluxKeys;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.SymmetricHeaderCodec;
import zeroecho.core.context.EncryptionContext;
import zeroecho.core.err.ProviderFailureException;
import zeroecho.core.io.CipherTransformInputStreamBuilder;
import zeroecho.core.spi.ContextAware;
/**
* <h2>Abstract streaming cipher context for ChaCha algorithms</h2>
*
* Base implementation of {@link zeroecho.core.context.EncryptionContext} for
* ChaCha20 and ChaCha20-Poly1305. Provides a streaming
* {@link java.io.InputStream}-based interface for encryption and decryption,
* with support for nonce management and optional {@link SymmetricHeaderCodec}
* headers.
*
* <h3>Features</h3>
* <ul>
* <li>Automatic 12-byte nonce generation (encryption) or validation
* (decryption).</li>
* <li>Optional header encoding/decoding via {@link SymmetricHeaderCodec} bound
* in the spec.</li>
* <li>Streaming transformation using {@link javax.crypto.Cipher} with staged
* {@code doFinal()}.</li>
* <li>Integration with {@link ConfluxKeys} for propagating IV/nonce
* values.</li>
* </ul>
*
* <h3>Subclass responsibilities</h3>
* <ul>
* <li>Implement {@link #jceName()} to return a JCE transformation string (e.g.,
* {@code "ChaCha20"} or {@code "ChaCha20-Poly1305"}).</li>
* <li>Implement {@link #initCipher(Cipher, byte[])} to configure parameters and
* AAD if required.</li>
* </ul>
*
* <h3>Usage</h3> <pre>{@code
* EncryptionContext enc = new ChaCha20Poly1305Context(algo, key, true, spec, null);
* InputStream ciphertext = enc.attach(plaintextStream);
*
* EncryptionContext dec = new ChaCha20Poly1305Context(algo, key, false, spec, null);
* InputStream plaintext = dec.attach(ciphertext);
* }</pre>
*
* @param <S> specification type carrying ChaCha parameters
* @since 1.0
*/
abstract class AbstractChaChaCipherContext<S extends ChaChaBaseSpec> implements EncryptionContext, ContextAware {
/** Required ChaCha nonce length (12 bytes). */
private static final int NONCE_LEN = 12; // both variants
/** Algorithm definition that created this context. */
protected final CryptoAlgorithm algorithm;
/** Symmetric key used by this context. */
protected final SecretKey key;
/**
* Operation mode flag - {@code true} for encryption, {@code false} for
* decryption.
*/
protected final boolean encrypt;
/** Algorithm-specific specification (e.g., AEAD parameters). */
protected final S spec;
/** Secure random source for nonce generation. */
protected final SecureRandom rnd;
/** Optional per-operation context for exchanging headers, IVs, etc. */
protected CtxInterface ctx; // optional
/**
* Creates a new ChaCha cipher context.
*
* @param algorithm parent algorithm
* @param key ChaCha secret key
* @param encrypt {@code true} for encryption, {@code false} for decryption
* @param spec ChaCha-specific context specification
* @param rnd source of randomness for nonces (uses default if null)
*/
protected AbstractChaChaCipherContext(CryptoAlgorithm algorithm, SecretKey key, boolean encrypt, S spec,
SecureRandom rnd) {
this.algorithm = algorithm;
this.key = key;
this.encrypt = encrypt;
this.spec = spec;
this.rnd = (rnd != null ? rnd : new SecureRandom());
}
/** {@inheritDoc} */
@Override
public CryptoAlgorithm algorithm() {
return algorithm;
}
/** {@inheritDoc} */
@Override
public java.security.Key key() {
return key;
}
/** {@inheritDoc} */
@Override
public void setContext(CtxInterface context) {
this.ctx = context;
}
/** {@inheritDoc} */
@Override
public CtxInterface context() {
return ctx;
}
/**
* Returns the JCE transformation string, e.g. "ChaCha20" or
* "ChaCha20-Poly1305".
*
* @return transformation string for
* {@link javax.crypto.Cipher#getInstance(String)}
*/
protected abstract String jceName();
/**
* Initializes the cipher with algorithm-specific parameters and optional AAD.
*
* @param cipher configured cipher instance
* @param nonce 12-byte nonce generated or supplied from context
* @throws GeneralSecurityException if parameter initialization fails
* @throws IOException if AAD setup or parameter resolution fails
*/
protected abstract void initCipher(Cipher cipher, byte[] nonce) throws GeneralSecurityException, IOException;
/**
* Attaches this context to an upstream input stream and returns a
* transformation stream.
*
* <p>
* Encryption prepends optional headers; decryption consumes headers first.
* </p>
*
* @param upstream plaintext or ciphertext input stream
* @return transformed stream (ciphertext for encryption, plaintext for
* decryption)
* @throws IOException if cipher initialization fails
*/
@Override
public InputStream attach(InputStream upstream) throws IOException {
try {
final SymmetricHeaderCodec header = spec.header();
final boolean hasCtxHeader = ctx != null && header != null;
InputStream in = upstream;
if (!encrypt && hasCtxHeader) {
in = header.readHeader(in, algorithm, ctx);
}
final Cipher cipher = Cipher.getInstance(jceName());
final byte[] nonce = ensureNonce(); // generate or require from ctx
initCipher(cipher, nonce);
InputStream out = // new Stream(in, cipher, jceName()); // same stream pattern as AES
CipherTransformInputStreamBuilder.builder().withUpstream(in).withCipher(cipher)
.withUpdateStreaming().withInputBlockSize(64 /* chacha block */ ).withOutputBlockSize(64)
.withBufferedBlocks(100).withFinalizationOutputChunks(2).build();
if (encrypt && hasCtxHeader) {
final ByteArrayOutputStream hdr = new ByteArrayOutputStream(48);
header.writeHeader(hdr, algorithm, ctx);
out = new SequenceInputStream(new ByteArrayInputStream(hdr.toByteArray()), out);
}
return out;
} catch (GeneralSecurityException e) {
throw new ProviderFailureException(jceName() + " attach/init failed", e);
}
}
/**
* Releases context resources.
* <p>
* No-op for ChaCha contexts.
* </p>
*/
@Override
public void close() { // NOPMD
/* no-op */
}
/**
* Ensures a nonce is available in the context.
*
* <ul>
* <li>For encryption, generates a new nonce if absent and stores it in
* context.</li>
* <li>For decryption, validates presence and correct length.</li>
* </ul>
*
* @return 12-byte nonce
* @throws IOException if nonce is missing or invalid
*/
private byte[] ensureNonce() throws IOException {
final String id = algorithm.id();
byte[] nonce = (ctx == null) ? null : ctx.get(ConfluxKeys.iv(id));
if (encrypt) {
if (nonce == null) {
nonce = new byte[NONCE_LEN];
rnd.nextBytes(nonce);
if (ctx != null) {
ctx.put(ConfluxKeys.iv(id), nonce);
}
} else if (nonce.length != NONCE_LEN) {
throw new IOException("Nonce length mismatch: expected 12 bytes, got " + nonce.length);
}
} else {
if (nonce == null || nonce.length != NONCE_LEN) {
throw new IOException("Nonce missing/invalid for " + jceName() + " decryption");
}
}
return nonce;
}
}

View File

@@ -0,0 +1,128 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
import zeroecho.core.SymmetricHeaderCodec;
/**
* <h2>ChaCha20-Poly1305 (AEAD) algorithm</h2>
*
* Registers the {@code ChaCha20-Poly1305} AEAD cipher within the ZeroEcho
* framework. Extends {@link AbstractChaChaAlgorithm} and declares symmetric
* capabilities for encryption and decryption using either
* {@link ChaCha20Poly1305Spec} or {@link zeroecho.core.spec.VoidSpec}
* (convenience default mirroring AES-GCM).
*
* <h3>Capabilities</h3>
* <ul>
* <li><b>Encrypt</b>:
* <ul>
* <li>Family: {@link zeroecho.core.AlgorithmFamily#SYMMETRIC}</li>
* <li>Usage: {@link zeroecho.core.KeyUsage#ENCRYPT}</li>
* <li>Context: {@link zeroecho.core.context.EncryptionContext}</li>
* <li>Key: {@link javax.crypto.SecretKey}</li>
* <li>Spec: {@link ChaCha20Poly1305Spec} (or
* {@link zeroecho.core.spec.VoidSpec} default)</li>
* </ul>
* </li>
* <li><b>Decrypt</b>:
* <ul>
* <li>Family: {@link zeroecho.core.AlgorithmFamily#SYMMETRIC}</li>
* <li>Usage: {@link zeroecho.core.KeyUsage#DECRYPT}</li>
* <li>Context: {@link zeroecho.core.context.EncryptionContext}</li>
* <li>Key: {@link javax.crypto.SecretKey}</li>
* <li>Spec: {@link ChaCha20Poly1305Spec} (or
* {@link zeroecho.core.spec.VoidSpec} default)</li>
* </ul>
* </li>
* </ul>
*
* <h3>Defaults</h3> When used with {@link zeroecho.core.spec.VoidSpec}, a
* minimal {@link ChaCha20Poly1305Spec} is synthesized with no
* {@link SymmetricHeaderCodec} header. Nonces are 12 bytes and managed by the
* corresponding cipher context.
*
* <h3>Example</h3> <pre>{@code
* var algo = new ChaCha20Poly1305Algorithm();
* SecretKey key = algo.generateSecret(ChaChaKeyGenSpec.chacha256());
*
* // Encrypt with explicit spec
* var spec = ChaCha20Poly1305Spec.builder().header(null).build();
* EncryptionContext enc = algo.newContext(
* zeroecho.core.AlgorithmFamily.SYMMETRIC,
* zeroecho.core.KeyUsage.ENCRYPT, key, spec);
*
* // Decrypt using VoidSpec default
* EncryptionContext dec = algo.newContext(
* zeroecho.core.AlgorithmFamily.SYMMETRIC,
* zeroecho.core.KeyUsage.DECRYPT, key, zeroecho.core.spec.VoidSpec.INSTANCE);
* }</pre>
*
* @since 1.0
*/
public final class ChaCha20Poly1305Algorithm extends AbstractChaChaAlgorithm {
/**
* Creates and registers the ChaCha20-Poly1305 AEAD algorithm with
* encryption/decryption capabilities for {@link ChaCha20Poly1305Spec} and
* {@link zeroecho.core.spec.VoidSpec} defaults.
*/
public ChaCha20Poly1305Algorithm() {
super("CHACHA20-POLY1305", "ChaCha20-Poly1305 (AEAD)");
capability(zeroecho.core.AlgorithmFamily.SYMMETRIC, zeroecho.core.KeyUsage.ENCRYPT,
zeroecho.core.context.EncryptionContext.class, javax.crypto.SecretKey.class, ChaCha20Poly1305Spec.class,
(k, s) -> new ChaCha20Poly1305CipherContext(this, k, true, s, new java.security.SecureRandom()),
() -> ChaCha20Poly1305Spec.builder().header(null).build());
capability(zeroecho.core.AlgorithmFamily.SYMMETRIC, zeroecho.core.KeyUsage.DECRYPT,
zeroecho.core.context.EncryptionContext.class, javax.crypto.SecretKey.class, ChaCha20Poly1305Spec.class,
(k, s) -> new ChaCha20Poly1305CipherContext(this, k, false, s, new java.security.SecureRandom()),
() -> ChaCha20Poly1305Spec.builder().header(null).build());
// VoidSpec defaults like AES-GCM
capability(zeroecho.core.AlgorithmFamily.SYMMETRIC, zeroecho.core.KeyUsage.ENCRYPT,
zeroecho.core.context.EncryptionContext.class, javax.crypto.SecretKey.class,
zeroecho.core.spec.VoidSpec.class,
(k, v) -> new ChaCha20Poly1305CipherContext(this, k, true,
ChaCha20Poly1305Spec.builder().header(null).build(), new java.security.SecureRandom()),
() -> zeroecho.core.spec.VoidSpec.INSTANCE);
capability(zeroecho.core.AlgorithmFamily.SYMMETRIC, zeroecho.core.KeyUsage.DECRYPT,
zeroecho.core.context.EncryptionContext.class, javax.crypto.SecretKey.class,
zeroecho.core.spec.VoidSpec.class,
(k, v) -> new ChaCha20Poly1305CipherContext(this, k, false,
ChaCha20Poly1305Spec.builder().header(null).build(), new java.security.SecureRandom()),
() -> zeroecho.core.spec.VoidSpec.INSTANCE);
}
}

View File

@@ -0,0 +1,133 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
import java.io.IOException;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import zeroecho.core.ConfluxKeys;
import zeroecho.core.CryptoAlgorithm;
/**
* <h2>ChaCha20-Poly1305 cipher context (AEAD)</h2>
*
* Concrete {@link zeroecho.core.context.EncryptionContext} for the
* {@code ChaCha20-Poly1305} AEAD construction. Configures a JCE
* {@link javax.crypto.Cipher} with a 12-byte nonce (managed by the parent
* {@link AbstractChaChaCipherContext}) and applies optional AAD obtained from
* {@link ConfluxKeys#aad(String)} via the bound context.
*
* <h3>Behavior</h3>
* <ul>
* <li>Uses transformation {@code "ChaCha20-Poly1305"}.</li>
* <li>Initializes with {@link javax.crypto.spec.IvParameterSpec} for the
* 12-byte nonce.</li>
* <li>Supplies Additional Authenticated Data (AAD) from the active context if
* present.</li>
* </ul>
*
* <h3>Usage</h3> <pre>{@code
* CryptoAlgorithm alg = ...;
* SecretKey key = ...; // ChaCha20 key (256-bit)
* ChaCha20Poly1305Spec spec = ChaCha20Poly1305Spec.builder().header(null).build();
*
* // Encrypt
* EncryptionContext enc = new ChaCha20Poly1305CipherContext(alg, key, true, spec, new SecureRandom());
*
* // Decrypt
* EncryptionContext dec = new ChaCha20Poly1305CipherContext(alg, key, false, spec, new SecureRandom());
* }</pre>
*
* @since 1.0
*/
final class ChaCha20Poly1305CipherContext extends AbstractChaChaCipherContext<ChaCha20Poly1305Spec> {
/**
* Creates a ChaCha20-Poly1305 context.
*
* @param alg algorithm definition
* @param key ChaCha20 secret key
* @param enc {@code true} for encryption, {@code false} for decryption
* @param spec algorithm-specific parameters
* @param rnd randomness source for nonce generation
*/
protected ChaCha20Poly1305CipherContext(CryptoAlgorithm alg, SecretKey key, boolean enc, ChaCha20Poly1305Spec spec,
java.security.SecureRandom rnd) {
super(alg, key, enc, spec, rnd);
}
/**
* Returns {@code "ChaCha20-Poly1305"} as the JCE transformation.
*
* @return transformation string
*/
@Override
protected String jceName() {
return "ChaCha20-Poly1305";
}
/**
* Initializes the cipher for the current mode with the supplied nonce and
* optional AAD.
*
* <p>
* Uses {@link javax.crypto.spec.IvParameterSpec} for the 12-byte nonce and, if
* present, applies AAD retrieved from the bound context under
* {@link ConfluxKeys#aad(String)}.
* </p>
*
* @param cipher configured cipher instance
* @param nonce 12-byte nonce value
* @throws java.security.GeneralSecurityException if cipher initialization fails
* @throws java.io.IOException if AAD retrieval/processing
* fails
*/
@Override
protected void initCipher(Cipher cipher, byte[] nonce) throws GeneralSecurityException, IOException {
cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, new IvParameterSpec(nonce));
// AAD handling mirrors your AES-GCM path via ConfluxKeys.aad(id).
//
final String id = algorithm.id();
byte[] aad = (context() == null) ? null : context().get(ConfluxKeys.aad(id));
if (aad == null) {
aad = new byte[0];
}
cipher.updateAAD(aad);
}
}

View File

@@ -0,0 +1,180 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import conflux.CtxInterface;
import zeroecho.core.ConfluxKeys;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.SymmetricHeaderCodec;
import zeroecho.core.io.Util;
/**
* <h2>ChaCha20-Poly1305 streaming header codec</h2>
*
* Implements {@link SymmetricHeaderCodec} for the ChaCha20-Poly1305 AEAD mode.
* Encodes a compact header that precedes the ciphertext stream and conveys:
* <ul>
* <li>a 12-byte nonce (IV) required by ChaCha20-Poly1305, and</li>
* <li>an optional SHA-256 hash of AAD to assert integrity of externally
* supplied AAD.</li>
* </ul>
*
* <p>
* The nonce and AAD are exchanged via the bound {@link CtxInterface} using
* {@link ConfluxKeys#iv(String)} and {@link ConfluxKeys#aad(String)} keys,
* respectively. On encryption, the codec reads these values from the context
* and writes the header. On decryption, it restores the nonce into the context
* and, when present, verifies the supplied AAD by comparing its hash to the
* header.
* </p>
*
* <h3>Header layout</h3> <pre>
* [0..11] : 12-byte nonce (IV)
* [12] : 1-byte AAD flag (0 = none, 1 = present)
* [13..44] : 32-byte SHA-256(AAD) if flag == 1
* </pre>
*
* <h3>Failure modes</h3>
* <ul>
* <li>Missing/invalid nonce in context when writing the header.</li>
* <li>AAD expected by header but not provided in context during read.</li>
* <li>AAD hash mismatch when verifying during read.</li>
* </ul>
*
* @since 1.0
*/
public final class ChaCha20Poly1305HeaderCodec implements SymmetricHeaderCodec {
/**
* Logger for debug-level diagnostics of header encode/decode operations.
*/
private static final Logger LOG = Logger.getLogger(ChaCha20Poly1305HeaderCodec.class.getName());
/**
* Required nonce length for ChaCha20-Poly1305 headers (12 bytes).
*/
private static final int NONCE_LEN = 12;
/**
* Writes the ChaCha20-Poly1305 header to the provided stream.
*
* <p>
* Reads the 12-byte nonce and optional AAD from {@code ctx}. If AAD is
* non-empty, its SHA-256 is written after a presence flag. Flushes the output
* upon completion.
* </p>
*
* @param out destination stream to receive the header
* @param algorithm the algorithm instance (used for context key scoping)
* @param ctx operation context carrying nonce and optional AAD
* @throws java.io.IOException if the nonce is missing/invalid or I/O fails
*/
@Override
public void writeHeader(OutputStream out, CryptoAlgorithm algorithm, CtxInterface ctx) throws IOException {
final String id = algorithm.id(); // "CHACHA20-POLY1305"
LOG.log(Level.FINE, "writeHeader={0}", id);
byte[] nonce = ctx.get(ConfluxKeys.iv(id));
if (nonce == null || nonce.length != NONCE_LEN) {
throw new IOException("ChaCha20-Poly1305 header: nonce missing/invalid in Ctx");
}
byte[] aad = ctx.get(ConfluxKeys.aad(id));
byte[] aadHash = (aad == null || aad.length == 0) ? null : sha256(aad);
Util.write(out, nonce); // 12 bytes
out.write(aadHash == null ? 0 : 1);
if (aadHash != null) {
out.write(aadHash);
}
out.flush();
}
/**
* Reads and validates the ChaCha20-Poly1305 header from the provided stream.
*
* <p>
* Restores the 12-byte nonce into {@code ctx}. If the header signals AAD
* presence, computes SHA-256 over the AAD obtained from {@code ctx} and
* verifies it against the header hash.
* </p>
*
* @param in source stream containing the header and subsequent payload
* @param algorithm the algorithm instance (used for context key scoping)
* @param ctx operation context to populate (nonce) and validate (AAD)
* @return the same {@code in} stream positioned after the header
* @throws java.io.IOException if the header is malformed, AAD is missing when
* required, the AAD hash mismatches, or I/O fails
*/
@Override
public InputStream readHeader(InputStream in, CryptoAlgorithm algorithm, CtxInterface ctx) throws IOException {
final String id = algorithm.id();
LOG.log(Level.FINE, "readHeader={0}", id);
byte[] nonce = Util.read(in, NONCE_LEN);
int aadFlag = in.read();
byte[] aadHash = null;
if (aadFlag == 1) { // NOPMD
aadHash = in.readNBytes(32);
}
// hydrate Ctx
ctx.put(ConfluxKeys.iv(id), nonce);
if (aadHash != null) {
byte[] aad = ctx.get(ConfluxKeys.aad(id));
if (aad == null || aad.length == 0) {
throw new IOException("ChaCha20-Poly1305 header expects AAD, but none provided in Ctx");
}
if (!Arrays.equals(aadHash, sha256(aad))) {
throw new IOException("ChaCha20-Poly1305 header: AAD hash mismatch");
}
}
return in;
}
private static byte[] sha256(byte[] a) throws IOException {
try {
return MessageDigest.getInstance("SHA-256").digest(a);
} catch (NoSuchAlgorithmException e) {
throw new IOException("SHA-256 unavailable", e);
}
}
}

View File

@@ -0,0 +1,146 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
import zeroecho.core.SymmetricHeaderCodec;
import zeroecho.core.annotation.Describable;
/**
* <h2>ChaCha20-Poly1305 algorithm specification</h2>
*
* Immutable parameters for configuring a ChaCha20-Poly1305 operation.
* Optionally carries a {@link SymmetricHeaderCodec} to prepend/parse per-stream
* headers (e.g., nonce and AAD hash) during encryption/decryption.
*
* <h3>Notes</h3>
* <ul>
* <li>If {@link #header()} is {@code null}, no header is written or read;
* callers must exchange the nonce/AAD out-of-band via the context.</li>
* <li>The effective authentication tag size is 128 bits.</li>
* </ul>
*
* <h3>Example</h3> <pre>{@code
* ChaCha20Poly1305Spec spec = ChaCha20Poly1305Spec.builder()
* .header(new ChaCha20Poly1305HeaderCodec())
* .build();
* }</pre>
*
* @since 1.0
*/
public final class ChaCha20Poly1305Spec implements ChaChaBaseSpec, Describable {
/**
* Optional streaming header codec used to serialize/deserialize the per-stream
* parameters (e.g., nonce and AAD hash). When {@code null}, no header is used.
*/
private final SymmetricHeaderCodec header; // optional
/**
* Creates a specification instance.
*
* @param header optional {@link SymmetricHeaderCodec}; may be {@code null}
*/
private ChaCha20Poly1305Spec(SymmetricHeaderCodec header) {
this.header = header; // may be null
}
/**
* Returns a new builder for {@link ChaCha20Poly1305Spec}.
*
* @return a fresh {@link Builder}
*/
public static Builder builder() {
return new Builder();
}
/**
* Fluent builder for {@link ChaCha20Poly1305Spec}.
*/
public static final class Builder {
/**
* Header codec to embed/parse per-stream parameters. May be {@code null}.
*/
private SymmetricHeaderCodec header;
/**
* Sets an optional streaming header codec.
*
* @param codec header codec to use, or {@code null} to disable headers
* @return this builder
*/
public Builder header(SymmetricHeaderCodec codec) {
this.header = codec;
return this;
}
/**
* Builds an immutable {@link ChaCha20Poly1305Spec}.
*
* @return the constructed spec
*/
public ChaCha20Poly1305Spec build() {
return new ChaCha20Poly1305Spec(header);
}
}
/**
* Convenience factory that returns a spec with the provided header codec.
*
* @param header optional header codec; may be {@code null}
* @return a new {@code ChaCha20Poly1305Spec} configured with {@code header}
*/
public static ChaCha20Poly1305Spec withHeader(SymmetricHeaderCodec header) {
return builder().header(header).build();
}
/**
* Returns the optional header codec.
*
* @return header codec or {@code null} if none
*/
@Override
public SymmetricHeaderCodec header() {
return header;
}
/**
* Human-readable description of this spec.
*
* @return {@code "ChaCha20-Poly1305(tag=128)"}
*/
@Override
public String description() {
return "ChaCha20-Poly1305(tag=128)";
}
}

View File

@@ -0,0 +1,109 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
/**
* <h2>ChaCha20 (stream) algorithm</h2>
*
* Registers the {@code ChaCha20} stream cipher within the ZeroEcho framework.
* Extends {@link AbstractChaChaAlgorithm} and installs symmetric capabilities
* for encryption and decryption using {@link ChaChaSpec}, with convenience
* defaults for {@link zeroecho.core.spec.VoidSpec}.
*
* <h3>Capabilities</h3>
* <ul>
* <li><b>Encrypt</b>
* <ul>
* <li>Family: {@link zeroecho.core.AlgorithmFamily#SYMMETRIC}</li>
* <li>Usage: {@link zeroecho.core.KeyUsage#ENCRYPT}</li>
* <li>Context: {@link zeroecho.core.context.EncryptionContext}</li>
* <li>Key: {@link javax.crypto.SecretKey}</li>
* <li>Spec: {@link ChaChaSpec} (or {@link zeroecho.core.spec.VoidSpec}
* default)</li>
* </ul>
* </li>
* <li><b>Decrypt</b>
* <ul>
* <li>Family: {@link zeroecho.core.AlgorithmFamily#SYMMETRIC}</li>
* <li>Usage: {@link zeroecho.core.KeyUsage#DECRYPT}</li>
* <li>Context: {@link zeroecho.core.context.EncryptionContext}</li>
* <li>Key: {@link javax.crypto.SecretKey}</li>
* <li>Spec: {@link ChaChaSpec} (or {@link zeroecho.core.spec.VoidSpec}
* default)</li>
* </ul>
* </li>
* </ul>
*
* <h3>Defaults</h3> When used with {@link zeroecho.core.spec.VoidSpec}, a
* minimal {@link ChaChaSpec} is synthesized with {@code initialCounter(1)} and
* no header.
*
* @since 1.0
*/
public final class ChaChaAlgorithm extends AbstractChaChaAlgorithm {
/**
* Creates and registers the ChaCha20 stream cipher, declaring encryption and
* decryption capabilities for {@link ChaChaSpec} and
* {@link zeroecho.core.spec.VoidSpec}.
*/
public ChaChaAlgorithm() {
super("CHACHA20", "ChaCha20 (stream)");
// ENCRYPT
capability(zeroecho.core.AlgorithmFamily.SYMMETRIC, zeroecho.core.KeyUsage.ENCRYPT,
zeroecho.core.context.EncryptionContext.class, javax.crypto.SecretKey.class, ChaChaSpec.class,
(k, s) -> new ChaChaCipherContext(this, k, true, s, new java.security.SecureRandom()),
() -> ChaChaSpec.builder().initialCounter(1).header(null).build());
// DECRYPT
capability(zeroecho.core.AlgorithmFamily.SYMMETRIC, zeroecho.core.KeyUsage.DECRYPT,
zeroecho.core.context.EncryptionContext.class, javax.crypto.SecretKey.class, ChaChaSpec.class,
(k, s) -> new ChaChaCipherContext(this, k, false, s, new java.security.SecureRandom()),
() -> ChaChaSpec.builder().initialCounter(1).header(null).build());
// VoidSpec defaults (mirrors AES)
capability(zeroecho.core.AlgorithmFamily.SYMMETRIC, zeroecho.core.KeyUsage.ENCRYPT,
zeroecho.core.context.EncryptionContext.class, javax.crypto.SecretKey.class,
zeroecho.core.spec.VoidSpec.class,
(k, v) -> new ChaChaCipherContext(this, k, true,
ChaChaSpec.builder().initialCounter(1).header(null).build(), new java.security.SecureRandom()),
() -> zeroecho.core.spec.VoidSpec.INSTANCE);
capability(zeroecho.core.AlgorithmFamily.SYMMETRIC, zeroecho.core.KeyUsage.DECRYPT,
zeroecho.core.context.EncryptionContext.class, javax.crypto.SecretKey.class,
zeroecho.core.spec.VoidSpec.class,
(k, v) -> new ChaChaCipherContext(this, k, false,
ChaChaSpec.builder().initialCounter(1).header(null).build(), new java.security.SecureRandom()),
() -> zeroecho.core.spec.VoidSpec.INSTANCE);
}
}

View File

@@ -0,0 +1,77 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
import zeroecho.core.SymmetricHeaderCodec;
import zeroecho.core.spec.ContextSpec;
/**
* <h2>ChaCha base specification marker</h2>
*
* Common sealed interface for all ChaCha-family specifications. Implemented by
* {@link ChaChaSpec} (raw stream cipher) and {@link ChaCha20Poly1305Spec}
* (AEAD).
*
* <p>
* Extends {@link zeroecho.core.spec.ContextSpec} to allow binding
* algorithm-specific parameters into a context.
* </p>
*
* <h3>Header support</h3>
* <ul>
* <li>{@link #header()} may return a {@link SymmetricHeaderCodec} that encodes
* parameters (e.g., nonce, AAD hash) into the ciphertext stream.</li>
* <li>If {@code null}, no header is used and parameters must be managed via
* {@code CtxInterface} or other out-of-band means.</li>
* </ul>
*
* <h3>Example</h3> <pre>{@code
* ChaChaBaseSpec spec = ChaCha20Poly1305Spec.withHeader(
* new ChaCha20Poly1305HeaderCodec()
* );
* SymmetricHeaderCodec codec = spec.header(); // non-null
* }</pre>
*
* @since 1.0
*/
public sealed interface ChaChaBaseSpec extends ContextSpec permits ChaChaSpec, ChaCha20Poly1305Spec {
/**
* Returns the optional header codec used to serialize/deserialize stream
* headers for this ChaCha mode.
*
* @return {@link SymmetricHeaderCodec} instance, or {@code null} if no header
*/
SymmetricHeaderCodec header(); // may be null
}

View File

@@ -0,0 +1,128 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
import java.io.IOException;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.ChaCha20ParameterSpec;
import conflux.CtxInterface;
import zeroecho.core.ConfluxKeys;
import zeroecho.core.CryptoAlgorithm;
/**
* <h2>ChaCha20 cipher context (stream)</h2>
*
* Concrete {@link zeroecho.core.context.EncryptionContext} for the
* {@code ChaCha20} stream cipher. Relies on the parent
* {@link AbstractChaChaCipherContext} for streaming, nonce management, and
* optional header handling, while configuring ChaCha20-specific parameters:
* <ul>
* <li>Transformation: {@code "ChaCha20"}.</li>
* <li>12-byte nonce via {@link javax.crypto.spec.ChaCha20ParameterSpec}.</li>
* <li>Initial counter sourced from {@link ChaChaSpec#initialCounter()},
* optionally overridden via {@link ConfluxKeys#tagBits(String)} in the bound
* context.</li>
* </ul>
*
* <h3>Counter handling</h3> On attach, the counter is taken from the spec; if
* the active context contains an integer under {@code ConfluxKeys.tagBits(id)},
* that value overrides the spec and is used to initialize the cipher counter.
* If absent, the spec's value is stored into the context for downstream
* consumers.
*
* @since 1.0
*/
public final class ChaChaCipherContext extends AbstractChaChaCipherContext<ChaChaSpec> {
/**
* Creates a ChaCha20 context.
*
* @param alg algorithm definition
* @param key ChaCha20 secret key
* @param enc {@code true} for encryption, {@code false} for decryption
* @param spec ChaCha20 stream specification (includes initial counter)
* @param rnd randomness source for nonce generation
*/
public ChaChaCipherContext(CryptoAlgorithm alg, SecretKey key, boolean enc, ChaChaSpec spec,
java.security.SecureRandom rnd) {
super(alg, key, enc, spec, rnd);
}
/**
* Returns {@code "ChaCha20"} as the JCE transformation.
*
* @return transformation string
*/
@Override
protected String jceName() {
return "ChaCha20";
}
/**
* Initializes the cipher in the configured mode with the supplied nonce and
* counter.
*
* <p>
* Uses {@link javax.crypto.spec.ChaCha20ParameterSpec} with a 12-byte nonce and
* an initial counter taken from {@link ChaChaSpec#initialCounter()} or, if
* present, from {@link ConfluxKeys#tagBits(String)} in the bound context.
* </p>
*
* @param cipher initialized cipher instance
* @param nonce 12-byte nonce value
* @throws java.security.GeneralSecurityException if cipher initialization fails
* @throws java.io.IOException if context access or parameter
* resolution fails
*/
@Override
protected void initCipher(Cipher cipher, byte[] nonce) throws GeneralSecurityException, IOException {
final String id = algorithm.id();
int counter = spec.initialCounter();
CtxInterface c = context();
if (c != null) {
Integer ctxCtr = c.get(ConfluxKeys.tagBits(id));
if (ctxCtr != null) {
counter = ctxCtr;
} else {
c.put(ConfluxKeys.tagBits(id), counter);
}
}
cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key,
new ChaCha20ParameterSpec(nonce, counter));
}
}

View File

@@ -0,0 +1,139 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import conflux.CtxInterface;
import zeroecho.core.ConfluxKeys;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.SymmetricHeaderCodec;
import zeroecho.core.io.Util;
/**
* <h2>ChaCha20 streaming header codec</h2>
*
* Implements {@link SymmetricHeaderCodec} for the {@code ChaCha20} stream
* cipher. Encodes a compact header containing:
* <ul>
* <li>a 12-byte nonce (IV), and</li>
* <li>the stream counter as a 7-bit packed integer.</li>
* </ul>
*
* <p>
* The nonce and counter are exchanged through the bound {@link CtxInterface}
* using {@link ConfluxKeys#iv(String)} and {@link ConfluxKeys#tagBits(String)}
* keys, namespaced by the algorithm id (e.g., {@code "CHACHA20"}).
* </p>
*
* <h3>Header layout</h3> <pre>
* [0..11] : 12-byte nonce (IV)
* [12..N] : counter encoded via 7-bit packed integer
* </pre>
*
* <h3>Behavior</h3>
* <ul>
* <li><b>writeHeader</b>: requires {@code iv(id)} in context (12 bytes). Writes
* the nonce, then the counter from {@code tagBits(id)} if present, otherwise
* uses {@code 1}.</li>
* <li><b>readHeader</b>: reads nonce and counter and stores them into the
* context under the same keys.</li>
* </ul>
*
* @since 1.0
*/
public final class ChaChaHeaderCodec implements SymmetricHeaderCodec {
/**
* Required nonce length for ChaCha20 headers (12 bytes).
*/
private static final int NONCE_LEN = 12;
/**
* Writes the ChaCha20 header to the provided output.
*
* <p>
* Reads a 12-byte nonce from {@link ConfluxKeys#iv(String)} and a stream
* counter from {@link ConfluxKeys#tagBits(String)} (defaulting to {@code 1} if
* absent). Emits the nonce followed by the counter encoded as a 7-bit packed
* integer, then flushes.
* </p>
*
* @param out destination stream
* @param algorithm algorithm instance used for context key scoping
* @param ctx operation context carrying nonce and optional counter
* @throws java.io.IOException if the nonce is missing/invalid or I/O fails
*/
@Override
public void writeHeader(OutputStream out, CryptoAlgorithm algorithm, CtxInterface ctx) throws IOException {
final String id = algorithm.id(); // "CHACHA20"
byte[] nonce = ctx.get(ConfluxKeys.iv(id));
if (nonce == null || nonce.length != NONCE_LEN) {
throw new IOException("ChaChaHeaderCodec: nonce missing/invalid in Ctx");
}
Integer ctr = ctx.get(ConfluxKeys.tagBits(id));
int counter = ctr == null ? 1 : ctr;
Util.write(out, nonce);
Util.writePack7I(out, counter);
out.flush();
}
/**
* Reads a ChaCha20 header from the input and hydrates the context.
*
* <p>
* Consumes a 12-byte nonce and a 7-bit packed counter, then stores them into
* {@code ctx} under {@link ConfluxKeys#iv(String)} and
* {@link ConfluxKeys#tagBits(String)}, respectively. Returns the same input
* stream positioned after the header.
* </p>
*
* @param in source stream
* @param algorithm algorithm instance used for context key scoping
* @param ctx context to populate with nonce and counter
* @return the input stream positioned after the header
* @throws java.io.IOException if the header is malformed or I/O fails
*/
@Override
public InputStream readHeader(InputStream in, CryptoAlgorithm algorithm, CtxInterface ctx) throws IOException {
final String id = algorithm.id();
byte[] nonce = Util.read(in, NONCE_LEN);
int counter = Util.readPack7I(in);
ctx.put(ConfluxKeys.iv(id), nonce);
ctx.put(ConfluxKeys.tagBits(id), counter);
return in;
}
}

View File

@@ -0,0 +1,97 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
import zeroecho.core.annotation.Describable;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>ChaCha20 key generation specification</h2>
*
* Immutable record describing the key size for ChaCha20 key generation. Only
* 256-bit keys are permitted by the ChaCha20 design.
*
* <h3>Validation</h3>
* <ul>
* <li>The constructor enforces {@code keySizeBits == 256}.</li>
* <li>Any other size results in an {@link IllegalArgumentException}.</li>
* </ul>
*
* <h3>Factory method</h3>
* <ul>
* <li>{@link #chacha256()} provides a convenient way to obtain a standard
* 256-bit spec.</li>
* </ul>
*
* <h3>Usage example</h3> <pre>{@code
* ChaChaKeyGenSpec spec = ChaChaKeyGenSpec.chacha256();
* SecretKey key = cryptoAlgorithm.generateSecret(spec);
* }</pre>
*
* @param keySizeBits key size in bits (must be 256)
* @since 1.0
*/
public record ChaChaKeyGenSpec(int keySizeBits) implements AlgorithmKeySpec, Describable {
/**
* Constructs a new ChaCha20 key generation spec.
*
* @param keySizeBits must be 256; otherwise an exception is thrown
* @throws IllegalArgumentException if {@code keySizeBits != 256}
*/
public ChaChaKeyGenSpec {
if (keySizeBits != 256) { // NOPMD
throw new IllegalArgumentException("ChaCha20 keySizeBits must be 256");
}
}
/**
* Returns the standard 256-bit key generation spec.
*
* @return a new {@code ChaChaKeyGenSpec} with size 256
*/
public static ChaChaKeyGenSpec chacha256() {
return new ChaChaKeyGenSpec(256);
}
/**
* Returns a short human-readable description of this spec.
*
* @return the string {@code "256bits"}
*/
@Override
public String description() {
return "256bits";
}
}

View File

@@ -0,0 +1,184 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.HexFormat;
import java.util.Objects;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>ChaCha20 key import specification</h2>
*
* Wraps a raw ChaCha20 key for import into the ZeroEcho framework. Keys must be
* exactly 32 bytes (256 bits).
*
* <h3>Construction</h3>
* <ul>
* <li>{@link #fromRaw(byte[])} - construct from a raw byte array.</li>
* <li>{@link #fromHex(String)} - construct from a hexadecimal string.</li>
* <li>{@link #fromBase64(String)} - construct from a Base64 string.</li>
* </ul>
*
* <h3>Marshalling</h3> Keys can be serialized/deserialized using
* {@link PairSeq}:
* <ul>
* <li>{@link #marshal(ChaChaKeyImportSpec)} encodes the key as Base64.</li>
* <li>{@link #unmarshal(PairSeq)} accepts fields {@code k.b64}, {@code k.hex},
* or {@code k.raw} (ISO-8859-1) to restore a spec.</li>
* </ul>
*
* <h3>Usage</h3> <pre>{@code
* // Import from raw key bytes
* ChaChaKeyImportSpec spec = ChaChaKeyImportSpec.fromRaw(keyBytes);
* SecretKey key = cryptoAlgorithm.importSecret(spec);
*
* // Serialize to PairSeq
* PairSeq seq = ChaChaKeyImportSpec.marshal(spec);
*
* // Deserialize back
* ChaChaKeyImportSpec restored = ChaChaKeyImportSpec.unmarshal(seq);
* }</pre>
*
* @since 1.0
*/
public final class ChaChaKeyImportSpec implements AlgorithmKeySpec {
private final byte[] key;
/**
* Creates a new import spec with the given raw key.
*
* @param key raw 32-byte key
* @throws NullPointerException if {@code key} is null
* @throws IllegalArgumentException if {@code key.length != 32}
*/
private ChaChaKeyImportSpec(byte[] key) {
Objects.requireNonNull(key, "key must not be null");
if (key.length != 32) { // NOPMD
throw new IllegalArgumentException("ChaCha20 key must be 32 bytes, got " + key.length);
}
this.key = Arrays.copyOf(key, 32);
}
/**
* Creates a spec from a raw byte array.
*
* @param key 32-byte raw key
* @return spec wrapping the key
*/
public static ChaChaKeyImportSpec fromRaw(byte[] key) {
return new ChaChaKeyImportSpec(key);
}
/**
* Creates a spec from a hexadecimal string.
*
* @param hex hex-encoded key
* @return spec wrapping the decoded key
*/
public static ChaChaKeyImportSpec fromHex(String hex) {
return fromRaw(HexFormat.of().parseHex(hex));
}
/**
* Creates a spec from a Base64 string.
*
* @param b64 base64-encoded key
* @return spec wrapping the decoded key
*/
public static ChaChaKeyImportSpec fromBase64(String b64) {
return fromRaw(Base64.getDecoder().decode(b64));
}
/**
* Returns a defensive copy of the raw key.
*
* @return 32-byte key array
*/
public byte[] key() {
return Arrays.copyOf(key, key.length);
}
/**
* Serializes this spec into a {@link PairSeq}, storing the key as Base64.
*
* @param spec spec to serialize
* @return serialized key representation
*/
public static PairSeq marshal(ChaChaKeyImportSpec spec) {
String k = Base64.getEncoder().withoutPadding().encodeToString(spec.key);
return PairSeq.of("type", "CHACHA-KEY", "k.b64", k);
}
/**
* Restores a spec from a serialized {@link PairSeq}.
*
* <p>
* Recognized fields:
* </p>
* <ul>
* <li>{@code k.b64} - Base64 encoding</li>
* <li>{@code k.hex} - hexadecimal string</li>
* <li>{@code k.raw} - raw ISO-8859-1 string</li>
* </ul>
*
* @param p serialized key representation
* @return reconstructed spec
* @throws IllegalArgumentException if none of the recognized fields are present
*/
public static ChaChaKeyImportSpec unmarshal(PairSeq p) {
byte[] out = null;
PairSeq.Cursor c = p.cursor();
while (c.next()) {
String k = c.key();
String v = c.value();
switch (k) {
case "k.b64" -> out = Base64.getDecoder().decode(v);
case "k.hex" -> out = HexFormat.of().parseHex(v);
case "k.raw" -> out = v.getBytes(StandardCharsets.ISO_8859_1);
default -> {
}
}
}
if (out == null) {
throw new IllegalArgumentException("ChaCha20 key missing (k.b64 / k.hex / k.raw)");
}
return new ChaChaKeyImportSpec(out);
}
}

View File

@@ -0,0 +1,175 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.chacha;
import zeroecho.core.SymmetricHeaderCodec;
import zeroecho.core.annotation.Describable;
/**
* <h2>ChaCha20 stream cipher specification</h2>
*
* Immutable parameter set for configuring a {@code ChaCha20} context. Provides
* an initial counter and an optional {@link SymmetricHeaderCodec}.
*
* <h3>Fields</h3>
* <ul>
* <li>{@link #initialCounter()} - the initial block counter used when no
* counter is present in the context or header (must be non-negative). Default
* is {@code 1}, matching common practice.</li>
* <li>{@link #header()} - optional codec for encoding/decoding a stream header
* that carries runtime parameters such as nonce and counter.</li>
* </ul>
*
* <h3>Construction</h3> Use the fluent {@link Builder}: <pre>{@code
* ChaChaSpec spec = ChaChaSpec.builder()
* .initialCounter(42)
* .header(new ChaChaHeaderCodec())
* .build();
* }</pre>
*
* <h3>Convenience factory</h3>
* <ul>
* <li>{@link #chacha20(SymmetricHeaderCodec)} returns a spec with initial
* counter {@code 1} and the provided header codec.</li>
* </ul>
*
* @since 1.0
*/
public final class ChaChaSpec implements ChaChaBaseSpec, Describable {
private final int initialCounter; // used when counter not present in ctx/header
private final SymmetricHeaderCodec header; // optional header codec
/**
* Creates a new ChaCha20 specification.
*
* @param initialCounter initial block counter (must be >= 0)
* @param header optional header codec (may be {@code null})
* @throws IllegalArgumentException if {@code initialCounter < 0}
*/
private ChaChaSpec(int initialCounter, SymmetricHeaderCodec header) {
if (initialCounter < 0) {
throw new IllegalArgumentException("initialCounter must be >= 0");
}
this.initialCounter = initialCounter;
this.header = header; // may be null
}
/**
* Returns a new builder for constructing a {@link ChaChaSpec}.
*
* @return fresh builder instance
*/
public static Builder builder() {
return new Builder();
}
/**
* Fluent builder for {@link ChaChaSpec}.
*/
public static final class Builder {
private int initialCounter = 1; // sane default per common practice
private SymmetricHeaderCodec header; // = null;
/**
* Sets the initial counter value.
*
* @param c non-negative counter value
* @return this builder
*/
public Builder initialCounter(int c) {
this.initialCounter = c;
return this;
}
/**
* Sets the optional header codec.
*
* @param codec header codec or {@code null}
* @return this builder
*/
public Builder header(SymmetricHeaderCodec codec) {
this.header = codec;
return this;
}
/**
* Builds an immutable {@link ChaChaSpec}.
*
* @return constructed spec
*/
public ChaChaSpec build() {
return new ChaChaSpec(initialCounter, header);
}
}
/**
* Convenience factory for a ChaCha20 spec with counter = 1.
*
* @param header optional header codec
* @return new spec instance
*/
public static ChaChaSpec chacha20(SymmetricHeaderCodec header) {
return builder().initialCounter(1).header(header).build();
}
/**
* Returns the configured initial counter.
*
* @return non-negative counter value
*/
public int initialCounter() {
return initialCounter;
}
/**
* Returns the optional header codec.
*
* @return codec instance or {@code null}
*/
@Override
public SymmetricHeaderCodec header() {
return header;
}
/**
* Human-readable description of this spec.
*
* @return string of the form {@code "ChaCha20(counter=N)"}
*/
@Override
public String description() {
return "ChaCha20(counter=" + initialCounter + ")";
}
}

View File

@@ -0,0 +1,90 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* Classic McEliece (CMCE) KEM integration and utilities.
*
* <p>
* This package adapts the Bouncy Castle PQC CMCE primitives to the core SPI. It
* provides the algorithm descriptor, a runtime KEM context, and key
* specifications for generation and import. The design keeps provider-specific
* details encapsulated behind factories while exposing clear roles and metadata
* to the higher layers.
* </p>
*
* <h2>Scope and responsibilities</h2>
* <ul>
* <li>Expose a concrete algorithm descriptor that registers CMCE KEM roles and
* a KEM-backed message-agreement adapter.</li>
* <li>Provide a runtime context that performs encapsulation and
* decapsulation.</li>
* <li>Define key specifications for key-pair generation and for importing X.509
* and PKCS#8 encodings.</li>
* </ul>
*
* <h2>Components</h2>
* <ul>
* <li><b>Algorithm descriptor:</b> {@link zeroecho.core.alg.cmce.CmceAlgorithm}
* declares {@code ENCAPSULATE}/{@code DECAPSULATE} KEM roles and wires an
* {@code AGREEMENT} role through a KEM-based adapter. It also registers
* asymmetric key builders for generation and import. The provider requirement
* is the Bouncy Castle PQC provider under the standard name
* {@code "BCPQC"}.</li>
* <li><b>Runtime context:</b> {@link zeroecho.core.alg.cmce.CmceKemContext}
* holds state for encapsulation or decapsulation depending on which constructor
* is used.</li>
* <li><b>Key generation spec:</b> {@link zeroecho.core.alg.cmce.CmceKeyGenSpec}
* selects a CMCE parameter set (variant) used by the key-pair builder.</li>
* <li><b>Key import specs:</b> {@link zeroecho.core.alg.cmce.CmcePublicKeySpec}
* wraps X.509 public keys and {@link zeroecho.core.alg.cmce.CmcePrivateKeySpec}
* wraps PKCS#8 private keys; both are immutable and defensively copy their byte
* arrays.</li>
* </ul>
*
* <h2>Provider requirements</h2>
* <p>
* The algorithm expects the Bouncy Castle PQC provider to be installed before
* use; the descriptor verifies this when generating or importing keys.
* </p>
*
* <h2>Thread-safety</h2>
* <ul>
* <li>Algorithm descriptors are immutable and safe to share across
* threads.</li>
* <li>Runtime contexts are stateful and not thread-safe.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.chacha;

View File

@@ -0,0 +1,250 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.cmce;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
import org.bouncycastle.pqc.jcajce.spec.CMCEParameterSpec;
import zeroecho.core.AlgorithmFamily;
import zeroecho.core.KeyUsage;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.alg.common.agreement.KemMessageAgreementAdapter;
import zeroecho.core.context.KemContext;
import zeroecho.core.context.MessageAgreementContext;
import zeroecho.core.spec.VoidSpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>Classic McEliece (CMCE) algorithm adapter</h2>
*
* <p>
* Integrates the Bouncy Castle PQC CMCE primitives into the ZeroEcho SPI. This
* algorithm publishes:
* </p>
*
* <ul>
* <li><b>KEM</b> capabilities:
* <ul>
* <li>{@code ENCAPSULATE} using a recipient {@link PublicKey}.</li>
* <li>{@code DECAPSULATE} using a {@link PrivateKey}.</li>
* </ul>
* </li>
* <li><b>Agreement</b> capability implemented via a KEM-backed adapter:
* <ul>
* <li>Initiator: constructs an agreement context that encapsulates to the peer
* public key.</li>
* <li>Responder: constructs an agreement context that decapsulates with the
* local private key.</li>
* </ul>
* </li>
* <li><b>Asymmetric key builders</b>:
* <ul>
* <li>Key generation from {@link CmceKeyGenSpec} variants.</li>
* <li>Public key import from X.509 bytes.</li>
* <li>Private key import from PKCS#8 bytes.</li>
* </ul>
* </li>
* </ul>
*
* <p>
* <b>Provider requirement:</b> the Bouncy Castle PQC provider must be
* registered under the standard name {@code "BCPQC"} before use.
* </p>
*
* <p>
* Usage example:
* </p>
* <pre>{@code
* // Register BCPQC once at startup.
* Security.addProvider(new BouncyCastlePQCProvider());
*
* // Obtain contexts using the CMCE algorithm id.
* CmceAlgorithm alg = new CmceAlgorithm();
*
* // Generate a key pair with a chosen CMCE variant.
* KeyPair kp = alg.asymmetricKeyBuilder(CmceKeyGenSpec.class)
* .generateKeyPair(CmceKeyGenSpec.mceliece8192128f());
*
* // Create a KEM encapsulation context with the recipient public key.
* KemContext enc = alg.create(KeyUsage.ENCAPSULATE, kp.getPublic(), VoidSpec.INSTANCE);
*
* // Create an agreement initiator context backed by CMCE KEM.
* MessageAgreementContext initiator =
* alg.create(KeyUsage.AGREEMENT, kp.getPublic(), VoidSpec.INSTANCE);
* }</pre>
*
* @since 1.0
*/
public final class CmceAlgorithm extends AbstractCryptoAlgorithm {
/**
* Constructs and registers CMCE capabilities and key builders.
*
* <p>
* This constructor registers:
* </p>
* <ul>
* <li>KEM roles for {@code ENCAPSULATE} and {@code DECAPSULATE}.</li>
* <li>Agreement role wired through a KEM-backed initiator/responder
* adapter.</li>
* <li>Asymmetric key builder for {@link CmceKeyGenSpec} (generation), X.509
* public key import, and PKCS#8 private key import.</li>
* </ul>
*
* <p>
* The algorithm id is {@code "CMCE"}, the display name is
* {@code "Classic McEliece (CMCE)"}, and the provider name is taken from the
* Bouncy Castle PQC provider.
* </p>
*/
public CmceAlgorithm() {
super("CMCE", "Classic McEliece (CMCE)", BouncyCastlePQCProvider.PROVIDER_NAME);
capability(AlgorithmFamily.KEM, KeyUsage.ENCAPSULATE, KemContext.class, PublicKey.class, VoidSpec.class,
(PublicKey k, VoidSpec s) -> new CmceKemContext(this, k), () -> VoidSpec.INSTANCE);
capability(AlgorithmFamily.KEM, KeyUsage.DECAPSULATE, KemContext.class, PrivateKey.class, VoidSpec.class,
(PrivateKey k, VoidSpec s) -> new CmceKemContext(this, k), () -> VoidSpec.INSTANCE);
// AGREEMENT (initiator): Alice has Bob's public key → encapsulate
capability(AlgorithmFamily.AGREEMENT, KeyUsage.AGREEMENT, MessageAgreementContext.class, // ← return your
// existing KemContext
PublicKey.class, // ← initiator uses recipient's public key
VoidSpec.class, // ← must implement ContextSpec
(PublicKey recipient, VoidSpec spec) -> {
// create a context bound to recipient public key for encapsulation
return KemMessageAgreementAdapter.builder().upon(new CmceKemContext(this, recipient)).asInitiator()
.build();
}, () -> VoidSpec.INSTANCE // default
);
// AGREEMENT (responder): Bob has his private key → decapsulate
capability(AlgorithmFamily.AGREEMENT, KeyUsage.AGREEMENT, MessageAgreementContext.class, // ← same KemContext
// type
PrivateKey.class, // ← responder uses their private key
VoidSpec.class, (PrivateKey myPriv, VoidSpec spec) -> {
return KemMessageAgreementAdapter.builder().upon(new CmceKemContext(this, myPriv)).asResponder()
.build();
}, () -> VoidSpec.INSTANCE);
registerAsymmetricKeyBuilder(CmceKeyGenSpec.class, new AsymmetricKeyBuilder<>() {
@Override
public KeyPair generateKeyPair(CmceKeyGenSpec spec) throws GeneralSecurityException {
ensureProvider();
KeyPairGenerator kpg = KeyPairGenerator.getInstance("CMCE", providerName());
CMCEParameterSpec params = switch (spec.variant()) {
case MCELIECE_348864 -> CMCEParameterSpec.mceliece348864;
case MCELIECE_348864F -> CMCEParameterSpec.mceliece348864f;
case MCELIECE_460896 -> CMCEParameterSpec.mceliece460896;
case MCELIECE_460896F -> CMCEParameterSpec.mceliece460896f;
case MCELIECE_6688128 -> CMCEParameterSpec.mceliece6688128;
case MCELIECE_6688128F -> CMCEParameterSpec.mceliece6688128f;
case MCELIECE_6960119 -> CMCEParameterSpec.mceliece6960119;
case MCELIECE_6960119F -> CMCEParameterSpec.mceliece6960119f;
case MCELIECE_8192128 -> CMCEParameterSpec.mceliece8192128;
case MCELIECE_8192128F -> CMCEParameterSpec.mceliece8192128f;
};
kpg.initialize(params, new SecureRandom());
return kpg.generateKeyPair();
}
@Override
public PublicKey importPublic(CmceKeyGenSpec spec) {
throw new UnsupportedOperationException();
}
@Override
public PrivateKey importPrivate(CmceKeyGenSpec spec) {
throw new UnsupportedOperationException();
}
}, CmceKeyGenSpec::mceliece8192128f);
registerAsymmetricKeyBuilder(CmcePublicKeySpec.class, new AsymmetricKeyBuilder<>() {
@Override
public KeyPair generateKeyPair(CmcePublicKeySpec spec) {
throw new UnsupportedOperationException();
}
@Override
public PublicKey importPublic(CmcePublicKeySpec spec) throws GeneralSecurityException {
ensureProvider();
KeyFactory kf = KeyFactory.getInstance("CMCE", providerName());
return kf.generatePublic(new X509EncodedKeySpec(spec.x509()));
}
@Override
public PrivateKey importPrivate(CmcePublicKeySpec spec) {
throw new UnsupportedOperationException();
}
}, null);
registerAsymmetricKeyBuilder(CmcePrivateKeySpec.class, new AsymmetricKeyBuilder<>() {
@Override
public KeyPair generateKeyPair(CmcePrivateKeySpec spec) {
throw new UnsupportedOperationException();
}
@Override
public PublicKey importPublic(CmcePrivateKeySpec spec) {
throw new UnsupportedOperationException();
}
@Override
public PrivateKey importPrivate(CmcePrivateKeySpec spec) throws GeneralSecurityException {
ensureProvider();
KeyFactory kf = KeyFactory.getInstance("CMCE", providerName());
return kf.generatePrivate(new PKCS8EncodedKeySpec(spec.pkcs8()));
}
}, null);
}
private static void ensureProvider() throws NoSuchProviderException {
Provider p = Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME);
if (p == null) {
throw new NoSuchProviderException("BCPQC provider not registered");
}
}
}

View File

@@ -0,0 +1,226 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.cmce;
import java.io.IOException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.Objects;
import javax.security.auth.DestroyFailedException;
import org.bouncycastle.crypto.SecretWithEncapsulation;
import org.bouncycastle.pqc.crypto.cmce.CMCEKEMExtractor;
import org.bouncycastle.pqc.crypto.cmce.CMCEKEMGenerator;
import org.bouncycastle.pqc.crypto.cmce.CMCEPrivateKeyParameters;
import org.bouncycastle.pqc.crypto.cmce.CMCEPublicKeyParameters;
import org.bouncycastle.pqc.crypto.util.PrivateKeyFactory;
import org.bouncycastle.pqc.crypto.util.PublicKeyFactory;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.context.KemContext;
/**
* <h2>Classic McEliece (CMCE) KEM context</h2>
*
* <p>
* Holds the state required to perform CMCE key encapsulation or decapsulation.
* The operational mode is determined by the constructor used:
* </p>
*
* <ul>
* <li>PublicKey constructor - encapsulate mode</li>
* <li>PrivateKey constructor - decapsulate mode</li>
* </ul>
*
* <p>
* Usage:
* </p>
* <pre>{@code
* CryptoAlgorithm alg = ...;
* PublicKey recipient = ...;
*
* // Encapsulation
* try (CmceKemContext ctx = new CmceKemContext(alg, recipient)) {
* KemResult kem = ctx.encapsulate();
* byte[] ct = kem.ciphertext();
* byte[] secret = kem.secret();
* // send ct to recipient; use secret for key derivation
* }
*
* // Decapsulation
* PrivateKey myPriv = ...;
* byte[] ct = ...;
* try (CmceKemContext ctx = new CmceKemContext(alg, myPriv)) {
* byte[] secret = ctx.decapsulate(ct);
* }
* }</pre>
*
* <p>
* Notes:
* </p>
* <ul>
* <li>Encapsulation requires a CMCE public key; decapsulation requires a CMCE
* private key.</li>
* <li>Returned arrays are owned by the caller; callers should clear secrets
* when no longer needed.</li>
* <li>This class holds no external resources and is safe to close
* repeatedly.</li>
* </ul>
*
* @since 1.0
*/
public final class CmceKemContext implements KemContext {
private final CryptoAlgorithm algorithm;
private final Key key;
private final boolean encapsulate;
/**
* Creates an encapsulation context bound to a recipient public key.
*
* @param algorithm parent algorithm metadata (for diagnostics)
* @param k CMCE public key
* @throws NullPointerException if any argument is null
*/
public CmceKemContext(CryptoAlgorithm algorithm, PublicKey k) {
this.algorithm = Objects.requireNonNull(algorithm);
this.key = Objects.requireNonNull(k);
this.encapsulate = true;
}
/**
* Creates a decapsulation context bound to a private key.
*
* @param algorithm parent algorithm metadata (for diagnostics)
* @param k CMCE private key
* @throws NullPointerException if any argument is null
*/
public CmceKemContext(CryptoAlgorithm algorithm, PrivateKey k) {
this.algorithm = Objects.requireNonNull(algorithm);
this.key = Objects.requireNonNull(k);
this.encapsulate = false;
}
/**
* Returns the parent algorithm descriptor for this context.
*
* @return algorithm descriptor; never null
*/
@Override
public CryptoAlgorithm algorithm() {
return algorithm;
}
/**
* Returns the key bound to this context.
*
* <p>
* In encapsulate mode this is a {@link java.security.PublicKey}; in decapsulate
* mode it is a {@link java.security.PrivateKey}.
* </p>
*
* @return key used by this context; never null
*/
@Override
public Key key() {
return key;
}
/**
* Releases resources held by this context.
*
* <p>
* This implementation holds no resources and performs no action. It is safe to
* call multiple times.
* </p>
*/
@Override
public void close() {
// empty
}
/**
* Generates a CMCE ciphertext and shared secret using the stored public key.
*
* @return result containing ciphertext and secret
* @throws IllegalStateException if this context is not in encapsulate mode
* @throws IOException if encapsulation fails
*/
@Override
public KemResult encapsulate() throws IOException {
if (!encapsulate) {
throw new IllegalStateException("Not initialized for ENCAPSULATE");
}
try {
final CMCEPublicKeyParameters keyParam = (CMCEPublicKeyParameters) PublicKeyFactory
.createKey(key.getEncoded());
CMCEKEMGenerator gen = new CMCEKEMGenerator(new SecureRandom());
SecretWithEncapsulation res = gen.generateEncapsulated(keyParam);
byte[] secret = res.getSecret();
byte[] ct = res.getEncapsulation();
res.destroy();
return new KemResult(ct, secret);
} catch (DestroyFailedException e) {
throw new IOException("CMCE encapsulate failed", e);
}
}
/**
* Extracts the shared secret from the given ciphertext using the stored private
* key.
*
* @param ciphertext CMCE ciphertext (must be non-null and non-empty)
* @return shared secret bytes
* @throws IllegalStateException if this context is not in decapsulate mode
* @throws IllegalArgumentException if {@code ciphertext} is null or empty
* @throws IOException if decapsulation fails
*/
@Override
public byte[] decapsulate(byte[] ciphertext) throws IOException {
if (encapsulate) {
throw new IllegalStateException("Not initialized for DECAPSULATE");
}
try {
final CMCEPrivateKeyParameters keyParam = (CMCEPrivateKeyParameters) PrivateKeyFactory
.createKey(key.getEncoded());
CMCEKEMExtractor ex = new CMCEKEMExtractor(keyParam);
return ex.extractSecret(ciphertext);
} catch (Exception e) { // NOPMD
throw new IOException("CMCE decapsulate failed", e);
}
}
}

View File

@@ -0,0 +1,243 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.cmce;
import zeroecho.core.annotation.Describable;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>CMCE key generation specification</h2>
*
* <p>
* Encapsulates the choice of Classic McEliece parameter set (variant) to be
* used when generating new key pairs. Each variant corresponds to a
* standardized security level and key size trade-off as defined in the
* post-quantum KEM standardization process.
* </p>
*
* <p>
* Usage example:
* </p>
* <pre>{@code
* // Generate a key pair for McEliece 8192128F (256-bit security, fast)
* CmceKeyGenSpec spec = CmceKeyGenSpec.mceliece8192128f();
* KeyPair kp = alg.asymmetricKeyBuilder(CmceKeyGenSpec.class).generateKeyPair(spec);
* }</pre>
*
* @since 1.0
*/
public final class CmceKeyGenSpec implements AlgorithmKeySpec, Describable {
/**
* Enumeration of supported CMCE parameter set variants.
*
* <p>
* Each value corresponds to a named parameter set from the Classic McEliece
* post-quantum KEM standardization.
* </p>
*/
public enum Variant {
/**
* McEliece 348864, standard parameter set (128-bit security).
*/
MCELIECE_348864,
/**
* McEliece 348864, fast parameter set (128-bit security).
*/
MCELIECE_348864F,
/**
* McEliece 460896, standard parameter set (128-bit security, larger keys).
*/
MCELIECE_460896,
/**
* McEliece 460896, fast parameter set (128-bit security, larger keys).
*/
MCELIECE_460896F,
/**
* McEliece 6688128, standard parameter set (192-bit security).
*/
MCELIECE_6688128,
/**
* McEliece 6688128, fast parameter set (192-bit security).
*/
MCELIECE_6688128F,
/**
* McEliece 6960119, standard parameter set (192-bit security, alternative
* form).
*/
MCELIECE_6960119,
/**
* McEliece 6960119, fast parameter set (192-bit security, alternative form).
*/
MCELIECE_6960119F,
/**
* McEliece 8192128, standard parameter set (256-bit security).
*/
MCELIECE_8192128,
/**
* McEliece 8192128, fast parameter set (256-bit security).
*/
MCELIECE_8192128F
}
private final Variant variant;
private CmceKeyGenSpec(Variant v) {
this.variant = v;
}
/**
* Creates a new key generation spec bound to a specific variant.
*
* @param v variant to use
* @return new specification for the given variant
* @throws NullPointerException if {@code v} is null
*/
public static CmceKeyGenSpec of(Variant v) {
return new CmceKeyGenSpec(v);
}
/**
* Convenience factory for {@link Variant#MCELIECE_348864}.
*
* @return a new specification for {@link Variant#MCELIECE_348864}
*/
public static CmceKeyGenSpec mceliece348864() {
return new CmceKeyGenSpec(Variant.MCELIECE_348864);
}
/**
* Convenience factory for {@link Variant#MCELIECE_348864F}.
*
* @return a new specification for {@link Variant#MCELIECE_348864F}
*/
public static CmceKeyGenSpec mceliece348864f() {
return new CmceKeyGenSpec(Variant.MCELIECE_348864F);
}
/**
* Convenience factory for {@link Variant#MCELIECE_460896}.
*
* @return a new specification for {@link Variant#MCELIECE_460896}
*/
public static CmceKeyGenSpec mceliece460896() {
return new CmceKeyGenSpec(Variant.MCELIECE_460896);
}
/**
* Convenience factory for {@link Variant#MCELIECE_460896F}.
*
* @return a new specification for {@link Variant#MCELIECE_460896F}
*/
public static CmceKeyGenSpec mceliece460896f() {
return new CmceKeyGenSpec(Variant.MCELIECE_460896F);
}
/**
* Convenience factory for {@link Variant#MCELIECE_6688128}.
*
* @return a new specification for {@link Variant#MCELIECE_6688128}
*/
public static CmceKeyGenSpec mceliece6688128() {
return new CmceKeyGenSpec(Variant.MCELIECE_6688128);
}
/**
* Convenience factory for {@link Variant#MCELIECE_6688128F}.
*
* @return a new specification for {@link Variant#MCELIECE_6688128F}
*/
public static CmceKeyGenSpec mceliece6688128f() {
return new CmceKeyGenSpec(Variant.MCELIECE_6688128F);
}
/**
* Convenience factory for {@link Variant#MCELIECE_6960119}.
*
* @return a new specification for {@link Variant#MCELIECE_6960119}
*/
public static CmceKeyGenSpec mceliece6960119() {
return new CmceKeyGenSpec(Variant.MCELIECE_6960119);
}
/**
* Convenience factory for {@link Variant#MCELIECE_6960119F}.
*
* @return a new specification for {@link Variant#MCELIECE_6960119F}
*/
public static CmceKeyGenSpec mceliece6960119f() {
return new CmceKeyGenSpec(Variant.MCELIECE_6960119F);
}
/**
* Convenience factory for {@link Variant#MCELIECE_8192128}.
*
* @return a new specification for {@link Variant#MCELIECE_8192128}
*/
public static CmceKeyGenSpec mceliece8192128() {
return new CmceKeyGenSpec(Variant.MCELIECE_8192128);
}
/**
* Convenience factory for {@link Variant#MCELIECE_8192128F}.
*
* @return a new specification for {@link Variant#MCELIECE_8192128F}
*/
public static CmceKeyGenSpec mceliece8192128f() {
return new CmceKeyGenSpec(Variant.MCELIECE_8192128F);
}
/**
* Returns the selected variant for this specification.
*
* @return non-null variant
*/
public Variant variant() {
return variant;
}
/**
* Returns a human-readable description of this specification.
*
* <p>
* The value is simply the {@link Variant#toString()} of the selected variant.
* </p>
*
* @return string description of the variant
*/
@Override
public String description() {
return variant.toString();
}
}

View File

@@ -0,0 +1,164 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.cmce;
import java.util.Base64;
import java.util.Objects;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.marshal.PairSeq.Cursor;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>Classic McEliece (CMCE) private key specification</h2>
*
* <p>
* Wraps a CMCE private key in PKCS#8 (DER) encoding. This spec is used to
* import or serialize private keys into the ZeroEcho SPI.
* </p>
*
* <p>
* Instances are immutable. The internal byte array is cloned on construction
* and on every accessor to prevent accidental mutation.
* </p>
*
* <h2>Marshalling</h2>
* <ul>
* <li>{@link #marshal(CmcePrivateKeySpec)} produces a {@link PairSeq} with
* Base64-encoded PKCS#8.</li>
* <li>{@link #unmarshal(PairSeq)} reconstructs a spec from that format.</li>
* </ul>
*
* <p>
* Example:
* </p>
* <pre>{@code
* // Wrap an existing PKCS#8 byte array
* CmcePrivateKeySpec spec = new CmcePrivateKeySpec(pkcs8Bytes);
*
* // Serialize to PairSeq for storage or transport
* PairSeq encoded = CmcePrivateKeySpec.marshal(spec);
*
* // Reconstruct later
* CmcePrivateKeySpec restored = CmcePrivateKeySpec.unmarshal(encoded);
* }</pre>
*
* @since 1.0
*/
public final class CmcePrivateKeySpec implements AlgorithmKeySpec {
private static final String PKCS8_B64 = "pkcs8.b64";
private final byte[] pkcs8;
/**
* Creates a new specification from a PKCS#8-encoded CMCE private key.
*
* <p>
* The input is defensively copied.
* </p>
*
* @param pkcs8Der DER-encoded PKCS#8 private key
* @throws NullPointerException if {@code pkcs8Der} is null
*/
public CmcePrivateKeySpec(byte[] pkcs8Der) {
this.pkcs8 = Objects.requireNonNull(pkcs8Der).clone();
}
/**
* Returns a defensive copy of the PKCS#8 bytes.
*
* @return a fresh copy of the underlying PKCS#8 encoding
*/
public byte[] pkcs8() {
return pkcs8.clone();
}
/**
* Serializes the given private key spec into a {@link PairSeq}.
*
* <p>
* The PKCS#8 bytes are Base64-encoded (without padding) and stored under the
* key {@code "pkcs8.b64"}. The type tag {@code "CmcePrivateKeySpec"} is also
* included.
* </p>
*
* @param spec the spec to serialize
* @return a PairSeq containing type and Base64-encoded key
* @throws NullPointerException if {@code spec} is null
*/
public static PairSeq marshal(CmcePrivateKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.pkcs8);
return PairSeq.of("type", "CmcePrivateKeySpec", PKCS8_B64, b64);
}
/**
* Deserializes a {@link CmcePrivateKeySpec} from a {@link PairSeq}.
*
* <p>
* The method scans for a key named {@code "pkcs8.b64"}, decodes its value from
* Base64, and reconstructs the spec.
* </p>
*
* @param p PairSeq containing serialized fields
* @return reconstructed {@code CmcePrivateKeySpec}
* @throws IllegalArgumentException if no {@code "pkcs8.b64"} field is found
*/
public static CmcePrivateKeySpec unmarshal(PairSeq p) {
String b64 = null;
for (Cursor cur = p.cursor(); cur.next();) {
if (PKCS8_B64.equals(cur.key())) {
b64 = cur.value();
}
}
if (b64 == null) {
throw new IllegalArgumentException("CmcePrivateKeySpec: missing pkcs8.b64");
}
return new CmcePrivateKeySpec(Base64.getDecoder().decode(b64));
}
/**
* Returns a diagnostic string with the length of the encoded key.
*
* <p>
* The output is safe to log; it does not include key material.
* </p>
*
* @return a string in the form {@code CmcePrivateKeySpec[len=N]}
*/
@Override
public String toString() {
return "CmcePrivateKeySpec[len=" + pkcs8.length + "]";
}
}

View File

@@ -0,0 +1,164 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.cmce;
import java.util.Base64;
import java.util.Objects;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.marshal.PairSeq.Cursor;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>Classic McEliece (CMCE) public key specification</h2>
*
* <p>
* Wraps a CMCE public key in X.509 (DER) encoding. This spec is used to import
* or serialize public keys into the ZeroEcho SPI.
* </p>
*
* <p>
* Instances are immutable. The internal byte array is cloned on construction
* and on every accessor to prevent accidental mutation.
* </p>
*
* <h2>Marshalling</h2>
* <ul>
* <li>{@link #marshal(CmcePublicKeySpec)} produces a {@link PairSeq} with
* Base64-encoded X.509 data.</li>
* <li>{@link #unmarshal(PairSeq)} reconstructs a spec from that format.</li>
* </ul>
*
* <p>
* Example:
* </p>
* <pre>{@code
* // Wrap an existing X.509-encoded public key
* CmcePublicKeySpec spec = new CmcePublicKeySpec(x509Bytes);
*
* // Serialize to PairSeq for storage or transport
* PairSeq encoded = CmcePublicKeySpec.marshal(spec);
*
* // Reconstruct later
* CmcePublicKeySpec restored = CmcePublicKeySpec.unmarshal(encoded);
* }</pre>
*
* @since 1.0
*/
public final class CmcePublicKeySpec implements AlgorithmKeySpec {
private static final String X509_B64 = "x509.b64";
private final byte[] x509;
/**
* Creates a new specification from an X.509-encoded CMCE public key.
*
* <p>
* The input is defensively copied.
* </p>
*
* @param x509Der DER-encoded X.509 public key
* @throws NullPointerException if {@code x509Der} is null
*/
public CmcePublicKeySpec(byte[] x509Der) {
this.x509 = Objects.requireNonNull(x509Der).clone();
}
/**
* Returns a defensive copy of the X.509 bytes.
*
* @return a fresh copy of the underlying X.509 encoding
*/
public byte[] x509() {
return x509.clone();
}
/**
* Serializes the given public key spec into a {@link PairSeq}.
*
* <p>
* The X.509 bytes are Base64-encoded (without padding) and stored under the key
* {@code "x509.b64"}. The type tag {@code "CmcePublicKeySpec"} is also
* included.
* </p>
*
* @param spec the spec to serialize
* @return a PairSeq containing type and Base64-encoded key
* @throws NullPointerException if {@code spec} is null
*/
public static PairSeq marshal(CmcePublicKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.x509);
return PairSeq.of("type", "CmcePublicKeySpec", X509_B64, b64);
}
/**
* Deserializes a {@link CmcePublicKeySpec} from a {@link PairSeq}.
*
* <p>
* The method scans for a key named {@code "x509.b64"}, decodes its value from
* Base64, and reconstructs the spec.
* </p>
*
* @param p PairSeq containing serialized fields
* @return reconstructed {@code CmcePublicKeySpec}
* @throws IllegalArgumentException if no {@code "x509.b64"} field is found
*/
public static CmcePublicKeySpec unmarshal(PairSeq p) {
String b64 = null;
for (Cursor cur = p.cursor(); cur.next();) {
if (X509_B64.equals(cur.key())) {
b64 = cur.value();
}
}
if (b64 == null) {
throw new IllegalArgumentException("CmcePublicKeySpec: missing x509.b64");
}
return new CmcePublicKeySpec(Base64.getDecoder().decode(b64));
}
/**
* Returns a diagnostic string with the length of the encoded key.
*
* <p>
* The output is safe to log; it does not include key material.
* </p>
*
* @return a string in the form {@code CmcePublicKeySpec[len=N]}
*/
@Override
public String toString() {
return "CmcePublicKeySpec[len=" + x509.length + "]";
}
}

View File

@@ -0,0 +1,102 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* <h2>Classic McEliece (CMCE)</h2>
*
* <p>
* This package integrates the Classic McEliece cryptosystem, one of the oldest
* and most studied code-based public-key cryptosystems. Originally proposed by
* Robert McEliece in 1978, it is based on the hardness of decoding random
* binary Goppa codes. Despite large public key sizes, the scheme has withstood
* decades of cryptanalysis and remains unbroken by both classical and quantum
* computers.
* </p>
*
* <h2>Post-quantum KEM</h2>
*
* <p>
* Classic McEliece has been selected by NIST in the post-quantum cryptography
* standardization process for key encapsulation. Its primary appeal is
* long-term confidence: no efficient attacks are known even in the quantum
* setting. It provides IND-CCA2 security through a well-studied transform and
* is especially suited for use cases where large public keys are acceptable but
* extremely strong security margins are desired.
* </p>
*
* <h2>Contents</h2>
* <ul>
* <li>{@link zeroecho.core.alg.cmce.CmceAlgorithm} algorithm adapter exposing
* CMCE as a KEM and agreement primitive.</li>
* <li>{@link zeroecho.core.alg.cmce.CmceKemContext} runtime context for
* encapsulation and decapsulation.</li>
* <li>{@link zeroecho.core.alg.cmce.CmceKeyGenSpec} enumeration of
* standardized CMCE parameter sets (variants).</li>
* <li>{@link zeroecho.core.alg.cmce.CmcePublicKeySpec} wrapper for
* X.509-encoded public keys.</li>
* <li>{@link zeroecho.core.alg.cmce.CmcePrivateKeySpec} wrapper for
* PKCS#8-encoded private keys.</li>
* </ul>
*
* <h2>Security properties</h2>
* <ul>
* <li>Underlying assumption: hardness of decoding binary Goppa codes.</li>
* <li>Selected as a NIST post-quantum KEM standard (2022).</li>
* <li>Public keys are large (hundreds of kilobytes), but ciphertexts and
* secrets are compact.</li>
* <li>Considered quantum-resistant and secure against known attacks.</li>
* </ul>
*
* <h2>Usage</h2> <pre>{@code
* // Select a variant (e.g., 8192128F for 256-bit security)
* CmceKeyGenSpec spec = CmceKeyGenSpec.mceliece8192128f();
* CmceAlgorithm alg = new CmceAlgorithm();
* KeyPair kp = alg.asymmetricKeyBuilder(CmceKeyGenSpec.class).generateKeyPair(spec);
*
* // Encapsulation (sender)
* try (CmceKemContext ctx = new CmceKemContext(alg, kp.getPublic())) {
* KemResult kem = ctx.encapsulate();
* byte[] ct = kem.ciphertext();
* byte[] secret = kem.secret();
* }
*
* // Decapsulation (recipient)
* try (CmceKemContext ctx = new CmceKemContext(alg, kp.getPrivate())) {
* byte[] secret = ctx.decapsulate(ct);
* }
* }</pre>
*
* @since 1.0
*/
package zeroecho.core.alg.cmce;

View File

@@ -0,0 +1,183 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.common.agreement;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.KeyAgreement;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.context.AgreementContext;
/**
* <h2>Generic JCA-based Key Agreement Context</h2>
*
* An {@link AgreementContext} backed by the standard JCA {@link KeyAgreement}
* 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.
*
* <p>
* Instances of this context are created with a local {@link PrivateKey}, and
* require the peers {@link PublicKey} to be provided later via
* {@link #setPeerPublic(PublicKey)} before deriving the shared secret.
* </p>
*
* <h2>Lifecycle</h2>
* <ol>
* <li>Construct with local private key and algorithm name.</li>
* <li>Call {@link #setPeerPublic(PublicKey)} with the remote partys key.</li>
* <li>Invoke {@link #deriveSecret()} to compute the raw shared secret.</li>
* <li>Optionally call {@link #close()} (no resources are held here).</li>
* </ol>
*
* <h2>Notes</h2>
* <ul>
* <li>The derived secret is the raw key agreement output; protocols should
* apply a KDF before using it as a symmetric key.</li>
* <li>If {@code provider} is {@code null}, the default JCA provider lookup is
* used; otherwise, the specific provider is requested.</li>
* </ul>
*
* @since 1.0
*/
public final class GenericJcaAgreementContext implements AgreementContext {
private final CryptoAlgorithm algorithm;
private final PrivateKey privateKey;
private final String jcaName; // e.g., "ECDH" or "XDH" (or "X25519"/"X448")
private final String provider; // null => default
private PublicKey peer;
/**
* Creates a new JCA-based agreement context.
*
* @param alg the enclosing {@link CryptoAlgorithm} definition
* @param priv the local private key used in the key agreement
* @param jcaName the JCA algorithm name (e.g., {@code "ECDH"},
* {@code "X25519"})
* @param provider optional JCA provider name, or {@code null} to use the
* default
* @throws NullPointerException if {@code alg}, {@code priv}, or {@code jcaName}
* is {@code null}
*/
public GenericJcaAgreementContext(CryptoAlgorithm alg, PrivateKey priv, String jcaName, String provider) {
this.algorithm = alg;
this.privateKey = priv;
this.jcaName = jcaName;
this.provider = provider;
}
/**
* Returns the {@link CryptoAlgorithm} that created this context.
*
* @return the parent algorithm definition
*/
@Override
public CryptoAlgorithm algorithm() {
return algorithm;
}
/**
* Returns the local private key bound to this agreement context.
*
* @return the private {@link Key} used in the key agreement
*/
@Override
public Key key() {
return privateKey;
}
/**
* Assigns the peers public key for the key agreement.
*
* <p>
* This must be called before {@link #deriveSecret()}, otherwise the context
* cannot complete the protocol.
* </p>
*
* @param peer the remote partys public key
*/
@Override
public void setPeerPublic(PublicKey peer) {
this.peer = peer;
}
/**
* Computes the raw shared secret using the configured local private key and the
* previously assigned peer public key.
*
* <p>
* Internally this delegates to the JCA {@link KeyAgreement} API with the given
* {@code jcaName} and optional provider.
* </p>
*
* @return the raw shared secret as a byte array
* @throws IllegalStateException if the peer key has not been set
* @throws IllegalArgumentException if key agreement fails due to invalid keys,
* unsupported parameters, or provider errors
*/
@Override
public byte[] deriveSecret() {
if (peer == null) {
throw new IllegalStateException("Peer public key not set");
}
try {
KeyAgreement ka = (provider == null) ? KeyAgreement.getInstance(jcaName)
: KeyAgreement.getInstance(jcaName, provider);
ka.init(privateKey);
ka.doPhase(peer, true);
return ka.generateSecret();
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("KeyAgreement failed for " + jcaName, e);
}
}
/**
* Closes this context.
*
* <p>
* For this implementation, there are no system resources to release, so the
* method is a no-op. It exists to satisfy the {@link AgreementContext} contract
* and for future compatibility.
* </p>
*/
@Override
public void close() {
/* nothing to release */
}
}

View File

@@ -0,0 +1,274 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.common.agreement;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.Key;
import java.security.PublicKey;
import java.util.Objects;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.context.KemContext;
import zeroecho.core.context.MessageAgreementContext;
/**
* <h2>Adapter: using a KEM as a message-based agreement primitive</h2>
*
* {@code KemMessageAgreementAdapter} adapts a {@link KemContext} into a
* {@link MessageAgreementContext}, making KEMs usable in higher-level protocols
* that expect a two-party message agreement API.
*
* <h2>Roles</h2>
* <ul>
* <li>{@link Role#INITIATOR} - encapsulates to a peers public key, producing a
* ciphertext (peer message) and shared secret.</li>
* <li>{@link Role#RESPONDER} - receives a peer message (ciphertext),
* decapsulates with their private key, and derives the shared secret.</li>
* </ul>
*
* <h2>Lifecycle</h2>
* <ol>
* <li>Create via {@link Builder} with a bound {@link KemContext}.</li>
* <li>Initiator calls {@link #getPeerMessage()} to obtain ciphertext to
* transmit.</li>
* <li>Responder calls {@link #setPeerMessage(byte[])} with received
* ciphertext.</li>
* <li>Both parties call {@link #deriveSecret()} to obtain the agreed
* secret.</li>
* </ol>
*
* <h2>Thread-safety</h2> Instances are not thread-safe; synchronize externally
* if sharing across threads.
*
* @since 1.0
*/
public final class KemMessageAgreementAdapter implements MessageAgreementContext {
/**
* Role of the adapter: initiator or responder.
*/
public enum Role {
/** Initiator: produces a peer message via encapsulation. */
INITIATOR,
/** Responder: consumes a peer message via decapsulation. */
RESPONDER
}
private final KemContext kem;
private final Role role;
private byte[] producedMessage; // initiator: ciphertext (encapsulation)
private byte[] receivedMessage; // responder: ciphertext to decapsulate
private byte[] derivedSecret; // memoized deriveSecret()
private KemMessageAgreementAdapter(KemContext kem, Role role) {
this.kem = Objects.requireNonNull(kem, "kem must not be null");
this.role = Objects.requireNonNull(role, "role must not be null");
}
/**
* Returns a new builder for constructing a {@code KemMessageAgreementAdapter}.
*
* @return builder instance
*/
public static Builder builder() {
return new Builder();
}
/**
* Builder for {@link KemMessageAgreementAdapter}.
*/
public static final class Builder {
private KemContext kem;
private Role role;
/**
* Binds this adapter to a KEM context.
*
* @param kem underlying KEM context
* @return this builder
*/
public Builder upon(KemContext kem) {
this.kem = kem;
return this;
}
/**
* Configures the adapter as an initiator.
*
* @return this builder
*/
public Builder asInitiator() {
this.role = Role.INITIATOR;
return this;
}
/**
* Configures the adapter as a responder.
*
* @return this builder
*/
public Builder asResponder() {
this.role = Role.RESPONDER;
return this;
}
/**
* Builds the adapter.
*
* @return new adapter instance
* @throws NullPointerException if no KEM context or role is set
*/
public KemMessageAgreementAdapter build() {
return new KemMessageAgreementAdapter(kem, role);
}
}
/**
* Stores the peers ciphertext for decapsulation.
*
* @param message ciphertext received from initiator
* @throws IllegalStateException if called in initiator mode
*/
@Override
public void setPeerMessage(byte[] message) {
if (role != Role.RESPONDER) {
throw new IllegalStateException("setPeerMessage only valid for RESPONDER");
}
this.receivedMessage = (message == null ? null : message.clone());
}
/**
* Returns the ciphertext produced by encapsulation.
*
* @return defensive copy of ciphertext to send
* @throws IllegalStateException if called in responder mode
*/
@Override
public byte[] getPeerMessage() {
if (role != Role.INITIATOR) {
throw new IllegalStateException("getPeerMessage only valid for INITIATOR");
}
ensureEncapsulated();
return producedMessage.clone();
}
/**
* No-op for KEM-based contexts.
*
* <p>
* Unlike DiffieHellman, KEMs are already bound to the correct key at
* construction. This method exists for interface symmetry.
* </p>
*
* @param peer ignored
*/
@Override
public void setPeerPublic(PublicKey peer) {
// KEM already bound to the correct key at construction; nothing to do.
// Provided for API symmetry; ignore or validate if you wish.
}
/**
* Derives the shared secret from this exchange.
*
* @return defensive copy of the derived secret
* @throws UncheckedIOException if encapsulation/decapsulation fails
*/
@Override
public byte[] deriveSecret() {
if (derivedSecret != null) {
return derivedSecret.clone();
}
try {
if (role == Role.INITIATOR) {
ensureEncapsulated(); // fills producedMessage + derivedSecret
} else {
if (receivedMessage == null) {
throw new IllegalStateException("Responder missing peer encapsulation message");
}
byte[] ss = kem.decapsulate(receivedMessage);
derivedSecret = (ss == null ? new byte[0] : ss.clone());
}
return derivedSecret.clone();
} catch (IOException e) {
throw new UncheckedIOException("KEM deriveSecret failed", e);
}
}
private void ensureEncapsulated() {
if (producedMessage != null && derivedSecret != null) {
return;
}
try {
KemContext.KemResult res = kem.encapsulate();
this.producedMessage = res.ciphertext().clone();
this.derivedSecret = res.sharedSecret().clone();
} catch (IOException e) {
throw new UncheckedIOException("KEM encapsulate failed", e);
}
}
/**
* Returns the underlying algorithm descriptor.
*
* @return algorithm bound to this adapter
*/
@Override
public CryptoAlgorithm algorithm() {
return kem.algorithm();
}
/**
* Returns the key bound to the underlying KEM context.
*
* @return encapsulation (public) or decapsulation (private) key
*/
@Override
public Key key() {
return kem.key();
}
/**
* Closes the underlying KEM context if it is closeable.
*
* @throws IOException if the wrapped context fails to close
*/
@Override
public void close() throws IOException {
kem.close();
}
}

View File

@@ -0,0 +1,80 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* Adapters and generic contexts for key agreement built on the core SPI.
*
* <p>
* This package provides a generic JCA-backed agreement context and a thin
* adapter that exposes a KEM as a message-based agreement primitive. The goal
* is to keep provider-specific details encapsulated while presenting clear
* roles and lifecycles that higher layers can compose.
* </p>
*
* <h2>Scope and responsibilities</h2>
* <ul>
* <li>Expose a generic agreement context that delegates to the JCA
* {@code KeyAgreement} API for algorithms such as ECDH and XDH.</li>
* <li>Adapt KEM contexts to a two-message agreement API suitable for initiator/
* responder protocols.</li>
* <li>Preserve clear separation between algorithm descriptors, runtime
* contexts, and higher-level composition utilities.</li>
* </ul>
*
* <h2>Components</h2>
* <ul>
* <li><b>GenericJcaAgreementContext</b>: an
* {@link zeroecho.core.context.AgreementContext} backed by
* {@link javax.crypto.KeyAgreement}; constructed with a local private key and
* configured using a JCA algorithm name and optional provider.</li>
* <li><b>KemMessageAgreementAdapter</b>: a
* {@link zeroecho.core.context.MessageAgreementContext} built on a
* {@link zeroecho.core.context.KemContext}, modeling initiator/responder roles
* and exchanging a single peer message (ciphertext) when required.</li>
* </ul>
*
* <h2>Lifecycle and usage notes</h2>
* <ul>
* <li>Agreement contexts should be created with the correct local key and
* configured before deriving secrets; KDF application remains the caller's
* responsibility.</li>
* <li>KEM-based adapters encapsulate or decapsulate depending on role and
* memoize results for repeated reads within a single exchange.</li>
* <li>Instances are not thread-safe; synchronize externally if they are
* shared.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.common.agreement;

View File

@@ -0,0 +1,145 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.common.eddsa;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import zeroecho.core.spec.AlgorithmKeySpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>Abstract EdDSA Key-Pair Builder</h2>
*
* Base class for key generation builders targeting Edwards-curve Digital
* Signature Algorithm (EdDSA) variants such as Ed25519 and Ed448.
*
* <p>
* This class integrates with the JCA {@link KeyPairGenerator} by exposing the
* correct algorithm name (e.g., {@code "Ed25519"} or {@code "Ed448"}). Concrete
* subclasses provide this algorithm identifier via {@link #jcaKeyPairAlg()}.
* </p>
*
* <h2>Responsibilities</h2>
* <ul>
* <li>Expose a template method {@link #jcaKeyPairAlg()} to return the canonical
* JCA algorithm identifier.</li>
* <li>Generate key pairs using JCA without requiring extra parameters.</li>
* <li>Intentionally reject public and private key imports, as those are
* delegated to the corresponding {@code *KeySpec} builders.</li>
* </ul>
*
* <h2>Thread-safety</h2> Instances are stateless. Each call to
* {@link #generateKeyPair(AlgorithmKeySpec)} acquires a new
* {@link KeyPairGenerator}, so builders are safe for concurrent use.
*
* @param <S> the algorithm-specific {@link AlgorithmKeySpec} subtype
*
* @since 1.0
*/
public abstract class AbstractEdDSAKeyGenBuilder<S extends AlgorithmKeySpec> implements AsymmetricKeyBuilder<S> {
/**
* Returns the JCA algorithm name understood by {@link KeyPairGenerator}.
*
* <p>
* Implementations must return the canonical algorithm string supported by the
* JDK, e.g.:
* </p>
* <ul>
* <li>{@code "Ed25519"}</li>
* <li>{@code "Ed448"}</li>
* </ul>
*
* @return the JCA algorithm identifier string
*/
protected abstract String jcaKeyPairAlg(); // e.g., "Ed25519", "Ed448"
/**
* Generates a new EdDSA key pair using JCA defaults.
*
* <p>
* The provided {@code spec} is not inspected in this base implementation, but
* it satisfies the {@link AsymmetricKeyBuilder} contract. Subclasses may extend
* this behavior to interpret spec parameters.
* </p>
*
* @param spec algorithm-specific key specification (currently unused)
* @return a fresh {@link KeyPair} for the chosen EdDSA variant
* @throws GeneralSecurityException if the JCA provider does not support the
* specified EdDSA algorithm
*/
@Override
public KeyPair generateKeyPair(S spec) throws GeneralSecurityException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(jcaKeyPairAlg());
return kpg.generateKeyPair();
}
/**
* Always throws, as this builder does not support public key import.
*
* <p>
* Importing encoded EdDSA public keys must be done through the corresponding
* {@code *PublicKeySpec} builder class.
* </p>
*
* @param spec algorithm-specific key specification
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public PublicKey importPublic(S spec) {
throw new UnsupportedOperationException("Use the corresponding PublicKeySpec to import a public key.");
}
/**
* Always throws, as this builder does not support private key import.
*
* <p>
* Importing encoded EdDSA private keys must be done through the corresponding
* {@code *PrivateKeySpec} builder class.
* </p>
*
* @param spec algorithm-specific key specification
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public PrivateKey importPrivate(S spec) {
throw new UnsupportedOperationException("Use the corresponding PrivateKeySpec to import a private key.");
}
}

View File

@@ -0,0 +1,150 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.common.eddsa;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import zeroecho.core.spec.AlgorithmKeySpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>Abstract EdDSA Encoded Private Key Builder</h2>
*
* Base class for reconstructing EdDSA private keys (e.g., Ed25519, Ed448) from
* PKCS#8-encoded specifications.
*
* <p>
* Unlike {@link AbstractEdDSAKeyGenBuilder}, which is responsible for
* generating fresh key pairs, this class focuses on <b>importing existing
* private keys</b> from their encoded representation. Public key import and key
* pair generation are deliberately unsupported here.
* </p>
*
* <h2>Responsibilities</h2>
* <ul>
* <li>Define {@link #jcaKeyFactoryAlg()} to specify the canonical JCA algorithm
* name (e.g., {@code "Ed25519"} or {@code "Ed448"}).</li>
* <li>Define {@link #encodedPkcs8(AlgorithmKeySpec)} to extract the raw
* PKCS#8-encoded private key material from the given spec.</li>
* <li>Provide an {@link #importPrivate(AlgorithmKeySpec)} implementation that
* rebuilds a {@link PrivateKey} using {@link KeyFactory} and the PKCS#8-encoded
* material.</li>
* </ul>
*
* <h2>Thread-safety</h2> Stateless and safe for concurrent use. Each import
* operation creates a new {@link KeyFactory} instance internally.
*
* @param <S> the algorithm-specific key specification carrying PKCS#8 data
*
* @since 1.0
*/
public abstract class AbstractEncodedPrivateKeyBuilder<S extends AlgorithmKeySpec> implements AsymmetricKeyBuilder<S> {
/**
* Returns the canonical JCA algorithm identifier used by
* {@link KeyFactory#getInstance(String)}.
*
* <p>
* Must be one of the algorithm names recognized by the JDK (e.g.,
* {@code "Ed25519"}, {@code "Ed448"}).
* </p>
*
* @return the JCA algorithm identifier string
*/
protected abstract String jcaKeyFactoryAlg(); // e.g., "Ed25519", "Ed448"
/**
* Extracts the raw PKCS#8-encoded private key bytes from the given spec.
*
* <p>
* Subclasses must implement this to pull the encoded material from their
* {@link AlgorithmKeySpec} representation.
* </p>
*
* @param spec algorithm-specific key specification
* @return PKCS#8-encoded private key bytes
*/
protected abstract byte[] encodedPkcs8(S spec);
/**
* Unsupported in this builder, since generation is handled by the
* {@link AbstractEdDSAKeyGenBuilder}.
*
* @param spec algorithm-specific key specification
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public KeyPair generateKeyPair(S spec) {
throw new UnsupportedOperationException("Generation not supported by this spec.");
}
/**
* Unsupported in this builder, since public key import is delegated to the
* matching {@code *PublicKeySpec} builder.
*
* @param spec algorithm-specific key specification
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public PublicKey importPublic(S spec) {
throw new UnsupportedOperationException("Use the corresponding PublicKeySpec.");
}
/**
* Imports an EdDSA private key from its PKCS#8-encoded form.
*
* <p>
* This method uses {@link KeyFactory} initialized with the algorithm returned
* by {@link #jcaKeyFactoryAlg()} to parse the bytes provided by
* {@link #encodedPkcs8(AlgorithmKeySpec)}.
* </p>
*
* @param spec algorithm-specific key specification containing PKCS#8 bytes
* @return a reconstructed {@link PrivateKey} instance
* @throws GeneralSecurityException if the key material is invalid or the JCA
* provider does not support the algorithm
*/
@Override
public PrivateKey importPrivate(S spec) throws GeneralSecurityException {
KeyFactory kf = KeyFactory.getInstance(jcaKeyFactoryAlg());
return kf.generatePrivate(new PKCS8EncodedKeySpec(encodedPkcs8(spec)));
}
}

View File

@@ -0,0 +1,152 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.common.eddsa;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import zeroecho.core.spec.AlgorithmKeySpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>Abstract EdDSA Encoded Public Key Builder</h2>
*
* Base class for reconstructing EdDSA public keys (e.g., Ed25519, Ed448) from
* X.509-encoded specifications.
*
* <p>
* Unlike {@link AbstractEdDSAKeyGenBuilder}, which generates new key pairs, and
* {@link AbstractEncodedPrivateKeyBuilder}, which restores private keys, this
* class focuses exclusively on <b>importing existing public keys</b> from their
* encoded form. Key pair generation and private key import are intentionally
* unsupported here.
* </p>
*
* <h2>Responsibilities</h2>
* <ul>
* <li>Define {@link #jcaKeyFactoryAlg()} to return the canonical JCA algorithm
* name (e.g., {@code "Ed25519"}, {@code "Ed448"}).</li>
* <li>Define {@link #encodedX509(AlgorithmKeySpec)} to extract the raw
* X.509-encoded public key bytes from the given spec.</li>
* <li>Provide an {@link #importPublic(AlgorithmKeySpec)} implementation that
* rebuilds a {@link PublicKey} using {@link KeyFactory} and the encoded X.509
* material.</li>
* </ul>
*
* <h2>Thread-safety</h2> Stateless and safe for concurrent use. Each call to
* {@link #importPublic(AlgorithmKeySpec)} creates a new {@link KeyFactory}.
*
* @param <S> the algorithm-specific key specification carrying X.509 data
*
* @since 1.0
*/
public abstract class AbstractEncodedPublicKeyBuilder<S extends AlgorithmKeySpec> implements AsymmetricKeyBuilder<S> {
/**
* Returns the canonical JCA algorithm identifier used by
* {@link KeyFactory#getInstance(String)}.
*
* <p>
* Must be one of the algorithm names recognized by the JDK (e.g.,
* {@code "Ed25519"}, {@code "Ed448"}).
* </p>
*
* @return the JCA algorithm identifier string
*/
protected abstract String jcaKeyFactoryAlg(); // e.g., "Ed25519", "Ed448"
/**
* Extracts the raw X.509-encoded public key bytes from the given spec.
*
* <p>
* Subclasses must implement this to pull the encoded material from their
* {@link AlgorithmKeySpec} representation.
* </p>
*
* @param spec algorithm-specific key specification
* @return X.509-encoded public key bytes
*/
protected abstract byte[] encodedX509(S spec);
/**
* Unsupported in this builder, since key pair generation is handled by
* {@link AbstractEdDSAKeyGenBuilder}.
*
* @param spec algorithm-specific key specification
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public KeyPair generateKeyPair(S spec) {
throw new UnsupportedOperationException("Generation not supported by this spec.");
}
/**
* Imports an EdDSA public key from its X.509-encoded form.
*
* <p>
* This method uses {@link KeyFactory} initialized with the algorithm returned
* by {@link #jcaKeyFactoryAlg()} to parse the bytes provided by
* {@link #encodedX509(AlgorithmKeySpec)}.
* </p>
*
* @param spec algorithm-specific key specification containing X.509 bytes
* @return a reconstructed {@link PublicKey} instance
* @throws GeneralSecurityException if the key material is invalid or the JCA
* provider does not support the algorithm
*/
@Override
public PublicKey importPublic(S spec) throws GeneralSecurityException {
KeyFactory kf = KeyFactory.getInstance(jcaKeyFactoryAlg());
return kf.generatePublic(new X509EncodedKeySpec(encodedX509(spec)));
}
/**
* Unsupported in this builder, since private key import is delegated to the
* matching {@code *PrivateKeySpec} builder.
*
* @param spec algorithm-specific key specification
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public PrivateKey importPrivate(S spec) {
throw new UnsupportedOperationException("Use the corresponding PrivateKeySpec.");
}
}

View File

@@ -0,0 +1,246 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.common.eddsa;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.alg.common.sig.GenericJcaSignatureContext;
import zeroecho.core.context.SignatureContext;
import zeroecho.core.tag.TagEngine;
import zeroecho.core.tag.ThrowingBiPredicate.VerificationBiPredicate;
/**
* Base class for EdDSA signature contexts that adapts a JCA {@code Signature}
* for streaming sign and verify.
*
* <p>
* This class is a thin adapter over {@link GenericJcaSignatureContext}: it
* wires the JCA signature name (for example, {@code "Ed25519"} or
* {@code "Ed448"}) and a fixed tag length, then delegates all
* {@link SignatureContext} operations to the internal delegate. Concrete
* subclasses such as {@code Ed25519SignatureContext} and
* {@code Ed448SignatureContext} expose role-specific contexts for signing or
* verifying.
* </p>
*
* <h2>Responsibilities</h2>
* <ul>
* <li>Bind an EdDSA variant to a {@link CryptoAlgorithm} and a key
* ({@link PrivateKey} for signing, {@link PublicKey} for verifying).</li>
* <li>Delegate {@link SignatureContext} and {@link TagEngine} behavior to the
* {@link GenericJcaSignatureContext} instance.</li>
* <li>Enforce a fixed tag length appropriate for the algorithm (64 bytes for
* Ed25519, 114 bytes for Ed448).</li>
* </ul>
*
* <h2>Thread-safety</h2>
* <p>
* Instances are stateful and not guaranteed to be thread-safe. Use one context
* per signing or verification pipeline.
* </p>
*
* @since 1.0
*/
public class CommonEdDSASignatureContext implements SignatureContext {
private final GenericJcaSignatureContext delegate;
/**
* Constructs a signing context for the given EdDSA algorithm.
*
* <p>
* The created context operates in SIGN mode. The JCA engine is obtained using
* the supplied {@code jcaSignatureName}, and the produced signature length is
* fixed to {@code tagLength}.
* </p>
*
* @param algorithm associated algorithm descriptor; must not be
* {@code null}
* @param privateKey private key used for signing; must not be
* {@code null}
* @param jcaSignatureName JCA signature name (for example, {@code "Ed25519"} or
* {@code "Ed448"}); must not be {@code null}
* @param tagLength fixed signature length in bytes (64 for Ed25519, 114
* for Ed448)
* @throws GeneralSecurityException if the JCA provider cannot initialize the
* signature engine
* @throws NullPointerException if any argument is {@code null}
*/
protected CommonEdDSASignatureContext(final CryptoAlgorithm algorithm, final PrivateKey privateKey,
final String jcaSignatureName, final int tagLength) throws GeneralSecurityException {
this.delegate = new GenericJcaSignatureContext(algorithm, privateKey,
GenericJcaSignatureContext.jcaFactory(jcaSignatureName, null),
GenericJcaSignatureContext.SignLengthResolver.fixed(tagLength));
}
/**
* Constructs a verification context for the given EdDSA algorithm.
*
* <p>
* The created context operates in VERIFY mode. The JCA engine is obtained using
* the supplied {@code jcaSignatureName}, and the expected signature length is
* fixed to {@code tagLength}.
* </p>
*
* @param algorithm associated algorithm descriptor; must not be
* {@code null}
* @param publicKey public key used for verification; must not be
* {@code null}
* @param jcaSignatureName JCA signature name (for example, {@code "Ed25519"} or
* {@code "Ed448"}); must not be {@code null}
* @param tagLength fixed signature length in bytes (64 for Ed25519, 114
* for Ed448)
* @throws GeneralSecurityException if the JCA provider cannot initialize the
* signature engine
* @throws NullPointerException if any argument is {@code null}
*/
protected CommonEdDSASignatureContext(final CryptoAlgorithm algorithm, final PublicKey publicKey,
final String jcaSignatureName, final int tagLength) throws GeneralSecurityException {
this.delegate = new GenericJcaSignatureContext(algorithm, publicKey,
GenericJcaSignatureContext.jcaFactory(jcaSignatureName, null),
GenericJcaSignatureContext.VerifyLengthResolver.fixed(tagLength));
}
/**
* Returns the algorithm associated with this context.
*
* @return the {@link CryptoAlgorithm} descriptor
*/
@Override
public CryptoAlgorithm algorithm() {
return delegate.algorithm();
}
/**
* Returns the signing or verification key bound to this context.
*
* @return the {@link java.security.Key} in use
*/
@Override
public java.security.Key key() {
return delegate.key();
}
/**
* Releases resources associated with this context.
*
* <p>
* After calling {@code close()}, further use of this context is undefined.
* </p>
*/
@Override
public void close() {
delegate.close();
}
/**
* Wraps an upstream {@link InputStream} so that all data read from it is
* processed by the underlying signature engine.
*
* <p>
* In SIGN mode the wrapped stream emits the original data followed by a
* detached signature trailer at end-of-stream. In VERIFY mode the wrapped
* stream emits only the body and performs verification at end-of-stream against
* the expected tag.
* </p>
*
* @param upstream input stream supplying data to be signed or verified; must
* not be {@code null}
* @return a wrapped stream that updates the signature engine on read
* @throws IOException if stream wrapping fails
*/
@Override
public InputStream wrap(InputStream upstream) throws IOException {
return delegate.wrap(upstream);
}
/**
* Returns the fixed tag (signature) length in bytes for this algorithm.
*
* @return the signature length in bytes
*/
@Override
public int tagLength() {
return delegate.tagLength();
}
/**
* Sets the expected signature (tag) used in VERIFY mode.
*
* <p>
* Passing {@code null} clears the expected tag.
* </p>
*
* @param expected the signature bytes to verify against, or {@code null} to
* clear
*/
@Override
public void setExpectedTag(byte[] expected) {
delegate.setExpectedTag(expected);
}
/**
* Sets the verification approach used to compare the computed and expected
* signatures.
*
* @param strategy verification predicate to apply in VERIFY mode; may be
* decorated to throw or flag
*/
@Override
public void setVerificationApproach(VerificationBiPredicate<Signature> strategy) {
delegate.setVerificationApproach(strategy);
}
/**
* Returns the core verification predicate used by this context.
*
* <p>
* The returned predicate typically delegates to
* {@link Signature#verify(byte[])}.
* </p>
*
* @return the base verification predicate
*/
@Override
public VerificationBiPredicate<Signature> getVerificationCore() {
return delegate.getVerificationCore();
}
}

View File

@@ -0,0 +1,86 @@
/**
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* EdDSA (Edwards-curve Digital Signature Algorithm) key builders and contexts.
*
* <p>
* This package provides common infrastructure for Ed25519 and Ed448 algorithm
* support. It focuses on key-pair generation, importing encoded keys, and
* wiring EdDSA variants into the generic signature context API.
* </p>
*
* <h2>Scope and responsibilities</h2>
* <ul>
* <li>Provide abstract builders for EdDSA key-pair generation and encoded key
* import.</li>
* <li>Offer an abstract signature context that binds an EdDSA variant to the
* generic JCA-based signature context, with fixed tag lengths.</li>
* <li>Keep provider-specific concerns encapsulated in small, composable
* building blocks.</li>
* </ul>
*
* <h2>Components</h2>
* <ul>
* <li><b>Key generation:</b>
* {@link zeroecho.core.alg.common.eddsa.AbstractEdDSAKeyGenBuilder} integrates
* with {@link java.security.KeyPairGenerator} to produce new Ed25519/Ed448 key
* pairs.</li>
* <li><b>Private key import:</b>
* {@link zeroecho.core.alg.common.eddsa.AbstractEncodedPrivateKeyBuilder}
* reconstructs private keys from PKCS#8 encodings via
* {@link java.security.KeyFactory}.</li>
* <li><b>Public key import:</b>
* {@link zeroecho.core.alg.common.eddsa.AbstractEncodedPublicKeyBuilder}
* 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>
* </ul>
*
* <h2>Design notes</h2>
* <ul>
* <li>Builders are stateless and safe for concurrent use; each import or
* generation creates a fresh JCA engine internally.</li>
* <li>Import methods intentionally throw for unsupported directions (e.g.,
* public import in the private builder) to keep responsibilities clear.</li>
* <li>Signature contexts are not thread-safe; they are expected to be used for
* a single signing or verification stream at a time.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.common.eddsa;

View File

@@ -0,0 +1,80 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* Common algorithm infrastructure shared across multiple cryptographic
* families.
*
* <p>
* This package contains reusable building blocks and adapters that are not tied
* to a single primitive family but are needed by several of them. It provides
* generic JCA wrappers, abstract base classes for key builders, adapters to map
* KEM into agreement workflows, and streaming helpers for signature engines.
* </p>
*
* <h2>Scope and responsibilities</h2>
* <ul>
* <li>Expose abstract base classes for asymmetric key generation and encoded
* key import so that concrete algorithms can implement only variant-specific
* details.</li>
* <li>Provide generic JCA adapters for key agreement and signature processing
* that integrate with the core streaming context model.</li>
* <li>Offer thin adapters to reinterpret existing primitives (for example, KEM
* as a two-message agreement) for higher-level composition layers.</li>
* </ul>
*
* <h2>Subpackages</h2>
* <ul>
* <li>{@link zeroecho.core.alg.common.agreement} generic JCA-based agreement
* contexts and KEM-to-agreement adapters.</li>
* <li>{@link zeroecho.core.alg.common.eddsa} EdDSA infrastructure: abstract
* key builders, encoded key importers, and signature contexts for Ed25519 and
* Ed448.</li>
* <li>{@link zeroecho.core.alg.common.sig} streaming JCA-backed signature
* contexts and internal stream helpers for sign/verify pipelines.</li>
* </ul>
*
* <h2>Design notes</h2>
* <ul>
* <li>Abstract builders separate generation from import paths, and unsupported
* operations fail fast with clear exceptions.</li>
* <li>Contexts adapt JCA primitives into the cores streaming model, handling
* resource lifecycles and fixed tag lengths where applicable.</li>
* <li>All components are designed to be composable and reusable across multiple
* algorithms, reducing duplication and ensuring consistent behavior.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.common;

View File

@@ -0,0 +1,531 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.common.sig;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Arrays;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.context.SignatureContext;
import zeroecho.core.tag.SignatureVerificationStrategy;
import zeroecho.core.tag.TagEngine;
import zeroecho.core.tag.ThrowingBiPredicate;
import zeroecho.core.tag.ThrowingBiPredicate.VerificationBiPredicate;
/**
* Adapts a JCA {@link java.security.Signature} to the streaming
* {@link SignatureContext}/{@link TagEngine} model.
*
* <p>
* {@code GenericJcaSignatureContext} binds a key and a
* {@link java.security.Signature} engine to a pull-based pipeline: the wrapped
* stream forwards bytes unchanged while updating the engine. In SIGN mode a
* fixed-length trailer containing the signature is appended at end-of-stream;
* in VERIFY mode the computed signature is compared at end-of-stream to an
* expected tag supplied by the caller. The trailer length is determined up
* front so pipelines can append or strip trailers without buffering the whole
* stream.
* </p>
*
* <h2>Modes and usage</h2>
* <ul>
* <li><b>SIGN</b>: consume body bytes; on EOF call
* {@link java.security.Signature#sign()} and append a trailer of
* {@link #tagLength()} bytes.</li>
* <li><b>VERIFY</b>: consume body bytes; on EOF compare against the expected
* tag set via {@link #setExpectedTag(byte[])} using the verification approach
* configured with
* {@link #setVerificationApproach(ThrowingBiPredicate.VerificationBiPredicate)}.</li>
* </ul>
*
* <h2>Length resolvers</h2>
* <p>
* A fixed tag length is required in both modes. For SIGN, a
* {@link SignLengthResolver} supplies the produced length (either a constant or
* by probing a provider). For VERIFY, a {@link VerifyLengthResolver} supplies
* the expected length derived from key parameters or a known constant.
* </p>
*
* <h2>Examples</h2>
* <h3>Sign with RSA-PSS</h3> <pre>
* {@code
* GenericJcaSignatureContext ctx = new GenericJcaSignatureContext(
* algorithm,
* privateKey,
* GenericJcaSignatureContext.jcaFactory("RSASSA-PSS", null),
* GenericJcaSignatureContext.SignLengthResolver.probeWith("RSASSA-PSS", null));
*
* try (InputStream in = ctx.wrap(sourceStream)) {
* in.transferTo(out); // body, then trailer of ctx.tagLength() bytes
* }
* }
* </pre>
*
* <h3>Verify detached RSA signature</h3> <pre>
* {@code
* GenericJcaSignatureContext vctx = new GenericJcaSignatureContext(
* algorithm,
* publicKey,
* GenericJcaSignatureContext.jcaFactory("SHA256withRSA", null),
* GenericJcaSignatureContext.VerifyLengthResolver.fixed(256)); // 2048-bit modulus
*
* vctx.setVerificationApproach(vctx.getVerificationCore().getThrowOnMismatch());
* vctx.setExpectedTag(signatureBytes);
*
* try (InputStream verified = vctx.wrap(bodyWithoutTrailer)) {
* verified.transferTo(java.io.OutputStream.nullOutputStream()); // throws on mismatch at EOF
* }
* }
* </pre>
*
* <h2>Thread-safety</h2>
* <p>
* Instances are stateful, single-use, and not thread-safe. Call
* {@link #wrap(InputStream)} at most once per instance.
* </p>
*/
public final class GenericJcaSignatureContext implements SignatureContext {
private static final Logger LOG = Logger.getLogger(GenericJcaSignatureContext.class.getName());
private final CryptoAlgorithm algorithm;
private final Key key;
private final boolean signMode;
private final Signature engine;
/**
* Declared tag length used by {@link TagEngine#tagLength()} in both modes.
*
* <p>
* Determined during construction by the provided resolver and constant for this
* context's lifetime.
* </p>
*/
private final int declaredTagLen;
private byte[] expectedTag;
private VerificationBiPredicate<Signature> verificationStrategy;
// lifecycle
private boolean wrapped; // = false;
private Stream activeStream;
private boolean autoCloseActiveStream;
/**
* Factory of initialized {@link java.security.Signature} engines.
*
* <p>
* Implementations must return a ready-to-use {@code Signature} configured for
* signing or verifying with the given key.
* </p>
*/
@FunctionalInterface
public interface EngineFactory {
/**
* Creates and initializes a {@link java.security.Signature} for the given key
* and mode.
*
* @param key key to initialize the engine with;
* {@link java.security.PrivateKey} for sign mode,
* {@link java.security.PublicKey} for verify mode
* @param signMode {@code true} for signing, {@code false} for verifying
* @return initialized {@code Signature} ready for incremental
* {@code update(...)} calls
* @throws GeneralSecurityException if engine creation or initialization fails
*/
Signature create(Key key, boolean signMode) throws GeneralSecurityException;
}
/**
* Strategy for determining the signature trailer length in SIGN mode.
*
* <p>
* The resolver is evaluated before the signing engine is created and may probe
* a provider when the length is not fixed by specification.
* </p>
*/
@FunctionalInterface
public interface SignLengthResolver {
/**
* Resolves the signature length for a given private key in SIGN mode.
*
* @param privateKey private key used for signing
* @return exact number of bytes {@link java.security.Signature#sign()} will
* produce
* @throws GeneralSecurityException if the length cannot be determined
*/
int resolve(PrivateKey privateKey) throws GeneralSecurityException;
/**
* Returns a resolver that always reports a fixed length.
*
* @param len positive length in bytes
* @return resolver returning {@code len}
* @throws IllegalArgumentException if {@code len} &lt;= 0
*/
static SignLengthResolver fixed(int len) {
LOG.log(Level.FINE, "SignLengthResolver.len={0}", len);
if (len <= 0) {
throw new IllegalArgumentException("fixed signature length must be > 0");
}
return pk -> len;
}
/**
* Returns a resolver that probes a JCA {@link java.security.Signature} by
* signing an empty message.
*
* <p>
* Useful for algorithms/providers where the produced length is not trivially
* known from parameters.
* </p>
*
* @param jcaAlg JCA signature name (for example, {@code "SHA256withRSA"},
* {@code "Ed25519"})
* @param providerName optional provider name; {@code null} selects the
* highest-priority provider
* @return resolver that initializes a {@code Signature} for signing and returns
* {@code sign().length}
*/
static SignLengthResolver probeWith(final String jcaAlg, final String providerName) {
return privateKey -> {
final Signature s = (providerName == null) ? Signature.getInstance(jcaAlg)
: Signature.getInstance(jcaAlg, providerName);
s.initSign(privateKey);
return s.sign().length; // empty-message probe
};
}
}
/**
* Strategy for determining the expected signature length in VERIFY mode.
*
* <p>
* The resolver is evaluated during construction and should return the fixed tag
* length for verification.
* </p>
*/
@FunctionalInterface
public interface VerifyLengthResolver {
/**
* Resolves the expected signature length for a given public key in VERIFY mode.
*
* @param publicKey public key used for verification
* @return exact number of bytes expected in the verification tag
* @throws GeneralSecurityException if the length cannot be determined
*/
int resolve(PublicKey publicKey) throws GeneralSecurityException;
/**
* Returns a resolver that always reports a fixed length.
*
* @param len positive length in bytes
* @return resolver returning {@code len}
* @throws IllegalArgumentException if {@code len} &lt;= 0
*/
static VerifyLengthResolver fixed(int len) {
LOG.log(Level.FINE, "VerifyLengthResolver.len={0}", len);
if (len <= 0) {
throw new IllegalArgumentException("fixed signature length must be > 0");
}
return pk -> len;
}
}
/**
* Convenience factory that produces a JCA {@link java.security.Signature}
* initialized for sign or verify.
*
* <p>
* The returned factory performs {@link Signature#getInstance(String)}
* (optionally with a provider) and then calls
* {@link Signature#initSign(PrivateKey)} or
* {@link Signature#initVerify(PublicKey)}.
* </p>
*
* <pre>
* {@code
* EngineFactory f = GenericJcaSignatureContext.jcaFactory("SHA256withRSA", null);
* Signature signer = f.create(privateKey, true);
* }
* </pre>
*
* @param jcaAlg JCA signature algorithm name; must not be {@code null}
* @param provider optional provider name; {@code null} selects the
* highest-priority provider
* @return factory creating initialized {@code Signature} engines for the
* specified algorithm/provider
*/
public static EngineFactory jcaFactory(final String jcaAlg, final String provider) {
return (key, signMode) -> {
final Signature s = (provider == null) ? Signature.getInstance(jcaAlg)
: Signature.getInstance(jcaAlg, provider);
if (signMode) {
s.initSign((PrivateKey) key);
} else {
s.initVerify((PublicKey) key);
}
return s;
};
}
/**
* Constructs a SIGN-mode context.
*
* <p>
* First resolves the produced signature length via {@code lengthResolver}, then
* creates and initializes the signing engine via {@code engineFactory}.
* </p>
*
* @param algorithm logical algorithm descriptor associated with this
* context; must not be {@code null}
* @param privateKey private key used for signing; must not be {@code null}
* @param engineFactory factory creating an initialized
* {@link java.security.Signature} in sign mode; must not
* be {@code null}
* @param lengthResolver strategy to resolve the produced signature length up
* front; must not be {@code null}
* @throws GeneralSecurityException if length resolution or engine
* initialization fails
* @throws NullPointerException if any required argument is {@code null}
*/
public GenericJcaSignatureContext(final CryptoAlgorithm algorithm, final PrivateKey privateKey,
final EngineFactory engineFactory, final SignLengthResolver lengthResolver)
throws GeneralSecurityException {
this.algorithm = Objects.requireNonNull(algorithm, "algorithm");
this.key = Objects.requireNonNull(privateKey, "privateKey");
Objects.requireNonNull(engineFactory, "engineFactory");
Objects.requireNonNull(lengthResolver, "lengthResolver");
// compute trailer length first (with an independent probe if needed)
this.declaredTagLen = lengthResolver.resolve(privateKey);
this.engine = engineFactory.create(privateKey, true);
this.signMode = true;
}
/**
* Constructs a VERIFY-mode context.
*
* <p>
* First resolves the expected tag length via {@code verifyLengthResolver}, then
* creates and initializes the verifying engine via {@code engineFactory}.
* </p>
*
* @param algorithm logical algorithm descriptor associated with this
* context; must not be {@code null}
* @param publicKey public key used for verification; must not be
* {@code null}
* @param engineFactory factory creating an initialized
* {@link java.security.Signature} in verify mode;
* must not be {@code null}
* @param verifyLengthResolver strategy to resolve the expected signature length
* up front; must not be {@code null}
* @throws GeneralSecurityException if length resolution or engine
* initialization fails
* @throws NullPointerException if any required argument is {@code null}
*/
public GenericJcaSignatureContext(final CryptoAlgorithm algorithm, final PublicKey publicKey,
final EngineFactory engineFactory, final VerifyLengthResolver verifyLengthResolver)
throws GeneralSecurityException {
this.algorithm = Objects.requireNonNull(algorithm, "algorithm");
this.key = Objects.requireNonNull(publicKey, "publicKey");
Objects.requireNonNull(engineFactory, "engineFactory");
Objects.requireNonNull(verifyLengthResolver, "verifyLengthResolver");
this.engine = engineFactory.create(publicKey, false);
this.signMode = false;
this.declaredTagLen = verifyLengthResolver.resolve(publicKey);
}
/**
* Returns the logical algorithm associated with this context.
*
* @return the algorithm descriptor
*/
@Override
public CryptoAlgorithm algorithm() {
return algorithm;
}
/**
* Returns the key bound to this context.
*
* @return the signing key in SIGN mode or the verification key in VERIFY mode
*/
@Override
public Key key() {
return key;
}
/**
* Closes the active wrapped stream, if any, suppressing
* {@link java.io.IOException}.
*
* <p>
* Keeping {@code close()} non-throwing simplifies pipeline cleanup.
* </p>
*/
@Override
public void close() {
LOG.log(Level.FINE, "close");
if (autoCloseActiveStream) {
try {
if (activeStream != null) {
activeStream.close();
}
} catch (IOException ignore) {
LOG.log(Level.INFO, "exception ignored on close", ignore);
}
}
}
/**
* Wraps the supplied upstream stream so the underlying
* {@link java.security.Signature} is updated as data flows.
*
* <p>
* This method may be called only once per instance. In SIGN mode the returned
* stream appends a trailer of {@link #tagLength()} bytes when the upstream
* finishes. In VERIFY mode the returned stream performs verification at EOF
* against the expected tag configured via {@link #setExpectedTag(byte[])} and
* surfaces the outcome using the verification approach set via
* {@link #setVerificationApproach(ThrowingBiPredicate.VerificationBiPredicate)}.
* </p>
*
* @param upstream source stream whose bytes will be fed into the signature;
* must not be {@code null}
* @return stream that must be fully consumed to trigger signing or verification
* @throws NullPointerException if {@code upstream} is {@code null}
* @throws IllegalStateException if this context has already wrapped a stream
* @throws IOException if signing or verification fails during
* processing or finalization
*/
@Override
public InputStream wrap(final InputStream upstream) throws IOException {
Objects.requireNonNull(upstream, "upstream");
if (wrapped) {
throw new IllegalStateException(
"This SignatureContext instance was already used; create a new one per stream.");
}
wrapped = true;
LOG.log(Level.INFO, "wrap for signing, tagLength={0}", declaredTagLen);
Stream s = new Stream(engine, signMode, upstream, tagLength(), expectedTag, verifier());
this.activeStream = s;
return s;
}
/**
* Returns the declared tag length in bytes.
*
* <p>
* Computed during construction by the configured resolver and constant for the
* lifetime of this context.
* </p>
*
* @return fixed signature trailer length
*/
@Override
public int tagLength() {
// Always advertise a concrete length so callers (like TagTrailer) can strip
// trailers.
return declaredTagLen;
}
/**
* Sets the expected verification tag to be checked when the wrapped stream
* finishes.
*
* <p>
* Applicable only in VERIFY mode. Passing {@code null} clears the tag. The
* array is defensively copied. If a wrapped stream is already active, its
* expected tag is updated as well.
* </p>
*
* @param expected expected signature bytes or {@code null} to clear
* @throws UnsupportedOperationException if called in SIGN mode
*/
@Override
public void setExpectedTag(final byte[] expected) {
if (signMode) {
throw new UnsupportedOperationException("setExpectedTag is only applicable in VERIFY mode");
}
this.expectedTag = (expected == null) ? null : Arrays.copyOf(expected, expected.length);
if (activeStream != null) {
activeStream.setExpectedTag(expected);
}
}
/**
* Sets the verification approach used in VERIFY mode to compare the expected
* and computed signatures.
*
* @param strategy verification predicate; may be decorated to throw or to flag
* into a context; {@code null} keeps the default
*/
@Override
public void setVerificationApproach(VerificationBiPredicate<Signature> strategy) {
verificationStrategy = strategy;
}
/**
* Returns the core verification predicate for signature comparison.
*
* @return predicate that delegates to {@link Signature#verify(byte[])}
*/
@Override
public VerificationBiPredicate<Signature> getVerificationCore() {
return new SignatureVerificationStrategy();
}
/**
* Selects the effective verification predicate: the user-supplied strategy if
* present, otherwise the default core with throw-on-mismatch decoration.
*
* @return effective verification predicate
*/
private VerificationBiPredicate<Signature> verifier() {
return verificationStrategy == null ? getVerificationCore().getThrowOnMismatch() : verificationStrategy;
}
}

View File

@@ -0,0 +1,225 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.common.sig;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.Signature;
import java.security.SignatureException;
import java.util.logging.Level;
import java.util.logging.Logger;
import zeroecho.core.err.VerificationException;
import zeroecho.core.io.AbstractPassthroughInputStream;
import zeroecho.core.tag.ThrowingBiPredicate.VerificationBiPredicate;
import zeroecho.core.util.Strings;
/**
* Passthrough stream that feeds a {@link java.security.Signature} for streaming
* sign or verify.
*
* <p>
* All bytes read from the wrapped upstream are forwarded unchanged to the
* caller and are also passed to the signature engine via
* {@link Signature#update(byte[], int, int)}. At EOF the behavior depends on
* mode:
* </p>
* <ul>
* <li><b>Sign mode</b>: compute the signature once via {@link Signature#sign()}
* and append it as a trailer by returning it from
* {@link #produceTrailer(byte[])}.</li>
* <li><b>Verify mode</b>: compute and compare the signature at completion using
* the provided {@link VerificationBiPredicate}, surfacing the result according
* to that strategy.</li>
* </ul>
*
* <h2>Lifecycle</h2>
* <ul>
* <li>{@link #update(byte[], int, int)} - feed each chunk into the engine.</li>
* <li>{@link #produceTrailer(byte[])} - sign mode only: emit the computed
* signature once; verify mode: return 0 (no trailer).</li>
* <li>{@link #onCompleted()} - verify mode only: invoke the verification
* strategy and translate any {@link VerificationException} to
* {@link java.io.IOException}.</li>
* </ul>
*
* <h2>Thread-safety</h2>
* <p>
* Not thread-safe. Bound to a single {@link java.security.Signature} engine and
* upstream stream.
* </p>
*/
final class Stream extends AbstractPassthroughInputStream {
private static final Logger LOG = Logger.getLogger(Stream.class.getName());
/** Cached trailer in sign mode: computed once from {@link Signature#sign()}. */
private byte[] signature;
/** Underlying JCA signature engine used for streaming updates. */
private final Signature engine;
/** True if this stream signs, false if it verifies. */
private final boolean signMode;
/** Expected signature to verify against (verify mode only). */
private byte[] expectedTag;
/** Verification strategy controlling how results are surfaced. */
private final VerificationBiPredicate<Signature> strategy;
/**
* Creates a streaming signature passthrough.
*
* @param engine initialized {@link Signature} engine; must not be
* {@code null}
* @param signMode {@code true} for signing, {@code false} for verifying
* @param upstream upstream input to wrap; must not be {@code null}
* @param bodyBufSize body buffer size for passthrough
* @param expectedTag expected signature in verify mode; may be {@code null} to
* disable verification
* @param strategy verification predicate used in verify mode; ignored in
* sign mode; must not be {@code null} in verify mode
*/
/* package */ Stream(final Signature engine, final boolean signMode, final InputStream upstream,
final int bodyBufSize, final byte[] expectedTag, final VerificationBiPredicate<Signature> strategy) {
super(upstream, bodyBufSize);
this.engine = engine;
this.signMode = signMode;
this.expectedTag = expectedTag;
this.strategy = strategy;
}
/**
* Feeds a chunk of bytes into the underlying signature engine.
*
* @param buf input buffer
* @param off start offset within {@code buf}
* @param len number of bytes to process
* @throws IOException if {@link Signature#update(byte[], int, int)} fails
*/
@Override
protected void update(final byte[] buf, final int off, final int len) throws IOException {
try {
LOG.log(Level.FINEST, "update with {0} bytes block", len);
engine.update(buf, off, len);
} catch (SignatureException e) {
throw new IOException("Signature.update failed", e);
}
}
/**
* Emits the signature trailer exactly once in sign mode; emits nothing in
* verify mode.
*
* <p>
* In sign mode this computes the signature if needed, copies it to {@code buf},
* and returns its length. If the signature does not fit, an {@link IOException}
* is thrown. In verify mode the method returns {@code 0}.
* </p>
*
* @param buf destination buffer
* @return number of bytes written, or {@code 0} if no trailer is emitted
* @throws IOException if signature computation fails or the trailer does not
* fit in {@code buf}
*/
@Override
protected int produceTrailer(byte[] buf) throws IOException {
LOG.log(Level.FINE, "trailer (length={0}) production started", buf.length);
if (!signMode) {
LOG.log(Level.FINE, "signature will not be appended to the stream: not in the signing mode");
return 0; // VERIFY mode never emits a trailer
}
if (signature == null) {
try {
signature = engine.sign();
} catch (GeneralSecurityException e) {
throw new IOException("Signature finalize failed", e);
}
}
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "signature produced: length={0} signature={1}",
new Object[] { signature.length, Strings.toShortString(signature) });
}
if (signature.length == 0) {
return 0;
}
if (signature.length > buf.length) {
throw new IOException(
"Trailer does not fit into buffer have: " + buf.length + " but need: " + signature.length);
}
System.arraycopy(signature, 0, buf, 0, signature.length);
return signature.length;
}
/**
* Finalizes verification on stream completion (verify mode only).
*
* <p>
* Compares the computed signature against {@code expectedTag} using
* {@link #strategy}. Any {@link VerificationException} raised by the strategy
* is translated to {@link IOException}. In sign mode this method does nothing.
* </p>
*
* @throws IOException if the verification strategy signals failure
*/
@Override
protected void onCompleted() throws IOException {
if (signMode) {
LOG.log(Level.FINE, "Signature verification is not executed during signing");
return; // nothing to do for signing
}
try {
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "verification {0}", Strings.toShortString(expectedTag));
}
strategy.verify(engine, expectedTag);
} catch (VerificationException e) {
throw new IOException(e);
}
}
/**
* Replaces the expected verification tag.
*
* @param expectedTag new expected tag; may be {@code null} to clear
*/
/* default */ void setExpectedTag(byte[] expectedTag) {
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "resetting expectedTag to {0}", Strings.toShortString(expectedTag));
}
this.expectedTag = expectedTag;
}
}

View File

@@ -0,0 +1,128 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* Streaming signature contexts and helpers that adapt JCA
* {@link java.security.Signature} to a pull-based pipeline.
*
* <p>
* This package provides a generic signature context that wraps an
* {@link java.io.InputStream}, updates a JCA engine as bytes flow, and either
* appends a fixed-length trailer (sign) or verifies against a caller-supplied
* tag (verify). A small internal passthrough stream performs byte forwarding
* and end-of-stream finalization.
* </p>
*
* <h2>Design goals</h2>
* <ul>
* <li><b>Single-use pipeline integration:</b> create a context for SIGN or
* VERIFY and call {@code wrap(InputStream)} once.</li>
* <li><b>Known tag length up front:</b> produced/expected signature length is
* resolved at construction so downstream components can append or strip
* trailers without buffering the whole stream.</li>
* <li><b>Provider encapsulation:</b> JCA provider details are hidden behind
* factories that create initialized engines.</li>
* </ul>
*
* <h2>Components</h2>
* <ul>
* <li><b>GenericJcaSignatureContext</b> - streaming context that uses a
* 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>Stream</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>
* <p>
* Tag length is determined by resolvers supplied at construction time:
* </p>
* <ul>
* <li><b>SignLengthResolver</b> - returns the produced length (fixed or by
* probing a provider with an empty-message sign).</li>
* <li><b>VerifyLengthResolver</b> - returns the expected length (fixed or
* derived from key parameters).</li>
* </ul>
*
* <h2>Verification approach</h2>
* <p>
* Verification is delegated to a {@code VerificationBiPredicate} strategy. The
* default core delegates to {@link java.security.Signature#verify(byte[])}, and
* callers may decorate it to throw on mismatch or to record results externally.
* </p>
*
* <h2>Usage sketch</h2>
* <h3>Sign</h3> <pre>
* {@code
* GenericJcaSignatureContext ctx = new GenericJcaSignatureContext(
* algorithm,
* privateKey,
* GenericJcaSignatureContext.jcaFactory("SHA256withRSA", null),
* GenericJcaSignatureContext.SignLengthResolver.probeWith("SHA256withRSA", null));
*
* try (InputStream in = ctx.wrap(upstream)) {
* in.transferTo(out); // body, then signature trailer of ctx.tagLength() bytes
* }
* }
* </pre>
*
* <h3>Verify</h3> <pre>
* {@code
* GenericJcaSignatureContext vctx = new GenericJcaSignatureContext(
* algorithm,
* publicKey,
* GenericJcaSignatureContext.jcaFactory("SHA256withRSA", null),
* GenericJcaSignatureContext.VerifyLengthResolver.fixed(256));
*
* vctx.setVerificationApproach(vctx.getVerificationCore().getThrowOnMismatch());
* vctx.setExpectedTag(signatureBytes);
*
* try (InputStream verified = vctx.wrap(bodyWithoutTrailer)) {
* verified.transferTo(java.io.OutputStream.nullOutputStream()); // throws on mismatch at EOF
* }
* }
* </pre>
*
* <h2>Thread-safety</h2>
* <ul>
* <li>Contexts and streams are stateful, not thread-safe, and intended for
* single use.</li>
* <li>Create a new context per wrapped stream.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.common.sig;

View File

@@ -0,0 +1,176 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.dh;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import zeroecho.core.AlgorithmFamily;
import zeroecho.core.KeyUsage;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.alg.common.agreement.GenericJcaAgreementContext;
import zeroecho.core.context.AgreementContext;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* Diffie-Hellman algorithm registration for use in the pluggable cryptography
* catalog.
*
* <p>
* This class registers a Diffie-Hellman (DH) algorithm under the identifier
* {@code "DH"} with sane defaults and JCA compatibility. It declares the
* algorithms capabilities, default parameters, and key builders so that higher
* layers can use DH for key agreement in a consistent and type-safe way.
* </p>
*
* <h2>Features</h2>
* <ul>
* <li>Algorithm identifier and display name: {@code "DH"}.</li>
* <li>Agreement capability producing {@link AgreementContext} instances backed
* by the JCA algorithm name {@code "DiffieHellman"}.</li>
* <li>Default parameter specification: {@link DhSpec#ffdhe2048()}.</li>
* <li>Asymmetric key builder for {@link DhSpec}-based key pair generation via
* {@link DhKeyGenBuilder}.</li>
* <li>Support for importing keys from {@link DhPublicKeySpec} and
* {@link DhPrivateKeySpec} using standard JCA key factories.</li>
* </ul>
*
* <h2>Thread-safety</h2> Instances of {@code DhAlgorithm} are immutable after
* construction. They are typically created once at application startup and
* safely reused across threads as shared registrations in a
* {@link zeroecho.core.CryptoCatalog}.
*
* <h2>Usage example</h2> <pre>{@code
* // Create and register the algorithm
* DhAlgorithm dh = new DhAlgorithm();
* CryptoCatalog catalog = CryptoCatalog.load();
*
* // Create a DH spec (or rely on the default ffdhe2048)
* DhSpec spec = DhSpec.ffdhe3072();
*
* // Generate a key pair using the registered builder
* KeyPair kp = CryptoAlgorithms.keyPair("DH", spec);
*
* // Obtain an agreement context for DH key agreement
* AgreementContext ctx = CryptoAlgorithms.create("DH", KeyUsage.AGREEMENT, kp.getPrivate(), spec);
*
* // Use the context with a peer public key to derive a shared secret
* ctx.setPeerPublic(peerPublicKey);
* byte[] secret = ctx.deriveSecret();
* }</pre>
*
* @see AlgorithmFamily
* @see KeyUsage
* @see AgreementContext
* @see DhSpec
* @see DhKeyGenBuilder
* @see AbstractCryptoAlgorithm
* @since 1.0
*/
public final class DhAlgorithm extends AbstractCryptoAlgorithm {
/**
* Creates a Diffie-Hellman algorithm registration with standard defaults.
*
* <p>
* This constructor:
* </p>
* <ul>
* <li>Registers the algorithm under the identifier {@code "DH"} and display
* name {@code "DH"}.</li>
* <li>Declares an {@link AlgorithmFamily#AGREEMENT} capability for
* {@link KeyUsage#AGREEMENT}, producing {@link GenericJcaAgreementContext}
* instances bound to the JCA name {@code "DiffieHellman"}.</li>
* <li>Sets {@link DhSpec#ffdhe2048()} as the default parameter
* specification.</li>
* <li>Registers {@link DhKeyGenBuilder} as the asymmetric key builder for
* {@link DhSpec}.</li>
* <li>Registers asymmetric key builders for importing keys via
* {@link DhPublicKeySpec} and {@link DhPrivateKeySpec} using JCA
* {@link java.security.KeyFactory}.</li>
* </ul>
*/
public DhAlgorithm() {
super("DH", "DH");
capability(AlgorithmFamily.AGREEMENT, KeyUsage.AGREEMENT, AgreementContext.class, PrivateKey.class,
DhSpec.class,
(PrivateKey k, DhSpec s) -> new GenericJcaAgreementContext(this, k, "DiffieHellman", null),
DhSpec::ffdhe2048);
registerAsymmetricKeyBuilder(DhSpec.class, new DhKeyGenBuilder(), DhSpec::ffdhe2048);
registerAsymmetricKeyBuilder(DhPublicKeySpec.class, new AsymmetricKeyBuilder<>() {
@Override
public KeyPair generateKeyPair(DhPublicKeySpec spec) throws GeneralSecurityException {
throw new UnsupportedOperationException("Use DhKeyGenBuilder for keypair generation.");
}
@Override
public PublicKey importPublic(DhPublicKeySpec spec) throws GeneralSecurityException {
KeyFactory kf = KeyFactory.getInstance("DH");
return kf.generatePublic(new X509EncodedKeySpec(spec.encoded()));
}
@Override
public PrivateKey importPrivate(DhPublicKeySpec spec) throws GeneralSecurityException {
throw new UnsupportedOperationException("Use DhPrivateKeySpec for private key import.");
}
}, null);
registerAsymmetricKeyBuilder(DhPrivateKeySpec.class, new AsymmetricKeyBuilder<>() {
@Override
public KeyPair generateKeyPair(DhPrivateKeySpec spec) throws GeneralSecurityException {
throw new UnsupportedOperationException("Use DhKeyGenBuilder for keypair generation.");
}
@Override
public PublicKey importPublic(DhPrivateKeySpec spec) throws GeneralSecurityException {
throw new UnsupportedOperationException("Use DhPrivateKeySpec for public key import.");
}
@Override
public PrivateKey importPrivate(DhPrivateKeySpec spec) throws GeneralSecurityException {
KeyFactory kf = KeyFactory.getInstance("DH");
return kf.generatePrivate(new PKCS8EncodedKeySpec(spec.encoded()));
}
}, null);
}
}

View File

@@ -0,0 +1,194 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.dh;
import java.security.AlgorithmParameterGenerator;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.spec.DHParameterSpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>DH key pair builder</h2>
*
* Builds Diffie-Hellman key pairs using the JCA providers available at runtime.
* This builder targets the JCA algorithm name {@code "DiffieHellman"} and
* accepts a {@link DhSpec} describing the desired parameter set.
*
* <p>
* <strong>How it works</strong>
* </p>
* <ul>
* <li>If {@link DhSpec#params()} is non-null, those DH parameters are
* used.</li>
* <li>Otherwise, the builder creates parameters with
* {@link java.security.AlgorithmParameterGenerator} for the requested bit
* length, then initializes a {@link java.security.KeyPairGenerator} to produce
* the key pair.</li>
* </ul>
*
* <p>
* <strong>Security note</strong>: Prefer named, vetted groups such as the RFC
* 7919 FFDHE sets exposed by {@code DhSpec} (for example
* {@code DhSpec.ffdhe2048()}) over ad-hoc parameter generation.
* </p>
*
* <p>
* <strong>Thread-safety</strong>: Instances are stateless and can be shared
* across threads.
* </p>
*
* <p>
* <strong>Example</strong>
* </p>
* <pre>{@code
* DhKeyGenBuilder builder = new DhKeyGenBuilder();
*
* // Use a standard FFDHE group
* KeyPair kp1 = builder.generateKeyPair(DhSpec.ffdhe2048());
*
* // Or request parameters by size when DhSpec.params() is null
* DhSpec sized = DhSpec.ofBits(3072); // example factory returning size-only spec
* KeyPair kp2 = builder.generateKeyPair(sized);
* }</pre>
*/
public final class DhKeyGenBuilder implements AsymmetricKeyBuilder<DhSpec> {
/**
* Generates a Diffie-Hellman key pair for the given specification.
*
* <p>
* If {@code spec.params()} is non-null, the contained
* {@link javax.crypto.spec.DHParameterSpec} is used directly. Otherwise,
* parameters are generated with
* {@link java.security.AlgorithmParameterGenerator} using
* {@code spec.sizeBits()}, and the resulting parameters are applied to a
* {@link java.security.KeyPairGenerator} for {@code "DiffieHellman"}.
* </p>
*
* <p>
* <strong>Performance note</strong>: On some providers, ad-hoc parameter
* generation can be slow for large sizes. When possible, pass a {@code DhSpec}
* that supplies a known-good {@code DHParameterSpec}.
* </p>
*
* <p>
* <strong>Example</strong>
* </p>
* <pre>{@code
* KeyPair kp = new DhKeyGenBuilder().generateKeyPair(DhSpec.ffdhe3072());
* }</pre>
*
* @param spec the DH specification indicating either a concrete
* {@link DHParameterSpec} or the target bit length for parameter
* generation; must not be null
* @return a newly generated DH {@link KeyPair}
* @throws GeneralSecurityException if the JCA provider does not support
* Diffie-Hellman, parameter generation fails,
* or key pair generation cannot be completed
*/
@Override
public KeyPair generateKeyPair(DhSpec spec) throws GeneralSecurityException {
// Simplest path: AlgorithmParameterGenerator to get params, then init KPG.
AlgorithmParameterGenerator apg = AlgorithmParameterGenerator.getInstance("DiffieHellman");
apg.init(spec.sizeBits());
DHParameterSpec dh = spec.params();
if (dh == null) {
AlgorithmParameters params = apg.generateParameters();
dh = params.getParameterSpec(DHParameterSpec.class);
}
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DiffieHellman");
kpg.initialize(dh);
return kpg.generateKeyPair();
}
/**
* Unsupported for DH in this builder.
*
* <p>
* Raw public key import is not implemented because this builder focuses on key
* generation from DH parameters. Use higher-level catalog or codec facilities
* to parse or construct {@link PublicKey} instances if needed.
* </p>
*
* <p>
* <strong>Example</strong>
* </p>
* <pre>{@code
* // This will throw UnsupportedOperationException
* new DhKeyGenBuilder().importPublic(DhSpec.ffdhe2048());
* }</pre>
*
* @param spec the DH specification (ignored)
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public PublicKey importPublic(DhSpec spec) {
throw new UnsupportedOperationException();
}
/**
* Unsupported for DH in this builder.
*
* <p>
* Raw private key import is not implemented because this builder focuses on key
* generation from DH parameters. Use higher-level catalog or codec facilities
* to parse or construct {@link PrivateKey} instances if needed.
* </p>
*
* <p>
* <strong>Example</strong>
* </p>
* <pre>{@code
* // This will throw UnsupportedOperationException
* new DhKeyGenBuilder().importPrivate(DhSpec.ffdhe2048());
* }</pre>
*
* @param spec the DH specification (ignored)
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public PrivateKey importPrivate(DhSpec spec) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,149 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.dh;
import java.util.Base64;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* Key specification for a Diffie-Hellman private key encoded in PKCS#8 format.
*
* <p>
* This spec encapsulates the raw encoded private key bytes following the
* standard PKCS#8 structure. It is used with algorithms in the
* {@link zeroecho.core.AlgorithmFamily#AGREEMENT} family, allowing safe
* import/export of DH private keys across contexts.
* </p>
*
* <h2>Design</h2>
* <ul>
* <li>Immutable: the internal byte array is defensively copied at construction
* and when returned by {@link #encoded()}.</li>
* <li>Encodable: supports marshaling to/from a
* {@link zeroecho.core.marshal.PairSeq} so keys can be serialized in
* human-readable or protocol-friendly formats.</li>
* <li>Type-safe: used as a binding for asymmetric key builders within
* {@code CryptoAlgorithm} definitions.</li>
* </ul>
*
* <h2>Example</h2> <pre>{@code
* // Import a DH private key from encoded bytes
* DhPrivateKeySpec spec = new DhPrivateKeySpec(pkcs8Bytes);
* PrivateKey priv = CryptoAlgorithms.privateKey("DH", spec);
*
* // Marshal to a text-friendly representation
* PairSeq ps = DhPrivateKeySpec.marshal(spec);
*
* // Unmarshal back to spec
* DhPrivateKeySpec parsed = DhPrivateKeySpec.unmarshal(ps);
* }</pre>
*
* @since 1.0
*/
public class DhPrivateKeySpec implements AlgorithmKeySpec {
private static final String PKCS8_B64 = "pkcs8.b64";
private final byte[] pkcs8;
/**
* Creates a new specification from a PKCS#8 encoded DH private key.
*
* @param pkcs8 the encoded private key bytes; must not be {@code null}
* @throws IllegalArgumentException if {@code pkcs8} is {@code null}
*/
public DhPrivateKeySpec(byte[] pkcs8) {
if (pkcs8 == null) {
throw new IllegalArgumentException("pkcs8 must not be null");
}
this.pkcs8 = pkcs8.clone();
}
/**
* Returns a clone of the encoded PKCS#8 bytes.
*
* @return a defensive copy of the PKCS#8 encoded DH private key
*/
public byte[] encoded() {
return pkcs8.clone();
}
/**
* Marshals the given DH private key spec into a {@link PairSeq}.
*
* <p>
* The sequence contains a {@code type} field with value {@code "DH-PRIV"} and a
* {@code pkcs8.b64} field with the Base64 encoding of the key.
* </p>
*
* @param spec the spec to encode
* @return a {@code PairSeq} representation of the spec
* @throws NullPointerException if {@code spec} is {@code null}
*/
public static PairSeq marshal(DhPrivateKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.pkcs8);
return PairSeq.of("type", "DH-PRIV", PKCS8_B64, b64);
}
/**
* Reconstructs a {@code DhPrivateKeySpec} from a {@link PairSeq}.
*
* <p>
* The sequence must contain a {@code pkcs8.b64} field with the Base64-encoded
* private key. If this field is missing, an {@link IllegalArgumentException} is
* thrown.
* </p>
*
* @param p the sequence to decode
* @return a new spec containing the decoded PKCS#8 bytes
* @throws IllegalArgumentException if required fields are missing or malformed
*/
public static DhPrivateKeySpec unmarshal(PairSeq p) {
byte[] out = null;
PairSeq.Cursor cur = p.cursor();
while (cur.next()) {
String k = cur.key();
String v = cur.value();
if (PKCS8_B64.equals(k)) {
out = Base64.getDecoder().decode(v);
}
}
if (out == null) {
throw new IllegalArgumentException("pkcs8.b64 missing for DH private key");
}
return new DhPrivateKeySpec(out);
}
}

View File

@@ -0,0 +1,149 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.dh;
import java.util.Arrays;
import java.util.Base64;
import java.util.Objects;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* Key specification for a Diffie-Hellman public key encoded in X.509 format.
*
* <p>
* This spec carries the raw encoded public key bytes in the standard X.509
* SubjectPublicKeyInfo form. It is intended for use with algorithms in the
* {@link zeroecho.core.AlgorithmFamily#AGREEMENT} family, allowing safe
* import/export of DH public keys across contexts.
* </p>
*
* <h2>Design</h2>
* <ul>
* <li>Immutable: the internal byte array is defensively copied at construction
* and when returned by {@link #encoded()}.</li>
* <li>Encodable: supports marshaling to/from a
* {@link zeroecho.core.marshal.PairSeq} so keys can be serialized in
* human-readable or protocol-friendly formats.</li>
* <li>Type-safe: used as a binding for asymmetric key builders within
* {@code CryptoAlgorithm} definitions.</li>
* </ul>
*
* <h2>Example</h2> <pre>{@code
* // Import a DH public key from encoded bytes
* DhPublicKeySpec spec = new DhPublicKeySpec(x509Bytes);
* PublicKey pub = CryptoAlgorithms.publicKey("DH", spec);
*
* // Marshal to a text-friendly representation
* PairSeq ps = DhPublicKeySpec.marshal(spec);
*
* // Unmarshal back to spec
* DhPublicKeySpec parsed = DhPublicKeySpec.unmarshal(ps);
* }</pre>
*
* @since 1.0
*/
public class DhPublicKeySpec implements AlgorithmKeySpec {
private static final String X509_B64 = "x509.b64";
private final byte[] x509;
/**
* Creates a new specification from an X.509 encoded DH public key.
*
* @param key the encoded public key bytes; must not be {@code null}
* @throws NullPointerException if {@code key} is {@code null}
*/
public DhPublicKeySpec(byte[] key) {
Objects.requireNonNull(key, "key must not be null");
this.x509 = Arrays.copyOf(key, key.length);
}
/**
* Returns a clone of the encoded X.509 bytes.
*
* @return a defensive copy of the X.509 encoded DH public key
*/
public byte[] encoded() {
return x509.clone();
}
/**
* Marshals the given DH public key spec into a {@link PairSeq}.
*
* <p>
* The sequence contains a {@code type} field with value {@code "DH-PUB"} and an
* {@code x509.b64} field with the Base64 encoding of the key.
* </p>
*
* @param spec the spec to encode
* @return a {@code PairSeq} representation of the spec
* @throws NullPointerException if {@code spec} is {@code null}
*/
public static PairSeq marshal(DhPublicKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.x509);
return PairSeq.of("type", "DH-PUB", X509_B64, b64);
}
/**
* Reconstructs a {@code DhPublicKeySpec} from a {@link PairSeq}.
*
* <p>
* The sequence must contain an {@code x509.b64} field with the Base64-encoded
* public key. If this field is missing, an {@link IllegalArgumentException} is
* thrown.
* </p>
*
* @param p the sequence to decode
* @return a new spec containing the decoded X.509 bytes
* @throws IllegalArgumentException if required fields are missing or malformed
*/
public static DhPublicKeySpec unmarshal(PairSeq p) {
byte[] out = null;
PairSeq.Cursor cur = p.cursor();
while (cur.next()) {
String k = cur.key();
String v = cur.value();
if (X509_B64.equals(k)) {
out = Base64.getDecoder().decode(v);
}
}
if (out == null) {
throw new IllegalArgumentException("x509.b64 missing for DH public key");
}
return new DhPublicKeySpec(out);
}
}

View File

@@ -0,0 +1,215 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.dh;
import javax.crypto.spec.DHParameterSpec;
import org.bouncycastle.crypto.agreement.DHStandardGroups;
import org.bouncycastle.crypto.params.DHParameters;
import zeroecho.core.annotation.Describable;
import zeroecho.core.spec.AlgorithmKeySpec;
import zeroecho.core.spec.ContextSpec;
/**
* <h2>Diffie-Hellman parameter specification</h2>
*
* Immutable container for Diffie-Hellman (DH) group parameters and their
* effective key size.
*
* <p>
* A {@code DhSpec} acts both as a {@link zeroecho.core.spec.ContextSpec} for
* agreement contexts and as an {@link zeroecho.core.spec.AlgorithmKeySpec} for
* key generation. Instances wrap a standard
* {@link javax.crypto.spec.DHParameterSpec} with a declared bit length. They
* are typically constructed via the provided factory methods for the RFC 7919
* finite-field DH (FFDHE) groups.
* </p>
*
* <h2>Predefined groups</h2>
* <ul>
* <li>{@link #ffdhe2048()} - ~112-bit security, suitable minimum baseline.</li>
* <li>{@link #ffdhe3072()} - ~128-bit security, recommended
* general-purpose.</li>
* <li>{@link #ffdhe4096()} - ~150-bit security.</li>
* <li>{@link #ffdhe6144()} - ~176-bit security.</li>
* <li>{@link #ffdhe8192()} - ~192-bit security, high-assurance
* environments.</li>
* </ul>
*
* <p>
* These correspond exactly to the safe-prime groups defined in
* <a href="https://datatracker.ietf.org/doc/html/rfc7919">RFC 7919</a>. Using
* well-standardized groups avoids parameter negotiation pitfalls and ensures
* interoperability.
* </p>
*
* <h2>Thread-safety</h2> {@code DhSpec} instances are immutable and can be
* shared freely.
*
* <h2>Example</h2> <pre>{@code
* // Create a key pair in the FFDHE-3072 group
* KeyPair kp = CryptoAlgorithms.keyPair("DH", DhSpec.ffdhe3072());
*
* // Establish an agreement context
* AgreementContext ctx = CryptoAlgorithms.create(
* "DH", KeyUsage.AGREEMENT, kp.getPrivate(), DhSpec.ffdhe3072());
* }</pre>
*
* @since 1.0
*/
public final class DhSpec implements ContextSpec, AlgorithmKeySpec, Describable {
private final int sizeBits; // e.g., 2048
private final DHParameterSpec params;
private final String desc;
private DhSpec(int sizeBits, DHParameterSpec params, String desc) {
this.sizeBits = sizeBits;
this.params = params;
this.desc = desc;
}
/**
* Returns the standard FFDHE-2048 group.
*
* <p>
* Provides ~112-bit classical security, the minimum acceptable baseline for
* modern use per RFC 7919.
* </p>
*
* @return a {@code DhSpec} wrapping FFDHE-2048 parameters
*/
public static DhSpec ffdhe2048() {
DHParameters p = DHStandardGroups.rfc7919_ffdhe2048;
return new DhSpec(2048, new DHParameterSpec(p.getP(), p.getG()), "rfc7919_ffdhe2048");
}
/**
* Returns the standard FFDHE-3072 group.
*
* <p>
* Provides ~128-bit classical security, recommended for general-purpose
* long-term deployments.
* </p>
*
* @return a {@code DhSpec} wrapping FFDHE-3072 parameters
*/
public static DhSpec ffdhe3072() {
DHParameters p = DHStandardGroups.rfc7919_ffdhe3072;
return new DhSpec(3072, new DHParameterSpec(p.getP(), p.getG()), "rfc7919_ffdhe3072");
}
/**
* Returns the standard FFDHE-4096 group.
*
* <p>
* Provides ~150-bit classical security. Chosen when a moderate increase over
* 128-bit strength is required.
* </p>
*
* @return a {@code DhSpec} wrapping FFDHE-4096 parameters
*/
public static DhSpec ffdhe4096() {
DHParameters p = DHStandardGroups.rfc7919_ffdhe4096;
return new DhSpec(4096, new DHParameterSpec(p.getP(), p.getG()), "rfc7919_ffdhe4096");
}
/**
* Returns the standard FFDHE-6144 group.
*
* <p>
* Provides ~176-bit classical security, suitable for high-value environments
* requiring extra margin.
* </p>
*
* @return a {@code DhSpec} wrapping FFDHE-6144 parameters
*/
public static DhSpec ffdhe6144() {
DHParameters p = DHStandardGroups.rfc7919_ffdhe6144;
return new DhSpec(6144, new DHParameterSpec(p.getP(), p.getG()), "rfc7919_ffdhe6144");
}
/**
* Returns the standard FFDHE-8192 group.
*
* <p>
* Provides ~192-bit classical security, rarely used but suitable for
* maximum-assurance deployments.
* </p>
*
* @return a {@code DhSpec} wrapping FFDHE-8192 parameters
*/
public static DhSpec ffdhe8192() {
DHParameters p = DHStandardGroups.rfc7919_ffdhe8192;
return new DhSpec(8192, new DHParameterSpec(p.getP(), p.getG()), "rfc7919_ffdhe8192");
}
/**
* Returns the underlying JCA {@link DHParameterSpec}.
*
* @return the DH parameters (prime modulus and generator)
*/
public DHParameterSpec params() {
return params;
}
/**
* Returns the effective bit length of this group.
*
* <p>
* This value reflects the size of the prime modulus {@code p}.
* </p>
*
* @return size of the group modulus in bits
*/
public int sizeBits() {
return sizeBits;
}
/**
* Returns a short, human-readable description of this object.
*
* <p>
* The description should be concise and stable enough for logging or display
* purposes, while avoiding exposure of any sensitive information.
* </p>
*
* @return non-null descriptive string
*/
@Override
public String description() {
return desc;
}
}

View File

@@ -0,0 +1,85 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* Diffie-Hellman (DH) algorithm integration.
*
* <p>
* This package provides the core support for Diffie-Hellman key agreement
* within the ZeroEcho framework. It includes the algorithm descriptor, a
* builder for generating key pairs, immutable parameter specifications, and
* encoded key specs for import/export. The implementation favors use of
* standardized RFC 7919 finite-field DH groups and encapsulates JCA
* interoperability.
* </p>
*
* <h2>Scope and responsibilities</h2>
* <ul>
* <li>Register the DH algorithm with a canonical identifier and declare its
* {@link zeroecho.core.KeyUsage#AGREEMENT} capability.</li>
* <li>Provide a builder for generating key pairs from known parameter sets or
* ad-hoc parameter generation.</li>
* <li>Expose predefined RFC 7919 FFDHE groups for safe parameter selection.
* </li>
* <li>Allow import/export of encoded keys via immutable key specs supporting
* PKCS#8 and X.509.</li>
* </ul>
*
* <h2>Components</h2>
* <ul>
* <li><b>DhAlgorithm</b>: registers the algorithm under id {@code "DH"}, binds
* agreement contexts to the JCA {@code "DiffieHellman"} implementation, and
* wires key builders.</li>
* <li><b>DhKeyGenBuilder</b>: generates key pairs using
* {@link java.security.AlgorithmParameterGenerator} or supplied
* {@link javax.crypto.spec.DHParameterSpec} instances.</li>
* <li><b>DhSpec</b>: immutable container for DH parameters; provides static
* factories for FFDHE groups (20488192 bits).</li>
* <li><b>DhPublicKeySpec</b> and <b>DhPrivateKeySpec</b>: immutable encoded key
* specs for importing/exporting X.509 and PKCS#8 encodings, with
* {@link zeroecho.core.marshal.PairSeq} marshalling support.</li>
* </ul>
*
* <h2>Design notes</h2>
* <ul>
* <li>Algorithm descriptors are immutable and thread-safe for reuse.</li>
* <li>Builders are stateless; contexts produced for key agreement are not
* thread-safe and should be used per operation.</li>
* <li>Use of standardized groups is strongly recommended over ad-hoc parameter
* generation for interoperability and safety.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.dh;

View File

@@ -0,0 +1,262 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.digest;
import java.util.Objects;
import zeroecho.core.annotation.Describable;
import zeroecho.core.annotation.DisplayName;
import zeroecho.core.spec.ContextSpec;
/**
* <h2>Digest parameters for SHA-2, SHA-3, and SHAKE</h2>
*
* {@code DigestSpec} is a context specification for the
* {@link Sha2Sha3Algorithm}, selecting which digest algorithm variant to use
* and, for extendable-output functions (XOFs), the desired output length.
*
* <h2>Algorithm selection</h2> The nested {@link Algorithm} enum defines the
* supported digest families:
* <ul>
* <li>{@link Algorithm#SHA_256}, {@link Algorithm#SHA_384},
* {@link Algorithm#SHA_512} - fixed-length SHA-2 digests.</li>
* <li>{@link Algorithm#SHA3_256}, {@link Algorithm#SHA3_512} - fixed-length
* SHA-3 digests.</li>
* <li>{@link Algorithm#SHAKE128}, {@link Algorithm#SHAKE256} -
* extendable-output functions (XOFs).</li>
* </ul>
*
* <p>
* For fixed-length digests, the {@link #outputLenBytes()} is always {@code 0}
* (the digest length is implicit). For XOFs, a positive output length must be
* supplied at construction time.
* </p>
*
* <h2>Construction</h2> Factory methods are provided for common cases:
* <ul>
* <li>{@link #sha256()}, {@link #sha384()}, {@link #sha512()}</li>
* <li>{@link #sha3_256()}, {@link #sha3_512()}</li>
* <li>{@link #shake128(int)}, {@link #shake256(int)} (require explicit output
* length)</li>
* </ul>
*
* <h2>Usage example</h2> <pre>{@code
* // Create a spec for SHAKE256 with 64-byte output
* DigestSpec spec = DigestSpec.shake256(64);
*
* // Use in context creation
* DigestContext ctx = CryptoAlgorithms.create(
* "DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE, spec);
*
* byte[] digest = ctx.doFinal(data);
* }</pre>
*
* <h2>Thread-safety</h2> Instances are immutable and can be safely reused
* across threads.
*
* @see Sha2Sha3Algorithm
* @see zeroecho.core.context.DigestContext
* @since 1.0
*/
@DisplayName("Digest parameters")
public final class DigestSpec implements ContextSpec, Describable {
/**
* Enumeration of supported digest algorithms.
*
* <p>
* Each constant defines the JCA algorithm name and whether the variant is an
* extendable-output function (XOF).
* </p>
*/
public enum Algorithm {
/** SHA-256 digest from the SHA-2 family. */
SHA_256("SHA-256", false),
/** SHA-384 digest from the SHA-2 family. */
SHA_384("SHA-384", false),
/** SHA-512 digest from the SHA-2 family. */
SHA_512("SHA-512", false),
/** SHA3-256 digest from the SHA-3 family. */
SHA3_256("SHA3-256", false),
/** SHA3-512 digest from the SHA-3 family. */
SHA3_512("SHA3-512", false),
/** SHAKE128 extendable-output function (XOF). */
SHAKE128("SHAKE128", true),
/** SHAKE256 extendable-output function (XOF). */
SHAKE256("SHAKE256", true);
private final String jca;
private final boolean xof;
Algorithm(String jca, boolean xof) {
this.jca = jca;
this.xof = xof;
}
/**
* Returns the canonical JCA algorithm name.
*
* @return JCA name string (e.g., {@code "SHA-256"} or {@code "SHAKE256"})
*/
public String jca() {
return jca;
}
/**
* Indicates whether this algorithm is an XOF (extendable-output function).
*
* @return {@code true} if this algorithm is an XOF, {@code false} otherwise
*/
public boolean isXof() {
return xof;
}
}
private final Algorithm algorithm;
private final int outputLenBytes; // for XOFs; 0 for fixed-length digests
private DigestSpec(Algorithm algorithm, int outputLenBytes) {
this.algorithm = Objects.requireNonNull(algorithm, "algorithm must not be null");
if (algorithm.isXof() && outputLenBytes <= 0) {
throw new IllegalArgumentException("XOF output length must be > 0");
}
this.outputLenBytes = algorithm.isXof() ? outputLenBytes : 0;
}
/**
* Returns a {@code DigestSpec} for SHA-256.
*
* @return spec for SHA-256
*/
public static DigestSpec sha256() {
return new DigestSpec(Algorithm.SHA_256, 0);
}
/**
* Returns a {@code DigestSpec} for SHA-384.
*
* @return spec for SHA-384
*/
public static DigestSpec sha384() {
return new DigestSpec(Algorithm.SHA_384, 0);
}
/**
* Returns a {@code DigestSpec} for SHA-512.
*
* @return spec for SHA-512
*/
public static DigestSpec sha512() {
return new DigestSpec(Algorithm.SHA_512, 0);
}
/**
* Returns a {@code DigestSpec} for SHA3-256.
*
* @return spec for SHA3-256
*/
public static DigestSpec sha3_256() {
return new DigestSpec(Algorithm.SHA3_256, 0);
}
/**
* Returns a {@code DigestSpec} for SHA3-512.
*
* @return spec for SHA3-512
*/
public static DigestSpec sha3_512() {
return new DigestSpec(Algorithm.SHA3_512, 0);
}
/**
* Returns a {@code DigestSpec} for SHAKE128 with the given output length.
*
* @param outLen desired output length in bytes (must be > 0)
* @return spec for SHAKE128 with the specified output length
* @throws IllegalArgumentException if {@code outLen <= 0}
*/
public static DigestSpec shake128(int outLen) {
return new DigestSpec(Algorithm.SHAKE128, outLen);
}
/**
* Returns a {@code DigestSpec} for SHAKE256 with the given output length.
*
* @param outLen desired output length in bytes (must be > 0)
* @return spec for SHAKE256 with the specified output length
* @throws IllegalArgumentException if {@code outLen <= 0}
*/
public static DigestSpec shake256(int outLen) {
return new DigestSpec(Algorithm.SHAKE256, outLen);
}
/**
* Returns the digest algorithm.
*
* @return algorithm enum constant
*/
public Algorithm algorithm() {
return algorithm;
}
/**
* Returns the requested output length in bytes.
*
* <p>
* For fixed-length digests, this value is {@code 0}. For XOFs, this is the
* explicit length requested at construction.
* </p>
*
* @return output length in bytes (0 if not applicable)
*/
public int outputLenBytes() {
return outputLenBytes;
}
/**
* Returns a human-readable description of this specification.
*
* <p>
* For XOFs, the description includes the chosen output length, e.g.,
* {@code "SHAKE128(64B)"}.
* </p>
*
* @return description string
*/
@Override
public String description() {
return algorithm.isXof() ? algorithm.jca() + "(" + outputLenBytes + "B)" : algorithm.jca();
}
}

View File

@@ -0,0 +1,452 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.digest;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.context.DigestContext;
import zeroecho.core.err.VerificationException;
import zeroecho.core.io.AbstractPassthroughInputStream;
import zeroecho.core.tag.ByteVerificationStrategy;
import zeroecho.core.tag.TagEngine;
import zeroecho.core.tag.ThrowingBiPredicate;
import zeroecho.core.tag.ThrowingBiPredicate.VerificationBiPredicate;
/**
* Digest context that adapts a JCA {@link java.security.MessageDigest} to a
* pull-based streaming pipeline.
*
* <p>
* {@code JcaDigestContext} implements {@link DigestContext} for
* {@link Sha2Sha3Algorithm} by delegating to a JCA
* {@link java.security.MessageDigest} and exposing the {@link TagEngine}
* contract. As data is read from the wrapped stream the digest state is
* updated; at end-of-stream the digest is finalized and either appended as a
* trailer (produce mode) or compared against an expected tag (verify mode).
* </p>
*
* <h2>Features</h2>
* <ul>
* <li>Supports SHA-2, SHA-3, and SHAKE digests as defined by
* {@link DigestSpec}.</li>
* <li>Provides pull-stream semantics via
* {@link #wrap(java.io.InputStream)}.</li>
* <li>Two modes:
* <ul>
* <li><b>Produce mode</b>: appends the computed digest as a trailer.</li>
* <li><b>Verify mode</b>: computes the digest and compares it to an expected
* tag using a pluggable verification approach (see
* {@link #setVerificationApproach(ThrowingBiPredicate.VerificationBiPredicate)}).</li>
* </ul>
* </li>
* <li>For XOFs (SHAKE), the output length is explicitly controlled by
* {@link DigestSpec}.</li>
* </ul>
*
* <h2>Verification approach</h2>
* <p>
* Verification is delegated to a
* {@link ThrowingBiPredicate.VerificationBiPredicate} over {@code byte[]}. The
* default core returned by {@link #getVerificationCore()} performs
* constant-time byte comparison ({@link ByteVerificationStrategy}). If no
* strategy is set explicitly, the effective verifier defaults to
* {@code getVerificationCore().getThrowOnMismatch()}, causing a mismatch to be
* signaled as an I/O error at end-of-stream.
* </p>
*
* <h2>Thread-safety</h2>
* <p>
* Instances are stateful and not thread-safe. Each context may wrap exactly one
* stream; subsequent calls to {@link #wrap(java.io.InputStream)} are rejected.
* </p>
*
* @since 1.0
*/
public final class JcaDigestContext implements DigestContext {
private static final Logger LOG = Logger.getLogger(JcaDigestContext.class.getName());
/** Algorithm descriptor that owns this context. */
private final CryptoAlgorithm algorithm;
/** JCA message digest engine used for computation. */
private final MessageDigest md; // NOPMD
/** Digest parameters (algorithm choice, output length for XOFs). */
private final DigestSpec spec;
/** JCA algorithm name string, cached for diagnostics. */
private final String algorithmName;
/** Trailer length in bytes (digest size or requested XOF length). */
private final int outLen;
/** Whether this context is operating in verification mode. */
private boolean verifyMode; // = false;
/** Expected digest/tag value provided by caller. */
private byte[] expectedTag;
/** Verification failure handling policy. */
private VerificationBiPredicate<byte[]> strategy;
/** Whether {@link #wrap(InputStream)} has already been called. */
private boolean wrapped; // = false;
/** Active digesting stream, if {@link #wrap(InputStream)} was invoked. */
private Stream activeStream;
private boolean autoCloseActiveStream;
/**
* Creates a new digest context bound to a JCA {@link MessageDigest}.
*
* <p>
* This constructor is normally invoked by {@link Sha2Sha3Algorithm}:
* </p>
*
* <pre>{@code
* MessageDigest md = MessageDigest.getInstance(spec.algorithm().jca());
* return new JcaDigestContext(algo, md, spec);
* }</pre>
*
* @param algorithm the owning algorithm descriptor
* @param md JCA message digest implementation
* @param spec digest parameters (algorithm, output length for XOFs)
* @throws NullPointerException if any parameter is null
* @throws IllegalArgumentException if the output length for an XOF is invalid
*/
public JcaDigestContext(final CryptoAlgorithm algorithm, final MessageDigest md, final DigestSpec spec) {
this.algorithm = Objects.requireNonNull(algorithm, "algorithm");
this.md = Objects.requireNonNull(md, "md");
this.spec = Objects.requireNonNull(spec, "spec");
this.algorithmName = md.getAlgorithm();
this.outLen = resolveOutputLength(md, spec);
}
/**
* Returns the {@link CryptoAlgorithm} definition that created this context.
*
* @return algorithm descriptor
*/
@Override
public CryptoAlgorithm algorithm() {
return algorithm;
}
/**
* Returns the key associated with this context.
*
* <p>
* Digests are unkeyed; this method always returns {@code null}.
* </p>
*
* @return always {@code null}
*/
@Override
public java.security.Key key() {
return null; // digest is keyless
}
/**
* Closes the active digest stream if one was opened.
*
* <p>
* The close operation is non-throwing; any underlying {@link IOException} is
* suppressed to preserve pipeline robustness.
* </p>
*/
@Override
public void close() {
LOG.log(Level.INFO, "close()");
if (autoCloseActiveStream) {
try {
if (activeStream != null) {
activeStream.close();
}
} catch (IOException ignore) {
LOG.log(Level.INFO, "exception ignored on close", ignore);
}
}
}
/**
* Wraps an upstream {@link InputStream} in a digesting pipeline stage.
*
* <p>
* Data read from the returned stream is passed through unchanged while being
* accumulated into the underlying {@link MessageDigest}. At end-of-stream the
* digest is finalized and either emitted as a trailer (produce mode) or
* compared to the expected tag (verify mode).
* </p>
*
* <p>
* This method may be called at most once per context instance.
* </p>
*
* @param upstream the input stream to wrap; must not be {@code null}
* @return a stream that transparently digests data
* @throws IOException if stream initialization fails
* @throws IllegalStateException if {@code wrap} was already invoked on this
* instance
* @throws NullPointerException if {@code upstream} is {@code null}
*/
@Override
public InputStream wrap(final InputStream upstream) throws IOException {
Objects.requireNonNull(upstream, "upstream");
if (wrapped) {
throw new IllegalStateException(
"This JcaDigestContext instance was already used; create a new one per stream.");
}
wrapped = true;
md.reset();
Stream s = new Stream(upstream);
this.activeStream = s;
return s;
}
/**
* Returns the digest length in bytes.
*
* @return output length of the digest or XOF
*/
@Override
public int tagLength() {
return outLen;
}
/**
* Installs an expected digest tag for verification mode.
*
* <p>
* When provided, the context operates in verification mode: at end-of-stream
* the computed digest is compared against this tag using the effective
* verification strategy. Passing {@code null} clears the tag and leaves the
* mode unchanged.
* </p>
*
* @param expected expected digest bytes; copied defensively (may be
* {@code null} to clear)
*/
@Override
public void setExpectedTag(final byte[] expected) {
this.expectedTag = (expected == null) ? null : Arrays.copyOf(expected, expected.length);
this.verifyMode = true;
}
/**
* Internal pass-through stream that updates the digest as data flows and
* finalizes at EOF.
*
* <p>
* In produce mode, the finalized digest is emitted as a trailer. In verify
* mode, the finalized digest is compared to the expected tag using the
* configured verification strategy.
* </p>
*/
private final class Stream extends AbstractPassthroughInputStream {
/** Produce mode: cached digest to emit as a trailer. */
private byte[] trailer; // NOPMD
/**
* Creates a new digesting stream.
*
* @param upstream underlying input stream to wrap
*/
private Stream(final InputStream upstream) {
super(upstream, 8192);
}
/**
* Updates the digest with newly read bytes.
*
* @param buf buffer containing input
* @param off offset into buffer
* @param len number of bytes to process
*/
@Override
protected void update(final byte[] buf, final int off, final int len) {
md.update(buf, off, len);
}
/**
* Produces the digest trailer once in produce mode; emits nothing in verify
* mode.
*
* <p>
* In produce mode this finalizes the digest and copies it into {@code buf}. For
* XOFs or when the provider's reported digest length differs from the
* configured output length, exactly {@code outLen} bytes are requested. In
* verify mode the method returns {@code 0}.
* </p>
*
* @param buf destination buffer to receive the trailer
* @return number of bytes written, or {@code 0} if no trailer is emitted
* @throws IOException if digest finalization fails or the trailer does not fit
* in {@code buf}
*/
@Override
protected int produceTrailer(byte[] buf) throws IOException {
if (verifyMode) {
return 0; // verification path handled in onCompleted()
}
// Compute the digest output once for trailer emission.
final byte[] out;
try {
// For XOFs or unknown/size-mismatch digests, request exactly outLen bytes into
// a fresh array.
if (spec.algorithm().isXof() || md.getDigestLength() == 0 || md.getDigestLength() != outLen) {
out = new byte[outLen];
md.digest(out, 0, outLen);
} else {
out = md.digest();
}
} catch (java.security.DigestException e) {
throw new IOException("Digest finalize failed (" + algorithmName + ")", e);
}
trailer = out;
if (trailer.length == 0) {
return 0;
}
if (trailer.length > buf.length) {
throw new IOException("Trailer does not fit into buffer");
}
System.arraycopy(trailer, 0, buf, 0, trailer.length);
return trailer.length;
}
/**
* Completes verification in verify mode; no-op in produce mode.
*
* <p>
* Finalizes the digest and compares it to {@code expectedTag} using the
* configured verification strategy. Any {@link VerificationException} is
* translated to {@link IOException}.
* </p>
*
* @throws IOException if verification fails and the strategy signals failure
*/
@Override
protected void onCompleted() throws IOException {
if (!verifyMode) {
return; // nothing to do for produce mode; trailer already emitted (if any)
}
try {
// Compute the digest output for verification.
final byte[] out;
if (spec.algorithm().isXof() || md.getDigestLength() == 0 || md.getDigestLength() != outLen) {
out = new byte[outLen];
md.digest(out, 0, outLen);
} else {
out = md.digest();
}
strategy.verify(expectedTag, out);
} catch (java.security.DigestException e) {
throw new IllegalStateException("Digest finalize failed (" + algorithmName + ")", e);
} catch (VerificationException e) {
throw new IOException(e);
}
}
}
/**
* Resolves the digest output length for the given algorithm.
*
* <p>
* For XOFs, uses the explicit length from {@link DigestSpec}. For fixed-length
* digests, queries the provider via {@link MessageDigest#getDigestLength()}.
* Falls back to the hint in {@link DigestSpec} or 32 bytes if the provider
* reports zero.
* </p>
*
* @param md JCA message digest; must not be {@code null}
* @param spec digest specification; must not be {@code null}
* @return output length in bytes
* @throws IllegalArgumentException if an XOF output length is non-positive
*/
private static int resolveOutputLength(final MessageDigest md, final DigestSpec spec) {
Objects.requireNonNull(md, "md");
Objects.requireNonNull(spec, "spec");
if (spec.algorithm().isXof()) {
int out = spec.outputLenBytes();
if (out < 1) { // NOPMD
throw new IllegalArgumentException("XOF requires positive output length");
}
return out;
}
int dl = md.getDigestLength();
if (dl > 0) {
return dl;
}
int hinted = spec.outputLenBytes();
return hinted > 0 ? hinted : 32; // conservative default if provider doesn't report length
}
/**
* Sets the verification approach used to compare computed and expected digests.
*
* <p>
* If no strategy is set explicitly, the effective verifier defaults to
* {@code getVerificationCore().getThrowOnMismatch()}.
* </p>
*
* @param strategy verification predicate; may be {@code null} to keep the
* default behavior
*/
@Override
public void setVerificationApproach(VerificationBiPredicate<byte[]> strategy) {
this.strategy = strategy;
}
/**
* Returns the core verification predicate for digest tags.
*
* <p>
* The default core is a constant-time byte-array comparison implemented by
* {@link ByteVerificationStrategy}.
* </p>
*
* @return base verification predicate (never {@code null})
*/
@Override
public VerificationBiPredicate<byte[]> getVerificationCore() {
return new ByteVerificationStrategy();
}
}

View File

@@ -0,0 +1,126 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.digest;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import zeroecho.core.AlgorithmFamily;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.KeyUsage;
import zeroecho.core.NullKey;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.context.DigestContext;
/**
* <h2>SHA-2, SHA-3, and SHAKE digest algorithms</h2>
*
* Implementation of the {@link CryptoAlgorithm} abstraction for unkeyed hash
* functions from the SHA-2 and SHA-3 families, including extendable-output
* functions (XOFs) such as SHAKE128 and SHAKE256.
*
* <p>
* This algorithm is registered under the canonical identifier {@code "DIGEST"}
* with the display name {@code "SHA-2/SHA-3/SHAKE"}.
* </p>
*
* <h2>Capabilities</h2> The algorithm declares a single capability:
* <ul>
* <li>Family: {@link AlgorithmFamily#DIGEST}</li>
* <li>Role: {@link KeyUsage#DIGEST}</li>
* <li>Context type: {@link zeroecho.core.context.DigestContext}</li>
* <li>Key type: {@link NullKey} (no cryptographic key required)</li>
* <li>Spec type: {@link DigestSpec} (algorithm selection)</li>
* <li>Default spec: {@link DigestSpec#sha256()}</li>
* </ul>
*
* <h2>Provider model</h2> Internally, this class delegates to the JCA
* {@link java.security.MessageDigest} implementation corresponding to the
* chosen digest variant. The constructor performs lookup via
* {@link MessageDigest#getInstance(String)}, and wraps the result in a
* {@link JcaDigestContext}.
*
* <h2>Usage example</h2> <pre>{@code
* // Acquire algorithm via static registry
* CryptoAlgorithm algo = CryptoAlgorithms.require("DIGEST");
*
* // Create a digest context for SHA3-512
* DigestContext ctx = CryptoAlgorithms.create(
* "DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE, DigestSpec.sha3_512());
*
* // Stream data into the digest
* ctx.update(inputStream);
*
* byte[] hash = ctx.doFinal();
* }</pre>
*
* <p>
* The {@link NullKey} sentinel must always be provided for the key argument
* since digests are unkeyed functions. The {@link DigestSpec} determines the
* exact digest variant (SHA-256, SHA-512, SHA3-256, SHAKE128, etc.).
* </p>
*
* <h2>Thread-safety</h2> The {@code Sha2Sha3Algorithm} instance is immutable
* and can be shared across threads. The created {@link DigestContext} objects
* are not thread-safe and must not be used concurrently.
*
* @since 1.0
*/
public final class Sha2Sha3Algorithm extends AbstractCryptoAlgorithm {
/**
* Constructs the SHA-2/SHA-3/SHAKE digest algorithm definition and registers
* its {@link zeroecho.core.Capability}.
*
* <p>
* The default digest specification is {@link DigestSpec#sha256()}, ensuring
* compatibility with common tests and catalog listings.
* </p>
*/
public Sha2Sha3Algorithm() {
super("DIGEST", "SHA-2/SHA-3/SHAKE");
capability(AlgorithmFamily.DIGEST, KeyUsage.DIGEST, DigestContext.class, NullKey.class, DigestSpec.class,
(NullKey k, DigestSpec s) -> {
try {
MessageDigest md = MessageDigest.getInstance(s.algorithm().jca());
return new JcaDigestContext(this, md, s);
} catch (GeneralSecurityException e) {
throw new IOException("Failed to init MessageDigest: " + s.algorithm().jca(), e);
}
}, DigestSpec::sha256 // default for catalog/tests
);
}
}

View File

@@ -0,0 +1,83 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* Digest algorithms (SHA-2, SHA-3, SHAKE) and their streaming contexts.
*
* <p>
* This package provides the core unkeyed hash functions of the ZeroEcho
* library. It integrates JCA {@link java.security.MessageDigest} engines into
* the frameworks streaming context model, allowing digests to be applied as
* pipeline stages with produce/verify modes. It also defines immutable
* specifications for selecting digest variants and output lengths for
* extendable-output functions.
* </p>
*
* <h2>Scope and responsibilities</h2>
* <ul>
* <li>Expose a single algorithm descriptor under id {@code "DIGEST"} that
* covers SHA-2, SHA-3, and SHAKE variants.</li>
* <li>Provide a JCA-backed {@link zeroecho.core.context.DigestContext} for
* streaming input through a {@link java.security.MessageDigest} engine.</li>
* <li>Support both fixed-length digests and extendable-output functions (XOFs)
* with configurable output size.</li>
* <li>Ensure interoperability with JCA provider implementations.</li>
* </ul>
*
* <h2>Components</h2>
* <ul>
* <li><b>Sha2Sha3Algorithm</b>: registers the algorithm, declares its digest
* capability, and creates contexts bound to the appropriate JCA
* {@link java.security.MessageDigest}.</li>
* <li><b>DigestSpec</b>: immutable specification of digest variant and output
* length (for XOFs); provides static factories for common cases.</li>
* <li><b>JcaDigestContext</b>: streaming digest context that adapts
* {@link java.security.MessageDigest} to the core SPI, supporting produce
* (append digest) and verify (check expected tag) modes.</li>
* </ul>
*
* <h2>Design notes</h2>
* <ul>
* <li>Algorithm instances are immutable and thread-safe for reuse.</li>
* <li>Context instances are stateful and not thread-safe; create a new context
* for each pipeline.</li>
* <li>Verification policies integrate with the tagging engine to either throw
* on mismatch or set a flag in the context.</li>
* <li>Extendable-output functions require explicit output lengths; fixed
* digests derive their size from the algorithm.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.digest;

View File

@@ -0,0 +1,161 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ecdh;
import java.security.PrivateKey;
import zeroecho.core.AlgorithmFamily;
import zeroecho.core.KeyUsage;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.alg.common.agreement.GenericJcaAgreementContext;
import zeroecho.core.alg.ecdsa.EcdsaCurveSpec;
import zeroecho.core.alg.ecdsa.EcdsaPrivateKeyBuilder;
import zeroecho.core.alg.ecdsa.EcdsaPrivateKeySpec;
import zeroecho.core.alg.ecdsa.EcdsaPublicKeyBuilder;
import zeroecho.core.alg.ecdsa.EcdsaPublicKeySpec;
import zeroecho.core.context.AgreementContext;
/**
* <h2>Elliptic Curve Diffie-Hellman (ECDH) Algorithm</h2>
*
* Implementation of the Elliptic Curve Diffie-Hellman key agreement scheme
* within the ZeroEcho framework.
*
* <p>
* ECDH allows two parties, each possessing an elliptic-curve key pair, to
* derive a common shared secret over an insecure channel. Only the private key
* of one party and the public key of the other are required to compute the same
* shared value. This shared secret can then be expanded into symmetric session
* keys.
* </p>
*
* <h2>Capabilities</h2>
* <ul>
* <li>{@link AlgorithmFamily#AGREEMENT} with {@link KeyUsage#AGREEMENT}:
* creates an {@link zeroecho.core.context.AgreementContext} from a local
* {@link java.security.PrivateKey} and a peer's public key supplied through the
* context.</li>
* </ul>
*
* <h2>Key builders</h2>
* <ul>
* <li>{@link EcdhCurveSpec}: used to generate EC key pairs for ECDH; default is
* {@code P-256}.</li>
* <li>{@link EcdsaPublicKeySpec} and {@link EcdsaPrivateKeySpec}: reused for
* importing elliptic-curve keys since ECDH and ECDSA share curve parameters and
* encoding formats.</li>
* </ul>
*
* <h2>Notes</h2>
* <ul>
* <li>The {@link EcdsaCurveSpec} parameter passed in the agreement capability
* is currently ignored in this implementation (defaulting always to the curve
* of the key itself). This is retained for API uniformity and may be enforced
* in future versions.</li>
* </ul>
*
* <h2>Example</h2> <pre>{@code
* // Generate a key pair for Alice
* KeyPair aliceKeys = CryptoAlgorithms.generateKeyPair("ECDH", EcdhCurveSpec.P256);
*
* // Generate a key pair for Bob
* KeyPair bobKeys = CryptoAlgorithms.generateKeyPair("ECDH", EcdhCurveSpec.P256);
*
* // Alice computes shared secret using her private key
* AgreementContext aliceCtx = CryptoAlgorithms.create("ECDH",
* KeyUsage.AGREEMENT, aliceKeys.getPrivate(), EcdsaCurveSpec.P256);
* byte[] aliceSecret = aliceCtx.derive(bobKeys.getPublic());
*
* // Bob computes shared secret using his private key
* AgreementContext bobCtx = CryptoAlgorithms.create("ECDH",
* KeyUsage.AGREEMENT, bobKeys.getPrivate(), EcdsaCurveSpec.P256);
* byte[] bobSecret = bobCtx.derive(aliceKeys.getPublic());
*
* // aliceSecret and bobSecret will be identical
* }</pre>
*
* @since 1.0
*/
public final class EcdhAlgorithm extends AbstractCryptoAlgorithm {
/**
* Constructs a new ECDH algorithm definition.
*
* <p>
* This constructor registers the capabilities and key builders required for
* Elliptic Curve Diffie-Hellman (ECDH) key agreement:
* </p>
*
* <ul>
* <li><b>Capability</b>: {@link AlgorithmFamily#AGREEMENT} with
* {@link KeyUsage#AGREEMENT}, producing an
* {@link zeroecho.core.context.AgreementContext} from a local
* {@link java.security.PrivateKey} and a peers public key (supplied at
* runtime).</li>
*
* <li><b>Asymmetric key builders</b>:
* <ul>
* <li>{@link EcdhCurveSpec} - generates EC key pairs suitable for ECDH
* (default: {@code P-256}).</li>
* <li>{@link EcdsaPublicKeySpec} - imports existing EC public keys.</li>
* <li>{@link EcdsaPrivateKeySpec} - imports existing EC private keys.</li>
* </ul>
* </li>
* </ul>
*
* <p>
* Note: although an {@link EcdsaCurveSpec} is accepted as a context parameter,
* the current implementation does not use it when constructing the
* {@link zeroecho.core.context.AgreementContext}. The curve is inferred from
* the provided key. This placeholder remains for API uniformity and may be
* enforced in later revisions.
* </p>
*
* @since 1.0
*/
public EcdhAlgorithm() {
super("ECDH", "ECDH");
// AGREEMENT (our private key + peer public provided via context)
capability(AlgorithmFamily.AGREEMENT, KeyUsage.AGREEMENT, AgreementContext.class, PrivateKey.class,
EcdsaCurveSpec.class,
(PrivateKey k, EcdsaCurveSpec s) -> new GenericJcaAgreementContext(this, k, "ECDH", null),
() -> EcdsaCurveSpec.P256); // XXX spec is not used at all ?!?!
// Reuse EC builders/importers
registerAsymmetricKeyBuilder(EcdhCurveSpec.class, new EcdhKeyGenBuilder(), () -> EcdhCurveSpec.P256);
registerAsymmetricKeyBuilder(EcdsaPublicKeySpec.class, new EcdsaPublicKeyBuilder(), null);
registerAsymmetricKeyBuilder(EcdsaPrivateKeySpec.class, new EcdsaPrivateKeyBuilder(), null);
}
}

View File

@@ -0,0 +1,122 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ecdh;
import zeroecho.core.spec.AlgorithmKeySpec;
import zeroecho.core.spec.ContextSpec;
/**
* <h2>Standardized ECDH Curve Specifications</h2>
*
* Enumeration of commonly used NIST-recommended elliptic curves for ECDH
* (Elliptic Curve Diffie-Hellman) and related algorithms.
*
* <p>
* Each constant provides:
* </p>
* <ul>
* <li><b>Curve name</b> - the JCA-standardized name, suitable for use with
* {@link java.security.spec.ECGenParameterSpec}.</li>
* <li><b>Signature algorithm name</b> - a JCA/JCE identifier combining the hash
* function and ECDSA variant in P1363 encoding.</li>
* <li><b>Fixed signature length</b> - the expected byte length of deterministic
* ECDSA signatures for the curve, following the P1363 format.</li>
* </ul>
*
* <h2>Curves</h2>
* <ul>
* <li>{@link #P256} - secp256r1, 128-bit security level.</li>
* <li>{@link #P384} - secp384r1, 192-bit security level.</li>
* <li>{@link #P512} - secp521r1, ~256-bit security level.</li>
* </ul>
*
* <h2>Usage</h2> <pre>{@code
* // Generate a key pair on P-256
* KeyPair kp = CryptoAlgorithms.generateKeyPair("ECDH", EcdhCurveSpec.P256);
*
* // Use curve metadata
* String jcaName = EcdhCurveSpec.P256.curveName(); // "secp256r1"
* int sigLen = EcdhCurveSpec.P256.signFixedLength(); // 64 bytes
* }</pre>
*
* @since 1.0
*/
public enum EcdhCurveSpec implements ContextSpec, AlgorithmKeySpec {
/** NIST P-256 (secp256r1), approx. 128-bit security. */
P256("secp256r1", "SHA256withECDSAinP1363Format", 64),
/** NIST P-384 (secp384r1), approx. 192-bit security. */
P384("secp384r1", "SHA384withECDSAinP1363Format", 96),
/** NIST P-521 (secp521r1), approx. 256-bit security. */
P512("secp521r1", "SHA512withECDSAinP1363Format", 132);
private final String curveName;
private final String jca;
private final int tagLen;
EcdhCurveSpec(String curveName, String jca, int tagLen) {
this.curveName = curveName;
this.jca = jca;
this.tagLen = tagLen;
}
/**
* Returns the JCA-standardized name of the elliptic curve.
*
* @return curve name, e.g., {@code "secp256r1"}
*/
public String curveName() {
return curveName;
}
/**
* Returns the JCA factory name of the corresponding signature algorithm (ECDSA
* with hash, P1363 encoding).
*
* @return JCA algorithm name, e.g., {@code "SHA256withECDSAinP1363Format"}
*/
public String jcaFactory() {
return jca;
}
/**
* Returns the fixed byte length of deterministic ECDSA signatures for this
* curve in P1363 format.
*
* @return fixed signature length in bytes
*/
public int signFixedLength() {
return tagLen;
}
}

View File

@@ -0,0 +1,132 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ecdh;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.ECGenParameterSpec;
import zeroecho.core.alg.ecdsa.EcdsaPrivateKeyBuilder;
import zeroecho.core.alg.ecdsa.EcdsaPrivateKeySpec;
import zeroecho.core.alg.ecdsa.EcdsaPublicKeyBuilder;
import zeroecho.core.alg.ecdsa.EcdsaPublicKeySpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>ECDH Key Pair Generator</h2>
*
* Implementation of {@link AsymmetricKeyBuilder} for elliptic curve
* Diffie-Hellman (ECDH) key pairs.
*
* <p>
* This builder generates fresh EC key pairs suitable for ECDH key agreement. It
* accepts an {@link EcdhCurveSpec} that defines the named curve to use (e.g.,
* {@code P-256}, {@code P-384}, {@code P-521}).
* </p>
*
* <h2>Usage</h2> <pre>{@code
* // Generate a key pair on P-256
* KeyPair kp = new EcdhKeyGenBuilder().generateKeyPair(EcdhCurveSpec.P256);
* }</pre>
*
* <p>
* Import of existing keys is not supported by this builder. Instead, use
* {@link EcdsaPublicKeyBuilder} and {@link EcdsaPrivateKeyBuilder} together
* with {@code EcdsaPublicKeySpec} or {@code EcdsaPrivateKeySpec} when importing
* encoded elliptic curve keys.
* </p>
*
* @since 1.0
*/
public final class EcdhKeyGenBuilder implements AsymmetricKeyBuilder<EcdhCurveSpec> {
/**
* Generates a new elliptic curve key pair for use in ECDH key agreement.
*
* <p>
* The curve is determined from the provided {@link EcdhCurveSpec}. Internally,
* a standard JCA {@link KeyPairGenerator} for {@code "EC"} is initialized with
* the corresponding {@link ECGenParameterSpec}.
* </p>
*
* @param spec curve specification selecting the named curve
* @return a freshly generated {@link KeyPair} suitable for ECDH
* @throws GeneralSecurityException if the curve is not supported by the
* underlying JCA provider or if key generation
* fails
*/
@Override
public KeyPair generateKeyPair(EcdhCurveSpec spec) throws GeneralSecurityException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(new ECGenParameterSpec(spec.curveName()));
return kpg.generateKeyPair();
}
/**
* Unsupported operation for this builder.
*
* <p>
* Importing existing ECDH public keys should be performed via
* {@link EcdsaPublicKeyBuilder} with an {@link EcdsaPublicKeySpec}. This method
* will always throw an {@link UnsupportedOperationException}.
* </p>
*
* @param spec ignored
* @return never returns normally
* @throws UnsupportedOperationException always
*/
@Override
public java.security.PublicKey importPublic(EcdhCurveSpec spec) {
throw new UnsupportedOperationException("Use EcdhPublicKeySpec with EcdsaPublicKeyBuilder.");
}
/**
* Unsupported operation for this builder.
*
* <p>
* Importing existing ECDH private keys should be performed via
* {@link EcdsaPrivateKeyBuilder} with an {@link EcdsaPrivateKeySpec}. This
* method will always throw an {@link UnsupportedOperationException}.
* </p>
*
* @param spec ignored
* @return never returns normally
* @throws UnsupportedOperationException always
*/
@Override
public java.security.PrivateKey importPrivate(EcdhCurveSpec spec) {
throw new UnsupportedOperationException("Use EcdhPrivateKeySpec with EcdsaPrivateKeyBuilder.");
}
}

View File

@@ -0,0 +1,77 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* Elliptic Curve Diffie-Hellman (ECDH) key agreement integration.
*
* <p>
* This package provides the ECDH algorithm descriptor, named-curve selection,
* and a key-pair builder backed by the JCA. It wires ECDH into the core
* agreement SPI through a generic JCA-based agreement context and reuses EC key
* importers shared with ECDSA where appropriate.
* </p>
*
* <h2>Scope and responsibilities</h2>
* <ul>
* <li>Register a canonical ECDH algorithm and declare its agreement
* capability.</li>
* <li>Expose a curve specification for standard NIST curves suitable for
* ECDH.</li>
* <li>Provide a key-pair builder that generates EC keys on the selected curve
* using JCA primitives.</li>
* <li>Reuse EC public/private key importers to avoid duplication.</li>
* </ul>
*
* <h2>Components</h2>
* <ul>
* <li><b>EcdhAlgorithm:</b> registers the {@code "ECDH"} algorithm, binds the
* agreement capability to a JCA-backed context, and wires EC key
* builders/importers.</li>
* <li><b>EcdhCurveSpec:</b> immutable enum of named curves providing the JCA
* curve name and auxiliary metadata.</li>
* <li><b>EcdhKeyGenBuilder:</b> generates EC key pairs for ECDH on the
* requested curve.</li>
* </ul>
*
* <h2>Design notes</h2>
* <ul>
* <li>Algorithm descriptors are immutable and thread-safe; agreement contexts
* they produce are stateful and not thread-safe.</li>
* <li>Key import paths are shared between ECDH and ECDSA, as the encoding and
* curve parameters are identical at the key level.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.ecdh;

View File

@@ -0,0 +1,143 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ecdsa;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import zeroecho.core.AlgorithmFamily;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.CryptoAlgorithms;
import zeroecho.core.CryptoCatalog;
import zeroecho.core.KeyUsage;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.alg.common.sig.GenericJcaSignatureContext;
import zeroecho.core.context.SignatureContext;
/**
* <h2>Elliptic Curve Digital Signature Algorithm (ECDSA)</h2>
*
* Implementation of the ECDSA digital signature scheme within the ZeroEcho
* cryptographic framework. This algorithm supports signing and verification
* using elliptic curve keys defined by {@link EcdsaCurveSpec}.
*
* <h2>Roles</h2>
* <ul>
* <li>{@link KeyUsage#SIGN} - Create digital signatures using a
* {@link PrivateKey} on a selected curve.</li>
* <li>{@link KeyUsage#VERIFY} - Verify digital signatures using a
* {@link PublicKey} on the same curve.</li>
* </ul>
*
* <h2>Key builders</h2>
* <ul>
* <li>{@link EcdsaCurveSpec} - Used to generate a new key pair for a chosen
* curve.</li>
* <li>{@link EcdsaPublicKeySpec} - Used to import an existing ECDSA public
* key.</li>
* <li>{@link EcdsaPrivateKeySpec} - Used to import an existing ECDSA private
* key.</li>
* </ul>
*
* <h2>Provider model</h2> This implementation leverages the Java Cryptography
* Architecture (JCA) with algorithm identifiers supplied by
* {@link EcdsaCurveSpec#jcaFactory()}. The {@link GenericJcaSignatureContext}
* is used internally to provide streaming-friendly signing and verification
* with strict enforcement of fixed-length signatures.
*
* <h2>Default configuration</h2> If no curve specification is explicitly
* provided, the default is {@link EcdsaCurveSpec#P256}.
*
* <pre>{@code
* // Example: Sign and verify with ECDSA/P-256
* KeyPair kp = CryptoAlgorithms.keyPair("ECDSA", EcdsaCurveSpec.P256);
* SignatureContext signer = CryptoAlgorithms.create("ECDSA", KeyUsage.SIGN, kp.getPrivate(), EcdsaCurveSpec.P256);
* SignatureContext verifier = CryptoAlgorithms.create("ECDSA", KeyUsage.VERIFY, kp.getPublic(), EcdsaCurveSpec.P256);
* }</pre>
*
* @since 1.0
*/
public final class EcdsaAlgorithm extends AbstractCryptoAlgorithm {
/**
* Constructs a new ECDSA algorithm instance and registers its capabilities:
* <ul>
* <li>{@link KeyUsage#SIGN} with {@link PrivateKey} and
* {@link EcdsaCurveSpec}</li>
* <li>{@link KeyUsage#VERIFY} with {@link PublicKey} and
* {@link EcdsaCurveSpec}</li>
* <li>Asymmetric key builders for {@link EcdsaCurveSpec},
* {@link EcdsaPublicKeySpec}, and {@link EcdsaPrivateKeySpec}</li>
* </ul>
*
* <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
* {@link CryptoAlgorithms} convenience methods.
* </p>
*/
public EcdsaAlgorithm() {
super("ECDSA", "ECDSA");
// SIGN
capability(AlgorithmFamily.ASYMMETRIC, KeyUsage.SIGN, SignatureContext.class, PrivateKey.class,
EcdsaCurveSpec.class, (PrivateKey k, EcdsaCurveSpec s) -> {
try {
return new GenericJcaSignatureContext(this, k,
GenericJcaSignatureContext.jcaFactory(s.jcaFactory(), null),
GenericJcaSignatureContext.SignLengthResolver.fixed(s.signFixedLength()));
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Cannot init " + s.jcaFactory() + " signer", e);
}
}, () -> EcdsaCurveSpec.P256);
// VERIFY
capability(AlgorithmFamily.ASYMMETRIC, KeyUsage.VERIFY, SignatureContext.class, PublicKey.class,
EcdsaCurveSpec.class, (PublicKey k, EcdsaCurveSpec s) -> {
try {
return new GenericJcaSignatureContext(this, k,
GenericJcaSignatureContext.jcaFactory(s.jcaFactory(), null),
GenericJcaSignatureContext.VerifyLengthResolver.fixed(s.signFixedLength()));
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Cannot init " + s.jcaFactory() + " verifier", e);
}
}, () -> EcdsaCurveSpec.P256);
registerAsymmetricKeyBuilder(EcdsaCurveSpec.class, new EcdsaKeyGenBuilder(), () -> EcdsaCurveSpec.P256);
registerAsymmetricKeyBuilder(EcdsaPublicKeySpec.class, new EcdsaPublicKeyBuilder(), null);
registerAsymmetricKeyBuilder(EcdsaPrivateKeySpec.class, new EcdsaPrivateKeyBuilder(), null);
}
}

View File

@@ -0,0 +1,144 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ecdsa;
import zeroecho.core.annotation.Describable;
import zeroecho.core.spec.AlgorithmKeySpec;
import zeroecho.core.spec.ContextSpec;
/**
* <h2>ECDSA Curve Specifications</h2>
*
* Enumeration of supported elliptic curve domain parameters for the
* {@link EcdsaAlgorithm}. Each value binds a named curve, its associated JCA
* signature algorithm string, and the fixed-length of produced signatures in
* bytes (P1363 format).
*
* <h2>Curves</h2>
* <ul>
* <li>{@link #P256} - NIST P-256 (secp256r1), 64-byte signatures, SHA-256
* digest binding.</li>
* <li>{@link #P384} - NIST P-384 (secp384r1), 96-byte signatures, SHA-384
* digest binding.</li>
* <li>{@link #P512} - NIST P-521 (secp521r1), 132-byte signatures, SHA-512
* digest binding.</li>
* </ul>
*
* <h2>Usage</h2> The specification is used both as a {@link ContextSpec} for
* signature contexts and as an {@link AlgorithmKeySpec} for key pair generation
* or import. It ensures consistent algorithm selection and enforces
* deterministic signature lengths.
*
* <pre>{@code
* // Example: select curve specification
* EcdsaCurveSpec spec = EcdsaCurveSpec.P256;
* String curve = spec.curveName(); // "secp256r1"
* String jcaAlg = spec.jcaFactory(); // "SHA256withECDSAinP1363Format"
* int sigLen = spec.signFixedLength(); // 64
* }</pre>
*
* @since 1.0
*/
public enum EcdsaCurveSpec implements ContextSpec, AlgorithmKeySpec, Describable {
/** NIST P-256 (secp256r1), SHA-256 binding, 64-byte signatures. */
P256("secp256r1", "SHA256withECDSAinP1363Format", 64),
/** NIST P-384 (secp384r1), SHA-384 binding, 96-byte signatures. */
P384("secp384r1", "SHA384withECDSAinP1363Format", 96),
/** NIST P-521 (secp521r1), SHA-512 binding, 132-byte signatures. */
P512("secp521r1", "SHA512withECDSAinP1363Format", 132);
private final String curveName;
private final String jca;
private final int tagLen;
/**
* Constructs a curve specification.
*
* @param curveName canonical JCA curve name (e.g., "secp256r1")
* @param jca JCA algorithm identifier for signature operations
* @param tagLen fixed signature length in bytes (P1363 format)
*/
EcdsaCurveSpec(String curveName, String jca, int tagLen) {
this.curveName = curveName;
this.jca = jca;
this.tagLen = tagLen;
}
/**
* Returns the canonical JCA curve name.
*
* @return curve name string (e.g., {@code "secp256r1"})
*/
public String curveName() {
return curveName;
}
/**
* Returns the JCA algorithm identifier for this curve.
*
* @return JCA signature algorithm string
*/
public String jcaFactory() {
return jca;
}
/**
* Returns the fixed signature length for this curve in bytes.
*
* <p>
* Lengths follow the IEEE P1363 format (raw concatenation of R and S).
* </p>
*
* @return signature length in bytes
*/
public int signFixedLength() {
return tagLen;
}
/**
* Returns a short, human-readable description of this object.
*
* <p>
* The description should be concise and stable enough for logging or display
* purposes, while avoiding exposure of any sensitive information.
* </p>
*
* @return non-null descriptive string
*/
@Override
public String description() {
return curveName;
}
}

View File

@@ -0,0 +1,131 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ecdsa;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.ECGenParameterSpec;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.CryptoAlgorithms;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>ECDSA Key Pair Generator</h2>
*
* Implementation of {@link AsymmetricKeyBuilder} for {@link EcdsaCurveSpec}.
* This builder is responsible for generating new elliptic curve key pairs for
* use with the {@link EcdsaAlgorithm}.
*
* <h2>Supported operations</h2>
* <ul>
* <li>{@link #generateKeyPair(EcdsaCurveSpec)} - create a fresh key pair for
* the given named curve.</li>
* <li>{@link #importPublic(EcdsaCurveSpec)} - unsupported; use
* {@link EcdsaPublicKeySpec} with {@link EcdsaPublicKeyBuilder} instead.</li>
* <li>{@link #importPrivate(EcdsaCurveSpec)} - unsupported; use
* {@link EcdsaPrivateKeySpec} with {@link EcdsaPrivateKeyBuilder} instead.</li>
* </ul>
*
* <h2>Usage</h2> Typically accessed indirectly through
* {@link CryptoAlgorithms#keyPair(String, zeroecho.core.spec.AlgorithmKeySpec)}
* or
* {@link CryptoAlgorithm#generateKeyPair(zeroecho.core.spec.AlgorithmKeySpec)}.
*
* <pre>{@code
* // Example: Generate an ECDSA P-256 key pair
* EcdsaCurveSpec spec = EcdsaCurveSpec.P256;
* KeyPair kp = new EcdsaKeyGenBuilder().generateKeyPair(spec);
* }</pre>
*
* @since 1.0
*/
public final class EcdsaKeyGenBuilder implements AsymmetricKeyBuilder<EcdsaCurveSpec> {
/**
* Generates a new elliptic curve key pair for the given curve specification.
*
* <p>
* Internally uses the JCA {@link KeyPairGenerator} with the {@code "EC"}
* algorithm and initializes it with an {@link ECGenParameterSpec} corresponding
* to the curve name (e.g., {@code "secp256r1"}).
* </p>
*
* @param spec the curve specification to use
* @return newly generated EC {@link KeyPair}
* @throws GeneralSecurityException if the curve is not supported by the
* provider
*/
@Override
public KeyPair generateKeyPair(EcdsaCurveSpec spec) throws GeneralSecurityException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(new ECGenParameterSpec(spec.curveName()));
return kpg.generateKeyPair();
}
/**
* Unsupported operation for this builder.
*
* <p>
* Public key import should be performed using {@link EcdsaPublicKeySpec} and
* {@link EcdsaPublicKeyBuilder}.
* </p>
*
* @param spec unused curve specification
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public java.security.PublicKey importPublic(EcdsaCurveSpec spec) {
throw new UnsupportedOperationException("Use EcdsaPublicKeySpec with EcdsaPublicKeyBuilder.");
}
/**
* Unsupported operation for this builder.
*
* <p>
* Private key import should be performed using {@link EcdsaPrivateKeySpec} and
* {@link EcdsaPrivateKeyBuilder}.
* </p>
*
* @param spec unused curve specification
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public java.security.PrivateKey importPrivate(EcdsaCurveSpec spec) {
throw new UnsupportedOperationException("Use EcdsaPrivateKeySpec with EcdsaPrivateKeyBuilder.");
}
}

View File

@@ -0,0 +1,134 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ecdsa;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.CryptoAlgorithms;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>ECDSA Private Key Builder</h2>
*
* Implementation of {@link AsymmetricKeyBuilder} for
* {@link EcdsaPrivateKeySpec}. This builder is responsible for importing ECDSA
* private keys from encoded representations.
*
* <h2>Supported operations</h2>
* <ul>
* <li>{@link #importPrivate(EcdsaPrivateKeySpec)} - construct a
* {@link PrivateKey} instance from a PKCS#8 encoded key.</li>
* <li>{@link #generateKeyPair(EcdsaPrivateKeySpec)} - unsupported; use
* {@link EcdsaKeyGenBuilder} instead.</li>
* <li>{@link #importPublic(EcdsaPrivateKeySpec)} - unsupported; use
* {@link EcdsaPublicKeySpec} with {@link EcdsaPublicKeyBuilder} instead.</li>
* </ul>
*
* <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
* {@code "EC"} algorithm to reconstruct a usable {@link PrivateKey}.
*
* <h2>Usage</h2> Typically accessed indirectly through
* {@link CryptoAlgorithms#privateKey(String, zeroecho.core.spec.AlgorithmKeySpec)}
* or
* {@link CryptoAlgorithm#importPrivate(zeroecho.core.spec.AlgorithmKeySpec)}.
*
* <pre>{@code
* // Example: Import an ECDSA private key
* byte[] pkcs8 = ...; // load PKCS#8 DER data
* EcdsaPrivateKeySpec spec = new EcdsaPrivateKeySpec(pkcs8);
* PrivateKey priv = new EcdsaPrivateKeyBuilder().importPrivate(spec);
* }</pre>
*
* @since 1.0
*/
public final class EcdsaPrivateKeyBuilder implements AsymmetricKeyBuilder<EcdsaPrivateKeySpec> {
/**
* Unsupported operation for this builder.
*
* <p>
* ECDSA key pair generation should be performed using
* {@link EcdsaKeyGenBuilder}, not from a private key specification.
* </p>
*
* @param spec unused private key specification
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public java.security.KeyPair generateKeyPair(EcdsaPrivateKeySpec spec) {
throw new UnsupportedOperationException("Use EcdsaKeyGenBuilder for keypair generation.");
}
/**
* Unsupported operation for this builder.
*
* <p>
* Public key import should be performed using {@link EcdsaPublicKeySpec} with
* {@link EcdsaPublicKeyBuilder}.
* </p>
*
* @param spec unused private key specification
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public java.security.PublicKey importPublic(EcdsaPrivateKeySpec spec) {
throw new UnsupportedOperationException("Use EcdsaPublicKeySpec with EcdsaPublicKeyBuilder.");
}
/**
* Imports a private key from a PKCS#8 encoded specification.
*
* <p>
* Uses a JCA {@link KeyFactory} instance for {@code "EC"} to parse the given
* {@link EcdsaPrivateKeySpec} and construct a {@link PrivateKey}.
* </p>
*
* @param spec private key specification containing PKCS#8 DER encoding
* @return reconstructed {@link PrivateKey} instance
* @throws GeneralSecurityException if the encoding is invalid or the key cannot
* be reconstructed
*/
@Override
public PrivateKey importPrivate(EcdsaPrivateKeySpec spec) throws GeneralSecurityException {
KeyFactory kf = KeyFactory.getInstance("EC");
return kf.generatePrivate(new PKCS8EncodedKeySpec(spec.encoded()));
}
}

View File

@@ -0,0 +1,143 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ecdsa;
import java.util.Base64;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>ECDSA Private Key Specification</h2>
*
* An immutable wrapper around a PKCS#8-encoded ECDSA private key. This
* specification is used by {@link EcdsaPrivateKeyBuilder} to import keys into
* the JCA {@link java.security.PrivateKey} representation.
*
* <h2>Encoding</h2>
* <ul>
* <li>Keys are stored internally in PKCS#8 DER format.</li>
* <li>Instances are defensive: the internal byte array is cloned on
* construction and on every access.</li>
* <li>For serialization, {@link #marshal(EcdsaPrivateKeySpec)} encodes the
* PKCS#8 bytes in Base64 without padding.</li>
* </ul>
*
* <h2>Usage</h2> <pre>{@code
* // Construct from PKCS#8 DER
* byte[] pkcs8 = ...; // load from file or keystore
* EcdsaPrivateKeySpec spec = new EcdsaPrivateKeySpec(pkcs8);
*
* // Import into a PrivateKey
* PrivateKey priv = new EcdsaPrivateKeyBuilder().importPrivate(spec);
*
* // Marshal/unmarshal for transport or storage
* PairSeq seq = EcdsaPrivateKeySpec.marshal(spec);
* EcdsaPrivateKeySpec restored = EcdsaPrivateKeySpec.unmarshal(seq);
* }</pre>
*
* @since 1.0
*/
public final class EcdsaPrivateKeySpec implements AlgorithmKeySpec {
private static final String PKCS8_B64 = "pkcs8.b64";
private final byte[] pkcs8;
/**
* Creates a new private key specification from a PKCS#8 encoded byte array.
*
* @param pkcs8 PKCS#8 DER-encoded private key (must not be {@code null})
* @throws IllegalArgumentException if {@code pkcs8} is {@code null}
*/
public EcdsaPrivateKeySpec(byte[] pkcs8) {
if (pkcs8 == null) {
throw new IllegalArgumentException("pkcs8 must not be null");
}
this.pkcs8 = pkcs8.clone();
}
/**
* Returns a defensive copy of the encoded PKCS#8 data.
*
* @return cloned PKCS#8 byte array
*/
public byte[] encoded() {
return pkcs8.clone();
}
/**
* Serializes this specification into a {@link PairSeq}.
*
* <p>
* The PKCS#8 data is Base64-encoded (without padding) and emitted with the key
* {@code "pkcs8.b64"}.
* </p>
*
* @param spec private key spec to marshal
* @return serialized representation in key-value form
*/
public static PairSeq marshal(EcdsaPrivateKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.pkcs8);
return PairSeq.of("type", "ECDSA-PRIV", PKCS8_B64, b64);
}
/**
* Reconstructs a private key specification from a {@link PairSeq}.
*
* <p>
* The sequence must contain a {@code "pkcs8.b64"} field with Base64-encoded
* PKCS#8 DER data. If missing, an {@link IllegalArgumentException} is thrown.
* </p>
*
* @param p serialized key-value sequence
* @return reconstructed {@code EcdsaPrivateKeySpec}
* @throws IllegalArgumentException if {@code pkcs8.b64} is absent
*/
public static EcdsaPrivateKeySpec unmarshal(PairSeq p) {
byte[] out = null;
PairSeq.Cursor cur = p.cursor();
while (cur.next()) {
String k = cur.key();
String v = cur.value();
if (PKCS8_B64.equals(k)) {
out = Base64.getDecoder().decode(v);
}
}
if (out == null) {
throw new IllegalArgumentException("pkcs8.b64 missing for ECDSA private key");
}
return new EcdsaPrivateKeySpec(out);
}
}

View File

@@ -0,0 +1,133 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ecdsa;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.CryptoAlgorithms;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>ECDSA Public Key Builder</h2>
*
* Implementation of {@link AsymmetricKeyBuilder} for
* {@link EcdsaPublicKeySpec}. This builder is responsible for importing ECDSA
* public keys from X.509 SubjectPublicKeyInfo encodings.
*
* <h2>Supported operations</h2>
* <ul>
* <li>{@link #importPublic(EcdsaPublicKeySpec)} - construct a {@link PublicKey}
* instance from an X.509-encoded key.</li>
* <li>{@link #generateKeyPair(EcdsaPublicKeySpec)} - unsupported; use
* {@link EcdsaKeyGenBuilder} instead.</li>
* <li>{@link #importPrivate(EcdsaPublicKeySpec)} - unsupported; use
* {@link EcdsaPrivateKeySpec} with {@link EcdsaPrivateKeyBuilder} instead.</li>
* </ul>
*
* <h2>Encoding</h2> The {@link EcdsaPublicKeySpec} stores the public key in
* standard X.509 DER format. This builder delegates to a JCA {@link KeyFactory}
* for the {@code "EC"} algorithm to reconstruct a usable {@link PublicKey}.
*
* <h2>Usage</h2> Typically accessed indirectly through
* {@link CryptoAlgorithms#publicKey(String, zeroecho.core.spec.AlgorithmKeySpec)}
* or {@link CryptoAlgorithm#importPublic(zeroecho.core.spec.AlgorithmKeySpec)}.
*
* <pre>{@code
* // Example: Import an ECDSA public key
* byte[] x509 = ...; // load SubjectPublicKeyInfo DER data
* EcdsaPublicKeySpec spec = new EcdsaPublicKeySpec(x509);
* PublicKey pub = new EcdsaPublicKeyBuilder().importPublic(spec);
* }</pre>
*
* @since 1.0
*/
public final class EcdsaPublicKeyBuilder implements AsymmetricKeyBuilder<EcdsaPublicKeySpec> {
/**
* Unsupported operation for this builder.
*
* <p>
* ECDSA key pair generation should be performed using
* {@link EcdsaKeyGenBuilder}, not from a public key specification.
* </p>
*
* @param spec unused public key specification
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public java.security.KeyPair generateKeyPair(EcdsaPublicKeySpec spec) {
throw new UnsupportedOperationException("Use EcdsaKeyGenBuilder for keypair generation.");
}
/**
* Imports a public key from an X.509 SubjectPublicKeyInfo specification.
*
* <p>
* Uses a JCA {@link KeyFactory} instance for {@code "EC"} to parse the given
* {@link EcdsaPublicKeySpec} and construct a {@link PublicKey}.
* </p>
*
* @param spec public key specification containing X.509 DER encoding
* @return reconstructed {@link PublicKey} instance
* @throws GeneralSecurityException if the encoding is invalid or the key cannot
* be reconstructed
*/
@Override
public PublicKey importPublic(EcdsaPublicKeySpec spec) throws GeneralSecurityException {
KeyFactory kf = KeyFactory.getInstance("EC");
return kf.generatePublic(new X509EncodedKeySpec(spec.encoded()));
}
/**
* Unsupported operation for this builder.
*
* <p>
* Private key import should be performed using {@link EcdsaPrivateKeySpec} with
* {@link EcdsaPrivateKeyBuilder}.
* </p>
*
* @param spec unused public key specification
* @return never returns normally
* @throws UnsupportedOperationException always thrown
*/
@Override
public java.security.PrivateKey importPrivate(EcdsaPublicKeySpec spec) {
throw new UnsupportedOperationException("Use EcdsaPrivateKeySpec with EcdsaPrivateKeyBuilder.");
}
}

View File

@@ -0,0 +1,144 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ecdsa;
import java.util.Base64;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>ECDSA Public Key Specification</h2>
*
* An immutable wrapper around an X.509-encoded ECDSA public key. This
* specification is used by {@link EcdsaPublicKeyBuilder} to import keys into
* the JCA {@link java.security.PublicKey} representation.
*
* <h2>Encoding</h2>
* <ul>
* <li>Keys are stored internally in X.509 SubjectPublicKeyInfo DER format.</li>
* <li>Instances are defensive: the internal byte array is cloned on
* construction and on every access.</li>
* <li>For serialization, {@link #marshal(EcdsaPublicKeySpec)} encodes the X.509
* bytes in Base64 without padding.</li>
* </ul>
*
* <h2>Usage</h2> <pre>{@code
* // Construct from X.509 DER
* byte[] x509 = ...; // load from certificate or key file
* EcdsaPublicKeySpec spec = new EcdsaPublicKeySpec(x509);
*
* // Import into a PublicKey
* PublicKey pub = new EcdsaPublicKeyBuilder().importPublic(spec);
*
* // Marshal/unmarshal for transport or storage
* PairSeq seq = EcdsaPublicKeySpec.marshal(spec);
* EcdsaPublicKeySpec restored = EcdsaPublicKeySpec.unmarshal(seq);
* }</pre>
*
* @since 1.0
*/
public final class EcdsaPublicKeySpec implements AlgorithmKeySpec {
private static final String X509_B64 = "x509.b64";
private final byte[] x509;
/**
* Creates a new public key specification from an X.509 encoded byte array.
*
* @param x509 X.509 SubjectPublicKeyInfo DER-encoded public key (must not be
* {@code null})
* @throws IllegalArgumentException if {@code x509} is {@code null}
*/
public EcdsaPublicKeySpec(byte[] x509) {
if (x509 == null) {
throw new IllegalArgumentException("x509 must not be null");
}
this.x509 = x509.clone();
}
/**
* Returns a defensive copy of the encoded X.509 data.
*
* @return cloned X.509 byte array
*/
public byte[] encoded() {
return x509.clone();
}
/**
* Serializes this specification into a {@link PairSeq}.
*
* <p>
* The X.509 data is Base64-encoded (without padding) and emitted with the key
* {@code "x509.b64"}.
* </p>
*
* @param spec public key spec to marshal
* @return serialized representation in key-value form
*/
public static PairSeq marshal(EcdsaPublicKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.x509);
return PairSeq.of("type", "ECDSA-PUB", X509_B64, b64);
}
/**
* Reconstructs a public key specification from a {@link PairSeq}.
*
* <p>
* The sequence must contain a {@code "x509.b64"} field with Base64-encoded
* X.509 DER data. If missing, an {@link IllegalArgumentException} is thrown.
* </p>
*
* @param p serialized key-value sequence
* @return reconstructed {@code EcdsaPublicKeySpec}
* @throws IllegalArgumentException if {@code x509.b64} is absent
*/
public static EcdsaPublicKeySpec unmarshal(PairSeq p) {
byte[] out = null;
PairSeq.Cursor cur = p.cursor();
while (cur.next()) {
String k = cur.key();
String v = cur.value();
if (X509_B64.equals(k)) {
out = Base64.getDecoder().decode(v);
}
}
if (out == null) {
throw new IllegalArgumentException("x509.b64 missing for ECDSA public key");
}
return new EcdsaPublicKeySpec(out);
}
}

View File

@@ -0,0 +1,86 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* Elliptic Curve Digital Signature Algorithm (ECDSA) integration.
*
* <p>
* This package provides the ECDSA algorithm descriptor, curve specifications,
* key builders for generation and import, and immutable encoded key specs. It
* wires ECDSA into the core signature SPI through a JCA-backed streaming
* signature context that enforces fixed signature lengths.
* </p>
*
* <h2>Scope and responsibilities</h2>
* <ul>
* <li>Register ECDSA under a canonical identifier and declare its
* {@link zeroecho.core.KeyUsage#SIGN} and {@link zeroecho.core.KeyUsage#VERIFY}
* roles.</li>
* <li>Expose curve specifications for NIST P-256, P-384, and P-521 with
* canonical curve names, JCA algorithm identifiers, and fixed tag lengths.</li>
* <li>Provide key builders for generating EC key pairs and importing
* X.509/PKCS#8 encodings.</li>
* <li>Adapt JCA {@link java.security.Signature} engines to the core streaming
* context model with strict length enforcement.</li>
* </ul>
*
* <h2>Components</h2>
* <ul>
* <li><b>EcdsaAlgorithm:</b> registers the algorithm, declares sign/verify
* capabilities, and wires EC key builders and specs.</li>
* <li><b>EcdsaCurveSpec:</b> enum of supported curves (P-256, P-384, P-521)
* with curve names, JCA identifiers, and deterministic signature lengths.</li>
* <li><b>EcdsaKeyGenBuilder:</b> generates EC key pairs for the chosen curve
* using {@link java.security.KeyPairGenerator} with
* {@link java.security.spec.ECGenParameterSpec}.</li>
* <li><b>EcdsaPublicKeyBuilder</b> and <b>EcdsaPrivateKeyBuilder:</b> import
* keys from X.509 and PKCS#8 encodings via
* {@link java.security.KeyFactory}.</li>
* <li><b>EcdsaPublicKeySpec</b> and <b>EcdsaPrivateKeySpec:</b> immutable
* wrappers around encoded keys with marshalling support.</li>
* </ul>
*
* <h2>Design notes</h2>
* <ul>
* <li>Algorithm descriptors are immutable and thread-safe; signature contexts
* are stateful and not thread-safe.</li>
* <li>Unsupported directions in key builders (e.g., generating from an import
* spec) fail fast with clear exceptions.</li>
* <li>Signatures are always encoded in IEEE P1363 format (R and S concatenated)
* with deterministic lengths.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.ecdsa;

View File

@@ -0,0 +1,131 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed25519;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import zeroecho.core.AlgorithmFamily;
import zeroecho.core.KeyUsage;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.context.SignatureContext;
import zeroecho.core.spec.VoidSpec;
/**
* <h2>Ed25519 Digital Signature Algorithm</h2>
*
* Implementation of the Edwards-curve Digital Signature Algorithm (Ed25519).
* This class integrates Ed25519 into the ZeroEcho cryptographic framework as an
* {@link AbstractCryptoAlgorithm}.
*
* <p>
* Ed25519 is a modern, high-security, high-performance signature scheme with
* fixed-size keys and signatures. It is resistant to a wide class of
* implementation pitfalls and side-channel attacks, making it a recommended
* choice for new protocols.
* </p>
*
* <h2>Capabilities</h2>
* <ul>
* <li>{@link KeyUsage#SIGN}: Creates a {@link SignatureContext} bound to a
* {@link PrivateKey}. This context produces 64-byte Ed25519 signatures.</li>
* <li>{@link KeyUsage#VERIFY}: Creates a {@link SignatureContext} bound to a
* {@link PublicKey}. This context verifies 64-byte Ed25519 signatures.</li>
* </ul>
*
* <h2>Key material</h2>
* <ul>
* <li>Generation via {@link Ed25519KeyGenSpec} and
* {@link Ed25519KeyGenBuilder}.</li>
* <li>Import of public keys via {@link Ed25519PublicKeySpec} and
* {@link Ed25519PublicKeyBuilder}.</li>
* <li>Import of private keys via {@link Ed25519PrivateKeySpec} and
* {@link Ed25519PrivateKeyBuilder}.</li>
* </ul>
*
* <h2>Thread-safety</h2> This algorithm definition is immutable and safe to
* share across threads. The created {@link SignatureContext} instances are not
* guaranteed to be thread-safe.
*
* @since 1.0
*/
public final class Ed25519Algorithm extends AbstractCryptoAlgorithm {
/**
* Constructs the Ed25519 algorithm definition and registers its roles and key
* builders.
*
* <p>
* This includes:
* </p>
* <ul>
* <li>Binding SIGN (with {@link PrivateKey}) to
* {@link Ed25519SignatureContext}.</li>
* <li>Binding VERIFY (with {@link PublicKey}) to
* {@link Ed25519SignatureContext}.</li>
* <li>Registering key builders for generation and import of Ed25519 key
* material.</li>
* </ul>
*/
public Ed25519Algorithm() {
super("Ed25519", "Ed25519");
// SIGN (private key)
capability(AlgorithmFamily.ASYMMETRIC, KeyUsage.SIGN, SignatureContext.class, PrivateKey.class, VoidSpec.class,
(PrivateKey k, VoidSpec s) -> {
try {
return new Ed25519SignatureContext(this, k);
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Cannot init Ed25519 signer", e);
}
}, () -> VoidSpec.INSTANCE);
// VERIFY (public key)
capability(AlgorithmFamily.ASYMMETRIC, KeyUsage.VERIFY, SignatureContext.class, PublicKey.class, VoidSpec.class,
(PublicKey k, VoidSpec s) -> {
try {
return new Ed25519SignatureContext(this, k);
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Cannot init Ed25519 verifier", e);
}
}, () -> VoidSpec.INSTANCE);
// Key builders
registerAsymmetricKeyBuilder(Ed25519KeyGenSpec.class, new Ed25519KeyGenBuilder(),
Ed25519KeyGenSpec::defaultSpec);
registerAsymmetricKeyBuilder(Ed25519PublicKeySpec.class, new Ed25519PublicKeyBuilder(), null);
registerAsymmetricKeyBuilder(Ed25519PrivateKeySpec.class, new Ed25519PrivateKeyBuilder(), null);
}
}

View File

@@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed25519;
import zeroecho.core.alg.common.eddsa.AbstractEdDSAKeyGenBuilder;
/**
* <h2>Key-pair builder for Ed25519</h2>
*
* Concrete {@link zeroecho.core.spi.AsymmetricKeyBuilder} implementation for
* generating Ed25519 key pairs.
*
* <p>
* This builder delegates to the JCA provider under the canonical algorithm name
* {@code "Ed25519"}. It is registered by {@link Ed25519Algorithm} to support
* key generation from an {@link Ed25519KeyGenSpec}.
* </p>
*
* <h2>Usage example</h2> <pre>{@code
* // Generate a new Ed25519 key pair with default parameters
* Ed25519KeyGenSpec spec = Ed25519KeyGenSpec.defaultSpec();
* KeyPair kp = CryptoAlgorithms.keyPair("Ed25519", spec);
* }</pre>
*
* <h2>Thread-safety</h2> Instances of this builder are stateless and may be
* reused safely across threads.
*
* @since 1.0
*/
public final class Ed25519KeyGenBuilder extends AbstractEdDSAKeyGenBuilder<Ed25519KeyGenSpec> {
/**
* Returns the canonical JCA algorithm name for Ed25519 key-pair generation.
*
* <p>
* This method is invoked by the parent {@link AbstractEdDSAKeyGenBuilder} to
* construct a {@code KeyPairGenerator}.
* </p>
*
* @return the string {@code "Ed25519"}
*/
@Override
protected String jcaKeyPairAlg() {
return "Ed25519";
}
}

View File

@@ -0,0 +1,80 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed25519;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>Specification for Ed25519 key-pair generation</h2>
*
* Marker {@link zeroecho.core.spec.AlgorithmKeySpec} used to request generation
* of Ed25519 key pairs.
*
* <p>
* Ed25519 has no tunable domain parameters (e.g., key size or curve options).
* Therefore, this spec acts as a simple token to identify the algorithm when
* invoking key generation. All instances are equivalent; applications should
* typically use {@link #defaultSpec()}.
* </p>
*
* <h2>Usage example</h2> <pre>{@code
* // Generate a new Ed25519 key pair using the default spec
* KeyPair kp = CryptoAlgorithms.keyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec());
* }</pre>
*
* <h2>Thread-safety</h2> The default spec instance is immutable and safe to
* reuse across threads.
*
* @since 1.0
*/
public final class Ed25519KeyGenSpec implements AlgorithmKeySpec {
private static final Ed25519KeyGenSpec DEFAULT = new Ed25519KeyGenSpec();
/**
* Returns the canonical, shared default spec instance for Ed25519 key
* generation.
*
* <p>
* Since Ed25519 has no configurable parameters, this singleton should be used
* for all generation requests. Applications may still construct additional
* instances, but they are functionally identical.
* </p>
*
* @return the default Ed25519 key generation spec
*/
public static Ed25519KeyGenSpec defaultSpec() {
return DEFAULT;
}
}

View File

@@ -0,0 +1,102 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed25519;
import zeroecho.core.alg.common.eddsa.AbstractEncodedPrivateKeyBuilder;
/**
* <h2>Private key builder for Ed25519</h2>
*
* Concrete {@link zeroecho.core.spi.AsymmetricKeyBuilder} for importing and
* wrapping Ed25519 private keys.
*
* <p>
* This builder integrates with the JCA under the canonical key factory
* algorithm name {@code "Ed25519"}. It consumes an
* {@link Ed25519PrivateKeySpec}, which provides the encoded PKCS#8
* representation of the private key.
* </p>
*
* <h2>Responsibilities</h2>
* <ul>
* <li>Expose the JCA key factory name {@code "Ed25519"} for
* interoperability.</li>
* <li>Provide the encoded PKCS#8 bytes via
* {@link Ed25519PrivateKeySpec#encoded()} for key material import.</li>
* </ul>
*
* <h2>Usage example</h2> <pre>{@code
* // Import a private key from its encoded PKCS#8 form
* Ed25519PrivateKeySpec spec = new Ed25519PrivateKeySpec(pkcs8Bytes);
* PrivateKey privateKey = CryptoAlgorithms.privateKey("Ed25519", spec);
* }</pre>
*
* <h2>Thread-safety</h2> Instances of this builder are stateless and may be
* reused safely across threads.
*
* @since 1.0
*/
public final class Ed25519PrivateKeyBuilder extends AbstractEncodedPrivateKeyBuilder<Ed25519PrivateKeySpec> {
/**
* Returns the canonical JCA algorithm name for Ed25519 key factories.
*
* <p>
* This value is used by the parent {@link AbstractEncodedPrivateKeyBuilder} to
* obtain a {@code KeyFactory}.
* </p>
*
* @return the string {@code "Ed25519"}
*/
@Override
protected String jcaKeyFactoryAlg() {
return "Ed25519";
}
/**
* Returns the PKCS#8 encoded bytes from the given key spec.
*
* <p>
* This encoding is passed to the JCA {@code KeyFactory} for parsing into a
* {@link java.security.PrivateKey} instance.
* </p>
*
* @param spec the private key specification holding the PKCS#8 encoding
* @return a defensive copy of the PKCS#8-encoded key bytes
*/
@Override
protected byte[] encodedPkcs8(final Ed25519PrivateKeySpec spec) {
return spec.encoded();
}
}

View File

@@ -0,0 +1,155 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed25519;
import java.util.Base64;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>Specification for Ed25519 private keys</h2>
*
* {@link zeroecho.core.spec.AlgorithmKeySpec} representing an Ed25519 private
* key in its encoded PKCS#8 form.
*
* <p>
* This spec is used by {@link Ed25519PrivateKeyBuilder} and related APIs to
* import and wrap Ed25519 private keys. The encoding is expected to conform to
* the PKCS#8 standard as produced by standard JCA key factories or external
* tooling.
* </p>
*
* <h2>Responsibilities</h2>
* <ul>
* <li>Encapsulates the PKCS#8-encoded private key bytes.</li>
* <li>Provides safe cloning to avoid exposing mutable internal state.</li>
* <li>Supports serialization to and from a compact base64-based {@link PairSeq}
* representation via {@link #marshal(Ed25519PrivateKeySpec)} and
* {@link #unmarshal(PairSeq)}.</li>
* </ul>
*
* <h2>Usage example</h2> <pre>{@code
* // Wrap PKCS#8 bytes in a spec
* Ed25519PrivateKeySpec spec = new Ed25519PrivateKeySpec(pkcs8Bytes);
*
* // Import into a PrivateKey using ZeroEcho
* PrivateKey priv = CryptoAlgorithms.privateKey("Ed25519", spec);
*
* // Serialize to PairSeq (e.g., for configuration or transport)
* PairSeq seq = Ed25519PrivateKeySpec.marshal(spec);
*
* // Reconstruct from serialized form
* Ed25519PrivateKeySpec restored = Ed25519PrivateKeySpec.unmarshal(seq);
* }</pre>
*
* <h2>Thread-safety</h2> Instances are immutable. The internal key bytes are
* defensively copied on construction and retrieval, making this class safe to
* share across threads.
*
* @since 1.0
*/
public final class Ed25519PrivateKeySpec implements AlgorithmKeySpec {
private static final String PKCS8_B64 = "pkcs8.b64";
private final byte[] encodedPkcs8;
/**
* Creates a new Ed25519 private key specification from its PKCS#8 encoding.
*
* @param encodedPkcs8 PKCS#8-encoded private key bytes; must not be null
* @throws IllegalArgumentException if {@code encodedPkcs8} is null
*/
public Ed25519PrivateKeySpec(byte[] encodedPkcs8) {
if (encodedPkcs8 == null) {
throw new IllegalArgumentException("encodedPkcs8 must not be null");
}
this.encodedPkcs8 = encodedPkcs8.clone();
}
/**
* Returns a defensive copy of the PKCS#8-encoded private key bytes.
*
* @return clone of the PKCS#8 encoding
*/
public byte[] encoded() {
return encodedPkcs8.clone();
}
/**
* Serializes the given private key spec to a base64-encoded {@link PairSeq}.
*
* <p>
* The output includes a type marker ({@code "Ed25519-PRIV"}) and the field
* {@code "pkcs8.b64"} containing the base64 representation of the key.
* </p>
*
* @param spec private key spec to marshal
* @return serialized representation as a {@link PairSeq}
*/
public static PairSeq marshal(Ed25519PrivateKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.encodedPkcs8);
return PairSeq.of("type", "Ed25519-PRIV", PKCS8_B64, b64);
}
/**
* Reconstructs a private key spec from a base64-encoded {@link PairSeq}.
*
* <p>
* The sequence must contain the field {@code "pkcs8.b64"} with the base64
* encoding of the PKCS#8 key.
* </p>
*
* @param p serialized key data
* @return a new {@code Ed25519PrivateKeySpec} instance
* @throws IllegalArgumentException if the sequence does not contain
* {@code "pkcs8.b64"}
*/
public static Ed25519PrivateKeySpec unmarshal(PairSeq p) {
byte[] out = null;
PairSeq.Cursor cur = p.cursor();
while (cur.next()) {
String k = cur.key();
String v = cur.value();
if (PKCS8_B64.equals(k)) {
out = Base64.getDecoder().decode(v);
}
}
if (out == null) {
throw new IllegalArgumentException("pkcs8.b64 missing for Ed25519 private key");
}
return new Ed25519PrivateKeySpec(out);
}
}

View File

@@ -0,0 +1,102 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed25519;
import zeroecho.core.alg.common.eddsa.AbstractEncodedPublicKeyBuilder;
/**
* <h2>Public key builder for Ed25519</h2>
*
* Concrete {@link zeroecho.core.spi.AsymmetricKeyBuilder} for importing and
* wrapping Ed25519 public keys.
*
* <p>
* This builder integrates with the JCA under the canonical key factory
* algorithm name {@code "Ed25519"}. It consumes an
* {@link Ed25519PublicKeySpec}, which provides the encoded X.509 representation
* of the public key.
* </p>
*
* <h2>Responsibilities</h2>
* <ul>
* <li>Expose the JCA key factory name {@code "Ed25519"} for
* interoperability.</li>
* <li>Provide the encoded X.509 bytes via
* {@link Ed25519PublicKeySpec#encoded()} for key material import.</li>
* </ul>
*
* <h2>Usage example</h2> <pre>{@code
* // Import a public key from its encoded X.509 form
* Ed25519PublicKeySpec spec = new Ed25519PublicKeySpec(x509Bytes);
* PublicKey publicKey = CryptoAlgorithms.publicKey("Ed25519", spec);
* }</pre>
*
* <h2>Thread-safety</h2> Instances of this builder are stateless and may be
* reused safely across threads.
*
* @since 1.0
*/
public final class Ed25519PublicKeyBuilder extends AbstractEncodedPublicKeyBuilder<Ed25519PublicKeySpec> {
/**
* Returns the canonical JCA algorithm name for Ed25519 key factories.
*
* <p>
* This value is used by the parent {@link AbstractEncodedPublicKeyBuilder} to
* obtain a {@code KeyFactory}.
* </p>
*
* @return the string {@code "Ed25519"}
*/
@Override
protected String jcaKeyFactoryAlg() {
return "Ed25519";
}
/**
* Returns the X.509 encoded bytes from the given key spec.
*
* <p>
* This encoding is passed to the JCA {@code KeyFactory} for parsing into a
* {@link java.security.PublicKey} instance.
* </p>
*
* @param spec the public key specification holding the X.509 encoding
* @return a defensive copy of the X.509-encoded key bytes
*/
@Override
protected byte[] encodedX509(final Ed25519PublicKeySpec spec) {
return spec.encoded();
}
}

View File

@@ -0,0 +1,155 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed25519;
import java.util.Base64;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>Specification for Ed25519 public keys</h2>
*
* {@link zeroecho.core.spec.AlgorithmKeySpec} representing an Ed25519 public
* key in its encoded X.509 form.
*
* <p>
* This spec is used by {@link Ed25519PublicKeyBuilder} and related APIs to
* import and wrap Ed25519 public keys. The encoding is expected to conform to
* the X.509 SubjectPublicKeyInfo structure as produced by standard JCA key
* factories or external tooling.
* </p>
*
* <h2>Responsibilities</h2>
* <ul>
* <li>Encapsulates the X.509-encoded public key bytes.</li>
* <li>Provides safe cloning to avoid exposing mutable internal state.</li>
* <li>Supports serialization to and from a compact base64-based {@link PairSeq}
* representation via {@link #marshal(Ed25519PublicKeySpec)} and
* {@link #unmarshal(PairSeq)}.</li>
* </ul>
*
* <h2>Usage example</h2> <pre>{@code
* // Wrap X.509 bytes in a spec
* Ed25519PublicKeySpec spec = new Ed25519PublicKeySpec(x509Bytes);
*
* // Import into a PublicKey using ZeroEcho
* PublicKey pub = CryptoAlgorithms.publicKey("Ed25519", spec);
*
* // Serialize to PairSeq (e.g., for configuration or transport)
* PairSeq seq = Ed25519PublicKeySpec.marshal(spec);
*
* // Reconstruct from serialized form
* Ed25519PublicKeySpec restored = Ed25519PublicKeySpec.unmarshal(seq);
* }</pre>
*
* <h2>Thread-safety</h2> Instances are immutable. The internal key bytes are
* defensively copied on construction and retrieval, making this class safe to
* share across threads.
*
* @since 1.0
*/
public final class Ed25519PublicKeySpec implements AlgorithmKeySpec {
private static final String X509_B64 = "x509.b64";
private final byte[] encodedX509;
/**
* Creates a new Ed25519 public key specification from its X.509 encoding.
*
* @param encodedX509 X.509-encoded public key bytes; must not be null
* @throws IllegalArgumentException if {@code encodedX509} is null
*/
public Ed25519PublicKeySpec(byte[] encodedX509) {
if (encodedX509 == null) {
throw new IllegalArgumentException("encodedX509 must not be null");
}
this.encodedX509 = encodedX509.clone();
}
/**
* Returns a defensive copy of the X.509-encoded public key bytes.
*
* @return clone of the X.509 encoding
*/
public byte[] encoded() {
return encodedX509.clone();
}
/**
* Serializes the given public key spec to a base64-encoded {@link PairSeq}.
*
* <p>
* The output includes a type marker ({@code "Ed25519-PUB"}) and the field
* {@code "x509.b64"} containing the base64 representation of the key.
* </p>
*
* @param spec public key spec to marshal
* @return serialized representation as a {@link PairSeq}
*/
public static PairSeq marshal(Ed25519PublicKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.encodedX509);
return PairSeq.of("type", "Ed25519-PUB", X509_B64, b64);
}
/**
* Reconstructs a public key spec from a base64-encoded {@link PairSeq}.
*
* <p>
* The sequence must contain the field {@code "x509.b64"} with the base64
* encoding of the X.509 key.
* </p>
*
* @param p serialized key data
* @return a new {@code Ed25519PublicKeySpec} instance
* @throws IllegalArgumentException if the sequence does not contain
* {@code "x509.b64"}
*/
public static Ed25519PublicKeySpec unmarshal(PairSeq p) {
byte[] out = null;
PairSeq.Cursor cur = p.cursor();
while (cur.next()) {
String k = cur.key();
String v = cur.value();
if (X509_B64.equals(k)) {
out = Base64.getDecoder().decode(v);
}
}
if (out == null) {
throw new IllegalArgumentException("x509.b64 missing for Ed25519 public key");
}
return new Ed25519PublicKeySpec(out);
}
}

View File

@@ -0,0 +1,139 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed25519;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.alg.common.eddsa.CommonEdDSASignatureContext;
import zeroecho.core.context.SignatureContext;
/**
* Signature context for Ed25519 with a fixed 64-byte tag length.
*
* <p>
* This implementation binds the Ed25519 algorithm to {@link SignatureContext}
* via {@link CommonEdDSASignatureContext}. Internally it configures a JCA
* {@code Signature} with the {@code "Ed25519"} name and delegates all streaming
* and verification behavior to the common adapter.
* </p>
*
* <h2>Algorithm characteristics</h2>
* <ul>
* <li>JCA signature name: {@code "Ed25519"}.</li>
* <li>Fixed signature length: 64 bytes (reported by {@link #tagLength()}).</li>
* </ul>
*
* <h2>Usage</h2>
* <h3>One-shot API</h3> <pre>
* {@code
* // Signing
* PrivateKey priv = ...;
* SignatureContext signer =
* new Ed25519SignatureContext(CryptoAlgorithms.require("Ed25519"), priv);
* signer.update(message);
* byte[] sig = signer.sign();
*
* // Verifying
* PublicKey pub = ...;
* SignatureContext verifier =
* new Ed25519SignatureContext(CryptoAlgorithms.require("Ed25519"), pub);
* verifier.update(message);
* boolean ok = verifier.verify(sig);
* }
* </pre>
*
* <h3>Streaming pipeline (wrap)</h3> <pre>
* {@code
* // SIGN mode: body bytes followed by 64-byte signature trailer
* try (SignatureContext ctx =
* new Ed25519SignatureContext(CryptoAlgorithms.require("Ed25519"), priv);
* InputStream in = ctx.wrap(upstream)) {
* in.transferTo(out);
* }
*
* // VERIFY mode: supply expected signature; verification occurs at EOF
* try (SignatureContext ctx =
* new Ed25519SignatureContext(CryptoAlgorithms.require("Ed25519"), pub)) {
* ctx.setExpectedTag(expectedSig);
* try (InputStream in = ctx.wrap(bodyWithoutTrailer)) {
* in.transferTo(java.io.OutputStream.nullOutputStream());
* }
* }
* }
* </pre>
*
* <h2>Thread-safety</h2>
* <p>
* Instances are stateful and not thread-safe. Use one context per signing or
* verification operation, and call {@code wrap(...)} at most once per instance.
* </p>
*
* @since 1.0
*/
public final class Ed25519SignatureContext extends CommonEdDSASignatureContext {
private static final String SIG_NAME = "Ed25519";
private static final int TAG_LEN = 64;
/**
* Constructs a signing context bound to the given private key.
*
* @param algorithm parent algorithm descriptor; must not be {@code null}
* @param privateKey Ed25519 private key; must not be {@code null}
* @throws GeneralSecurityException if the underlying JCA engine cannot be
* initialized
* @throws NullPointerException if any argument is {@code null}
*/
public Ed25519SignatureContext(final CryptoAlgorithm algorithm, final PrivateKey privateKey)
throws GeneralSecurityException {
super(algorithm, privateKey, SIG_NAME, TAG_LEN);
}
/**
* Constructs a verification context bound to the given public key.
*
* @param algorithm parent algorithm descriptor; must not be {@code null}
* @param publicKey Ed25519 public key; must not be {@code null}
* @throws GeneralSecurityException if the underlying JCA engine cannot be
* initialized
* @throws NullPointerException if any argument is {@code null}
*/
public Ed25519SignatureContext(final CryptoAlgorithm algorithm, final PublicKey publicKey)
throws GeneralSecurityException {
super(algorithm, publicKey, SIG_NAME, TAG_LEN);
}
}

View File

@@ -0,0 +1,85 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* Ed25519 digital signature integration.
*
* <p>
* This package wires the Ed25519 signature scheme into the core layer. It
* provides the algorithm descriptor, a streaming signature context with a fixed
* tag length, builders for generating and importing keys, and compact key
* specifications for marshalling and transport.
* </p>
*
* <h2>Scope and responsibilities</h2>
* <ul>
* <li>Register the Ed25519 algorithm and declare SIGN and VERIFY roles with
* fixed-length signatures.</li>
* <li>Provide a streaming signature context that adapts JCA engines and
* enforces the 64-byte tag size.</li>
* <li>Expose builders for key-pair generation and for importing encoded
* public/private keys.</li>
* <li>Define immutable key specifications suitable for safe cloning and simple
* marshalling.</li>
* </ul>
*
* <h2>Components</h2>
* <ul>
* <li><b>Ed25519Algorithm</b>: algorithm descriptor that binds roles to a
* signature context and registers builders for key generation and import.</li>
* <li><b>Ed25519SignatureContext</b>: streaming context for signing and
* verification with a fixed 64-byte tag.</li>
* <li><b>Ed25519KeyGenBuilder</b> and <b>Ed25519KeyGenSpec</b>: generator and
* marker spec for producing key pairs.</li>
* <li><b>Ed25519PublicKeyBuilder</b> / <b>Ed25519PrivateKeyBuilder</b>:
* importers backed by JCA key factories.</li>
* <li><b>Ed25519PublicKeySpec</b> / <b>Ed25519PrivateKeySpec</b>: immutable
* wrappers over X.509 and PKCS#8 encodings, with defensive copying and simple
* base64 marshalling helpers.</li>
* </ul>
*
* <h2>Design notes</h2>
* <ul>
* <li>Algorithm descriptors are immutable and thread-safe.</li>
* <li>Signature contexts are stateful and not thread-safe; create a new
* instance per operation.</li>
* <li>Key specification classes never expose internal byte arrays; cloning is
* used on input and output.</li>
* <li>Marshalling helpers use a compact key-value form intended for
* configuration, transport, and tests.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.ed25519;

View File

@@ -0,0 +1,163 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed448;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import zeroecho.core.AlgorithmFamily;
import zeroecho.core.KeyUsage;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.context.SignatureContext;
import zeroecho.core.spec.VoidSpec;
/**
* <h2>Ed448 Digital Signature Algorithm</h2>
*
* Implementation of the Edwards-curve Digital Signature Algorithm over the
* Curve448 curve, commonly referred to as Ed448.
*
* <p>
* Ed448 is a modern elliptic curve signature scheme standardized in
* <a href="https://www.rfc-editor.org/rfc/rfc8032">RFC 8032</a>. It provides
* high security margins (224-bit classical strength) and is designed for
* simplicity, determinism, and resilience against common implementation
* pitfalls such as malleability.
* </p>
*
* <h2>Roles</h2>
* <ul>
* <li>{@link KeyUsage#SIGN}: Contexts that sign messages using a private key,
* producing fixed-length Ed448 signatures.</li>
* <li>{@link KeyUsage#VERIFY}: Contexts that verify Ed448 signatures using the
* corresponding public key.</li>
* </ul>
*
* <h2>Key material</h2>
* <ul>
* <li>{@link Ed448KeyGenSpec}: Generation of new key pairs with defined
* parameters.</li>
* <li>{@link Ed448PublicKeySpec}: Encoded form of Ed448 public keys (X.509
* format).</li>
* <li>{@link Ed448PrivateKeySpec}: Encoded form of Ed448 private keys (PKCS#8
* format).</li>
* </ul>
*
* <h2>Thread-safety</h2> Instances of this class are immutable and safe to
* reuse across threads. Context objects created through capabilities are not
* necessarily thread-safe.
*
* <pre>{@code
* // Example: generate a key pair and sign data
* CryptoAlgorithm ed448 = new Ed448Algorithm();
* KeyPair kp = ed448.generateKeyPair(Ed448KeyGenSpec.defaultSpec());
*
* SignatureContext signer = ed448.create(KeyUsage.SIGN, kp.getPrivate(), null);
* byte[] sig = signer.sign(data);
*
* SignatureContext verifier = ed448.create(KeyUsage.VERIFY, kp.getPublic(), null);
* boolean ok = verifier.verify(data, sig);
* }</pre>
*
* @since 1.0
*/
public final class Ed448Algorithm extends AbstractCryptoAlgorithm {
/**
* Constructs and wires the Ed448 algorithm definition.
*
* <p>
* The constructor registers:
* </p>
* <ul>
* <li><b>Roles</b>:
* <ul>
* <li>{@link KeyUsage#SIGN} using {@link SignatureContext} with a
* {@link PrivateKey} and {@link VoidSpec} (no additional parameters).</li>
* <li>{@link KeyUsage#VERIFY} using {@link SignatureContext} with a
* {@link PublicKey} and {@link VoidSpec}.</li>
* </ul>
* </li>
* <li><b>Asymmetric key builders</b>:
* <ul>
* <li>{@link Ed448KeyGenSpec} via {@link Ed448KeyGenBuilder}, defaulting to
* {@link Ed448KeyGenSpec#defaultSpec()}.</li>
* <li>{@link Ed448PublicKeySpec} via {@link Ed448PublicKeyBuilder}.</li>
* <li>{@link Ed448PrivateKeySpec} via {@link Ed448PrivateKeyBuilder}.</li>
* </ul>
* </li>
* </ul>
*
* <p>
* No exceptions are thrown by this constructor. Any provider initialization
* needed for signing or verification is deferred to the creation of
* {@link SignatureContext} instances.
* </p>
*
* <pre>{@code
* // Example: instantiate and obtain a signer
* CryptoAlgorithm ed448 = new Ed448Algorithm();
* SignatureContext signer = ed448.create(KeyUsage.SIGN, privateKey, null);
* }</pre>
*/
public Ed448Algorithm() {
super("Ed448", "Ed448");
// SIGN (private key)
capability(AlgorithmFamily.ASYMMETRIC, KeyUsage.SIGN, SignatureContext.class, PrivateKey.class, VoidSpec.class,
(PrivateKey k, VoidSpec s) -> {
try {
return new Ed448SignatureContext(this, k);
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Cannot init Ed448 signer", e);
}
}, () -> VoidSpec.INSTANCE);
// VERIFY (public key)
capability(AlgorithmFamily.ASYMMETRIC, KeyUsage.VERIFY, SignatureContext.class, PublicKey.class, VoidSpec.class,
(PublicKey k, VoidSpec s) -> {
try {
return new Ed448SignatureContext(this, k);
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Cannot init Ed448 verifier", e);
}
}, () -> VoidSpec.INSTANCE);
// Key builders
registerAsymmetricKeyBuilder(Ed448KeyGenSpec.class, new Ed448KeyGenBuilder(), Ed448KeyGenSpec::defaultSpec);
registerAsymmetricKeyBuilder(Ed448PublicKeySpec.class, new Ed448PublicKeyBuilder(), null);
registerAsymmetricKeyBuilder(Ed448PrivateKeySpec.class, new Ed448PrivateKeyBuilder(), null);
}
}

View File

@@ -0,0 +1,75 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed448;
import zeroecho.core.alg.common.eddsa.AbstractEdDSAKeyGenBuilder;
/**
* <h2>Ed448 Key-Pair Builder</h2>
*
* Concrete key generation builder for the Ed448 Edwards-curve Digital Signature
* Algorithm, standardized in
* <a href="https://www.rfc-editor.org/rfc/rfc8032">RFC 8032</a>.
*
* <p>
* This builder integrates with the JCA {@link java.security.KeyPairGenerator}
* under the canonical algorithm name {@code "Ed448"}. It is responsible for
* producing fresh {@link java.security.KeyPair} instances; key import is
* delegated to {@link Ed448PublicKeyBuilder} and
* {@link Ed448PrivateKeyBuilder}.
* </p>
*
* <h2>Usage</h2> <pre>{@code
* // Generate a new Ed448 key pair
* Ed448KeyGenBuilder builder = new Ed448KeyGenBuilder();
* KeyPair kp = builder.generateKeyPair(Ed448KeyGenSpec.defaultSpec());
* }</pre>
*
* <h2>Thread-safety</h2> Instances are stateless and may be reused across
* threads. Each call creates a new {@link java.security.KeyPairGenerator}.
*
* @since 1.0
*/
public final class Ed448KeyGenBuilder extends AbstractEdDSAKeyGenBuilder<Ed448KeyGenSpec> {
/**
* Returns the canonical JCA algorithm identifier for Ed448 key pair generation.
*
* @return the string {@code "Ed448"}
*/
@Override
protected String jcaKeyPairAlg() {
return "Ed448";
}
}

View File

@@ -0,0 +1,79 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed448;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>Ed448 Key Generation Specification</h2>
*
* Marker specification for generating Ed448 key pairs.
*
* <p>
* Unlike parameterized algorithms (e.g., RSA with modulus size), Ed448 has
* fixed domain parameters defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8032">RFC 8032</a>. As a result,
* this spec carries no configuration fields - it serves only as a typed handle
* linking {@link Ed448KeyGenBuilder} with the surrounding framework.
* </p>
*
* <h2>Usage</h2> <pre>{@code
* // Generate a new Ed448 key pair using the default spec
* KeyPair kp = new Ed448KeyGenBuilder().generateKeyPair(Ed448KeyGenSpec.defaultSpec());
* }</pre>
*
* <h2>Thread-safety</h2> The {@link #defaultSpec()} instance is immutable and
* safe to reuse across threads.
*
* @since 1.0
*/
public final class Ed448KeyGenSpec implements AlgorithmKeySpec {
private static final Ed448KeyGenSpec DEFAULT = new Ed448KeyGenSpec();
/**
* Returns the shared default specification instance for Ed448 key generation.
*
* <p>
* Since Ed448 has no configurable generation parameters, all key generation
* uses this singleton. This avoids unnecessary object allocation and clarifies
* intent.
* </p>
*
* @return the singleton {@code Ed448KeyGenSpec} instance
*/
public static Ed448KeyGenSpec defaultSpec() {
return DEFAULT;
}
}

View File

@@ -0,0 +1,97 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed448;
import zeroecho.core.alg.common.eddsa.AbstractEncodedPrivateKeyBuilder;
import zeroecho.core.spec.AlgorithmKeySpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>Ed448 Private Key Builder</h2>
*
* Concrete builder for importing Ed448 private keys from their PKCS#8-encoded
* representation.
*
* <p>
* This class extends {@link AbstractEncodedPrivateKeyBuilder} and specifies the
* Ed448 algorithm. It reconstructs {@link java.security.PrivateKey} instances
* from {@link Ed448PrivateKeySpec}, which carries the raw encoded form.
* </p>
*
* <h2>Responsibilities</h2>
* <ul>
* <li>Provide the canonical JCA key factory algorithm name
* ({@code "Ed448"}).</li>
* <li>Extract the PKCS#8-encoded key bytes from
* {@link Ed448PrivateKeySpec}.</li>
* <li>Delegate actual key reconstruction to
* {@link java.security.KeyFactory}.</li>
* </ul>
*
* <h2>Usage</h2> <pre>{@code
* // Import an Ed448 private key from encoded bytes
* Ed448PrivateKeySpec spec = new Ed448PrivateKeySpec(encodedPkcs8Bytes);
* PrivateKey key = new Ed448PrivateKeyBuilder().importPrivate(spec);
* }</pre>
*
* <h2>Thread-safety</h2> Stateless and safe for concurrent use. Each call to
* {@link AsymmetricKeyBuilder#importPrivate(AlgorithmKeySpec)
* importPrivate(Ed448PrivateKeySpec)} creates a new
* {@link java.security.KeyFactory}.
*
* @since 1.0
*/
public final class Ed448PrivateKeyBuilder extends AbstractEncodedPrivateKeyBuilder<Ed448PrivateKeySpec> {
/**
* Returns the canonical JCA key factory algorithm name for Ed448.
*
* @return the string {@code "Ed448"}
*/
@Override
protected String jcaKeyFactoryAlg() {
return "Ed448";
}
/**
* Returns the PKCS#8-encoded private key bytes from the given specification.
*
* @param spec the {@link Ed448PrivateKeySpec} holding encoded private key data
* @return raw PKCS#8-encoded private key bytes
*/
@Override
protected byte[] encodedPkcs8(Ed448PrivateKeySpec spec) {
return spec.encoded();
}
}

View File

@@ -0,0 +1,154 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed448;
import java.util.Base64;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>Ed448 Private Key Specification</h2>
*
* Immutable specification for an Ed448 private key in PKCS#8 encoding.
*
* <p>
* This class acts as a typed carrier for encoded private key material,
* typically used with {@link Ed448PrivateKeyBuilder} to reconstruct a usable
* {@link java.security.PrivateKey}. It also provides marshal/unmarshal helpers
* to serialize the key into a portable {@link PairSeq} representation.
* </p>
*
* <h2>Encoding</h2>
* <ul>
* <li>The byte array is expected to contain a valid PKCS#8-encoded Ed448
* private key.</li>
* <li>Internally, the array is defensively cloned on construction and when
* returned by {@link #encoded()}.</li>
* <li>The {@link #marshal(Ed448PrivateKeySpec)} and {@link #unmarshal(PairSeq)}
* methods wrap and unwrap the PKCS#8 bytes using base64 without padding.</li>
* </ul>
*
* <h2>Usage</h2> <pre>{@code
* // Wrap existing PKCS#8-encoded bytes
* Ed448PrivateKeySpec spec = new Ed448PrivateKeySpec(pkcs8Bytes);
*
* // Import into JCA PrivateKey via builder
* PrivateKey key = new Ed448PrivateKeyBuilder().importPrivate(spec);
*
* // Serialize to PairSeq (e.g., JSON transport)
* PairSeq p = Ed448PrivateKeySpec.marshal(spec);
*
* // Deserialize from PairSeq
* Ed448PrivateKeySpec restored = Ed448PrivateKeySpec.unmarshal(p);
* }</pre>
*
* <h2>Thread-safety</h2> Instances are immutable and safe to share across
* threads.
*
* @since 1.0
*/
public final class Ed448PrivateKeySpec implements AlgorithmKeySpec {
private static final String PKCS8_B64 = "pkcs8.b64";
private final byte[] encodedPkcs8;
/**
* Constructs a new Ed448 private key spec from the given PKCS#8-encoded bytes.
*
* @param encodedPkcs8 PKCS#8-encoded Ed448 private key (non-null)
* @throws IllegalArgumentException if {@code encodedPkcs8} is {@code null}
*/
public Ed448PrivateKeySpec(byte[] encodedPkcs8) {
if (encodedPkcs8 == null) {
throw new IllegalArgumentException("encodedPkcs8 must not be null");
}
this.encodedPkcs8 = encodedPkcs8.clone();
}
/**
* Returns a defensive copy of the PKCS#8-encoded private key bytes.
*
* @return cloned PKCS#8-encoded key bytes
*/
public byte[] encoded() {
return encodedPkcs8.clone();
}
/**
* Serializes this key spec into a {@link PairSeq} record.
*
* <p>
* The encoding is base64 without padding. The {@code type} field is set to
* {@code "Ed448-PRIV"}, and the PKCS#8 bytes are stored under the key
* {@code "pkcs8.b64"}.
* </p>
*
* @param spec the key spec to serialize
* @return a {@link PairSeq} containing the type and base64 data
*/
public static PairSeq marshal(Ed448PrivateKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.encodedPkcs8);
return PairSeq.of("type", "Ed448-PRIV", PKCS8_B64, b64);
}
/**
* Deserializes a {@link PairSeq} into a new {@link Ed448PrivateKeySpec}.
*
* <p>
* Expects a field {@code "pkcs8.b64"} containing the base64-encoded PKCS#8
* private key bytes. Other fields are ignored.
* </p>
*
* @param p the serialized pair sequence
* @return a reconstructed {@link Ed448PrivateKeySpec}
* @throws IllegalArgumentException if {@code pkcs8.b64} is missing
*/
public static Ed448PrivateKeySpec unmarshal(PairSeq p) {
byte[] out = null;
PairSeq.Cursor cur = p.cursor();
while (cur.next()) {
String k = cur.key();
String v = cur.value();
if (PKCS8_B64.equals(k)) {
out = Base64.getDecoder().decode(v);
}
}
if (out == null) {
throw new IllegalArgumentException("pkcs8.b64 missing for Ed448 private key");
}
return new Ed448PrivateKeySpec(out);
}
}

View File

@@ -0,0 +1,96 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed448;
import zeroecho.core.alg.common.eddsa.AbstractEncodedPublicKeyBuilder;
import zeroecho.core.spec.AlgorithmKeySpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>Ed448 Public Key Builder</h2>
*
* Concrete builder for importing Ed448 public keys from their X.509-encoded
* representation.
*
* <p>
* This class extends {@link AbstractEncodedPublicKeyBuilder} and specifies the
* Ed448 algorithm. It reconstructs {@link java.security.PublicKey} instances
* from {@link Ed448PublicKeySpec}, which carries the raw encoded form.
* </p>
*
* <h2>Responsibilities</h2>
* <ul>
* <li>Provide the canonical JCA key factory algorithm name
* ({@code "Ed448"}).</li>
* <li>Extract the X.509-encoded key bytes from {@link Ed448PublicKeySpec}.</li>
* <li>Delegate actual key reconstruction to
* {@link java.security.KeyFactory}.</li>
* </ul>
*
* <h2>Usage</h2> <pre>{@code
* // Import an Ed448 public key from encoded bytes
* Ed448PublicKeySpec spec = new Ed448PublicKeySpec(x509Bytes);
* PublicKey key = new Ed448PublicKeyBuilder().importPublic(spec);
* }</pre>
*
* <h2>Thread-safety</h2> Stateless and safe for concurrent use. Each call to
* {@link AsymmetricKeyBuilder#importPublic(AlgorithmKeySpec)
* importPublic(Ed448PublicKeySpec)} creates a new
* {@link java.security.KeyFactory}.
*
* @since 1.0
*/
public final class Ed448PublicKeyBuilder extends AbstractEncodedPublicKeyBuilder<Ed448PublicKeySpec> {
/**
* Returns the canonical JCA key factory algorithm name for Ed448.
*
* @return the string {@code "Ed448"}
*/
@Override
protected String jcaKeyFactoryAlg() {
return "Ed448";
}
/**
* Returns the X.509-encoded public key bytes from the given specification.
*
* @param spec the {@link Ed448PublicKeySpec} holding encoded public key data
* @return raw X.509-encoded public key bytes
*/
@Override
protected byte[] encodedX509(Ed448PublicKeySpec spec) {
return spec.encoded();
}
}

View File

@@ -0,0 +1,154 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed448;
import java.util.Base64;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>Ed448 Public Key Specification</h2>
*
* Immutable specification for an Ed448 public key in X.509 encoding.
*
* <p>
* This class acts as a typed carrier for encoded public key material, typically
* used with {@link Ed448PublicKeyBuilder} to reconstruct a usable
* {@link java.security.PublicKey}. It also provides marshal/unmarshal helpers
* to serialize the key into a portable {@link PairSeq} representation.
* </p>
*
* <h2>Encoding</h2>
* <ul>
* <li>The byte array is expected to contain a valid X.509-encoded Ed448 public
* key.</li>
* <li>The constructor defensively clones the provided array, and
* {@link #encoded()} returns a fresh copy on each call.</li>
* <li>The {@link #marshal(Ed448PublicKeySpec)} and {@link #unmarshal(PairSeq)}
* methods wrap and unwrap the X.509 bytes using base64 without padding.</li>
* </ul>
*
* <h2>Usage</h2> <pre>{@code
* // Wrap existing X.509-encoded bytes
* Ed448PublicKeySpec spec = new Ed448PublicKeySpec(x509Bytes);
*
* // Import into JCA PublicKey via builder
* PublicKey key = new Ed448PublicKeyBuilder().importPublic(spec);
*
* // Serialize to PairSeq (e.g., for transport)
* PairSeq p = Ed448PublicKeySpec.marshal(spec);
*
* // Deserialize from PairSeq
* Ed448PublicKeySpec restored = Ed448PublicKeySpec.unmarshal(p);
* }</pre>
*
* <h2>Thread-safety</h2> Instances are immutable and safe to share across
* threads.
*
* @since 1.0
*/
public final class Ed448PublicKeySpec implements AlgorithmKeySpec {
private static final String X509_B64 = "x509.b64";
private final byte[] encodedX509;
/**
* Constructs a new Ed448 public key spec from the given X.509-encoded bytes.
*
* @param encodedX509 X.509-encoded Ed448 public key (non-null)
* @throws IllegalArgumentException if {@code encodedX509} is {@code null}
*/
public Ed448PublicKeySpec(byte[] encodedX509) {
if (encodedX509 == null) {
throw new IllegalArgumentException("encodedX509 must not be null");
}
this.encodedX509 = encodedX509.clone();
}
/**
* Returns a defensive copy of the X.509-encoded public key bytes.
*
* @return cloned X.509-encoded key bytes
*/
public byte[] encoded() {
return encodedX509.clone();
}
/**
* Serializes this key spec into a {@link PairSeq} record.
*
* <p>
* The encoding is base64 without padding. The {@code type} field is set to
* {@code "Ed448-PUB"}, and the X.509 bytes are stored under the key
* {@code "x509.b64"}.
* </p>
*
* @param spec the key spec to serialize
* @return a {@link PairSeq} containing the type and base64 data
*/
public static PairSeq marshal(Ed448PublicKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.encodedX509);
return PairSeq.of("type", "Ed448-PUB", X509_B64, b64);
}
/**
* Deserializes a {@link PairSeq} into a new {@link Ed448PublicKeySpec}.
*
* <p>
* Expects a field {@code "x509.b64"} containing the base64-encoded X.509 public
* key bytes. Other fields are ignored.
* </p>
*
* @param p the serialized pair sequence
* @return a reconstructed {@link Ed448PublicKeySpec}
* @throws IllegalArgumentException if {@code x509.b64} is missing
*/
public static Ed448PublicKeySpec unmarshal(PairSeq p) {
byte[] out = null;
PairSeq.Cursor cur = p.cursor();
while (cur.next()) {
String k = cur.key();
String v = cur.value();
if (X509_B64.equals(k)) {
out = Base64.getDecoder().decode(v);
}
}
if (out == null) {
throw new IllegalArgumentException("x509.b64 missing for Ed448 public key");
}
return new Ed448PublicKeySpec(out);
}
}

View File

@@ -0,0 +1,141 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.ed448;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.alg.common.eddsa.CommonEdDSASignatureContext;
import zeroecho.core.context.SignatureContext;
/**
* Signature context for Ed448 with a fixed 114-byte tag length.
*
* <p>
* This class binds the Ed448 algorithm to {@link SignatureContext} via
* {@link CommonEdDSASignatureContext}. Internally it configures a JCA
* {@code Signature} with the name {@code "Ed448"} and delegates all streaming
* and verification behavior to the common adapter.
* </p>
*
* <h2>Algorithm characteristics</h2>
* <ul>
* <li>JCA signature name: {@code "Ed448"}.</li>
* <li>Fixed signature length: 114 bytes (reported by
* {@link #tagLength()}).</li>
* <li>Edwards-curve Digital Signature Algorithm as specified in RFC 8032.</li>
* </ul>
*
* <h2>Usage</h2>
* <h3>One-shot API</h3> <pre>
* {@code
* // Sign
* PrivateKey priv = ...;
* SignatureContext signer =
* new Ed448SignatureContext(CryptoAlgorithms.require("Ed448"), priv);
* signer.update(message);
* byte[] sig = signer.sign();
*
* // Verify
* PublicKey pub = ...;
* SignatureContext verifier =
* new Ed448SignatureContext(CryptoAlgorithms.require("Ed448"), pub);
* verifier.update(message);
* boolean ok = verifier.verify(sig);
* }
* </pre>
*
* <h3>Streaming pipeline (wrap)</h3> <pre>
* {@code
* // SIGN mode: body bytes followed by 114-byte signature trailer
* try (SignatureContext ctx =
* new Ed448SignatureContext(CryptoAlgorithms.require("Ed448"), priv);
* InputStream in = ctx.wrap(upstream)) {
* in.transferTo(out);
* }
*
* // VERIFY mode: supply expected signature; verification occurs at EOF
* try (SignatureContext ctx =
* new Ed448SignatureContext(CryptoAlgorithms.require("Ed448"), pub)) {
* ctx.setExpectedTag(expectedSig);
* try (InputStream in = ctx.wrap(bodyWithoutTrailer)) {
* in.transferTo(java.io.OutputStream.nullOutputStream());
* }
* }
* }
* </pre>
*
* <h2>Thread-safety</h2>
* <p>
* Instances are stateful and not thread-safe. Use one context per signing or
* verification operation, and call {@code wrap(...)} at most once per instance.
* </p>
*
* @since 1.0
*/
public final class Ed448SignatureContext extends CommonEdDSASignatureContext {
private static final String SIG_NAME = "Ed448";
private static final int TAG_LEN = 114;
/**
* Constructs a signing context bound to the given private key.
*
* @param algorithm parent algorithm descriptor; must not be {@code null}
* @param privateKey Ed448 private key; must not be {@code null}
* @throws GeneralSecurityException if the underlying JCA engine cannot be
* initialized
* @throws NullPointerException if any argument is {@code null}
*/
public Ed448SignatureContext(final CryptoAlgorithm algorithm, final PrivateKey privateKey)
throws GeneralSecurityException {
super(algorithm, privateKey, SIG_NAME, TAG_LEN);
}
/**
* Constructs a verification context bound to the given public key.
*
* @param algorithm parent algorithm descriptor; must not be {@code null}
* @param publicKey Ed448 public key; must not be {@code null}
* @throws GeneralSecurityException if the underlying JCA engine cannot be
* initialized
* @throws NullPointerException if any argument is {@code null}
*/
public Ed448SignatureContext(final CryptoAlgorithm algorithm, final PublicKey publicKey)
throws GeneralSecurityException {
super(algorithm, publicKey, SIG_NAME, TAG_LEN);
}
}

View File

@@ -0,0 +1,85 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* Ed448 digital signature integration.
*
* <p>
* This package wires the Ed448 Edwards-curve Digital Signature Algorithm into
* the core layer. It provides the algorithm descriptor, a streaming signature
* context with a fixed 114-byte tag length, builders for generating and
* importing keys, and immutable key specifications with marshalling helpers.
* </p>
*
* <h2>Scope and responsibilities</h2>
* <ul>
* <li>Register the Ed448 algorithm and declare SIGN and VERIFY roles with
* fixed-length signatures.</li>
* <li>Provide a streaming signature context that adapts JCA engines and
* enforces the 114-byte tag size.</li>
* <li>Expose builders for key-pair generation and for importing encoded
* keys.</li>
* <li>Define immutable key specifications suitable for safe cloning and simple
* marshalling.</li>
* </ul>
*
* <h2>Components</h2>
* <ul>
* <li><b>Ed448Algorithm</b>: algorithm descriptor that binds roles to a
* signature context and registers builders for key generation and import.</li>
* <li><b>Ed448SignatureContext</b>: streaming context for signing and
* verification with a fixed 114-byte tag.</li>
* <li><b>Ed448KeyGenBuilder</b> and <b>Ed448KeyGenSpec</b>: generator and
* marker spec for producing key pairs.</li>
* <li><b>Ed448PublicKeyBuilder</b> / <b>Ed448PrivateKeyBuilder</b>: importers
* backed by JCA key factories.</li>
* <li><b>Ed448PublicKeySpec</b> / <b>Ed448PrivateKeySpec</b>: immutable
* wrappers over X.509 and PKCS#8 encodings, with defensive copying and base64
* marshalling helpers.</li>
* </ul>
*
* <h2>Design notes</h2>
* <ul>
* <li>Algorithm descriptors are immutable and thread-safe.</li>
* <li>Signature contexts are stateful and not thread-safe; create a new
* instance per operation.</li>
* <li>Key specification classes never expose internal byte arrays; cloning is
* used on input and output.</li>
* <li>Marshalling helpers use compact key-value sequences intended for
* configuration, transport, and testing.</li>
* </ul>
*
* @since 1.0
*/
package zeroecho.core.alg.ed448;

View File

@@ -0,0 +1,250 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.elgamal;
import java.security.AlgorithmParameterGenerator;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.bouncycastle.jce.spec.ElGamalParameterSpec;
import zeroecho.core.AlgorithmFamily;
import zeroecho.core.KeyUsage;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.context.EncryptionContext;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* <h2>ElGamal Asymmetric Encryption Algorithm</h2>
*
* Concrete {@link zeroecho.core.CryptoAlgorithm} for the ElGamal public-key
* encryption scheme.
*
* <p>
* ElGamal is an asymmetric cryptosystem based on the hardness of the discrete
* logarithm problem in a finite cyclic group. It provides semantic security
* under chosen-plaintext attacks when combined with proper padding (e.g.,
* PKCS#1 style). This implementation delegates to the Bouncy Castle provider
* ({@code "BC"}) for underlying primitives and parameter generation.
* </p>
*
* <p>
* <b>Note:</b> OAEP is not offered for ElGamal; for modern IND-CCA security
* prefer KEM or KEM-DEM constructions such as DHIES, ECIES, or Cramer-Shoup, or
* use a KEM provided elsewhere in the catalog.
* </p>
*
* <h2>Declared capabilities</h2>
* <ul>
* <li>{@link zeroecho.core.KeyUsage#ENCRYPT} using a
* {@link java.security.PublicKey} and producing an
* {@link zeroecho.core.context.EncryptionContext}.</li>
* <li>{@link zeroecho.core.KeyUsage#DECRYPT} using a
* {@link java.security.PrivateKey} and producing an
* {@link zeroecho.core.context.EncryptionContext}.</li>
* </ul>
*
* <h2>Key material</h2>
* <ul>
* <li>{@link ElgamalParamSpec} - wrapper for explicit ElGamal group/domain
* parameters. Registered with a default of
* {@link ElgamalParamSpec#ffdhe2048()}.</li>
* <li>{@link ElgamalPublicKeySpec} - X.509-encoded public key import.</li>
* <li>{@link ElgamalPrivateKeySpec} - PKCS#8-encoded private key import.</li>
* <li>{@code ElgamalKeyGenSpec} is supported but disabled by default because
* parameter generation is slow; prefer reusing standardized
* {@link ElgamalParamSpec} instances.</li>
* </ul>
*
* <h2>Provider requirements</h2> This implementation requires the Bouncy Castle
* JCE provider to be registered under the name {@code "BC"}. If unavailable,
* {@link #ensureBC()} will throw a
* {@link java.security.NoSuchProviderException}.
*
* <h2>Thread-safety</h2> Instances of {@code ElgamalAlgorithm} are immutable
* and may be safely shared across threads. Generated
* {@link zeroecho.core.context.CryptoContext} instances are not necessarily
* thread-safe.
*
* <h2>Example</h2> <pre>{@code
* CryptoAlgorithm algo = new ElgamalAlgorithm();
* KeyPair kp = algo.generateKeyPair(ElgamalParamSpec.ffdhe2048());
*
* EncryptionContext enc = algo.create(KeyUsage.ENCRYPT, kp.getPublic(),
* ElgamalEncSpec.pkcs1());
* EncryptionContext dec = algo.create(KeyUsage.DECRYPT, kp.getPrivate(),
* ElgamalEncSpec.pkcs1());
* }</pre>
*
* @since 1.0
*/
public final class ElgamalAlgorithm extends AbstractCryptoAlgorithm {
private static final String EL_GAMAL = "ElGamal";
/**
* Constructs a new ElGamal algorithm instance bound to the {@code "ElGamal"}
* identifier and backed by the Bouncy Castle provider.
*
* <p>
* During construction, the algorithm registers:
* <ul>
* <li>Encryption and decryption capabilities,</li>
* <li>Key builders for parameter-based, public, and private key specs,</li>
* <li>Default parameter spec suppliers (e.g., ffdhe2048).</li>
* </ul>
*/
@SuppressWarnings("unused")
public ElgamalAlgorithm() {
super(EL_GAMAL, EL_GAMAL, "BC");
capability(AlgorithmFamily.ASYMMETRIC, KeyUsage.ENCRYPT, EncryptionContext.class, PublicKey.class,
ElgamalEncSpec.class, (PublicKey k, ElgamalEncSpec s) -> new ElgamalCipherContext(this, k, s, true),
ElgamalEncSpec::pkcs1);
capability(AlgorithmFamily.ASYMMETRIC, KeyUsage.DECRYPT, EncryptionContext.class, PrivateKey.class,
ElgamalEncSpec.class, (PrivateKey k, ElgamalEncSpec s) -> new ElgamalCipherContext(this, k, s, false),
ElgamalEncSpec::pkcs1);
if (false) { // NOPMD
// this key generation is slow
registerAsymmetricKeyBuilder(ElgamalKeyGenSpec.class, new AsymmetricKeyBuilder<>() {
@Override
public KeyPair generateKeyPair(ElgamalKeyGenSpec spec) throws GeneralSecurityException {
ensureBC();
AlgorithmParameterGenerator apg = AlgorithmParameterGenerator.getInstance(EL_GAMAL, providerName());
apg.init(spec.keySize(), new SecureRandom());
AlgorithmParameters ap = apg.generateParameters();
ElGamalParameterSpec eg = ap.getParameterSpec(ElGamalParameterSpec.class);
KeyPairGenerator kpg = KeyPairGenerator.getInstance(EL_GAMAL, providerName());
kpg.initialize(eg, new SecureRandom());
return kpg.generateKeyPair();
}
@Override
public PublicKey importPublic(ElgamalKeyGenSpec spec) {
throw new UnsupportedOperationException("Use ElgamalPublicKeySpec to import a public key.");
}
@Override
public PrivateKey importPrivate(ElgamalKeyGenSpec spec) {
throw new UnsupportedOperationException("Use ElgamalPrivateKeySpec to import a private key.");
}
}, ElgamalKeyGenSpec::elgamal2048);
}
registerAsymmetricKeyBuilder(ElgamalParamSpec.class, new AsymmetricKeyBuilder<>() {
@Override
public KeyPair generateKeyPair(ElgamalParamSpec spec) throws GeneralSecurityException {
ensureBC();
ElGamalParameterSpec eg = spec.parameters();
KeyPairGenerator kpg = KeyPairGenerator.getInstance(EL_GAMAL, providerName());
kpg.initialize(eg, new SecureRandom());
return kpg.generateKeyPair();
}
@Override
public PublicKey importPublic(ElgamalParamSpec spec) {
throw new UnsupportedOperationException("Use ElgamalPublicKeySpec to import a public key.");
}
@Override
public PrivateKey importPrivate(ElgamalParamSpec spec) {
throw new UnsupportedOperationException("Use ElgamalPrivateKeySpec to import a private key.");
}
}, ElgamalParamSpec::ffdhe2048);
registerAsymmetricKeyBuilder(ElgamalPublicKeySpec.class, new AsymmetricKeyBuilder<>() {
@Override
public KeyPair generateKeyPair(ElgamalPublicKeySpec spec) {
throw new UnsupportedOperationException("Generation not supported for encoded spec.");
}
@Override
public PublicKey importPublic(ElgamalPublicKeySpec spec) throws GeneralSecurityException {
ensureBC();
KeyFactory kf = KeyFactory.getInstance(EL_GAMAL, providerName());
return kf.generatePublic(new X509EncodedKeySpec(spec.encoded()));
}
@Override
public PrivateKey importPrivate(ElgamalPublicKeySpec spec) {
throw new UnsupportedOperationException("Use ElgamalPrivateKeySpec for private keys.");
}
}, null);
registerAsymmetricKeyBuilder(ElgamalPrivateKeySpec.class, new AsymmetricKeyBuilder<>() {
@Override
public KeyPair generateKeyPair(ElgamalPrivateKeySpec spec) {
throw new UnsupportedOperationException("Generation not supported for encoded spec.");
}
@Override
public PublicKey importPublic(ElgamalPrivateKeySpec spec) {
throw new UnsupportedOperationException("Use ElgamalPublicKeySpec for public keys.");
}
@Override
public PrivateKey importPrivate(ElgamalPrivateKeySpec spec) throws GeneralSecurityException {
ensureBC();
KeyFactory kf = KeyFactory.getInstance(EL_GAMAL, providerName());
return kf.generatePrivate(new PKCS8EncodedKeySpec(spec.encoded()));
}
}, null);
}
/**
* Ensures that the Bouncy Castle provider is available.
*
* @throws java.security.NoSuchProviderException if the provider {@code "BC"} is
* not registered with the JCA
*/
private static void ensureBC() throws NoSuchProviderException {
Provider p = Security.getProvider("BC");
if (p == null) {
throw new NoSuchProviderException("Bouncy Castle provider (BC) not registered");
}
}
}

View File

@@ -0,0 +1,245 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.elgamal;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.interfaces.DHKey;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.context.EncryptionContext;
import zeroecho.core.io.CipherTransformInputStreamBuilder;
/**
* Streaming ElGamal cipher context that adapts an upstream stream into an
* encrypted or decrypted stream.
*
* <p>
* The context applies ElGamal block-by-block over a pull pipeline. For a
* modulus of size {@code pBytes}, each ciphertext block is fixed at
* {@code 2 * pBytes} bytes (two group elements), while the plaintext-per-block
* depends on the padding:
* </p>
*
* <ul>
* <li>NOPADDING: {@code ptBlock = pBytes - 1}</li>
* <li>PKCS1: {@code ptBlock = pBytes - 11}</li>
* </ul>
*
* <h2>Padding subtleties</h2>
* <ul>
* <li><b>NOPADDING:</b> some providers encode integers with a leading
* {@code 0x00} byte for positive sign. This implementation trims a leading sign
* byte and enforces a fixed plaintext length for <i>non-last</i> blocks only.
* The last block is emitted as-is.</li>
* <li><b>PKCS1:</b> the last plaintext block may be shorter by design; it is
* never padded on output.</li>
* </ul>
*
* <h2>EOF rules</h2>
* <ul>
* <li><b>Encrypt:</b> a short final plaintext block is allowed and produces a
* single ciphertext block.</li>
* <li><b>Decrypt:</b> ciphertext must be an integral number of full ciphertext
* blocks; a partial tail is rejected.</li>
* </ul>
*
* <h2>Thread-safety</h2> Instances are not thread-safe. Create one context per
* pipeline.
*/
public final class ElgamalCipherContext implements EncryptionContext {
private final CryptoAlgorithm algorithm;
private final Key key;
private final ElgamalEncSpec spec;
private final boolean encrypt;
/**
* Creates a new ElGamal cipher context.
*
* <p>
* The transformation used is {@code "ElGamal/None/NOPADDING"} for
* {@link ElgamalEncSpec.Padding#NOPADDING} and
* {@code "ElGamal/None/PKCS1Padding"} for {@link ElgamalEncSpec.Padding#PKCS1}.
* The JCE provider must support the chosen transformation and the provided
* {@code key} for the requested direction.
* </p>
*
* @param algorithm the owning algorithm for metadata; must not be {@code null}
* @param key public key for encrypt or private key for decrypt; must not
* be {@code null}
* @param spec ElGamal encoding/padding specification; must not be
* {@code null}
* @param encrypt {@code true} for encryption, {@code false} for decryption
* @throws NullPointerException if any argument is {@code null}
*/
public ElgamalCipherContext(CryptoAlgorithm algorithm, Key key, ElgamalEncSpec spec, boolean encrypt) {
this.algorithm = java.util.Objects.requireNonNull(algorithm, "algorithm");
this.key = java.util.Objects.requireNonNull(key, "key");
this.spec = java.util.Objects.requireNonNull(spec, "spec");
this.encrypt = encrypt;
}
/**
* Returns an input stream that transforms bytes on-the-fly using ElGamal.
*
* <p>
* A fresh {@link Cipher} is created and initialized per call. The returned
* stream implements the block model and EOF rules described in the class
* documentation. Callers must close the returned stream to finalize the
* transformation and release resources.
* </p>
*
* @param upstream the upstream source to transform; must not be {@code null}
* @return a transforming input stream
* @throws IOException if the cipher cannot be created or initialized
* @throws NullPointerException if {@code upstream} is {@code null}
*/
@Override
public InputStream attach(InputStream upstream) throws IOException {
java.util.Objects.requireNonNull(upstream, "upstream");
final Cipher cipher = newCipher(spec.padding(), encrypt, key);
final BlockGeometry g = new BlockGeometry(modulusBytes(key), spec.padding(), encrypt);
return CipherTransformInputStreamBuilder.builder().withCipher(cipher).withUpstream(upstream)
.withInputBlockSize(g.inputBlockSize()).withOutputBlockSize(g.perBlockOutput())
.withLeftZeroPadding(g.noPadding).build();
}
/**
* Returns the associated algorithm object for metadata and auditing.
*
* @return the algorithm that created this context
*/
@Override
public CryptoAlgorithm algorithm() {
return algorithm;
}
/**
* Returns the key bound to this context.
*
* @return the key used for encryption or decryption
*/
@Override
public Key key() {
return key;
}
/**
* Closes this context.
*
* <p>
* The returned transforming streams are independent of the context object, so
* closing the context has no effect on previously created streams. This method
* is provided to satisfy the {@link zeroecho.core.context.CryptoContext}
* contract and may be used by higher layers for lifecycle hooks.
* </p>
*/
@Override
public void close() throws IOException {
// No resources to release here. Streams manage their own lifetimes.
}
private static Cipher newCipher(ElgamalEncSpec.Padding padding, boolean encrypt, Key key) throws IOException {
final String transformation = switch (padding) {
case NOPADDING -> "ElGamal/None/NOPADDING";
case PKCS1 -> "ElGamal/None/PKCS1Padding";
};
try {
Cipher c = Cipher.getInstance(transformation);
c.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key);
return c;
} catch (GeneralSecurityException e) {
throw new IOException("ElGamal cipher init failed: " + e.getMessage(), e);
}
}
/**
* Derives the modulus size in bytes from a DH-like key (FFDHE/ElGamal).
*/
private static int modulusBytes(Key key) {
if (key instanceof DHKey dh) {
// FFDHE: p bit length is a multiple of 64; convert to exact bytes.
final int bits = dh.getParams().getP().bitLength();
return (bits + 7) >>> 3;
}
// If your keys expose p through a different interface, add handling here.
throw new IllegalArgumentException("Unsupported key type for ElGamal modulus: " + key.getClass().getName());
}
/**
* Immutable description of the ElGamal block layout for a given modulus size,
* padding, and direction.
*/
private static final class BlockGeometry {
private final int pBytes; // modulus size in bytes
private final int ptBlock; // plaintext-per-block
private final int ctBlock; // ciphertext-per-block (always 2*pBytes)
private final boolean encrypt;
private final boolean noPadding;
private BlockGeometry(int pBytes, ElgamalEncSpec.Padding padding, boolean encrypt) {
this.pBytes = pBytes;
this.ctBlock = 2 * pBytes;
this.encrypt = encrypt;
this.noPadding = (padding == ElgamalEncSpec.Padding.NOPADDING);
this.ptBlock = switch (padding) {
case NOPADDING -> pBytes - 1;
case PKCS1 -> pBytes - 11;
};
}
private int inputBlockSize() {
// How many upstream bytes constitute one cipher operation
return encrypt ? ptBlock : ctBlock;
}
private int perBlockOutput() {
// Upper-bound for bytes produced by one operation (for buffer sizing)
return encrypt ? ctBlock : ptBlock;
}
@Override
public String toString() {
return "BlockGeometry [pBytes=" + pBytes + ", ptBlock=" + ptBlock + ", ctBlock=" + ctBlock + ", encrypt="
+ encrypt + ", noPadding=" + noPadding + ", inputBlockSize()=" + inputBlockSize()
+ ", perBlockOutput()=" + perBlockOutput() + "]";
}
}
}

View File

@@ -0,0 +1,160 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.elgamal;
import java.util.Objects;
import zeroecho.core.annotation.Describable;
import zeroecho.core.annotation.DisplayName;
import zeroecho.core.spec.ContextSpec;
/**
* <h2>ElGamal Encryption Parameters</h2>
*
* Specification for configuring ElGamal encryption contexts.
*
* <p>
* {@code ElgamalEncSpec} selects the padding mode to apply when transforming
* plaintext into group elements for ElGamal encryption. This affects both
* security properties and maximum plaintext block size.
* </p>
*
* <p>
* <b>Note:</b> OAEP is not offered for ElGamal; for modern IND-CCA security
* prefer KEM or KEM-DEM constructions such as DHIES, ECIES, or Cramer-Shoup, or
* use a KEM provided elsewhere in the catalog.
* </p>
*
* <h2>Padding modes</h2>
* <ul>
* <li>{@link Padding#NOPADDING} - raw ElGamal encryption of messages less than
* the modulus {@code p}. Provides minimal security (plaintexts are only
* randomized by ephemeral exponent) and must not be used directly in modern
* protocols without additional safeguards (e.g., hybrid encryption or KEM
* construction).</li>
* <li>{@link Padding#PKCS1} - PKCS#1 v1.5-style padding applied to the
* plaintext before encryption. Adds structural redundancy and prevents trivial
* small-subgroup attacks. Still considered outdated for new protocols but
* available for compatibility.</li>
* </ul>
*
* <h2>Usage</h2> Instances are created via the static factories: <pre>{@code
* ElgamalEncSpec spec = ElgamalEncSpec.pkcs1();
* EncryptionContext ctx = algo.create(KeyUsage.ENCRYPT, pubKey, spec);
* }</pre>
*
* <h2>Thread-safety</h2> {@code ElgamalEncSpec} is immutable and safe to share
* across threads.
*
* @since 1.0
*/
@DisplayName("ElGamal encryption parameters")
public final class ElgamalEncSpec implements ContextSpec, Describable {
/**
* Supported padding modes for ElGamal encryption.
*/
public enum Padding {
/**
* No padding applied.
*
* <p>
* Plaintext must be strictly less than modulus {@code p}. Provides only
* semantic security under chosen-plaintext attack if used in combination with
* fresh random exponents. Not recommended for use outside compatibility
* scenarios.
* </p>
*/
NOPADDING,
/**
* PKCS#1 v1.5-style padding.
*
* <p>
* Adds structured redundancy to plaintext before encryption. Historically used
* in RSA and adapted here for ElGamal. Provides limited protection against
* certain classes of attacks but should be replaced by modern OAEP-style
* constructions where available.
* </p>
*/
PKCS1
}
private final Padding padding;
private ElgamalEncSpec(Padding padding) {
this.padding = Objects.requireNonNull(padding, "padding");
}
/**
* Returns an {@code ElgamalEncSpec} with no padding.
*
* @return specification with {@link Padding#NOPADDING}
*/
public static ElgamalEncSpec noPadding() {
return new ElgamalEncSpec(Padding.NOPADDING);
}
/**
* Returns an {@code ElgamalEncSpec} with PKCS#1 v1.5-style padding.
*
* @return specification with {@link Padding#PKCS1}
*/
public static ElgamalEncSpec pkcs1() {
return new ElgamalEncSpec(Padding.PKCS1);
}
/**
* Returns the selected padding mode.
*
* @return padding enumeration value
*/
public Padding padding() {
return padding;
}
/**
* Human-readable description of this spec.
*
* <p>
* Matches the transformation string used by the JCA provider (e.g.,
* {@code "NOPADDING"} or {@code "PKCS1Padding"}).
* </p>
*
* @return descriptive string for this padding mode
*/
@Override
public String description() {
return (padding == Padding.NOPADDING) ? "NOPADDING" : "PKCS1Padding";
}
}

View File

@@ -0,0 +1,141 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.elgamal;
import zeroecho.core.annotation.Describable;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* <h2>ElGamal Key Generation Specification</h2>
*
* Parameters controlling ElGamal key pair generation. This specification
* provides the modulus size and the certainty parameter for primality testing.
*
* <p>
* When generating a new ElGamal key pair, the modulus {@code p} must be chosen
* as a large safe prime. The {@code keySize} determines the bit length of
* {@code p}, and {@code certainty} controls the statistical strength of
* MillerRabin primality checks.
* </p>
*
* <h2>Validation rules</h2>
* <ul>
* <li>{@code keySize} must be at least 1024 bits.</li>
* <li>{@code certainty} must be at least 64, which ensures negligible error
* probability in primality testing.</li>
* </ul>
*
* <h2>Factory</h2>
* <ul>
* <li>{@link #elgamal2048()} returns a commonly used default spec with a
* 2048-bit modulus and 128 MillerRabin iterations.</li>
* </ul>
*
* <h2>Usage</h2> <pre>{@code
* ElgamalKeyGenSpec spec = ElgamalKeyGenSpec.elgamal2048();
* KeyPair kp = algo.generateKeyPair(spec);
* }</pre>
*
* <h2>Thread-safety</h2> Instances are immutable and can be freely shared
* between threads.
*
* @since 1.0
*/
public final class ElgamalKeyGenSpec implements AlgorithmKeySpec, Describable {
private final int keySize; // bits for p
private final int certainty; // Miller-Rabin certainty
/**
* Constructs a new ElGamal key generation spec.
*
* @param keySize desired modulus size in bits (minimum 1024)
* @param certainty number of MillerRabin iterations for primality testing
* (minimum 64)
* @throws IllegalArgumentException if {@code keySize} &lt; 1024 or
* {@code certainty} &lt; 64
*/
public ElgamalKeyGenSpec(int keySize, int certainty) {
if (keySize < 1024) { // NOPMD
throw new IllegalArgumentException("keySize too small");
}
if (certainty < 64) { // NOPMD
throw new IllegalArgumentException("certainty too small");
}
this.keySize = keySize;
this.certainty = certainty;
}
/**
* Returns the modulus size in bits.
*
* @return number of bits for the prime modulus p
*/
public int keySize() {
return keySize;
}
/**
* Returns the MillerRabin certainty parameter.
*
* @return number of iterations used in primality testing
*/
public int certainty() {
return certainty;
}
/**
* Returns a default ElGamal spec with a 2048-bit modulus and 128 iterations for
* primality testing.
*
* @return default ElGamal key generation specification
*/
public static ElgamalKeyGenSpec elgamal2048() {
return new ElgamalKeyGenSpec(2048, 128);
}
/**
* Human-readable description of this spec.
*
* <p>
* Example: {@code "p=2048, certainty=128"}.
* </p>
*
* @return descriptive string including modulus size and certainty
*/
@Override
public String description() {
return "p=" + keySize + ", certainty=" + certainty;
}
}

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