Implement MSK generation for EAP MSCHAPv2.
AwaitingEapSuccessState will return an EapSuccess object when an
EAP-Success message is received. However, this requires generating the
MSK for the current session first. Utils are defined for generating the
MSK as defined in RFC 3079. Only 128-bit (16B) keys are generated with
this implementation.
Bug: 141483998
Test: added tests for EapMsChapV2MethodStateMachineTest.
Test: atest FrameworksIkeTests
Change-Id: Ia472cc7260bb270ab5c5069c309b8cac89b2d719
diff --git a/src/java/com/android/ike/eap/statemachine/EapMsChapV2MethodStateMachine.java b/src/java/com/android/ike/eap/statemachine/EapMsChapV2MethodStateMachine.java
index 4b7e253..d5a27de 100644
--- a/src/java/com/android/ike/eap/statemachine/EapMsChapV2MethodStateMachine.java
+++ b/src/java/com/android/ike/eap/statemachine/EapMsChapV2MethodStateMachine.java
@@ -102,13 +102,17 @@
private static final int Z_PASSWORD_HASH_LEN = 21;
private static final int Z_PASSWORD_SECTION_LEN = 7;
private static final int RESPONSE_SECTION_LEN = 8;
+ private static final int SHS_PAD_LEN = 40;
+ private static final int MASTER_KEY_LEN = 16;
+ private static final int SESSION_KEY_LEN = 16;
+ private static final int MASTER_SESSION_KEY_LEN = 2 * SESSION_KEY_LEN;
// Reserved for future use and must be 0 (EAP MSCHAPv2#2.2)
private static final int FLAGS = 0;
// we all need a little magic in our lives
- // Defined in RFC 2759#8.7. Constants used for response Success response generation.
- private static final byte[] MAGIC_1 = {
+ // Defined in RFC 2759#8.7. Constants used for Success response generation.
+ private static final byte[] CHALLENGE_MAGIC_1 = {
(byte) 0x4D, (byte) 0x61, (byte) 0x67, (byte) 0x69, (byte) 0x63, (byte) 0x20, (byte) 0x73,
(byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65, (byte) 0x72, (byte) 0x20, (byte) 0x74,
(byte) 0x6F, (byte) 0x20, (byte) 0x63, (byte) 0x6C, (byte) 0x69, (byte) 0x65, (byte) 0x6E,
@@ -116,7 +120,7 @@
(byte) 0x6E, (byte) 0x67, (byte) 0x20, (byte) 0x63, (byte) 0x6F, (byte) 0x6E, (byte) 0x73,
(byte) 0x74, (byte) 0x61, (byte) 0x6E, (byte) 0x74
};
- private static final byte[] MAGIC_2 = {
+ private static final byte[] CHALLENGE_MAGIC_2 = {
(byte) 0x50, (byte) 0x61, (byte) 0x64, (byte) 0x20, (byte) 0x74, (byte) 0x6F, (byte) 0x20,
(byte) 0x6D, (byte) 0x61, (byte) 0x6B, (byte) 0x65, (byte) 0x20, (byte) 0x69, (byte) 0x74,
(byte) 0x20, (byte) 0x64, (byte) 0x6F, (byte) 0x20, (byte) 0x6D, (byte) 0x6F, (byte) 0x72,
@@ -125,6 +129,54 @@
(byte) 0x72, (byte) 0x61, (byte) 0x74, (byte) 0x69, (byte) 0x6F, (byte) 0x6E
};
+ // Defined in RFC 3079#3.4. Constants used for Master Session Key (MSK) generation
+ private static final byte[] SHS_PAD_1 = new byte[SHS_PAD_LEN];
+ private static final byte[] SHS_PAD_2 = new byte[SHS_PAD_LEN];
+
+ static {
+ Arrays.fill(SHS_PAD_2, (byte) 0xF2);
+ }
+
+ private static final byte[] MSK_MAGIC_1 = {
+ (byte) 0x54, (byte) 0x68, (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x69,
+ (byte) 0x73, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x65, (byte) 0x20,
+ (byte) 0x4D, (byte) 0x50, (byte) 0x50, (byte) 0x45, (byte) 0x20, (byte) 0x4D,
+ (byte) 0x61, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x20,
+ (byte) 0x4B, (byte) 0x65, (byte) 0x79
+ };
+ private static final byte[] MSK_MAGIC_2 = {
+ (byte) 0x4F, (byte) 0x6E, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x65,
+ (byte) 0x20, (byte) 0x63, (byte) 0x6C, (byte) 0x69, (byte) 0x65, (byte) 0x6E,
+ (byte) 0x74, (byte) 0x20, (byte) 0x73, (byte) 0x69, (byte) 0x64, (byte) 0x65,
+ (byte) 0x2C, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x69, (byte) 0x73,
+ (byte) 0x20, (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x74, (byte) 0x68,
+ (byte) 0x65, (byte) 0x20, (byte) 0x73, (byte) 0x65, (byte) 0x6E, (byte) 0x64,
+ (byte) 0x20, (byte) 0x6B, (byte) 0x65, (byte) 0x79, (byte) 0x3B, (byte) 0x20,
+ (byte) 0x6F, (byte) 0x6E, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x65,
+ (byte) 0x20, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65,
+ (byte) 0x72, (byte) 0x20, (byte) 0x73, (byte) 0x69, (byte) 0x64, (byte) 0x65,
+ (byte) 0x2C, (byte) 0x20, (byte) 0x69, (byte) 0x74, (byte) 0x20, (byte) 0x69,
+ (byte) 0x73, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x65, (byte) 0x20,
+ (byte) 0x72, (byte) 0x65, (byte) 0x63, (byte) 0x65, (byte) 0x69, (byte) 0x76,
+ (byte) 0x65, (byte) 0x20, (byte) 0x6B, (byte) 0x65, (byte) 0x79, (byte) 0x2E
+ };
+ private static final byte[] MSK_MAGIC_3 = {
+ (byte) 0x4F, (byte) 0x6E, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x65,
+ (byte) 0x20, (byte) 0x63, (byte) 0x6C, (byte) 0x69, (byte) 0x65, (byte) 0x6E,
+ (byte) 0x74, (byte) 0x20, (byte) 0x73, (byte) 0x69, (byte) 0x64, (byte) 0x65,
+ (byte) 0x2C, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x69, (byte) 0x73,
+ (byte) 0x20, (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x74, (byte) 0x68,
+ (byte) 0x65, (byte) 0x20, (byte) 0x72, (byte) 0x65, (byte) 0x63, (byte) 0x65,
+ (byte) 0x69, (byte) 0x76, (byte) 0x65, (byte) 0x20, (byte) 0x6B, (byte) 0x65,
+ (byte) 0x79, (byte) 0x3B, (byte) 0x20, (byte) 0x6F, (byte) 0x6E, (byte) 0x20,
+ (byte) 0x74, (byte) 0x68, (byte) 0x65, (byte) 0x20, (byte) 0x73, (byte) 0x65,
+ (byte) 0x72, (byte) 0x76, (byte) 0x65, (byte) 0x72, (byte) 0x20, (byte) 0x73,
+ (byte) 0x69, (byte) 0x64, (byte) 0x65, (byte) 0x2C, (byte) 0x20, (byte) 0x69,
+ (byte) 0x74, (byte) 0x20, (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x74,
+ (byte) 0x68, (byte) 0x65, (byte) 0x20, (byte) 0x73, (byte) 0x65, (byte) 0x6E,
+ (byte) 0x64, (byte) 0x20, (byte) 0x6B, (byte) 0x65, (byte) 0x79, (byte) 0x2E
+ };
+
private final EapMsChapV2Config mEapMsChapV2Config;
private final SecureRandom mSecureRandom;
private final EapMsChapV2TypeDataDecoder mTypeDataDecoder;
@@ -488,14 +540,14 @@
MessageDigest sha1 = MessageDigest.getInstance(SHA_ALG);
sha1.update(passwordHashHash);
sha1.update(ntResponse);
- sha1.update(MAGIC_1); // add just a dash of magic
+ sha1.update(CHALLENGE_MAGIC_1); // add just a dash of magic
byte[] digest = sha1.digest();
byte[] challenge = challengeHash(peerChallenge, authenticatorChallenge, username);
sha1.update(digest);
sha1.update(challenge);
- sha1.update(MAGIC_2);
+ sha1.update(CHALLENGE_MAGIC_2);
return sha1.digest();
}
@@ -515,4 +567,44 @@
password, ntResponse, peerChallenge, authenticatorChallenge, userName);
return Arrays.equals(myResponse, receivedResponse);
}
+
+ /* Implementation of RFC 3079#3.4: GetMasterKey() */
+ @VisibleForTesting
+ static byte[] getMasterKey(byte[] passwordHashHash, byte[] ntResponse)
+ throws GeneralSecurityException {
+ MessageDigest sha1 = MessageDigest.getInstance(SHA_ALG);
+ sha1.update(passwordHashHash);
+ sha1.update(ntResponse);
+ sha1.update(MSK_MAGIC_1);
+ return Arrays.copyOf(sha1.digest(), MASTER_KEY_LEN);
+ }
+
+ /* Implementation of RFC 3079#3.4: GetAsymmetricStartKey() */
+ @VisibleForTesting
+ static byte[] getAsymmetricStartKey(byte[] masterKey, boolean isSend)
+ throws GeneralSecurityException {
+ // salt: referred to as 's' in RFC 3079#3.4 GetAsymmetricStartKey()
+ byte[] salt = isSend ? MSK_MAGIC_2 : MSK_MAGIC_3;
+ MessageDigest sha1 = MessageDigest.getInstance(SHA_ALG);
+ sha1.update(masterKey);
+ sha1.update(SHS_PAD_1);
+ sha1.update(salt);
+ sha1.update(SHS_PAD_2);
+ return Arrays.copyOf(sha1.digest(), SESSION_KEY_LEN);
+ }
+
+ @VisibleForTesting
+ static byte[] generateMsk(String password, byte[] ntResponse)
+ throws GeneralSecurityException, UnsupportedEncodingException {
+ byte[] passwordHash = ntPasswordHash(password);
+ byte[] passwordHashHash = hashNtPasswordHash(passwordHash);
+ byte[] masterKey = getMasterKey(passwordHashHash, ntResponse);
+
+ // MSK: SendKey + ReceiveKey
+ ByteBuffer msk = ByteBuffer.allocate(MASTER_SESSION_KEY_LEN);
+ msk.put(getAsymmetricStartKey(masterKey, true /* isSend */));
+ msk.put(getAsymmetricStartKey(masterKey, false /* isSend */));
+
+ return msk.array();
+ }
}
diff --git a/tests/iketests/src/java/com/android/ike/eap/message/EapTestMessageDefinitions.java b/tests/iketests/src/java/com/android/ike/eap/message/EapTestMessageDefinitions.java
index a2a811a..50e88a8 100644
--- a/tests/iketests/src/java/com/android/ike/eap/message/EapTestMessageDefinitions.java
+++ b/tests/iketests/src/java/com/android/ike/eap/message/EapTestMessageDefinitions.java
@@ -236,7 +236,7 @@
public static final byte[] EAP_REQUEST_MSCHAP_V2 =
hexStringToByteArray("01" + ID + "00061A01");
- // MSCHAPv2 Test vectors taken from RFC 2759#9.2
+ // MSCHAPv2 Test vectors taken from RFC 2759#9.2 and RFC 3079#3.5.3
public static final String MSCHAP_V2_USERNAME = "User";
public static final String MSCHAP_V2_USERNAME_HEX = "55736572";
public static final byte[] MSCHAP_V2_USERNAME_ASCII_BYTES =
@@ -262,6 +262,20 @@
hexStringToByteArray(MSCHAP_V2_NT_RESPONSE_STRING);
public static final byte[] MSCHAP_V2_AUTHENTICATOR_RESPONSE =
hexStringToByteArray("407A5589115FD0D6209F510FE9C04566932CDA56");
+ public static final byte[] MSCHAP_V2_MASTER_KEY =
+ hexStringToByteArray("FDECE3717A8C838CB388E527AE3CDD31");
+
+ // generated based on RFC 3079#3.5.3 params
+ public static final String SEND_KEY = "D5F0E9521E3EA9589645E86051C82226";
+ public static final byte[] MSCHAP_V2_SEND_START_KEY = hexStringToByteArray(SEND_KEY);
+
+ // This value is labeled 'send key' in RFC 3079#3.5.3. However, it's used as 'receive key' here,
+ // because send and receive keys are swapped for peers relative to authenticators.
+ public static final String RECEIVE_KEY = "8B7CDC149B993A1BA118CB153F56DCCB";
+ public static final byte[] MSCHAP_V2_RECEIVE_START_KEY = hexStringToByteArray(RECEIVE_KEY);
+
+ // MSK: MSCHAP_V2_SEND_START_KEY + MSCHAP_V2_RECEIVE_START_KEY
+ public static final byte[] MSCHAP_V2_MSK = hexStringToByteArray(SEND_KEY + RECEIVE_KEY);
public static final String MSCHAP_V2_ID = "42";
public static final int MSCHAP_V2_ID_INT = Integer.parseInt(MSCHAP_V2_ID, 16 /* radix */);
diff --git a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapMsChapV2MethodStateMachineTest.java b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapMsChapV2MethodStateMachineTest.java
index f671a42..4d57e43 100644
--- a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapMsChapV2MethodStateMachineTest.java
+++ b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapMsChapV2MethodStateMachineTest.java
@@ -20,12 +20,16 @@
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_AUTHENTICATOR_CHALLENGE;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_AUTHENTICATOR_RESPONSE;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_CHALLENGE;
+import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_MASTER_KEY;
+import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_MSK;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_NT_RESPONSE;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PASSWORD;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PASSWORD_HASH;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PASSWORD_HASH_HASH;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PASSWORD_UTF_BYTES;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_PEER_CHALLENGE;
+import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_RECEIVE_START_KEY;
+import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_SEND_START_KEY;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_USERNAME;
import static com.android.ike.eap.message.EapTestMessageDefinitions.MSCHAP_V2_USERNAME_ASCII_BYTES;
@@ -35,6 +39,7 @@
import com.android.ike.eap.EapSessionConfig.EapMsChapV2Config;
import com.android.ike.eap.statemachine.EapMsChapV2MethodStateMachine.CreatedState;
+import com.android.ike.utils.Log;
import org.junit.Before;
import org.junit.Test;
@@ -143,4 +148,34 @@
MSCHAP_V2_USERNAME,
MSCHAP_V2_AUTHENTICATOR_RESPONSE));
}
+
+ @Test
+ public void testGetMasterKey() throws Exception {
+ byte[] masterKey =
+ EapMsChapV2MethodStateMachine.getMasterKey(
+ MSCHAP_V2_PASSWORD_HASH_HASH, MSCHAP_V2_NT_RESPONSE);
+ assertArrayEquals(MSCHAP_V2_MASTER_KEY, masterKey);
+ }
+
+ @Test
+ public void testGetAsymmetricStartKeySendKey() throws Exception {
+ byte[] startKey =
+ EapMsChapV2MethodStateMachine.getAsymmetricStartKey(MSCHAP_V2_MASTER_KEY, true);
+ assertArrayEquals(Log.byteArrayToHexString(startKey), MSCHAP_V2_SEND_START_KEY, startKey);
+ }
+
+ @Test
+ public void testGetAsymmetricStartKeyReceiveKey() throws Exception {
+ byte[] receiveKey =
+ EapMsChapV2MethodStateMachine.getAsymmetricStartKey(MSCHAP_V2_MASTER_KEY, false);
+ assertArrayEquals(MSCHAP_V2_RECEIVE_START_KEY, receiveKey);
+ }
+
+ @Test
+ public void testGenerateMsk() throws Exception {
+ byte[] msk =
+ EapMsChapV2MethodStateMachine.generateMsk(
+ MSCHAP_V2_PASSWORD, MSCHAP_V2_NT_RESPONSE);
+ assertArrayEquals(MSCHAP_V2_MSK, msk);
+ }
}