前述のAndroid
であるURLConnection
彼らはサポートしていないためALPN
ないようにサポートします、HTTP/2
。そして、OkHttp 2.5
上記の、それはサポートしているALPN
とHTTP/2
、これらに限定され、プラットフォームの要件のためにAndroid 5.0
上記。違いはそれのプラットフォームになるだろう、なぜそう、それが原因、あなたのお母さんの度合いに言われたopenssl
、必要openssl 1.0.2
以上だけ支援よりもTLS
、私の手Android 4.4
ではopenssl 1.0.1e
その理由、レッツ・スタートではありませんOkHttp
開始します。
まず、関連するインタフェースの存在?
ではOkHttp
、ソースコードokhttp3.internal.platform.Platform.java
のコメントで、これは言うことがあります。
プラットフォーム固有の機能へのアクセス。... ALPN(アプリケーション層プロトコルのネゴシエーション)
アンドロイド5.0以上でサポートされています。APIには、Android 4.4に存在していたが、その実装が不安定でした。
(JettyALPNブートライブラリ経由)OpenJDKの7と8でサポートされています。
SSLParametersとのSSLSocket機能を通じてOpenJDKの9でサポートされています。...
意味が言うようですが、この部分の内側にはありますが、実装が不安定になりますか??!不安定な、それはいくつかのことでした。見て続行:API
4.4
Platform.java
/** Attempt to match the host runtime to a capable Platform implementation. */
private static Platform findPlatform() {
Platform android = AndroidPlatform.buildIfSupported();
if (android != null) {
return android;
}
Platform jdk9 = Jdk9Platform.buildIfSupported();
if (jdk9 != null) {
return jdk9;
}
Platform jdkWithJettyBoot = JdkWithJettyBootPlatform.buildIfSupported();
if (jdkWithJettyBoot != null) {
return jdkWithJettyBoot;
}
// Probably an Oracle JDK like OpenJDK.
return new Platform();
}
見ることができますAndroid
適応プラットフォームは経由でAndroidPlatform.java
で、実現buildIfSupported()
方法:
public static Platform buildIfSupported() {
// Attempt to find Android 2.3+ APIs.
try {
Class<?> sslParametersClass;
try {
sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl");
} catch (ClassNotFoundException e) {
// Older platform before being unbundled.
sslParametersClass = Class.forName("org.apache.harmony.xnet.provider.jsse.SSLParametersImpl");
}
OptionalMethod<Socket> setUseSessionTickets = new OptionalMethod<>(null, "setUseSessionTickets", boolean.class);
OptionalMethod<Socket> setHostname = new OptionalMethod<>(null, "setHostname", String.class);
OptionalMethod<Socket> getAlpnSelectedProtocol = null;
OptionalMethod<Socket> setAlpnProtocols = null;
// Attempt to find Android 5.0+ APIs.
try {
Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0.
getAlpnSelectedProtocol = new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");
setAlpnProtocols = new OptionalMethod<>(null, "setAlpnProtocols", byte[].class);
} catch (ClassNotFoundException ignored) {
}
return new AndroidPlatform(sslParametersClass, setUseSessionTickets, setHostname, getAlpnSelectedProtocol, setAlpnProtocols);
} catch (ClassNotFoundException ignored) {
// This isn't an Android runtime.
}
return null;
}
つまり、なかったOkHttp
を見てAndroid 5.0+
、既存のクラスandroid.net.Network.java
キーであることを特徴と指定されたクラスからの反射の二つの方法、つつ、プラットフォームのバージョンを決定するために、getAlpnSelectedProtocol()
ここでは、戻り値任意キャッシングメソッド名であるパラメータそのため、指定されたクラスは、それは何ですか、決して心は、我々は置くことを、反射によってバックと呼ばれる上Class.forName("android.net.Network");
で実行するために彼を強制的に、この行はコメントアウトを4.4
先に言ったかどうかを確認するために、上、API
存在しているが、不安定な、探しているgetAlpnSelectedProtocol()
特定のローカルコール:
@Override public String getSelectedProtocol(SSLSocket socket) {
if (getAlpnSelectedProtocol == null) return null;
if (!getAlpnSelectedProtocol.isSupported(socket)) return null;
byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket);
return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null;
}
isSupported()
これは、反射することによって決定されるtarget
適切な方法の有無を。
/**
* Returns true if the method exists on the supplied {@code target}.
*/
public boolean isSupported(T target) {
return getMethod(target.getClass()) != null;
}
debug
モードは、ここで我々が見つかりました:
上記された4.4
演算結果は、得られた結果のみが、によって行うことができるalpnResult
のに対して、空である5.0
機械、文字列の解析値が存在し、それはh2
:
これは、公式文書が言ったように、思わgetAlpnSelectedProtocol()
インターフェイス4.4
そこには、が、空を返す、ためので、正しい結果を得ることはありませんでしたかunstable
?
第二に、なぜgetAlpnSelectedProtocolは空気に戻りますか?
次は、我々は、このインターフェイスを保持するクラスを参照してください。debug
あなたが見ることができます:
もともとと呼ばれていたcom.android.org.conscrypt.OpenSSLSocketImplWrapper
、残念ながら、そこに、クラスのソースコードは、クラスAndroid SDK
のダウンロードのための開発キットが利用できるsources
見つけることができません。。。それはここにあります:
所以我们要找conscrypt.jar
的源码, OpenSSLSocketImplWrapper
其实是OpenSSLSocketImpl
的子类:https://android.googlesource.com/platform/external/conscrypt/+/android-5.0.0_r1/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
还有4.4
的,我可是翻了很久才找到的: https://android.googlesource.com/platform/external/conscrypt/+/e75878c72b717696d7e4f6cc1052f1cdaca3bda8/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
两者的实现差不多,都包含:
/**
* Returns the protocol agreed upon by client and server, or {@code null} if
* no protocol was agreed upon.
*/
public byte[] getAlpnSelectedProtocol() {
return NativeCrypto.SSL_get0_alpn_selected(sslNativePointer);
}
从注释可以看到,是需要客户端和服务端都同意ALPN
,才会有返回值,具体实现,是个Native
方法:
/**
* Returns the selected ALPN protocol. If the server did not select a
* protocol, {@code null} will be returned.
*/
public static native byte[] SSL_get0_alpn_selected(long sslPointer);
那这个native
方法指向哪里呢,没错,它指向了openssl
,
openssl-1.0.2j
下载:https://www.openssl.org/source/openssl-1.0.2j.tar.gz
openssl-1.0.1e
下载:https://www.openssl.org/source/old/1.0.1/openssl-1.0.1e.tar.gz
在openssl-1.0.2jincludeopensslssl.h
中可以找到:
void大专栏 Android 4.4 ALPN支持性探究an> SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data,
unsigned *len);
而在openssl-1.0.1eincludeopensslssl.h
中:
我勒个去,Unable to find…
原来这就是unstable
的原因啊!!!
三、替换openssl
既然Android 4.4
的openssl-1.0.1e
缺少SSL_get0_alpn_selected
,那我们换成openssl-1.0.2j
试试。
编译过程在这里:Android Openssl交叉编译
另外,Openssl
官方wiki
有个Android
环境编译教程: https://wiki.openssl.org/index.php/Android#Acquire_the_Required_Files
可惜编译出来的没法使用:
CANNOT LINK EXECUTABLE: could not load library "libandroid_runtime.so" needed by "app_process"; caused by could not load library "libcrypto.so" needed by "libandroid_runtime.so"; caused by "libcrypto.so" has bad ELF magic
或者caused by "libssl.so" has bad ELF magic
替换完成后debug
到getAlpnSelectedProtocol()
:
alpnResult
还是null
。。。最终协商结果还是HTTP/1.1
:
四、这到底是为什么
从前面getAlpnSelectedProtocol()
方法的注释说明:
/**
* Returns the protocol agreed upon by client and server, or {@code null} if
* no protocol was agreed upon.
*/
public byte[] getAlpnSelectedProtocol() {
return NativeCrypto.SSL_get0_alpn_selected(sslNativePointer);
}
还有SSL_get0_alpn_selected
的注释说明:
/*
* SSL_get0_alpn_selected gets the selected ALPN protocol (if any) from
* |ssl|. On return it sets |*data| to point to |*len| bytes of protocol name
* (not including the leading length-prefix byte). If the server didn't
* respond with a negotiated protocol then |*len| will be zero.
*/
void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data, unsigned int *len)
{
*data = NULL;
if (ssl->s3)
*data = ssl->s3->alpn_selected;
if (*data == NULL)
*len = 0;
else
*len = ssl->s3->alpn_selected_len;
}
可以看到,是需要客户端和服务端都同意ALPN
协商,而SSL_get0_alpn_selected
只是从协商结果中判断是否能使用HTTP/2
,难道不是openssl
的问题?那问题出在哪呢
五、TLS链接建立和ALPN协商过程
看来有必要从头了解一下HTTP/2
请求过程,我们在浏览器上访问一下HTTP/2
网站,抓包看看:
首先,Client
向Server
发送一个ClientHello
消息,说明它支持的密码算法列表、压缩方法及最高协议版本,以及稍后将被使用的随机数:
在TLSv1.2
中,会有个扩展字段Extension
,通过ALPN
扩展列出了自己支持的各种应用层协议,比如h2
、http/1.1
:
然后服务端会在Server Hello
中选取所支持的加密算法和密钥大小,以及支持的协议,如果服务端支持HTTP/2
,指定ALPN
的协商结果为h2
就可以了;如果服务端不支持HTTP/2
,就会从客户端的 ALPN
支持列表中选一个自己可以支持的。比如下面这图,服务端选择了降级成HTTP/1.1
:
这就是TLS
链接建立ALPN
协商的过程。
其中ALPN
被包含了在TLS v1.22
的Extension
字段中,查看TLS wiki,在1.2
以前的版本是没有Extension
字段的,看来问题的关键在于TLS
的版本。
六、TLS支持
我们来看看Android
所支持的TLS
版本:https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
好吧,API 16
(4.1
)就提供支持,但API 20
(5.0
)才默认打开。。。
那能不能手动打开呢,先看一下执行结果:
5.0
:在初始化的ssLSocketFactory
的时候,sslParameters
里面enabledProtocls
列表包含了TLSv1.2
:
4.4
:只有TLSv1
:
再看初始化sslParameters
的源码:
5.0
下: com.android.org.conscrypt.SSLParametersImpl.java
:
protected SSLParametersImpl(KeyManager[] kms, TrustManager[] tms, SecureRandom sr, ClientSessionContext clientSessionContext, ServerSessionContext serverSessionContext) throws KeyManagementException {
this.serverSessionContext = serverSessionContext;
this.clientSessionContext = clientSessionContext;
// initialize key managers
if (kms == null) {
x509KeyManager = getDefaultX509KeyManager();
// There's no default PSK key manager
pskKeyManager = null;
} else {
x509KeyManager = findFirstX509KeyManager(kms);
pskKeyManager = findFirstPSKKeyManager(kms);
}
// initialize x509TrustManager
if (tms == null) {
x509TrustManager = getDefaultX509TrustManager();
} else {
x509TrustManager = findFirstX509TrustManager(tms);
}
// initialize secure random
// We simply use the SecureRandom passed in by the caller. If it's
// null, we don't replace it by a new instance. The native code below
// then directly accesses /dev/urandom. Not the most elegant solution,
// but faster than going through the SecureRandom object.
secureRandom = sr;
// initialize the list of cipher suites and protocols enabled by default
enabledProtocols = getDefaultProtocols();
boolean x509CipherSuitesNeeded = (x509KeyManager != null) || (x509TrustManager != null);
boolean pskCipherSuitesNeeded = pskKeyManager != null;
enabledCipherSuites = getDefaultCipherSuites(x509CipherSuitesNeeded, pskCipherSuitesNeeded);
}
private static String[] getDefaultProtocols() {
return NativeCrypto.DEFAULT_PROTOCOLS.clone();
}
com.android.org.conscrypt. NativeCrypto.java
:
public static final String[] DEFAULT_PROTOCOLS = new String[] {
SUPPORTED_PROTOCOL_SSLV3,
SUPPORTED_PROTOCOL_TLSV1,
SUPPORTED_PROTOCOL_TLSV1_1,
SUPPORTED_PROTOCOL_TLSV1_2,
};
4.4
下:
com.android.org.conscrypt.SSLParametersImpl.java
:
// protocols available for SSL connection
private String[] enabledProtocols = ProtocolVersion.supportedProtocols;
com.android.org.conscrypt. ProtocolVersion.java
:
/**
* Protocols supported by this provider implementation
*/
public static final String[] supportedProtocols = new String[] { "TLSv1", "SSLv3" };
所以不管是4.4
还是5.0
,支持的TLS
版本都被写死在了外部扩展库conscrypt.jar
里面。
所以看来需要重新编译Android 4.4
的源码才能解决了。。。