/**
 * Copyright 2003-2016 SSHTOOLS Limited. All Rights Reserved.
 *
 * For product documentation visit https://www.sshtools.com/
 *
 * This file is part of J2SSH Maverick.
 *
 * J2SSH Maverick is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * J2SSH Maverick 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 J2SSH Maverick.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sshtools.ssh.components.jce;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import com.sshtools.ssh.SshException;
import com.sshtools.ssh.SshKeyFingerprint;
import com.sshtools.ssh.components.SshDsaPublicKey;
import com.sshtools.ssh.components.SshPublicKey;
import com.sshtools.util.ByteArrayReader;
import com.sshtools.util.ByteArrayWriter;
import com.sshtools.util.SimpleASNWriter;

/**
 * A DSA public key implementation which uses a JCE provider.
 * 
 * @author Lee David Painter
 */
public class Ssh2DsaPublicKey implements SshDsaPublicKey {

	protected DSAPublicKey pubkey;

	public Ssh2DsaPublicKey() {
	}

	public Ssh2DsaPublicKey(DSAPublicKey pub) {
		this.pubkey = pub;
	}

	public Ssh2DsaPublicKey(BigInteger p, BigInteger q, BigInteger g,
			BigInteger y) throws NoSuchAlgorithmException,
			InvalidKeySpecException {

		KeyFactory keyFactory = JCEProvider
				.getProviderForAlgorithm(JCEAlgorithms.JCE_DSA) == null ? KeyFactory
				.getInstance(JCEAlgorithms.JCE_DSA) : KeyFactory.getInstance(
				JCEAlgorithms.JCE_DSA,
				JCEProvider.getProviderForAlgorithm(JCEAlgorithms.JCE_DSA));
		KeySpec publicKeySpec = new DSAPublicKeySpec(y, p, q, g);
		pubkey = (DSAPublicKey) keyFactory.generatePublic(publicKeySpec);
	}

	/**
	 * Get the algorithm name for the public key.
	 * 
	 * @return the algorithm name, for example "ssh-dss"
	 * @todo Implement this com.sshtools.ssh.SshPublicKey method
	 */
	public String getAlgorithm() {
		return "ssh-dss";
	}

	/**
	 * 
	 * @return the bit length of the public key
	 * @todo Implement this com.sshtools.ssh.SshPublicKey method
	 */
	public int getBitLength() {
		return pubkey.getParams().getP().bitLength();
	}

	/**
	 * Encode the public key into a blob of binary data, the encoded result will
	 * be passed into init to recreate the key.
	 * 
	 * @return an encoded byte array
	 * @throws SshException
	 * @todo Implement this com.sshtools.ssh.SshPublicKey method
	 */
	public byte[] getEncoded() throws SshException {
		ByteArrayWriter baw = new ByteArrayWriter();
		try {

			baw.writeString(getAlgorithm());
			baw.writeBigInteger(pubkey.getParams().getP());
			baw.writeBigInteger(pubkey.getParams().getQ());
			baw.writeBigInteger(pubkey.getParams().getG());
			baw.writeBigInteger(pubkey.getY());

			return baw.toByteArray();
		} catch (IOException ioe) {
			throw new SshException("Failed to encoded DSA key",
					SshException.INTERNAL_ERROR, ioe);
		} finally {
			try {
				baw.close();
			} catch (IOException e) {
			}
		}
	}

	/**
	 * 
	 * @return java.lang.String
	 * @throws SshException
	 * @todo Implement this com.sshtools.ssh.SshPublicKey method
	 */
	public String getFingerprint() throws SshException {
		return SshKeyFingerprint.getFingerprint(getEncoded());
	}

