/* * Copyright 2013 The Android Open Source Project * * 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. */ package com.koushikdutta.async.test; import android.content.Context; import android.test.AndroidTestCase; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.charset.Charset; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; /** * Created by koush on 7/15/14. */ public class ConscryptTests extends AndroidTestCase { boolean initialized; Field peerHost; Field peerPort; Field sslParameters; Field npnProtocols; Field alpnProtocols; Field sslNativePointer; Field useSni; Method nativeGetNpnNegotiatedProtocol; Method nativeGetAlpnNegotiatedProtocol; private void configure(SSLEngine engine, String host, int port) throws Exception { if (!initialized) { initialized = true; peerHost = engine.getClass().getSuperclass().getDeclaredField("peerHost"); peerPort = engine.getClass().getSuperclass().getDeclaredField("peerPort"); sslParameters = engine.getClass().getDeclaredField("sslParameters"); npnProtocols = sslParameters.getType().getDeclaredField("npnProtocols"); alpnProtocols = sslParameters.getType().getDeclaredField("alpnProtocols"); useSni = sslParameters.getType().getDeclaredField("useSni"); sslNativePointer = engine.getClass().getDeclaredField("sslNativePointer"); String nativeCryptoName = sslParameters.getType().getPackage().getName() + ".NativeCrypto"; nativeGetNpnNegotiatedProtocol = Class.forName(nativeCryptoName, true, sslParameters.getType().getClassLoader()) .getDeclaredMethod("SSL_get_npn_negotiated_protocol", long.class); nativeGetAlpnNegotiatedProtocol = Class.forName(nativeCryptoName, true, sslParameters.getType().getClassLoader()) .getDeclaredMethod("SSL_get0_alpn_selected", long.class); peerHost.setAccessible(true); peerPort.setAccessible(true); sslParameters.setAccessible(true); npnProtocols.setAccessible(true); alpnProtocols.setAccessible(true); useSni.setAccessible(true); sslNativePointer.setAccessible(true); nativeGetNpnNegotiatedProtocol.setAccessible(true); nativeGetAlpnNegotiatedProtocol.setAccessible(true); } byte[] protocols = concatLengthPrefixed( "http/1.1", "spdy/3.1" ); peerHost.set(engine, host); peerPort.set(engine, port); Object sslp = sslParameters.get(engine); // npnProtocols.set(sslp, protocols); alpnProtocols.set(sslp, protocols); useSni.set(sslp, true); } static byte[] concatLengthPrefixed(String... protocols) { ByteBuffer result = ByteBuffer.allocate(8192); for (String protocol: protocols) { result.put((byte) protocol.toString().length()); result.put(protocol.toString().getBytes(Charset.forName("UTF-8"))); } result.flip(); byte[] ret = new byte[result.remaining()]; result.get(ret); return ret; } public void testConscryptSSLEngineNPNHandshakeBug() throws Exception { // Security.insertProviderAt(new OpenSSLProvider("MyNameBlah"), 1); Context gms = getContext().createPackageContext("com.google.android.gms", Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); gms .getClassLoader() .loadClass("com.google.android.gms.common.security.ProviderInstallerImpl") .getMethod("insertProvider", Context.class) .invoke(null, getContext()); SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, null, null); SSLEngine engine = ctx.createSSLEngine(); configure(engine, "www.google.com", 443); engine.setUseClientMode(true); engine.beginHandshake(); Socket socket = new Socket(); socket.connect(new InetSocketAddress("www.google.com", 443)); InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); byte[] buf = new byte[65536]; ByteBuffer unwrap = null; ByteBuffer dummy = ByteBuffer.allocate(65536); SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus(); while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED && handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { System.out.println("waiting for read... " + engine.getHandshakeStatus()); int read = is.read(buf); System.out.println("read: " + read); if (read <= 0) throw new Exception("closed!"); if (unwrap != null) { int bufLen = unwrap.remaining() + read; ByteBuffer b = ByteBuffer.allocate(bufLen); b.put(unwrap); b.put(buf, 0, read); b.flip(); unwrap = b; } else { unwrap = ByteBuffer.wrap(buf, 0, read); } if (!unwrap.hasRemaining()) { unwrap = null; } dummy.clear(); SSLEngineResult res = engine.unwrap(unwrap, dummy); System.out.println("data remaining after unwrap: " + unwrap.remaining()); handshakeStatus = res.getHandshakeStatus(); } if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) { dummy.clear(); SSLEngineResult res = engine.wrap(ByteBuffer.allocate(0), dummy); handshakeStatus = res.getHandshakeStatus(); dummy.flip(); if (dummy.hasRemaining()) { os.write(dummy.array(), 0, dummy.remaining()); } } else if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) { engine.getDelegatedTask().run(); } } System.out.println("Done handshaking! Thank you come again."); long ptr = (Long)sslNativePointer.get(engine); byte[] proto = (byte[]) nativeGetAlpnNegotiatedProtocol.invoke(null, ptr); // byte[] proto = (byte[]) nativeGetNpnNegotiatedProtocol.invoke(null, ptr); String protoString = new String(proto); System.out.println("negotiated protocol was: " + protoString); assertEquals(protoString, "spdy/3.1"); socket.close(); } }