/*
 * 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_provider_JDKDSASignerOpenSSL.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_provider_JDKDSASignerOpenSSL
 * Method:    initOpenSSLECDSA
 * Signature: (Ljava/lang/String;[BI[BI)J
 */
JNIEXPORT jlong JNICALL Java_org_apache_security_juice_provider_JDKDSASignerOpenSSL_initOpenSSLECDSA (JNIEnv * env, jclass jobj, 
                                                                                                      jstring curveName, 
                                                                                                      jbyteArray q, jint qlen, 
                                                                                                      jbyteArray d, jint dlen) {
    jclass clazz;
    jbyte *qb, *db;

    int nid;
    int ret;

    EC_GROUP *group;
    EC_KEY *ecKey;
    EC_POINT *ecPoint;

    /*
     * To initialize an EC_KEY we need the structure EC_GROUP that holds the
     * predefined ellipitc curves. To find this we take the curve's name and
     * lookup its NID. Once we have that we can get the curve group.
     */

    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: EC curve not supported: %s", 
		     name);
	    (*env)->ReleaseStringUTFChars(env, curveName, name);
        (*env)->ThrowNew(env, clazz, bufferOpenSSL);
	    return 0L;
    }
    (*env)->ReleaseStringUTFChars(env, curveName, name);

    group = EC_GROUP_new_by_curve_name(nid);
    if (group == 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;
    }

    if ((ecKey = EC_KEY_new()) == NULL) {
        EC_GROUP_free(group);
        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;
    }
    
    if (EC_KEY_set_group(ecKey, group) == 0) {
        EC_KEY_free(ecKey);
        EC_GROUP_free(group);
        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;
    }

    /*
     * If qlen is set then we handle a public key setup here. This is somewhat more
     * complicated than the private key. The public key is handed to use as an
     * encoded byte array that contains all the data for the public key. This encoded
     * data is an EC_POINT, thus we have to decode this data into an EC_POINT
     * data structure. This is the job of EC_POINT_oct2point. ("oct" stands for
     * octet string - good old ITU/ASN.1 wording). After initializing the public key
     * with this EC_POINT we can release it.
     *
     * The private key is just a Bignumber. Take the byte array, convert it to a bignumber
     * and set it in the key structure.
     */
    if (qlen > 0) {
        if ((ecPoint = EC_POINT_new(group)) == NULL) {
            EC_GROUP_free(group);
            EC_KEY_free(ecKey);
            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;
        }
    
        qb = (*env)->GetByteArrayElements(env, q, 0);
        if (qb == NULL) {
            EC_GROUP_free(group);
            EC_KEY_free(ecKey);
            EC_POINT_free(ecPoint);
            return 0L;
        }
        ret = EC_POINT_oct2point(group, ecPoint, qb, qlen, NULL);
        (*env)->ReleaseByteArrayElements(env, q, qb, JNI_ABORT);

        if (ret != 1) {
            EC_GROUP_free(group);
            EC_KEY_free(ecKey);
            EC_POINT_free(ecPoint);
            clazz = (*env)->FindClass(env, INVALID_KEY_EX);
            (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(EC_KEY_PUBLIC1_MSG));
            return 0L;
        }
        ret = EC_KEY_set_public_key(ecKey, ecPoint);
        EC_POINT_free(ecPoint);

        if (ret != 1) {
            EC_GROUP_free(group);
            EC_KEY_free(ecKey);
            clazz = (*env)->FindClass(env, INVALID_KEY_EX);
            (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(EC_KEY_PUBLIC2_MSG));
            return 0L;
        }
    }
    /*
     * Setup the private key
     */
    else if (dlen > 0) {
        db = (*env)->GetByteArrayElements(env, d, 0);
        if (db == NULL) {
            EC_GROUP_free(group);
            EC_KEY_free(ecKey);
            return 0L;
        }
        ret =  EC_KEY_set_private_key(ecKey, BN_bin2bn(db, dlen, NULL));
        (*env)->ReleaseByteArrayElements(env, d, db, JNI_ABORT);

        if (ret != 1) {
            EC_GROUP_free(group);
            EC_KEY_free(ecKey);
            clazz = (*env)->FindClass(env, INVALID_KEY_EX);
            (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(EC_KEY_PRIV1_MSG));
            return 0L;
        }
    }
    else {  
        EC_GROUP_free(group);
        EC_KEY_free(ecKey);
        clazz = (*env)->FindClass(env, INVALID_KEY_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(EC_KEY_MSG));
    }

    EC_GROUP_free(group);
    return (unsigned long)ecKey;

}

