Implement keying material generation in EAP-TTLS

- Adds Conscrypt to IKE and implements keying material
generation in phase 2 of EAP-TTLS.

Bug: 161233250
Test: atest FrameworksIkeTests
Change-Id: I5c8d54f08795b327afb5f5ad3a99f074d6358310
diff --git a/Android.bp b/Android.bp
index 38e6895..53dbae1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -34,6 +34,7 @@
         "unsupportedappusage",
         "framework-annotations-lib",
         "framework-telephony-stubs",
+        "conscrypt.module.public.api",
     ],
 
     api_packages: [
diff --git a/src/java/com/android/internal/net/eap/EapResult.java b/src/java/com/android/internal/net/eap/EapResult.java
index 687c9d9..1b403c1 100644
--- a/src/java/com/android/internal/net/eap/EapResult.java
+++ b/src/java/com/android/internal/net/eap/EapResult.java
@@ -117,7 +117,7 @@
          * Constructs an EapError instance for the given cause.
          *
          * @param cause the Exception that caused the EapError to be returned from the
-         *              EapStateMachine
+         *     EapStateMachine
          */
         public EapError(Exception cause) {
             this.cause = cause;
diff --git a/src/java/com/android/internal/net/eap/crypto/TlsSession.java b/src/java/com/android/internal/net/eap/crypto/TlsSession.java
index 50ef031..cb62a6e 100644
--- a/src/java/com/android/internal/net/eap/crypto/TlsSession.java
+++ b/src/java/com/android/internal/net/eap/crypto/TlsSession.java
@@ -17,14 +17,20 @@
 package com.android.internal.net.eap.crypto;
 
 import static com.android.internal.net.eap.EapAuthenticator.LOG;
+import static com.android.internal.net.eap.statemachine.EapMethodStateMachine.MIN_EMSK_LEN_BYTES;
+import static com.android.internal.net.eap.statemachine.EapMethodStateMachine.MIN_MSK_LEN_BYTES;
 
 import android.annotation.IntDef;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.eap.EapResult.EapError;
+import com.android.internal.net.eap.exceptions.EapInvalidRequestException;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.nio.BufferOverflowException;
 import java.nio.ByteBuffer;
 import java.security.GeneralSecurityException;
@@ -87,6 +93,12 @@
     private static final String KEY_STORE_TYPE_PKCS12 = "PKCS12";
     private static final Provider TRUST_MANAGER_PROVIDER = Security.getProvider("HarmonyJSSE");
 
+    // Label for key generation (RFC 5281#8)
+    private static final String TTLS_EXPORTER_LABEL = "ttls keying material";
+    // 128 bytes of keying material. First 64 bytes represent the MSK and the second 64 bytes
+    // represent the EMSK (RFC5281#8)
+    private static final int TTLS_KEYING_MATERIAL_LEN = 128;
+
     private final SSLContext mSslContext;
     private final SSLSession mSslSession;
     private final SSLEngine mSslEngine;
@@ -95,6 +107,7 @@
     // this is kept as an outer variable as the finished state is returned exclusively by
     // wrap/unwrap so it is important to keep track of the handshake status separately
     @VisibleForTesting HandshakeStatus mHandshakeStatus;
+    @VisibleForTesting boolean mHandshakeComplete = false;
     private TrustManager[] mTrustManagers;
 
     private ByteBuffer mApplicationData;
@@ -253,6 +266,7 @@
                     mPacketData.clear();
                     tlsResult = doWrap();
                     if (mHandshakeStatus == HandshakeStatus.FINISHED) {
+                        mHandshakeComplete = true;
                         mHandshakeStatus = mSslEngine.getHandshakeStatus();
                     }
                     break processingLoop;
@@ -397,6 +411,61 @@
     }
 
     /**
+     * Generates the keying material required in EAP-TTLS (RFC5281#8)
+     *
+     * @return EapTtlsKeyingMaterial containing the MSK and EMSK
+     */
+    public EapTtlsKeyingMaterial generateKeyingMaterial() {
+        if (!mHandshakeComplete) {
+            EapInvalidRequestException invalidRequestException =
+                    new EapInvalidRequestException(
+                            "Keying material can only be generated once the handshake is"
+                                    + " complete.");
+            return new EapTtlsKeyingMaterial(new EapError(invalidRequestException));
+        }
+
+        try {
+            // TODO(b/165823103): Use CorePlatformApi for Conscrypt#exportKeyingMaterial
+            Class conscryptClass = Class.forName("com.android.org.conscrypt.Conscrypt");
+            Method getKeyingMaterial =
+                    conscryptClass.getMethod(
+                            "exportKeyingMaterial",
+                            SSLEngine.class,
+                            String.class,
+                            byte[].class,
+                            int.class);
+            // As per RFC5281#8 (and RFC5705#4), generation of keying material in EAP-TTLS does not
+            // require a context.
+            ByteBuffer keyingMaterial =
+                    ByteBuffer.wrap(
+                            (byte[])
+                                    getKeyingMaterial.invoke(
+                                            null /* static, no instance */,
+                                            mSslEngine,
+                                            TTLS_EXPORTER_LABEL,
+                                            null /* context */,
+                                            TTLS_KEYING_MATERIAL_LEN));
+
+            byte[] msk = new byte[MIN_MSK_LEN_BYTES];
+            byte[] emsk = new byte[MIN_EMSK_LEN_BYTES];
+            keyingMaterial.get(msk);
+            keyingMaterial.get(emsk);
+
+            return new EapTtlsKeyingMaterial(msk, emsk);
+        } catch (LinkageError
+                | ClassNotFoundException
+                | IllegalAccessException
+                | NoSuchMethodException
+                | InvocationTargetException e) {
+            // Catch LinkageError to prevent crashing the process and allow the caller to attempt
+            // another authentication method.
+            Exception cause = (e instanceof LinkageError) ? new RuntimeException(e) : (Exception) e;
+            LOG.e(TAG, "Failed to generate EAP-TTLS keying material", cause);
+            return new EapTtlsKeyingMaterial(new EapError(cause));
+        }
+    }
+
+    /**
      * Verifies whether the packet data buffer is in need of additional memory and reallocates if
      * necessary
      */
@@ -447,4 +516,27 @@
             this(new byte[0], status);
         }
     }
