有一种简单的方法在Java中使用OpenSSL BIO对象还是有其他选择?

时间:2015-11-16 12:17:33

标签: java python encryption openssl radius

有人可以告诉我有没有办法使用OpenSSL's中的BIO Java个对象?

我正在开展一个项目,该项目旨在为TinyRadius处理PEAPhttps://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的一些即用型实施或端口,请告诉我 - 所有答案都非常感谢。

谢谢!

1 个答案:

答案 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_CTXSSL个对象:

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,则会出现异常。