クライアントアプリケーションをAndroidで作成しました。これは、HttpsUrlConnection APIを使用してhttpsサーバーに接続します。Poodleの脆弱性のため、リクエストを呼び出す間、有効なプロトコルのリストからSSLv3.
Oracleが http://www.Oracle.com/technetwork/Java/javase/documentation/cve-2014-3566-2342133.html としてキャプチャしたガイドラインに従いました。
url接続を呼び出す前に次の行を追加しました
Java.lang.System.setProperty("https.protocols", "TLSv1");
このソリューションは、通常のJavaプログラムで正常に動作します。SSLv3プロトコルでのみ動作するサーバーに接続しようとすると、SSLHandShakeExceptionが発生しました。
しかし、懸念はです:同じ修正はAndroidでは機能しません。私は何かを見逃していますか、アンドロイドのために別のアプローチを試す必要がありますか?提案してください。
Wiresharkを使用してデータパケットを分析することで、その解決策を見つけました。私が見つけたのは、安全な接続を確立している間、AndroidはSSLv3からTLSv1。Androidバージョン<4.4のバグであり、SSLv3プロトコルをEnabled Protocolsリストから削除することで解決できます。 NoSSLv3SocketFactory.Javaという名前のカスタムsocketFactoryクラスを作成しました。これを使用して、ソケットファクトリを作成します。
/*Copyright 2015 Bhavit Singh Sengar
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.You may obtain a copy of the License at
http://www.Apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.*/
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.OutputStream;
import Java.net.InetAddress;
import Java.net.Socket;
import Java.net.SocketAddress;
import Java.net.SocketException;
import Java.nio.channels.SocketChannel;
import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.List;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class NoSSLv3SocketFactory extends SSLSocketFactory{
private final SSLSocketFactory delegate;
public NoSSLv3SocketFactory() {
this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
}
public NoSSLv3SocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
private Socket makeSocketSafe(Socket socket) {
if (socket instanceof SSLSocket) {
socket = new NoSSLv3SSLSocket((SSLSocket) socket);
}
return socket;
}
@Override
public Socket createSocket(Socket s, String Host, int port, boolean autoClose) throws IOException {
return makeSocketSafe(delegate.createSocket(s, Host, port, autoClose));
}
@Override
public Socket createSocket(String Host, int port) throws IOException {
return makeSocketSafe(delegate.createSocket(Host, port));
}
@Override
public Socket createSocket(String Host, int port, InetAddress localHost, int localPort) throws IOException {
return makeSocketSafe(delegate.createSocket(Host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress Host, int port) throws IOException {
return makeSocketSafe(delegate.createSocket(Host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort));
}
private class NoSSLv3SSLSocket extends DelegateSSLSocket {
private NoSSLv3SSLSocket(SSLSocket delegate) {
super(delegate);
}
@Override
public void setEnabledProtocols(String[] protocols) {
if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) {
List<String> enabledProtocols = new ArrayList<String>(Arrays.asList(delegate.getEnabledProtocols()));
if (enabledProtocols.size() > 1) {
enabledProtocols.remove("SSLv3");
System.out.println("Removed SSLv3 from enabled protocols");
} else {
System.out.println("SSL stuck with protocol available for " + String.valueOf(enabledProtocols));
}
protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
}
super.setEnabledProtocols(protocols);
}
}
public class DelegateSSLSocket extends SSLSocket {
protected final SSLSocket delegate;
DelegateSSLSocket(SSLSocket delegate) {
this.delegate = delegate;
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public String[] getEnabledCipherSuites() {
return delegate.getEnabledCipherSuites();
}
@Override
public void setEnabledCipherSuites(String[] suites) {
delegate.setEnabledCipherSuites(suites);
}
@Override
public String[] getSupportedProtocols() {
return delegate.getSupportedProtocols();
}
@Override
public String[] getEnabledProtocols() {
return delegate.getEnabledProtocols();
}
@Override
public void setEnabledProtocols(String[] protocols) {
delegate.setEnabledProtocols(protocols);
}
@Override
public SSLSession getSession() {
return delegate.getSession();
}
@Override
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
delegate.addHandshakeCompletedListener(listener);
}
@Override
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
delegate.removeHandshakeCompletedListener(listener);
}
@Override
public void startHandshake() throws IOException {
delegate.startHandshake();
}
@Override
public void setUseClientMode(boolean mode) {
delegate.setUseClientMode(mode);
}
@Override
public boolean getUseClientMode() {
return delegate.getUseClientMode();
}
@Override
public void setNeedClientAuth(boolean need) {
delegate.setNeedClientAuth(need);
}
@Override
public void setWantClientAuth(boolean want) {
delegate.setWantClientAuth(want);
}
@Override
public boolean getNeedClientAuth() {
return delegate.getNeedClientAuth();
}
@Override
public boolean getWantClientAuth() {
return delegate.getWantClientAuth();
}
@Override
public void setEnableSessionCreation(boolean flag) {
delegate.setEnableSessionCreation(flag);
}
@Override
public boolean getEnableSessionCreation() {
return delegate.getEnableSessionCreation();
}
@Override
public void bind(SocketAddress localAddr) throws IOException {
delegate.bind(localAddr);
}
@Override
public synchronized void close() throws IOException {
delegate.close();
}
@Override
public void connect(SocketAddress remoteAddr) throws IOException {
delegate.connect(remoteAddr);
}
@Override
public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
delegate.connect(remoteAddr, timeout);
}
@Override
public SocketChannel getChannel() {
return delegate.getChannel();
}
@Override
public InetAddress getInetAddress() {
return delegate.getInetAddress();
}
@Override
public InputStream getInputStream() throws IOException {
return delegate.getInputStream();
}
@Override
public boolean getKeepAlive() throws SocketException {
return delegate.getKeepAlive();
}
@Override
public InetAddress getLocalAddress() {
return delegate.getLocalAddress();
}
@Override
public int getLocalPort() {
return delegate.getLocalPort();
}
@Override
public SocketAddress getLocalSocketAddress() {
return delegate.getLocalSocketAddress();
}
@Override
public boolean getOOBInline() throws SocketException {
return delegate.getOOBInline();
}
@Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
@Override
public int getPort() {
return delegate.getPort();
}
@Override
public synchronized int getReceiveBufferSize() throws SocketException {
return delegate.getReceiveBufferSize();
}
@Override
public SocketAddress getRemoteSocketAddress() {
return delegate.getRemoteSocketAddress();
}
@Override
public boolean getReuseAddress() throws SocketException {
return delegate.getReuseAddress();
}
@Override
public synchronized int getSendBufferSize() throws SocketException {
return delegate.getSendBufferSize();
}
@Override
public int getSoLinger() throws SocketException {
return delegate.getSoLinger();
}
@Override
public synchronized int getSoTimeout() throws SocketException {
return delegate.getSoTimeout();
}
@Override
public boolean getTcpNoDelay() throws SocketException {
return delegate.getTcpNoDelay();
}
@Override
public int getTrafficClass() throws SocketException {
return delegate.getTrafficClass();
}
@Override
public boolean isBound() {
return delegate.isBound();
}
@Override
public boolean isClosed() {
return delegate.isClosed();
}
@Override
public boolean isConnected() {
return delegate.isConnected();
}
@Override
public boolean isInputShutdown() {
return delegate.isInputShutdown();
}
@Override
public boolean isOutputShutdown() {
return delegate.isOutputShutdown();
}
@Override
public void sendUrgentData(int value) throws IOException {
delegate.sendUrgentData(value);
}
@Override
public void setKeepAlive(boolean keepAlive) throws SocketException {
delegate.setKeepAlive(keepAlive);
}
@Override
public void setOOBInline(boolean oobinline) throws SocketException {
delegate.setOOBInline(oobinline);
}
@Override
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
}
@Override
public synchronized void setReceiveBufferSize(int size) throws SocketException {
delegate.setReceiveBufferSize(size);
}
@Override
public void setReuseAddress(boolean reuse) throws SocketException {
delegate.setReuseAddress(reuse);
}
@Override
public synchronized void setSendBufferSize(int size) throws SocketException {
delegate.setSendBufferSize(size);
}
@Override
public void setSoLinger(boolean on, int timeout) throws SocketException {
delegate.setSoLinger(on, timeout);
}
@Override
public synchronized void setSoTimeout(int timeout) throws SocketException {
delegate.setSoTimeout(timeout);
}
@Override
public void setTcpNoDelay(boolean on) throws SocketException {
delegate.setTcpNoDelay(on);
}
@Override
public void setTrafficClass(int value) throws SocketException {
delegate.setTrafficClass(value);
}
@Override
public void shutdownInput() throws IOException {
delegate.shutdownInput();
}
@Override
public void shutdownOutput() throws IOException {
delegate.shutdownOutput();
}
@Override
public String toString() {
return delegate.toString();
}
@Override
public boolean equals(Object o) {
return delegate.equals(o);
}
}
}
接続中にこのクラスを使用します:
SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null,
null,
null);
SSLSocketFactory NoSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(NoSSLv3Factory);
l_connection = (HttpsURLConnection) l_url.openConnection();
l_connection.connect();
UPDATE:
現在、正しい解決策は、 Google Play Services を使用して新しいセキュリティプロバイダーをインストールすることです。
ProviderInstaller.installIfNeeded(getApplicationContext());
これにより、アプリはOpenSSLの新しいバージョンとSSLEngineでTLSv1.2のサポートを含むJava Security Providerにアクセスできます。新しいプロバイダーをインストールしたら、サポートするSSLEngineを作成できますSSLv3、TLSv1、TLSv1.1およびTLSv1.2の通常の方法:
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, null);
SSLEngine engine = sslContext.createSSLEngine();
または、engine.setEnabledProtocols
を使用して、有効なプロトコルを制限できます。
次の依存関係を追加することを忘れないでください( 最新バージョンはここにあります ):
compile 'com.google.Android.gms:play-services-auth:11.8.0'
詳細については、こちらをご覧ください link 。
Bhavit S. Sengarの answer から着想を得て、この手法を単純なメソッドコールにまとめました。 NetCipher ライブラリを使用して、AndroidのHttpsURLConnection
を使用するときに最新のTLS構成を取得できます。 NetCipherは、サポートされている最適なTLSバージョンを使用するようにHttpsURLConnection
インスタンスを構成し、SSLv3サポートを削除し、そのTLSバージョンに最適な暗号スイートを構成します。まず、build.gradleに追加します:
compile 'info.guardianproject.netcipher:netcipher:1.2'
または、netcipher-1.2.jarをダウンロードして、アプリに直接含めることができます。次に、呼び出す代わりに:
HttpURLConnection connection = (HttpURLConnection) sourceUrl.openConnection();
これを呼び出します:
HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);
最初は、Bhavit S. Sengarの answer を試してみましたが、ほとんどの場合でうまくいきました。ただし、SSLv3プロトコルがAndroid 4.4.4デバイスのEnabled Protocolsから削除された場合でも問題が発生することがあります。したがって、Hans-Christoph Steinerによる NetCipher ライブラリは私がテストできる限り、その問題を解決します。
Jsoupを使用して、さまざまなサーバーで大量のWebスクレイピングを行うため、HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);
を設定できません。 OkHttpを使用する場合も同じ問題だと思います。
最善の解決策は、info.guardianproject.netcipher.client.TlsOnlySocketFactory
NetCipherから、静的ブロック内のDefaultSSLSocketFactory
として。したがって、アプリのランタイム全体に設定されます。
SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null, null, null);
SSLSocketFactory noSSLv3Factory = new TlsOnlySocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(noSSLv3Factory);
完全な詳細を(trustAllCertificates
で)調べたい場合は、それを行うことができます here 。
サーバーがSSLv3対応の場合、このコードスニペットを使用すると、ハンドシェイクに失敗します。
SocketFactory sf = SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket) sf.createSocket("Host-name", 443);
socket.setEnabledProtocols(new String[] { "TLSv1"});
socket.startHandshake();
クライアント側からのハンドシェイクで証明書が必要なhttpsサーバーに接続します。 1年前、自己署名証明書を使用して次の方法で同様の問題を解決しました。
import Java.security.KeyManagementException;
import Java.security.NoSuchAlgorithmException;
import Java.security.SecureRandom;
import Java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class HttpsTrustManager implements X509TrustManager {
private static TrustManager[] trustManagers;
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};
@Override
public void checkClientTrusted(
Java.security.cert.X509Certificate[] x509Certificates, String s)
throws Java.security.cert.CertificateException {
}
@Override
public void checkServerTrusted(
Java.security.cert.X509Certificate[] x509Certificates, String s)
throws Java.security.cert.CertificateException {
}
public boolean isClientTrusted(X509Certificate[] chain) {
return true;
}
public boolean isServerTrusted(X509Certificate[] chain) {
return true;
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return _AcceptedIssuers;
}
public static void allowAllSSL() {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
SSLContext context = null;
if (trustManagers == null) {
trustManagers = new TrustManager[]{new HttpsTrustManager()};
}
try {
context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
HttpsURLConnection.setDefaultSSLSocketFactory(context
.getSocketFactory());
}
}
HttpsUrlConnectionの前のクライアント側での使用
HttpsTrustManager.allowAllSSL();
うまくいけば:)
実際、Android <5デバイスでTLSV1.1またはTLSv1.2を有効にするために必要なものは、SSLV3またはTLSV1.0を無効にする必要はありません。
問題は、Android <5でデフォルトで有効になっていないTLSv1.1およびTLSv1.2であり、これらの最新のセキュアプロトコルを使用して接続するには、Android = <5デバイス。
この解決策は私の問題を解決しました: https://stackoverflow.com/a/45853669/34480
SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(null, null, null);
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
httpURLConnection.setSSLSocketFactory(socketFactory);
TSLを使用したHttpsURLConnectionはセキュリティの作成に失敗しました。Android実装はSSLV3に接続してフォールバックします。
これを参照してくださいhttp://code.google.com/p/Android/issues/detail?id=78431
PlayServiceパブリッシャークライアントライブラリ running on Android sample の実行時に同じ問題が発生しました。
上記の@ bhavit-s-sengarのオーナで修正しました。 AndroidPublisherHelper.newTrustedTransport()
もこれに変更しなければなりませんでした:
SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null, null, null);
// NoSSLv3SocketFactory is @bhavit-s-sengar's http://stackoverflow.com/a/29946540/8524
SSLSocketFactory noSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory());
NetHttpTransport.Builder netTransportBuilder = new NetHttpTransport.Builder();
netTransportBuilder.setSslSocketFactory(noSSLv3Factory);
HTTP_TRANSPORT = netTransportBuilder.build();