/*
 * 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.
 */

/*
 * @author Werner Dittmann (Werner.Dittmann@apache.org)
 */

#include <stdio.h>
#include <openssl/evp.h>
#include <openssl/ecdsa.h>
#include <openssl/ec.h>
#include <openssl/bn.h>
#include <openssl/buffer.h>
#include <jni.h>
#include "org_apache_security_juice_generators_ECKeyPairGeneratorOpenSSL.h"
// #include "/home/wd/devhome/openssl-0.9.8/crypto/ec/ec_lcl.h"
// #include "d:/devhome/openssl-0.9.8/crypto/ec/ec_lcl.h"


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

#include "openSSLNative.h"

extern char bufferOpenSSL[1024];

int OBJ_txt2nid(const char *);

/*
 * Class:     org_apache_security_juice_generators_ECKeyPairGeneratorOpenSSL
 * Method:    generateECKeyOpenSSL
 * Signature: (Ljava/lang/String;[I)J
 */
JNIEXPORT jlong JNICALL Java_org_apache_security_juice_generators_ECKeyPairGeneratorOpenSSL_generateECKeyOpenSSL (JNIEnv *env, jclass jobj, 
														  jstring curveName, 
														  jintArray lengths) {
    jclass clazz;
    jint *lengthsArray;

    int nid;
    int ret;

    EC_KEY *ecKey;
    const EC_POINT *ecPoint;

    const char *name = (*env)->GetStringUTFChars(env, curveName, 0);
    nid = OBJ_txt2nid(name);

    if (nid == NID_undef) {
        clazz = (*env)->FindClass(env, NO_SUCH_ALGO_EX);
        BIO_snprintf(bufferOpenSSL, sizeof(bufferOpenSSL), "JuICE OpenSSL: Generate key - EC curve not supported: %s", 
		     name);
	    (*env)->ReleaseStringUTFChars(env, curveName, name);
        (*env)->ThrowNew(env, clazz, bufferOpenSSL);
	return 0L;
    }
    (*env)->ReleaseStringUTFChars(env, curveName, name);

    if ((ecKey = EC_KEY_new_by_curve_name(nid)) == NULL) {
        JUICEerr(F_initOpenSSLECDSA, ERR_R_MALLOC_FAILURE);
        clazz = (*env)->FindClass(env, OUT_OF_MEMORY_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(OUT_OF_MEMORY_MSG));
        return 0L;
    }
    ret = EC_KEY_generate_key(ecKey);

    lengthsArray = (*env)->GetIntArrayElements(env, lengths, 0);
    if (lengthsArray == NULL) {
        return 0L;
    }
	
    /*
     * Get the lengths of the generated big numbers and return
     * the lengths to caller. The caller may now allocate enough space to
     * get them using getECKeyOpenSSL(...)
     */
    lengthsArray[org_apache_security_juice_generators_ECKeyPairGeneratorOpenSSL_PRIV_OFFSET] = BN_num_bytes(EC_KEY_get0_private_key(ecKey));
    /*
     * Compute length of octet array that can hold the public key
     */
    ecPoint = EC_KEY_get0_public_key(ecKey);
    ret = EC_POINT_point2oct(EC_KEY_get0_group(ecKey), ecPoint, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL);
    lengthsArray[org_apache_security_juice_generators_ECKeyPairGeneratorOpenSSL_PUB_OFFSET] = ret;

    (*env)->ReleaseIntArrayElements(env, lengths, lengthsArray, 0);

    return (unsigned long)ecKey;

}

#ifdef _JUICE_EC_DEBUG_
/*
 * Class:     org_apache_security_juice_generators_ECKeyPairGeneratorOpenSSL
 * Method:    generateECKeyPrivOpenSSL
 * Signature: (Ljava/lang/String;[BI[I)J
 */
