Repository: google/conscrypt
Issue: NullPointerException in getAlpnSelectedProtocol

We’ve got the exact same crash as #379 but this time we’re calling `getAlpnSelectedProtocol()` instead of `getProtocol()`. I presume the same fix will work: cache more session state when the connection is closed.

```
FATAL EXCEPTION: MockWebServer
Process: com.xxx.xxx.xxx, PID: 5607
java.lang.AssertionError: java.lang.reflect.InvocationTargetException
	at okhttp3.internal.platform.android.AndroidSocketAdapter.a(SourceFile:86)
	at okhttp3.internal.platform.AndroidPlatform.b(SourceFile:86)
	at okhttp3.mockwebserver.MockWebServer$SocketHandler.handle(MockWebServer.kt:516)
	at okhttp3.mockwebserver.MockWebServer$serveConnection$$inlined$execute$1.run(Util.kt:580)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
	at java.lang.Thread.run(Thread.java:764)
Caused by: java.lang.reflect.InvocationTargetException
	at java.lang.reflect.Method.invoke(Native Method)
	at okhttp3.internal.platform.android.AndroidSocketAdapter.a(SourceFile:81)
	... 6 more
Caused by: java.lang.NullPointerException: ssl == null
	at com.android.org.conscrypt.NativeCrypto.getApplicationProtocol(Native Method)
	at com.android.org.conscrypt.NativeSsl.getApplicationProtocol(NativeSsl.java:568)
	at com.android.org.conscrypt.ConscryptFileDescriptorSocket.getApplicationProtocol(ConscryptFileDescriptorSocket.java:1085)
	at com.android.org.conscrypt.OpenSSLSocketImpl.getAlpnSelectedProtocol(OpenSSLSocketImpl.java:134)
	... 8 more
```



Changed files:
common/src/main/java/org/conscrypt/ActiveSession.java
common/src/main/java/org/conscrypt/SessionSnapshot.java
common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java
openjdk/src/test/java/org/conscrypt/ConscryptEngineTest.java
common/src/main/java/org/conscrypt/SSLNullSession.java
common/src/main/java/org/conscrypt/ConscryptSession.java
common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java
common/src/main/java/org/conscrypt/ExternalSession.java

diff --git a/common/src/main/java/org/conscrypt/ActiveSession.java b/common/src/main/java/org/conscrypt/ActiveSession.java
index 4726499a..b00a3fa5 100644
--- a/common/src/main/java/org/conscrypt/ActiveSession.java
+++ b/common/src/main/java/org/conscrypt/ActiveSession.java
@@ -37,6 +37,7 @@ final class ActiveSession implements ConscryptSession {
     private byte[] id;
     private long creationTime;
     private String protocol;
+    private String applicationProtocol;
     private String peerHost;
     private int peerPort = -1;
     private long lastAccessedTime = 0;
@@ -153,25 +154,25 @@ final class ActiveSession implements ConscryptSession {
     @Override
     public void putValue(String name, Object value) {
         throw new UnsupportedOperationException(
-                "All calls to this method should be intercepted by ProvidedSessionDecorator.");
+                "All calls to this method should be intercepted by ExternalSession.");
     }
 
     @Override
     public Object getValue(String name) {
         throw new UnsupportedOperationException(
-                "All calls to this method should be intercepted by ProvidedSessionDecorator.");
+                "All calls to this method should be intercepted by ExternalSession.");
     }
 
     @Override
     public void removeValue(String name) {
         throw new UnsupportedOperationException(
-                "All calls to this method should be intercepted by ProvidedSessionDecorator.");
+                "All calls to this method should be intercepted by ExternalSession.");
     }
 
     @Override
     public String[] getValueNames() {
         throw new UnsupportedOperationException(
-                "All calls to this method should be intercepted by ProvidedSessionDecorator.");
+                "All calls to this method should be intercepted by ExternalSession.");
     }
 
     @Override
@@ -275,6 +276,18 @@ final class ActiveSession implements ConscryptSession {
         return NativeConstants.SSL3_RT_MAX_PLAIN_LENGTH;
     }
 
+    @Override
+    public String getApplicationProtocol() {
+        String applicationProtocol = this.applicationProtocol;
+        if (applicationProtocol == null) {
+            synchronized (ssl) {
+                applicationProtocol = SSLUtils.toProtocolString(ssl.getApplicationProtocol());
+            }
+            this.applicationProtocol = applicationProtocol;
+        }
+        return applicationProtocol;
+    }
+
     /**
      * Configures the peer information once it has been received by the handshake.
      */
