/*
 * GNU.FREE 2002
 *
 * Copyright (c) 1999, 2000, 2001, 2002
 * The Free Software Foundation (www.fsf.org)
 *
 * GNU.FREE Co-ordinator: Jason Kitcat <jeep@free-project.org>
 *
 * GNU site: http://www.gnu.org/software/free/
 * 
 * FREE e-democracy site: http://www.free-project.org
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program (COPYING); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

/* $Id: RSAPKCS1.java,v 1.2 2000/05/06 13:54:56 edwin Exp $
 *
 * Copyright (C) 2000 The Cryptix Foundation Limited.
 * All rights reserved.
 * 
 * Use, modification, copying and distribution of this software is subject 
 * the terms and conditions of the Cryptix General Licence. You should have 
 * received a copy of the Cryptix General License along with this library; 
 * if not, you can download a copy from http://www.cryptix.org/ .
 */
package FreeTest;

import cryptix.provider.rsa.RawRSACipher;

import java.security.Key;                   // A Sun JCA class
import java.security.KeyException;          // A Sun JCA class
import java.security.SecureRandom;          // A Sun JCA class

/*
 * This class implements PKCS#1 v1.5 style padding for RSA.
 *
 * This is an addition to Cryptix 3.1.x, which does not support PKCS#1 padding.
 * Feel free to use it in your programs.
 *
 *
 * Note: This is for encryption only, PKCS#1 padding for Signatures is 
 *       implemented in the normal Cryptix classes.
 *
 *
 * PKCS#1 padding looks like this:
 *
 *   EB = 00 || BT || PS || 00 || D
 *
 *   where EB = Encryption Block
 *         BT = Block Type (always 0x02 for encrypted data)
 *         PS = Padding String
 *         D  = Data
 *
 * The minimum length of the Padding String PS is 8 bytes. Therefore the 
 * maximum length of the Data D is the blocksize minus 11 bytes. The Padding
 * String PS is always as long as needed to make the length of the Encryption
 * Block EB equal to the blocksize. The Padding String consists of non-zero
 * random octets.
 *
 *
 * Unfortunately it was impossible to have this class extend from 
 * java.security.PaddingScheme or java.security.Cipher because it works
 * different than other paddingschemes like PKCS#5 or PKCS#7. The code in
 * java.security.Cipher.updateInternal() is not flexible enough to handle it.
 * This class will however mimic much of the features of a normal Cipher, so
 * it can be used without much changes.
 *
 * Using this class will look something like this:
 *
 *          RSAPKCS1 rsa = new RSAPKCS1(new SecureRandom());
 *          rsa.initEncrypt(pubkey);
 *          byte[] encdata = rsa.crypt(data);
 *
 * Note that you have to encrypt all data at once, multiple calls to an update()
 * method are not allowed.
 *
 * @version 1.1 12 June 2001
 */
public class RSAPKCS1 {
    
    private SecureRandom random;
    private RawRSACipher cipher = new RawRSACipher();
    
    // Because initialising SecureRandom can take some time, use this method to
    // supply an already initialised SecureRandom.
    public RSAPKCS1(SecureRandom random) {
        this.random = random;
    }
    
    // If you don't want to supply one, we'll have to create one ourselves.
    public RSAPKCS1() {
        this.random = new SecureRandom();
    }
    
    // This is where all happens....
    private int crypt(byte[] in, int inOffset, int inLen, byte[] out, int outOffset) {
        
        // Everything has to fit into one block.
        if ( (getState() == cipher.ENCRYPT) && (inLen > (cipher.getInputBlockSize()-10)) ) {
            throw new IllegalArgumentException("inLen > PlaintextBlockSize, multiple blocks are not supported, input too large.");
        }
        // On decryption we want one full block, nothing more nothing less.
        if ( (getState() == cipher.DECRYPT) && (inLen != cipher.getInputBlockSize()) ) {
            throw new IllegalArgumentException("inLen != CiphertextBlockSize, we want one (and only one) complete block.");
        }
        // Uh oh...
        if ( (getState() != cipher.ENCRYPT) && (getState() != cipher.DECRYPT) ) {
            throw new IllegalStateException("Not in ENCRYPT or DECRYPT state (yet).");
        }
        
        if (getState() == cipher.ENCRYPT) {
            
            // Create a buffer to hold the data. Note that the buffer size is
            // one byte less than the RSA blocksize. This is because Cryptix
            // already expects the first byte to be zero to prevent that the
            // data is larger than the modulus.
            // For example: when the RSA keysize/blocksize is 1024 bits 
            // (64 bytes), the Cipher only accepts 63 bytes as input and the
            // first byte is set to zero.
            byte buffer[] = new byte[cipher.getOutputBlockSize()-1];
            // The Block Type BT. 
            buffer[0] = 0x02;
            // The Padding String PS
            for (int i=1; i<buffer.length-inLen-1; i++) {
            	byte[] b = new byte[1];
            	b[0] = 0;
            	while (b[0] == 0) random.nextBytes(b);
            	buffer[i] = b[0];
                //buffer[i] = (byte)(random.nextInt(255)+1);
            }
            // The null byte that separates the padding from the real data.
            buffer[buffer.length-inLen-1] = 0;
            // The Data D
            System.arraycopy(in,inOffset,buffer,buffer.length-inLen,inLen);
            
            // Perform the *real* RSA encryption
            return cipher.crypt(buffer,0,buffer.length,out,outOffset);
            
        } else { // DECRYPT
        
            // The size of buffer to hold the decrypted data will be one byte 
            // less than the size of the encrypted data, for the reason 
            // explained above.
            byte buffer[] = new byte[cipher.getOutputBlockSize()];
            
            int result = cipher.crypt(in,inOffset,inLen,buffer,0);
            if (result != buffer.length) {
                throw new IllegalArgumentException("The decrypted data is not a full PKCS#1 encoded data block");
            }

            // Search for the null byte that separates the padding from the data
            int pos = -1;
            for (int i=1; i<buffer.length; i++) {
                if (buffer[i] == 0) {
                    pos = i;
                    i = buffer.length;  // Ends loop
                }
            }
            
            // Whoops, no null byte found, so call 911
            if (pos == -1) {
                throw new IllegalArgumentException("The decrypted data does not contain a valid PKCS#1 encoded data block");
            }
            
            // Get the real data and drop everything else
            System.arraycopy(buffer,pos+1,out,outOffset,buffer.length-pos-1);

            // We'll return the length of the decrypted data
            return buffer.length-pos-1;
            
        }
    
    }   
    
    // Wrapper around the previous crypt(...) method.
    public byte[] crypt(byte[] in, int offset, int length) {
        byte[] out = new byte[cipher.getOutputBlockSize()];
        int outlen = crypt(in, offset, length, out, 0);
        if (outlen != out.length) {
            byte[] newout = new byte[outlen];
            System.arraycopy(out, 0, newout, 0, outlen);
            return newout;
        } else
            return out;
    }
    
    // Wrapper around the previous crypt(...) method.
    public byte[] crypt(byte[] in) {
        return crypt(in,0,in.length);
    }
    
    // Some methods that mimic java.security.Cipher behaviour
    public final int getState() { return cipher.getState(); }
    
    public final void initEncrypt(Key key) throws KeyException { cipher.initEncrypt(key); }
    public final void initDecrypt(Key key) throws KeyException { cipher.initDecrypt(key); }
    
    public final int getInputBlockSize() { return cipher.getInputBlockSize(); }
    public final int getOutputBlockSize() { return cipher.getOutputBlockSize(); }
    
}