+
+    /** EapTtlsKeyingMaterial encapsulates the result of keying material generation in EAP-TTLS */
+    public class EapTtlsKeyingMaterial {
+        public final byte[] msk;
+        public final byte[] emsk;
+        public final EapError eapError;
+
+        public EapTtlsKeyingMaterial(byte[] msk, byte[] emsk) {
+            this.msk = msk;
+            this.emsk = emsk;
+            this.eapError = null;
+        }
+
+        public EapTtlsKeyingMaterial(EapError eapError) {
+            this.msk = null;
+            this.emsk = null;
+            this.eapError = eapError;
+        }
+
+        public boolean isSuccessful() {
+            return eapError == null;
+        }
+    }
 }
diff --git a/src/java/com/android/internal/net/eap/statemachine/EapTtlsMethodStateMachine.java b/src/java/com/android/internal/net/eap/statemachine/EapTtlsMethodStateMachine.java
index ab11ad2..e5db0ce 100644
--- a/src/java/com/android/internal/net/eap/statemachine/EapTtlsMethodStateMachine.java
+++ b/src/java/com/android/internal/net/eap/statemachine/EapTtlsMethodStateMachine.java
@@ -42,6 +42,7 @@
 import com.android.internal.net.eap.EapResult.EapResponse;
 import com.android.internal.net.eap.EapResult.EapSuccess;
 import com.android.internal.net.eap.crypto.TlsSession;
+import com.android.internal.net.eap.crypto.TlsSession.EapTtlsKeyingMaterial;
 import com.android.internal.net.eap.crypto.TlsSession.TlsResult;
 import com.android.internal.net.eap.crypto.TlsSessionFactory;
 import com.android.internal.net.eap.exceptions.EapInvalidRequestException;
@@ -546,11 +547,10 @@
          * Handles EAP-Success and EAP-Failure messages in the tunnel state
          *
          * <p>Both success/failure messages are passed into the inner state machine for processing.
