有人可以告诉我有没有办法使用OpenSSL's
中的BIO
Java
个对象?
我正在开展一个项目,该项目旨在为TinyRadius处理PEAP
(https://en.wikipedia.org/wiki/Protected_Extensible_Authentication_Protocol)数据包提供支持。
我试图在PEAP
中搜索任何现有的Java
实现,但似乎没有人。
成功地,我找到了一个用Python编写的实现,它使用pyOpenSSL
来解密和加密PEAP
个会话中的数据。但问题是该代码使用了几个OpenSSL
功能,javax.net.ssl
不提供这些功能,例如读取和写入BIO
会话的SSL
个对象或获取主密钥并且安全随机,由客户端从会话中生成。
以下是我尝试移植的代码示例:
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
的一些即用型实施或端口,请告诉我 - 所有答案都非常感谢。
谢谢!
答案 0 :(得分:0)
经过大量搜索从Java利用OpenSSL的方法后,我最终得到了JNA包装器实现,令人惊讶的是,这看起来非常简单。
幸运的是,OpenSSL的设计方式使得在绝大多数用例中我们不需要确切地知道从OpenSSL函数调用返回的值的类型(例如,OpenSSL不需要调用任何方法结构或直接使用结构字段)因此,OpenSSL的数据类型不需要大量复杂的包装。
OpenSSL的大多数函数都使用指针操作,这些指针可以包装到com.sun.jna.Pointer
类的实例中,相当于按照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对象。
关于原始问题,可以通过以下方法获得客户端/服务器安全randoms和主密钥:
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,则会出现异常。