/*
 * 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/rsa.h>
#include <openssl/bn.h>
#include <openssl/err.h>
#include <openssl/buffer.h>
#include <jni.h>
#include "org_apache_security_juice_provider_JCERSACipherOpenSSL.h"

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

#include "openSSLNative.h"

/*
 * Class:     org_apache_security_juice_provider_JCERSACipherOpenSSL
 * Method:    initOpenSSLRSA
 * Signature: ([BI[BI[BI[BI[BI[BI[BI[BI)J
 */
JNIEXPORT jlong JNICALL Java_org_apache_security_juice_provider_JCERSACipherOpenSSL_initOpenSSLRSA (JNIEnv *env, jclass jobj,
												    jbyteArray n, jint nlen,
												    jbyteArray e, jint elen, 
												    jbyteArray d, jint dlen,
												    jbyteArray p, jint plen,
												    jbyteArray q, jint qlen,
												    jbyteArray dmp1, jint dmp1len,
												    jbyteArray dmq1, jint dmq1len,
												    jbyteArray iqmp , jint iqmplen) {

    jbyte *nb, *eb, *db, *pb, *qb, *dmp1b, *dmq1b, *iqmpb;
    jclass clazz;

    RSA *key = RSA_new();
    
    if (key == NULL) {
	JUICEerr(F_initOpenSSLRSA, ERR_R_MALLOC_FAILURE);
	clazz = (*env)->FindClass(env, OUT_OF_MEMORY_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(OUT_OF_MEMORY_MSG));
	return 0L;
    }
    if (n == NULL) {
        clazz = (*env)->FindClass(env, INVALID_KEY_EX);
        (*env)->ThrowNew(env, clazz, "JuICE OpenSSL: Mandatory RSA key parameter 'n' is NULL");
	return 0L;
    }
    else {
	nb = (*env)->GetByteArrayElements(env, n, 0);
	if (nb == NULL) {
	    return 0L;
	}
	key->n = BN_bin2bn(nb, nlen, NULL);
	(*env)->ReleaseByteArrayElements(env, n, nb, JNI_ABORT);
    }

    if (e == NULL) {
        clazz = (*env)->FindClass(env, INVALID_KEY_EX);
        (*env)->ThrowNew(env, clazz, "JuICE OpenSSL: Mandatory RSA key parameter 'e' is NULL");
	return 0L;
    }
    else {
	eb = (*env)->GetByteArrayElements(env, e, 0);
	if (eb == NULL) {
	    return 0L;
	}
	key->e = BN_bin2bn(eb, elen, NULL);
	(*env)->ReleaseByteArrayElements(env, e, eb, JNI_ABORT);
    }

    if (d != NULL) {
	db = (*env)->GetByteArrayElements(env, d, 0);
	if (db == NULL) {
	    return 0L;
	}
	key->d = BN_bin2bn(db, dlen, NULL);
	(*env)->ReleaseByteArrayElements(env, d, db, JNI_ABORT);
    }

    if (p != NULL) {
	pb = (*env)->GetByteArrayElements(env, p, 0);
	if (pb == NULL) {
	    return 0L;
	}
	key->p = BN_bin2bn(pb, plen, NULL);
	(*env)->ReleaseByteArrayElements(env, p, pb, JNI_ABORT);
    }

    if (q != NULL) {
	qb = (*env)->GetByteArrayElements(env, q, 0);
	if (qb == NULL) {
	    return 0L;
	}
	key->q = BN_bin2bn(qb, qlen, NULL);
	(*env)->ReleaseByteArrayElements(env, q, qb, JNI_ABORT);
    }

    if (dmp1 != NULL) {
	dmp1b = (*env)->GetByteArrayElements(env, dmp1, 0);
	if (dmp1b == NULL) {
	    return 0L;
	}
	key->dmp1 = BN_bin2bn(dmp1b, dmp1len, NULL);
	(*env)->ReleaseByteArrayElements(env, dmp1, dmp1b, JNI_ABORT);
    }

    if (dmq1 != NULL) {
	dmq1b = (*env)->GetByteArrayElements(env, dmq1, 0);
	if (dmq1b == NULL) {
	    return 0L;
	}
	key->dmq1 = BN_bin2bn(dmq1b, dmq1len, NULL);
	(*env)->ReleaseByteArrayElements(env, dmq1, dmq1b, JNI_ABORT);
    }

    if (iqmp != NULL) {
	iqmpb = (*env)->GetByteArrayElements(env, iqmp, 0);
	if (iqmpb == NULL) {
	    return 0L;
	}
	key->iqmp = BN_bin2bn(iqmpb, iqmplen, NULL);
	(*env)->ReleaseByteArrayElements(env, iqmp, iqmpb, JNI_ABORT);
    }

//    if (d != NULL && RSA_check_key(key) > 0) {
//	printf("RSA private key OK for OpenSSL\n");
//    }

    return (unsigned long)key;
}

