有没有一种简单的方法可以在 Java 中使用 OpenSSL BIO 对象,或者有其他选择吗?
Is there a simple way to use OpenSSL BIO objects in Java or are there any alternatives?
谁能告诉我有什么方法可以使用 OpenSSL's
BIO
来自 Java
的对象吗?
我正在做一个项目,旨在为 TinyRadius 处理 PEAP
(https://en.wikipedia.org/wiki/Protected_Extensible_Authentication_Protocol) 数据包提供支持。
我试图在 Java
中搜索任何现有的 PEAP
实现,但似乎没有。
我成功地找到了一个用 Python 编写的实现,它使用 pyOpenSSL
来解密和加密 PEAP
会话中的数据。但问题是代码使用了几个 OpenSSL
功能,javax.net.ssl
没有提供这些功能,例如读取和写入 SSL
会话的 BIO
对象或获取主密钥和安全随机数,由客户端从会话中生成。
这是我正在尝试移植的代码示例:
def get_keys(self):
self.master_key = self.ssl_connection.master_key()
self.server_sec_random = self.ssl_connection.server_random()
self.client_sec_random = self.ssl_connection.client_random()
...
def write(self, data):
self.ssl_connection.bio_write(data)
...
def read(self):
return self.ssl_connection.bio_read(4096)
我研究了 pyOpenSSL,发现所有这些调用都只是通过 libffi (http://sourceware.org/libffi) 对 OpenSSL 库函数的包装,但我不知道如何在 Java 中实现相同的功能。
据我所知,唯一的方法是使用 JNI
(或 JNA
)调用 OpenSSL
函数。此外,我需要实现用于管理对象生命周期的代码,该代码是在 OpenSSL
访问期间创建的,但我不知道该怎么做,因为我之前没有任何使用 Java
的本机代码的经验。
如果有人知道使用 Java 中的 OpenSSL
的其他方法或者 OpenSSL
的一些现成的实现或端口,请告诉我 - 所有答案都非常感谢.
谢谢!
在多次搜索 Java 中使用 OpenSSL 的方法后,我最终得到了 JNA 包装器实现,令人惊讶的是,它看起来非常简单。
幸运的是,OpenSSL 的设计方式使得在绝大多数用例中我们不需要确切知道从调用 OpenSSL 函数返回的值的类型(例如,OpenSSL 不需要调用任何来自结构的方法或直接使用结构字段),因此不需要对 OpenSSL 的数据类型进行庞大而复杂的包装。
OpenSSL的大部分函数都是用指针来操作的,可以将指针包装成com.sun.jna.Pointer
class的一个实例,相当于用C语言转换为void*
,而被调用者将确定正确的类型并正确取消引用给定的指针。
这里是一个关于如何加载 ssleay32.dll
、初始化库和创建上下文的小代码示例:
1) 定义 ssleay32.dll
库的接口(函数列表可以从 OpenSSL GitHub repo 或通过研究 dll 的导出部分获得):
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
public interface OpenSSLLib extends Library {
public OpenSSLLib INSTANCE = (OpenSSLLib) Native.loadLibrary("ssleay32",
OpenSSLLib.class);
// Non-re-enterable! Should be called once per a thread (process).
public void SSL_library_init();
public void SSL_load_error_strings();
// Supported context methods.
public Pointer TLSv1_method();
...
// Context-related methods.
public Pointer SSL_CTX_new(Pointer method);
public void SSL_CTX_free(Pointer context);
public int SSL_CTX_use_PrivateKey_file(Pointer context, String filePath, int type);
public int SSL_CTX_use_certificate_file(Pointer context, String filePath, int type);
public int SSL_CTX_check_private_key(Pointer context);
public int SSL_CTX_ctrl(Pointer context, int cmd, int larg, Pointer arg);
public void SSL_CTX_set_verify(Pointer context, int mode, Pointer verifyCallback);
public int SSL_CTX_set_cipher_list(Pointer context, String cipherList);
public Pointer SSL_new(Pointer context);
public void SSL_free(Pointer ssl);
...
}
2) 初始化库:
...
public static OpenSSLLib libSSL;
public static LibEayLib libEay;
...
static {
libSSL = OpenSSLLib.INSTANCE;
libEay = LibEayLib.INSTANCE;
libSSL.SSL_library_init();
libEay.OPENSSL_add_all_algorithms_conf(); // This function is called from
// libeay32.dll via another JNA interface.
libSSL.SSL_load_error_strings();
}
...
3) 创建并初始化 SSL_CTX
和 SSL
对象:
public class SSLEndpoint {
public Pointer context; // SSL_CTX*
public Pointer ssl; // SSL*
...
}
...
SSLEndpoint endpoint = new SSLEndpoint();
...
// Use one of supported SSL/TLS methods; here is the example for TLSv1 Method
endpoint.context = libSSL.SSL_CTX_new(libSSL.TLSv1_method());
if(endpoint.context.equals(Pointer.NULL)) {
throw new SSLGeneralException("Failed to create SSL Context!");
}
int res = libSSL.SSL_CTX_set_cipher_list(endpoint.context, OpenSSLLib.DEFAULT_CIPHER_LIST);
if(res != 1) {
throw new SSLGeneralException("Failed to set the default cipher list!");
}
libSSL.SSL_CTX_set_verify(endpoint.context, OpenSSLLib.SSL_VERIFY_NONE, Pointer.NULL);
// pathToCert is a String object, which defines a path to a cerificate
// in PEM format.
res = libSSL.SSL_CTX_use_certificate_file(endpoint.context, pathToCert, certKeyTypeToX509Const(certType));
if(res != 1) {
throw new SSLGeneralException("Failed to load the cert file " + pathToCert);
}
// pathToKey is a String object, which defines a path to a priv. key
// in PEM format.
res = libSSL.SSL_CTX_use_PrivateKey_file(endpoint.context, pathToKey, certKeyTypeToX509Const(keyType));
if(res != 1) {
throw new SSLGeneralException("Failed to load the private key file " + pathToKey);
}
res = libSSL.SSL_CTX_check_private_key(endpoint.context);
if(res != 1) {
throw new SSLGeneralException("Given key " + pathToKey + " seems to be not valid.");
}
SSLGeneralException
是自定义异常,简单继承自RuntimeException
.
...
// Create and init SSL object with given SSL_CTX
endpoint.ssl = libSSL.SSL_new(endpoint.context);
...
下一步可能是创建 BIO
对象并将它们链接到 SSL 对象。
关于原题,client/server安全随机数和主密钥可以通过以下方法获得:
public int SSL_get_client_random(Pointer ssl, byte[] out, int outLen);
public int SSL_get_server_random(Pointer ssl, byte[] out, int outLen);
public int SSL_SESSION_get_master_key(Pointer session, byte[] out, int outLen);
请注意,JNA 将搜索 dll 以加载 jna.library.path
参数。因此,如果 dll 位于目录 D:\dlls
中,您必须指定这样的 VM 选项:
-Djna.library.path="D:/dll"
此外,JNA 需要 32 位 JRE 来实现 32 位 dll(可能 libffi
约束?)。如果您尝试使用 64 位 JRE 加载 32 位 dll,则会出现异常。
谁能告诉我有什么方法可以使用 OpenSSL's
BIO
来自 Java
的对象吗?
我正在做一个项目,旨在为 TinyRadius 处理 PEAP
(https://en.wikipedia.org/wiki/Protected_Extensible_Authentication_Protocol) 数据包提供支持。
我试图在 Java
中搜索任何现有的 PEAP
实现,但似乎没有。
我成功地找到了一个用 Python 编写的实现,它使用 pyOpenSSL
来解密和加密 PEAP
会话中的数据。但问题是代码使用了几个 OpenSSL
功能,javax.net.ssl
没有提供这些功能,例如读取和写入 SSL
会话的 BIO
对象或获取主密钥和安全随机数,由客户端从会话中生成。
这是我正在尝试移植的代码示例:
def get_keys(self):
self.master_key = self.ssl_connection.master_key()
self.server_sec_random = self.ssl_connection.server_random()
self.client_sec_random = self.ssl_connection.client_random()
...
def write(self, data):
self.ssl_connection.bio_write(data)
...
def read(self):
return self.ssl_connection.bio_read(4096)
我研究了 pyOpenSSL,发现所有这些调用都只是通过 libffi (http://sourceware.org/libffi) 对 OpenSSL 库函数的包装,但我不知道如何在 Java 中实现相同的功能。
据我所知,唯一的方法是使用 JNI
(或 JNA
)调用 OpenSSL
函数。此外,我需要实现用于管理对象生命周期的代码,该代码是在 OpenSSL
访问期间创建的,但我不知道该怎么做,因为我之前没有任何使用 Java
的本机代码的经验。
如果有人知道使用 Java 中的 OpenSSL
的其他方法或者 OpenSSL
的一些现成的实现或端口,请告诉我 - 所有答案都非常感谢.
谢谢!
在多次搜索 Java 中使用 OpenSSL 的方法后,我最终得到了 JNA 包装器实现,令人惊讶的是,它看起来非常简单。
幸运的是,OpenSSL 的设计方式使得在绝大多数用例中我们不需要确切知道从调用 OpenSSL 函数返回的值的类型(例如,OpenSSL 不需要调用任何来自结构的方法或直接使用结构字段),因此不需要对 OpenSSL 的数据类型进行庞大而复杂的包装。
OpenSSL的大部分函数都是用指针来操作的,可以将指针包装成com.sun.jna.Pointer
class的一个实例,相当于用C语言转换为void*
,而被调用者将确定正确的类型并正确取消引用给定的指针。
这里是一个关于如何加载 ssleay32.dll
、初始化库和创建上下文的小代码示例:
1) 定义 ssleay32.dll
库的接口(函数列表可以从 OpenSSL GitHub repo 或通过研究 dll 的导出部分获得):
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
public interface OpenSSLLib extends Library {
public OpenSSLLib INSTANCE = (OpenSSLLib) Native.loadLibrary("ssleay32",
OpenSSLLib.class);
// Non-re-enterable! Should be called once per a thread (process).
public void SSL_library_init();
public void SSL_load_error_strings();
// Supported context methods.
public Pointer TLSv1_method();
...
// Context-related methods.
public Pointer SSL_CTX_new(Pointer method);
public void SSL_CTX_free(Pointer context);
public int SSL_CTX_use_PrivateKey_file(Pointer context, String filePath, int type);
public int SSL_CTX_use_certificate_file(Pointer context, String filePath, int type);
public int SSL_CTX_check_private_key(Pointer context);
public int SSL_CTX_ctrl(Pointer context, int cmd, int larg, Pointer arg);
public void SSL_CTX_set_verify(Pointer context, int mode, Pointer verifyCallback);
public int SSL_CTX_set_cipher_list(Pointer context, String cipherList);
public Pointer SSL_new(Pointer context);
public void SSL_free(Pointer ssl);
...
}
2) 初始化库:
...
public static OpenSSLLib libSSL;
public static LibEayLib libEay;
...
static {
libSSL = OpenSSLLib.INSTANCE;
libEay = LibEayLib.INSTANCE;
libSSL.SSL_library_init();
libEay.OPENSSL_add_all_algorithms_conf(); // This function is called from
// libeay32.dll via another JNA interface.
libSSL.SSL_load_error_strings();
}
...
3) 创建并初始化 SSL_CTX
和 SSL
对象:
public class SSLEndpoint {
public Pointer context; // SSL_CTX*
public Pointer ssl; // SSL*
...
}
...
SSLEndpoint endpoint = new SSLEndpoint();
...
// Use one of supported SSL/TLS methods; here is the example for TLSv1 Method
endpoint.context = libSSL.SSL_CTX_new(libSSL.TLSv1_method());
if(endpoint.context.equals(Pointer.NULL)) {
throw new SSLGeneralException("Failed to create SSL Context!");
}
int res = libSSL.SSL_CTX_set_cipher_list(endpoint.context, OpenSSLLib.DEFAULT_CIPHER_LIST);
if(res != 1) {
throw new SSLGeneralException("Failed to set the default cipher list!");
}
libSSL.SSL_CTX_set_verify(endpoint.context, OpenSSLLib.SSL_VERIFY_NONE, Pointer.NULL);
// pathToCert is a String object, which defines a path to a cerificate
// in PEM format.
res = libSSL.SSL_CTX_use_certificate_file(endpoint.context, pathToCert, certKeyTypeToX509Const(certType));
if(res != 1) {
throw new SSLGeneralException("Failed to load the cert file " + pathToCert);
}
// pathToKey is a String object, which defines a path to a priv. key
// in PEM format.
res = libSSL.SSL_CTX_use_PrivateKey_file(endpoint.context, pathToKey, certKeyTypeToX509Const(keyType));
if(res != 1) {
throw new SSLGeneralException("Failed to load the private key file " + pathToKey);
}
res = libSSL.SSL_CTX_check_private_key(endpoint.context);
if(res != 1) {
throw new SSLGeneralException("Given key " + pathToKey + " seems to be not valid.");
}
SSLGeneralException
是自定义异常,简单继承自RuntimeException
.
...
// Create and init SSL object with given SSL_CTX
endpoint.ssl = libSSL.SSL_new(endpoint.context);
...
下一步可能是创建 BIO
对象并将它们链接到 SSL 对象。
关于原题,client/server安全随机数和主密钥可以通过以下方法获得:
public int SSL_get_client_random(Pointer ssl, byte[] out, int outLen);
public int SSL_get_server_random(Pointer ssl, byte[] out, int outLen);
public int SSL_SESSION_get_master_key(Pointer session, byte[] out, int outLen);
请注意,JNA 将搜索 dll 以加载 jna.library.path
参数。因此,如果 dll 位于目录 D:\dlls
中,您必须指定这样的 VM 选项:
-Djna.library.path="D:/dll"
此外,JNA 需要 32 位 JRE 来实现 32 位 dll(可能 libffi
约束?)。如果您尝试使用 64 位 JRE 加载 32 位 dll,则会出现异常。