package ssh.RSA;

import ssh.*;
import Tools.*;
import java.io.*;
import java.math.*;
import java.util.*;
import java.security.interfaces.*;

/**
 * The RSAKey class holds half of an RSA key pair, and performs
 * RSA encryption and decryption (or signing and verifying) operations.
 *
 * @author Jon Howell <jonh@cs.dartmouth.edu>
 * @rcs $Id: RSAKey.java,v 1.4 2000/05/22 01:36:09 jonh Exp $
 * @license This code is Copyright 1999 Jon Howell.
 * It is available for use under the GNU Public
 * License, available at: http://www.gnu.org/copyleft/gpl.html 
 */

public class RSAKey
	implements Serializable,
		java.security.interfaces.RSAPublicKey,
		java.security.interfaces.RSAPrivateKey {
// Implementations of j.s.i.RSA*Key are hacks until I get rid of
// this class entirely, in favor of the Java standard implementations.
// Darn it all, being ahead of the curve.

	public int			bits;
	public BigInteger	exponent;
	public BigInteger	modulus;

	public static RSAKey nullKey() {
		RSAKey r = new RSAKey();
		r.bits = 0;
		r.exponent = new BigInteger("0");
		r.modulus = new BigInteger("0");
		return r;
	}

	public static RSAKey fromRSAPublicKey(RSAPublicKey pk) {
		RSAKey r = new RSAKey();
		r.exponent = pk.getPublicExponent();
		r.modulus = pk.getModulus();
		r.bits = r.modulus.bitLength();
		return r;
	}

	public static RSAKey fromRSAPrivateKey(RSAPrivateKey pk) {
		RSAKey r = new RSAKey();
		r.exponent = pk.getPrivateExponent();
		r.modulus = pk.getModulus();
		r.bits = r.modulus.bitLength();
		return r;
	}

	public static RSAKey readSsh(DataInput di)
		throws IOException {

		RSAKey key = new RSAKey();
		key.bits = di.readInt();
		key.exponent = StreamExtras.readBigInteger(di);
		key.modulus = StreamExtras.readBigInteger(di);
		key.checkLength();
		return key;
	}
		
	public static RSAKey readSerialized(DataInput di)
		throws IOException {
		// Uses a primitive byte array, that's really an
		// object stream in disguise.
		// I can't remember why readSsh() wasn't good enough for here;
		// perhaps it was an unrelated bug?
		int len = di.readInt();
		byte[] ba = new byte[len];
		di.readFully(ba);
		ByteArrayInputStream baos = new ByteArrayInputStream(ba);
		ObjectInputStream ois = new ObjectInputStream(baos);
		RSAKey key;
		try {
			key = (RSAKey) ois.readObject();
		} catch (ClassNotFoundException ex) {
			throw new IOException(ex.toString());
		}

		key.checkLength();
		return key;
	}

	void checkLength() {
		// TODO: crummy way to report this; mostly here to see
		// whether this ever actually happens.
		// Yes it does. I'm not sure I care.
		// The protocol shouldn't rely on how a key is transmitted.
//		if (bits != modulus.bitLength()) {
//			System.err.println(
//				"Read an RSAKey where reported bits ("+bits
//				+") != actual bitLength ("
//				+modulus.bitLength()+")");
//			// bits = modulus.bitLength(); // would that make sense?
//		}
	}

	public void readAsciiSsh(InputStream is)
		throws IOException {
		// readLine() the hard way :v( (Don't want to buffer input stream.)
		StringBuffer sb = new StringBuffer();
		int c;
		do {
			c = is.read();
			if (c==-1) {
				throw new EOFException();
			}
			sb.append((char) c);
		} while (c!='\n');

		StringTokenizer st = new StringTokenizer(sb.toString());
		bits = Integer.parseInt(st.nextToken());
		exponent = new BigInteger(st.nextToken());
		modulus = new BigInteger(st.nextToken());

		if (bits!=modulus.bitLength()) {
			System.err.println(
				"readAsciiSsh(): Read an RSAKey where reported bits ("+bits
				+") != actual bitLength ("
				+modulus.bitLength()+")");
		}
	}

	public void writeSsh(DataOutput dop)
		throws IOException {

		dop.writeInt(bits);
		StreamExtras.writeBigInteger(dop, exponent);
		StreamExtras.writeBigInteger(dop, modulus);
	}

	public void writeSerialized(DataOutput dop)
		throws IOException {

		byte[] ba = toByteArray();
		dop.writeInt(ba.length);
		dop.write(ba);
	}

	public byte[] toByteArray() {
		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(this);
			return baos.toByteArray();
		} catch (IOException ex) {
			throw new Error("shouldn't happen");
			// ByteArrayOutputStreams shouldn't throw IOExceptions.
		}
	}

	public BigInteger encrypt(byte[] from, SshRandom random) {
		return encrypt(from, 0, from.length, random);
	}

	public BigInteger encrypt(byte[] from, int fromoff, int fromlen,
							SshRandom random) {
		return cryptBasic(randomPad(from, fromoff, fromlen, random));
	}

	// pad a buffer before encrypting
	public BigInteger randomPad(byte[] from, int fromoff, int fromlen,
							SshRandom random) {
		if ((fromlen<<3)+10 > modulus.bitLength()) {
			// not enough bits in modulus to encrypt all data
			return null;
		}

		// public-key encrypted data must be represented
		// as 0x00, 0x02, nonzero random padding, 0x00,
		// then the data. The total number of bytes must be equal
		// to the number of bytes needed to represent the encrypting modulus.
		int modlen = modulus.bitLength()/8;
		int padlen = modlen - 3 - fromlen;

		// shift in MSB 0 -- done. :v) Shift in next MSB 2:
		BigInteger input = BigInteger.valueOf(2L);
		// shift in padlen bytes of padding
		for (int i=0; i<padlen; i++) {
			input = input.shiftLeft(8);
			BigInteger rand = BigInteger.valueOf(
					(long) random.nextNonzeroByte());
			input = input.or(rand);
		}
		// shift in a zero sentinel byte
		input = input.shiftLeft(8);
		// shift in data
		for (int i=0; i<fromlen; i++) {
			input = input.shiftLeft(8);
			BigInteger val = BigInteger.valueOf((long)
				(from[fromoff+i] & 0xff));
			input = input.or(val);
		}

		return input;
	}

	// decrypt and remove the padding
	public byte[] decrypt(BigInteger input) {
		return unpad(cryptBasic(input));
	}
	
	public byte[] unpad(BigInteger input) {
		byte[] oba = input.toByteArray();

		// find 0 sentinel byte
		int i;
		for (i=0; i<oba.length; i++) {
			if (oba[i]==0) {
				break;
			}
		}

		if (i==oba.length) {
			// no zero byte found: decryption failed
			return null;
		}

		// remaining bytes are data
		byte[] out = new byte[oba.length-i-1];
		System.arraycopy(oba, i+1, out, 0, out.length);
		return out;
	}

	// the most basic operation; no padding
	public BigInteger cryptBasic(BigInteger input) {
		return input.modPow(exponent, modulus);
	}

	public boolean equals(Object o) {
		if (!(o instanceof RSAKey))
			return false;
		RSAKey k2 = (RSAKey) o;
		return bits==k2.bits
			&& exponent.equals(k2.exponent)
			&& modulus.equals(k2.modulus);
	}

	public int hashCode() {
		return bits ^ exponent.hashCode() ^ modulus.hashCode();
	}

	public BigInteger getModulus() {
		return modulus;
	}

	// This hack implementation of RSAPrivateKey/RSAPublicKey does not
	// distinguish between whether this is the private or public half
	// of the key.
	public BigInteger getPrivateExponent() {
		return exponent;
	}

	public BigInteger getPublicExponent() {
		return exponent;
	}

	public String getAlgorithm() {
		return "RSA";	// almost certainly not the right string
	}

	public String getFormat() {
		return null;
	}

	public byte[] getEncoded() {
		return null;
	}
}
