/*
 * Copyright 2004 - 2013 Wayne Grant
 *           2013 - 2025 Kai Kramer
 *
 * This file is part of KeyStore Explorer.
 *
 * KeyStore Explorer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * KeyStore Explorer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with KeyStore Explorer.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kse.crypto.publickey;

import java.io.IOException;
import java.security.PublicKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.ResourceBundle;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.kse.crypto.CryptoException;
import org.kse.crypto.digest.DigestType;
import org.kse.crypto.digest.DigestUtil;

/**
 * Generator for public key identifiers of various forms.
 */
public class KeyIdentifierGenerator {
    private static ResourceBundle res = ResourceBundle.getBundle("org/kse/crypto/publickey/resources");

    private PublicKey publicKey;

    /**
     * Construct KeyIdentifierGenerator.
     *
     * @param publicKey Public key to generate identifiers for
     */
    public KeyIdentifierGenerator(PublicKey publicKey) {
        this.publicKey = publicKey;
    }

    /**
     * Generate 160 bit hash key identifier.
     *
     * @return Key identifier
     * @throws CryptoException If generation fails
     */
    public byte[] generate160BitHashId() throws CryptoException {
        /*
         * RFC 5280: The keyIdentifier is composed of the 160-bit SHA-1 hash of
         * the value of the BIT STRING subjectPublicKey (excluding the tag,
         * length, and number of unused bits)
         */

        try {
            DERBitString publicKeyBitString = encodePublicKeyAsBitString(publicKey);
            return DigestUtil.getMessageDigest(publicKeyBitString.getBytes(), DigestType.SHA1);
        } catch (IOException ex) {
            throw new CryptoException(res.getString("NoGenerateKeyIdentifier.exception.message"), ex);
        }
    }

    /**
     * Generate 64 bit hash key identifier.
     *
     * @return Key identifier
     * @throws CryptoException If generation fails
     */
    public byte[] generate64BitHashId() throws CryptoException {
        /*
         * RFC 5280: The keyIdentifier is composed of a four bit type field with
         * the value 0100 followed by the least significant 60 bits of the SHA-1
         * hash of the value of the BIT STRING subjectPublicKey (excluding the
         * tag, length, and number of unused bit string bits)
         */

        try {
            DERBitString publicKeyBitString = encodePublicKeyAsBitString(publicKey);
            byte[] hash = DigestUtil.getMessageDigest(publicKeyBitString.getBytes(), DigestType.SHA1);
            byte[] subHash = Arrays.copyOfRange(hash, 12, 20);
            subHash[0] &= 0x0F;
            subHash[0] |= 0x40;

            return subHash;
        } catch (IOException ex) {
            throw new CryptoException(res.getString("NoGenerateKeyIdentifier.exception.message"), ex);
        }
    }

    private DERBitString encodePublicKeyAsBitString(PublicKey publicKey) throws IOException {
        byte[] encodedPublicKey;

        if (publicKey instanceof RSAPublicKey) {
            encodedPublicKey = encodeRsaPublicKeyAsBitString((RSAPublicKey) publicKey);
        } else if (publicKey instanceof DSAPublicKey) {
            encodedPublicKey = encodeDsaPublicKeyAsBitString((DSAPublicKey) publicKey);
        } else {
            SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
            encodedPublicKey = publicKeyInfo.getPublicKeyData().getBytes();
        }

        return new DERBitString(encodedPublicKey);
    }

    private byte[] encodeRsaPublicKeyAsBitString(RSAPublicKey rsaPublicKey) throws IOException {
        ASN1EncodableVector vec = new ASN1EncodableVector();
        vec.add(new ASN1Integer(rsaPublicKey.getModulus()));
        vec.add(new ASN1Integer(rsaPublicKey.getPublicExponent()));

        DERSequence derSequence = new DERSequence(vec);
        return derSequence.getEncoded();
    }

    private byte[] encodeDsaPublicKeyAsBitString(DSAPublicKey dsaPublicKey) throws IOException {
        ASN1Integer pubKey = new ASN1Integer(dsaPublicKey.getY());
        return pubKey.getEncoded(ASN1Encoding.DER);
    }

}
