GordianCoreAgreementCalculator.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.agree;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherFactory;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherParameters;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamCipher;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamCipherSpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymCipher;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymCipherSpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestType;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactory;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactoryType;
import io.github.tonywasher.joceanus.gordianknot.api.key.GordianKey;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySet;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySetSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianDataConverter;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianParameters;
import io.github.tonywasher.joceanus.gordianknot.impl.core.kdf.GordianHKDFEngine;
import io.github.tonywasher.joceanus.gordianknot.impl.core.kdf.GordianHKDFParams;
import io.github.tonywasher.joceanus.gordianknot.impl.core.key.GordianCoreKeyGenerator;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keyset.GordianCoreKeySet;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keyset.GordianCoreKeySetFactory;
import org.bouncycastle.util.Arrays;
import java.nio.charset.StandardCharsets;
import java.util.Random;
/**
* Result derivation.
*/
public class GordianCoreAgreementCalculator {
/**
* The factory.
*/
private final GordianBaseFactory theFactory;
/**
* The state.
*/
private final GordianCoreAgreementState theState;
/**
* constructor.
*
* @param pFactory the factory
* @param pState the state
*/
GordianCoreAgreementCalculator(final GordianBaseFactory pFactory,
final GordianCoreAgreementState pState) {
theFactory = pFactory;
theState = pState;
}
/**
* Process the secret.
*
* @param pSecret the secret
* @param pResultType the resultType
* @return the result
* @throws GordianException on error
*/
Object processSecret(final byte[] pSecret,
final Object pResultType) throws GordianException {
/* If the resultType is a FactoryType */
if (pResultType instanceof GordianFactoryType myType) {
/* derive the factory */
return deriveFactory(myType, pSecret);
/* If the resultType is a KeySetSpec */
} else if (pResultType instanceof GordianKeySetSpec mySpec) {
/* Derive the keySet */
return deriveKeySet(mySpec, pSecret);
/* If the resultType is a SymCipherSpec */
} else if (pResultType instanceof GordianSymCipherSpec mySpec) {
/* Derive the key */
return deriveCipher(mySpec, pSecret);
/* If the resultType is a StreamCipherSpec */
} else if (pResultType instanceof GordianStreamCipherSpec mySpec) {
/* Derive the key */
return deriveCipher(mySpec, pSecret);
/* If the resultType is an Integer */
} else if (pResultType instanceof Integer myLength) {
/* Derive the key */
return deriveBytes(pSecret, myLength);
}
/* Return the raw secret */
return Arrays.clone(pSecret);
}
/**
* Derive factory from the secret.
*
* @param pFactoryType the factoryType
* @param pSecret the secret
* @return the factory
* @throws GordianException on error
*/
private GordianFactory deriveFactory(final GordianFactoryType pFactoryType,
final byte[] pSecret) throws GordianException {
/* Ensure that we clear out the seed */
byte[] mySeed = null;
try {
/* Calculate the seed */
mySeed = calculateDerivedSecret(pSecret, GordianDerivationId.FACTORY, GordianParameters.SEED_LEN.getByteLength());
/* Create a new Factory using the phrase */
final GordianParameters myParams = new GordianParameters(pFactoryType, mySeed);
return theFactory.newFactory(myParams);
/* Clear buffer */
} finally {
if (mySeed != null) {
Arrays.fill(mySeed, (byte) 0);
}
}
}
/**
* Derive a keySet from the secret.
*
* @param pSpec the keySetSpec
* @param pSecret the secret
* @return the derived keySet
* @throws GordianException on error
*/
private GordianKeySet deriveKeySet(final GordianKeySetSpec pSpec,
final byte[] pSecret) throws GordianException {
/* Derive a shared factory */
final GordianFactory myFactory = deriveFactory(GordianFactoryType.BC, pSecret);
/* Ensure that we clear out the secret */
byte[] mySecret = null;
try {
/* Calculate the secret */
mySecret = calculateDerivedSecret(pSecret, GordianDerivationId.KEYSET, GordianParameters.SECRET_LEN.getByteLength());
/* Derive the keySet */
final GordianCoreKeySetFactory myKeySets = (GordianCoreKeySetFactory) myFactory.getKeySetFactory();
final GordianCoreKeySet myKeySet = myKeySets.createKeySet(pSpec);
myKeySet.buildFromSecret(mySecret);
return myKeySet;
} finally {
if (mySecret != null) {
Arrays.fill(mySecret, (byte) 0);
}
}
}
/**
* Derive a symKeyCipher pair from the secret.
*
* @param pCipherSpec the cipherSpec
* @param pSecret the secret
* @return the ciphers
* @throws GordianException on error
*/
private GordianSymCipher[] deriveCipher(final GordianSymCipherSpec pCipherSpec,
final byte[] pSecret) throws GordianException {
/* Derive a shared factory */
final GordianFactory myFactory = deriveFactory(GordianFactoryType.BC, pSecret);
/* Generate the key */
final GordianKey<GordianSymKeySpec> myKey = deriveKey(myFactory, pCipherSpec.getKeyType(), pSecret);
/* Create the cipher and initialise with key */
final GordianCipherFactory myCiphers = myFactory.getCipherFactory();
final GordianSymCipher myOutCipher = myCiphers.createSymKeyCipher(pCipherSpec);
final GordianSymCipher myInCipher = myCiphers.createSymKeyCipher(pCipherSpec);
/* If we need an IV */
if (pCipherSpec.needsIV()) {
/* Calculate the IV */
final byte[] myIV = deriveIV(pSecret, pCipherSpec.getIVLength());
/* Initialise the ciphers */
final GordianCipherParameters myParms = GordianCipherParameters.keyAndNonce(myKey, myIV);
myOutCipher.initForEncrypt(myParms);
myInCipher.initForDecrypt(myParms);
/* else no IV */
} else {
final GordianCipherParameters myParms = GordianCipherParameters.key(myKey);
myOutCipher.initForEncrypt(myParms);
myInCipher.initForDecrypt(myParms);
}
return new GordianSymCipher[]{myOutCipher, myInCipher};
}
/**
* Derive a streamKeyCipher pair from the secret.
*
* @param pCipherSpec the cipherSpec
* @param pSecret the secret
* @return the ciphers
* @throws GordianException on error
*/
private GordianStreamCipher[] deriveCipher(final GordianStreamCipherSpec pCipherSpec,
final byte[] pSecret) throws GordianException {
/* Derive a shared factory */
final GordianFactory myFactory = deriveFactory(GordianFactoryType.BC, pSecret);
/* Generate the key */
final GordianKey<GordianStreamKeySpec> myKey = deriveKey(myFactory, pCipherSpec.getKeyType(), pSecret);
/* Create the ciphers */
final GordianCipherFactory myCiphers = myFactory.getCipherFactory();
final GordianStreamCipher myOutCipher = myCiphers.createStreamKeyCipher(pCipherSpec);
final GordianStreamCipher myInCipher = myCiphers.createStreamKeyCipher(pCipherSpec);
/* If we need an IV */
if (pCipherSpec.needsIV()) {
/* Calculate the IV */
final byte[] myIV = deriveIV(pSecret, pCipherSpec.getIVLength());
/* Initialise the ciphers */
final GordianCipherParameters myParms = GordianCipherParameters.keyAndNonce(myKey, myIV);
myOutCipher.initForEncrypt(myParms);
myInCipher.initForDecrypt(myParms);
/* else no IV */
} else {
/* Initialise the ciphers */
final GordianCipherParameters myParms = GordianCipherParameters.key(myKey);
myOutCipher.initForEncrypt(myParms);
myInCipher.initForDecrypt(myParms);
}
return new GordianStreamCipher[]{myOutCipher, myInCipher};
}
/**
* Derive a key from the secret.
*
* @param <T> the key type
* @param pFactory the factory
* @param pKeyType the keyType
* @param pSecret the secret
* @return the key
* @throws GordianException on error
*/
private <T extends GordianKeySpec> GordianKey<T> deriveKey(final GordianFactory pFactory,
final T pKeyType,
final byte[] pSecret) throws GordianException {
/* Ensure that we clear out the key */
byte[] myKey = null;
try {
/* Calculate the secret */
myKey = calculateDerivedSecret(pSecret, GordianDerivationId.KEY, pKeyType.getKeyLength().getByteLength());
/* Derive the key */
final GordianCipherFactory myCiphers = pFactory.getCipherFactory();
final GordianCoreKeyGenerator<T> myGenerator = (GordianCoreKeyGenerator<T>) myCiphers.getKeyGenerator(pKeyType);
return myGenerator.buildKeyFromBytes(myKey);
/* Clear buffers */
} finally {
if (myKey != null) {
Arrays.fill(myKey, (byte) 0);
}
}
}
/**
* Calculate the derived IV.
*
* @param pSecret the secret
* @param pIVLen the IV length
* @return the derived IV
* @throws GordianException on error
*/
private byte[] deriveIV(final byte[] pSecret,
final int pIVLen) throws GordianException {
/* Calculate the secret */
return calculateDerivedSecret(pSecret, GordianDerivationId.IV, pIVLen);
}
/**
* Derive bytes.
*
* @param pSecret the secret
* @param pLength the length of bytes
* @return the factory
* @throws GordianException on error
*/
private byte[] deriveBytes(final byte[] pSecret,
final int pLength) throws GordianException {
/* Return the secret */
return calculateDerivedSecret(pSecret, GordianDerivationId.BYTES, pLength);
}
/**
* Calculate the derived secret.
*
* @param pSecret the secret
* @param pId the derivation Id
* @param pResultLen the length of the result
* @return the derived secret
* @throws GordianException on error
*/
byte[] calculateDerivedSecret(final byte[] pSecret,
final GordianDerivationId pId,
final int pResultLen) throws GordianException {
/* Build the 64-bit seed and create the seeded random */
final long mySeed = GordianDataConverter.byteArrayToLong(pSecret);
final Random myRandom = new Random(mySeed);
/* Protect against exceptions */
final GordianHKDFParams myParams = GordianHKDFParams.extractThenExpand(pResultLen);
try {
/* Customise the HKDF parameters */
final GordianDigestSpec myDigestSpec = new GordianDigestSpec(pId.getDigestType());
final byte[] myBytes = new byte[Long.BYTES];
myRandom.nextBytes(myBytes);
myParams.withIKM(pSecret).withIKM(pId.getId())
.withSalt(theState.getClient().getInitVector())
.withSalt(theState.getServer().getInitVector())
.withInfo(myBytes);
/* Derive the bytes */
final GordianHKDFEngine myEngine = new GordianHKDFEngine(theFactory, myDigestSpec);
return myEngine.deriveBytes(myParams);
} finally {
myParams.clearParameters();
}
}
/**
* Derived secret id.
*/
public enum GordianDerivationId {
/**
* Factory.
*/
FACTORY("Factory"),
/**
* KeySet.
*/
KEYSET("KeySet"),
/**
* Key.
*/
KEY("Key"),
/**
* IV.
*/
IV("IV"),
/**
* Bytes.
*/
BYTES("Bytes"),
/**
* Tags.
*/
TAGS("Tags"),
/**
* Composite.
*/
COMPOSITE("Composite");
/**
* The id.
*/
private final byte[] theId;
/**
* Constructor.
*
* @param pId the id
*/
GordianDerivationId(final String pId) {
theId = pId.getBytes(StandardCharsets.UTF_8);
}
/**
* Obtain the id.
*
* @return the id.
*/
byte[] getId() {
return theId;
}
/**
* Obtain digest type for id.
*
* @return the id
*/
public GordianDigestType getDigestType() {
/*
* Assign a different digestType to each Id.
* Note that each type must be available as an HMAC in JCA.
*/
switch (this) {
case FACTORY:
return GordianDigestType.SHA3;
case KEYSET:
return GordianDigestType.SHA2;
case KEY:
return GordianDigestType.SKEIN;
case IV:
return GordianDigestType.RIPEMD;
case BYTES:
return GordianDigestType.STREEBOG;
case TAGS:
return GordianDigestType.WHIRLPOOL;
case COMPOSITE:
return GordianDigestType.SM3;
default:
throw new IllegalArgumentException("Invalid ID");
}
}
}
}