diff --git a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
index efbc80a0..aa17760a 100644
--- a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
+++ b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
@@ -1118,7 +1118,7 @@ class ConscryptFileDescriptorSocket extends OpenSSLSocketImpl
 
     @Override
     public final String getApplicationProtocol() {
-        return SSLUtils.toProtocolString(ssl.getApplicationProtocol());
+        return provideSession().getApplicationProtocol();
     }
 
     @Override
diff --git a/common/src/main/java/org/conscrypt/ConscryptSession.java b/common/src/main/java/org/conscrypt/ConscryptSession.java
index e59c1921..ce89b418 100644
--- a/common/src/main/java/org/conscrypt/ConscryptSession.java
+++ b/common/src/main/java/org/conscrypt/ConscryptSession.java
@@ -52,4 +52,6 @@ interface ConscryptSession extends SSLSession {
 
   @Override
   X509Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException;
+
+  String getApplicationProtocol();
 }
diff --git a/common/src/main/java/org/conscrypt/ExternalSession.java b/common/src/main/java/org/conscrypt/ExternalSession.java
index 38a96c1b..18c67a3f 100644
--- a/common/src/main/java/org/conscrypt/ExternalSession.java
+++ b/common/src/main/java/org/conscrypt/ExternalSession.java
@@ -47,7 +47,7 @@ import javax.security.cert.X509Certificate;
  */
 final class ExternalSession implements ConscryptSession {
 
-  // Use an initialcapacity of 2 to keep it small in the average case.
+  // Use an initial capacity of 2 to keep it small in the average case.
   private final HashMap<String, Object> values = new HashMap<String, Object>(2);
   private final Provider provider;
 
@@ -156,6 +156,11 @@ final class ExternalSession implements ConscryptSession {
     return provider.provideSession().getApplicationBufferSize();
   }
 
+  @Override
+  public String getApplicationProtocol() {
+    return provider.provideSession().getApplicationProtocol();
+  }
+
   @Override
   public Object getValue(String name) {
     if (name == null) {
diff --git a/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java b/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java
index 32cb0823..10b11046 100644
--- a/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java
+++ b/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java
@@ -177,4 +177,9 @@ class Java7ExtendedSSLSession extends ExtendedSSLSession implements ConscryptSes
     public final int getApplicationBufferSize() {
         return delegate.getApplicationBufferSize();
     }
+
+    @Override
+    public String getApplicationProtocol() {
+        return delegate.getApplicationProtocol();
+    }
 }
diff --git a/common/src/main/java/org/conscrypt/SSLNullSession.java b/common/src/main/java/org/conscrypt/SSLNullSession.java
index 0cc9066a..b2668439 100644
--- a/common/src/main/java/org/conscrypt/SSLNullSession.java
+++ b/common/src/main/java/org/conscrypt/SSLNullSession.java
@@ -75,6 +75,11 @@ final class SSLNullSession implements ConscryptSession, Cloneable {
         return NativeConstants.SSL3_RT_MAX_PLAIN_LENGTH;
     }
 
+    @Override
+    public String getApplicationProtocol()  {
+        return null;
+    }
+
     @Override
     public String getCipherSuite() {
         return INVALID_CIPHER;
@@ -149,13 +154,13 @@ final class SSLNullSession implements ConscryptSession, Cloneable {
     @Override
     public Object getValue(String name) {
         throw new UnsupportedOperationException(
-                "All calls to this method should be intercepted by ProvidedSessionDecorator.");
+                "All calls to this method should be intercepted by ExternalSession.");
     }
 
     @Override
     public String[] getValueNames() {
         throw new UnsupportedOperationException(
-                "All calls to this method should be intercepted by ProvidedSessionDecorator.");
+                "All calls to this method should be intercepted by ExternalSession.");
     }
 
     @Override
@@ -170,12 +175,12 @@ final class SSLNullSession implements ConscryptSession, Cloneable {
     @Override
     public void putValue(String name, Object value) {
         throw new UnsupportedOperationException(
-                "All calls to this method should be intercepted by ProvidedSessionDecorator.");
+                "All calls to this method should be intercepted by ExternalSession.");
     }
 
     @Override
     public void removeValue(String name) {
         throw new UnsupportedOperationException(
-                "All calls to this method should be intercepted by ProvidedSessionDecorator.");
+                "All calls to this method should be intercepted by ExternalSession.");
     }
 }
