GordianCoreSignatureFactory.java
/*
* GordianKnot: Security Suite
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.gordianknot.impl.core.sign;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestType;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.spec.GordianGOSTSpec;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.spec.GordianKeyPairSpec;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.spec.GordianKeyPairType;
import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianNewSignParamsBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianSignatureFactory;
import io.github.tonywasher.joceanus.gordianknot.api.sign.spec.GordianSignatureSpec;
import io.github.tonywasher.joceanus.gordianknot.api.sign.spec.GordianSignatureSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.sign.spec.GordianSignatureType;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseData;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.digest.GordianCoreDigestFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.digest.GordianCoreDigestSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreKeyPairSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreKeyPairType;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.sign.GordianCoreSignatureSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.sign.GordianCoreSignatureSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.sign.GordianCoreSignatureType;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
/**
* GordianKnot base for signatureFactory.
*/
public abstract class GordianCoreSignatureFactory
implements GordianSignatureFactory {
/**
* The factory.
*/
private final GordianBaseFactory theFactory;
/**
* The algorithm Ids.
*/
private GordianCoreSignatureAlgId theAlgIds;
/**
* Constructor.
*
* @param pFactory the factory
*/
protected GordianCoreSignatureFactory(final GordianBaseFactory pFactory) {
theFactory = pFactory;
}
@Override
public GordianSignatureSpecBuilder newSignatureSpecBuilder() {
return GordianCoreSignatureSpecBuilder.newInstance();
}
@Override
public GordianNewSignParamsBuilder newSignParamsBuilder() {
return GordianCoreSignParamsBuilder.newInstance();
}
/**
* Obtain the factory.
*
* @return the factory
*/
protected GordianBaseFactory getFactory() {
return theFactory;
}
@Override
public Predicate<GordianSignatureSpec> supportedKeyPairSignatures() {
return this::validSignatureSpec;
}
/**
* Check the signatureSpec.
*
* @param pSignatureSpec the signatureSpec
* @throws GordianException on error
*/
protected void checkSignatureSpec(final GordianSignatureSpec pSignatureSpec) throws GordianException {
/* Check validity of signature */
if (!validSignatureSpec(pSignatureSpec)) {
throw new GordianDataException(GordianBaseData.getInvalidText(pSignatureSpec));
}
}
@Override
public boolean validSignatureSpecForKeyPairSpec(final GordianKeyPairSpec pKeyPairSpec,
final GordianSignatureSpec pSignSpec) {
/* Check signature matches keySpec */
if (pSignSpec.getKeyPairType() != pKeyPairSpec.getKeyPairType()) {
return false;
}
/* Check that the signatureSpec is supported */
if (!validSignatureSpec(pSignSpec)) {
return false;
}
/* Disallow ECNR if keySize is smaller than digestSize */
final GordianCoreSignatureSpec mySpec = (GordianCoreSignatureSpec) pSignSpec;
final GordianCoreKeyPairSpec myKeyPairSpec = (GordianCoreKeyPairSpec) pKeyPairSpec;
if (GordianSignatureType.NR.equals(pSignSpec.getSignatureType())) {
return myKeyPairSpec.getElliptic().getKeySize() > mySpec.getDigestSpec().getDigestLength().getLength();
}
/* Disallow incorrectly sized digest for GOST */
if (GordianKeyPairType.GOST.equals(pKeyPairSpec.getKeyPairType())) {
final int myDigestLen = mySpec.getDigestSpec().getDigestLength().getLength();
return myKeyPairSpec.getElliptic().getKeySize() == myDigestLen;
}
/* If this is a RSA Signature */
if (GordianKeyPairType.RSA.equals(pKeyPairSpec.getKeyPairType())) {
/* If this is a PSS signature */
if (mySpec.getCoreType().isPSS()) {
/* The digest length cannot be too large wrt to the modulus */
int myLen = mySpec.getDigestSpec().getDigestLength().getLength();
myLen += Byte.SIZE;
if (myKeyPairSpec.getRSASpec().getLength() < (myLen << 1)) {
return false;
}
}
/* Must be X931/ISO9796d2 Signature */
/* The digest length cannot be too large wrt to the modulus */
int myLen = mySpec.getDigestSpec().getDigestLength().getLength();
myLen += Integer.SIZE;
if (myKeyPairSpec.getRSASpec().getLength() < myLen) {
return false;
}
}
/* For Composite EncryptorSpec */
if (pKeyPairSpec.getKeyPairType() == GordianKeyPairType.COMPOSITE) {
/* Loop through the keyPairs */
final Iterator<GordianKeyPairSpec> pairIterator = myKeyPairSpec.keySpecIterator();
final Iterator<GordianSignatureSpec> sigIterator = mySpec.signatureSpecIterator();
while (pairIterator.hasNext() && sigIterator.hasNext()) {
final GordianKeyPairSpec myPairSpec = pairIterator.next();
final GordianSignatureSpec mySigSpec = sigIterator.next();
if (!validSignatureSpecForKeyPairSpec(myPairSpec, mySigSpec)) {
return false;
}
}
if (pairIterator.hasNext() || sigIterator.hasNext()) {
return false;
}
}
/* OK */
return true;
}
/**
* Check SignatureSpec.
*
* @param pSignSpec the macSpec
* @return true/false
*/
protected boolean validSignatureSpec(final GordianSignatureSpec pSignSpec) {
/* Reject invalid signatureSpec */
if (pSignSpec == null || !pSignSpec.isValid()) {
return false;
}
/* Check that the signatureType is supported */
final GordianCoreSignatureSpec mySignSpec = (GordianCoreSignatureSpec) pSignSpec;
final GordianKeyPairType myType = pSignSpec.getKeyPairType();
final GordianCoreSignatureType mySignType = mySignSpec.getCoreType();
if (!mySignType.isSupported(myType)) {
return false;
}
/* Don't worry about digestSpec if it is irrelevant */
final GordianCoreKeyPairType myKeyType = GordianCoreKeyPairType.mapCoreType(myType);
if (myKeyType.useDigestForSignatures().mustNotExist()) {
return pSignSpec.getSignatureSpec() == null;
}
if (myKeyType.useDigestForSignatures().canNotExist()
&& pSignSpec.getSignatureSpec() == null) {
return true;
}
/* Composite signatures */
if (GordianKeyPairType.COMPOSITE.equals(myType)) {
/* Loop through the specs */
final Iterator<GordianSignatureSpec> myIterator = mySignSpec.signatureSpecIterator();
while (myIterator.hasNext()) {
final GordianSignatureSpec mySpec = myIterator.next();
if (!validSignatureSpec(mySpec)) {
return false;
}
}
return true;
}
/* Check that the digestSpec is supported */
final GordianDigestSpec mySpec = mySignSpec.getDigestSpec();
if (mySpec == null
|| !validSignatureDigestSpec(mySpec)) {
return false;
}
/* Check RSA signatures */
if (GordianKeyPairType.RSA.equals(myType)) {
return validRSASignature(pSignSpec);
}
/* Check DDSA signatures */
if (GordianSignatureType.DDSA.equals(pSignSpec.getSignatureType())) {
return validDDSASignature(pSignSpec);
}
/* Only allow GOST for DSTU signature */
if (GordianKeyPairType.DSTU.equals(myType)) {
return GordianDigestType.GOST.equals(mySpec.getDigestType());
}
/* Only allow STREEBOG for GOST signature */
if (GordianKeyPairType.GOST.equals(myType)) {
return GordianDigestType.STREEBOG.equals(mySpec.getDigestType());
}
/* OK */
return true;
}
/**
* Check SignatureDigestSpec.
*
* @param pDigestSpec the digestSpec
* @return true/false
*/
protected boolean validSignatureDigestSpec(final GordianDigestSpec pDigestSpec) {
final GordianCoreDigestFactory myDigests = (GordianCoreDigestFactory) theFactory.getDigestFactory();
return myDigests.validDigestSpec(pDigestSpec);
}
/**
* Check RSASignature.
*
* @param pSpec the signatureSpec
* @return true/false
*/
private static boolean validRSASignature(final GordianSignatureSpec pSpec) {
/* Apply restrictions on PREHASH */
if (GordianSignatureType.PREHASH.equals(pSpec.getSignatureType())) {
/* Switch on DigestType */
final GordianCoreSignatureSpec mySpec = (GordianCoreSignatureSpec) pSpec;
final GordianDigestSpec myDigest = mySpec.getDigestSpec();
return switch (myDigest.getDigestType()) {
case SHA1, SHA2, SHA3, MD2, MD4, MD5 -> true;
case RIPEMD -> myDigest.getDigestLength().getLength() <= GordianLength.LEN_256.getLength();
default -> false;
};
}
/* Otherwise OK */
return true;
}
/**
* Check DDSASignature.
*
* @param pSpec the signatureSpec
* @return true/false
*/
private static boolean validDDSASignature(final GordianSignatureSpec pSpec) {
/* Switch on DigestType */
final GordianCoreSignatureSpec mySpec = (GordianCoreSignatureSpec) pSpec;
final GordianDigestSpec myDigest = mySpec.getDigestSpec();
return switch (myDigest.getDigestType()) {
case ASCON, ISAP, PHOTONBEETLE, SPARKLE, XOODYAK -> false;
default -> true;
};
}
/**
* Obtain Identifier for SignatureSpec.
*
* @param pSpec the signatureSpec.
* @param pKeyPair the keyPair
* @return the Identifier
*/
public AlgorithmIdentifier getIdentifierForSpecAndKeyPair(final GordianSignatureSpec pSpec,
final GordianKeyPair pKeyPair) {
return getAlgorithmIds().getIdentifierForSpecAndKeyPair(pSpec, pKeyPair);
}
/**
* Obtain SignatureSpec for Identifier.
*
* @param pIdentifier the identifier.
* @return the signatureSpec (or null if not found)
*/
public GordianSignatureSpec getSpecForIdentifier(final AlgorithmIdentifier pIdentifier) {
return getAlgorithmIds().getSpecForIdentifier(pIdentifier);
}
/**
* Obtain the signature algorithm Ids.
*
* @return the signature Algorithm Ids
*/
private GordianCoreSignatureAlgId getAlgorithmIds() {
if (theAlgIds == null) {
theAlgIds = new GordianCoreSignatureAlgId(theFactory);
}
return theAlgIds;
}
@Override
public List<GordianSignatureSpec> listAllSupportedSignatures(final GordianKeyPair pKeyPair) {
return listAllSupportedSignatures(pKeyPair.getKeyPairSpec());
}
@Override
public List<GordianSignatureSpec> listAllSupportedSignatures(final GordianKeyPairSpec pKeySpec) {
return listPossibleSignatures(pKeySpec.getKeyPairType())
.stream()
.filter(s -> validSignatureSpecForKeyPairSpec(pKeySpec, s))
.toList();
}
@Override
public List<GordianSignatureSpec> listPossibleSignatures(final GordianKeyPairType pKeyType) {
return GordianCoreSignatureSpecBuilder.listAllPossibleSpecs(pKeyType);
}
@Override
public GordianSignatureSpec defaultForKeyPair(final GordianKeyPairSpec pKeySpec) {
final GordianCoreSignatureSpecBuilder myBuilder = GordianCoreSignatureSpecBuilder.newInstance();
final GordianCoreDigestSpecBuilder myDigestBuilder = GordianCoreDigestSpecBuilder.newInstance();
switch (pKeySpec.getKeyPairType()) {
case RSA:
return myBuilder.rsa(GordianSignatureType.PSSMGF1, myDigestBuilder.sha3(GordianLength.LEN_256));
case DSA:
return myBuilder.dsa(GordianSignatureType.DSA, myDigestBuilder.sha2(GordianLength.LEN_512));
case EC:
return myBuilder.ec(GordianSignatureType.DSA, myDigestBuilder.sha3(GordianLength.LEN_512));
case SM2:
return myBuilder.sm2(myDigestBuilder.sm3());
case DSTU:
return myBuilder.dstu4145();
case GOST:
return myBuilder.gost2012(GordianGOSTSpec.GOST256A.equals(pKeySpec.getSubSpec())
? GordianLength.LEN_256 : GordianLength.LEN_512);
case EDDSA:
return myBuilder.edDSA();
case SLHDSA:
return myBuilder.slhdsa();
case MLDSA:
return myBuilder.mldsa();
case FALCON:
return myBuilder.falcon();
case MAYO:
return myBuilder.mayo();
case SNOVA:
return myBuilder.snova();
case PICNIC:
return myBuilder.picnic();
case XMSS:
return myBuilder.xmss();
case LMS:
return myBuilder.lms();
case COMPOSITE:
final List<GordianSignatureSpec> mySpecs = new ArrayList<>();
final GordianCoreKeyPairSpec myKeySpec = (GordianCoreKeyPairSpec) pKeySpec;
final Iterator<GordianKeyPairSpec> myIterator = myKeySpec.keySpecIterator();
while (myIterator.hasNext()) {
final GordianKeyPairSpec mySpec = myIterator.next();
mySpecs.add(defaultForKeyPair(mySpec));
}
final GordianSignatureSpec mySpec = myBuilder.composite(mySpecs);
return mySpec.isValid() ? mySpec : null;
default:
return null;
}
}
}