GordianEncryptorAlgId.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.encrypt;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpecBuilder;
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.encrypt.GordianSM2EncryptionSpec;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPairType;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianASN1Util;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.digest.GordianDigestAlgId;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Mappings from EncodedId to EncryptorSpec.
 */
public class GordianEncryptorAlgId {
    /**
     * EncryptorOID branch.
     */
    private static final ASN1ObjectIdentifier ENCRYPTOID = GordianASN1Util.ASYMOID.branch("3");

    /**
     * Map of EncryptorSpec to Identifier.
     */
    private final Map<GordianEncryptorSpec, AlgorithmIdentifier> theSpecMap;

    /**
     * Map of Identifier to EncryptorSpec.
     */
    private final Map<AlgorithmIdentifier, GordianEncryptorSpec> theIdentifierMap;

    /**
     * The factory.
     */
    private final GordianEncryptorFactory theFactory;

    /**
     * Constructor.
     *
     * @param pFactory the factory
     */
    GordianEncryptorAlgId(final GordianBaseFactory pFactory) {
        /* Create the maps */
        theSpecMap = new HashMap<>();
        theIdentifierMap = new HashMap<>();

        /* Access the encryptorFactory  */
        theFactory = pFactory.getAsyncFactory().getEncryptorFactory();

        /* Populate with the public standards */
        addWellKnownEncryptors();

        /* Loop through the possible AsymKeys */
        for (GordianKeyPairType myKeyType : GordianKeyPairType.values()) {
            /* Add any non-standard encryptorSpecs */
            addEncryptors(myKeyType);
        }
    }

    /**
     * Obtain Identifier for EncryptorSpec.
     *
     * @param pSpec the encryptorSpec.
     * @return the Identifier
     */
    public AlgorithmIdentifier getIdentifierForSpec(final GordianEncryptorSpec pSpec) {
        /* Handle Composite keyPairs specially */
        if (pSpec.getKeyPairType() == GordianKeyPairType.COMPOSITE) {
            final Iterator<GordianEncryptorSpec> myIterator = pSpec.encryptorSpecIterator();
            final ASN1EncodableVector ks = new ASN1EncodableVector();
            while (myIterator.hasNext()) {
                ks.add(getIdentifierForSpec(myIterator.next()));
            }
            return new AlgorithmIdentifier(MiscObjectIdentifiers.id_alg_composite, new DERSequence(ks));
        }
        return theSpecMap.get(pSpec);
    }

    /**
     * Obtain EncryptorSpec for Identifier.
     *
     * @param pIdentifier the identifier.
     * @return the encryptorSpec (or null if not found)
     */
    public GordianEncryptorSpec getSpecForIdentifier(final AlgorithmIdentifier pIdentifier) {
        /* Handle Composite keyPairs specially */
        if (MiscObjectIdentifiers.id_alg_composite.equals(pIdentifier.getAlgorithm())) {
            final List<GordianEncryptorSpec> myList = new ArrayList<>();
            final ASN1Sequence myAlgs = ASN1Sequence.getInstance(pIdentifier.getParameters());
            final Enumeration<?> en = myAlgs.getObjects();
            while (en.hasMoreElements()) {
                myList.add(getSpecForIdentifier(AlgorithmIdentifier.getInstance(en.nextElement())));
            }
            return GordianEncryptorSpecBuilder.composite(myList);
        }
        return theIdentifierMap.get(pIdentifier);
    }