/*
 * Class:     org_apache_security_juice_provider_JCERSACipherOpenSSL
 * Method:    publicDecryptOpenSSLRSA
 * Signature: (I[B[BJI)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JCERSACipherOpenSSL_publicDecryptOpenSSLRSA (JNIEnv *env, jclass jobj,
													    jint flen, jbyteArray from,
													    jbyteArray to, jlong handle, jint padding) {
    jbyte *fromBytes, *toBytes;
    jclass clazz;

#if SIZEOF_INT_P == 4
    RSA *key = (RSA *)((int)(handle & 0xffffffff));
#else
    RSA *key = (RSA *)handle;
#endif
    int RSAPadding = padding;
    int ret;

    if (key == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    fromBytes = (*env)->GetByteArrayElements(env, from, 0);
    if (fromBytes == NULL) {
	return 0L;
    }
    toBytes = (*env)->GetByteArrayElements(env, to, 0);
    if (toBytes == NULL) {
	return 0L;
    }
    
    ret = RSA_public_decrypt(flen, fromBytes, toBytes, key, RSAPadding);

    (*env)->ReleaseByteArrayElements(env, from, fromBytes, 0);
    (*env)->ReleaseByteArrayElements(env, to, toBytes, 0);

    if (ret < 0) {
	clazz = (*env)->FindClass(env, RUNTIME_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: Error during RSA public decrypt"));
	return 0;
    }
    return ret;
}

/*
 * Class:     org_apache_security_juice_provider_JCERSACipherOpenSSL
 * Method:    privateDecryptOpenSSLRSA
 * Signature: (I[B[BJI)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JCERSACipherOpenSSL_privateDecryptOpenSSLRSA (JNIEnv *env, jclass jobj,
													     jint flen, jbyteArray from,
													     jbyteArray to, jlong handle, jint padding) {
    jbyte *fromBytes, *toBytes;
    jclass clazz;

#if SIZEOF_INT_P == 4
    RSA *key = (RSA *)((int)(handle & 0xffffffff));
#else
    RSA *key = (RSA *)handle;
#endif
    int RSAPadding = padding;
    int ret;

    if (key == NULL) {
	clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }

    fromBytes = (*env)->GetByteArrayElements(env, from, 0);
    if (fromBytes == NULL) {
	return 0L;
    }
    toBytes = (*env)->GetByteArrayElements(env, to, 0);
    if (toBytes == NULL) {
	return 0L;
    }
    
    ret = RSA_private_decrypt(flen, fromBytes, toBytes, key, RSAPadding);

    (*env)->ReleaseByteArrayElements(env, from, fromBytes, 0);
    (*env)->ReleaseByteArrayElements(env, to, toBytes, 0);

    if (ret < 0) {
	clazz = (*env)->FindClass(env, RUNTIME_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: Error during RSA private decrypt"));
	return 0;
    }
    return ret;
}

/*
 * Class:     org_apache_security_juice_provider_JCERSACipherOpenSSL
 * Method:    publicEncryptOpenSSLRSA
 * Signature: (I[B[BJI)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JCERSACipherOpenSSL_publicEncryptOpenSSLRSA (JNIEnv *env, jclass jobj,
													    jint flen, jbyteArray from,
													    jbyteArray to, jlong handle, jint padding) {
    jbyte *fromBytes, *toBytes;
    jclass clazz;

#if SIZEOF_INT_P == 4
    RSA *key = (RSA *)((int)(handle & 0xffffffff));
#else
    RSA *key = (RSA *)handle;
#endif
    int RSAPadding = padding;
    int ret;

    if (key == NULL) {
	clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    fromBytes = (*env)->GetByteArrayElements(env, from, 0);
    if (fromBytes == NULL) {
	return 0L;
    }
    toBytes = (*env)->GetByteArrayElements(env, to, 0);
    if (toBytes == NULL) {
	return 0L;
    }
    
    ret = RSA_public_encrypt(flen, fromBytes, toBytes, key, RSAPadding);

    (*env)->ReleaseByteArrayElements(env, from, fromBytes, 0);
    (*env)->ReleaseByteArrayElements(env, to, toBytes, 0);

    if (ret < 0) {
	clazz = (*env)->FindClass(env, RUNTIME_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: Error during RSA public encrypt"));
	return 0;
    }
    return ret;
}

/*
 * Class:     org_apache_security_juice_provider_JCERSACipherOpenSSL
 * Method:    privateEncryptOpenSSLRSA
 * Signature: (I[B[BJI)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JCERSACipherOpenSSL_privateEncryptOpenSSLRSA (JNIEnv *env, jclass jobj,
													     jint flen, jbyteArray from,
													     jbyteArray to, jlong handle, jint padding) {
    jbyte *fromBytes, *toBytes;
    jclass clazz;

#if SIZEOF_INT_P == 4
    RSA *key = (RSA *)((int)(handle & 0xffffffff));
#else
    RSA *key = (RSA *)handle;
#endif
    int RSAPadding = padding;
    int ret;

    if (key == NULL) {
	clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    fromBytes = (*env)->GetByteArrayElements(env, from, 0);
    if (fromBytes == NULL) {
	return 0L;
    }
    toBytes = (*env)->GetByteArrayElements(env, to, 0);
    if (toBytes == NULL) {
	return 0L;
    }
    
    ret = RSA_private_encrypt(flen, fromBytes, toBytes, key, RSAPadding);

    (*env)->ReleaseByteArrayElements(env, from, fromBytes, 0);
    (*env)->ReleaseByteArrayElements(env, to, toBytes, 0);

    if (ret < 0) {
	clazz = (*env)->FindClass(env, RUNTIME_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: Error during RSA public decrypt"));
	return 0;
    }
    return ret;
}

/*
 * Class:     org_apache_security_juice_provider_JCERSACipherOpenSSL
 * Method:    openSSLRSAsize
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JCERSACipherOpenSSL_openSSLRSAsize (JNIEnv *env, jclass jobj,
												   jlong handle) {
    jclass clazz;

#if SIZEOF_INT_P == 4
    RSA *key = (RSA *)((int)(handle & 0xffffffff));
#else
    RSA *key = (RSA *)handle;
#endif
    if (key == NULL) {
	clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    return RSA_size(key);
}

/*
 * Class:     org_apache_security_juice_provider_JCERSACipherOpenSSL
 * Method:    openSSLRSAFree
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_org_apache_security_juice_provider_JCERSACipherOpenSSL_openSSLRSAFree (JNIEnv * env, jclass jobj,
												   jlong handle) {
    jclass clazz;

#if SIZEOF_INT_P == 4
    RSA *key = (RSA *)((int)(handle & 0xffffffff));
#else
    RSA *key = (RSA *)handle;
#endif
    if (key == NULL) {
	clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    RSA_free(key);
    return;
}


/*
 * Class:     org_apache_security_juice_provider_JCERSACipherOpenSSL
 * Method:    openSSLRSASign
 * Signature: (I[BI[BJ)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JCERSACipherOpenSSL_openSSLRSASign
(JNIEnv *env, jclass jobj, jint digestType, jbyteArray hash, jint hashlen, jbyteArray sig, jlong handle) {

    jbyte *hashBytes, *sigBytes;
    jclass clazz;

#if SIZEOF_INT_P == 4
    RSA *key = (RSA *)((int)(handle & 0xffffffff));
#else
    RSA *key = (RSA *)handle;
#endif
    int ret;
    int len;

    if (key == NULL) {
	clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    hashBytes = (*env)->GetByteArrayElements(env, hash, 0);
    if (hashBytes == NULL) {
	return 0L;
    }
    sigBytes = (*env)->GetByteArrayElements(env, sig, 0);
    if (sigBytes == NULL) {
	return 0L;
    }
    
    ret = RSA_sign(digestType, hashBytes, hashlen, sigBytes, &len, key);
    
    (*env)->ReleaseByteArrayElements(env, hash, hashBytes, 0);
    (*env)->ReleaseByteArrayElements(env, sig, sigBytes, 0);

    if (ret == 0) {
	clazz = (*env)->FindClass(env, RUNTIME_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: Error during RSA sign"));
	return 0;
    }

    return ret;

}

/*
 * Class:     org_apache_security_juice_provider_JCERSACipherOpenSSL
 * Method:    openSSLRSAVerify
 * Signature: (I[BI[BIJ)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JCERSACipherOpenSSL_openSSLRSAVerify
(JNIEnv *env, jclass jobj, jint digestType, jbyteArray hash, jint hashlen, jbyteArray sig, jint siglen, jlong handle) {
  
    jbyte *hashBytes, *sigBytes;
    jclass clazz;

#if SIZEOF_INT_P == 4
    RSA *key = (RSA *)((int)(handle & 0xffffffff));
#else
    RSA *key = (RSA *)handle;
#endif
    int ret;

    if (key == NULL) {
	clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    hashBytes = (*env)->GetByteArrayElements(env, hash, 0);
    if (hashBytes == NULL) {
	return 0L;
    }
    sigBytes = (*env)->GetByteArrayElements(env, sig, 0);
    if (sigBytes == NULL) {
	return 0L;
    }
    
    ret = RSA_verify(digestType, hashBytes, hashlen, sigBytes, siglen, key);
    
    (*env)->ReleaseByteArrayElements(env, hash, hashBytes, 0);
    (*env)->ReleaseByteArrayElements(env, sig, sigBytes, 0);

    if (ret == 0) {
	clazz = (*env)->FindClass(env, RUNTIME_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: Error during RSA verify"));
	return 0;
    }

    return ret;
}
