/* ====================================================================
 * Copyright (c) 2014 - 2017 The GmSSL Project.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the GmSSL Project.
 *    (http://gmssl.org/)"
 *
 * 4. The name "GmSSL Project" must not be used to endorse or promote
 *    products derived from this software without prior written
 *    permission. For written permission, please contact
 *    guanzhi1980@gmail.com.
 *
 * 5. Products derived from this software may not be called "GmSSL"
 *    nor may "GmSSL" appear in their names without prior written
 *    permission of the GmSSL Project.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the GmSSL Project
 *    (http://gmssl.org/)"
 *
 * THIS SOFTWARE IS PROVIDED BY THE GmSSL PROJECT ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE GmSSL PROJECT OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/hmac.h>
#ifndef OPENSSL_NO_CMAC
# include <openssl/cmac.h>
#endif
#ifndef OPENSSL_NO_SM2
#include <openssl/sm2.h>
#endif
#include <openssl/x509.h>
#include <openssl/stack.h>
#include <openssl/crypto.h>
#include <openssl/safestack.h>
#include "../e_os.h"
#include "gmssl_err.h"
#include "gmssl_err.c"
#include "GmSSL.h"

#define GMSSL_JNI_VERSION	"GmSSL-JNI API/1.1 2017-09-01"

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
	(void)ERR_load_JNI_strings();
	return JNI_VERSION_1_2;
}

JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved)
{
	ERR_unload_JNI_strings();
}

JNIEXPORT jobjectArray JNICALL Java_GmSSL_getVersions(JNIEnv *env, jobject this)
{
	jobjectArray ret = NULL;
	int i;

	if (!(ret = (jobjectArray)(*env)->NewObjectArray(env, 7,
		(*env)->FindClass(env, "java/lang/String"),
		(*env)->NewStringUTF(env, "")))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETVERSIONS, JNI_R_JNI_MALLOC_FAILURE);
		return NULL;
	}

	(*env)->SetObjectArrayElement(env, ret, 0,
		(*env)->NewStringUTF(env, GMSSL_JNI_VERSION));

	for (i = 1; i < 7; i++) {
		(*env)->SetObjectArrayElement(env, ret, i,
			(*env)->NewStringUTF(env, OpenSSL_version(i-1)));
	}

	return ret;
}

JNIEXPORT jbyteArray JNICALL Java_GmSSL_generateRandom(
	JNIEnv *env, jobject this, jint outlen)
{
	jbyteArray ret = NULL;
	jbyte *outbuf = NULL;

	if (outlen <= 0 || outlen >= INT_MAX) {
		JNIerr(JNI_F_JAVA_GMSSL_GENERATERANDOM, JNI_R_INVALID_LENGTH);
		return NULL;
	}

	if (!(outbuf = OPENSSL_malloc(outlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_GENERATERANDOM, ERR_R_MALLOC_FAILURE);
		goto end;
	}

	if (!RAND_bytes((unsigned char *)outbuf, outlen)) {
		JNIerr(JNI_F_JAVA_GMSSL_GENERATERANDOM, JNI_R_GMSSL_RNG_ERROR);
		goto end;
	}
	if (!(ret = (*env)->NewByteArray(env, outlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_GENERATERANDOM, JNI_R_JNI_MALLOC_FAILURE);
		goto end;
	}

	(*env)->SetByteArrayRegion(env, ret, 0, outlen, (jbyte *)outbuf);

end:
	OPENSSL_free(outbuf);
	return ret;
}

static void list_cipher_fn(const EVP_CIPHER *c,
	const char *from, const char *to, void *argv)
{
	STACK_OF(OPENSSL_CSTRING) *sk = argv;
	if (c) {
		sk_OPENSSL_CSTRING_push(sk, EVP_CIPHER_name(c));
	} else {
		sk_OPENSSL_CSTRING_push(sk, from);
	}
}

JNIEXPORT jobjectArray JNICALL Java_GmSSL_getCiphers(
	JNIEnv *env, jobject this)
{
	jobjectArray ret = NULL;
	STACK_OF(OPENSSL_CSTRING) *sk = NULL;
	int i;

	if (!(sk = sk_OPENSSL_CSTRING_new_null())) {
		JNIerr(JNI_F_JAVA_GMSSL_GETCIPHERS, ERR_R_MALLOC_FAILURE);
		goto end;
	}

	EVP_CIPHER_do_all_sorted(list_cipher_fn, sk);

	if (!(ret = (jobjectArray)(*env)->NewObjectArray(env,
		sk_OPENSSL_CSTRING_num(sk),
		(*env)->FindClass(env, "java/lang/String"),
		(*env)->NewStringUTF(env, "")))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETCIPHERS, JNI_R_JNI_MALLOC_FAILURE);
		goto end;
	}

	for (i = 0; i < sk_OPENSSL_CSTRING_num(sk); i++) {
		(*env)->SetObjectArrayElement(env, ret, i,
			(*env)->NewStringUTF(env, sk_OPENSSL_CSTRING_value(sk, i)));
	}

end:
	sk_OPENSSL_CSTRING_free(sk);
	return ret;
}

JNIEXPORT jint JNICALL Java_GmSSL_getCipherIVLength(
	JNIEnv *env, jobject this, jstring algor)
{
	jint ret = -1;
	const char *alg = NULL;
	const EVP_CIPHER *cipher;

	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETCIPHERIVLENGTH, JNI_R_BAD_ARGUMENT);
		goto end;
	}

	if (!(cipher = EVP_get_cipherbyname(alg))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETCIPHERIVLENGTH, JNI_R_INVALID_CIPHER);
		goto end;
	}

	ret = EVP_CIPHER_iv_length(cipher);

end:
	(*env)->ReleaseStringUTFChars(env, algor, alg);
	return ret;
}

JNIEXPORT jint JNICALL Java_GmSSL_getCipherKeyLength(
	JNIEnv *env, jobject this, jstring algor)
{
	jint ret = -1;
	const char *alg = NULL;
	const EVP_CIPHER *cipher;

	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETCIPHERKEYLENGTH, JNI_R_BAD_ARGUMENT);
		goto end;
	}

	if (!(cipher = EVP_get_cipherbyname(alg))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETCIPHERKEYLENGTH, JNI_R_INVALID_CIPHER);
		goto end;
	}

	ret = EVP_CIPHER_key_length(cipher);

end:
	if (alg) (*env)->ReleaseStringUTFChars(env, algor, alg);
	return ret;
}

JNIEXPORT jint JNICALL Java_GmSSL_getCipherBlockSize(
	JNIEnv *env, jobject this, jstring algor)
{
	jint ret = -1;
	const char *alg = NULL;
	const EVP_CIPHER *cipher;

	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETCIPHERBLOCKSIZE, JNI_R_BAD_ARGUMENT);
		goto end;
	}

	if (!(cipher = EVP_get_cipherbyname(alg))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETCIPHERBLOCKSIZE, JNI_R_NONSUPPORTED_CIPHER);
		goto end;
	}

	ret = EVP_CIPHER_block_size(cipher);

end:
	if (alg) (*env)->ReleaseStringUTFChars(env, algor, alg);
	return ret;
}

JNIEXPORT jbyteArray JNICALL Java_GmSSL_symmetricEncrypt(
	JNIEnv *env, jobject this, jstring algor,
	jbyteArray in, jbyteArray key, jbyteArray iv)
{
	jbyteArray ret = NULL;
	const char *alg = NULL;
	const unsigned char *keybuf = NULL;
	const unsigned char *ivbuf = NULL;
	const unsigned char *inbuf = NULL;
	unsigned char *outbuf = NULL;
	int inlen, keylen, ivlen, outlen, lastlen;
	const EVP_CIPHER *cipher;
	EVP_CIPHER_CTX *cctx = NULL;

	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(inbuf = (unsigned char *)(*env)->GetByteArrayElements(env, in, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((inlen = (*env)->GetArrayLength(env, in)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(keybuf = (unsigned char *)(*env)->GetByteArrayElements(env, key, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((keylen = (*env)->GetArrayLength(env, key)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	/* null IV can be valid input for some ciphers */
	ivbuf = (unsigned char *)(*env)->GetByteArrayElements(env, iv, 0);
	ivlen = (*env)->GetArrayLength(env, iv);

	if (!(cipher = EVP_get_cipherbyname(alg))) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, JNI_R_INVALID_CIPHER);
		goto end;
	}
	if (keylen != EVP_CIPHER_key_length(cipher)) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, JNI_R_INVALID_KEY_LENGTH);
		goto end;
	}
	if (ivlen != EVP_CIPHER_iv_length(cipher)) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, JNI_R_INVALID_IV_LENGTH);
		goto end;
	}
	if (!(outbuf = OPENSSL_malloc(inlen + 2 * EVP_CIPHER_block_size(cipher)))) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, ERR_R_MALLOC_FAILURE);
		goto end;
	}
	if (!(cctx = EVP_CIPHER_CTX_new())) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, ERR_R_MALLOC_FAILURE);
		goto end;
	}
	if (!EVP_EncryptInit_ex(cctx, cipher, NULL, keybuf, ivbuf)) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, ERR_R_EVP_LIB);
		goto end;
	}
	if (!EVP_EncryptUpdate(cctx, outbuf, &outlen, inbuf, inlen)) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, ERR_R_EVP_LIB);
		goto end;
	}
	if (!EVP_EncryptFinal_ex(cctx, outbuf + outlen, &lastlen)) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, ERR_R_EVP_LIB);
		goto end;
	}
	outlen += lastlen;

	if (!(ret = (*env)->NewByteArray(env, outlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICENCRYPT, JNI_R_JNI_MALLOC_FAILURE);
		goto end;
	}

	(*env)->SetByteArrayRegion(env, ret, 0, outlen, (jbyte *)outbuf);

end:
	if (alg) (*env)->ReleaseStringUTFChars(env, algor, alg);
	if (keybuf) (*env)->ReleaseByteArrayElements(env, key, (jbyte *)keybuf, JNI_ABORT);
	if (inbuf) (*env)->ReleaseByteArrayElements(env, in, (jbyte *)inbuf, JNI_ABORT);
	if (ivbuf) (*env)->ReleaseByteArrayElements(env, iv, (jbyte *)ivbuf, JNI_ABORT);
	OPENSSL_free(outbuf);
	EVP_CIPHER_CTX_free(cctx);
	return ret;
}