    /**
     * Add WellKnown encryptors.
     */
    private void addWellKnownEncryptors() {
        addToMaps(GordianEncryptorSpecBuilder.sm2(GordianSM2EncryptionSpec.c1c2c3(GordianDigestSpecBuilder.sm3())),
                new AlgorithmIdentifier(GMObjectIdentifiers.sm2encrypt_with_sm3, DERNull.INSTANCE));
        addToMaps(GordianEncryptorSpecBuilder.sm2(GordianSM2EncryptionSpec.c1c2c3(GordianDigestSpecBuilder.sha2(GordianLength.LEN_224))),
                new AlgorithmIdentifier(GMObjectIdentifiers.sm2encrypt_with_sha224, DERNull.INSTANCE));
        addToMaps(GordianEncryptorSpecBuilder.sm2(GordianSM2EncryptionSpec.c1c2c3(GordianDigestSpecBuilder.sha2(GordianLength.LEN_256))),
                new AlgorithmIdentifier(GMObjectIdentifiers.sm2encrypt_with_sha256, DERNull.INSTANCE));
        addToMaps(GordianEncryptorSpecBuilder.sm2(GordianSM2EncryptionSpec.c1c2c3(GordianDigestSpecBuilder.sha2(GordianLength.LEN_384))),
                new AlgorithmIdentifier(GMObjectIdentifiers.sm2encrypt_with_sha384, DERNull.INSTANCE));
        addToMaps(GordianEncryptorSpecBuilder.sm2(GordianSM2EncryptionSpec.c1c2c3(GordianDigestSpecBuilder.sha2(GordianLength.LEN_512))),
                new AlgorithmIdentifier(GMObjectIdentifiers.sm2encrypt_with_sha512, DERNull.INSTANCE));
        addToMaps(GordianEncryptorSpecBuilder.sm2(GordianSM2EncryptionSpec.c1c2c3(GordianDigestSpecBuilder.whirlpool())),
                new AlgorithmIdentifier(GMObjectIdentifiers.sm2encrypt_with_whirlpool, DERNull.INSTANCE));
        addToMaps(GordianEncryptorSpecBuilder.sm2(GordianSM2EncryptionSpec.c1c2c3(GordianDigestSpecBuilder.blake2s(GordianLength.LEN_256))),
                new AlgorithmIdentifier(GMObjectIdentifiers.sm2encrypt_with_blake2s256, DERNull.INSTANCE));
        addToMaps(GordianEncryptorSpecBuilder.sm2(GordianSM2EncryptionSpec.c1c2c3(GordianDigestSpecBuilder.blake2b(GordianLength.LEN_512))),
                new AlgorithmIdentifier(GMObjectIdentifiers.sm2encrypt_with_blake2b512, DERNull.INSTANCE));
    }

    /**
     * Create Identifiers for all valid EncryptorTypes.
     *
     * @param pKeyType the keyType
     */
    private void addEncryptors(final GordianKeyPairType pKeyType) {
        for (GordianEncryptorSpec mySpec : theFactory.listAllSupportedEncryptors(pKeyType)) {
            ensureEncryptor(mySpec);
        }
    }

    /**
     * Add encryptorSpec to map if supported and not already present.
     *
     * @param pSpec the encryptorSpec
     */
    private void ensureEncryptor(final GordianEncryptorSpec pSpec) {
        /* If the encryptor is not already known */
        if (!theSpecMap.containsKey(pSpec)) {
            addEncryptor(pSpec);
        }
    }

    /**
     * Create Identifier for an encryptorSpec.
     *
     * @param pSpec the macSpec
     */
    private void addEncryptor(final GordianEncryptorSpec pSpec) {
        /* Create a branch for encryptor based on the keyType */
        final GordianKeyPairType myKeyType = pSpec.getKeyPairType();
        ASN1ObjectIdentifier myId = ENCRYPTOID.branch(Integer.toString(myKeyType.ordinal() + 1));

        /* Obtain the encryptor */
        final Object myEncryptor = pSpec.getEncryptorType();
        if (myEncryptor instanceof GordianDigestSpec mySpec) {
            myId = GordianDigestAlgId.appendDigestOID(myId.branch("2"), mySpec);
        } else if (myEncryptor instanceof GordianSM2EncryptionSpec mySpec) {
            myId = myId.branch("4").branch(Integer.toString(mySpec.getEncryptionType().ordinal() + 1));
            myId = GordianDigestAlgId.appendDigestOID(myId, mySpec.getDigestSpec());
        } else {
            myId = myId.branch("1");
        }

        /* Add the spec to the maps */
        addToMaps(pSpec, new AlgorithmIdentifier(myId, DERNull.INSTANCE));
    }

    /**
     * Add encryptor to maps.
     *
     * @param pSpec       the encryptorSpec
     * @param pIdentifier the identifier
     */
    private void addToMaps(final GordianEncryptorSpec pSpec,
                           final AlgorithmIdentifier pIdentifier) {
        theSpecMap.put(pSpec, pIdentifier);
        theIdentifierMap.put(pIdentifier, pSpec);
    }
}