	/**
	 * Initialize the public key from a blob of binary data.
	 * 
	 * @param blob
	 *            byte[]
	 * @param start
	 *            int
	 * @param len
	 *            int
	 * @throws SshException
	 * @todo Implement this com.sshtools.ssh.SshPublicKey method
	 */
	public void init(byte[] blob, int start, int len) throws SshException {

		ByteArrayReader bar = new ByteArrayReader(blob, start, len);

		try {
			DSAPublicKeySpec dsaKey;

			// Extract the key information
			String header = bar.readString();

			if (!header.equals(getAlgorithm())) {
				throw new SshException("The encoded key is not DSA",
						SshException.INTERNAL_ERROR);
			}

			BigInteger p = bar.readBigInteger();
			BigInteger q = bar.readBigInteger();
			BigInteger g = bar.readBigInteger();
			BigInteger y = bar.readBigInteger();
			dsaKey = new DSAPublicKeySpec(y, p, q, g);

			KeyFactory kf = JCEProvider
					.getProviderForAlgorithm(JCEAlgorithms.JCE_DSA) == null ? KeyFactory
					.getInstance(JCEAlgorithms.JCE_DSA) : KeyFactory
					.getInstance(JCEAlgorithms.JCE_DSA, JCEProvider
							.getProviderForAlgorithm(JCEAlgorithms.JCE_DSA));
			pubkey = (DSAPublicKey) kf.generatePublic(dsaKey);

		} catch (Exception ex) {
			throw new SshException(
					"Failed to obtain DSA key instance from JCE",
					SshException.INTERNAL_ERROR, ex);

		} finally {
			try {
				bar.close();
			} catch (IOException e) {
			}
		}
	}

	/**
	 * Verify the signature.
	 * 
	 * @param signature
	 *            byte[]
	 * @param data
	 *            byte[]
	 * @return <code>true</code> if the signature was produced by the
	 *         corresponding private key that owns this public key, otherwise
	 *         <code>false</code>.
	 * @throws SshException
	 * @todo Implement this com.sshtools.ssh.SshPublicKey method
	 */
	public boolean verifySignature(byte[] signature, byte[] data)
			throws SshException {

		ByteArrayReader bar = new ByteArrayReader(signature);

		try {

			if (signature.length != 40 // 160 bits
					&& signature.length != 56 // 224 bits
					&& signature.length != 64) { // 256 bits

				byte[] sig = bar.readBinaryString();

				// log.debug("Signature blob is " + new String(sig));
				String header = new String(sig);

				if (!header.equals("ssh-dss")) {
					throw new SshException("The encoded signature is not DSA",
							SshException.INTERNAL_ERROR);
				}

				signature = bar.readBinaryString();
			}

			int numSize = signature.length / 2;

			// Using a SimpleASNWriter
			ByteArrayOutputStream r = new ByteArrayOutputStream();
			ByteArrayOutputStream s = new ByteArrayOutputStream();
			SimpleASNWriter asn = new SimpleASNWriter();
			asn.writeByte(0x02);

			if (((signature[0] & 0x80) == 0x80) && (signature[0] != 0x00)) {
				r.write(0);
				r.write(signature, 0, numSize);
			} else {
				r.write(signature, 0, numSize);
			}

			asn.writeData(r.toByteArray());
			asn.writeByte(0x02);

			if (((signature[numSize] & 0x80) == 0x80)
					&& (signature[numSize] != 0x00)) {
				s.write(0);
				s.write(signature, numSize, numSize);
			} else {
				s.write(signature, numSize, numSize);
			}

			asn.writeData(s.toByteArray());

			SimpleASNWriter asnEncoded = new SimpleASNWriter();
			asnEncoded.writeByte(0x30);
			asnEncoded.writeData(asn.toByteArray());

			byte[] encoded = asnEncoded.toByteArray();

			Signature sig = JCEProvider
					.getProviderForAlgorithm(JCEAlgorithms.JCE_SHA1WithDSA) == null ? Signature
					.getInstance(JCEAlgorithms.JCE_SHA1WithDSA)
					: Signature
							.getInstance(
									JCEAlgorithms.JCE_SHA1WithDSA,
									JCEProvider
											.getProviderForAlgorithm(JCEAlgorithms.JCE_SHA1WithDSA));
			sig.initVerify(pubkey);
			sig.update(data);

			return sig.verify(encoded);
		} catch (Exception ex) {
			throw new SshException(SshException.JCE_ERROR, ex);
		} finally {
			try {
				bar.close();
			} catch (IOException e) {
			}
		}
	}

	public boolean equals(Object obj) {
		if (obj instanceof SshDsaPublicKey) {
			try {
				return (((SshPublicKey) obj).getFingerprint()
						.equals(getFingerprint()));
			} catch (SshException ex) {
			}
		}

		return false;
	}

	public int hashCode() {
		try {
			return getFingerprint().hashCode();
		} catch (SshException ex) {
			return 0;
		}
	}

	public BigInteger getG() {
		return pubkey.getParams().getG();
	}

	public BigInteger getP() {
		return pubkey.getParams().getP();
	}

	public BigInteger getQ() {
		return pubkey.getParams().getQ();
	}

	public BigInteger getY() {
		return pubkey.getY();
	}
}