JNIEXPORT jbyteArray JNICALL Java_GmSSL_symmetricDecrypt(
	JNIEnv *env, jobject this, jstring algor,
	jbyteArray in, jbyteArray key, jbyteArray iv)
{
	jbyteArray ret = NULL;
	const char *alg = NULL;
	const unsigned char *inbuf = NULL;
	const unsigned char *keybuf = NULL;
	const unsigned char *ivbuf = NULL;
	unsigned char *outbuf = NULL;
	int inlen, keylen, ivlen, outlen, lastlen;
	const EVP_CIPHER *cipher;
	EVP_CIPHER_CTX *cctx = NULL;

	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(inbuf = (unsigned char *)(*env)->GetByteArrayElements(env, in, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((inlen = (*env)->GetArrayLength(env, in)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(keybuf = (unsigned char *)(*env)->GetByteArrayElements(env, key, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((keylen = (*env)->GetArrayLength(env, key)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	ivbuf = (unsigned char *)(*env)->GetByteArrayElements(env, iv, 0);
	ivlen = (*env)->GetArrayLength(env, iv);


	if (!(cipher = EVP_get_cipherbyname(alg))) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, JNI_R_INVALID_CIPHER);
		goto end;
	}
	if (keylen != EVP_CIPHER_key_length(cipher)) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, JNI_R_KEY_LENGTH);
		goto end;
	}
	if (ivlen != EVP_CIPHER_iv_length(cipher)) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, JNI_R_IV_LENGTH);
		goto end;
	}
	if (!(outbuf = OPENSSL_malloc(inlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, ERR_R_MALLOC_FAILURE);
		goto end;
	}
	if (!(cctx = EVP_CIPHER_CTX_new())) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, ERR_R_MALLOC_FAILURE);
		goto end;
	}
	if (!EVP_DecryptInit_ex(cctx, cipher, NULL, keybuf, ivbuf)) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, ERR_R_EVP_LIB);
		goto end;
	}
	if (!EVP_DecryptUpdate(cctx, outbuf, &outlen, inbuf, inlen)) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, ERR_R_EVP_LIB);
		goto end;
	}
	if (!EVP_DecryptFinal_ex(cctx, outbuf + outlen, &lastlen)) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, ERR_R_EVP_LIB);
		goto end;
	}
	outlen += lastlen;

	if (!(ret = (*env)->NewByteArray(env, outlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_SYMMETRICDECRYPT, JNI_R_JNI_MALLOC_FAILURE);
	}

	(*env)->SetByteArrayRegion(env, ret, 0, outlen, (jbyte *)outbuf);

end:
	if (alg) (*env)->ReleaseStringUTFChars(env, algor, alg);
	if (keybuf) (*env)->ReleaseByteArrayElements(env, key, (jbyte *)keybuf, JNI_ABORT);
	if (inbuf) (*env)->ReleaseByteArrayElements(env, in, (jbyte *)inbuf, JNI_ABORT);
	if (ivbuf) (*env)->ReleaseByteArrayElements(env, iv, (jbyte *)ivbuf, JNI_ABORT);
	EVP_CIPHER_CTX_free(cctx);
	return ret;
}

static void list_md_fn(const EVP_MD *md,
	const char *from, const char *to, void *argv)
{
	STACK_OF(OPENSSL_CSTRING) *sk = argv;
	if (md) {
		sk_OPENSSL_CSTRING_push(sk, EVP_MD_name(md));
	} else {
		sk_OPENSSL_CSTRING_push(sk, from);
	}
}

JNIEXPORT jobjectArray JNICALL Java_GmSSL_getDigests(
	JNIEnv *env, jobject this)
{
	jobjectArray ret = NULL;
	STACK_OF(OPENSSL_CSTRING) *sk = NULL;
	int i;

	if (!(sk = sk_OPENSSL_CSTRING_new_null())) {
		JNIerr(JNI_F_JAVA_GMSSL_GETDIGESTS, ERR_R_MALLOC_FAILURE);
		goto end;
	}

	EVP_MD_do_all_sorted(list_md_fn, sk);

	if (!(ret = (jobjectArray)(*env)->NewObjectArray(env,
		sk_OPENSSL_CSTRING_num(sk),
		(*env)->FindClass(env, "java/lang/String"),
		(*env)->NewStringUTF(env, "")))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETDIGESTS, JNI_R_JNI_MALLOC_FAILURE);
		goto end;
	}

	for (i = 0; i < sk_OPENSSL_CSTRING_num(sk); i++) {
		(*env)->SetObjectArrayElement(env, ret, i,
			(*env)->NewStringUTF(env, sk_OPENSSL_CSTRING_value(sk, i)));
	}

