GordianKeyAlgId.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.key;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherFactory;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeyType;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeySpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeyType;
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.key.GordianKeyLengths;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacFactory;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpec;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacType;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianSipHashSpec;
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.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.nsri.NSRIObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.rosstandart.RosstandartObjectIdentifiers;
import org.bouncycastle.asn1.ua.UAObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Mappings from EncodedId to KeySpec.
 */
public class GordianKeyAlgId {
    /**
     * CipherOID branch.
     */
    private static final ASN1ObjectIdentifier KEYOID = GordianASN1Util.SYMOID.branch("2");

    /**
     * SymKeysOID branch.
     */
    private static final ASN1ObjectIdentifier SYMKEYOID = KEYOID.branch("1");

    /**
     * StreamKeysOID branch.
     */
    private static final ASN1ObjectIdentifier STREAMKEYOID = KEYOID.branch("2");

    /**
     * MacOID branch.
     */
    private static final ASN1ObjectIdentifier MACOID = GordianASN1Util.SYMOID.branch("3");

    /**
     * Map of KeySpec to Identifier.
     */
    private final Map<GordianKeySpec, AlgorithmIdentifier> theSpecMap;

    /**
     * Map of Identifier to KeySpec.
     */
    private final Map<AlgorithmIdentifier, GordianKeySpec> theIdentifierMap;

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

        /* Access the factories  */
        final GordianCipherFactory myCipherFactory = pFactory.getCipherFactory();
        final GordianMacFactory myMacFactory = pFactory.getMacFactory();

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