/*
 * Class:     org_apache_security_juice_provider_JDKDSASignerOpenSSL
 * Method:    openSSLECDSASign
 * Signature: ([BI[BJ)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JDKDSASignerOpenSSL_openSSLECDSASign(JNIEnv *env, jclass jobj, 
                                                                                                    jbyteArray digest , jint diglen,
                                                                                                    jbyteArray sign, jlong handle) {
#if SIZEOF_INT_P == 4
    EC_KEY *key = (EC_KEY *)((int)(handle & 0xffffffff));
#else
    EC_KEY *key = (EC_KEY *)handle;
#endif
    jbyte *digestBytes, *signBytes;
    int ret, sigLen = 0;
    jclass clazz;
    
    if (key == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    digestBytes = (*env)->GetByteArrayElements(env, digest, 0);
    if (digestBytes == NULL) {
        return 0;
    }
    signBytes = (*env)->GetByteArrayElements(env, sign, 0);
    if (signBytes == NULL) {
        return 0;
    }
    ret = ECDSA_sign(0, digestBytes, diglen, signBytes, &sigLen, key);
    (*env)->ReleaseByteArrayElements(env, digest, digestBytes, JNI_ABORT);
    (*env)->ReleaseByteArrayElements(env, sign, signBytes, 0);

    if (ret == 0) {
        clazz = (*env)->FindClass(env, RUNTIME_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: Error during ECDSA sign"));
    }
    EC_KEY_free(key);
    return sigLen;
}
/*
 * Class:     org_apache_security_juice_provider_JDKDSASignerOpenSSL
 * Method:    openSSLECDSAVerify
 * Signature: ([BI[BIJ)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JDKDSASignerOpenSSL_openSSLECDSAVerify (JNIEnv *env, jclass jobj,
                                                                                                       jbyteArray digest , jint diglen,
                                                                                                       jbyteArray sign, jint sigLen, jlong handle) {
#if SIZEOF_INT_P == 4
    EC_KEY *key = (EC_KEY *)((int)(handle & 0xffffffff));
#else
    EC_KEY *key = (EC_KEY *)handle;
#endif
    jbyte *digestBytes, *signBytes;
    int ret;
    jclass clazz;

    if (key == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    digestBytes = (*env)->GetByteArrayElements(env, digest, 0);
    if (digestBytes == NULL) {
        return 0;
    }
    signBytes = (*env)->GetByteArrayElements(env, sign, 0);
    if (signBytes == NULL) {
        return 0;
    }
    ret = ECDSA_verify(0, digestBytes, diglen, signBytes, sigLen, key);

    (*env)->ReleaseByteArrayElements(env, digest, digestBytes, JNI_ABORT);
    (*env)->ReleaseByteArrayElements(env, sign, signBytes, JNI_ABORT);

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

/*
 * Class:     org_apache_security_juice_provider_JDKDSASignerOpenSSL
 * Method:    openSSLECDSASize
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JDKDSASignerOpenSSL_openSSLECDSASize (JNIEnv *env, jclass jobj,
                                                                                                     jlong handle) {
    jclass clazz;
#if SIZEOF_INT_P == 4
    EC_KEY *key = (EC_KEY *)((int)(handle & 0xffffffff));
#else
    EC_KEY *key = (EC_KEY *)handle;
#endif
    if (key == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    return ECDSA_size(key);
}

