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