我不認爲這可以通過公共API完成。您可以獲取會話ID,但沒有公共接口來獲取密鑰。
但是,我能夠使用反射和本機代碼的組合來訪問底層的OpenSSL struct,它包含會話ID和主密鑰。所以這是可能的,但它不是安全的,因爲隱藏的成員和圖書館不保證保持不變。實際上,它看起來像OpenSSL主分支上的結構佈局已更改,所以下面的解析代碼將需要更新,如果/當它被拉入Android。
我使用URL.openConnection()
而不是DefaultHttpClient
來製作HTTPS連接,因爲後者現在已被棄用。下面是調用URL.openConnection()
和(這裏沒有什麼有趣的)替換默認SSLSocketFactory
類:
public class MyConnection implements Runnable {
@Override
public void run() {
try {
// Create the connection.
URL url = new URL("https://www.google.com");
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
// Replace the default SSLSocketFactory with our own.
MySSLSocketFactory sslSocketFactory = new MySSLSocketFactory();
urlConnection.setSSLSocketFactory(sslSocketFactory);
// Establish the TLS connection.
int statusCode = urlConnection.getResponseCode();
Log.i("MyConnection", String.format("status %d", statusCode));
// Get SSL details from the captured socket.
sslSocketFactory.getSessionInfo();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
這裏是習俗SSLSocketFactory
,其中最神奇的是。它所做的只是將重寫的方法轉發到真實的SSLSocketFactory
,緩存創建的SSLSocket
實例。還有兩種新的(未覆蓋的)方法 - 下面進一步顯示的本地方法和getSessionInfo()
,它使用SSLSocket
上的反射來獲取本機OpenSSL ssl_session_st
指針並解析(並記錄)感興趣的字段。請注意,您可以使用支持的SSLSession.getId()
獲得會話ID;它獲得了需要偷偷摸摸的關鍵。
// Use Decorator pattern to capture the SSL socket from the default SSLSocketFactory.
class MySSLSocketFactory extends SSLSocketFactory {
// Load NDK shared library.
static {
System.loadLibrary("my_native_helper");
}
// All overridden methods will be forwarded to the real SSLSocketFactory.
// The only addition is that the SSLSocket returned by createSocket() is
// cached.
SSLSocketFactory realFactory_ = HttpsURLConnection.getDefaultSSLSocketFactory();
SSLSocket s_;
// This native method copies data from a native pointer into a ByteBuffer.
native void readNative(long pointer, ByteBuffer dst);
// Use the cached SSLSocket to access native OpenSSL session data.
void getSessionInfo() throws NoSuchFieldException, IllegalAccessException {
// Get the protected OpenSSL ssl_session_st pointer. Note that this
// is not part of the API and could change across Android versions.
// See https://android.googlesource.com/platform/external/conscrypt/+/lollipop-mr1-dev/src/main/java/org/conscrypt/OpenSSLSessionImpl.java
SSLSession session = s_.getSession();
Field field = session.getClass().getDeclaredField("sslSessionNativePointer");
field.setAccessible(true);
long sessionPointer = field.getLong(session);
// Read as many bytes as we need from the native pointer.
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(104);
byteBuffer.order(ByteOrder.nativeOrder());
readNative(sessionPointer, byteBuffer);
// Parse the OpenSSL ssl_session_st. Note that the layout of this structure
// may change with OpenSSL versions and different compilers/platforms (e.g.
// 32-bit vs. 64-bit).
// See https://github.com/openssl/openssl/blob/OpenSSL_1_0_0-stable/ssl/ssl.h#L451
IntBuffer intBuffer = byteBuffer.asIntBuffer();
Log.i("MyConnection", String.format("SSL version %04x", intBuffer.get(0)));
int master_key_length = intBuffer.get(4);
String master_key = "";
for (int i = 0; i < master_key_length; ++i)
master_key += String.format("%02x", byteBuffer.get(20 + i));
Log.i("MyConnection", String.format("Master key %s", master_key));
int session_id_length = intBuffer.get(17);
String session_id = "";
for (int i = 0; i < session_id_length; ++i)
session_id += String.format("%02x", byteBuffer.get(72 + i));
Log.i("MyConnection", String.format("Session ID %s", session_id));
}
@Override
public String[] getDefaultCipherSuites() {
return realFactory_.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return realFactory_.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
s_ = (SSLSocket)realFactory_.createSocket(s, host, port, autoClose);
return s_;
}
@Override
public Socket createSocket(String host, int port) throws IOException {
s_ = (SSLSocket)realFactory_.createSocket(host, port);
return s_;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
s_ = (SSLSocket)realFactory_.createSocket(host, port, localHost, localPort);
return s_;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
s_ = (SSLSocket)realFactory_.createSocket(host, port);
return s_;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
s_ = (SSLSocket)realFactory_.createSocket(address, port, localAddress, localPort);
return s_;
}
}
最後,這裏是本地C代碼,它允許從本地指針讀取內存到ByteBuffer中。這需要使用Android NDK構建並加載,如MySSLSocketFactory
的頂部所示。
#include <jni.h>
#include <string.h>
JNIEXPORT
void JNICALL Java_com_example_mysocketfactory_MySSLSocketFactory_readNative(
JNIEnv *env, jobject o,
jlong pointer, jobject buffer) {
const char *p = (const char *)pointer;
memcpy(
(*env)->GetDirectBufferAddress(env, buffer),
p,
(*env)->GetDirectBufferCapacity(env, buffer));
}
就是這樣。當MyConnection.run()
我的奇巧設備上調用,日誌顯示:
I/MyConnection﹕ status 200
I/MyConnection﹕ SSL version 0301
I/MyConnection﹕ Master key 81ef39c5f8f7f796a34b307ff453511378fd081d14c37eb2e912fa829edf280e0fa7a499c370fdc156b8499758373d67
I/MyConnection﹕ Session ID b9ee4ae0c7738909430d47e9b0d6d60420d34a17d08181f21996e55a463aa5cf
我確實同DefaultHttpClient
短暫的嘗試,但放棄了,當我無法弄清楚如何訪問默認SchemeRegistry。我認爲可以通過在構建DefaultHttpClient
時指定ClientConnectionManager
來完成,但是我不想追求更進一步的棄用路徑。如果您想嘗試,那麼您可能會使用類似的方法來攔截處理連接的實例SSLSessionImpl
。這個類有一個master_secret
成員,所以不需要本地代碼,只有反射(這段代碼路徑不使用OpenSSL)。
這可以幫助我很多。謝謝你的詳細解答.... – davidOhara