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

32
lib/.classpath Normal file
View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="bin/main" path="src/main/java">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/test" path="src/test/java">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/test" path="src/test/resources">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/main" path="src/main/resources">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

29
lib/.project Normal file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>lib</name>
<comment>Project lib created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>net.sourceforge.pmd.eclipse.plugin.pmdBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
<nature>net.sourceforge.pmd.eclipse.plugin.pmdNature</nature>
</natures>
</projectDescription>

31
lib/LICENSE Normal file
View File

@@ -0,0 +1,31 @@
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.

56
lib/build.gradle Normal file
View File

@@ -0,0 +1,56 @@
plugins {
id 'buildlogic.java-library-conventions'
id 'com.palantir.git-version'
}
group 'org.egothor'
dependencies {
implementation 'org.bouncycastle:bcpkix-jdk18on'
implementation 'org.egothor:conflux'
implementation 'org.apache.commons:commons-imaging'
}
def generatedDir = layout.buildDirectory.dir("generated/docs").get().asFile
def staticOverview = file("src/main/javadoc/overview.html")
def overviewCss = file("src/main/javadoc/css/overview.css")
tasks.register('generateCryptoTable', JavaExec) {
group = 'documentation'
description = 'Generates the Crypto Catalog table fragment'
classpath = sourceSets.main.runtimeClasspath
mainClass = 'zeroecho.core.util.GenerateCryptoCatalogTable'
args file("$generatedDir/crypto-catalog-table.html").absolutePath
dependsOn classes
}
tasks.register('composeOverview') {
group = 'documentation'
description = 'Produces a final overview.html by injecting the generated table into the static template'
inputs.file(staticOverview)
inputs.file("$generatedDir/crypto-catalog-table.html")
outputs.file("$generatedDir/overview.composed.html")
dependsOn tasks.named('generateCryptoTable')
doLast {
def template = staticOverview.getText('UTF-8')
def table = file("$generatedDir/crypto-catalog-table.html").getText('UTF-8')
def marker = "<!-- CRYPTO_CATALOG_TABLE -->"
if (!template.contains(marker)) {
throw new GradleException("Marker not found in ${staticOverview}: ${marker}")
}
def composed = template.replace(marker, table)
file("$generatedDir/overview.composed.html").setText(composed, 'UTF-8')
}
}
javadoc {
dependsOn tasks.named('composeOverview')
options.overview = file("$generatedDir/overview.composed.html")
options.encoding = 'UTF-8'
// options.stylesheetFile = overviewCss
options.addStringOption("-add-stylesheet", overviewCss.absolutePath)
options.links("https://www.egothor.org/javadoc/conflux")
// options.overview = file("src/main/javadoc/overview.html")
}

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;

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