/*
 * Copyright 2001-2003, Danger, Inc.  All Rights Reserved.
 * This file is subject to the Danger, Inc. Sample Code License,
 * which is provided in the file SAMPLE_CODE_LICENSE.
 * Copies are also available from http://developer.danger.com/
 * 
 * author: robey, Sep 2005
 */

package com.danger.terminal;

import java.math.BigInteger;
import net.lag.crai.*;
import danger.crypto.Cipher;
import danger.crypto.CipherException;
import danger.crypto.DSAKey;
import danger.crypto.DSAKeyException;
import danger.crypto.Hash;
import danger.crypto.HMAC;
import danger.crypto.MD5;
import danger.crypto.RSAKey;
import danger.crypto.RSAKeyException;
import danger.crypto.SHA;
import danger.crypto.SecureRandom;


public class DangerCRAI
    implements Crai
{
    private static class RSAPrivateKey
        implements CraiPrivateKey
    {
        public
        RSAPrivateKey (RSAKey rsa)
        {
            mRSA = rsa;
        }
        
        public byte[]
        sign (byte[] data, int off, int len)
            throws CraiException
        {
            try {
                byte[] blob = pkcs1imify(mRSA, data, off, len);
                return mRSA.sign(blob, 0, blob.length);
            } catch (RSAKeyException x) {
                throw new CraiException("RSA exception: " + x);
            }
        }
        
        
        private RSAKey mRSA;
    }
    
    private static class DSAPrivateKey
        implements CraiPrivateKey
    {
        public DSAPrivateKey (DSAKey dsa)
        {
            mDSA = dsa;
        }
        
        public byte[]
        sign (byte[] data, int off, int len)
            throws CraiException
        {
            try {
                return mDSA.sign(data, off, len);
            } catch (DSAKeyException x) {
                throw new CraiException("DSA exception: " + x);
            }
        }
        
        
        private DSAKey mDSA;
    }
    
    private static class RSAPublicKey
        implements CraiPublicKey
    {
        public RSAPublicKey (RSAKey rsa)
        {
            mRSA = rsa;
        }

        public boolean
        verify (byte[] data, int off, int len, byte[] signature)
            throws CraiException
        {
            try {
                byte[] blob = pkcs1imify(mRSA, data, off, len);
                return mRSA.verify(blob, 0, blob.length, signature);
            } catch (RSAKeyException x) {
                throw new CraiException("RSA exception: " + x);
            }
        }

        
        private RSAKey mRSA;
    }
    
    private static class DSAPublicKey
        implements CraiPublicKey
    {
        public DSAPublicKey (DSAKey dsa)
        {
            mDSA = dsa;
        }
        
        public boolean
        verify (byte[] data, int off, int len, byte[] signature)
            throws CraiException
        {
            try {
                return mDSA.verify(data, off, len, signature);
            } catch (DSAKeyException x) {
                throw new CraiException("DSA exception: " + x);
            }
        }
        
        
        private DSAKey mDSA;
    }
    
    private static class DangerHash
        implements CraiDigest
    {
        public
        DangerHash (Hash h)
        {
            mHash = h;
        }
        
        public void
        reset ()
        {
            mHash.reset();
        }
        
        public void
        update (byte[] data, int off, int len)
        {
            mHash.update(data, off, len);
        }
        
        public byte[]
        finish ()
        {
            byte[] b = mHash.digest();
            mHash.reset();
            return b;
        }
        
        public void
        finish (byte[] out, int off)
        {
            mHash.digest(out, off);
            mHash.reset();
        }

        
        private Hash mHash;
    }
    
    private static class DangerHMAC
        implements CraiDigest
    {
        public DangerHMAC (HMAC h)
        {
            mHMAC = h;
        }
        
        public void
        reset ()
        {
            mHMAC.reset();
        }
        
        public void
        update (byte[] data, int off, int len)
        {
            mHMAC.update(data, off, len);
        }
        
        public byte[]
        finish ()
        {
            byte[] b = mHMAC.digest();
            mHMAC.reset();
            return b;
        }
        
        public void
        finish (byte[] out, int off)
        {
            mHMAC.digest(out, off);
            mHMAC.reset();
        }
        
        
        private HMAC mHMAC;
    }
    
    private static class DangerCipher
        implements CraiCipher
    {
        public
        DangerCipher (Cipher c)
        {
            mCipher = c;
        }
        
        public void
        initEncrypt (byte[] key, byte[] iv)
            throws CraiException
        {
            try {
                mCipher.init(key, iv);
            } catch (CipherException x) {
                throw new CraiException("cipher exception: " + x);
            }
            mEncrypt = true;
        }
        
        public void
        initDecrypt (byte[] key, byte[] iv)
            throws CraiException
        {
            try {
                mCipher.init(key, iv);
            } catch (CipherException x) {
                throw new CraiException("cipher exception: " + x);
            }
            mEncrypt = false;
        }

        public void
        process (byte[] in, int off, int len, byte[] out, int off_out)
        {
            if (mEncrypt) {
                mCipher.encrypt(in, off, out, off_out, len);
            } else {
                mCipher.decrypt(in, off, out, off_out, len);
            }
        }

        
        private Cipher mCipher;
        private boolean mEncrypt;
    }
    
    
    // convert a java BigInteger to a native (fast) BigInteger
    private static danger.crypto.BigInteger
    javaBigIntegerAsNative (BigInteger x)
    {
        return new danger.crypto.BigInteger(x.toByteArray());
    }
    
    private static BigInteger
    nativeBigIntegerAsJava (danger.crypto.BigInteger x)
    {
        return new BigInteger(x.toByteArray());
    }
    
    
    public CraiRandom
    getPRNG ()
    {
        return sPRNG;
    }
    
    public CraiPrivateKey
    makePrivateRSAKey (BigInteger n, BigInteger d)
    {
        RSAKey rsa = new RSAKey();
        rsa.setN(javaBigIntegerAsNative(n));
        rsa.setD(javaBigIntegerAsNative(d));
        return new RSAPrivateKey(rsa);
    }
    
    public CraiPrivateKey
    makePrivateDSAKey (BigInteger x, BigInteger p, BigInteger q, BigInteger g)
    {
        DSAKey dsa = new DSAKey();
        dsa.setX(javaBigIntegerAsNative(x));
        dsa.setP(javaBigIntegerAsNative(p));
        dsa.setQ(javaBigIntegerAsNative(q));
        dsa.setG(javaBigIntegerAsNative(g));
        return new DSAPrivateKey(dsa);
    }

    public CraiPublicKey
    makePublicRSAKey (BigInteger n, BigInteger e)
    {
        RSAKey rsa = new RSAKey();
        rsa.setN(javaBigIntegerAsNative(n));
        rsa.setE(javaBigIntegerAsNative(e));
        return new RSAPublicKey(rsa);
    }
    
    public CraiPublicKey
    makePublicDSAKey (BigInteger y, BigInteger p, BigInteger q, BigInteger g)
    {
        DSAKey dsa = new DSAKey();
        dsa.setY(javaBigIntegerAsNative(y));
        dsa.setP(javaBigIntegerAsNative(p));
        dsa.setQ(javaBigIntegerAsNative(q));
        dsa.setG(javaBigIntegerAsNative(g));
        return new DSAPublicKey(dsa);
    }
    
    public CraiDigest
    makeSHA1 ()
    {
        return new DangerHash(new SHA());
    }
    
    public CraiDigest
    makeMD5 ()
    {
        return new DangerHash(new MD5());
    }
    
    public CraiDigest
    makeSHA1HMAC (byte[] key)
    {
        HMAC h = new HMAC(new SHA());
        h.init(key);
        return new DangerHMAC(h);
    }
    
    public CraiDigest
    makeMD5HMAC (byte[] key)
    {
        HMAC h = new HMAC(new MD5());
        h.init(key);
        return new DangerHMAC(h);
    }

    public CraiCipher
    getCipher (CraiCipherAlgorithm algorithm)
        throws CraiException
    {
        Cipher c = null;
        if (algorithm == CraiCipherAlgorithm.DES3_CBC) {
            c = Cipher.getInstance("des3-cbc");
        } else if (algorithm == CraiCipherAlgorithm.AES_CBC) {
            c = Cipher.getInstance("aes-cbc");
        } else if (algorithm == CraiCipherAlgorithm.BLOWFISH_CBC) {
            c = Cipher.getInstance("blowfish-cbc");
        }
        if (c == null) {
            throw new CraiException("unknown cipher");
        }

        return new DangerCipher(c);
    }

    public BigInteger
    modPow (BigInteger b, BigInteger e, BigInteger m)
    {
        return nativeBigIntegerAsJava(javaBigIntegerAsNative(b).modPow(javaBigIntegerAsNative(e), javaBigIntegerAsNative(m)));
    }

    
    /* turn a 20-byte SHA1 hash into a blob of data as large as the key's
     * N, using PKCS1's "emsa-pkcs1-v1_5" encoding.  totally bizarre.
     * also, skip the initial zero byte, to work around a quirk in the
     * danger RSA code (oops).
     */
    private static byte[]
    pkcs1imify (RSAKey rsa, byte[] data, int off, int len)
    {
        SHA sha = new SHA();
        sha.update(data, off, len);
        byte[] digest = sha.digest();
        
        final byte[] SHA1_DIGESTINFO = { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
                                         0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
        int nBytes = ((rsa.getN().bitLength() + 7) >> 3) - 1;
        int fillerBytes = nBytes - SHA1_DIGESTINFO.length - digest.length - 2;
        byte[] out = new byte[nBytes];
        out[0] = 1;
        for (int i = 0; i < fillerBytes; i++) {
            out[1 + i] = -1;
        }
        out[fillerBytes + 2] = 0;
        System.arraycopy(SHA1_DIGESTINFO, 0, out, fillerBytes + 2, SHA1_DIGESTINFO.length);
        System.arraycopy(digest, 0, out, out.length - digest.length, digest.length);
        return out;
    }
    
    
    static CraiRandom sPRNG = new CraiRandom() {
        public void getBytes (byte[] b)
        {
            SecureRandom.getBytes(b);
        }
    };
}
