/******************************************************************************* * Copyright (C) 2025, Leo Galambos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. All advertising materials mentioning features or use of this software must * display the following acknowledgement: * This product includes software developed by the Egothor project. * * 4. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (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 java.io.IOException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.util.Objects; import java.util.function.Supplier; import conflux.CtxInterface; import conflux.Key; import zeroecho.core.CryptoAlgorithms; import zeroecho.core.KeyUsage; import zeroecho.core.spec.ContextSpec; import zeroecho.core.tag.TagEngine; import zeroecho.sdk.builders.core.DataContentBuilder; import zeroecho.sdk.content.api.DataContent; import zeroecho.sdk.hybrid.signature.HybridSignatureContexts; import zeroecho.sdk.hybrid.signature.HybridSignatureProfile; /** * Signature-specific trailer builder for {@link DataContent} pipelines. * *

* This class is intended as a convenient, signature-specialized replacement * for: *

* *
 * new TagTrailerDataContentBuilder<Signature>(engine).bufferSize(8192)
 * new TagTrailerDataContentBuilder<Signature>(engine).bufferSize(8192).throwOnMismatch()
 * 
* *

* It keeps {@link TagTrailerDataContentBuilder} as the generic implementation * while providing a compact API for {@code Signature} usage, including * construction of {@code SignatureContext} for both single-algorithm and hybrid * signatures. *

* *

Mode selection

* * *

Checked exceptions

*

* Context construction may involve I/O (e.g., catalog/provider loading) and * therefore throw {@link IOException}. This builder converts such failures to * {@link IllegalStateException} because fluent builder APIs are expected to be * used in configuration code without mandatory checked-exception plumbing. *

* * @since 1.0 */ public final class SignatureTrailerDataContentBuilder implements DataContentBuilder { private final TagTrailerDataContentBuilder delegate; private SignatureTrailerDataContentBuilder(TagEngine engine) { this.delegate = new TagTrailerDataContentBuilder<>(engine); } private SignatureTrailerDataContentBuilder(Supplier> engineFactory) { this.delegate = new TagTrailerDataContentBuilder<>(engineFactory); } /** * Core mode: wraps a fixed engine instance. * *

* This is the direct signature-specialized equivalent of: * {@code new TagTrailerDataContentBuilder(engine)}. *

* * @param engine signature engine (typically a {@code SignatureContext}); must * not be {@code null} * @return builder instance * @throws NullPointerException if {@code engine} is {@code null} * @since 1.0 */ public static SignatureTrailerDataContentBuilder core(TagEngine engine) { Objects.requireNonNull(engine, "engine"); return new SignatureTrailerDataContentBuilder(engine); } /** * Core mode: wraps an engine factory. * *

* This is the direct signature-specialized equivalent of: * {@code new TagTrailerDataContentBuilder(engineFactory)}. *

* * @param engineFactory engine factory; must not be {@code null} * @return builder instance * @throws NullPointerException if {@code engineFactory} is {@code null} * @since 1.0 */ public static SignatureTrailerDataContentBuilder core(Supplier> engineFactory) { Objects.requireNonNull(engineFactory, "engineFactory"); return new SignatureTrailerDataContentBuilder(engineFactory); } /** * Enters single-algorithm (non-hybrid) construction helpers. * * @return selector for creating signing/verifying builders * @since 1.0 */ public static SingleSelector single() { return new SingleSelector(); } /** * Enters hybrid signature construction helpers. * * @return selector for creating signing/verifying builders * @since 1.0 */ public static HybridSelector hybrid() { return new HybridSelector(); } /** * Sets the internal I/O buffer size used when stripping the trailer. * * @param bytes buffer size in bytes; must be at least 1 * @return this builder for chaining * @throws IllegalArgumentException if {@code bytes < 1} * @since 1.0 */ public SignatureTrailerDataContentBuilder bufferSize(int bytes) { delegate.bufferSize(bytes); return this; } /** * Configures verification to throw on mismatch (default verification behavior). * * @return this builder for chaining * @since 1.0 */ public SignatureTrailerDataContentBuilder throwOnMismatch() { delegate.throwOnMismatch(); return this; } /** * Instead of throwing on mismatch, records the verification outcome into a * context flag. * * @param ctx context instance to receive the flag; must not be * {@code null} * @param verifyKey key under which the {@code Boolean} result is recorded; must * not be {@code null} * @return this builder for chaining * @throws NullPointerException if {@code ctx} or {@code verifyKey} is * {@code null} * @since 1.0 */ public SignatureTrailerDataContentBuilder flagInContext(CtxInterface ctx, Key verifyKey) { delegate.flagInContext(Objects.requireNonNull(ctx, "ctx"), Objects.requireNonNull(verifyKey, "verifyKey")); return this; } @Override public DataContent build(boolean encrypt) { return delegate.build(encrypt); } // ====================================================================== // Single-algorithm helpers // ====================================================================== /** * Helper entry for single-algorithm signature construction. * * @since 1.0 */ public static final class SingleSelector { private SingleSelector() { } /** * Creates a signing trailer for a single signature algorithm (no spec). * * @param algorithmId signature algorithm id * @param privateKey private key for signing * @return builder ready to be added to a pipeline * @throws NullPointerException if {@code algorithmId} or {@code privateKey} is * {@code null} * @throws IllegalStateException if the signature context cannot be created * (e.g., I/O/provider issues) * @since 1.0 */ public SignatureTrailerDataContentBuilder sign(String algorithmId, PrivateKey privateKey) { return sign(algorithmId, privateKey, null); } /** * Creates a signing trailer for a single signature algorithm with an optional * spec. * * @param algorithmId signature algorithm id * @param privateKey private key for signing * @param spec optional context spec (may be {@code null}) * @return builder ready to be added to a pipeline * @throws NullPointerException if {@code algorithmId} or {@code privateKey} is * {@code null} * @throws IllegalStateException if the signature context cannot be created * (e.g., I/O/provider issues) * @since 1.0 */ public SignatureTrailerDataContentBuilder sign(String algorithmId, PrivateKey privateKey, ContextSpec spec) { Objects.requireNonNull(algorithmId, "algorithmId"); Objects.requireNonNull(privateKey, "privateKey"); Supplier> factory = () -> { try { return CryptoAlgorithms.create(algorithmId, KeyUsage.SIGN, privateKey, spec); } catch (IOException e) { throw new IllegalStateException("Failed to create SIGN SignatureContext for: " + algorithmId, e); } }; return core(factory); } /** * Creates a verification trailer for a single signature algorithm (no spec). * * @param algorithmId signature algorithm id * @param publicKey public key for verification * @return builder ready to be added to a pipeline * @throws NullPointerException if {@code algorithmId} or {@code publicKey} is * {@code null} * @throws IllegalStateException if the signature context cannot be created * (e.g., I/O/provider issues) * @since 1.0 */ public SignatureTrailerDataContentBuilder verify(String algorithmId, PublicKey publicKey) { return verify(algorithmId, publicKey, null); } /** * Creates a verification trailer for a single signature algorithm with an * optional spec. * * @param algorithmId signature algorithm id * @param publicKey public key for verification * @param spec optional context spec (may be {@code null}) * @return builder ready to be added to a pipeline * @throws NullPointerException if {@code algorithmId} or {@code publicKey} is * {@code null} * @throws IllegalStateException if the signature context cannot be created * (e.g., I/O/provider issues) * @since 1.0 */ public SignatureTrailerDataContentBuilder verify(String algorithmId, PublicKey publicKey, ContextSpec spec) { Objects.requireNonNull(algorithmId, "algorithmId"); Objects.requireNonNull(publicKey, "publicKey"); Supplier> factory = () -> { try { return CryptoAlgorithms.create(algorithmId, KeyUsage.VERIFY, publicKey, spec); } catch (IOException e) { throw new IllegalStateException("Failed to create VERIFY SignatureContext for: " + algorithmId, e); } }; return core(factory); } } // ====================================================================== // Hybrid helpers // ====================================================================== /** * Helper entry for hybrid signature construction. * * @since 1.0 */ public static final class HybridSelector { private static final int DEFAULT_MAX_BODY_BYTES = 2 * 1024 * 1024; private HybridSelector() { } /** * Creates a hybrid signing trailer for the common case where both specs are * {@code null}. * * @param classicSigId classic signature algorithm id * @param pqcSigId post-quantum signature algorithm id * @param rule aggregation rule * @param classicPrivate classic private key * @param pqcPrivate PQC private key * @return builder ready to be added to a pipeline * @throws NullPointerException if any required argument is {@code null} * @throws IllegalStateException if the hybrid context cannot be created * @since 1.0 */ public SignatureTrailerDataContentBuilder sign(String classicSigId, String pqcSigId, HybridSignatureProfile.VerifyRule rule, PrivateKey classicPrivate, PrivateKey pqcPrivate) { return sign(classicSigId, pqcSigId, null, null, rule, classicPrivate, pqcPrivate, DEFAULT_MAX_BODY_BYTES); } /** * Creates a hybrid verification trailer for the common case where both specs * are {@code null}. * * @param classicSigId classic signature algorithm id * @param pqcSigId post-quantum signature algorithm id * @param rule aggregation rule * @param classicPublic classic public key * @param pqcPublic PQC public key * @return builder ready to be added to a pipeline * @throws NullPointerException if any required argument is {@code null} * @throws IllegalStateException if the hybrid context cannot be created * @since 1.0 */ public SignatureTrailerDataContentBuilder verify(String classicSigId, String pqcSigId, HybridSignatureProfile.VerifyRule rule, PublicKey classicPublic, PublicKey pqcPublic) { return verify(classicSigId, pqcSigId, null, null, rule, classicPublic, pqcPublic, DEFAULT_MAX_BODY_BYTES); } /** * Creates a hybrid signing trailer with optional specs and explicit * {@code maxBodyBytes}. * * @param classicSigId classic signature algorithm id * @param pqcSigId post-quantum signature algorithm id * @param classicSpec optional classic spec (may be {@code null}) * @param pqcSpec optional PQC spec (may be {@code null}) * @param rule aggregation rule * @param classicPrivate classic private key * @param pqcPrivate PQC private key * @param maxBodyBytes maximum body size accepted by the hybrid context; must * be at least 1 * @return builder ready to be added to a pipeline * @throws NullPointerException if any required argument is {@code null} * @throws IllegalArgumentException if {@code maxBodyBytes < 1} * @throws IllegalStateException if the hybrid context cannot be created * @since 1.0 */ public SignatureTrailerDataContentBuilder sign(String classicSigId, String pqcSigId, ContextSpec classicSpec, ContextSpec pqcSpec, HybridSignatureProfile.VerifyRule rule, PrivateKey classicPrivate, PrivateKey pqcPrivate, int maxBodyBytes) { Objects.requireNonNull(classicSigId, "classicSigId"); Objects.requireNonNull(pqcSigId, "pqcSigId"); Objects.requireNonNull(rule, "rule"); Objects.requireNonNull(classicPrivate, "classicPrivate"); Objects.requireNonNull(pqcPrivate, "pqcPrivate"); if (maxBodyBytes < 1) { // NOPMD throw new IllegalArgumentException("maxBodyBytes must be >= 1"); } HybridSignatureProfile profile = new HybridSignatureProfile(classicSigId, pqcSigId, classicSpec, pqcSpec, rule); Supplier> factory = () -> { try { return HybridSignatureContexts.sign(profile, classicPrivate, pqcPrivate, maxBodyBytes); } catch (RuntimeException e) { // NOPMD throw e; } catch (Exception e) { throw new IllegalStateException("Failed to create hybrid SIGN SignatureContext", e); } }; return core(factory); } /** * Creates a hybrid verification trailer with optional specs and explicit * {@code maxBodyBytes}. * * @param classicSigId classic signature algorithm id * @param pqcSigId post-quantum signature algorithm id * @param classicSpec optional classic spec (may be {@code null}) * @param pqcSpec optional PQC spec (may be {@code null}) * @param rule aggregation rule * @param classicPublic classic public key * @param pqcPublic PQC public key * @param maxBodyBytes maximum body size accepted by the hybrid context; must * be at least 1 * @return builder ready to be added to a pipeline * @throws NullPointerException if any required argument is {@code null} * @throws IllegalArgumentException if {@code maxBodyBytes < 1} * @throws IllegalStateException if the hybrid context cannot be created * @since 1.0 */ public SignatureTrailerDataContentBuilder verify(String classicSigId, String pqcSigId, ContextSpec classicSpec, ContextSpec pqcSpec, HybridSignatureProfile.VerifyRule rule, PublicKey classicPublic, PublicKey pqcPublic, int maxBodyBytes) { Objects.requireNonNull(classicSigId, "classicSigId"); Objects.requireNonNull(pqcSigId, "pqcSigId"); Objects.requireNonNull(rule, "rule"); Objects.requireNonNull(classicPublic, "classicPublic"); Objects.requireNonNull(pqcPublic, "pqcPublic"); if (maxBodyBytes < 1) { // NOPMD throw new IllegalArgumentException("maxBodyBytes must be >= 1"); } HybridSignatureProfile profile = new HybridSignatureProfile(classicSigId, pqcSigId, classicSpec, pqcSpec, rule); Supplier> factory = () -> { try { return HybridSignatureContexts.verify(profile, classicPublic, pqcPublic, maxBodyBytes); } catch (RuntimeException e) { // NOPMD throw e; } catch (Exception e) { throw new IllegalStateException("Failed to create hybrid VERIFY SignatureContext", e); } }; return core(factory); } } }