end:
	sk_OPENSSL_CSTRING_free(sk);
	return ret;
}

JNIEXPORT jint JNICALL Java_GmSSL_getDigestLength(
	JNIEnv *env, jobject this, jstring algor)
{
	jint ret = -1;
	const char *alg = NULL;
	const EVP_MD *md;

	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETDIGESTLENGTH, JNI_R_BAD_ARGUMENT);
		goto end;
	}

	if (!(md = EVP_get_digestbyname(alg))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETDIGESTLENGTH, JNI_R_INVALID_DIGEST);
		goto end;
	}

	ret = EVP_MD_size(md);

end:
	if (alg) (*env)->ReleaseStringUTFChars(env, algor, alg);
	return ret;
}

JNIEXPORT jint JNICALL Java_GmSSL_getDigestBlockSize(
	JNIEnv *env, jobject this, jstring algor)
{
	jint ret = -1;
	const char *alg = NULL;
	const EVP_MD *md;

	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETDIGESTBLOCKSIZE, JNI_R_BAD_ARGUMENT);
		goto end;
	}

	if (!(md = EVP_get_digestbyname(alg))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETDIGESTBLOCKSIZE, JNI_R_INVALID_DIGEST);
		goto end;
	}

	ret = EVP_MD_block_size(md);

end:
	(*env)->ReleaseStringUTFChars(env, algor, alg);
	return ret;
}