-         * If the inner state machine returns an EapSuccess, the same EapSuccess is returned as a
-         * temporary measure until keying material generation is implemented (b/161233250). The same
-         * is done for an EapFailure. In both cases, the state transitions to the FinalState.
          *
-         * <p>As for EapErrors, this will simply be returned.
+         * <p>If an EAP-Success is returned by the inner state machine, it is discarded and a new
+         * EAP-Success that contains the keying material generated during the TLS negotiation is
+         * sent instead.
          *
          * @param message the EapMessage to be checked for Success/Failure
          * @return the EapResult generated from handling the give EapMessage, or null if the message
@@ -560,21 +560,21 @@
         @Override
         EapResult handleEapSuccessFailure(EapMessage message) {
             if (message.eapCode == EAP_CODE_SUCCESS || message.eapCode == EAP_CODE_FAILURE) {
-                mTlsSession.closeConnection();
                 EapResult innerResult = mInnerEapStateMachine.process(message.encode());
                 if (innerResult instanceof EapSuccess) {
-                    // TODO(b/161233250): Implement keying material generation in EAP-TTLS
-                    // Once implemented, EapSuccess should be handled separately and a new
-                    // EapSuccess with TLS keying material should be returned
-                    LOG.d(mTAG, "Tunneled Authentication Successful");
+                    EapTtlsKeyingMaterial keyingMaterial = mTlsSession.generateKeyingMaterial();
+                    mTlsSession.closeConnection();
                     transitionTo(new FinalState());
-                    return innerResult;
-                } else if (innerResult instanceof EapFailure) {
-                    LOG.d(mTAG, "Tunneled Authentication failed");
-                    transitionTo(new FinalState());
-                    return innerResult;
+
+                    if (!keyingMaterial.isSuccessful()) {
+                        return keyingMaterial.eapError;
+                    }
+
+                    return new EapSuccess(keyingMaterial.msk, keyingMaterial.emsk);
                 }
 
+                transitionTo(new FinalState());
+                mTlsSession.closeConnection();
                 return innerResult;
             }
 
diff --git a/tests/iketests/src/java/com/android/internal/net/eap/EapTtlsTest.java b/tests/iketests/src/java/com/android/internal/net/eap/EapTtlsTest.java
index 99c8a3e..938aca1 100644
--- a/tests/iketests/src/java/com/android/internal/net/eap/EapTtlsTest.java
+++ b/tests/iketests/src/java/com/android/internal/net/eap/EapTtlsTest.java
@@ -22,6 +22,7 @@
 import static com.android.internal.net.eap.crypto.TlsSessionTest.RESULT_NEED_WRAP_OK;
 import static com.android.internal.net.eap.crypto.TlsSessionTest.RESULT_NOT_HANDSHAKING_OK;
 import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_REQUEST_AKA_IDENTITY_PACKET;
+import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_SUCCESS;
 
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
@@ -29,6 +30,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -71,6 +73,20 @@
     private static final int PACKET_BUFFER_SIZE_TLS_MESSAGE = 16384;
     private static final byte[] EMPTY_BYTE_ARRAY = new byte[PACKET_BUFFER_SIZE_TLS_MESSAGE];
 
+    public static final byte[] TLS_MSK =
+            hexStringToByteArray(
+                    "00112233445566778899AABBCCDDEEFF"
+                            + "00112233445566778899AABBCCDDEEFF"
+                            + "00112233445566778899AABBCCDDEEFF"
+                            + "00112233445566778899AABBCCDDEEFF");
+
+    public static final byte[] TLS_EMSK =
+            hexStringToByteArray(
+                    "FFEEDDCCBBAA99887766554433221100"
+                            + "FFEEDDCCBBAA99887766554433221100"
+                            + "FFEEDDCCBBAA99887766554433221100"
+                            + "FFEEDDCCBBAA99887766554433221100");
+
     // TUNNELED MSCHAPV2 (Phase 2)
 
     private static final String MSCHAPV2_USERNAME = "mschapv2.android.net";
@@ -419,6 +435,7 @@
     private final TlsSessionFactory mMockTlsSessionFactory = mock(TlsSessionFactory.class);
     private final SSLEngine mMockSslEngine = mock(SSLEngine.class);
     private final SSLSession mMockSslSession = mock(SSLSession.class);
+    private TlsSession mTlsSessionSpy;
 
     @Before
     @Override
@@ -443,14 +460,23 @@
         when(mMockSslSession.getApplicationBufferSize())
                 .thenReturn(APPLICATION_BUFFER_SIZE_TLS_MESSAGE);
         when(mMockSslSession.getPacketBufferSize()).thenReturn(PACKET_BUFFER_SIZE_TLS_MESSAGE);
-        TlsSession tlsSession =
-                new TlsSession(
-                        mock(SSLContext.class), mMockSslEngine, mMockSslSession, mMockSecureRandom);
+
+        // TODO(b/165823103): Switch EAP-TTLS to use CorePlatformApi for
+        // Conscrypt#exportKeyingMaterial
+        mTlsSessionSpy =
+                spy(
+                        new TlsSession(
+                                mock(SSLContext.class),
+                                mMockSslEngine,
+                                mMockSslSession,
+                                mMockSecureRandom));
+        when(mTlsSessionSpy.generateKeyingMaterial())
+                .thenReturn(mTlsSessionSpy.new EapTtlsKeyingMaterial(TLS_MSK, TLS_EMSK));
 
         EapTtlsMethodStateMachine.sTlsSessionFactory = mMockTlsSessionFactory;
         try {
             when(mMockTlsSessionFactory.newInstance(eq(null), eq(mMockSecureRandom)))
-                    .thenReturn(tlsSession);
+                    .thenReturn(mTlsSessionSpy);
         } catch (Exception e) {
             throw new AssertionError("TLS Session setup failed", e);
         }
@@ -469,8 +495,7 @@
         processAndVerifyServerFinished();
         processAndVerifyMsChapV2ChallengeRequest();
         processAndVerifyMsChapV2SuccessRequest();
-        // TODO(b/161233250): Implement keying material generation in EAP-TTLS
-        verifyEapSuccess(MSCHAPV2_MSK, MSCHAPV2_EMSK);
+        processAndVerifyEapSuccess(TLS_MSK, TLS_EMSK);
     }
 
     @Test