        /* For each KeyLength */
        final Iterator<GordianLength> myIterator = GordianKeyLengths.iterator();
        while (myIterator.hasNext()) {
            final GordianLength myKeyLen = myIterator.next();

            /* Loop through the possible SymKeySpecs */
            for (GordianSymKeySpec mySpec : myCipherFactory.listAllSupportedSymKeySpecs(myKeyLen)) {
                /* Add any non-standard symKeys */
                ensureSymKey(mySpec);
            }

            /* Loop through the possible StreamKeySpecs */
            for (GordianStreamKeySpec mySpec : myCipherFactory.listAllSupportedStreamKeySpecs(myKeyLen)) {
                /* Add any non-standard streamKeys */
                ensureStreamKey(mySpec);
            }

            /* Loop through the possible MacSpecs */
            for (GordianMacSpec mySpec : myMacFactory.listAllSupportedSpecs(myKeyLen)) {
                /* Add any non-standard macs */
                ensureMac(mySpec);
            }
        }
    }

    /**
     * Obtain Identifier for KeySpec.
     *
     * @param pSpec the keySpec.
     * @return the Identifier
     */
    public AlgorithmIdentifier getIdentifierForSpec(final GordianKeySpec pSpec) {
        return theSpecMap.get(pSpec);
    }

    /**
     * Obtain symKeySpec for Identifier.
     *
     * @param pIdentifier the identifier.
     * @return the keySpec (or null if not found)
     */
    public GordianKeySpec getKeySpecForIdentifier(final AlgorithmIdentifier pIdentifier) {
        return theIdentifierMap.get(pIdentifier);
    }

    /**
     * Add symKeySpec to map if supported and not already present.
     *
     * @param pSpec the symKeySpec
     */
    private void ensureSymKey(final GordianSymKeySpec pSpec) {
        /* If the key is not already known */
        if (!theSpecMap.containsKey(pSpec)) {
            addSymKey(pSpec);
        }
    }

    /**
     * Create Identifier for a symKeySpec.
     *
     * @param pSpec the keySpec
     */
    private void addSymKey(final GordianSymKeySpec pSpec) {
        /* Build SymKey id */
        final ASN1ObjectIdentifier myId = appendSymKeyOID(SYMKEYOID, true, pSpec);

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

    /**
     * Append Identifier for a symKeySpec.
     *
     * @param pBaseOID   the base OID
     * @param pAddLength add length to id true/false
     * @param pSpec      the keySpec
     * @return the resulting OID
     */
    public static ASN1ObjectIdentifier appendSymKeyOID(final ASN1ObjectIdentifier pBaseOID,
                                                       final boolean pAddLength,
                                                       final GordianSymKeySpec pSpec) {
        /* Create a branch based on the KeyType */
        final GordianSymKeyType myType = pSpec.getSymKeyType();
        ASN1ObjectIdentifier myId = pBaseOID.branch(Integer.toString(myType.ordinal() + 1));

        /* Add blockLength */
        final GordianLength myBlockLength = pSpec.getBlockLength();
        myId = myId.branch(Integer.toString(myBlockLength.ordinal() + 1));

        /* Add length if required */
        if (pAddLength) {
            final GordianLength myKeyLength = pSpec.getKeyLength();
            myId = myId.branch(Integer.toString(myKeyLength.ordinal() + 1));
        }

        /* Return the id */
        return myId;
    }

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

    /**
     * Add streamKeySpec to map if supported and not already present.
     *
     * @param pSpec the streamKeySpec
     */
    private void ensureStreamKey(final GordianStreamKeySpec pSpec) {
        /* If the key is not already known */
        if (!theSpecMap.containsKey(pSpec)) {
            addStreamKey(pSpec);
        }
    }

    /**
     * Create Identifier for a streamKeySpec.
     *
     * @param pSpec the keySpec
     */
    private void addStreamKey(final GordianStreamKeySpec pSpec) {
        /* Build StreamKey id */
        final ASN1ObjectIdentifier myId = appendStreamKeyOID(STREAMKEYOID, pSpec);

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

    /**
     * Append Identifier for a streamKeySpec.
     *
     * @param pBaseOID the base OID
     * @param pSpec    the keySpec
     * @return the resulting OID
     */
    public static ASN1ObjectIdentifier appendStreamKeyOID(final ASN1ObjectIdentifier pBaseOID,
                                                          final GordianStreamKeySpec pSpec) {
        /* Create a branch based on the KeyType */
        final GordianStreamKeyType myType = pSpec.getStreamKeyType();
        ASN1ObjectIdentifier myId = pBaseOID.branch(Integer.toString(myType.ordinal() + 1));

        /* Add keyLength */
        final GordianLength myKeyLength = pSpec.getKeyLength();
        myId = myId.branch(Integer.toString(myKeyLength.ordinal() + 1));

        /* Add subKeyType if required */
        if (pSpec.getSubKeyType() != null) {
            myId = myId.branch(Integer.toString(((Enum<?>) pSpec.getSubKeyType()).ordinal() + 1));
        }

        /* Return the id */
        return myId;
    }

    /**
     * Add well-known macs.
     */
    private void addWellKnownMacs() {
        addToMaps(GordianMacSpecBuilder.hMac(GordianDigestSpecBuilder.gost()), new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3411Hmac, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.hMac(GordianDigestSpecBuilder.streebog(GordianLength.LEN_256)),
                new AlgorithmIdentifier(RosstandartObjectIdentifiers.id_tc26_hmac_gost_3411_12_256, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.hMac(GordianDigestSpecBuilder.streebog(GordianLength.LEN_512)),
                new AlgorithmIdentifier(RosstandartObjectIdentifiers.id_tc26_hmac_gost_3411_12_512, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.hMac(GordianDigestSpecBuilder.sha1()), new AlgorithmIdentifier(PKCSObjectIdentifiers.id_hmacWithSHA1, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.hMac(GordianDigestSpecBuilder.sha2(GordianLength.LEN_224)), new AlgorithmIdentifier(PKCSObjectIdentifiers.id_hmacWithSHA224, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.hMac(GordianDigestSpecBuilder.sha2(GordianLength.LEN_256)), new AlgorithmIdentifier(PKCSObjectIdentifiers.id_hmacWithSHA256, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.hMac(GordianDigestSpecBuilder.sha2(GordianLength.LEN_384)), new AlgorithmIdentifier(PKCSObjectIdentifiers.id_hmacWithSHA384, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.hMac(GordianDigestSpecBuilder.sha2(GordianLength.LEN_512)), new AlgorithmIdentifier(PKCSObjectIdentifiers.id_hmacWithSHA512, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.hMac(GordianDigestSpecBuilder.sha3(GordianLength.LEN_224)), new AlgorithmIdentifier(NISTObjectIdentifiers.id_hmacWithSHA3_224, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.hMac(GordianDigestSpecBuilder.sha3(GordianLength.LEN_256)), new AlgorithmIdentifier(NISTObjectIdentifiers.id_hmacWithSHA3_256, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.hMac(GordianDigestSpecBuilder.sha3(GordianLength.LEN_384)), new AlgorithmIdentifier(NISTObjectIdentifiers.id_hmacWithSHA3_384, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.hMac(GordianDigestSpecBuilder.sha3(GordianLength.LEN_512)), new AlgorithmIdentifier(NISTObjectIdentifiers.id_hmacWithSHA3_512, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.cMac(GordianSymKeySpecBuilder.aria(GordianLength.LEN_128)), new AlgorithmIdentifier(NSRIObjectIdentifiers.id_aria128_cmac, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.cMac(GordianSymKeySpecBuilder.aria(GordianLength.LEN_192)), new AlgorithmIdentifier(NSRIObjectIdentifiers.id_aria192_cmac, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.cMac(GordianSymKeySpecBuilder.aria(GordianLength.LEN_256)), new AlgorithmIdentifier(NSRIObjectIdentifiers.id_aria256_cmac, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.kupynaMac(GordianLength.LEN_256, GordianLength.LEN_256), new AlgorithmIdentifier(UAObjectIdentifiers.dstu7564mac_256, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.kupynaMac(GordianLength.LEN_256, GordianLength.LEN_384), new AlgorithmIdentifier(UAObjectIdentifiers.dstu7564mac_384, DERNull.INSTANCE));
        addToMaps(GordianMacSpecBuilder.kupynaMac(GordianLength.LEN_256, GordianLength.LEN_512), new AlgorithmIdentifier(UAObjectIdentifiers.dstu7564mac_512, DERNull.INSTANCE));
    }

    /**
     * Add macSpec to map if supported and not already present.
     *
     * @param pSpec the macSpec
     */
    private void ensureMac(final GordianMacSpec pSpec) {
        /* If the mac is not already known */
        if (!theSpecMap.containsKey(pSpec)) {
            addMac(pSpec);
        }
    }

    /**
     * Create Identifier for a macSpec.
     *
     * @param pSpec the macSpec
     */
    private void addMac(final GordianMacSpec pSpec) {
        /* Create a branch for mac based on the MacType */
        final GordianMacType myType = pSpec.getMacType();
        ASN1ObjectIdentifier myId = MACOID.branch(Integer.toString(myType.ordinal() + 1));
        myId = myId.branch(Integer.toString(pSpec.getKeyLength().ordinal() + 1));

        /* Obtain the subSpec */
        final Object mySubSpec = pSpec.getSubSpec();
        if (mySubSpec instanceof GordianDigestSpec mySpec) {
            myId = GordianDigestAlgId.appendDigestOID(myId, mySpec);
        } else if (mySubSpec instanceof GordianSymKeySpec mySpec) {
            myId = appendSymKeyOID(myId, false, mySpec);
        } else if (mySubSpec instanceof GordianLength myLength) {
            myId = myId.branch(Integer.toString(myLength.ordinal() + 1));
        } else if (mySubSpec instanceof GordianSipHashSpec mySpec) {
            myId = myId.branch(Integer.toString(mySpec.ordinal() + 1));
        }

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