GordianKeyPairLockImpl.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.lock;

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.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.GordianKeyPairUsage;
import io.github.tonywasher.joceanus.gordianknot.api.cert.GordianKeyPairUse;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianAsyncFactory;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactoryType;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianEdwardsElliptic;
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.keyset.GordianKeySet;
import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianKeyPairLock;
import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianPasswordLockSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.agree.GordianCoreAgreementMessageASN1;
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.exc.GordianLogicException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keyset.GordianCoreKeySet;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keyset.GordianKeySetData;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;

import java.util.Arrays;
import java.util.Objects;

/**
 * KeyPair Lock implementation.
 */
public class GordianKeyPairLockImpl
        implements GordianKeyPairLock {
    /**
     * Server X500Name.
     */
    static final X500Name SERVER = new X500NameBuilder(BCStyle.INSTANCE).addRDN(BCStyle.CN, "Server").build();

    /**
     * The keySet.
     */
    private final GordianCoreKeySet theKeySet;

    /**
     * The lockASN1.
     */
    private final GordianKeyPairLockASN1 theLockASN1;

    /**
     * The lockBytes.
     */
    private final byte[] theLockBytes;

    /**
     * The keyPair.
     */
    private final GordianKeyPair theKeyPair;

    /**
     * Locking constructor.
     *
     * @param pLockingFactory the locking factory
     * @param pLockSpec       the passwordLockSpec
     * @param pKeyPair        the locking keyPair
     * @param pPassword       the password
     * @throws GordianException on error
     */
    public GordianKeyPairLockImpl(final GordianBaseFactory pLockingFactory,
                                  final GordianPasswordLockSpec pLockSpec,
                                  final GordianKeyPair pKeyPair,
                                  final char[] pPassword) throws GordianException {
        /* Protect from exceptions */
        byte[] myPassword = null;
        try {
            /* Create the agreement and derive the factory */
            final GordianAsyncFactory myAsyncFactory = pLockingFactory.getAsyncFactory();
            final GordianAgreementFactory myAgreeFactory = myAsyncFactory.getAgreementFactory();
            final GordianCertificate myCert = myAgreeFactory.newMiniCertificate(SERVER, pKeyPair,
                    new GordianKeyPairUsage(GordianKeyPairUse.AGREEMENT));
            final GordianAgreementSpec mySpec = getAgreementSpec(pKeyPair.getKeyPairSpec());
            final GordianAgreementParams myParams = myAgreeFactory.newAgreementParams(mySpec, GordianFactoryType.BC)
                    .setServerCertificate(myCert);
            final GordianAgreement myAgreement = myAgreeFactory.createAgreement(myParams);
            final byte[] myClientHello = myAgreement.nextMessage();
            final GordianBaseFactory myFactory = (GordianBaseFactory) myAgreement.getFactoryResult();

            /* Create a recipe */
            final GordianPasswordLockRecipe myRecipe = new GordianPasswordLockRecipe(pLockingFactory, pLockSpec);

            /* Generate the keySet */
            myPassword = GordianDataConverter.charsToByteArray(pPassword);
            theKeySet = myRecipe.processPassword(myFactory, myPassword);

            /* Create lockBytes */
            final GordianPasswordLockASN1 myLock = myRecipe.buildLockASN1(myPassword.length, null);
            theLockASN1 = new GordianKeyPairLockASN1(GordianCoreAgreementMessageASN1.getInstance(myClientHello), myLock);
            theLockBytes = theLockASN1.getEncodedBytes();
            theKeyPair = pKeyPair;

        } finally {
            if (myPassword != null) {
                Arrays.fill(myPassword, (byte) 0);
            }
        }
    }

    /**
     * UnLocking constructor.
     *
     * @param pLockingFactory the locking factory
     * @param pLockBytes      the lockBytes
     * @param pKeyPair        the keyPair
     * @param pPassword       the password
     * @throws GordianException on error
     */
    public GordianKeyPairLockImpl(final GordianBaseFactory pLockingFactory,
                                  final byte[] pLockBytes,
                                  final GordianKeyPair pKeyPair,
                                  final char[] pPassword) throws GordianException {
        this(pLockingFactory, GordianKeyPairLockASN1.getInstance(pLockBytes), pLockBytes, pKeyPair, pPassword);
    }

    /**
     * UnLocking constructor.
     *
     * @param pLockingFactory the locking factory
     * @param pLockASN1       the lockASN1
     * @param pKeyPair        the keyPair
     * @param pPassword       the password
     * @throws GordianException on error
     */
    public GordianKeyPairLockImpl(final GordianBaseFactory pLockingFactory,
                                  final GordianKeyPairLockASN1 pLockASN1,
                                  final GordianKeyPair pKeyPair,
                                  final char[] pPassword) throws GordianException {
        this(pLockingFactory, pLockASN1, pLockASN1.getEncodedBytes(), pKeyPair, pPassword);
    }

    /**
     * UnLocking constructor.
     *
     * @param pLockingFactory the locking factory
     * @param pLockASN1       the lockASN1
     * @param pLockBytes      the lockBytes
     * @param pKeyPair        the keyPair
     * @param pPassword       the password
     * @throws GordianException on error
     */
    public GordianKeyPairLockImpl(final GordianBaseFactory pLockingFactory,
                                  final GordianKeyPairLockASN1 pLockASN1,
                                  final byte[] pLockBytes,
                                  final GordianKeyPair pKeyPair,
                                  final char[] pPassword) throws GordianException {
        /* Protect from exceptions */
        byte[] myPassword = null;
        try {
            /* Store the Lock */
            theLockBytes = pLockBytes;
            theLockASN1 = pLockASN1;
            theKeyPair = pKeyPair;

            /* Resolve the agreement */
            final GordianAsyncFactory myAsyncFactory = pLockingFactory.getAsyncFactory();
            final GordianAgreementFactory myAgreeFactory = myAsyncFactory.getAgreementFactory();
            final byte[] myClientHello = theLockASN1.getAgreement().getEncodedBytes();
            final GordianAgreement myAgreement = myAgreeFactory.parseAgreementMessage(myClientHello);
            final GordianCertificate myCert = myAgreeFactory.newMiniCertificate(SERVER, pKeyPair,
                    new GordianKeyPairUsage(GordianKeyPairUse.AGREEMENT));
            final GordianAgreementParams myParams = myAgreement.getAgreementParams().setServerCertificate(myCert);
            myAgreement.updateParams(myParams);
            final GordianBaseFactory myFactory = (GordianBaseFactory) myAgreement.getFactoryResult();

            /* Resolve the recipe */
            myPassword = GordianDataConverter.charsToByteArray(pPassword);
            final GordianPasswordLockRecipe myRecipe = new GordianPasswordLockRecipe(pLockingFactory, myPassword.length, theLockASN1.getPasswordLock());

            /* Process the password, creating keySet */
            theKeySet = myRecipe.processPassword(myFactory, myPassword);

        } finally {
            if (myPassword != null) {
                Arrays.fill(myPassword, (byte) 0);
            }
        }
    }

    @Override
    public GordianKeySet getLockedObject() {
        return theKeySet;
    }

    @Override
    public GordianKeyPairLockASN1 getLockASN1() {
        return theLockASN1;
    }

    @Override
    public byte[] getLockBytes() {
        return theLockBytes;
    }

    @Override
    public GordianKeyPair getKeyPair() {
        return theKeyPair;
    }

    /**
     * Obtain AgreementSpec for asymKeySpec.
     *
     * @param pKeySpec the keySpec
     * @return the agreementSpec
     * @throws GordianException on error
     */
    private static GordianAgreementSpec getAgreementSpec(final GordianKeyPairSpec pKeySpec) throws GordianException {
        /* Determine KDF type */
        final GordianAgreementKDF myKDFType = GordianEdwardsElliptic.CURVE25519.equals(pKeySpec.getSubKeyType())
                ? GordianAgreementKDF.SHA256KDF
                : GordianAgreementKDF.SHA512KDF;

        /* Determine AgreementType - either ANON or KEM */
        if (GordianAgreementType.ANON.isSupported(pKeySpec.getKeyPairType())) {
            return new GordianAgreementSpec(pKeySpec, GordianAgreementType.ANON, myKDFType);
        }
        if (GordianAgreementType.KEM.isSupported(pKeySpec.getKeyPairType())) {
            return new GordianAgreementSpec(pKeySpec, GordianAgreementType.KEM, GordianAgreementKDF.NONE);
        }
        throw new GordianLogicException("Invalid KeyPair type");
    }

    /**
     * Obtain the byte length of the encoded sequence.
     *
     * @return the byte length
     */
    public static int getEncodedLength() {
        return GordianPasswordLockASN1.getEncodedLength(GordianKeySetData.getEncryptionLength(GordianParameters.SEED_LEN.getByteLength()));
    }

    @Override
    public boolean equals(final Object pThat) {
        /* Handle the trivial cases */
        if (pThat == this) {
            return true;
        }
        if (pThat == null) {
            return false;
        }

        /* Make sure that the object is the same class */
        if (!(pThat instanceof GordianKeyPairLockImpl)) {
            return false;
        }

        /* Access the target field */
        final GordianKeyPairLockImpl myThat = (GordianKeyPairLockImpl) pThat;

        /* Check differences */
        return theKeySet.equals(myThat.getLockedObject())
                && Arrays.equals(theLockBytes, myThat.getLockBytes());
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(theKeySet)
                + Arrays.hashCode(theLockBytes);
    }
}