JNIEXPORT jbyteArray JNICALL Java_GmSSL_digest(JNIEnv *env, jobject this,
	jstring algor, jbyteArray in)
{
	jbyteArray ret = NULL;
	const char *alg = NULL;
	const unsigned char *inbuf = NULL;
	unsigned char outbuf[EVP_MAX_MD_SIZE];
	int inlen;
	unsigned int outlen = sizeof(outbuf);
	const EVP_MD *md;

	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_DIGEST, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(inbuf = (unsigned char *)(*env)->GetByteArrayElements(env, in, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_DIGEST, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((inlen = (size_t)(*env)->GetArrayLength(env, in)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_DIGEST, JNI_R_BAD_ARGUMENT);
		goto end;
	}

	if (!(md = EVP_get_digestbyname(alg))) {
		JNIerr(JNI_F_JAVA_GMSSL_DIGEST, JNI_R_INVALID_DIGEST);
		goto end;
	}
	if (!EVP_Digest(inbuf, inlen, outbuf, &outlen, md, NULL)) {
		JNIerr(JNI_F_JAVA_GMSSL_DIGEST, ERR_R_EVP_LIB);
		goto end;
	}

	if (!(ret = (*env)->NewByteArray(env, outlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_DIGEST, JNI_R_JNI_MALLOC_FAILURE);
		goto end;
	}

	(*env)->SetByteArrayRegion(env, ret, 0, outlen, (jbyte *)outbuf);

end:
	if (alg) (*env)->ReleaseStringUTFChars(env, algor, alg);
	if (inbuf) (*env)->ReleaseByteArrayElements(env, in, (jbyte *)inbuf, JNI_ABORT);
	return ret;
}

char *mac_algors[] = {
	"CMAC-SMS4",
	"HMAC-SM3",
	"HMAC-SHA1",
	"HMAC-SHA256",
	"HMAC-SHA512",
};

JNIEXPORT jobjectArray JNICALL Java_GmSSL_getMacs(
	JNIEnv *env, jobject this)
{
	jobjectArray ret = NULL;
	int i;

	if (!(ret = (jobjectArray)(*env)->NewObjectArray(env,
		OSSL_NELEM(mac_algors),
		(*env)->FindClass(env, "java/lang/String"),
		(*env)->NewStringUTF(env, "")))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETMACS, JNI_R_JNI_MALLOC_FAILURE);
		return NULL;
	}

	for (i = 0; i < OSSL_NELEM(mac_algors); i++) {
		(*env)->SetObjectArrayElement(env, ret, i,
			(*env)->NewStringUTF(env, mac_algors[i]));
	}

	return ret;
}

JNIEXPORT jbyteArray JNICALL Java_GmSSL_mac(JNIEnv *env, jobject this,
	jstring algor, jbyteArray in, jbyteArray key)
{
	jbyteArray ret = NULL;
	const char *alg = NULL;
	const unsigned char *inbuf = NULL;
	const unsigned char *keybuf = NULL;
	unsigned char outbuf[EVP_MAX_MD_SIZE];
	int inlen, keylen, outlen = sizeof(outbuf);
#ifndef OPENSSL_NO_CMAC
	CMAC_CTX *cctx = NULL;
#endif

	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_MAC, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(inbuf = (unsigned char *)(*env)->GetByteArrayElements(env, in, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_MAC, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((inlen = (*env)->GetArrayLength(env, in)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_MAC, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(keybuf = (unsigned char *)(*env)->GetByteArrayElements(env, key, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_MAC, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((keylen = (*env)->GetArrayLength(env, key)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_MAC, JNI_R_BAD_ARGUMENT);
		goto end;
	}

	if (memcmp(alg, "HMAC-", strlen("HMAC-")) == 0) {
		const EVP_MD *md;
		unsigned int len = sizeof(outbuf);

		if (!(md = EVP_get_digestbyname(alg + strlen("HMAC-")))) {
			JNIerr(JNI_F_JAVA_GMSSL_MAC, JNI_R_INVALID_DIGEST);
			goto end;
		}

		if (!HMAC(md, keybuf, keylen, inbuf, inlen, outbuf, &len)) {
			JNIerr(JNI_F_JAVA_GMSSL_MAC, JNI_R_HMAC_ERROR);
			goto end;
		}

		outlen = len;

#ifndef OPENSSL_NO_CMAC
	} else if (memcmp(alg, "CMAC-", strlen("CMAC-")) == 0) {
		const EVP_CIPHER *cipher;
		size_t len = sizeof(outbuf);

		if (!(cipher = EVP_get_cipherbyname(alg + strlen("CMAC-")))) {
			JNIerr(JNI_F_JAVA_GMSSL_MAC, JNI_R_INVALID_CIPHER);
			goto end;
		}
		if (!(cctx = CMAC_CTX_new())) {
			JNIerr(JNI_F_JAVA_GMSSL_MAC, ERR_R_MALLOC_FAILURE);
			goto end;
		}
		if (!CMAC_Init(cctx, keybuf, keylen, cipher, NULL)) {
			JNIerr(JNI_F_JAVA_GMSSL_MAC, JNI_R_CMAC_ERROR);
			goto end;
		}
		if (!CMAC_Update(cctx, inbuf, inlen)) {
			JNIerr(JNI_F_JAVA_GMSSL_MAC, JNI_R_CMAC_ERROR);
			goto end;
		}
		if (!CMAC_Final(cctx, outbuf, &len)) {
			JNIerr(JNI_F_JAVA_GMSSL_MAC, JNI_R_CMAC_ERROR);
			goto end;
		}

		outlen = len;
#endif
	} else {
		goto end;
	}

	if (!(ret = (*env)->NewByteArray(env, outlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_MAC, JNI_R_JNI_MALLOC_FAILURE);
		goto end;
	}

	(*env)->SetByteArrayRegion(env, ret, 0, outlen, (jbyte *)outbuf);

end:
	if (alg) (*env)->ReleaseStringUTFChars(env, algor, alg);
	if (keybuf) (*env)->ReleaseByteArrayElements(env, key, (jbyte *)keybuf, JNI_ABORT);
	if (inbuf) (*env)->ReleaseByteArrayElements(env, in, (jbyte *)inbuf, JNI_ABORT);
#ifndef OPENSSL_NO_CMAC
	CMAC_CTX_free(cctx);
#endif
	return ret;
}

int sign_nids[] = {
#ifndef OPENSSL_NO_SM2
	NID_sm2sign,
#endif
	NID_ecdsa_with_Recommended,
#ifndef OPENSSL_NO_SHA
	NID_ecdsa_with_SHA1,
	NID_ecdsa_with_SHA256,
	NID_ecdsa_with_SHA512,
# ifndef OPENSSL_NO_RSA
	NID_sha1WithRSAEncryption,
	NID_sha256WithRSAEncryption,
	NID_sha512WithRSAEncryption,
# endif
# ifndef OPENSSL_NO_DSA
	NID_dsaWithSHA1,
# endif
#endif
};

static int get_sign_info(const char *alg, int *ppkey_type,
	const EVP_MD **pmd, int *pec_scheme)
{
	int pkey_type;
	const EVP_MD *md = NULL;
	int ec_scheme = -1;

	switch (OBJ_txt2nid(alg)) {
#ifndef OPENSSL_NO_SM2
	case NID_sm2sign:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_sm_scheme;
		break;
#endif
	case NID_ecdsa_with_Recommended:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		break;
#ifndef OPENSSL_NO_SHA
	case NID_ecdsa_with_SHA1:
		pkey_type = EVP_PKEY_EC;
		md = EVP_sha1();
		ec_scheme = NID_secg_scheme;
		break;
	case NID_ecdsa_with_SHA256:
		pkey_type = EVP_PKEY_EC;
		md = EVP_sha256();
		ec_scheme = NID_secg_scheme;
		break;
	case NID_ecdsa_with_SHA512:
		pkey_type = EVP_PKEY_EC;
		md = EVP_sha512();
		ec_scheme = NID_secg_scheme;
		break;
# ifndef OPENSSL_NO_RSA
	case NID_sha1WithRSAEncryption:
		pkey_type = EVP_PKEY_RSA;
		md = EVP_sha1();
		break;
	case NID_sha256WithRSAEncryption:
		pkey_type = EVP_PKEY_RSA;
		md = EVP_sha256();
		break;
	case NID_sha512WithRSAEncryption:
		pkey_type = EVP_PKEY_RSA;
		md = EVP_sha512();
		break;
# endif
# ifndef OPENSSL_NO_DSA
	case NID_dsaWithSHA1:
		pkey_type = EVP_PKEY_DSA;
		md = EVP_sha1();
		break;
# endif
#endif
	default:
		return 0;
	}

	*ppkey_type = pkey_type;
	*pmd = md;
	*pec_scheme = ec_scheme;

	return 1;
}

JNIEXPORT jobjectArray JNICALL Java_GmSSL_getSignAlgorithms(
	JNIEnv *env, jobject this)
{
	jobjectArray ret = NULL;
	int num_algors = sizeof(sign_nids)/sizeof(sign_nids[0]);
	int i;

	if (!(ret = (jobjectArray)(*env)->NewObjectArray(env,
		OSSL_NELEM(sign_nids),
		(*env)->FindClass(env, "java/lang/String"),
		(*env)->NewStringUTF(env, "")))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETSIGNALGORITHMS, JNI_R_JNI_MALLOC_FAILURE);
		return NULL;
	}

	for (i = 0; i < num_algors; i++) {
		(*env)->SetObjectArrayElement(env, ret, i,
			(*env)->NewStringUTF(env, OBJ_nid2sn(sign_nids[i])));
	}

	return ret;
}

JNIEXPORT jbyteArray JNICALL Java_GmSSL_sign(JNIEnv *env, jobject this,
	jstring algor, jbyteArray in, jbyteArray key)
{
	jbyteArray ret = NULL;
	const char *alg = NULL;
	const unsigned char *inbuf = NULL;
	const unsigned char *keybuf = NULL;
	unsigned char outbuf[1024];
	int inlen, keylen;
	size_t outlen = sizeof(outbuf);
	int pkey_type = 0;
	const EVP_MD *md = NULL;
	int ec_scheme = -1;
	const unsigned char *cp;
	EVP_PKEY *pkey = NULL;
	EVP_PKEY_CTX *pkctx = NULL;

	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_SIGN, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(keybuf = (unsigned char *)(*env)->GetByteArrayElements(env, key, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_SIGN, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((keylen = (*env)->GetArrayLength(env, key)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_SIGN, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(inbuf = (unsigned char *)(*env)->GetByteArrayElements(env, in, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_SIGN, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((inlen = (*env)->GetArrayLength(env, in)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_SIGN, JNI_R_BAD_ARGUMENT);
		goto end;
	}

	if (!get_sign_info(alg, &pkey_type, &md, &ec_scheme)) {
		JNIerr(JNI_F_JAVA_GMSSL_SIGN, JNI_R_INVALID_SIGN_ALGOR);
		goto end;
	}

	cp = keybuf;
	if (!(pkey = d2i_PrivateKey(pkey_type, NULL, &cp, keylen))) {
		JNIerr(JNI_F_JAVA_GMSSL_SIGN, JNI_R_INVALID_PRIVATE_KEY);
		goto end;
	}
	if (!(pkctx = EVP_PKEY_CTX_new(pkey, NULL))) {
		JNIerr(JNI_F_JAVA_GMSSL_SIGN, ERR_R_MALLOC_FAILURE);
		goto end;
	}
	if (EVP_PKEY_sign_init(pkctx) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_SIGN, ERR_R_EVP_LIB);
		goto end;
	}

	if (md) {
		if (!EVP_PKEY_CTX_set_signature_md(pkctx, md)) {
			JNIerr(JNI_F_JAVA_GMSSL_SIGN, ERR_R_EVP_LIB);
			goto end;
		}
	}
	if (pkey_type == EVP_PKEY_RSA) {
#ifndef OPENSSL_NO_RSA
		if (!EVP_PKEY_CTX_set_rsa_padding(pkctx, RSA_PKCS1_PSS_PADDING)) {
			JNIerr(JNI_F_JAVA_GMSSL_SIGN, ERR_R_RSA_LIB);
			goto end;
		}
#endif
	} else if (pkey_type == EVP_PKEY_EC) {
#ifndef OPENSSL_NO_SM2
		if (!EVP_PKEY_CTX_set_ec_scheme(pkctx, OBJ_txt2nid(alg) == NID_sm2sign ?
			NID_sm_scheme : NID_secg_scheme)) {
			JNIerr(JNI_F_JAVA_GMSSL_SIGN, ERR_R_EC_LIB);
			goto end;
		}
#endif
	}

	if (EVP_PKEY_sign(pkctx, outbuf, &outlen, inbuf, inlen) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_SIGN, ERR_R_EVP_LIB);
		goto end;
	}

	if (!(ret = (*env)->NewByteArray(env, outlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_SIGN, JNI_R_JNI_MALLOC_FAILURE);
		goto end;
	}

	(*env)->SetByteArrayRegion(env, ret, 0, outlen, (jbyte *)outbuf);

end:
	if (alg) (*env)->ReleaseStringUTFChars(env, algor, alg);
	if (inbuf) (*env)->ReleaseByteArrayElements(env, in, (jbyte *)inbuf, JNI_ABORT);
	if (keybuf) (*env)->ReleaseByteArrayElements(env, key, (jbyte *)keybuf, JNI_ABORT);
	EVP_PKEY_free(pkey);
	EVP_PKEY_CTX_free(pkctx);
	return ret;
}

JNIEXPORT jint JNICALL Java_GmSSL_verify(JNIEnv *env, jobject this,
	jstring algor, jbyteArray in, jbyteArray sig, jbyteArray key)
{
	jint ret = 0;
	const char *alg = NULL;
	const unsigned char *inbuf = NULL;
	const unsigned char *sigbuf = NULL;
	const unsigned char *keybuf = NULL;
	int inlen, siglen, keylen;
	const unsigned char *cp;
	int pkey_type = 0;
	const EVP_MD *md = NULL;
	int ec_scheme = -1;
	EVP_PKEY *pkey = NULL;
	EVP_PKEY_CTX *pkctx = NULL;

	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(inbuf = (unsigned char *)(*env)->GetByteArrayElements(env, in, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((inlen = (*env)->GetArrayLength(env, in)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(sigbuf = (unsigned char *)(*env)->GetByteArrayElements(env, sig, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((siglen = (*env)->GetArrayLength(env, sig)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(keybuf = (unsigned char *)(*env)->GetByteArrayElements(env, key, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((keylen = (*env)->GetArrayLength(env, key)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, JNI_R_BAD_ARGUMENT);
		goto end;
	}

	if (!get_sign_info(alg, &pkey_type, &md, &ec_scheme)) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, JNI_R_INVALID_SIGN_ALGOR);
		goto end;
	}

	cp = keybuf;
	if (!(pkey = d2i_PUBKEY(NULL, &cp, (long)keylen))) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, JNI_R_INVALID_PUBLIC_KEY);
		goto end;
	}

	if (EVP_PKEY_id(pkey) != pkey_type) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, JNI_R_INVALID_PUBLIC_KEY);
		goto end;
	}
	if (!(pkctx = EVP_PKEY_CTX_new(pkey, NULL))) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, ERR_R_MALLOC_FAILURE);
		goto end;
	}

	if (EVP_PKEY_verify_init(pkctx) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, ERR_R_EVP_LIB);
		goto end;
	}

	if (md && !EVP_PKEY_CTX_set_signature_md(pkctx, md)) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, ERR_R_EVP_LIB);
		goto end;
	}


	if (pkey_type == EVP_PKEY_RSA) {
#ifndef OPENSSL_NO_RSA
		if (!EVP_PKEY_CTX_set_rsa_padding(pkctx, RSA_PKCS1_PSS_PADDING)) {
			JNIerr(JNI_F_JAVA_GMSSL_VERIFY, ERR_R_RSA_LIB);
			goto end;
		}
#endif
	} else if (pkey_type == EVP_PKEY_EC) {
#ifndef OPENSSL_NO_SM2
		if (!EVP_PKEY_CTX_set_ec_scheme(pkctx, OBJ_txt2nid(alg) == NID_sm2sign ?
			NID_sm_scheme : NID_secg_scheme)) {
			JNIerr(JNI_F_JAVA_GMSSL_VERIFY, ERR_R_EC_LIB);
			goto end;
		}
#endif
	}

	if (EVP_PKEY_verify(pkctx, sigbuf, siglen, inbuf, inlen) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_VERIFY, ERR_R_EVP_LIB);
		goto end;
	}

	ret = 1;
end:
	if (alg) (*env)->ReleaseStringUTFChars(env, algor, alg);
	if (inbuf) (*env)->ReleaseByteArrayElements(env, in, (jbyte *)inbuf, JNI_ABORT);
	if (sigbuf) (*env)->ReleaseByteArrayElements(env, sig, (jbyte *)sigbuf, JNI_ABORT);
	if (keybuf) (*env)->ReleaseByteArrayElements(env, key, (jbyte *)keybuf, JNI_ABORT);
	EVP_PKEY_free(pkey);
	EVP_PKEY_CTX_free(pkctx);
	return ret;
}

int pke_nids[] = {
#ifndef OPENSSL_NO_RSA
	NID_rsaesOaep,
#endif
#ifndef OPENSSL_NO_ECIES
	NID_ecies_recommendedParameters,
	NID_ecies_specifiedParameters,
# ifndef OPENSSL_NO_SHA
	NID_ecies_with_x9_63_sha1_xor_hmac,
	NID_ecies_with_x9_63_sha256_xor_hmac,
	NID_ecies_with_x9_63_sha512_xor_hmac,
	NID_ecies_with_x9_63_sha1_aes128_cbc_hmac,
	NID_ecies_with_x9_63_sha256_aes128_cbc_hmac,
	NID_ecies_with_x9_63_sha512_aes256_cbc_hmac,
	NID_ecies_with_x9_63_sha256_aes128_ctr_hmac,
	NID_ecies_with_x9_63_sha512_aes256_ctr_hmac,
	NID_ecies_with_x9_63_sha256_aes128_cbc_hmac_half,
	NID_ecies_with_x9_63_sha512_aes256_cbc_hmac_half,
	NID_ecies_with_x9_63_sha256_aes128_ctr_hmac_half,
	NID_ecies_with_x9_63_sha512_aes256_ctr_hmac_half,
	NID_ecies_with_x9_63_sha1_aes128_cbc_cmac,
	NID_ecies_with_x9_63_sha256_aes128_cbc_cmac,
	NID_ecies_with_x9_63_sha512_aes256_cbc_cmac,
	NID_ecies_with_x9_63_sha256_aes128_ctr_cmac,
	NID_ecies_with_x9_63_sha512_aes256_ctr_cmac,
# endif
#endif
#ifndef OPENSSL_NO_SM2
	NID_sm2encrypt_with_sm3,
# ifndef OPENSSL_NO_SHA
	NID_sm2encrypt_with_sha1,
	NID_sm2encrypt_with_sha256,
	NID_sm2encrypt_with_sha512,
# endif
#endif
};

static int get_pke_info(const char *alg, int *ppkey_type,
	int *pec_scheme, int *pec_encrypt_param)
{
	int pkey_type = 0;
	int ec_scheme = 0;
	int ec_encrypt_param = 0;

	switch (OBJ_txt2nid(alg)) {
#ifndef OPENSSL_NO_RSA
	case NID_rsaesOaep:
		pkey_type = EVP_PKEY_RSA;
		break;
#endif
#ifndef OPENSSL_NO_ECIES
	case NID_ecies_recommendedParameters:
	case NID_ecies_specifiedParameters:
# ifndef OPENSSL_NO_SHA
	case NID_ecies_with_x9_63_sha1_xor_hmac:
	case NID_ecies_with_x9_63_sha256_xor_hmac:
	case NID_ecies_with_x9_63_sha512_xor_hmac:
	case NID_ecies_with_x9_63_sha1_aes128_cbc_hmac:
	case NID_ecies_with_x9_63_sha256_aes128_cbc_hmac:
	case NID_ecies_with_x9_63_sha512_aes256_cbc_hmac:
	case NID_ecies_with_x9_63_sha256_aes128_ctr_hmac:
	case NID_ecies_with_x9_63_sha512_aes256_ctr_hmac:
	case NID_ecies_with_x9_63_sha256_aes128_cbc_hmac_half:
	case NID_ecies_with_x9_63_sha512_aes256_cbc_hmac_half:
	case NID_ecies_with_x9_63_sha256_aes128_ctr_hmac_half:
	case NID_ecies_with_x9_63_sha512_aes256_ctr_hmac_half:
	case NID_ecies_with_x9_63_sha1_aes128_cbc_cmac:
	case NID_ecies_with_x9_63_sha256_aes128_cbc_cmac:
	case NID_ecies_with_x9_63_sha512_aes256_cbc_cmac:
	case NID_ecies_with_x9_63_sha256_aes128_ctr_cmac:
	case NID_ecies_with_x9_63_sha512_aes256_ctr_cmac:
# endif
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ec_encrypt_param = OBJ_txt2nid(alg);
		break;
#endif
#ifndef OPENSSL_NO_SM2
	case NID_sm2encrypt_with_sm3:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_sm_scheme;
		ec_encrypt_param = NID_sm3;
		break;
# ifndef OPENSSL_NO_SHA
	case NID_sm2encrypt_with_sha1:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_sm_scheme;
		ec_encrypt_param = NID_sha1;
		break;
	case NID_sm2encrypt_with_sha256:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_sm_scheme;
		ec_encrypt_param = NID_sha256;
		break;
	case NID_sm2encrypt_with_sha512:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_sm_scheme;
		ec_encrypt_param = NID_sha512;
		break;
# endif
#endif
	default:
		return 0;
	}

	*ppkey_type = pkey_type;
	*pec_scheme = ec_scheme;
	*pec_encrypt_param = ec_encrypt_param;

	return 1;
}

JNIEXPORT jobjectArray JNICALL Java_GmSSL_getPublicKeyEncryptions(
	JNIEnv *env, jobject this)
{
	jobjectArray ret = NULL;
	int i;

	if (!(ret = (jobjectArray)(*env)->NewObjectArray(env,
		OSSL_NELEM(pke_nids),
		(*env)->FindClass(env, "java/lang/String"),
		(*env)->NewStringUTF(env, "")))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETPUBLICKEYENCRYPTIONS, JNI_R_JNI_MALLOC_FAILURE);
		return NULL;
	}

	for (i = 0; i < OSSL_NELEM(pke_nids); i++) {
		(*env)->SetObjectArrayElement(env, ret, i,
			(*env)->NewStringUTF(env, OBJ_nid2sn(pke_nids[i])));
	}

	return ret;
}

JNIEXPORT jbyteArray JNICALL Java_GmSSL_publicKeyEncrypt(
	JNIEnv *env, jobject this, jstring algor,
	jbyteArray in, jbyteArray key)
{
	jbyteArray ret = NULL;
	const char *alg = NULL;
	const unsigned char *inbuf = NULL;
	const unsigned char *keybuf = NULL;
	unsigned char *outbuf = NULL;
	int inlen, keylen;
	size_t outlen;
	int pkey_type = NID_undef;
	int ec_scheme = NID_undef;
	int ec_encrypt_param = NID_undef;
	const unsigned char *cp;
	EVP_PKEY *pkey = NULL;
	EVP_PKEY_CTX *pkctx = NULL;


	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(inbuf = (unsigned char *)(*env)->GetByteArrayElements(env, in, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((inlen = (*env)->GetArrayLength(env, in)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((inlen = (*env)->GetArrayLength(env, in)) > 256) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(keybuf = (unsigned char *)(*env)->GetByteArrayElements(env, key, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((keylen = (*env)->GetArrayLength(env, key)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	cp = keybuf;
	outlen = inlen + 1024;

	if (!get_pke_info(alg, &pkey_type, &ec_scheme, &ec_encrypt_param)) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, JNI_R_INVALID_PUBLIC_KEY_ENCRYPTION_ALGOR);
		goto end;
	}

	if (!(outbuf = OPENSSL_malloc(outlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, ERR_R_MALLOC_FAILURE);
		goto end;
	}
	if (!(pkey = d2i_PUBKEY(NULL, &cp, (long)keylen))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, JNI_R_INVALID_PUBLIC_KEY);
		goto end;
	}
	if (EVP_PKEY_id(pkey) != pkey_type) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, JNI_R_INVALID_PUBLIC_KEY);
		goto end;
	}
	if (!(pkctx = EVP_PKEY_CTX_new(pkey, NULL))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, ERR_R_MALLOC_FAILURE);
		goto end;
	}

	if (EVP_PKEY_encrypt_init(pkctx) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, ERR_R_EVP_LIB);
		goto end;
	}

	if (pkey_type == EVP_PKEY_EC) {
#if !defined(OPENSSL_NO_ECIES) || !defined(OPENSSL_NO_SM2)
		if (!EVP_PKEY_CTX_set_ec_scheme(pkctx, ec_scheme)) {
			JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, ERR_R_EC_LIB);
			goto end;
		}
		if (!EVP_PKEY_CTX_set_ec_encrypt_param(pkctx, ec_encrypt_param)) {
			JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, ERR_R_EC_LIB);
			goto end;
		}
#endif
	}

	if (EVP_PKEY_encrypt(pkctx, outbuf, &outlen, inbuf, inlen) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, ERR_R_EVP_LIB);
		goto end;
	}

	if (!(ret = (*env)->NewByteArray(env, outlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYENCRYPT, JNI_R_JNI_MALLOC_FAILURE);
		goto end;
	}

	(*env)->SetByteArrayRegion(env, ret, 0, outlen, (jbyte *)outbuf);

end:
	if (alg) (*env)->ReleaseStringUTFChars(env, algor, alg);
	if (inbuf) (*env)->ReleaseByteArrayElements(env, in, (jbyte *)inbuf, JNI_ABORT);
	if (keybuf) (*env)->ReleaseByteArrayElements(env, key, (jbyte *)keybuf, JNI_ABORT);
	OPENSSL_free(outbuf);
	EVP_PKEY_free(pkey);
	EVP_PKEY_CTX_free(pkctx);
	return ret;

}

/*
static int gmssl_pkey_encrypt(const char *algor, const unsigned char *in, size_t inlen,
	unsigned char *out, size_t *outlen, const unsigned char *key, size_t keylen)
{
}
*/

JNIEXPORT jbyteArray JNICALL Java_GmSSL_publicKeyDecrypt(
	JNIEnv *env, jobject this, jstring algor,
	jbyteArray in, jbyteArray key)
{
	jbyteArray ret = NULL;
	const char *alg = NULL;
	const unsigned char *inbuf = NULL;
	const unsigned char *keybuf = NULL;
	unsigned char *outbuf = NULL;
	int inlen, keylen;
	size_t outlen;
	int pkey_type = NID_undef;
	int ec_scheme = NID_undef;
	int ec_encrypt_param = NID_undef;
	const unsigned char *cp;
	EVP_PKEY *pkey = NULL;
	EVP_PKEY_CTX *pkctx = NULL;

	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(inbuf = (unsigned char *)(*env)->GetByteArrayElements(env, in, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((inlen = (*env)->GetArrayLength(env, in)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(keybuf = (unsigned char *)(*env)->GetByteArrayElements(env, key, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((keylen = (*env)->GetArrayLength(env, key)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	cp = keybuf;
	outlen = inlen;


	if(!get_pke_info(alg, &pkey_type, &ec_scheme, &ec_encrypt_param)) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, JNI_R_INVALID_PUBLIC_KEY_ENCRYPTION_ALGOR);
		goto end;
	}

	if (!(outbuf = OPENSSL_malloc(outlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, ERR_R_MALLOC_FAILURE);
		goto end;
	}
	if (!(pkey = d2i_PrivateKey(pkey_type, NULL, &cp, (long)keylen))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, JNI_R_INVALID_PRIVATE_KEY);
		goto end;
	}
	if (!(pkctx = EVP_PKEY_CTX_new(pkey, NULL))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, ERR_R_MALLOC_FAILURE);
		goto end;
	}
	if (EVP_PKEY_decrypt_init(pkctx) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, ERR_R_EVP_LIB);
		goto end;
	}

	if (pkey_type == EVP_PKEY_EC) {
#if !defined(OPENSSL_NO_ECIES) || !defined(OPENSSL_NO_SM2)
		if (!EVP_PKEY_CTX_set_ec_scheme(pkctx, ec_scheme)) {
			JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, ERR_R_EC_LIB);
			goto end;
		}

		if (!EVP_PKEY_CTX_set_ec_encrypt_param(pkctx, ec_encrypt_param)) {
			JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, ERR_R_EC_LIB);
			goto end;
		}
#endif
	}

	if (EVP_PKEY_decrypt(pkctx, outbuf, &outlen, inbuf, inlen) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, ERR_R_EVP_LIB);
		goto end;
	}

	if (!(ret = (*env)->NewByteArray(env, outlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_PUBLICKEYDECRYPT, JNI_R_JNI_MALLOC_FAILURE);
		goto end;
	}

	(*env)->SetByteArrayRegion(env, ret, 0, outlen, (jbyte *)outbuf);

end:
	if (alg) (*env)->ReleaseStringUTFChars(env, algor, alg);
	if (inbuf) (*env)->ReleaseByteArrayElements(env, in, (jbyte *)inbuf, JNI_ABORT);
	if (keybuf) (*env)->ReleaseByteArrayElements(env, key, (jbyte *)keybuf, JNI_ABORT);
	OPENSSL_free(outbuf);
	EVP_PKEY_free(pkey);
	EVP_PKEY_CTX_free(pkctx);
	return ret;
}

int exch_nids[] = {
#ifndef OPENSSL_NO_SM2
	NID_sm2exchange,
#endif
#ifndef OPENSSL_NO_SHA
	NID_dhSinglePass_stdDH_sha1kdf_scheme,
	NID_dhSinglePass_stdDH_sha224kdf_scheme,
	NID_dhSinglePass_stdDH_sha256kdf_scheme,
	NID_dhSinglePass_stdDH_sha384kdf_scheme,
	NID_dhSinglePass_stdDH_sha512kdf_scheme,
	NID_dhSinglePass_cofactorDH_sha1kdf_scheme,
	NID_dhSinglePass_cofactorDH_sha224kdf_scheme,
	NID_dhSinglePass_cofactorDH_sha256kdf_scheme,
	NID_dhSinglePass_cofactorDH_sha384kdf_scheme,
	NID_dhSinglePass_cofactorDH_sha512kdf_scheme,
#endif
#ifndef OPENSSL_NO_DH
	NID_dhKeyAgreement,
#endif
};

static int get_exch_info(const char *alg, int *ppkey_type, int *pec_scheme,
	int *pecdh_cofactor_mode, int *pecdh_kdf_type, int *pecdh_kdf_md,
	int *pecdh_kdf_outlen, char **pecdh_kdf_ukm, int *pecdh_kdf_ukmlen)
{
	int pkey_type = 0;
	int ec_scheme = 0;
	int ecdh_cofactor_mode = 0;
	int ecdh_kdf_type = 0;
	int ecdh_kdf_md = 0;
	int ecdh_kdf_outlen = 0;
	char *ecdh_kdf_ukm = NULL;
	int ecdh_kdf_ukmlen = 0;

	switch (OBJ_txt2nid(alg)) {
#ifndef OPENSSL_NO_SM2
	case NID_sm2exchange:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_sm_scheme;
		ecdh_kdf_md = NID_sm3;
		break;
#endif
#ifndef OPENSSL_NO_SHA
	case NID_dhSinglePass_stdDH_sha1kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 0;
		ecdh_kdf_type = NID_sha1;
		break;
	case NID_dhSinglePass_stdDH_sha224kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 0;
		ecdh_kdf_type = NID_sha224;
		break;
	case NID_dhSinglePass_stdDH_sha256kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 0;
		ecdh_kdf_type = NID_sha256;
		break;
	case NID_dhSinglePass_stdDH_sha384kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 0;
		ecdh_kdf_type = NID_sha384;
		break;
	case NID_dhSinglePass_stdDH_sha512kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 0;
		ecdh_kdf_type = NID_sha512;
		break;
	case NID_dhSinglePass_cofactorDH_sha1kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 1;
		ecdh_kdf_type = NID_sha1;
		break;
	case NID_dhSinglePass_cofactorDH_sha224kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 1;
		ecdh_kdf_type = NID_sha224;
		break;
	case NID_dhSinglePass_cofactorDH_sha256kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 1;
		ecdh_kdf_type = NID_sha256;
		break;
	case NID_dhSinglePass_cofactorDH_sha384kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 1;
		ecdh_kdf_type = NID_sha384;
		break;
	case NID_dhSinglePass_cofactorDH_sha512kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 1;
		ecdh_kdf_type = NID_sha512;
		break;
#endif
#ifndef OPENSSL_NO_DH
	case NID_dhKeyAgreement:
		pkey_type = EVP_PKEY_DH;
		break;
#endif
	default:
		return 0;
	}

	*ppkey_type = pkey_type;
	*pec_scheme = ec_scheme;
	*pecdh_cofactor_mode = ecdh_cofactor_mode;
	*pecdh_kdf_type = ecdh_kdf_type;
	*pecdh_kdf_md = ecdh_kdf_md;
	*pecdh_kdf_outlen = ecdh_kdf_outlen;
	*pecdh_kdf_ukm = ecdh_kdf_ukm;
	*pecdh_kdf_ukmlen = ecdh_kdf_ukmlen;

	return 1;
}

JNIEXPORT jobjectArray JNICALL Java_GmSSL_getDeriveKeyAlgorithms(
	JNIEnv *env, jobject this)
{
	jobjectArray ret = NULL;
	int i;

	if (!(ret = (jobjectArray)(*env)->NewObjectArray(env,
		OSSL_NELEM(exch_nids),
		(*env)->FindClass(env, "java/lang/String"),
		(*env)->NewStringUTF(env, "")))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETDERIVEKEYALGORITHMS, JNI_R_JNI_MALLOC_FAILURE);
		return NULL;
	}

	for (i = 0; i < OSSL_NELEM(exch_nids); i++) {
		(*env)->SetObjectArrayElement(env, ret, i,
			(*env)->NewStringUTF(env, OBJ_nid2sn(exch_nids[i])));
	}

	return ret;
}

JNIEXPORT jbyteArray JNICALL Java_GmSSL_deriveKey(
	JNIEnv *env, jobject this, jstring algor,
	jint outkeylen, jbyteArray peerkey, jbyteArray key)
{
	jbyteArray ret = NULL;
	const char *alg = NULL;
	const unsigned char *inbuf = NULL;
	const unsigned char *keybuf = NULL;
	unsigned char outbuf[256];
	int inlen, keylen;
	size_t outlen = outkeylen;
	int pkey_type;
	int ec_scheme;
	const unsigned char *cpin, *cpkey;
	EVP_PKEY *peerpkey = NULL;
	EVP_PKEY *pkey = NULL;
	EVP_PKEY_CTX *pkctx = NULL;

	int ecdh_cofactor_mode;
	int ecdh_kdf_type;
	int ecdh_kdf_md;
	int ecdh_kdf_outlen;
	char *ecdh_kdf_ukm;
	int ecdh_kdf_ukm_len;


	if (!(alg = (*env)->GetStringUTFChars(env, algor, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((outkeylen <= 0 || outkeylen > sizeof(outbuf))) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(inbuf = (unsigned char *)(*env)->GetByteArrayElements(env, peerkey, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((inlen = (*env)->GetArrayLength(env, peerkey)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if (!(keybuf = (unsigned char *)(*env)->GetByteArrayElements(env, key, 0))) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	if ((keylen = (*env)->GetArrayLength(env, key)) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, JNI_R_BAD_ARGUMENT);
		goto end;
	}
	cpin = inbuf;
	cpkey = keybuf;

	if (!get_exch_info(alg, &pkey_type, &ec_scheme,
		&ecdh_cofactor_mode, &ecdh_kdf_type, &ecdh_kdf_md, &ecdh_kdf_outlen, &ecdh_kdf_ukm, &ecdh_kdf_ukm_len)) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, JNI_R_INVALID_DERIVE_KEY_ALGOR);
		goto end;
	}


	if (!(peerpkey = d2i_PUBKEY(NULL, &cpin, (long)inlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, JNI_R_INVALID_PUBLIC_KEY);
		goto end;
	}
	if (EVP_PKEY_id(peerpkey) != pkey_type) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, JNI_R_INVALID_PUBLIC_KEY);
		goto end;
	}
	if (!(pkey = d2i_PrivateKey(pkey_type, NULL, &cpkey, (long)keylen))) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, JNI_R_INVALID_PRIVATE_KEY);
		goto end;
	}
	if (!(pkctx = EVP_PKEY_CTX_new(pkey, NULL))) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, ERR_R_MALLOC_FAILURE);
		goto end;
	}
	if (EVP_PKEY_derive_init(pkctx) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, ERR_R_EVP_LIB);
		goto end;
	}

	if (pkey_type == EVP_PKEY_EC) {
		if (!EVP_PKEY_CTX_set_ec_scheme(pkctx, ec_scheme)) {
			JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, ERR_R_EC_LIB);
			goto end;
		}
	}
	if (ec_scheme == NID_secg_scheme) {
		if (!EVP_PKEY_CTX_set_ecdh_cofactor_mode(pkctx, ecdh_cofactor_mode)) {
			JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, ERR_R_EC_LIB);
			goto end;
		}
		if (!EVP_PKEY_CTX_set_ecdh_kdf_type(pkctx, ecdh_kdf_type)) {
			JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, ERR_R_EC_LIB);
			goto end;
		}
		if (!EVP_PKEY_CTX_set_ecdh_kdf_md(pkctx, EVP_get_digestbynid(ecdh_kdf_md))) {
			JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, ERR_R_EC_LIB);
			goto end;
		}
		if (!EVP_PKEY_CTX_set_ecdh_kdf_outlen(pkctx, ecdh_kdf_outlen)) {
			JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, ERR_R_EC_LIB);
			goto end;
		}
		if (!EVP_PKEY_CTX_set0_ecdh_kdf_ukm(pkctx, ecdh_kdf_ukm, ecdh_kdf_ukm_len)) {
			JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, ERR_R_EC_LIB);
			goto end;
		}
	} else if (ec_scheme == NID_sm_scheme) {
	}

	if (EVP_PKEY_derive_set_peer(pkctx, peerpkey) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, ERR_R_EVP_LIB);
		goto end;
	}
	if (EVP_PKEY_derive(pkctx, outbuf, &outlen) <= 0) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, ERR_R_EVP_LIB);
		goto end;
	}

	if (!(ret = (*env)->NewByteArray(env, outlen))) {
		JNIerr(JNI_F_JAVA_GMSSL_DERIVEKEY, JNI_R_JNI_MALLOC_FAILURE);
		goto end;
	}

	(*env)->SetByteArrayRegion(env, ret, 0, outlen, (jbyte *)outbuf);

end:
	if (alg) (*env)->ReleaseStringUTFChars(env, algor, alg);
	if (inbuf) (*env)->ReleaseByteArrayElements(env, peerkey, (jbyte *)inbuf, JNI_ABORT);
	if (keybuf) (*env)->ReleaseByteArrayElements(env, key, (jbyte *)keybuf, JNI_ABORT);
	EVP_PKEY_free(peerpkey);
	EVP_PKEY_free(pkey);
	EVP_PKEY_CTX_free(pkctx);
	return ret;
}

