GordianKeyPairValidity.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.keypair;
import io.github.tonywasher.joceanus.gordianknot.api.agree.GordianAgreement;
import io.github.tonywasher.joceanus.gordianknot.api.agree.GordianAgreementFactory;
import io.github.tonywasher.joceanus.gordianknot.api.agree.GordianAgreementKDF;
import io.github.tonywasher.joceanus.gordianknot.api.agree.GordianAgreementParams;
import io.github.tonywasher.joceanus.gordianknot.api.agree.GordianAgreementSpec;
import io.github.tonywasher.joceanus.gordianknot.api.agree.GordianAgreementSpecBuilder;
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.cert.GordianCertificate;
import io.github.tonywasher.joceanus.gordianknot.api.cert.GordianKeyPairUsage;
import io.github.tonywasher.joceanus.gordianknot.api.cert.GordianKeyPairUse;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.encrypt.GordianEncryptor;
import io.github.tonywasher.joceanus.gordianknot.api.encrypt.GordianEncryptorFactory;
import io.github.tonywasher.joceanus.gordianknot.api.encrypt.GordianEncryptorSpec;
import io.github.tonywasher.joceanus.gordianknot.api.encrypt.GordianEncryptorSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPairSpec;
import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianSignParams;
import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianSignature;
import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianSignatureFactory;
import io.github.tonywasher.joceanus.gordianknot.api.sign.GordianSignatureSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianLogicException;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import java.util.Arrays;
/**
* KeyPair validity checking.
*/
public final class GordianKeyPairValidity {
/**
* Error message.
*/
private static final String ERRORMSG = "Mismatch on public/private key";
/**
* Server X500Name.
*/
private static final X500Name SERVER = new X500NameBuilder(BCStyle.INSTANCE).addRDN(BCStyle.CN, "Server").build();
/**
* Private constructor.
*/
private GordianKeyPairValidity() {
}
/**
* Check keyPair validity.
*
* @param pFactory the factory
* @param pKeyPair the keyPair
* @throws GordianException on error
*/
public static void checkValidity(final GordianBaseFactory pFactory,
final GordianKeyPair pKeyPair) throws GordianException {
final Object myCheck = getValidityCheck(pFactory, pKeyPair);
if (myCheck instanceof GordianSignatureSpec mySpec) {
checkValidity(pFactory, pKeyPair, mySpec);
} else if (myCheck instanceof GordianEncryptorSpec mySpec) {
checkValidity(pFactory, pKeyPair, mySpec);
} else if (myCheck instanceof GordianAgreementSpec mySpec) {
checkValidity(pFactory, pKeyPair, mySpec);
} else {
throw new GordianLogicException("Unexpected keyPairType");
}
}
/**
* Check keyPair validity via signature.
*
* @param pFactory the factory
* @param pKeyPair the keyPair
* @param pSignSpec the signature spec
* @throws GordianException on error
*/
private static void checkValidity(final GordianBaseFactory pFactory,
final GordianKeyPair pKeyPair,
final GordianSignatureSpec pSignSpec) throws GordianException {
/* Use default personalisation as the data to sign */
final byte[] myData = pFactory.getRandomSource().defaultPersonalisation();
/* Create signer */
final GordianSignatureFactory mySigns = pFactory.getAsyncFactory().getSignatureFactory();
final GordianSignature mySigner = mySigns.createSigner(pSignSpec);
/* Create signature */
final GordianSignParams myParams = GordianSignParams.keyPair(pKeyPair);
mySigner.initForSigning(myParams);
mySigner.update(myData);
final byte[] mySignature = mySigner.sign();
/* Validate signature */
mySigner.initForVerify(myParams);
mySigner.update(myData);
if (!mySigner.verify(mySignature)) {
throw new GordianDataException(ERRORMSG + " " + pSignSpec);
}
}
/**
* Check keyPair validity via encryption.
*
* @param pFactory the factory
* @param pKeyPair the keyPair
* @param pEncryptSpec the encryption spec
* @throws GordianException on error
*/
private static void checkValidity(final GordianBaseFactory pFactory,
final GordianKeyPair pKeyPair,
final GordianEncryptorSpec pEncryptSpec) throws GordianException {
/* Use default personalisation as the data to encrypt */
final byte[] myData = pFactory.getRandomSource().defaultPersonalisation();
/* Create encryptor */
final GordianEncryptorFactory myEncrypts = pFactory.getAsyncFactory().getEncryptorFactory();
final GordianEncryptor myEncryptor = myEncrypts.createEncryptor(pEncryptSpec);
/* Encrypt data */
myEncryptor.initForEncrypt(pKeyPair);
final byte[] myEncrypted = myEncryptor.encrypt(myData);
/* decrypt encrypted data */
myEncryptor.initForDecrypt(pKeyPair);
final byte[] myResult = myEncryptor.decrypt(myEncrypted);
/* Check that we have arrived back at the original data */
if (!Arrays.equals(myData, myResult)) {
throw new GordianDataException(ERRORMSG + " " + pEncryptSpec);
}
}
/**
* Check keyPair validity via agreement.
*
* @param pFactory the factory
* @param pKeyPair the keyPair
* @param pAgreeSpec the agreementSpec
* @throws GordianException on error
*/
private static void checkValidity(final GordianBaseFactory pFactory,
final GordianKeyPair pKeyPair,
final GordianAgreementSpec pAgreeSpec) throws GordianException {
/* Create agreement on client side */
final GordianAgreementFactory myAgrees = pFactory.getAsyncFactory().getAgreementFactory();
final GordianCertificate myCert = myAgrees.newMiniCertificate(SERVER, pKeyPair, new GordianKeyPairUsage(GordianKeyPairUse.AGREEMENT));
GordianAgreementParams myParams = myAgrees.newAgreementParams(pAgreeSpec, GordianLength.LEN_256.getByteLength())
.setServerCertificate(myCert);
GordianAgreement myAgreement = myAgrees.createAgreement(myParams);
final byte[] myHello = myAgreement.nextMessage();
final byte[] myClient = (byte[]) myAgreement.getResult();
/* Accept agreement on server side */
myAgreement = myAgrees.parseAgreementMessage(myHello);
myParams = myAgreement.getAgreementParams().setServerCertificate(myCert);
myAgreement.updateParams(myParams);
final byte[] myServer = (byte[]) myAgreement.getResult();
/* Check that we have the same result at either end */
if (!Arrays.equals(myClient, myServer)) {
throw new GordianDataException(ERRORMSG + " " + pAgreeSpec);
}
}
/**
* Obtain validity check for keyPair.
*
* @param pFactory the factory
* @param pKeyPair the keyPair
* @return the validity check
*/
private static Object getValidityCheck(final GordianBaseFactory pFactory,
final GordianKeyPair pKeyPair) {
/* Switch on keyType */
final GordianKeyPairSpec mySpec = pKeyPair.getKeyPairSpec();
switch (mySpec.getKeyPairType()) {
case RSA:
case DSA:
case EDDSA:
case EC:
case GOST2012:
case DSTU4145:
case SM2:
case SLHDSA:
case MLDSA:
case FALCON:
case MAYO:
case SNOVA:
case PICNIC:
case XMSS:
case LMS:
return pFactory.getAsyncFactory().getSignatureFactory().defaultForKeyPair(mySpec);
case ELGAMAL:
return GordianEncryptorSpecBuilder.elGamal(GordianDigestSpecBuilder.sha2(GordianLength.LEN_512));
case DH:
return GordianAgreementSpecBuilder.anon(mySpec, GordianAgreementKDF.SHA256KDF);
case XDH:
return mySpec.getEdwardsElliptic().is25519()
? GordianAgreementSpecBuilder.anon(mySpec, GordianAgreementKDF.SHA256KDF)
: GordianAgreementSpecBuilder.anon(mySpec, GordianAgreementKDF.SHA512KDF);
case CMCE:
case FRODO:
case SABER:
case MLKEM:
case HQC:
case BIKE:
case NTRU:
case NTRUPRIME:
case NEWHOPE:
return GordianAgreementSpecBuilder.kem(mySpec, GordianAgreementKDF.NONE);
default:
return null;
}
}
}