Compare commits
12 Commits
impl-pki
...
release@1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
f2260eff73
|
|||
|
4a5e7b9c11
|
|||
|
14a7a940de
|
|||
|
eca392007c
|
|||
|
2333b01a3f
|
|||
|
de55ea909f
|
|||
|
a66c115a80
|
|||
|
e74e833c5b
|
|||
|
14fbf31989
|
|||
|
a4b9eeffe1
|
|||
|
e235d0e2b5
|
|||
|
d1bdf7d9df
|
41
LICENSE
41
LICENSE
@@ -1,31 +1,28 @@
|
|||||||
Copyright (C) 2024, Leo Galambos
|
Copyright (C) 2026, Leo Galambos
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Redistribution and use in source and binary forms, with or without
|
||||||
are permitted provided that the following conditions are met:
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
list of conditions and the following disclaimer.
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
this list of conditions and the following disclaimer in the documentation
|
this list of conditions and the following disclaimer in the documentation
|
||||||
and/or other materials provided with the distribution.
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
3. All advertising materials mentioning features or use of this software must
|
3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
display the following acknowledgement:
|
may be used to endorse or promote products derived from this software
|
||||||
This product includes software developed by the Egothor project.
|
without specific prior written permission.
|
||||||
|
|
||||||
4. Neither the name of the copyright holder nor the names of its contributors
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
may be used to endorse or promote products derived from this software without
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
specific prior written permission.
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ dependencies {
|
|||||||
implementation 'org.apache.commons:commons-text'
|
implementation 'org.apache.commons:commons-text'
|
||||||
implementation 'commons-cli:commons-cli'
|
implementation 'commons-cli:commons-cli'
|
||||||
implementation project(':lib')
|
implementation project(':lib')
|
||||||
// might be removed if I move BC ops to the lib
|
implementation project(':ext')
|
||||||
testImplementation 'org.bouncycastle:bcpkix-jdk18on'
|
// might be removed if I move BC ops to the lib
|
||||||
|
testImplementation 'org.bouncycastle:bcpkix-jdk18on'
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ import org.apache.commons.cli.OptionGroup;
|
|||||||
import org.apache.commons.cli.Options;
|
import org.apache.commons.cli.Options;
|
||||||
import org.apache.commons.cli.ParseException;
|
import org.apache.commons.cli.ParseException;
|
||||||
|
|
||||||
import zeroecho.sdk.integrations.covert.jpeg.JpegExifEmbedder;
|
import zeroecho.ext.integrations.covert.jpeg.JpegExifEmbedder;
|
||||||
import zeroecho.sdk.integrations.covert.jpeg.Slot;
|
import zeroecho.ext.integrations.covert.jpeg.Slot;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command-line extension of ZeroEcho for covert embedding and extraction of
|
* Command-line extension of ZeroEcho for covert embedding and extraction of
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ dependencies {
|
|||||||
// Define dependency versions as constraints
|
// Define dependency versions as constraints
|
||||||
implementation 'org.apache.commons:commons-text:1.15.0'
|
implementation 'org.apache.commons:commons-text:1.15.0'
|
||||||
implementation 'commons-cli:commons-cli:1.11.0'
|
implementation 'commons-cli:commons-cli:1.11.0'
|
||||||
implementation 'org.bouncycastle:bcpkix-jdk18on:1.83'
|
implementation 'org.bouncycastle:bcpkix-jdk18on:1.84'
|
||||||
implementation 'org.egothor:conflux:[1.0,2.0)'
|
implementation 'org.egothor:conflux:[1.0,2.0)'
|
||||||
implementation 'org.apache.commons:commons-imaging:1.0.0-alpha6'
|
implementation 'org.apache.commons:commons-imaging:1.0.0-alpha6'
|
||||||
}
|
}
|
||||||
|
|||||||
32
ext/.classpath
Normal file
32
ext/.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/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="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="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>
|
||||||
23
ext/.project
Normal file
23
ext/.project
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>ext</name>
|
||||||
|
<comment>Project ext 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>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
||||||
31
ext/LICENSE
Normal file
31
ext/LICENSE
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
Copyright (C) 2026, Leo Galambos
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. All advertising materials mentioning features or use of this software must
|
||||||
|
display the following acknowledgement:
|
||||||
|
This product includes software developed by the Egothor project.
|
||||||
|
|
||||||
|
4. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
13
ext/build.gradle
Normal file
13
ext/build.gradle
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
plugins {
|
||||||
|
id 'buildlogic.java-library-conventions'
|
||||||
|
id 'com.palantir.git-version'
|
||||||
|
}
|
||||||
|
|
||||||
|
group='org.egothor'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api 'org.bouncycastle:bcpkix-jdk18on'
|
||||||
|
implementation 'org.apache.commons:commons-imaging'
|
||||||
|
implementation project(':lib')
|
||||||
|
testImplementation 'org.egothor:conflux'
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.content.export;
|
package zeroecho.ext.content.export;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.content.export;
|
package zeroecho.ext.content.export;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -36,10 +36,10 @@
|
|||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This package provides streaming utilities and exportable content
|
* This package provides streaming utilities and exportable content
|
||||||
* implementations that render {@link zeroecho.sdk.content.api.DataContent} for
|
* implementations that render {@link zeroecho.ext.content.api.DataContent} for
|
||||||
* deployment to external platforms or for script-based transport. Exports can
|
* deployment to external platforms or for script-based transport. Exports can
|
||||||
* be produced as raw bytes or as platform-specific scripts according to
|
* be produced as raw bytes or as platform-specific scripts according to
|
||||||
* {@link zeroecho.sdk.content.api.ExportableDataContent.ExportMode}.
|
* {@link zeroecho.ext.content.api.ExportableDataContent.ExportMode}.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Scope</h2>
|
* <h2>Scope</h2>
|
||||||
@@ -60,15 +60,15 @@
|
|||||||
* <li><i>Piwigo uploader</i> - an exportable content implementation
|
* <li><i>Piwigo uploader</i> - an exportable content implementation
|
||||||
* (package-private) that can either upload an image directly to a Piwigo server
|
* (package-private) that can either upload an image directly to a Piwigo server
|
||||||
* or generate Bash/CMD scripts that reconstruct and upload the image. It is
|
* or generate Bash/CMD scripts that reconstruct and upload the image. It is
|
||||||
* built on {@link zeroecho.sdk.content.api.AbstractExportableDataContent} and
|
* built on {@link zeroecho.ext.content.api.AbstractExportableDataContent} and
|
||||||
* honors
|
* honors
|
||||||
* {@link zeroecho.sdk.content.api.ExportableDataContent.ExportMode}.</li>
|
* {@link zeroecho.ext.content.api.ExportableDataContent.ExportMode}.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h2>Typical usage</h2>
|
* <h2>Typical usage</h2>
|
||||||
* <h3>Format a stream as Base64 command lines</h3> <pre>{@code
|
* <h3>Format a stream as Base64 command lines</h3> <pre>{@code
|
||||||
* java.io.InputStream source = ... raw bytes ...;
|
* java.io.InputStream source = ... raw bytes ...;
|
||||||
* java.io.InputStream encoded = new zeroecho.sdk.content.export.Base64Stream(
|
* java.io.InputStream encoded = new zeroecho.ext.content.export.Base64Stream(
|
||||||
* source,
|
* source,
|
||||||
* "echo ".getBytes(java.nio.charset.StandardCharsets.UTF_8),
|
* "echo ".getBytes(java.nio.charset.StandardCharsets.UTF_8),
|
||||||
* 76,
|
* 76,
|
||||||
@@ -78,8 +78,8 @@
|
|||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <h3>Render an exportable content in a chosen mode</h3> <pre>{@code
|
* <h3>Render an exportable content in a chosen mode</h3> <pre>{@code
|
||||||
* zeroecho.sdk.content.api.ExportableDataContent content = ... some exportable content ...;
|
* zeroecho.ext.content.api.ExportableDataContent content = ... some exportable content ...;
|
||||||
* content.setExportMode(zeroecho.sdk.content.api.ExportableDataContent.ExportMode.BASH_SCRIPT);
|
* content.setExportMode(zeroecho.ext.content.api.ExportableDataContent.ExportMode.BASH_SCRIPT);
|
||||||
* try (java.io.InputStream script = content.getStream()) {
|
* try (java.io.InputStream script = content.getStream()) {
|
||||||
* script.transferTo(out);
|
* script.transferTo(out);
|
||||||
* }
|
* }
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
*
|
*
|
||||||
* <h2>Security notes</h2>
|
* <h2>Security notes</h2>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Prefer exporting {@link zeroecho.sdk.content.api.EncryptedContent} when
|
* <li>Prefer exporting {@link zeroecho.ext.content.api.EncryptedContent} when
|
||||||
* targeting untrusted destinations.</li>
|
* targeting untrusted destinations.</li>
|
||||||
* <li>Avoid embedding secrets in scripts; pass credentials via environment
|
* <li>Avoid embedding secrets in scripts; pass credentials via environment
|
||||||
* variables or secure stores when possible.</li>
|
* variables or secure stores when possible.</li>
|
||||||
@@ -98,8 +98,8 @@
|
|||||||
* <h2>Extensibility</h2>
|
* <h2>Extensibility</h2>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>New deployers should extend
|
* <li>New deployers should extend
|
||||||
* {@link zeroecho.sdk.content.api.AbstractExportableDataContent} and select a
|
* {@link zeroecho.ext.content.api.AbstractExportableDataContent} and select a
|
||||||
* default {@link zeroecho.sdk.content.api.ExportableDataContent.ExportMode}
|
* default {@link zeroecho.ext.content.api.ExportableDataContent.ExportMode}
|
||||||
* appropriate for the platform.</li>
|
* appropriate for the platform.</li>
|
||||||
* <li>Utilities like {@link Base64Stream} can be reused to generate
|
* <li>Utilities like {@link Base64Stream} can be reused to generate
|
||||||
* platform-friendly payloads without buffering whole files.</li>
|
* platform-friendly payloads without buffering whole files.</li>
|
||||||
@@ -107,4 +107,4 @@
|
|||||||
*
|
*
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
package zeroecho.sdk.content.export;
|
package zeroecho.ext.content.export;
|
||||||
89
ext/src/main/java/zeroecho/ext/content/package-info.java
Normal file
89
ext/src/main/java/zeroecho/ext/content/package-info.java
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (C) 2026, Leo Galambos
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. All advertising materials mentioning features or use of this software must
|
||||||
|
* display the following acknowledgement:
|
||||||
|
* This product includes software developed by the Egothor project.
|
||||||
|
*
|
||||||
|
* 4. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
******************************************************************************/
|
||||||
|
/**
|
||||||
|
* Content abstractions and streaming payloads for the SDK.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This package defines the common content model used by the SDK and organizes
|
||||||
|
* concrete sources and exporters under dedicated subpackages. Content objects
|
||||||
|
* expose streaming access to bytes and can be chained into pipelines produced
|
||||||
|
* by builder APIs.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <h2>Subpackages</h2>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link zeroecho.ext.content.export} - export helpers and platform
|
||||||
|
* deployers, such as {@link zeroecho.ext.content.export.Base64Stream} and
|
||||||
|
* exportable content that targets external destinations (for example, gallery
|
||||||
|
* platforms) or script-based transports.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <h2>Responsibilities</h2>
|
||||||
|
* <ul>
|
||||||
|
* <li>Support exportable content that can render itself as raw bytes or
|
||||||
|
* platform-specific scripts via
|
||||||
|
* {@link zeroecho.sdk.content.api.ExportableDataContent}.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <h2>Typical usage</h2> <pre>{@code
|
||||||
|
* // Stream a DataContent instance to an OutputStream.
|
||||||
|
* zeroecho.sdk.content.api.DataContent content = ... obtained from a builder chain ...;
|
||||||
|
* try (java.io.InputStream in = content.getStream()) {
|
||||||
|
* in.transferTo(out);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // If exportable, render in an alternate mode (for example, a shell script).
|
||||||
|
* if (content instanceof zeroecho.sdk.content.api.ExportableDataContent exportable) {
|
||||||
|
* exportable.setExportMode(
|
||||||
|
* zeroecho.sdk.content.api.ExportableDataContent.ExportMode.BASH_SCRIPT);
|
||||||
|
* try (java.io.InputStream script = exportable.getStream()) {
|
||||||
|
* script.transferTo(out);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* <h2>Extensibility</h2>
|
||||||
|
* <ul>
|
||||||
|
* <li>New exporters should extend
|
||||||
|
* {@link zeroecho.sdk.content.api.AbstractExportableDataContent} and honor the
|
||||||
|
* selected
|
||||||
|
* {@link zeroecho.sdk.content.api.ExportableDataContent.ExportMode}.</li>
|
||||||
|
* <li>The export area is intended to host deployers for additional public
|
||||||
|
* platforms and, where appropriate, steganographic carriers for concealed
|
||||||
|
* delivery of encrypted streams.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
package zeroecho.ext.content;
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.integrations.covert;
|
package zeroecho.ext.integrations.covert;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.integrations.covert.jpeg;
|
package zeroecho.ext.integrations.covert.jpeg;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.integrations.covert.jpeg;
|
package zeroecho.ext.integrations.covert.jpeg;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -110,4 +110,4 @@
|
|||||||
*
|
*
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
package zeroecho.sdk.integrations.covert.jpeg;
|
package zeroecho.ext.integrations.covert.jpeg;
|
||||||
@@ -54,8 +54,8 @@
|
|||||||
*
|
*
|
||||||
* <h2>Typical usage</h2> <pre>{@code
|
* <h2>Typical usage</h2> <pre>{@code
|
||||||
* // Use the built-in English distribution to create a short cover text.
|
* // Use the built-in English distribution to create a short cover text.
|
||||||
* zeroecho.sdk.integrations.covert.TextualCodec.Generator gen =
|
* zeroecho.ext.integrations.covert.TextualCodec.Generator gen =
|
||||||
* zeroecho.sdk.integrations.covert.TextualCodec.Generator.EN;
|
* zeroecho.ext.integrations.covert.TextualCodec.Generator.EN;
|
||||||
* String sample = gen.getText(256);
|
* String sample = gen.getText(256);
|
||||||
*
|
*
|
||||||
* // Or construct a custom distribution.
|
* // Or construct a custom distribution.
|
||||||
@@ -63,8 +63,8 @@
|
|||||||
* java.util.Map.entry('e', 12.7), java.util.Map.entry('t', 9.1),
|
* java.util.Map.entry('e', 12.7), java.util.Map.entry('t', 9.1),
|
||||||
* java.util.Map.entry('a', 8.2), java.util.Map.entry(' ', 25.4)
|
* java.util.Map.entry('a', 8.2), java.util.Map.entry(' ', 25.4)
|
||||||
* );
|
* );
|
||||||
* zeroecho.sdk.integrations.covert.TextualCodec.Generator custom =
|
* zeroecho.ext.integrations.covert.TextualCodec.Generator custom =
|
||||||
* new zeroecho.sdk.integrations.covert.TextualCodec.Generator(freq);
|
* new zeroecho.ext.integrations.covert.TextualCodec.Generator(freq);
|
||||||
* String cover = custom.getText(128);
|
* String cover = custom.getText(128);
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
@@ -78,4 +78,4 @@
|
|||||||
*
|
*
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
package zeroecho.sdk.integrations.covert;
|
package zeroecho.ext.integrations.covert;
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.integrations.stegano;
|
package zeroecho.ext.integrations.stegano;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enumeration of image formats supported for steganographic processing.
|
* Enumeration of image formats supported for steganographic processing.
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.integrations.stegano;
|
package zeroecho.ext.integrations.stegano;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.integrations.stegano;
|
package zeroecho.ext.integrations.stegano;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.integrations.stegano;
|
package zeroecho.ext.integrations.stegano;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata that describes a steganographic method.
|
* Metadata that describes a steganographic method.
|
||||||
@@ -44,11 +44,11 @@
|
|||||||
* <h2>Design principles</h2>
|
* <h2>Design principles</h2>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>All algorithms implement the
|
* <li>All algorithms implement the
|
||||||
* {@link zeroecho.sdk.integrations.stegano.SteganographyMethod} interface,
|
* {@link zeroecho.ext.integrations.stegano.SteganographyMethod} interface,
|
||||||
* which defines stream-oriented {@code embed} and {@code extract} operations
|
* which defines stream-oriented {@code embed} and {@code extract} operations
|
||||||
* and supplies a metadata descriptor.</li>
|
* and supplies a metadata descriptor.</li>
|
||||||
* <li>Supported carrier formats are represented by
|
* <li>Supported carrier formats are represented by
|
||||||
* {@link zeroecho.sdk.integrations.stegano.ImageFormat}. Only lossless formats
|
* {@link zeroecho.ext.integrations.stegano.ImageFormat}. Only lossless formats
|
||||||
* are suitable for direct bit-level embedding, but some algorithms may also
|
* are suitable for direct bit-level embedding, but some algorithms may also
|
||||||
* provide support for lossy domains such as JPEG with dedicated
|
* provide support for lossy domains such as JPEG with dedicated
|
||||||
* techniques.</li>
|
* techniques.</li>
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
* the frequency domain (for example, DCT coefficients for JPEG) or more
|
* the frequency domain (for example, DCT coefficients for JPEG) or more
|
||||||
* advanced hybrid techniques. New methods can be registered by implementing the
|
* advanced hybrid techniques. New methods can be registered by implementing the
|
||||||
* {@code SteganographyMethod} interface and returning appropriate
|
* {@code SteganographyMethod} interface and returning appropriate
|
||||||
* {@link zeroecho.sdk.integrations.stegano.StegoMetadata}.
|
* {@link zeroecho.ext.integrations.stegano.StegoMetadata}.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Typical workflow</h2> <pre>{@code
|
* <h2>Typical workflow</h2> <pre>{@code
|
||||||
@@ -81,4 +81,4 @@
|
|||||||
* }
|
* }
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*/
|
*/
|
||||||
package zeroecho.sdk.integrations.stegano;
|
package zeroecho.ext.integrations.stegano;
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.content.export;
|
package zeroecho.ext.content.export;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.integrations.covert;
|
package zeroecho.ext.integrations.covert;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.integrations.covert.jpeg;
|
package zeroecho.ext.integrations.covert.jpeg;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.sdk.integrations.stegano;
|
package zeroecho.ext.integrations.stegano;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 230 KiB |
@@ -13,13 +13,6 @@
|
|||||||
<attribute name="test" value="true"/>
|
<attribute name="test" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</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">
|
<classpathentry kind="src" output="bin/main" path="src/main/resources">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="gradle_scope" value="main"/>
|
<attribute name="gradle_scope" value="main"/>
|
||||||
|
|||||||
@@ -5,10 +5,19 @@ plugins {
|
|||||||
|
|
||||||
group='org.egothor'
|
group='org.egothor'
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
mockitoAgent
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api 'org.bouncycastle:bcpkix-jdk18on'
|
api 'org.bouncycastle:bcpkix-jdk18on'
|
||||||
implementation 'org.egothor:conflux'
|
implementation 'org.egothor:conflux'
|
||||||
implementation 'org.apache.commons:commons-imaging'
|
|
||||||
|
testImplementation "org.mockito:mockito-core:5.23.0"
|
||||||
|
|
||||||
|
mockitoAgent("org.mockito:mockito-core:5.23.0") {
|
||||||
|
transitive = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -54,3 +63,8 @@ javadoc {
|
|||||||
options.links("https://www.egothor.org/javadoc/conflux")
|
options.links("https://www.egothor.org/javadoc/conflux")
|
||||||
// options.overview = file("src/main/javadoc/overview.html")
|
// options.overview = file("src/main/javadoc/overview.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
jvmArgs("-javaagent:${configurations.mockitoAgent.singleFile}")
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import javax.crypto.SecretKey;
|
|||||||
|
|
||||||
import zeroecho.core.audit.AuditListener;
|
import zeroecho.core.audit.AuditListener;
|
||||||
import zeroecho.core.audit.AuditedContexts;
|
import zeroecho.core.audit.AuditedContexts;
|
||||||
|
import zeroecho.core.context.AgreementContext;
|
||||||
import zeroecho.core.context.CryptoContext;
|
import zeroecho.core.context.CryptoContext;
|
||||||
import zeroecho.core.context.DigestContext;
|
import zeroecho.core.context.DigestContext;
|
||||||
import zeroecho.core.context.EncryptionContext;
|
import zeroecho.core.context.EncryptionContext;
|
||||||
@@ -335,33 +336,39 @@ public final class CryptoAlgorithms {
|
|||||||
|
|
||||||
if (AUDIT_MODE == AuditMode.WRAP) {
|
if (AUDIT_MODE == AuditMode.WRAP) {
|
||||||
final AuditListener listener = AUDIT; // pass through the global listener
|
final AuditListener listener = AUDIT; // pass through the global listener
|
||||||
if (ctx instanceof SignatureContext) {
|
return switch (ctx) {
|
||||||
@SuppressWarnings("unchecked")
|
case SignatureContext signatureContext -> wrapForAudit(signatureContext, listener, role);
|
||||||
C out = (C) AuditedContexts.wrap(ctx, listener, role);
|
case EncryptionContext encryptionContext -> wrapForAudit(encryptionContext, listener, role);
|
||||||
return out;
|
case KemContext kemContext -> wrapForAudit(kemContext, listener, role);
|
||||||
} else if (ctx instanceof EncryptionContext) {
|
case DigestContext digestContext -> wrapForAudit(digestContext, listener, role);
|
||||||
@SuppressWarnings("unchecked")
|
case MacContext macContext -> wrapForAudit(macContext, listener, role);
|
||||||
C out = (C) AuditedContexts.wrap(ctx, listener, role);
|
case AgreementContext agreementContext -> wrapForAudit(agreementContext, 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;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the audited wrapper for the supplied context.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The returned context remains owned by the caller of the factory method. This
|
||||||
|
* helper does not acquire an additional resource requiring local cleanup.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param <C> context type
|
||||||
|
* @param context source context
|
||||||
|
* @param listener audit listener
|
||||||
|
* @param role key usage role
|
||||||
|
* @return audited wrapper
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
/* default */ static <C extends CryptoContext> C wrapForAudit(CryptoContext context, AuditListener listener,
|
||||||
|
KeyUsage role) {
|
||||||
|
return (C) AuditedContexts.wrap(context, listener, role);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link CryptoContext} using the algorithm’s default spec for the
|
* Creates a {@link CryptoContext} using the algorithm’s default spec for the
|
||||||
* role.
|
* role.
|
||||||
|
|||||||
@@ -44,12 +44,12 @@ import java.util.Objects;
|
|||||||
import javax.security.auth.DestroyFailedException;
|
import javax.security.auth.DestroyFailedException;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.SecretWithEncapsulation;
|
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.PrivateKeyFactory;
|
||||||
import org.bouncycastle.pqc.crypto.util.PublicKeyFactory;
|
import org.bouncycastle.pqc.crypto.util.PublicKeyFactory;
|
||||||
|
import org.bouncycastle.pqc.legacy.bike.BIKEKEMExtractor;
|
||||||
|
import org.bouncycastle.pqc.legacy.bike.BIKEKEMGenerator;
|
||||||
|
import org.bouncycastle.pqc.legacy.bike.BIKEPrivateKeyParameters;
|
||||||
|
import org.bouncycastle.pqc.legacy.bike.BIKEPublicKeyParameters;
|
||||||
|
|
||||||
import zeroecho.core.CryptoAlgorithm;
|
import zeroecho.core.CryptoAlgorithm;
|
||||||
import zeroecho.core.context.KemContext;
|
import zeroecho.core.context.KemContext;
|
||||||
|
|||||||
@@ -232,6 +232,43 @@ public final class HybridKexBuilder {
|
|||||||
return new PqcKem(this);
|
return new PqcKem(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the configured classic agreement leg for the current builder state.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This method validates the classic-leg inputs required by the selected
|
||||||
|
* {@link ClassicMode} and returns the resulting {@link AgreementContext} ready
|
||||||
|
* for inclusion into a {@link HybridKexContext}. For
|
||||||
|
* {@link ClassicMode#CLASSIC_AGREEMENT}, the returned context is also bound to
|
||||||
|
* the configured peer public key.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return classic agreement context derived from the configured classic-leg
|
||||||
|
* state
|
||||||
|
* @throws IOException if underlying context creation fails
|
||||||
|
* @throws IllegalStateException if the selected classic mode is missing
|
||||||
|
* required state
|
||||||
|
*/
|
||||||
|
private AgreementContext buildClassicLeg() throws IOException {
|
||||||
|
if (classicMode == ClassicMode.CLASSIC_AGREEMENT) {
|
||||||
|
if (classicPrivate == null || classicPeerPublic == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"classic private key and peer public must be set for CLASSIC_AGREEMENT");
|
||||||
|
}
|
||||||
|
AgreementContext classic = CryptoAlgorithms.create(classicAlgId, KeyUsage.AGREEMENT, classicPrivate,
|
||||||
|
classicSpec);
|
||||||
|
classic.setPeerPublic(classicPeerPublic);
|
||||||
|
return classic;
|
||||||
|
}
|
||||||
|
if (classicMode == ClassicMode.PAIR_MESSAGE) {
|
||||||
|
if (classicKeyPair == null) {
|
||||||
|
throw new IllegalStateException("classic key pair must be set for PAIR_MESSAGE");
|
||||||
|
}
|
||||||
|
return CryptoAlgorithms.create(classicAlgId, KeyUsage.AGREEMENT, classicKeyPair, classicSpec);
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("classic mode must be selected");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds initiator-side context.
|
* Builds initiator-side context.
|
||||||
*
|
*
|
||||||
@@ -241,22 +278,7 @@ public final class HybridKexBuilder {
|
|||||||
public HybridKexContext buildInitiator() throws IOException {
|
public HybridKexContext buildInitiator() throws IOException {
|
||||||
validateCommon();
|
validateCommon();
|
||||||
|
|
||||||
AgreementContext classic;
|
AgreementContext classic = buildClassicLeg();
|
||||||
if (classicMode == ClassicMode.CLASSIC_AGREEMENT) {
|
|
||||||
if (classicPrivate == null || classicPeerPublic == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"classic private key and peer public must be set for CLASSIC_AGREEMENT");
|
|
||||||
}
|
|
||||||
classic = CryptoAlgorithms.create(classicAlgId, KeyUsage.AGREEMENT, classicPrivate, classicSpec);
|
|
||||||
classic.setPeerPublic(classicPeerPublic);
|
|
||||||
} else if (classicMode == ClassicMode.PAIR_MESSAGE) {
|
|
||||||
if (classicKeyPair == null) {
|
|
||||||
throw new IllegalStateException("classic key pair must be set for PAIR_MESSAGE");
|
|
||||||
}
|
|
||||||
classic = CryptoAlgorithms.create(classicAlgId, KeyUsage.AGREEMENT, classicKeyPair, classicSpec);
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("classic mode must be selected");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pqcPeerPublic == null) {
|
if (pqcPeerPublic == null) {
|
||||||
throw new IllegalStateException("pqc peer public must be set for initiator");
|
throw new IllegalStateException("pqc peer public must be set for initiator");
|
||||||
@@ -279,22 +301,7 @@ public final class HybridKexBuilder {
|
|||||||
public HybridKexContext buildResponder() throws IOException {
|
public HybridKexContext buildResponder() throws IOException {
|
||||||
validateCommon();
|
validateCommon();
|
||||||
|
|
||||||
AgreementContext classic;
|
AgreementContext classic = buildClassicLeg();
|
||||||
if (classicMode == ClassicMode.CLASSIC_AGREEMENT) {
|
|
||||||
if (classicPrivate == null || classicPeerPublic == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"classic private key and peer public must be set for CLASSIC_AGREEMENT");
|
|
||||||
}
|
|
||||||
classic = CryptoAlgorithms.create(classicAlgId, KeyUsage.AGREEMENT, classicPrivate, classicSpec);
|
|
||||||
classic.setPeerPublic(classicPeerPublic);
|
|
||||||
} else if (classicMode == ClassicMode.PAIR_MESSAGE) {
|
|
||||||
if (classicKeyPair == null) {
|
|
||||||
throw new IllegalStateException("classic key pair must be set for PAIR_MESSAGE");
|
|
||||||
}
|
|
||||||
classic = CryptoAlgorithms.create(classicAlgId, KeyUsage.AGREEMENT, classicKeyPair, classicSpec);
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("classic mode must be selected");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pqcPrivate == null) {
|
if (pqcPrivate == null) {
|
||||||
throw new IllegalStateException("pqc private key must be set for responder");
|
throw new IllegalStateException("pqc private key must be set for responder");
|
||||||
|
|||||||
@@ -54,10 +54,6 @@
|
|||||||
* {@link zeroecho.sdk.content.builtin.PlainString},
|
* {@link zeroecho.sdk.content.builtin.PlainString},
|
||||||
* {@link zeroecho.sdk.content.builtin.PlainFile}, and
|
* {@link zeroecho.sdk.content.builtin.PlainFile}, and
|
||||||
* {@link zeroecho.sdk.content.builtin.SecretPassword}.</li>
|
* {@link zeroecho.sdk.content.builtin.SecretPassword}.</li>
|
||||||
* <li>{@link zeroecho.sdk.content.export} - export helpers and platform
|
|
||||||
* deployers, such as {@link zeroecho.sdk.content.export.Base64Stream} and
|
|
||||||
* exportable content that targets external destinations (for example, gallery
|
|
||||||
* platforms) or script-based transports.</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h2>Responsibilities</h2>
|
* <h2>Responsibilities</h2>
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (C) 2026, Leo Galambos
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. All advertising materials mentioning features or use of this software must
|
||||||
|
* display the following acknowledgement:
|
||||||
|
* This product includes software developed by the Egothor project.
|
||||||
|
*
|
||||||
|
* 4. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (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 static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import zeroecho.core.CryptoAlgorithms.AuditMode;
|
||||||
|
import zeroecho.core.audit.AuditListener;
|
||||||
|
import zeroecho.core.context.AgreementContext;
|
||||||
|
import zeroecho.core.context.CryptoContext;
|
||||||
|
import zeroecho.core.context.DigestContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies audited proxy wrapping for representative {@link CryptoContext}
|
||||||
|
* contract types.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* These tests focus on the internal audit wrapping path used by
|
||||||
|
* {@link CryptoAlgorithms#wrapForAudit(CryptoContext, zeroecho.core.audit.AuditListener, KeyUsage)}.
|
||||||
|
* They verify that representative context types are wrapped as audited JDK
|
||||||
|
* proxies and that the resulting wrapper preserves the expected basic
|
||||||
|
* delegation behavior.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
class CryptoAlgorithmsAuditWrapTest {
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void restoreAuditConfiguration() {
|
||||||
|
CryptoAlgorithms.setAuditListener(AuditListener.noop());
|
||||||
|
CryptoAlgorithms.setAuditMode(AuditMode.OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void wrapForAuditDigestContextReturnsProxy() {
|
||||||
|
System.out.println("wrapForAuditDigestContextReturnsProxy");
|
||||||
|
|
||||||
|
DigestContext context = mock(DigestContext.class);
|
||||||
|
DigestContext wrapped = CryptoAlgorithms.wrapForAudit(context, AuditListener.noop(), KeyUsage.DIGEST);
|
||||||
|
|
||||||
|
System.out.println("...ctxClass=" + wrapped.getClass().getName());
|
||||||
|
assertTrue(Proxy.isProxyClass(wrapped.getClass()), "Digest context should be wrapped as JDK proxy");
|
||||||
|
|
||||||
|
System.out.println("wrapForAuditDigestContextReturnsProxy...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void wrapForAuditAgreementContextReturnsProxy() {
|
||||||
|
System.out.println("wrapForAuditAgreementContextReturnsProxy");
|
||||||
|
|
||||||
|
AgreementContext context = mock(AgreementContext.class);
|
||||||
|
AgreementContext wrapped = CryptoAlgorithms.wrapForAudit(context, AuditListener.noop(), KeyUsage.AGREEMENT);
|
||||||
|
|
||||||
|
System.out.println("...ctxClass=" + wrapped.getClass().getName());
|
||||||
|
assertTrue(Proxy.isProxyClass(wrapped.getClass()), "Agreement context should be wrapped as JDK proxy");
|
||||||
|
|
||||||
|
System.out.println("wrapForAuditAgreementContextReturnsProxy...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void wrapForAuditDigestContextCloseDelegatesToWrappedContext() throws Exception {
|
||||||
|
System.out.println("wrapForAuditDigestContextCloseDelegatesToWrappedContext");
|
||||||
|
|
||||||
|
DigestContext context = mock(DigestContext.class);
|
||||||
|
DigestContext wrapped = CryptoAlgorithms.wrapForAudit(context, AuditListener.noop(), KeyUsage.DIGEST);
|
||||||
|
|
||||||
|
wrapped.close();
|
||||||
|
|
||||||
|
verify(context).close();
|
||||||
|
System.out.println("...wrappedCloseDelegated=true");
|
||||||
|
System.out.println("wrapForAuditDigestContextCloseDelegatesToWrappedContext...ok");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,471 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (C) 2026, Leo Galambos
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. All advertising materials mentioning features or use of this software must
|
||||||
|
* display the following acknowledgement:
|
||||||
|
* This product includes software developed by the Egothor project.
|
||||||
|
*
|
||||||
|
* 4. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (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.sdk.builders;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import zeroecho.core.CryptoAlgorithms;
|
||||||
|
import zeroecho.core.alg.common.agreement.KeyPairKey;
|
||||||
|
import zeroecho.core.alg.kyber.KyberKeyGenSpec;
|
||||||
|
import zeroecho.core.alg.xdh.XdhSpec;
|
||||||
|
import zeroecho.sdk.hybrid.kex.HybridKexContext;
|
||||||
|
import zeroecho.sdk.hybrid.kex.HybridKexPolicy;
|
||||||
|
import zeroecho.sdk.hybrid.kex.HybridKexProfile;
|
||||||
|
import zeroecho.sdk.hybrid.kex.HybridKexTranscript;
|
||||||
|
import zeroecho.sdk.util.BouncyCastleActivator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Professional unit and regression tests for {@link HybridKexBuilder}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* These tests verify both supported classic-leg construction modes,
|
||||||
|
* builder-side validation failures, policy enforcement, transcript binding, and
|
||||||
|
* mode-switch behavior. The builder is exercised strictly through its public
|
||||||
|
* fluent API so that validation and resulting hybrid context construction are
|
||||||
|
* covered together.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
class HybridKexBuilderTest {
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setup() {
|
||||||
|
try {
|
||||||
|
BouncyCastleActivator.init();
|
||||||
|
} catch (Throwable ignore) {
|
||||||
|
// Keep tests runnable even when BC is not present.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildInitiatorResponderClassicAgreementRoundTrip() throws Exception {
|
||||||
|
System.out.println("buildInitiatorResponderClassicAgreementRoundTrip");
|
||||||
|
|
||||||
|
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
|
||||||
|
HybridKexTranscript transcript = new HybridKexTranscript().addUtf8("suite", "X25519+ML-KEM-768").addUtf8("role",
|
||||||
|
"builder-test");
|
||||||
|
|
||||||
|
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||||
|
|
||||||
|
HybridKexContext alice = null;
|
||||||
|
HybridKexContext bob = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
alice = HybridKexBuilder.builder().profile(profile).transcript(transcript).classicAgreement()
|
||||||
|
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate())
|
||||||
|
.peerPublic(bobClassic.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic())
|
||||||
|
.buildInitiator();
|
||||||
|
|
||||||
|
bob = HybridKexBuilder.builder().profile(profile).transcript(transcript).classicAgreement().algorithm("Xdh")
|
||||||
|
.spec(XdhSpec.X25519).privateKey(bobClassic.getPrivate()).peerPublic(aliceClassic.getPublic())
|
||||||
|
.pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate()).buildResponder();
|
||||||
|
|
||||||
|
byte[] aliceMessage = alice.getPeerMessage();
|
||||||
|
System.out.println("...aliceMessage(" + lens(aliceMessage) + ")=" + hex(aliceMessage));
|
||||||
|
|
||||||
|
bob.setPeerMessage(aliceMessage);
|
||||||
|
|
||||||
|
byte[] secretAlice = alice.deriveSecret();
|
||||||
|
byte[] secretBob = bob.deriveSecret();
|
||||||
|
|
||||||
|
System.out.println("...secretAlice=" + hex(secretAlice));
|
||||||
|
System.out.println("...secretBob=" + hex(secretBob));
|
||||||
|
|
||||||
|
assertNotNull(secretAlice);
|
||||||
|
assertNotNull(secretBob);
|
||||||
|
assertArrayEquals(secretAlice, secretBob);
|
||||||
|
assertEquals(profile.outLenBytes(), secretAlice.length);
|
||||||
|
} finally {
|
||||||
|
closeQuietly(alice);
|
||||||
|
closeQuietly(bob);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("buildInitiatorResponderClassicAgreementRoundTrip...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildInitiatorResponderPairMessageRoundTrip() throws Exception {
|
||||||
|
System.out.println("buildInitiatorResponderPairMessageRoundTrip");
|
||||||
|
|
||||||
|
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
|
||||||
|
|
||||||
|
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||||
|
|
||||||
|
HybridKexContext alice = null;
|
||||||
|
HybridKexContext bob = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
alice = HybridKexBuilder.builder().profile(profile).classicPairMessage().algorithm("Xdh")
|
||||||
|
.spec(XdhSpec.X25519).keyPair(new KeyPairKey(aliceClassic)).pqcKem().algorithm("ML-KEM")
|
||||||
|
.peerPublic(bobPqc.getPublic()).buildInitiator();
|
||||||
|
|
||||||
|
bob = HybridKexBuilder.builder().profile(profile).classicPairMessage().algorithm("Xdh").spec(XdhSpec.X25519)
|
||||||
|
.keyPair(new KeyPairKey(bobClassic)).pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate())
|
||||||
|
.buildResponder();
|
||||||
|
|
||||||
|
byte[] messageA = alice.getPeerMessage();
|
||||||
|
System.out.println("...messageA(" + lens(messageA) + ")=" + hex(messageA));
|
||||||
|
bob.setPeerMessage(messageA);
|
||||||
|
|
||||||
|
byte[] messageB = bob.getPeerMessage();
|
||||||
|
System.out.println("...messageB(" + lens(messageB) + ")=" + hex(messageB));
|
||||||
|
alice.setPeerMessage(messageB);
|
||||||
|
|
||||||
|
byte[] secretAlice = alice.deriveSecret();
|
||||||
|
byte[] secretBob = bob.deriveSecret();
|
||||||
|
|
||||||
|
System.out.println("...secretAlice=" + hex(secretAlice));
|
||||||
|
System.out.println("...secretBob=" + hex(secretBob));
|
||||||
|
|
||||||
|
assertNotNull(secretAlice);
|
||||||
|
assertNotNull(secretBob);
|
||||||
|
assertArrayEquals(secretAlice, secretBob);
|
||||||
|
assertEquals(profile.outLenBytes(), secretAlice.length);
|
||||||
|
} finally {
|
||||||
|
closeQuietly(alice);
|
||||||
|
closeQuietly(bob);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("buildInitiatorResponderPairMessageRoundTrip...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildInitiatorWithoutProfileFails() throws Exception {
|
||||||
|
System.out.println("buildInitiatorWithoutProfileFails");
|
||||||
|
|
||||||
|
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||||
|
|
||||||
|
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
|
||||||
|
HybridKexBuilder.builder().classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519)
|
||||||
|
.privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic()).pqcKem()
|
||||||
|
.algorithm("ML-KEM").peerPublic(bobPqc.getPublic()).buildInitiator();
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println("...exception=" + exception.getMessage());
|
||||||
|
assertEquals("profile must be set", exception.getMessage());
|
||||||
|
|
||||||
|
System.out.println("buildInitiatorWithoutProfileFails...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildInitiatorWithoutClassicModeFails() throws Exception {
|
||||||
|
System.out.println("buildInitiatorWithoutClassicModeFails");
|
||||||
|
|
||||||
|
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
|
||||||
|
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||||
|
|
||||||
|
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
|
||||||
|
HybridKexBuilder.builder().profile(profile).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic())
|
||||||
|
.buildInitiator();
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println("...exception=" + exception.getMessage());
|
||||||
|
assertEquals("classic mode must be selected", exception.getMessage());
|
||||||
|
|
||||||
|
System.out.println("buildInitiatorWithoutClassicModeFails...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildInitiatorClassicAgreementWithoutPeerPublicFails() throws Exception {
|
||||||
|
System.out.println("buildInitiatorClassicAgreementWithoutPeerPublicFails");
|
||||||
|
|
||||||
|
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
|
||||||
|
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||||
|
|
||||||
|
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
|
||||||
|
HybridKexBuilder.builder().profile(profile).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519)
|
||||||
|
.privateKey(aliceClassic.getPrivate()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic())
|
||||||
|
.buildInitiator();
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println("...exception=" + exception.getMessage());
|
||||||
|
assertEquals("classic private key and peer public must be set for CLASSIC_AGREEMENT", exception.getMessage());
|
||||||
|
|
||||||
|
System.out.println("buildInitiatorClassicAgreementWithoutPeerPublicFails...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildResponderPairMessageWithoutKeyPairFails() throws Exception {
|
||||||
|
System.out.println("buildResponderPairMessageWithoutKeyPairFails");
|
||||||
|
|
||||||
|
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
|
||||||
|
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||||
|
|
||||||
|
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
|
||||||
|
HybridKexBuilder.builder().profile(profile).classicPairMessage().algorithm("Xdh").spec(XdhSpec.X25519)
|
||||||
|
.pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate()).buildResponder();
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println("...exception=" + exception.getMessage());
|
||||||
|
assertEquals("classic key pair must be set for PAIR_MESSAGE", exception.getMessage());
|
||||||
|
|
||||||
|
System.out.println("buildResponderPairMessageWithoutKeyPairFails...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildInitiatorWithoutPqcPeerPublicFails() throws Exception {
|
||||||
|
System.out.println("buildInitiatorWithoutPqcPeerPublicFails");
|
||||||
|
|
||||||
|
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
|
||||||
|
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
|
||||||
|
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
|
||||||
|
HybridKexBuilder.builder().profile(profile).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519)
|
||||||
|
.privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic()).pqcKem()
|
||||||
|
.algorithm("ML-KEM").buildInitiator();
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println("...exception=" + exception.getMessage());
|
||||||
|
assertEquals("pqc peer public must be set for initiator", exception.getMessage());
|
||||||
|
|
||||||
|
System.out.println("buildInitiatorWithoutPqcPeerPublicFails...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildResponderWithoutPqcPrivateFails() throws Exception {
|
||||||
|
System.out.println("buildResponderWithoutPqcPrivateFails");
|
||||||
|
|
||||||
|
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
|
||||||
|
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
|
||||||
|
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
|
||||||
|
HybridKexBuilder.builder().profile(profile).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519)
|
||||||
|
.privateKey(bobClassic.getPrivate()).peerPublic(aliceClassic.getPublic()).pqcKem()
|
||||||
|
.algorithm("ML-KEM").buildResponder();
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println("...exception=" + exception.getMessage());
|
||||||
|
assertEquals("pqc private key must be set for responder", exception.getMessage());
|
||||||
|
|
||||||
|
System.out.println("buildResponderWithoutPqcPrivateFails...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildInitiatorRejectsPolicyWhenOkmTooShort() throws Exception {
|
||||||
|
System.out.println("buildInitiatorRejectsPolicyWhenOkmTooShort");
|
||||||
|
|
||||||
|
HybridKexProfile profile = HybridKexProfile.defaultProfile(16);
|
||||||
|
HybridKexPolicy policy = new HybridKexPolicy(0, 0, 32);
|
||||||
|
|
||||||
|
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||||
|
|
||||||
|
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
HybridKexBuilder.builder().profile(profile).policy(policy).classicAgreement().algorithm("Xdh")
|
||||||
|
.spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic())
|
||||||
|
.pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic()).buildInitiator();
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println("...exception=" + exception.getMessage());
|
||||||
|
assertEquals("Hybrid OKM length too small: 16 < 32", exception.getMessage());
|
||||||
|
|
||||||
|
System.out.println("buildInitiatorRejectsPolicyWhenOkmTooShort...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void switchingClassicModeClearsConflictingStateAndBuildsPairMessage() throws Exception {
|
||||||
|
System.out.println("switchingClassicModeClearsConflictingStateAndBuildsPairMessage");
|
||||||
|
|
||||||
|
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
|
||||||
|
KeyPair agreementKeyPair = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair pairMessageKeyPair = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||||
|
|
||||||
|
HybridKexContext context = null;
|
||||||
|
try {
|
||||||
|
HybridKexBuilder builder = HybridKexBuilder.builder().profile(profile);
|
||||||
|
|
||||||
|
builder.classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519).privateKey(agreementKeyPair.getPrivate())
|
||||||
|
.peerPublic(agreementKeyPair.getPublic());
|
||||||
|
|
||||||
|
context = builder.classicPairMessage().algorithm("Xdh").spec(XdhSpec.X25519)
|
||||||
|
.keyPair(new KeyPairKey(pairMessageKeyPair)).pqcKem().algorithm("ML-KEM")
|
||||||
|
.peerPublic(bobPqc.getPublic()).buildInitiator();
|
||||||
|
|
||||||
|
byte[] peerMessage = context.getPeerMessage();
|
||||||
|
System.out.println("...peerMessage(" + lens(peerMessage) + ")=" + hex(peerMessage));
|
||||||
|
|
||||||
|
assertNotNull(context);
|
||||||
|
assertNotNull(peerMessage);
|
||||||
|
} finally {
|
||||||
|
closeQuietly(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("switchingClassicModeClearsConflictingStateAndBuildsPairMessage...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void transcriptChangesDerivedSecret() throws Exception {
|
||||||
|
System.out.println("transcriptChangesDerivedSecret");
|
||||||
|
|
||||||
|
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
|
||||||
|
|
||||||
|
KeyPair aliceClassicA = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobClassicA = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobPqcA = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||||
|
|
||||||
|
KeyPair aliceClassicB = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobClassicB = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||||
|
KeyPair bobPqcB = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||||
|
|
||||||
|
HybridKexTranscript transcriptA = new HybridKexTranscript().addUtf8("context", "A");
|
||||||
|
HybridKexTranscript transcriptB = new HybridKexTranscript().addUtf8("context", "B");
|
||||||
|
|
||||||
|
byte[] secretA;
|
||||||
|
byte[] secretB;
|
||||||
|
|
||||||
|
HybridKexContext aliceA = null;
|
||||||
|
HybridKexContext bobA = null;
|
||||||
|
HybridKexContext aliceB = null;
|
||||||
|
HybridKexContext bobB = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
aliceA = HybridKexBuilder.builder().profile(profile).transcript(transcriptA).classicAgreement()
|
||||||
|
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassicA.getPrivate())
|
||||||
|
.peerPublic(bobClassicA.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqcA.getPublic())
|
||||||
|
.buildInitiator();
|
||||||
|
|
||||||
|
bobA = HybridKexBuilder.builder().profile(profile).transcript(transcriptA).classicAgreement()
|
||||||
|
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassicA.getPrivate())
|
||||||
|
.peerPublic(aliceClassicA.getPublic()).pqcKem().algorithm("ML-KEM").privateKey(bobPqcA.getPrivate())
|
||||||
|
.buildResponder();
|
||||||
|
|
||||||
|
bobA.setPeerMessage(aliceA.getPeerMessage());
|
||||||
|
secretA = aliceA.deriveSecret();
|
||||||
|
byte[] responderA = bobA.deriveSecret();
|
||||||
|
System.out.println("...secretA=" + hex(secretA));
|
||||||
|
System.out.println("...responderA=" + hex(responderA));
|
||||||
|
assertArrayEquals(secretA, responderA);
|
||||||
|
|
||||||
|
aliceB = HybridKexBuilder.builder().profile(profile).transcript(transcriptB).classicAgreement()
|
||||||
|
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassicB.getPrivate())
|
||||||
|
.peerPublic(bobClassicB.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqcB.getPublic())
|
||||||
|
.buildInitiator();
|
||||||
|
|
||||||
|
bobB = HybridKexBuilder.builder().profile(profile).transcript(transcriptB).classicAgreement()
|
||||||
|
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassicB.getPrivate())
|
||||||
|
.peerPublic(aliceClassicB.getPublic()).pqcKem().algorithm("ML-KEM").privateKey(bobPqcB.getPrivate())
|
||||||
|
.buildResponder();
|
||||||
|
|
||||||
|
bobB.setPeerMessage(aliceB.getPeerMessage());
|
||||||
|
secretB = aliceB.deriveSecret();
|
||||||
|
byte[] responderB = bobB.deriveSecret();
|
||||||
|
System.out.println("...secretB=" + hex(secretB));
|
||||||
|
System.out.println("...responderB=" + hex(responderB));
|
||||||
|
assertArrayEquals(secretB, responderB);
|
||||||
|
|
||||||
|
if (Arrays.equals(secretA, secretB)) {
|
||||||
|
throw new AssertionError("Transcript-bound secrets should differ for different transcripts");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
closeQuietly(aliceA);
|
||||||
|
closeQuietly(bobA);
|
||||||
|
closeQuietly(aliceB);
|
||||||
|
closeQuietly(bobB);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("transcriptChangesDerivedSecret...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void closeQuietly(HybridKexContext context) {
|
||||||
|
if (context == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
context.close();
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// Cleanup only.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String hex(byte[] value) {
|
||||||
|
if (value == null) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
StringBuilder builder = new StringBuilder(value.length * 2);
|
||||||
|
for (int index = 0; index < value.length; index++) {
|
||||||
|
if (builder.length() >= 30) {
|
||||||
|
builder.append("...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int current = value[index] & 0xFF;
|
||||||
|
if (current < 16) {
|
||||||
|
builder.append('0');
|
||||||
|
}
|
||||||
|
builder.append(Integer.toHexString(current));
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String lens(byte[] message) {
|
||||||
|
if (message == null || message.length < 8) {
|
||||||
|
return "classicLen=?, pqcLen=?";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DataInputStream input = new DataInputStream(new ByteArrayInputStream(message));
|
||||||
|
int classicLength = input.readInt();
|
||||||
|
int pqcLength = 0;
|
||||||
|
if (message.length >= 8 + Math.max(0, classicLength)) {
|
||||||
|
if (classicLength > 0) {
|
||||||
|
input.skipBytes(classicLength);
|
||||||
|
}
|
||||||
|
pqcLength = input.readInt();
|
||||||
|
}
|
||||||
|
return "classicLen=" + classicLength + ", pqcLen=" + pqcLength;
|
||||||
|
} catch (Exception exception) {
|
||||||
|
return "classicLen=?, pqcLen=?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,13 @@
|
|||||||
<attribute name="test" value="true"/>
|
<attribute name="test" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</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="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21/"/>
|
<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="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||||
<classpathentry kind="output" path="bin/default"/>
|
<classpathentry kind="output" path="bin/default"/>
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ import java.nio.file.Path;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.api.PkiId;
|
import zeroecho.pki.api.PkiId;
|
||||||
import zeroecho.pki.api.audit.Principal;
|
import zeroecho.pki.api.audit.Principal;
|
||||||
@@ -71,6 +73,7 @@ import zeroecho.pki.util.async.impl.DurableAsyncBus;
|
|||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public final class FileBackedAsyncBusProvider implements AsyncBusProvider {
|
public final class FileBackedAsyncBusProvider implements AsyncBusProvider {
|
||||||
|
private static final Logger LOG = Logger.getLogger(FileBackedAsyncBusProvider.class.getName());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration key for the log file path.
|
* Configuration key for the log file path.
|
||||||
@@ -87,13 +90,29 @@ public final class FileBackedAsyncBusProvider implements AsyncBusProvider {
|
|||||||
return Set.of(KEY_LOG_PATH);
|
return Set.of(KEY_LOG_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the durable file-backed async bus provider.
|
||||||
|
*
|
||||||
|
* @param config provider configuration (never {@code null})
|
||||||
|
* @throws IllegalArgumentException if the configuration is invalid
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void validateConfig(final ProviderConfig config) {
|
||||||
|
AsyncBusProvider.super.validateConfig(config);
|
||||||
|
Map<String, String> props = config.properties();
|
||||||
|
String logPath = props.getOrDefault(KEY_LOG_PATH, Path.of("pki-async").resolve("async.log").toString());
|
||||||
|
Path.of(logPath);
|
||||||
|
for (String key : props.keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown async bus configuration key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AsyncBus<PkiId, Principal, String, Object> allocate(ProviderConfig config) {
|
public AsyncBus<PkiId, Principal, String, Object> allocate(ProviderConfig config) {
|
||||||
Objects.requireNonNull(config, "config");
|
Objects.requireNonNull(config, "config");
|
||||||
|
validateConfig(config);
|
||||||
if (!id().equals(config.backendId())) {
|
|
||||||
throw new IllegalArgumentException("ProviderConfig backendId mismatch.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> props = config.properties();
|
Map<String, String> props = config.properties();
|
||||||
String logPath = props.getOrDefault(KEY_LOG_PATH, Path.of("pki-async").resolve("async.log").toString());
|
String logPath = props.getOrDefault(KEY_LOG_PATH, Path.of("pki-async").resolve("async.log").toString());
|
||||||
|
|||||||
@@ -36,15 +36,26 @@ package zeroecho.pki.impl.audit;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
import zeroecho.pki.spi.audit.AuditSink;
|
import zeroecho.pki.spi.audit.AuditSink;
|
||||||
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* {@link AuditSinkProvider} for the file-backed audit sink.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Supported configuration keys:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code root} - audit storage root directory (required)</li>
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class FileAuditSinkProvider implements AuditSinkProvider {
|
public class FileAuditSinkProvider implements AuditSinkProvider {
|
||||||
|
private static final Logger LOG = Logger.getLogger(FileAuditSinkProvider.class.getName());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String id() {
|
public String id() {
|
||||||
return "file";
|
return "file";
|
||||||
@@ -55,9 +66,29 @@ public class FileAuditSinkProvider implements AuditSinkProvider {
|
|||||||
return Set.of("root");
|
return Set.of("root");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the file-backed audit sink provider.
|
||||||
|
*
|
||||||
|
* @param config provider configuration (never {@code null})
|
||||||
|
* @throws IllegalArgumentException if the configuration is incomplete or
|
||||||
|
* invalid
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void validateConfig(final ProviderConfig config) {
|
||||||
|
AuditSinkProvider.super.validateConfig(config);
|
||||||
|
String rootString = config.require("root");
|
||||||
|
Path.of(rootString);
|
||||||
|
for (String key : config.properties().keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown audit sink configuration key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuditSink allocate(ProviderConfig config) {
|
public AuditSink allocate(ProviderConfig config) {
|
||||||
Objects.requireNonNull(config, "config");
|
Objects.requireNonNull(config, "config");
|
||||||
|
validateConfig(config);
|
||||||
|
|
||||||
String rootString = config.require("root");
|
String rootString = config.require("root");
|
||||||
Path root = Path.of(rootString);
|
Path root = Path.of(rootString);
|
||||||
|
|||||||
@@ -35,15 +35,27 @@ package zeroecho.pki.impl.audit;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
import zeroecho.pki.spi.audit.AuditSink;
|
import zeroecho.pki.spi.audit.AuditSink;
|
||||||
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* {@link AuditSinkProvider} for the in-memory audit sink.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Supported configuration keys:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code size} - maximum retained event count (optional, positive
|
||||||
|
* integer)</li>
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class InMemoryAuditSinkProvider implements AuditSinkProvider {
|
public class InMemoryAuditSinkProvider implements AuditSinkProvider {
|
||||||
|
private static final Logger LOG = Logger.getLogger(InMemoryAuditSinkProvider.class.getName());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String id() {
|
public String id() {
|
||||||
return "memory";
|
return "memory";
|
||||||
@@ -54,25 +66,54 @@ public class InMemoryAuditSinkProvider implements AuditSinkProvider {
|
|||||||
return Set.of("size");
|
return Set.of("size");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the in-memory audit sink provider.
|
||||||
|
*
|
||||||
|
* @param config provider configuration (never {@code null})
|
||||||
|
* @throws IllegalArgumentException if the configuration contains an invalid
|
||||||
|
* {@code size} value
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public AuditSink allocate(ProviderConfig config) {
|
public void validateConfig(final ProviderConfig config) {
|
||||||
|
AuditSinkProvider.super.validateConfig(config);
|
||||||
Optional<String> sizeStr = config.get("size");
|
Optional<String> sizeStr = config.get("size");
|
||||||
if (sizeStr.isPresent()) {
|
if (sizeStr.isPresent()) {
|
||||||
return new InMemoryAuditSink(parseIntOrDefault(sizeStr.get(), InMemoryAuditSink.DEFAULT_MAX_EVENTS));
|
int size = parseInt(sizeStr.get(), "size");
|
||||||
|
if (size <= 0) {
|
||||||
|
throw new IllegalArgumentException("Configuration key 'size' must be positive.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String key : config.properties().keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown audit sink configuration key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuditSink allocate(ProviderConfig config) {
|
||||||
|
validateConfig(config);
|
||||||
|
Optional<String> sizeStr = config.get("size");
|
||||||
|
if (sizeStr.isPresent()) {
|
||||||
|
return new InMemoryAuditSink(parseInt(sizeStr.get(), "size"));
|
||||||
} else {
|
} else {
|
||||||
return new InMemoryAuditSink();
|
return new InMemoryAuditSink();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int parseIntOrDefault(String value, int defaultValue) {
|
/**
|
||||||
if (value == null) {
|
* Parses an integer configuration value.
|
||||||
return defaultValue;
|
*
|
||||||
}
|
* @param value raw configuration value
|
||||||
|
* @param keyName configuration key name
|
||||||
|
* @return parsed integer value
|
||||||
|
* @throws IllegalArgumentException if the value is not a valid integer
|
||||||
|
*/
|
||||||
|
private static int parseInt(final String value, final String keyName) {
|
||||||
try {
|
try {
|
||||||
return Integer.parseInt(value);
|
return Integer.parseInt(value);
|
||||||
} catch (NumberFormatException ex) {
|
} catch (NumberFormatException ex) {
|
||||||
return defaultValue;
|
throw new IllegalArgumentException("Configuration key '" + keyName + "' must be an integer.", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,15 +35,23 @@ package zeroecho.pki.impl.audit;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
import zeroecho.pki.spi.audit.AuditSink;
|
import zeroecho.pki.spi.audit.AuditSink;
|
||||||
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* {@link AuditSinkProvider} for the standard-output audit sink.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This provider accepts no provider-specific configuration keys.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class StdoutAuditSinkProvider implements AuditSinkProvider {
|
public class StdoutAuditSinkProvider implements AuditSinkProvider {
|
||||||
|
private static final Logger LOG = Logger.getLogger(StdoutAuditSinkProvider.class.getName());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String id() {
|
public String id() {
|
||||||
return "stdout";
|
return "stdout";
|
||||||
@@ -54,8 +62,24 @@ public class StdoutAuditSinkProvider implements AuditSinkProvider {
|
|||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the standard-output audit sink provider.
|
||||||
|
*
|
||||||
|
* @param config provider configuration (never {@code null})
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void validateConfig(final ProviderConfig config) {
|
||||||
|
AuditSinkProvider.super.validateConfig(config);
|
||||||
|
for (String key : config.properties().keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown audit sink configuration key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuditSink allocate(ProviderConfig config) {
|
public AuditSink allocate(ProviderConfig config) {
|
||||||
|
validateConfig(config);
|
||||||
return new StdoutAuditSink();
|
return new StdoutAuditSink();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ package zeroecho.pki.impl.crypto.zeroecholib;
|
|||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
import zeroecho.pki.spi.crypto.SignatureWorkflow;
|
import zeroecho.pki.spi.crypto.SignatureWorkflow;
|
||||||
@@ -61,6 +63,7 @@ import zeroecho.pki.spi.crypto.SignatureWorkflowProvider;
|
|||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWorkflowProvider {
|
public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWorkflowProvider {
|
||||||
|
private static final Logger LOG = Logger.getLogger(ZeroEchoLibSignatureWorkflowProvider.class.getName());
|
||||||
|
|
||||||
private static final String KEY_KEYRING_PATH = "keyringPath";
|
private static final String KEY_KEYRING_PATH = "keyringPath";
|
||||||
private static final String KEY_KEYREF_PREFIX = "keyRefPrefix";
|
private static final String KEY_KEYREF_PREFIX = "keyRefPrefix";
|
||||||
@@ -76,17 +79,39 @@ public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWork
|
|||||||
return Set.of(KEY_KEYRING_PATH, KEY_KEYREF_PREFIX, KEY_REQUIRE_SUFFIX);
|
return Set.of(KEY_KEYRING_PATH, KEY_KEYREF_PREFIX, KEY_REQUIRE_SUFFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the ZeroEcho-lib signature workflow provider.
|
||||||
|
*
|
||||||
|
* @param config provider configuration (never {@code null})
|
||||||
|
* @throws IllegalArgumentException if the configuration is incomplete or
|
||||||
|
* invalid
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public SignatureWorkflow allocate(ProviderConfig config) {
|
public void validateConfig(final ProviderConfig config) {
|
||||||
if (config == null) {
|
SignatureWorkflowProvider.super.validateConfig(config);
|
||||||
throw new IllegalArgumentException("config must not be null");
|
String keyringPath = config.require(KEY_KEYRING_PATH);
|
||||||
}
|
Path.of(keyringPath);
|
||||||
|
config.get(KEY_KEYREF_PREFIX).ifPresent(value -> {
|
||||||
// Defensive hardening: fail fast if miswired config reaches this provider.
|
if (value.isBlank()) {
|
||||||
if (!id().equals(config.backendId())) {
|
throw new IllegalArgumentException("Configuration key '" + KEY_KEYREF_PREFIX + "' must not be blank.");
|
||||||
throw new IllegalArgumentException("ProviderConfig backendId mismatch.");
|
}
|
||||||
|
});
|
||||||
|
config.get(KEY_REQUIRE_SUFFIX).ifPresent(value -> {
|
||||||
|
if (!("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value))) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Configuration key '" + KEY_REQUIRE_SUFFIX + "' must be 'true' or 'false'.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (String key : config.properties().keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown signature workflow configuration key: " + key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SignatureWorkflow allocate(final ProviderConfig config) {
|
||||||
|
validateConfig(config);
|
||||||
String keyringPath = config.require(KEY_KEYRING_PATH);
|
String keyringPath = config.require(KEY_KEYRING_PATH);
|
||||||
String prefix = config.get(KEY_KEYREF_PREFIX).orElse("zeroecho-lib:");
|
String prefix = config.get(KEY_KEYREF_PREFIX).orElse("zeroecho-lib:");
|
||||||
boolean requireSuffix = config.get(KEY_REQUIRE_SUFFIX).map(Boolean::parseBoolean).orElse(Boolean.TRUE);
|
boolean requireSuffix = config.get(KEY_REQUIRE_SUFFIX).map(Boolean::parseBoolean).orElse(Boolean.TRUE);
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
package zeroecho.pki.impl.framework.x509.bc;
|
package zeroecho.pki.impl.framework.x509.bc;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
import zeroecho.pki.spi.framework.CredentialFramework;
|
import zeroecho.pki.spi.framework.CredentialFramework;
|
||||||
@@ -73,7 +75,9 @@ import zeroecho.pki.spi.framework.CredentialFrameworkProvider;
|
|||||||
* The current implementation does not consume any provider-specific
|
* The current implementation does not consume any provider-specific
|
||||||
* configuration keys beyond backend selection through
|
* configuration keys beyond backend selection through
|
||||||
* {@link ProviderConfig#backendId()}. Consequently, {@link #supportedKeys()}
|
* {@link ProviderConfig#backendId()}. Consequently, {@link #supportedKeys()}
|
||||||
* returns an empty set.
|
* returns an empty set and bootstrap will reject any
|
||||||
|
* {@code zeroecho.pki.framework.*} configuration keys other than the backend
|
||||||
|
* selector itself.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Thread-safety</h2>
|
* <h2>Thread-safety</h2>
|
||||||
@@ -83,6 +87,8 @@ import zeroecho.pki.spi.framework.CredentialFrameworkProvider;
|
|||||||
*/
|
*/
|
||||||
public final class BcX509CredentialFrameworkProvider implements CredentialFrameworkProvider {
|
public final class BcX509CredentialFrameworkProvider implements CredentialFrameworkProvider {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(BcX509CredentialFrameworkProvider.class.getName());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the stable provider identifier used to select this X.509 framework
|
* Returns the stable provider identifier used to select this X.509 framework
|
||||||
* backend.
|
* backend.
|
||||||
@@ -110,6 +116,32 @@ public final class BcX509CredentialFrameworkProvider implements CredentialFramew
|
|||||||
return Set.of();
|
return Set.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the Bouncy Castle backed X.509 credential
|
||||||
|
* framework provider.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This provider currently defines no provider-specific keys. Unknown keys are
|
||||||
|
* ignored for forward compatibility and are reported only by name through JUL
|
||||||
|
* warning output. The backend id must match {@link #id()}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param config provider configuration used to select this backend; must not be
|
||||||
|
* {@code null}
|
||||||
|
* @throws IllegalArgumentException if the backend id does not match this
|
||||||
|
* provider identifier
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void validateConfig(final ProviderConfig config) {
|
||||||
|
CredentialFrameworkProvider.super.validateConfig(config);
|
||||||
|
|
||||||
|
for (String key : config.properties().keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown credential framework configuration key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates a new Bouncy Castle backed X.509 credential framework instance.
|
* Allocates a new Bouncy Castle backed X.509 credential framework instance.
|
||||||
*
|
*
|
||||||
@@ -129,12 +161,7 @@ public final class BcX509CredentialFrameworkProvider implements CredentialFramew
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public CredentialFramework allocate(ProviderConfig config) {
|
public CredentialFramework allocate(ProviderConfig config) {
|
||||||
if (config == null) {
|
validateConfig(config);
|
||||||
throw new IllegalArgumentException("config must not be null");
|
|
||||||
}
|
|
||||||
if (!id().equals(config.backendId())) {
|
|
||||||
throw new IllegalArgumentException("ProviderConfig backendId mismatch");
|
|
||||||
}
|
|
||||||
return new BcX509CredentialFramework();
|
return new BcX509CredentialFramework();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,9 @@
|
|||||||
* issuance and status-object generation remain intentionally unsupported until
|
* issuance and status-object generation remain intentionally unsupported until
|
||||||
* concrete backend components are supplied. This allows bootstrap and runtime
|
* concrete backend components are supplied. This allows bootstrap and runtime
|
||||||
* composition to separate framework selection from full cryptographic and PKI
|
* composition to separate framework selection from full cryptographic and PKI
|
||||||
* service wiring.
|
* service wiring. The package is exposed to runtime composition only through
|
||||||
|
* the framework SPI and its ServiceLoader provider; bootstrap code must not
|
||||||
|
* depend on these implementation classes directly.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Security considerations</h2>
|
* <h2>Security considerations</h2>
|
||||||
|
|||||||
@@ -36,14 +36,15 @@ package zeroecho.pki.impl.fs;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
|
||||||
import zeroecho.pki.spi.store.PkiStore;
|
import zeroecho.pki.spi.store.PkiStore;
|
||||||
import zeroecho.pki.spi.store.PkiStoreProvider;
|
import zeroecho.pki.spi.store.PkiStoreProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link AuditSinkProvider} for the filesystem-backed {@link PkiStore}.
|
* {@link PkiStoreProvider} for the filesystem-backed {@link PkiStore}.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Supported configuration keys:
|
* Supported configuration keys:
|
||||||
@@ -53,6 +54,7 @@ import zeroecho.pki.spi.store.PkiStoreProvider;
|
|||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public final class FilesystemPkiStoreProvider implements PkiStoreProvider {
|
public final class FilesystemPkiStoreProvider implements PkiStoreProvider {
|
||||||
|
private static final Logger LOG = Logger.getLogger(FilesystemPkiStoreProvider.class.getName());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public no-arg constructor required by {@link java.util.ServiceLoader}.
|
* Public no-arg constructor required by {@link java.util.ServiceLoader}.
|
||||||
@@ -71,9 +73,41 @@ public final class FilesystemPkiStoreProvider implements PkiStoreProvider {
|
|||||||
return Set.of("root");
|
return Set.of("root");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates configuration for the filesystem-backed PKI store provider.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Required configuration:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code root}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Unknown keys are ignored for forward compatibility and are reported by key
|
||||||
|
* name only.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param config provider configuration (never {@code null})
|
||||||
|
* @throws IllegalArgumentException if the configuration is incomplete or
|
||||||
|
* invalid
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public PkiStore allocate(ProviderConfig config) {
|
public void validateConfig(final ProviderConfig config) {
|
||||||
|
PkiStoreProvider.super.validateConfig(config);
|
||||||
|
String rootString = config.require("root");
|
||||||
|
Path.of(rootString);
|
||||||
|
for (String key : config.properties().keySet()) {
|
||||||
|
if (!supportedKeys().contains(key) && LOG.isLoggable(Level.WARNING)) {
|
||||||
|
LOG.warning("Ignoring unknown PKI store configuration key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PkiStore allocate(final ProviderConfig config) {
|
||||||
Objects.requireNonNull(config, "config");
|
Objects.requireNonNull(config, "config");
|
||||||
|
validateConfig(config);
|
||||||
|
|
||||||
String rootString = config.require("root");
|
String rootString = config.require("root");
|
||||||
Path root = Path.of(rootString);
|
Path root = Path.of(rootString);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.pki.spi;
|
package zeroecho.pki.spi;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,8 +60,10 @@ import java.util.Set;
|
|||||||
* Configuration is provided as string properties through
|
* Configuration is provided as string properties through
|
||||||
* {@link ProviderConfig}. Providers must validate required keys and reject
|
* {@link ProviderConfig}. Providers must validate required keys and reject
|
||||||
* invalid configurations with {@link IllegalArgumentException}. Unknown keys
|
* invalid configurations with {@link IllegalArgumentException}. Unknown keys
|
||||||
* must be ignored for forward compatibility. {@link #supportedKeys()} is
|
* must be ignored for forward compatibility, but may be reported through
|
||||||
* informational and may be used for diagnostics or help output.
|
* provider-local diagnostic logging that names keys only.
|
||||||
|
* {@link #supportedKeys()} is informational and may be used for diagnostics or
|
||||||
|
* help output.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Security</h2>
|
* <h2>Security</h2>
|
||||||
@@ -94,6 +97,37 @@ public interface ConfigurableProvider<T> {
|
|||||||
*/
|
*/
|
||||||
Set<String> supportedKeys();
|
Set<String> supportedKeys();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the provided configuration before allocation.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Implementations should use this hook to enforce backend-specific invariants
|
||||||
|
* such as required keys and key formats. The default implementation already
|
||||||
|
* verifies that {@link ProviderConfig#backendId()} matches {@link #id()}.
|
||||||
|
* Unknown keys should normally be ignored for forward compatibility, but may be
|
||||||
|
* reported through provider-local logging that names keys only and never logs
|
||||||
|
* values.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The default implementation performs a null check and verifies that the
|
||||||
|
* supplied backend id matches {@link #id()}. Providers with additional
|
||||||
|
* requirements should override this method, invoke the default implementation,
|
||||||
|
* and then validate their own keys.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param config configuration to validate (never {@code null})
|
||||||
|
* @throws NullPointerException if {@code config} is {@code null}
|
||||||
|
* @throws IllegalArgumentException if the configuration is invalid
|
||||||
|
*/
|
||||||
|
default void validateConfig(final ProviderConfig config) {
|
||||||
|
Objects.requireNonNull(config, "config");
|
||||||
|
if (!id().equals(config.backendId())) {
|
||||||
|
throw new IllegalArgumentException("ProviderConfig backendId mismatch.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates a new instance using the provided configuration.
|
* Allocates a new instance using the provided configuration.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ import zeroecho.pki.spi.audit.AuditSink;
|
|||||||
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
import zeroecho.pki.spi.audit.AuditSinkProvider;
|
||||||
import zeroecho.pki.spi.crypto.SignatureWorkflow;
|
import zeroecho.pki.spi.crypto.SignatureWorkflow;
|
||||||
import zeroecho.pki.spi.crypto.SignatureWorkflowProvider;
|
import zeroecho.pki.spi.crypto.SignatureWorkflowProvider;
|
||||||
|
import zeroecho.pki.spi.framework.CredentialFramework;
|
||||||
|
import zeroecho.pki.spi.framework.CredentialFrameworkProvider;
|
||||||
import zeroecho.pki.spi.store.PkiStore;
|
import zeroecho.pki.spi.store.PkiStore;
|
||||||
import zeroecho.pki.spi.store.PkiStoreProvider;
|
import zeroecho.pki.spi.store.PkiStoreProvider;
|
||||||
import zeroecho.pki.util.async.AsyncBus;
|
import zeroecho.pki.util.async.AsyncBus;
|
||||||
@@ -58,7 +60,7 @@ import zeroecho.pki.util.async.AsyncBus;
|
|||||||
* <p>
|
* <p>
|
||||||
* This class provides deterministic selection and instantiation rules for
|
* This class provides deterministic selection and instantiation rules for
|
||||||
* components discovered via {@link java.util.ServiceLoader}. It is designed to
|
* components discovered via {@link java.util.ServiceLoader}. It is designed to
|
||||||
* scale as more SPIs are introduced (audit, publish, framework integrations,
|
* scale as more SPIs are introduced (audit, publish, credential frameworks,
|
||||||
* crypto/workflows, async orchestration, etc.).
|
* crypto/workflows, async orchestration, etc.).
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
@@ -77,6 +79,10 @@ import zeroecho.pki.util.async.AsyncBus;
|
|||||||
* <li>Select async bus provider: {@code -Dzeroecho.pki.async=<id>}</li>
|
* <li>Select async bus provider: {@code -Dzeroecho.pki.async=<id>}</li>
|
||||||
* <li>Configure async bus provider:
|
* <li>Configure async bus provider:
|
||||||
* {@code -Dzeroecho.pki.async.<key>=<value>}</li>
|
* {@code -Dzeroecho.pki.async.<key>=<value>}</li>
|
||||||
|
* <li>Select credential framework provider:
|
||||||
|
* {@code -Dzeroecho.pki.framework=<id>}</li>
|
||||||
|
* <li>Configure credential framework provider:
|
||||||
|
* {@code -Dzeroecho.pki.framework.<key>=<value>}</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
@@ -100,6 +106,9 @@ public final class PkiBootstrap {
|
|||||||
private static final String PROP_ASYNC_BACKEND = "zeroecho.pki.async";
|
private static final String PROP_ASYNC_BACKEND = "zeroecho.pki.async";
|
||||||
private static final String PROP_ASYNC_PREFIX = "zeroecho.pki.async.";
|
private static final String PROP_ASYNC_PREFIX = "zeroecho.pki.async.";
|
||||||
|
|
||||||
|
private static final String PROP_FRAMEWORK_BACKEND = "zeroecho.pki.framework";
|
||||||
|
private static final String PROP_FRAMEWORK_PREFIX = "zeroecho.pki.framework.";
|
||||||
|
|
||||||
private PkiBootstrap() {
|
private PkiBootstrap() {
|
||||||
throw new AssertionError("No instances.");
|
throw new AssertionError("No instances.");
|
||||||
}
|
}
|
||||||
@@ -264,6 +273,54 @@ public final class PkiBootstrap {
|
|||||||
return provider.allocate(config);
|
return provider.allocate(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a {@link CredentialFramework} using {@link CredentialFrameworkProvider}
|
||||||
|
* discovered via ServiceLoader.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Provider selection is deterministic and fail-fast:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>If {@code -Dzeroecho.pki.framework=<id>} is specified, the provider
|
||||||
|
* with the matching id is selected.</li>
|
||||||
|
* <li>If no id is specified and exactly one provider exists, that provider is
|
||||||
|
* selected.</li>
|
||||||
|
* <li>If multiple providers exist and no id is specified, bootstrap fails as
|
||||||
|
* configuration is ambiguous.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Configuration properties are read from {@link System#getProperties()} using
|
||||||
|
* the prefix {@code zeroecho.pki.framework.}. Values are treated as sensitive
|
||||||
|
* and are never logged; only keys may be logged.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return credential framework (never {@code null})
|
||||||
|
* @throws IllegalArgumentException if unsupported configuration keys are
|
||||||
|
* provided
|
||||||
|
* @throws IllegalStateException if provider selection fails
|
||||||
|
*/
|
||||||
|
public static CredentialFramework openCredentialFramework() {
|
||||||
|
String requestedId = System.getProperty(PROP_FRAMEWORK_BACKEND);
|
||||||
|
|
||||||
|
CredentialFrameworkProvider provider = SpiSelector.select(CredentialFrameworkProvider.class, requestedId,
|
||||||
|
new SpiSelector.ProviderId<>() {
|
||||||
|
@Override
|
||||||
|
public String id(CredentialFrameworkProvider p) {
|
||||||
|
return p.id();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, String> props = SpiSystemProperties.readPrefixed(PROP_FRAMEWORK_PREFIX);
|
||||||
|
ProviderConfig config = new ProviderConfig(provider.id(), props);
|
||||||
|
|
||||||
|
if (LOG.isLoggable(Level.INFO)) {
|
||||||
|
LOG.info("Selected credential framework provider: " + provider.id() + " (keys: " + props.keySet() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.allocate(config);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs provider help information (supported keys) for diagnostics.
|
* Logs provider help information (supported keys) for diagnostics.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -36,8 +36,8 @@
|
|||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This package centralizes deterministic provider selection and a minimal
|
* This package centralizes deterministic provider selection and a minimal
|
||||||
* configuration convention for multiple SPIs (store, audit, publication, and
|
* configuration convention for multiple SPIs (store, audit, publication,
|
||||||
* future components).
|
* credential frameworks, and future components).
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -33,8 +33,6 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package zeroecho.pki.spi.framework;
|
package zeroecho.pki.spi.framework;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import zeroecho.pki.spi.ConfigurableProvider;
|
import zeroecho.pki.spi.ConfigurableProvider;
|
||||||
import zeroecho.pki.spi.ProviderConfig;
|
import zeroecho.pki.spi.ProviderConfig;
|
||||||
|
|
||||||
@@ -44,6 +42,7 @@ import zeroecho.pki.spi.ProviderConfig;
|
|||||||
* <p>
|
* <p>
|
||||||
* The PKI runtime selects a framework provider using
|
* The PKI runtime selects a framework provider using
|
||||||
* {@link java.util.ServiceLoader} and instantiates it through
|
* {@link java.util.ServiceLoader} and instantiates it through
|
||||||
|
* {@link #validateConfig(ProviderConfig)} and
|
||||||
* {@link #allocate(ProviderConfig)}. Provider selection is performed by
|
* {@link #allocate(ProviderConfig)}. Provider selection is performed by
|
||||||
* {@code PkiBootstrap} using configuration properties (similarly to store,
|
* {@code PkiBootstrap} using configuration properties (similarly to store,
|
||||||
* audit, async bus, and signature workflow providers).
|
* audit, async bus, and signature workflow providers).
|
||||||
@@ -65,7 +64,9 @@ public interface CredentialFrameworkProvider extends ConfigurableProvider<Creden
|
|||||||
* <p>
|
* <p>
|
||||||
* Implementations must validate that {@link ProviderConfig#backendId()} matches
|
* Implementations must validate that {@link ProviderConfig#backendId()} matches
|
||||||
* {@link #id()}. A mismatch must be reported as
|
* {@link #id()}. A mismatch must be reported as
|
||||||
* {@link IllegalArgumentException}.
|
* {@link IllegalArgumentException}. Implementations should invoke
|
||||||
|
* {@link #validateConfig(ProviderConfig)} from this method so that the same
|
||||||
|
* enforcement applies even when a provider is used outside bootstrap.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param config provider configuration (never {@code null})
|
* @param config provider configuration (never {@code null})
|
||||||
@@ -78,24 +79,4 @@ public interface CredentialFrameworkProvider extends ConfigurableProvider<Creden
|
|||||||
@Override
|
@Override
|
||||||
CredentialFramework allocate(ProviderConfig config);
|
CredentialFramework allocate(ProviderConfig config);
|
||||||
|
|
||||||
/**
|
|
||||||
* Enforces that the provided configuration is intended for this provider.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* This helper is intended for defensive checks inside provider implementations.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param provider provider instance (never {@code null})
|
|
||||||
* @param config provider configuration (never {@code null})
|
|
||||||
* @throws NullPointerException if any argument is {@code null}
|
|
||||||
* @throws IllegalArgumentException if the backend id does not match
|
|
||||||
*/
|
|
||||||
static void requireIdMatch(final CredentialFrameworkProvider provider, final ProviderConfig config) {
|
|
||||||
Objects.requireNonNull(provider, "provider");
|
|
||||||
Objects.requireNonNull(config, "config");
|
|
||||||
|
|
||||||
if (!provider.id().equals(config.backendId())) {
|
|
||||||
throw new IllegalArgumentException("ProviderConfig backendId mismatch.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,5 +40,13 @@
|
|||||||
* framework-specific fields. Framework-specific types must not leak into the
|
* framework-specific fields. Framework-specific types must not leak into the
|
||||||
* public API.
|
* public API.
|
||||||
* </p>
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Runtime selection and allocation of concrete framework implementations is
|
||||||
|
* performed through {@link java.util.ServiceLoader} and the
|
||||||
|
* {@code CredentialFrameworkProvider} SPI. Bootstrap configuration follows the
|
||||||
|
* same deterministic, fail-fast conventions as the other PKI SPIs.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
package zeroecho.pki.spi.framework;
|
package zeroecho.pki.spi.framework;
|
||||||
|
|||||||
@@ -36,9 +36,9 @@
|
|||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* SPIs are used to plug in persistence backends, audit sinks, publishing
|
* SPIs are used to plug in persistence backends, audit sinks, publishing
|
||||||
* targets, and framework integrations while keeping the public PKI API
|
* targets, credential frameworks, and other integrations while keeping the
|
||||||
* framework-agnostic. Implementations are typically discovered via
|
* public PKI API framework-agnostic. Implementations are typically discovered
|
||||||
* {@link java.util.ServiceLoader}.
|
* via {@link java.util.ServiceLoader}.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
zeroecho.pki.impl.framework.x509.bc.BcX509CredentialFrameworkProvider
|
||||||
@@ -35,6 +35,7 @@ package zeroecho.pki.spi.bootstrap;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
@@ -44,8 +45,13 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import zeroecho.pki.api.PkiId;
|
||||||
|
import zeroecho.pki.api.audit.Principal;
|
||||||
import zeroecho.pki.spi.audit.AuditSink;
|
import zeroecho.pki.spi.audit.AuditSink;
|
||||||
|
import zeroecho.pki.spi.crypto.SignatureWorkflow;
|
||||||
|
import zeroecho.pki.spi.framework.CredentialFramework;
|
||||||
import zeroecho.pki.spi.store.PkiStore;
|
import zeroecho.pki.spi.store.PkiStore;
|
||||||
|
import zeroecho.pki.util.async.AsyncBus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JUnit 5 tests for {@link PkiBootstrap}.
|
* JUnit 5 tests for {@link PkiBootstrap}.
|
||||||
@@ -75,9 +81,15 @@ public final class PkiBootstrapTest {
|
|||||||
|
|
||||||
clearPrefix("zeroecho.pki.store.");
|
clearPrefix("zeroecho.pki.store.");
|
||||||
clearPrefix("zeroecho.pki.audit.");
|
clearPrefix("zeroecho.pki.audit.");
|
||||||
|
clearPrefix("zeroecho.pki.framework.");
|
||||||
|
clearPrefix("zeroecho.pki.async.");
|
||||||
|
clearPrefix("zeroecho.pki.crypto.workflow.");
|
||||||
|
|
||||||
System.clearProperty("zeroecho.pki.store");
|
System.clearProperty("zeroecho.pki.store");
|
||||||
System.clearProperty("zeroecho.pki.audit");
|
System.clearProperty("zeroecho.pki.audit");
|
||||||
|
System.clearProperty("zeroecho.pki.framework");
|
||||||
|
System.clearProperty("zeroecho.pki.async");
|
||||||
|
System.clearProperty("zeroecho.pki.crypto.workflow");
|
||||||
|
|
||||||
System.out.println("...tempDir=" + this.tempDir);
|
System.out.println("...tempDir=" + this.tempDir);
|
||||||
System.out.println("...ok");
|
System.out.println("...ok");
|
||||||
@@ -162,6 +174,125 @@ public final class PkiBootstrapTest {
|
|||||||
System.out.println("...ok");
|
System.out.println("...ok");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openAudit_memory_rejectsInvalidSize() {
|
||||||
|
System.out.println("openAudit_memory_rejectsInvalidSize");
|
||||||
|
|
||||||
|
System.setProperty("zeroecho.pki.audit", "memory");
|
||||||
|
System.setProperty("zeroecho.pki.audit.size", "abc");
|
||||||
|
|
||||||
|
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> PkiBootstrap.openAudit());
|
||||||
|
System.out.println("...message=" + exception.getMessage());
|
||||||
|
assertEquals("Configuration key 'size' must be an integer.", exception.getMessage());
|
||||||
|
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openCredentialFramework_explicitX509BcProvider() {
|
||||||
|
System.out.println("openCredentialFramework_explicitX509BcProvider");
|
||||||
|
|
||||||
|
System.setProperty("zeroecho.pki.framework", "x509-bc");
|
||||||
|
|
||||||
|
CredentialFramework framework = PkiBootstrap.openCredentialFramework();
|
||||||
|
assertNotNull(framework);
|
||||||
|
|
||||||
|
String frameworkClassName = framework.getClass().getName();
|
||||||
|
System.out.println("...frameworkClass=" + frameworkClassName);
|
||||||
|
|
||||||
|
assertEquals("zeroecho.pki.impl.framework.x509.bc.BcX509CredentialFramework", frameworkClassName);
|
||||||
|
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openCredentialFramework_x509Bc_byDefault() {
|
||||||
|
System.out.println("openCredentialFramework_x509Bc_byDefault");
|
||||||
|
|
||||||
|
CredentialFramework framework = PkiBootstrap.openCredentialFramework();
|
||||||
|
assertNotNull(framework);
|
||||||
|
|
||||||
|
String frameworkClassName = framework.getClass().getName();
|
||||||
|
System.out.println("...frameworkClass=" + frameworkClassName);
|
||||||
|
|
||||||
|
assertEquals("zeroecho.pki.impl.framework.x509.bc.BcX509CredentialFramework", frameworkClassName);
|
||||||
|
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openCredentialFramework_x509Bc_ignoresUnknownKey() {
|
||||||
|
System.out.println("openCredentialFramework_x509Bc_ignoresUnknownKey");
|
||||||
|
|
||||||
|
System.setProperty("zeroecho.pki.framework", "x509-bc");
|
||||||
|
System.setProperty("zeroecho.pki.framework.unused", "value");
|
||||||
|
|
||||||
|
CredentialFramework framework = PkiBootstrap.openCredentialFramework();
|
||||||
|
assertNotNull(framework);
|
||||||
|
|
||||||
|
String frameworkClassName = framework.getClass().getName();
|
||||||
|
System.out.println("...frameworkClass=" + frameworkClassName);
|
||||||
|
|
||||||
|
assertEquals("zeroecho.pki.impl.framework.x509.bc.BcX509CredentialFramework", frameworkClassName);
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openAsync_file_usesTempLogPath() {
|
||||||
|
System.out.println("openAsync_file_usesTempLogPath");
|
||||||
|
|
||||||
|
System.setProperty("zeroecho.pki.async", "file");
|
||||||
|
System.setProperty("zeroecho.pki.async.logPath", this.tempDir.resolve("async").resolve("async.log").toString());
|
||||||
|
|
||||||
|
AsyncBus<PkiId, Principal, String, Object> bus = PkiBootstrap.openAsyncBus();
|
||||||
|
assertNotNull(bus);
|
||||||
|
|
||||||
|
String busClassName = bus.getClass().getName();
|
||||||
|
System.out.println("...asyncClass=" + busClassName);
|
||||||
|
|
||||||
|
assertEquals("zeroecho.pki.util.async.impl.DurableAsyncBus", busClassName);
|
||||||
|
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openSignatureWorkflow_zeroEchoLib_usesConfiguredKeyringPath() {
|
||||||
|
System.out.println("openSignatureWorkflow_zeroEchoLib_usesConfiguredKeyringPath");
|
||||||
|
|
||||||
|
System.setProperty("zeroecho.pki.crypto.workflow", "zeroecho-lib");
|
||||||
|
System.setProperty("zeroecho.pki.crypto.workflow.keyringPath",
|
||||||
|
this.tempDir.resolve("workflow").resolve("keyring.zek").toString());
|
||||||
|
System.setProperty("zeroecho.pki.crypto.workflow.keyRefPrefix", "test-prefix:");
|
||||||
|
System.setProperty("zeroecho.pki.crypto.workflow.requireComponentSuffix", "false");
|
||||||
|
|
||||||
|
SignatureWorkflow workflow = PkiBootstrap.openSignatureWorkflow();
|
||||||
|
assertNotNull(workflow);
|
||||||
|
|
||||||
|
String workflowClassName = workflow.getClass().getName();
|
||||||
|
System.out.println("...workflowClass=" + workflowClassName);
|
||||||
|
|
||||||
|
assertEquals("zeroecho.pki.impl.crypto.zeroecholib.ZeroEchoLibSignatureWorkflow", workflowClassName);
|
||||||
|
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void providerValidation_rejectsBackendIdMismatch() {
|
||||||
|
System.out.println("providerValidation_rejectsBackendIdMismatch");
|
||||||
|
|
||||||
|
zeroecho.pki.impl.fs.FilesystemPkiStoreProvider provider = new zeroecho.pki.impl.fs.FilesystemPkiStoreProvider();
|
||||||
|
zeroecho.pki.spi.ProviderConfig config = new zeroecho.pki.spi.ProviderConfig("other",
|
||||||
|
java.util.Map.of("root", this.tempDir.resolve("store").toString()));
|
||||||
|
|
||||||
|
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> provider.validateConfig(config));
|
||||||
|
System.out.println("...message=" + exception.getMessage());
|
||||||
|
assertEquals("ProviderConfig backendId mismatch.", exception.getMessage());
|
||||||
|
|
||||||
|
System.out.println("...ok");
|
||||||
|
}
|
||||||
|
|
||||||
private static void clearPrefix(String prefix) {
|
private static void clearPrefix(String prefix) {
|
||||||
Properties props = System.getProperties();
|
Properties props = System.getProperties();
|
||||||
for (Object keyObj : props.keySet().toArray()) {
|
for (Object keyObj : props.keySet().toArray()) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ plugins {
|
|||||||
dependencies {
|
dependencies {
|
||||||
testImplementation(project(":lib"))
|
testImplementation(project(":lib"))
|
||||||
testImplementation 'org.egothor:conflux:[1.0,2.0)'
|
testImplementation 'org.egothor:conflux:[1.0,2.0)'
|
||||||
testImplementation("org.bouncycastle:bcpkix-jdk18on:1.81")
|
testImplementation("org.bouncycastle:bcpkix-jdk18on:1.84")
|
||||||
testImplementation(platform("org.junit:junit-bom:5.10.2"))
|
testImplementation(platform("org.junit:junit-bom:5.10.2"))
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// Apply the foojay-resolver plugin to allow automatic download of JDKs
|
// Apply the foojay-resolver plugin to allow automatic download of JDKs
|
||||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
|
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = 'ZeroEcho'
|
rootProject.name = 'ZeroEcho'
|
||||||
|
|
||||||
include('app', 'lib', 'pki', 'samples')
|
include('app', 'ext', 'lib', 'pki', 'samples')
|
||||||
|
|||||||
Reference in New Issue
Block a user