Initial commit (history reset)
This commit is contained in:
32
lib/.classpath
Normal file
32
lib/.classpath
Normal 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
29
lib/.project
Normal 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
31
lib/LICENSE
Normal 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
56
lib/build.gradle
Normal 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")
|
||||
}
|
||||
76
lib/src/main/java/zeroecho/core/AlgorithmFamily.java
Normal file
76
lib/src/main/java/zeroecho/core/AlgorithmFamily.java
Normal 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
|
||||
}
|
||||
106
lib/src/main/java/zeroecho/core/Capability.java
Normal file
106
lib/src/main/java/zeroecho/core/Capability.java
Normal 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");
|
||||
}
|
||||
}
|
||||
115
lib/src/main/java/zeroecho/core/CatalogSelector.java
Normal file
115
lib/src/main/java/zeroecho/core/CatalogSelector.java
Normal 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;
|
||||
}
|
||||
}
|
||||
148
lib/src/main/java/zeroecho/core/ConfluxKeys.java
Normal file
148
lib/src/main/java/zeroecho/core/ConfluxKeys.java
Normal 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 key–value 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);
|
||||
}
|
||||
}
|
||||
846
lib/src/main/java/zeroecho/core/CryptoAlgorithm.java
Normal file
846
lib/src/main/java/zeroecho/core/CryptoAlgorithm.java
Normal 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 algorithm’s 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 binding’s {@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 binding’s 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);
|
||||
}
|
||||
}
|
||||
609
lib/src/main/java/zeroecho/core/CryptoAlgorithms.java
Normal file
609
lib/src/main/java/zeroecho/core/CryptoAlgorithms.java
Normal 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 & 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 algorithm’s 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 algorithm’s 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 algorithm’s 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 algorithm’s 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);
|
||||
}
|
||||
}
|
||||
322
lib/src/main/java/zeroecho/core/CryptoCatalog.java
Normal file
322
lib/src/main/java/zeroecho/core/CryptoCatalog.java
Normal 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 & 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 &<>} 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
|
||||
* round‑tripping.
|
||||
* </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 &, <, >}.
|
||||
* </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("&", "&").replace("<", "<").replace(">", ">");
|
||||
}
|
||||
}
|
||||
86
lib/src/main/java/zeroecho/core/KeyUsage.java
Normal file
86
lib/src/main/java/zeroecho/core/KeyUsage.java
Normal 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
|
||||
}
|
||||
185
lib/src/main/java/zeroecho/core/NullKey.java
Normal file
185
lib/src/main/java/zeroecho/core/NullKey.java
Normal 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 don’t 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";
|
||||
}
|
||||
}
|
||||
57
lib/src/main/java/zeroecho/core/SymmetricHeaderCodec.java
Normal file
57
lib/src/main/java/zeroecho/core/SymmetricHeaderCodec.java
Normal 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;
|
||||
}
|
||||
178
lib/src/main/java/zeroecho/core/alg/AbstractCryptoAlgorithm.java
Normal file
178
lib/src/main/java/zeroecho/core/alg/AbstractCryptoAlgorithm.java
Normal 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 algorithm’s 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));
|
||||
}
|
||||
}
|
||||
134
lib/src/main/java/zeroecho/core/alg/aes/AesAlgorithm.java
Normal file
134
lib/src/main/java/zeroecho/core/alg/aes/AesAlgorithm.java
Normal 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>Thread‑safety</h2> Registration is static and thread‑safe. Produced
|
||||
* contexts are stateful and not thread‑safe.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
296
lib/src/main/java/zeroecho/core/alg/aes/AesCipherContext.java
Normal file
296
lib/src/main/java/zeroecho/core/alg/aes/AesCipherContext.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
174
lib/src/main/java/zeroecho/core/alg/aes/AesHeaderCodec.java
Normal file
174
lib/src/main/java/zeroecho/core/alg/aes/AesHeaderCodec.java
Normal 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 non‑GCM; otherwise 96..128.</li>
|
||||
* <li><b>aadFlag</b>: 1 when AAD is present; the header then includes a SHA‑256
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
107
lib/src/main/java/zeroecho/core/alg/aes/AesKeyGenSpec.java
Normal file
107
lib/src/main/java/zeroecho/core/alg/aes/AesKeyGenSpec.java
Normal 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";
|
||||
}
|
||||
}
|
||||
164
lib/src/main/java/zeroecho/core/alg/aes/AesKeyImportSpec.java
Normal file
164
lib/src/main/java/zeroecho/core/alg/aes/AesKeyImportSpec.java
Normal 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);
|
||||
}
|
||||
}
|
||||
362
lib/src/main/java/zeroecho/core/alg/aes/AesSpec.java
Normal file
362
lib/src/main/java/zeroecho/core/alg/aes/AesSpec.java
Normal 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 compile‑time 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 thread‑safe.</li>
|
||||
* <li><b>Validation:</b> GCM requires {@code NOPADDING} and a tag length
|
||||
* between 96 and 128 bits (inclusive) in 8‑bit increments; non‑GCM modes must
|
||||
* not specify a tag length.</li>
|
||||
* <li><b>Header codec:</b> when present, the codec persists IV and GCM
|
||||
* parameters in‑band, 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 (Encrypt‑then‑MAC) when integrity/authenticity are required.
|
||||
* </p>
|
||||
*/
|
||||
public enum Mode {
|
||||
/**
|
||||
* Cipher Block Chaining (CBC).
|
||||
*
|
||||
* <p>
|
||||
* Requires a 16‑byte 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 12‑byte IV and {@link Padding#NOPADDING}. The tag length must be
|
||||
* 96–128 bits (multiple of 8). Supports Additional Authenticated Data (AAD).
|
||||
* Recommended default.
|
||||
* </p>
|
||||
*/
|
||||
GCM,
|
||||
/**
|
||||
* Counter mode (CTR).
|
||||
*
|
||||
* <p>
|
||||
* Uses a 16‑byte 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}, 128‑bit tag, no header).
|
||||
*
|
||||
* @return a fresh builder for {@link AesSpec}
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link AesSpec}.
|
||||
*
|
||||
* <p>
|
||||
* Not thread‑safe. 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 96–128 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 128‑bit 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 128‑bit 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 non‑GCM 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 non‑null, 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 human‑readable 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 + ")" : "");
|
||||
}
|
||||
}
|
||||
94
lib/src/main/java/zeroecho/core/alg/aes/package-info.java
Normal file
94
lib/src/main/java/zeroecho/core/alg/aes/package-info.java
Normal 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;
|
||||
219
lib/src/main/java/zeroecho/core/alg/bike/BikeAlgorithm.java
Normal file
219
lib/src/main/java/zeroecho/core/alg/bike/BikeAlgorithm.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
193
lib/src/main/java/zeroecho/core/alg/bike/BikeKemContext.java
Normal file
193
lib/src/main/java/zeroecho/core/alg/bike/BikeKemContext.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
132
lib/src/main/java/zeroecho/core/alg/bike/BikeKeyGenSpec.java
Normal file
132
lib/src/main/java/zeroecho/core/alg/bike/BikeKeyGenSpec.java
Normal 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();
|
||||
}
|
||||
}
|
||||
136
lib/src/main/java/zeroecho/core/alg/bike/BikePrivateKeySpec.java
Normal file
136
lib/src/main/java/zeroecho/core/alg/bike/BikePrivateKeySpec.java
Normal 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 + "]";
|
||||
}
|
||||
}
|
||||
136
lib/src/main/java/zeroecho/core/alg/bike/BikePublicKeySpec.java
Normal file
136
lib/src/main/java/zeroecho/core/alg/bike/BikePublicKeySpec.java
Normal 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 + "]";
|
||||
}
|
||||
}
|
||||
88
lib/src/main/java/zeroecho/core/alg/bike/package-info.java
Normal file
88
lib/src/main/java/zeroecho/core/alg/bike/package-info.java
Normal 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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)";
|
||||
}
|
||||
}
|
||||
109
lib/src/main/java/zeroecho/core/alg/chacha/ChaChaAlgorithm.java
Normal file
109
lib/src/main/java/zeroecho/core/alg/chacha/ChaChaAlgorithm.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
175
lib/src/main/java/zeroecho/core/alg/chacha/ChaChaSpec.java
Normal file
175
lib/src/main/java/zeroecho/core/alg/chacha/ChaChaSpec.java
Normal 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 + ")";
|
||||
}
|
||||
}
|
||||
90
lib/src/main/java/zeroecho/core/alg/chacha/package-info.java
Normal file
90
lib/src/main/java/zeroecho/core/alg/chacha/package-info.java
Normal 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;
|
||||
250
lib/src/main/java/zeroecho/core/alg/cmce/CmceAlgorithm.java
Normal file
250
lib/src/main/java/zeroecho/core/alg/cmce/CmceAlgorithm.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
226
lib/src/main/java/zeroecho/core/alg/cmce/CmceKemContext.java
Normal file
226
lib/src/main/java/zeroecho/core/alg/cmce/CmceKemContext.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
243
lib/src/main/java/zeroecho/core/alg/cmce/CmceKeyGenSpec.java
Normal file
243
lib/src/main/java/zeroecho/core/alg/cmce/CmceKeyGenSpec.java
Normal 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();
|
||||
}
|
||||
}
|
||||
164
lib/src/main/java/zeroecho/core/alg/cmce/CmcePrivateKeySpec.java
Normal file
164
lib/src/main/java/zeroecho/core/alg/cmce/CmcePrivateKeySpec.java
Normal 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 + "]";
|
||||
}
|
||||
}
|
||||
164
lib/src/main/java/zeroecho/core/alg/cmce/CmcePublicKeySpec.java
Normal file
164
lib/src/main/java/zeroecho/core/alg/cmce/CmcePublicKeySpec.java
Normal 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 + "]";
|
||||
}
|
||||
}
|
||||
102
lib/src/main/java/zeroecho/core/alg/cmce/package-info.java
Normal file
102
lib/src/main/java/zeroecho/core/alg/cmce/package-info.java
Normal 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;
|
||||
@@ -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 peer’s {@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 party’s 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 peer’s 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 party’s 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 */
|
||||
}
|
||||
}
|
||||
@@ -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 peer’s 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 peer’s 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 Diffie–Hellman, 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
80
lib/src/main/java/zeroecho/core/alg/common/package-info.java
Normal file
80
lib/src/main/java/zeroecho/core/alg/common/package-info.java
Normal 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 core’s 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;
|
||||
@@ -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} <= 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} <= 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;
|
||||
}
|
||||
}
|
||||
225
lib/src/main/java/zeroecho/core/alg/common/sig/Stream.java
Normal file
225
lib/src/main/java/zeroecho/core/alg/common/sig/Stream.java
Normal 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;
|
||||
}
|
||||
}
|
||||
128
lib/src/main/java/zeroecho/core/alg/common/sig/package-info.java
Normal file
128
lib/src/main/java/zeroecho/core/alg/common/sig/package-info.java
Normal 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;
|
||||
176
lib/src/main/java/zeroecho/core/alg/dh/DhAlgorithm.java
Normal file
176
lib/src/main/java/zeroecho/core/alg/dh/DhAlgorithm.java
Normal 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
|
||||
* algorithm’s 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);
|
||||
|
||||
}
|
||||
}
|
||||
194
lib/src/main/java/zeroecho/core/alg/dh/DhKeyGenBuilder.java
Normal file
194
lib/src/main/java/zeroecho/core/alg/dh/DhKeyGenBuilder.java
Normal 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();
|
||||
}
|
||||
}
|
||||
149
lib/src/main/java/zeroecho/core/alg/dh/DhPrivateKeySpec.java
Normal file
149
lib/src/main/java/zeroecho/core/alg/dh/DhPrivateKeySpec.java
Normal 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);
|
||||
}
|
||||
}
|
||||
149
lib/src/main/java/zeroecho/core/alg/dh/DhPublicKeySpec.java
Normal file
149
lib/src/main/java/zeroecho/core/alg/dh/DhPublicKeySpec.java
Normal 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);
|
||||
}
|
||||
}
|
||||
215
lib/src/main/java/zeroecho/core/alg/dh/DhSpec.java
Normal file
215
lib/src/main/java/zeroecho/core/alg/dh/DhSpec.java
Normal 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;
|
||||
}
|
||||
}
|
||||
85
lib/src/main/java/zeroecho/core/alg/dh/package-info.java
Normal file
85
lib/src/main/java/zeroecho/core/alg/dh/package-info.java
Normal 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 (2048–8192 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;
|
||||
262
lib/src/main/java/zeroecho/core/alg/digest/DigestSpec.java
Normal file
262
lib/src/main/java/zeroecho/core/alg/digest/DigestSpec.java
Normal 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();
|
||||
}
|
||||
}
|
||||
452
lib/src/main/java/zeroecho/core/alg/digest/JcaDigestContext.java
Normal file
452
lib/src/main/java/zeroecho/core/alg/digest/JcaDigestContext.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
83
lib/src/main/java/zeroecho/core/alg/digest/package-info.java
Normal file
83
lib/src/main/java/zeroecho/core/alg/digest/package-info.java
Normal 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 framework’s 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;
|
||||
161
lib/src/main/java/zeroecho/core/alg/ecdh/EcdhAlgorithm.java
Normal file
161
lib/src/main/java/zeroecho/core/alg/ecdh/EcdhAlgorithm.java
Normal 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 peer’s 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);
|
||||
}
|
||||
}
|
||||
122
lib/src/main/java/zeroecho/core/alg/ecdh/EcdhCurveSpec.java
Normal file
122
lib/src/main/java/zeroecho/core/alg/ecdh/EcdhCurveSpec.java
Normal 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;
|
||||
}
|
||||
}
|
||||
132
lib/src/main/java/zeroecho/core/alg/ecdh/EcdhKeyGenBuilder.java
Normal file
132
lib/src/main/java/zeroecho/core/alg/ecdh/EcdhKeyGenBuilder.java
Normal 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.");
|
||||
}
|
||||
}
|
||||
77
lib/src/main/java/zeroecho/core/alg/ecdh/package-info.java
Normal file
77
lib/src/main/java/zeroecho/core/alg/ecdh/package-info.java
Normal 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;
|
||||
143
lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaAlgorithm.java
Normal file
143
lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaAlgorithm.java
Normal 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);
|
||||
}
|
||||
}
|
||||
144
lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaCurveSpec.java
Normal file
144
lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaCurveSpec.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
86
lib/src/main/java/zeroecho/core/alg/ecdsa/package-info.java
Normal file
86
lib/src/main/java/zeroecho/core/alg/ecdsa/package-info.java
Normal 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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
163
lib/src/main/java/zeroecho/core/alg/ed448/Ed448Algorithm.java
Normal file
163
lib/src/main/java/zeroecho/core/alg/ed448/Ed448Algorithm.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
85
lib/src/main/java/zeroecho/core/alg/ed448/package-info.java
Normal file
85
lib/src/main/java/zeroecho/core/alg/ed448/package-info.java
Normal 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
Reference in New Issue
Block a user