@@ -492,8 +517,7 @@
         processAndVerifyMsChapV2SuccessRequest();
 
         verifyEapNotification(6);
-        // TODO(b/161233250): Implement keying material generation in EAP-TTLS
-        verifyEapSuccess(MSCHAPV2_MSK, MSCHAPV2_EMSK);
+        processAndVerifyEapSuccess(TLS_MSK, TLS_EMSK);
     }
 
     @Test
@@ -510,8 +534,7 @@
         processAndVerifyMsChapV2SuccessRequest();
 
         processAndVerifyTunneledEapNotification(3);
-        // TODO(b/161233250): Implement keying material generation in EAP-TTLS
-        verifyEapSuccess(MSCHAPV2_MSK, MSCHAPV2_EMSK);
+        processAndVerifyEapSuccess(TLS_MSK, TLS_EMSK);
     }
 
     @Test
@@ -535,8 +558,7 @@
         processAndVerifyServerFinished();
         processAndVerifyMsChapV2ChallengeRequest();
         processAndVerifyMsChapV2SuccessRequest();
-        // TODO(b/161233250): Implement keying material generation in EAP-TTLS
-        verifyEapSuccess(MSCHAPV2_MSK, MSCHAPV2_EMSK);
+        processAndVerifyEapSuccess(TLS_MSK, TLS_EMSK);
     }
 
     @Test
@@ -550,8 +572,7 @@
 
         processAndVerifyMsChapV2ChallengeRequest();
         processAndVerifyMsChapV2SuccessRequest();
-        // TODO(b/161233250): Implement keying material generation in EAP-TTLS
-        verifyEapSuccess(MSCHAPV2_MSK, MSCHAPV2_EMSK);
+        processAndVerifyEapSuccess(TLS_MSK, TLS_EMSK);
     }
 
     private void processAndVerifyStartRequest() throws Exception {
@@ -710,6 +731,17 @@
         verifyNoMoreInteractions(mMockCallback);
     }
 
