GordianCoreAgreementParams.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.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.GordianAgreementType;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.cert.GordianCertificate;
import io.github.tonywasher.joceanus.gordianknot.api.cert.GordianKeyPairUse;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamCipherSpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymCipherSpec;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactoryType;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySetSpec;
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.GordianBaseData;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.cipher.GordianCoreCipherFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianLogicException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keyset.GordianCoreKeySetFactory;
import java.util.Arrays;
import java.util.Objects;
/**
* Key Agreement Parameters Implementation.
*/
public class GordianCoreAgreementParams
implements GordianAgreementParams {
/**
* The factory.
*/
private final GordianBaseFactory theFactory;
/**
* Is this a client or server parameters.
*/
private final boolean isClient;
/**
* The Id.
*/
private Long theId;
/**
* The Spec.
*/
private final GordianAgreementSpec theSpec;
/**
* The ResultType.
*/
private final Object theResultType;
/**
* The ClientCertificate.
*/
private GordianCertificate theClient;
/**
* The ServerCertificate.
*/
private GordianCertificate theServer;
/**
* The SignerCertificate.
*/
private GordianCertificate theSigner;
/**
* The SignatureSpec.
*/
private GordianSignatureSpec theSignSpec;
/**
* The Additional data.
*/
private byte[] theAdditional;
/**
* Constructor.
*
* @param pSupplier the supplier
* @param pSpec the agreement Spec
* @param pResultType the resultType
* @throws GordianException on error
*/
GordianCoreAgreementParams(final GordianCoreAgreementSupplier pSupplier,
final GordianAgreementSpec pSpec,
final Object pResultType) throws GordianException {
isClient = true;
theFactory = pSupplier.getFactory();
theSpec = pSpec;
checkResultType(pResultType);
theResultType = pResultType;
}
/**
* Constructor.
*
* @param pBuilder the builder
*/
GordianCoreAgreementParams(final GordianCoreAgreementBuilder pBuilder) {
/* Copy all fields */
final GordianCoreAgreementSupplier mySupplier = pBuilder.getSupplier();
final GordianCoreAgreementState myState = pBuilder.getState();
isClient = false;
theId = mySupplier.getNextId();
theFactory = pBuilder.getSupplier().getFactory();
theSpec = myState.getSpec();
theResultType = myState.getResultType();
theClient = myState.getClient().getCertificate();
theServer = myState.getServer().getCertificate();
theSigner = myState.getSignerCertificate();
theSignSpec = myState.getSignSpec();
}
/**
* Constructor.
*
* @param pSource the source parameters to copy
*/
GordianCoreAgreementParams(final GordianCoreAgreementParams pSource) {
/* Copy all fields */
isClient = pSource.isClient();
theId = pSource.getId();
theFactory = pSource.theFactory;
theSpec = pSource.getAgreementSpec();
theResultType = pSource.getResultType();
theClient = pSource.getClientCertificate();
theServer = pSource.getServerCertificate();
theSigner = pSource.getSignerCertificate();
theSignSpec = pSource.getSignatureSpec();
theAdditional = pSource.theAdditional;
}
/**
* is this a client parameters.
*
* @return true/false
*/
boolean isClient() {
return isClient;
}
/**
* Obtain the id.
*
* @return the id
*/
Long getId() {
return theId;
}
@Override
public GordianAgreementSpec getAgreementSpec() {
return theSpec;
}
@Override
public Object getResultType() {
return theResultType;
}
/**
* Check the resultType is valid.
*
* @param pResultType the resultType
* @throws GordianException on error
*/
private void checkResultType(final Object pResultType) throws GordianException {
/* No need to check FactoryType */
if (pResultType instanceof GordianFactoryType) {
return;
}
/* Validate a keySetSpec */
if (pResultType instanceof GordianKeySetSpec mySpec) {
/* Check Spec */
final GordianCoreKeySetFactory myKeySetFactory = (GordianCoreKeySetFactory) theFactory.getKeySetFactory();
myKeySetFactory.checkKeySetSpec(mySpec);
return;
}
/* Validate a symCipherSpec */
if (pResultType instanceof GordianSymCipherSpec mySpec) {
/* Check Spec */
final GordianCoreCipherFactory myCipherFactory = (GordianCoreCipherFactory) theFactory.getCipherFactory();
myCipherFactory.checkSymCipherSpec(mySpec);
return;
}
/* Validate a streamCipherSpec */
if (pResultType instanceof GordianStreamCipherSpec mySpec) {
/* Check Spec */
final GordianCoreCipherFactory myCipherFactory = (GordianCoreCipherFactory) theFactory.getCipherFactory();
myCipherFactory.checkStreamCipherSpec(mySpec);
return;
}
/* Validate a byte array */
if (pResultType instanceof Integer myInt) {
if (myInt <= 0) {
throw new GordianLogicException("Invalid length for byteArray");
}
return;
}
/* Invalid resultType */
throw new GordianLogicException("Invalid resultType");
}
@Override
public GordianCertificate getClientCertificate() {
return theClient;
}
@Override
public GordianCertificate getServerCertificate() {
return theServer;
}
@Override
public GordianCertificate getSignerCertificate() {
return theSigner;
}
@Override
public GordianSignatureSpec getSignatureSpec() {
return theSignSpec;
}
@Override
public byte[] getAdditionalData() {
return theAdditional == null ? null : theAdditional.clone();
}
@Override
public GordianAgreementParams setClientCertificate(final GordianCertificate pClient) throws GordianException {
/* Not allowed for server parameters */
if (!isClient) {
throw new GordianDataException("Client Certificate cannot be changed for server");
}
/* If we have a client certificate */
final GordianAgreementSpec mySpec = getAgreementSpec();
final GordianAgreementType myType = mySpec.getAgreementType();
if (pClient != null) {
/* Check that the keySpec matches the agreement and that we have a private key */
final GordianKeyPair myKeyPair = pClient.getKeyPair();
if (myType.isSigned() || myType.isAnonymous()) {
throw new GordianDataException("Client Certificate not supported for agreement");
}
if (!Objects.equals(mySpec.getKeyPairSpec(), myKeyPair.getKeyPairSpec())) {
throw new GordianDataException("Client Certificate not valid for agreement");
}
if (!pClient.getUsage().hasUse(GordianKeyPairUse.AGREEMENT)) {
throw new GordianDataException("Client Certificate must be capable of keyAgreement");
}
if (myKeyPair.isPublicOnly()) {
throw new GordianDataException("Client Certificate must supply privateKey");
}
} else if (!myType.isSigned() && !myType.isAnonymous()) {
throw new GordianDataException("Null Client Certificate not allowed");
}
/* Create new updated parameters */
final GordianCoreAgreementParams myParams = new GordianCoreAgreementParams(this);
myParams.theClient = pClient;
return myParams;
}
@Override
public GordianAgreementParams setServerCertificate(final GordianCertificate pServer) throws GordianException {
/* If we have a server certificate */
final GordianAgreementSpec mySpec = getAgreementSpec();
if (pServer != null) {
/* Check that the keySpec matches the agreement */
final GordianKeyPair myKeyPair = pServer.getKeyPair();
if (mySpec.getAgreementType().isSigned()) {
throw new GordianDataException("Server Certificate not supported for agreement");
}
if (!Objects.equals(mySpec.getKeyPairSpec(), myKeyPair.getKeyPairSpec())) {
throw new GordianDataException("Server Certificate not valid for agreement");
}
if (!pServer.getUsage().hasUse(GordianKeyPairUse.AGREEMENT)) {
throw new GordianDataException("Server Certificate must be capable of keyAgreement");
}
/* If we are a server */
if (!isClient) {
/* Perform additional checks */
if (myKeyPair.isPublicOnly()) {
throw new GordianDataException("Server Certificate must supply privateKey");
}
/* Check that we match the existing server certificate */
if (!Arrays.equals(theServer.getEncoded(), pServer.getEncoded())) {
throw new GordianDataException("Server Certificate must match requested certificate");
}
}
} else if (!mySpec.getAgreementType().isSigned()) {
throw new GordianDataException("Null Server Certificate not allowed");
}
/* Create new updated parameters */
final GordianCoreAgreementParams myParams = new GordianCoreAgreementParams(this);
myParams.theServer = pServer;
return myParams;
}
@Override
public GordianAgreementParams setSigner(final GordianCertificate pSigner) throws GordianException {
final GordianSignatureFactory mySignFactory = theFactory.getAsyncFactory().getSignatureFactory();
final GordianSignatureSpec mySignSpec = pSigner == null ? null : mySignFactory.defaultForKeyPair(pSigner.getKeyPair().getKeyPairSpec());
return setSigner(pSigner, mySignSpec);
}
@Override
public GordianAgreementParams setSigner(final GordianCertificate pSigner,
final GordianSignatureSpec pSignSpec) throws GordianException {
/* Not allowed for client parameters */
if (isClient) {
throw new GordianDataException("Signer Certificate cannot be set for client");
}
/* If we have a signer certificate */
final GordianAgreementSpec mySpec = getAgreementSpec();
if (pSigner != null) {
/* Check that we require a signer */
if (!mySpec.getAgreementType().isSigned()) {
throw new GordianDataException("Signer Certificate not allowed");
}
/* Check that certificate can sign data */
if (!pSigner.getUsage().hasUse(GordianKeyPairUse.SIGNATURE)) {
throw new GordianDataException("Certificate must be capable of signing data");
}
/* Check that signSpec is valid for keyPair */
final GordianSignatureFactory mySignFactory = theFactory.getAsyncFactory().getSignatureFactory();
if (!mySignFactory.validSignatureSpecForKeyPair(pSigner.getKeyPair(), pSignSpec)) {
throw new GordianDataException(GordianBaseData.getInvalidText(pSignSpec));
}
} else if (mySpec.getAgreementType().isSigned()) {
throw new GordianDataException("Null Signer Certificate not allowed");
}
/* Create new updated parameters */
final GordianCoreAgreementParams myParams = new GordianCoreAgreementParams(this);
myParams.theSigner = pSigner;
myParams.theSignSpec = pSignSpec;
return myParams;
}
@Override
public GordianAgreementParams setAdditionalData(final byte[] pData) throws GordianException {
/* Only allowed if KDFType is not NONE */
if (pData != null
&& GordianAgreementKDF.NONE.equals(theSpec.getKDFType())) {
throw new GordianDataException("Additional Data not allowed for KDFType NONE");
}
/* Create new updated parameters */
final GordianCoreAgreementParams myParams = new GordianCoreAgreementParams(this);
myParams.theAdditional = pData == null ? null : pData.clone();
return myParams;
}
}