293 lines
8.9 KiB
C
293 lines
8.9 KiB
C
/*
|
|
* gmssl_jni.c - JNI ????? Tongsuo SSL ?
|
|
* ? C ??? TCP ?? + SSL ????? Java fd ??
|
|
*
|
|
* ??:
|
|
* gcc -shared -fPIC -o libgmssl_jni.so \
|
|
* -I$JAVA_HOME/include -I$JAVA_HOME/include/linux \
|
|
* -I/opt/tongsuo-8.4.0/include \
|
|
* -L/opt/tongsuo-8.4.0/lib64 \
|
|
* gmssl_jni.c -lssl -lcrypto -ldl
|
|
*/
|
|
|
|
#include <jni.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netdb.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/bio.h>
|
|
|
|
#include "com_gateway_ssl_jni_GmSSLJNI.h"
|
|
|
|
/* ==================== ???? ==================== */
|
|
|
|
static void throw_ssl_error(JNIEnv *env, const char *msg) {
|
|
unsigned long err;
|
|
char errbuf[256];
|
|
err = ERR_get_error();
|
|
ERR_error_string_n(err, errbuf, sizeof(errbuf));
|
|
|
|
char fullmsg[512];
|
|
snprintf(fullmsg, sizeof(fullmsg), "%s: %s", msg, errbuf);
|
|
|
|
jclass exClass = (*env)->FindClass(env, "java/lang/RuntimeException");
|
|
if (exClass != NULL) {
|
|
(*env)->ThrowNew(env, exClass, fullmsg);
|
|
}
|
|
}
|
|
|
|
static char* jstring_to_cstr(JNIEnv *env, jstring jstr) {
|
|
if (jstr == NULL) return NULL;
|
|
const char *chars = (*env)->GetStringUTFChars(env, jstr, NULL);
|
|
char *copy = strdup(chars);
|
|
(*env)->ReleaseStringUTFChars(env, jstr, chars);
|
|
return copy;
|
|
}
|
|
|
|
/* ==================== ???? ==================== */
|
|
|
|
JNIEXPORT jlong JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_init
|
|
(JNIEnv *env, jclass cls) {
|
|
SSL_library_init();
|
|
OpenSSL_add_all_algorithms();
|
|
SSL_load_error_strings();
|
|
ERR_load_crypto_strings();
|
|
return (jlong)1;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_cleanup
|
|
(JNIEnv *env, jclass cls, jlong ctx) {}
|
|
|
|
/* ==================== SSL ??? ==================== */
|
|
|
|
JNIEXPORT jlong JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_createSSLContext
|
|
(JNIEnv *env, jclass cls, jlong ctx, jboolean isServer, jboolean tls13) {
|
|
SSL_CTX *sslCtx = SSL_CTX_new(TLS_method());
|
|
if (sslCtx == NULL) {
|
|
throw_ssl_error(env, "SSL_CTX_new failed");
|
|
return 0;
|
|
}
|
|
SSL_CTX_set_mode(sslCtx, SSL_MODE_AUTO_RETRY);
|
|
return (jlong)(intptr_t)sslCtx;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_setCipherSuites
|
|
(JNIEnv *env, jclass cls, jlong sslCtx, jstring cipherList) {
|
|
SSL_CTX *ctx = (SSL_CTX *)(intptr_t)sslCtx;
|
|
char *ciphers = jstring_to_cstr(env, cipherList);
|
|
if (SSL_CTX_set_ciphersuites(ctx, ciphers) != 1) {
|
|
throw_ssl_error(env, "SSL_CTX_set_ciphersuites failed");
|
|
}
|
|
SSL_CTX_set_cipher_list(ctx, ciphers);
|
|
free(ciphers);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_loadCertificate
|
|
(JNIEnv *env, jclass cls, jlong sslCtx, jstring certPath, jstring keyPath) {
|
|
SSL_CTX *ctx = (SSL_CTX *)(intptr_t)sslCtx;
|
|
char *cert = jstring_to_cstr(env, certPath);
|
|
char *key = jstring_to_cstr(env, keyPath);
|
|
|
|
if (SSL_CTX_use_certificate_file(ctx, cert, SSL_FILETYPE_PEM) != 1) {
|
|
throw_ssl_error(env, "SSL_CTX_use_certificate_file failed");
|
|
free(cert); free(key);
|
|
return;
|
|
}
|
|
if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) != 1) {
|
|
throw_ssl_error(env, "SSL_CTX_use_PrivateKey_file failed");
|
|
free(cert); free(key);
|
|
return;
|
|
}
|
|
if (SSL_CTX_check_private_key(ctx) != 1) {
|
|
throw_ssl_error(env, "Private key does not match certificate");
|
|
}
|
|
free(cert);
|
|
free(key);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_loadEncCertificate
|
|
(JNIEnv *env, jclass cls, jlong sslCtx, jstring certPath, jstring keyPath) {}
|
|
|
|
JNIEXPORT void JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_loadCAFile
|
|
(JNIEnv *env, jclass cls, jlong sslCtx, jstring caFile) {
|
|
SSL_CTX *ctx = (SSL_CTX *)(intptr_t)sslCtx;
|
|
char *ca = jstring_to_cstr(env, caFile);
|
|
if (SSL_CTX_load_verify_locations(ctx, ca, NULL) != 1) {
|
|
throw_ssl_error(env, "SSL_CTX_load_verify_locations failed");
|
|
}
|
|
free(ca);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_setVerifyPeer
|
|
(JNIEnv *env, jclass cls, jlong sslCtx, jboolean verify) {
|
|
SSL_CTX *ctx = (SSL_CTX *)(intptr_t)sslCtx;
|
|
if (verify) {
|
|
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
|
|
} else {
|
|
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
|
|
}
|
|
}
|
|
|
|
/* ==================== SSL ?????? C ?? ==================== */
|
|
|
|
/**
|
|
* ?? TCP ??
|
|
*/
|
|
static int tcp_connect(const char *host, int port) {
|
|
struct sockaddr_in addr;
|
|
struct hostent *he;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(port);
|
|
|
|
// ?? IP ??
|
|
if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) {
|
|
// ?? DNS ??
|
|
he = gethostbyname(host);
|
|
if (he == NULL) return -1;
|
|
memcpy(&addr.sin_addr, he->h_addr_list[0], he->h_length);
|
|
}
|
|
|
|
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sock < 0) return -1;
|
|
|
|
struct timeval tv;
|
|
tv.tv_sec = 10;
|
|
tv.tv_usec = 0;
|
|
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
|
|
|
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
|
close(sock);
|
|
return -1;
|
|
}
|
|
|
|
return sock;
|
|
}
|
|
|
|
JNIEXPORT jlong JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_connect
|
|
(JNIEnv *env, jclass cls, jlong sslCtx, jstring jhost, jint port) {
|
|
SSL_CTX *ctx = (SSL_CTX *)(intptr_t)sslCtx;
|
|
char *host = jstring_to_cstr(env, jhost);
|
|
|
|
// TCP ??
|
|
int sock = tcp_connect(host, (int)port);
|
|
if (sock < 0) {
|
|
char errbuf[256];
|
|
snprintf(errbuf, sizeof(errbuf), "TCP connect failed to %s:%d", host, (int)port);
|
|
free(host);
|
|
throw_ssl_error(env, errbuf);
|
|
return 0;
|
|
}
|
|
free(host);
|
|
|
|
// ?? SSL ??
|
|
SSL *ssl = SSL_new(ctx);
|
|
if (ssl == NULL) {
|
|
close(sock);
|
|
throw_ssl_error(env, "SSL_new failed");
|
|
return 0;
|
|
}
|
|
|
|
SSL_set_fd(ssl, sock);
|
|
|
|
// ??
|
|
int ret = SSL_connect(ssl);
|
|
if (ret != 1) {
|
|
int err = SSL_get_error(ssl, ret);
|
|
char errbuf[256];
|
|
snprintf(errbuf, sizeof(errbuf), "SSL_connect failed: error=%d", err);
|
|
SSL_free(ssl);
|
|
close(sock);
|
|
throw_ssl_error(env, errbuf);
|
|
return 0;
|
|
}
|
|
|
|
return (jlong)(intptr_t)ssl;
|
|
}
|
|
|
|
JNIEXPORT jint JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_send
|
|
(JNIEnv *env, jclass cls, jlong ssl, jbyteArray data, jint len) {
|
|
SSL *sslObj = (SSL *)(intptr_t)ssl;
|
|
jbyte *bytes = (*env)->GetByteArrayElements(env, data, NULL);
|
|
int ret = SSL_write(sslObj, bytes, len);
|
|
(*env)->ReleaseByteArrayElements(env, data, bytes, JNI_ABORT);
|
|
if (ret <= 0) {
|
|
int err = SSL_get_error(sslObj, ret);
|
|
return -err;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
JNIEXPORT jint JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_recv
|
|
(JNIEnv *env, jclass cls, jlong ssl, jbyteArray buf, jint maxLen) {
|
|
SSL *sslObj = (SSL *)(intptr_t)ssl;
|
|
jbyte *bytes = (*env)->GetByteArrayElements(env, buf, NULL);
|
|
int ret = SSL_read(sslObj, bytes, maxLen);
|
|
(*env)->ReleaseByteArrayElements(env, buf, bytes, 0);
|
|
if (ret <= 0) {
|
|
int err = SSL_get_error(sslObj, ret);
|
|
if (err == SSL_ERROR_ZERO_RETURN) return 0;
|
|
return -err;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_close
|
|
(JNIEnv *env, jclass cls, jlong ssl) {
|
|
SSL *sslObj = (SSL *)(intptr_t)ssl;
|
|
int fd = SSL_get_fd(sslObj);
|
|
SSL_shutdown(sslObj);
|
|
SSL_free(sslObj);
|
|
if (fd >= 0) close(fd);
|
|
}
|
|
|
|
/* ==================== ???? ==================== */
|
|
|
|
JNIEXPORT jstring JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_getCipherName
|
|
(JNIEnv *env, jclass cls, jlong ssl) {
|
|
SSL *sslObj = (SSL *)(intptr_t)ssl;
|
|
return (*env)->NewStringUTF(env, SSL_get_cipher_name(sslObj));
|
|
}
|
|
|
|
JNIEXPORT jstring JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_getVersion
|
|
(JNIEnv *env, jclass cls, jlong ssl) {
|
|
SSL *sslObj = (SSL *)(intptr_t)ssl;
|
|
return (*env)->NewStringUTF(env, SSL_get_version(sslObj));
|
|
}
|
|
|
|
JNIEXPORT jstring JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_getPeerCertificate
|
|
(JNIEnv *env, jclass cls, jlong ssl) {
|
|
SSL *sslObj = (SSL *)(intptr_t)ssl;
|
|
X509 *cert = SSL_get_peer_certificate(sslObj);
|
|
if (cert == NULL) {
|
|
return (*env)->NewStringUTF(env, "(none)");
|
|
}
|
|
char subject[256];
|
|
X509_NAME_oneline(X509_get_subject_name(cert), subject, sizeof(subject));
|
|
X509_free(cert);
|
|
return (*env)->NewStringUTF(env, subject);
|
|
}
|
|
|
|
JNIEXPORT jstring JNICALL Java_com_gateway_ssl_jni_GmSSLJNI_getErrorString
|
|
(JNIEnv *env, jclass cls, jlong ssl, jint ret) {
|
|
SSL *sslObj = (SSL *)(intptr_t)ssl;
|
|
unsigned long err = ERR_get_error();
|
|
char errbuf[256];
|
|
if (err != 0) {
|
|
ERR_error_string_n(err, errbuf, sizeof(errbuf));
|
|
} else {
|
|
int sslErr = SSL_get_error(sslObj, (int)ret);
|
|
snprintf(errbuf, sizeof(errbuf), "SSL error code: %d", sslErr);
|
|
}
|
|
return (*env)->NewStringUTF(env, errbuf);
|
|
}
|