/*
 * Copyright 2002-2006 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.
 */

#include <stdio.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/buffer.h>
#include <jni.h>
#include "org_apache_security_juice_provider_JCEBlockCipherOpenSSL.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "openSSLNative.h"

/* Definitions for specialised final functions that use 10126 padding */
int EVP_EncryptFinal_ex_ISO10126(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
int EVP_DecryptFinal_ex_ISO10126(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

/* Definitions for specialised final functions that use zero byte padding */
int EVP_EncryptFinal_ex_Zero(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
int EVP_DecryptFinal_ex_Zero(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);


void hexdump1(FILE *f,const char *title,const unsigned char *s,int l) {
    int n=0;

    fprintf(f,"%s",title);
    for( ; n < l ; ++n)
    {
	if((n%16) == 0)
	    fprintf(f,"\n%04x",n);
	fprintf(f," %02x",s[n]);
    }
    fprintf(f,"\n");
}

static char buf[256];
char bufferOpenSSL[1024];

char *getErrorStringOpenSSL(char *text) {
    unsigned long l;
    const char *file,*data;
    int line,flags;


    l=ERR_get_error_line_data(&file,&line,&data,&flags);
    ERR_error_string_n(l, buf, sizeof buf);
    BIO_snprintf(bufferOpenSSL, sizeof(bufferOpenSSL), "%s:%s:%s:%d:%s", text, buf,
		 file, line, (flags & ERR_TXT_STRING) ? data : "");
    return bufferOpenSSL;
}



/*
 * Class:     org_bouncycastle_jce_provider_JCEBlockCipherOpenSSL
 * Method:    initOpenSSLEvp
 * Signature: (Ljava/lang/String;Z[BI[BI)J
 */

JNIEXPORT jlong JNICALL Java_org_apache_security_juice_provider_JCEBlockCipherOpenSSL_initOpenSSLEvp
(JNIEnv *env, jobject jobj, jstring algoName, jboolean encrypt, jbyteArray key, jint klen, 
 jbyteArray iv, jint ivlen, jint pad, jint keyHandling, jint effectiveBits) {

    const EVP_CIPHER *c;
    EVP_CIPHER_CTX *ctx;
    int ret = 0;
    jbyte *keyBytes = NULL;
    jbyte *ivBytes = NULL;
    jclass clazz;

    const char *algo = (*env)->GetStringUTFChars(env, algoName, 0);

    c = EVP_get_cipherbyname(algo);

    if (!c) {
        clazz = (*env)->FindClass(env, NO_SUCH_ALGO_EX);
        BIO_snprintf(bufferOpenSSL, sizeof(bufferOpenSSL), "JuICE OpenSSL: cipher algorithm not supported: %s", 
		     algo);
	(*env)->ReleaseStringUTFChars(env, algoName, algo);
        (*env)->ThrowNew(env, clazz, bufferOpenSSL);
	return 0L;
    }
    (*env)->ReleaseStringUTFChars(env, algoName, algo);

    if (keyHandling == org_apache_security_juice_provider_JCEBlockCipherOpenSSL_KEY_HANDLING_NONE &&
	klen != c->key_len) {
        BIO_snprintf(bufferOpenSSL, sizeof(bufferOpenSSL), "JuICE OpenSSL: key length doesn't match, got %d expected %d", 
		     klen, c->key_len);
        clazz = (*env)->FindClass(env, INVALID_KEY_EX);
        (*env)->ThrowNew(env, clazz, bufferOpenSSL);
	return 0L;
    }
    ctx = malloc(sizeof (EVP_CIPHER_CTX));
    if (ctx == NULL) {
	JUICEerr(F_initOpenSSLEvp, ERR_R_MALLOC_FAILURE);
	clazz = (*env)->FindClass(env, OUT_OF_MEMORY_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(OUT_OF_MEMORY_MSG));
	return 0L;
    }

    EVP_CIPHER_CTX_init(ctx);

/*
 * The key setup for RC2 is somewhat tricky. 
 *
 * For RC2: in a first step initialize the cipher context (ctx) with the Cipher, the IV and
 * the encrypt flag. In the second step set the effective bits to use and the lenght of the
 * key (in bytes). In the third step initialize the key. When initializing the key the 
 * CipherInit_ex functions call the RC2 key setup function that needs the parameters set in
 * the second step.
 * RC4 is similar, only that RC4 does not use the effective bits parameter.
 */

    // Get pointer to key, if NULL we are "OutOfMemory"
    keyBytes = (*env)->GetByteArrayElements(env, key, 0);
    if (keyBytes == NULL) {
	return 0L;
    }
    if (ivlen > 0) {
	// Get pointer to IV, if NULL we are "OutOfMemory"
	ivBytes = (*env)->GetByteArrayElements(env, iv, 0);
	if (ivBytes == NULL) {
	    return 0L;
	}
        if (keyHandling == org_apache_security_juice_provider_JCEBlockCipherOpenSSL_KEY_HANDLING_NONE) {
	    ret = EVP_CipherInit_ex(ctx, c, NULL, keyBytes, ivBytes, encrypt);
        } else if (keyHandling == org_apache_security_juice_provider_JCEBlockCipherOpenSSL_KEY_HANDLING_RC2) {
            ret = EVP_CipherInit_ex(ctx, c, NULL, NULL, ivBytes, encrypt);
            if (ret) {
       	        EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, effectiveBits, NULL);
                EVP_CIPHER_CTX_set_key_length(ctx, klen);
                ret = EVP_CipherInit_ex(ctx, NULL, NULL, keyBytes, ivBytes, -1);
            }
        } else if (keyHandling == org_apache_security_juice_provider_JCEBlockCipherOpenSSL_KEY_HANDLING_RC4) {
            ret = EVP_CipherInit_ex(ctx, c, NULL, NULL, ivBytes, encrypt);
            if (ret) {
                EVP_CIPHER_CTX_set_key_length(ctx, klen);
                ret = EVP_CipherInit_ex(ctx, NULL, NULL, keyBytes, ivBytes, -1);
            }
        }
	(*env)->ReleaseByteArrayElements(env, iv, ivBytes, JNI_ABORT);
    }
    else {
        if (keyHandling == org_apache_security_juice_provider_JCEBlockCipherOpenSSL_KEY_HANDLING_NONE) {
	    ret = EVP_CipherInit_ex(ctx, c, NULL, keyBytes, NULL, encrypt);
        } else if (keyHandling == org_apache_security_juice_provider_JCEBlockCipherOpenSSL_KEY_HANDLING_RC2) {
            ret = EVP_CipherInit_ex(ctx, c, NULL, NULL, NULL, encrypt);
            if (ret) {
		EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, effectiveBits, NULL);
                EVP_CIPHER_CTX_set_key_length(ctx, klen);
                ret = EVP_CipherInit_ex(ctx, NULL, NULL, keyBytes, NULL, -1);
            }
        } else if (keyHandling == org_apache_security_juice_provider_JCEBlockCipherOpenSSL_KEY_HANDLING_RC4) {
            ret = EVP_CipherInit_ex(ctx, c, NULL, NULL, NULL, encrypt);
            if (ret) {
                EVP_CIPHER_CTX_set_key_length(ctx, klen);
                ret = EVP_CipherInit_ex(ctx, NULL, NULL, keyBytes, NULL, -1);
            }
        }
    }
    (*env)->ReleaseByteArrayElements(env, key, keyBytes, JNI_ABORT);
    if(ret == 0) {
        EVP_CIPHER_CTX_cleanup(ctx);
        free(ctx);
        clazz = (*env)->FindClass(env, RUNTIME_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: Cipher initialization failed"));
        return 0;
    }
    if (pad == org_apache_security_juice_provider_JCEBlockCipherOpenSSL_NOPAD) {
        ctx->flags |= EVP_CIPH_NO_PADDING;
    }
    return (unsigned long)ctx;
}


/*
 * Class:     org_bouncycastle_jce_provider_JCEBlockCipherOpenSSL
 * Method:    processBytesOpenSSL
 * Signature: ([BII[BII)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JCEBlockCipherOpenSSL_processBytesOpenSSL
(JNIEnv *env, jobject jobj, jlong handle, jbyteArray input, jint inOff, jint inLen, jbyteArray output, jint outOff) {
    int ret = 0;
    int outLen = -1;

    jbyte* inBytes, *outBytes;
    jclass clazz;


#if SIZEOF_INT_P == 4
    EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX *)((int)(handle & 0xffffffff));
#else
    EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX *)handle;
#endif

    if (ctx == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(CONTEXT_NULL_MSG));
    }
    /*
     * We check if the pointer of the array opeartion is NULL. In that
     * case an "OutOfMemoryError" occured. Return immediatly. The
     * exception will be handled by the java code (according to JNI
     * documentation)
     */

    inBytes = (*env)->GetByteArrayElements(env, input, 0);
    if (inBytes == NULL) {
	return 0;
    }
    // Get pointer to output, if NULL we are "OutOfMemory"
    outBytes = (*env)->GetByteArrayElements(env, output, 0);
    if (outBytes == NULL) {
	return 0;
    }
    ret = EVP_CipherUpdate(ctx, outBytes+outOff, &outLen, inBytes+inOff, inLen);

    (*env)->ReleaseByteArrayElements(env, input, inBytes, JNI_ABORT);
    (*env)->ReleaseByteArrayElements(env, output, outBytes, 0);

    if(ret == 0) {
        EVP_CIPHER_CTX_cleanup(ctx);
        free(ctx);
        clazz = (*env)->FindClass(env, RUNTIME_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: error during block cipher update"));
        return 0;
    }
    return outLen;
}


/*
 * Class:     org_bouncycastle_jce_provider_JCEBlockCipherOpenSSL
 * Method:    doFinalOpenSSL
 * Signature: (J[BI)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JCEBlockCipherOpenSSL_doFinalOpenSSL
(JNIEnv *env, jobject jobj, jlong handle, jbyteArray output, jint outOff, jint pad) {
    int ret = 0;
    int outLen = -1;
    jbyte* outBytes;
    jclass clazz;

#if SIZEOF_INT_P == 4
    EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX *)((int)(handle & 0xffffffff));
#else
    EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX *)handle;
#endif

    if (ctx == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(CONTEXT_NULL_MSG));
    }
    outBytes = (*env)->GetByteArrayElements(env, output, 0);
    if (outBytes == NULL) {
	return 0;
    }
    if (ctx->encrypt) {
	if (pad == org_apache_security_juice_provider_JCEBlockCipherOpenSSL_ISO10126) {
	    ret = EVP_EncryptFinal_ex_ISO10126(ctx, outBytes+outOff, &outLen);
	} else  if (pad == org_apache_security_juice_provider_JCEBlockCipherOpenSSL_ZERO_PAD) {
	    ret = EVP_EncryptFinal_ex_Zero(ctx, outBytes+outOff, &outLen);
	}
	else {
	    ret = EVP_EncryptFinal_ex(ctx, outBytes+outOff, &outLen);
	}
    }    
    else {
	if (pad == org_apache_security_juice_provider_JCEBlockCipherOpenSSL_ISO10126) {
	    ret = EVP_DecryptFinal_ex_ISO10126(ctx, outBytes+outOff, &outLen);
	} else if (pad == org_apache_security_juice_provider_JCEBlockCipherOpenSSL_ZERO_PAD) {
	    ret = EVP_DecryptFinal_ex_Zero(ctx, outBytes+outOff, &outLen);
	}
	else {
	    ret = EVP_DecryptFinal_ex(ctx, outBytes+outOff, &outLen);
	}
    }
    (*env)->ReleaseByteArrayElements(env, output, outBytes, 0);

    EVP_CIPHER_CTX_cleanup(ctx);
    free(ctx);

    if(ret == 0) {
        clazz = (*env)->FindClass(env, RUNTIME_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: error during block cipher doFinal"));
        return 0;
    }
    return outLen;
}

/*
 * The following methods were copied from OpenSSL/crypto/evp/evp_enc.c . OpenSSL supports
 * PKCS5/PKCS7 padding only. The following methods implement the padding types:
 * 
 * ISO10126 - padds with random values, the last padding byte is the padding length
 * ZERO_PAD - padds with zero bytes, no padding length is stored
 */


int EVP_EncryptFinal_ex_ISO10126(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl) {
    int n, ret;
    unsigned int b, bl;

    b=ctx->cipher->block_size;
    OPENSSL_assert(b <= sizeof ctx->buf);
    if (b == 1) {
	*outl=0;
	return 1;
    }
    bl=ctx->buf_len;
    if (ctx->flags & EVP_CIPH_NO_PADDING) {
	if(bl) {
#ifdef EVP_F_EVP_ENCRYPTFINAL_EX
	    EVPerr(EVP_F_EVP_ENCRYPTFINAL_EX,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);
#else
	    EVPerr(EVP_F_EVP_ENCRYPTFINAL,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);
#endif
	    return 0;
	}
	*outl = 0;
	return 1;
    }

    n = b - bl;
    RAND_bytes(&ctx->buf[bl], b-1);
    ctx->buf[b-1] = n;
    ret = ctx->cipher->do_cipher(ctx,out,ctx->buf,b);
    if(ret) {
	*outl=b;
    }
    return ret;
}


int EVP_DecryptFinal_ex_ISO10126(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl) {
    int i,n;
    unsigned int b;

    *outl=0;
    b=ctx->cipher->block_size;
    if (ctx->flags & EVP_CIPH_NO_PADDING) {
	if(ctx->buf_len) {
#ifdef EVP_F_EVP_DECRYPTFINAL_EX
	    EVPerr(EVP_F_EVP_DECRYPTFINAL_EX,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);	    
#else
	    EVPerr(EVP_F_EVP_DECRYPTFINAL,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);
#endif
	    return 0;
	}
	*outl = 0;
	return 1;
    }
    if (b > 1) {
        if (ctx->buf_len || !ctx->final_used) {
#ifdef EVP_F_EVP_DECRYPTFINAL_EX
	    EVPerr(EVP_F_EVP_DECRYPTFINAL_EX,EVP_R_WRONG_FINAL_BLOCK_LENGTH);
#else
	    EVPerr(EVP_F_EVP_DECRYPTFINAL,EVP_R_WRONG_FINAL_BLOCK_LENGTH);
#endif
	    return(0);
	}
	OPENSSL_assert(b <= sizeof ctx->final);
	n=ctx->final[b-1];
	if (n > (int)b) {
#ifdef EVP_F_EVP_DECRYPTFINAL_EX
	    EVPerr(EVP_F_EVP_DECRYPTFINAL_EX,EVP_R_BAD_DECRYPT);
#else
	    EVPerr(EVP_F_EVP_DECRYPTFINAL,EVP_R_BAD_DECRYPT);
#endif
	    return(0);
	}
	n=ctx->cipher->block_size-n;
	for (i=0; i<n; i++) {
	    out[i]=ctx->final[i];
	}
	*outl=n;
    }
    else {
	*outl=0;
    }
    return(1);
}


int EVP_EncryptFinal_ex_Zero(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl) {
    int n, ret;
    unsigned int i, b, bl;

    b=ctx->cipher->block_size;
    OPENSSL_assert(b <= sizeof ctx->buf);
    if (b == 1) {
	*outl=0;
	return 1;
    }
    bl=ctx->buf_len;
    if (ctx->flags & EVP_CIPH_NO_PADDING) {
	if(bl) {
#ifdef EVP_F_EVP_ENCRYPTFINAL_EX
	    EVPerr(EVP_F_EVP_ENCRYPTFINAL_EX,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);
#else
	    EVPerr(EVP_F_EVP_ENCRYPTFINAL,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);
#endif
	    return 0;
	}
	*outl = 0;
	return 1;
    }

    n=b-bl;
    for (i=bl; i<b; i++) {
	ctx->buf[i] = 0;
    }
    ret=ctx->cipher->do_cipher(ctx,out,ctx->buf,b);


    if(ret) {
	*outl=b;
    }

    return ret;
}


int EVP_DecryptFinal_ex_Zero(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl) {
    int i,n;
    unsigned int b;

    *outl=0;
    b=ctx->cipher->block_size;
    if (ctx->flags & EVP_CIPH_NO_PADDING) {
	if(ctx->buf_len) {
#ifdef EVP_F_EVP_DECRYPTFINAL_EX
	    EVPerr(EVP_F_EVP_DECRYPTFINAL_EX,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);	    
#else
	    EVPerr(EVP_F_EVP_DECRYPTFINAL,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);
#endif
	    return 0;
	}
	*outl = 0;
	return 1;
    }
    if (b > 1) {
        if (ctx->buf_len || !ctx->final_used) {
#ifdef EVP_F_EVP_DECRYPTFINAL_EX
	    EVPerr(EVP_F_EVP_DECRYPTFINAL_EX,EVP_R_WRONG_FINAL_BLOCK_LENGTH);
#else
	    EVPerr(EVP_F_EVP_DECRYPTFINAL,EVP_R_WRONG_FINAL_BLOCK_LENGTH);
#endif
	    return(0);
	}
	OPENSSL_assert(b <= sizeof ctx->final);
	n=ctx->final[b-1];
	if (n > (int)b) {
#ifdef EVP_F_EVP_DECRYPTFINAL_EX
	    EVPerr(EVP_F_EVP_DECRYPTFINAL_EX,EVP_R_BAD_DECRYPT);
#else
	    EVPerr(EVP_F_EVP_DECRYPTFINAL,EVP_R_BAD_DECRYPT);
#endif
	    return(0);
	}
	for (i=0; i<n; i++) {
	    if (ctx->final[--b] != 0) {
#ifdef EVP_F_EVP_DECRYPTFINAL_EX
		EVPerr(EVP_F_EVP_DECRYPTFINAL_EX,EVP_R_BAD_DECRYPT);
#else
		EVPerr(EVP_F_EVP_DECRYPTFINAL,EVP_R_BAD_DECRYPT);
#endif
		return(0);
	    }
	}
	n=ctx->cipher->block_size-n;
	for (i=0; i<n; i++) {
	    out[i]=ctx->final[i];
	}
	*outl=n;
    }
    else {
	*outl=0;
    }
    return(1);
}