diff --git a/common/src/main/java/org/conscrypt/SessionSnapshot.java b/common/src/main/java/org/conscrypt/SessionSnapshot.java
index d4a8bff4..28e63b96 100644
--- a/common/src/main/java/org/conscrypt/SessionSnapshot.java
+++ b/common/src/main/java/org/conscrypt/SessionSnapshot.java
@@ -39,6 +39,7 @@ final class SessionSnapshot implements ConscryptSession {
     private final String cipherSuite;
     private final String protocol;
     private final String peerHost;
+    private final String applicationProtocol;
     private final int peerPort;
 
     SessionSnapshot(ConscryptSession session) {
@@ -53,6 +54,7 @@ final class SessionSnapshot implements ConscryptSession {
         protocol = session.getProtocol();
         peerHost = session.getPeerHost();
         peerPort = session.getPeerPort();
+        applicationProtocol = session.getApplicationProtocol();
     }
 
     @Override
@@ -107,25 +109,25 @@ final class SessionSnapshot implements ConscryptSession {
     @Override
     public void putValue(String s, Object o) {
         throw new UnsupportedOperationException(
-                "All calls to this method should be intercepted by ProvidedSessionDecorator.");
+                "All calls to this method should be intercepted by ExternalSession.");
     }
 
     @Override
     public Object getValue(String s) {
         throw new UnsupportedOperationException(
-                "All calls to this method should be intercepted by ProvidedSessionDecorator.");
+                "All calls to this method should be intercepted by ExternalSession.");
     }
 
     @Override
     public void removeValue(String s) {
         throw new UnsupportedOperationException(
-                "All calls to this method should be intercepted by ProvidedSessionDecorator.");
+                "All calls to this method should be intercepted by ExternalSession.");
     }
 
     @Override
     public String[] getValueNames() {
         throw new UnsupportedOperationException(
-                "All calls to this method should be intercepted by ProvidedSessionDecorator.");
+                "All calls to this method should be intercepted by ExternalSession.");
     }
 
     @Override
@@ -183,4 +185,9 @@ final class SessionSnapshot implements ConscryptSession {
     public int getApplicationBufferSize() {
         return NativeConstants.SSL3_RT_MAX_PLAIN_LENGTH;
     }
+
+    @Override
+    public String getApplicationProtocol() {
+        return applicationProtocol;
+    }
 }
diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptEngineTest.java b/openjdk/src/test/java/org/conscrypt/ConscryptEngineTest.java
index 6cdd235b..473cc738 100644
--- a/openjdk/src/test/java/org/conscrypt/ConscryptEngineTest.java
+++ b/openjdk/src/test/java/org/conscrypt/ConscryptEngineTest.java
@@ -375,16 +375,26 @@ public class ConscryptEngineTest {
 
     @Test
     public void savedSessionWorksAfterClose() throws Exception {
+        String alpnProtocol = "spdy/2";
+        String[] alpnProtocols = new String[]{alpnProtocol};
+
         setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer());
+        Conscrypt.setApplicationProtocols(clientEngine, alpnProtocols);
+        Conscrypt.setApplicationProtocols(serverEngine, alpnProtocols);
+
         doHandshake(true);
 
         SSLSession session = clientEngine.getSession();
         String cipherSuite = session.getCipherSuite();
+        String protocol = session.getProtocol();
+        assertEquals(alpnProtocol, Conscrypt.getApplicationProtocol(clientEngine));
 
         clientEngine.closeOutbound();
         clientEngine.closeInbound();
 
         assertEquals(cipherSuite, session.getCipherSuite());
+        assertEquals(protocol, session.getProtocol());
+        assertEquals(alpnProtocol, Conscrypt.getApplicationProtocol(clientEngine));
     }
 
     private void doMutualAuthHandshake(
diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java b/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java
index 7a15eefd..d0a7eb1d 100644
--- a/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java
+++ b/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java
@@ -623,15 +623,23 @@ public class ConscryptSocketTest {
 
     @Test
     public void savedSessionWorksAfterClose() throws Exception {
+        String alpnProtocol = "spdy/2";
+        String[] alpnProtocols = new String[]{alpnProtocol};
         TestConnection connection = new TestConnection(new X509Certificate[] {cert, ca}, certKey);
+        connection.clientHooks.alpnProtocols = alpnProtocols;
+        connection.serverHooks.alpnProtocols = alpnProtocols;
         connection.doHandshakeSuccess();
 
         SSLSession session = connection.client.getSession();
         String cipherSuite = session.getCipherSuite();
+        String protocol = session.getProtocol();
+        assertEquals(alpnProtocol, Conscrypt.getApplicationProtocol(connection.client));
 
         connection.client.close();
 
         assertEquals(cipherSuite, session.getCipherSuite());
+        assertEquals(protocol, session.getProtocol());
+        assertEquals(alpnProtocol, Conscrypt.getApplicationProtocol(connection.client));
     }
 
     private static ServerSocket newServerSocket() throws IOException {