/*
 * Copyright 2002-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * imitations under the License.
 */

package test;

import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.security.juice.spec.F8ParameterSpec;

import org.bouncycastle.util.encoders.Hex;

/**
 * A test implementation of the SRTP F8 encryption mode (refer to RFC 3711).
 * 
 * Works with the test vectors provided in RFC3711. Next step is to bring
 * it into the native lib.
 * 
 * @author Werner Dittmann (werner@apache.org)
 * 
 */

public class OpenSSLTestF8 {

    Cipher cipherEncJuice = null;

    Cipher cipherDecJuice = null;

    byte[] cipherData = null;

    int blockSizeBit = 128; // bit

    int blockSize = 128 / 8; // bytes

    byte[] key;

    byte[] keySalt;

    byte[] payload;

    SecretKey maskedKey;

    byte[] IVAccent;

    byte[] ivBytes;

    String keyStr = "234829008467be186c3de14aae72d62c";

    String keySaltStr = "32f2870d";

    String payloadStr = "70736575646f72616e646f6d6e65737320697320746865206e6578742062657374207468696e67";

    String keyMaskStr = "32f2870d555555555555555555555555";

    String ivStr = "006e5cba50681de55c621599d462564a";
    
    long globalJ = 0;
    
    byte[] S = new byte[blockSize];

    public OpenSSLTestF8() {
        try {
            Class<?> c = Class
                    .forName("org.apache.security.juice.provider.JuiCEProviderOpenSSL");
            java.security.Security.addProvider((java.security.Provider) c
                    .newInstance());
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    protected void setup() {
        key = Hex.decode(keyStr);
        keySalt = Hex.decode(keySaltStr);
        payload = Hex.decode(payloadStr);
        ivBytes = Hex.decode(ivStr);
    }

    byte[] createKeyMask() throws Exception {
        byte[] out = new byte[blockSize];
        System.arraycopy(keySalt, 0, out, 0, keySalt.length);

        Arrays.fill(out, keySalt.length, blockSize, (byte) 0x55);
        System.out.print("Keymask: ");
        Hex.encode(out, System.out);
        System.out.println("");
        return out;
    }

    private byte[] createIVAccent(IvParameterSpec iv) throws Exception {
        cipherEncJuice.init(Cipher.ENCRYPT_MODE, maskedKey);
        byte[] ivAccent = cipherEncJuice.doFinal(iv.getIV());
        return ivAccent;
    }

    void init(SecretKey secKey, IvParameterSpec iv) throws Exception {
        maskedKey = secKey;
        IVAccent = createIVAccent(iv);

        System.out.print("IVAccent: ");
        Hex.encode(IVAccent, System.out);
        System.out.println("");
        Arrays.fill(S, (byte) 0x0);     // setup S(-1)
        cipherEncJuice.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
    }

//    byte[] ivTemp = new byte[blockSize];
    
    byte[] processBlock(byte[] in, int offset, int length) throws Exception {
        byte[] counter = new byte[4];
        counter[0] = (byte)(globalJ & 0xff);
        counter[1] = (byte)((globalJ >> 8) & 0xff);
        counter[2] = (byte)((globalJ >> 16) & 0xff);
        counter[3] = (byte)((globalJ >> 24) & 0xff);
        globalJ++;
//        System.arraycopy(IVAccent, 0, ivTemp, 0, blockSize);
//        for (int i = 0; i < 4; i++) {
//            ivTemp[ivTemp.length-1-i] = (byte)(IVAccent[IVAccent.length-1-i] ^ counter[i]); 
//        }
//        System.out.print("Xored IVAccent: ");
//        Hex.encode(ivTemp, System.out);
//        System.out.println("");
        /*
         * Now xor the previous stream key (in S) with the result of
         * IVAccent xor counter
         */
//        for (int i = 0; i < blockSize; i++) {
//            S[i] ^= ivTemp[i];
//        }
        for (int i = 0; i < blockSize; i++) {
            S[i] ^= IVAccent[i];
        }
        for (int i = 0; i < 4; i++) {
            S[S.length-1-i] ^= counter[i]; 
        }
        System.out.print("Xored S: ");
        Hex.encode(S, System.out);
        System.out.println("");
        
        cipherEncJuice.update(S, 0, blockSize, S);
        System.out.print("Next S: ");
        Hex.encode(S, System.out);
        System.out.println("");
        
        byte[] out = new byte[length];
        
        for (int i = 0; i < length; i++) {
            out[i] = (byte)(in[offset+i] ^ S[i]);
        }
        System.out.print("Ciphertext: ");
        Hex.encode(out, System.out);
        System.out.println("");
        return out;
    }
    
    void doFinal() throws Exception{
        byte[] rest = cipherEncJuice.doFinal();
        System.out.println("rest: " + rest.length);
    }
    
    void runF8() throws Exception {
        /*
         * set up test data and vectors
         */
        setup();
        /*
         * Get a AES cipher in ECB mode, nopadding. This is used to produce the
         * IV' and to produce the key stream.
         */
        cipherEncJuice = Cipher.getInstance("AES/F8/NOPADDING", "JuiCE");

        F8ParameterSpec ivp = new F8ParameterSpec(ivBytes, keySalt);
        cipherEncJuice.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), ivp);
        byte [] cipherText = cipherEncJuice.doFinal(payload);
        Hex.encode(cipherText, System.out);
        System.out.println("");
        
        cipherEncJuice.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), ivp);
        cipherText = cipherEncJuice.doFinal(cipherText);
        if (!Arrays.equals(payload, cipherText)) {
            System.out.println("Decrypted data does not match original data.");
        }
        Hex.encode(cipherText, System.out);
        System.out.println("");
       
        
//        byte[] mk = createKeyMask();
//        byte[] xoredKey = new byte[blockSize];
//        
//        for (int i = 0; i < blockSize; i++) {
//            xoredKey[i] = (byte)(key[i] ^ mk[i]);
//        }
//        System.out.print("xored key: ");
//        Hex.encode(xoredKey, System.out);
//        System.out.println("");        
//        
//        init(new SecretKeySpec(xoredKey, "AES"), ivp);
//        int length = payload.length;
//        processBlock(payload, payload.length - length , length > blockSize ? blockSize : length);
//        length -= blockSize;
//        processBlock(payload, payload.length - length , length > blockSize ? blockSize : length);
//        length -= blockSize;
//        processBlock(payload, payload.length - length , length > blockSize ? blockSize : length);
//        doFinal();
    }

    public static void main(String[] args) throws Exception {

        OpenSSLTestF8 f8 = new OpenSSLTestF8();
        f8.runF8();
    }
}