JNIEXPORT jlong JNICALL Java_org_apache_security_juice_generators_ECKeyPairGeneratorOpenSSL_generateECKeyPrivOpenSSL (JNIEnv *env, jclass jobj, 
														      jstring curveName, 
														      jbyteArray priv, 
														      jint privLen,
														      jintArray lengths) {
    jclass clazz;
    jint *lengthsArray;
    jbyte *privb;

    int nid;
    int ret;
    char *bnBuffer;

    EC_KEY *ecKey;
    const EC_POINT *ecPoint, *pub_key;

    const char *name = (*env)->GetStringUTFChars(env, curveName, 0);
    nid = OBJ_txt2nid(name);

    if (nid == NID_undef) {
        clazz = (*env)->FindClass(env, NO_SUCH_ALGO_EX);
        BIO_snprintf(bufferOpenSSL, sizeof(bufferOpenSSL), "JuICE OpenSSL: Generate key - EC curve not supported: %s", 
		            name);
	    (*env)->ReleaseStringUTFChars(env, curveName, name);
        (*env)->ThrowNew(env, clazz, bufferOpenSSL);
	return 0L;
    }
    (*env)->ReleaseStringUTFChars(env, curveName, name);

    if ((ecKey = EC_KEY_new_by_curve_name(nid)) == NULL) {
        JUICEerr(F_initOpenSSLECDSA, ERR_R_MALLOC_FAILURE);
        clazz = (*env)->FindClass(env, OUT_OF_MEMORY_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(OUT_OF_MEMORY_MSG));
        return 0L;
    }
    privb = (*env)->GetByteArrayElements(env, priv, 0);
    ret =  EC_KEY_set_private_key(ecKey, BN_bin2bn(privb, privLen, NULL));
    (*env)->ReleaseByteArrayElements(env, priv, privb, JNI_ABORT);

    pub_key = EC_POINT_new(ecKey->group);

    EC_POINT_mul(ecKey->group, pub_key, EC_KEY_get0_private_key(ecKey), NULL, NULL, NULL);

    ecPoint = EC_GROUP_get0_generator(ecKey->group);
    bnBuffer = BN_bn2hex(&ecPoint->X);
    printf("OpenSSL Generator X: %s\n", bnBuffer);
    OPENSSL_free(bnBuffer);
    
    bnBuffer = BN_bn2hex(&ecPoint->Y);
    printf("OpenSSL Generator Y: %s\n", bnBuffer);
    OPENSSL_free(bnBuffer);

    bnBuffer = BN_bn2hex(EC_KEY_get0_private_key(ecKey));
    printf("OpenSSL private key D: %s\n", bnBuffer);
    OPENSSL_free(bnBuffer);

    bnBuffer = BN_bn2hex(&pub_key->X);
    printf("OpenSSL Public key X: %s\n", bnBuffer);
    OPENSSL_free(bnBuffer);
    
    bnBuffer = BN_bn2hex(&pub_key->Y);
    printf("OpenSSL Public key Y: %s\n", bnBuffer);
    OPENSSL_free(bnBuffer);

    return (unsigned long)ecKey;

}
#endif

/*
 * Class:     org_apache_security_juice_generators_ECKeyPairGeneratorOpenSSL
 * Method:    getECKeyOpenSSL
 * Signature: (J[B[BI)V
 */
JNIEXPORT void JNICALL Java_org_apache_security_juice_generators_ECKeyPairGeneratorOpenSSL_getECKeyOpenSSL (JNIEnv *env, jclass jobj, 
													    jlong handle, 
													    jbyteArray priv, 
													    jbyteArray pub,
                                                        jint pubLen) {
    jbyte *privb, *pubb;
    const EC_POINT *ecPoint;
    const EC_GROUP *group;
    int ret;
    jclass clazz;

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

    /*
     * 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)
     */

    privb = (*env)->GetByteArrayElements(env, priv, 0);
    if (privb == NULL) {
        return;
    }
    BN_bn2bin(EC_KEY_get0_private_key(key), privb);
    (*env)->ReleaseByteArrayElements(env, priv, privb, 0);

    pubb = (*env)->GetByteArrayElements(env, pub, 0);
    if (pubb == NULL) {
        return;
    }
    ecPoint = EC_KEY_get0_public_key(key);
    group = EC_KEY_get0_group(key);

    ret = EC_POINT_point2oct(group, ecPoint, POINT_CONVERSION_UNCOMPRESSED, pubb, pubLen, NULL);
    if (ret == 0) {
	clazz = (*env)->FindClass(env, RUNTIME_EX);
	(*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
	return;
    }
    (*env)->ReleaseByteArrayElements(env, pub, pubb, 0);
}