+    private void processAndVerifyEapSuccess(byte[] msk, byte[] emsk) throws Exception {
+        // EAP-Success
+        mEapAuthenticator.processEapMessage(EAP_SUCCESS);
+        mTestLooper.dispatchAll();
+
+        // verify that onSuccess callback made
+        verify(mMockCallback).onSuccess(eq(msk), eq(emsk));
+        verify(mTlsSessionSpy).generateKeyingMaterial();
+        verifyNoMoreInteractions(mMockContext, mMockSecureRandom, mMockCallback);
+    }
+
     private void setupUnwrap(
             byte[] applicationData, byte[] packetData, SSLEngineResult result) throws Exception {
         doAnswer(invocation -> {
diff --git a/tests/iketests/src/java/com/android/internal/net/eap/crypto/TlsSessionTest.java b/tests/iketests/src/java/com/android/internal/net/eap/crypto/TlsSessionTest.java
index 1b4bd33..bc4bb15 100644
--- a/tests/iketests/src/java/com/android/internal/net/eap/crypto/TlsSessionTest.java
+++ b/tests/iketests/src/java/com/android/internal/net/eap/crypto/TlsSessionTest.java
@@ -22,12 +22,16 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.android.internal.net.eap.crypto.TlsSession.EapTtlsKeyingMaterial;
 import com.android.internal.net.eap.crypto.TlsSession.TlsResult;
+import com.android.internal.net.eap.exceptions.EapInvalidRequestException;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -176,6 +180,14 @@
         verify(mMockSslEngine).wrap(eq(EMPTY_APPLICATION_BUFFER), eq(EMPTY_PACKET_BUFFER));
     }
 
+    @Test
+    public void testGenerateKeyingMaterial_handshakeNotComplete() throws Exception {
+        EapTtlsKeyingMaterial result = mTlsSession.generateKeyingMaterial();
+
+        assertFalse(result.isSuccessful());
+        assertTrue(result.eapError.cause instanceof EapInvalidRequestException);
+    }
+
     /**
      * Mocks a wrap operation and inserts data into the packet buffer
      *
diff --git a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapTtlsTunnelStateTest.java b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapTtlsTunnelStateTest.java
index 46a38f6..d908638 100644
--- a/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapTtlsTunnelStateTest.java
+++ b/tests/iketests/src/java/com/android/internal/net/eap/statemachine/EapTtlsTunnelStateTest.java
@@ -36,9 +36,11 @@
 import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_TTLS_DUMMY_DATA_BYTES;
 import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_TTLS_DUMMY_DATA_FINAL_FRAGMENT_BYTES;
 import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EAP_TTLS_DUMMY_DATA_INITIAL_FRAGMENT_BYTES;
+import static com.android.internal.net.eap.message.EapTestMessageDefinitions.EMSK;
 import static com.android.internal.net.eap.message.EapTestMessageDefinitions.ID_INT;
 import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_EMSK;
 import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSCHAP_V2_MSK;
+import static com.android.internal.net.eap.message.EapTestMessageDefinitions.MSK;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertFalse;
@@ -131,13 +133,16 @@
         EapMessage eapMessage = new EapMessage(EAP_CODE_SUCCESS, ID_INT, null);
 
         when(mMockInnerEapStateMachine.process(eq(EAP_SUCCESS_PACKET))).thenReturn(msChapV2Success);
+        when(mMockTlsSession.generateKeyingMaterial())
+                .thenReturn(mMockTlsSession.new EapTtlsKeyingMaterial(MSK, EMSK));
 
         EapResult result = mStateMachine.process(eapMessage);
         EapSuccess eapSuccess = (EapSuccess) result;
-        assertArrayEquals(MSCHAP_V2_MSK, eapSuccess.msk);
-        assertArrayEquals(MSCHAP_V2_EMSK, eapSuccess.emsk);
+        assertArrayEquals(MSK, eapSuccess.msk);
+        assertArrayEquals(EMSK, eapSuccess.emsk);
         assertTrue(mStateMachine.getState() instanceof FinalState);
         verify(mMockInnerEapStateMachine).process(eq(EAP_SUCCESS_PACKET));
+        verify(mMockTlsSession).generateKeyingMaterial();
     }
 
     @Test