static int print_errors_cb(const char *str, size_t len, void *u)
{
	STACK_OF(OPENSSL_STRING *)sk = u;
	char *errstr;

	if (!(errstr = OPENSSL_strdup(str))) {
		JNIerr(JNI_F_PRINT_ERRORS_CB, ERR_R_MALLOC_FAILURE);
		return 0;
	}

	if (!sk_OPENSSL_STRING_push(sk, errstr)) {
		JNIerr(JNI_F_PRINT_ERRORS_CB, JNI_R_ERRORS_STACK_ERROR);
		return 0;
	}

	return len;
}

static void free_errstr(char *s)
{
	OPENSSL_free(s);
}

JNIEXPORT jobjectArray JNICALL Java_GmSSL_getErrorStrings(JNIEnv *env, jobject this)
{
	jobjectArray ret = NULL;
	STACK_OF(OPENSSL_STRING) *sk = NULL;
	int i;

	if (!(sk = sk_OPENSSL_STRING_new_null())) {
		JNIerr(JNI_F_JAVA_GMSSL_GETERRORSTRINGS, ERR_R_MALLOC_FAILURE);
		goto end;
	}

	ERR_print_errors_cb(print_errors_cb, sk);

	if (!(ret = (jobjectArray)(*env)->NewObjectArray(env,
		sk_OPENSSL_STRING_num(sk),
		(*env)->FindClass(env, "java/lang/String"),
		(*env)->NewStringUTF(env, "")))) {
		JNIerr(JNI_F_JAVA_GMSSL_GETERRORSTRINGS, JNI_R_JNI_MALLOC_FAILURE);
		return NULL;
	}

	for (i = 0; i < sk_OPENSSL_STRING_num(sk); i++) {
		(*env)->SetObjectArrayElement(env, ret, i,
			(*env)->NewStringUTF(env, sk_OPENSSL_STRING_value(sk, i)));
	}

end:
	sk_OPENSSL_STRING_pop_free(sk, free_errstr);
	return ret;
}
