Snap for 5988121 from fdef979532d951eed6d5d000820f23a794a4b725 to qt-aml-networking-release

Change-Id: I07401813a651d0c0d1414a52f7fe702e7d642284
diff --git a/src/java/com/android/ike/crypto/KeyGenerationUtils.java b/src/java/com/android/ike/crypto/KeyGenerationUtils.java
new file mode 100644
index 0000000..5ac8c57
--- /dev/null
+++ b/src/java/com/android/ike/crypto/KeyGenerationUtils.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 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.android.ike.crypto;
+
+import java.nio.ByteBuffer;
+
+/**
+ * KeyGenerationUtils is a util class that contains utils for key generation needed by IKEv2 and
+ * EAP.
+ */
+public class KeyGenerationUtils {
+    /**
+     * Returns the derived pseudorandom number with the specified length by iteratively applying a
+     * PRF.
+     *
+     * <p>prf+(K, S) outputs a pseudorandom stream by using the PRF iteratively. In this way it can
+     * generate long enough keying material containing all the keys.
+     *
+     * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.13">RFC 7296 Internet Key
+     *     Exchange Protocol Version 2 (IKEv2) 2.13. Generating Keying Material </a>
+     * @param byteSigner the PRF used to sign the given data using the given key.
+     * @param keyBytes the key to sign data.
+     * @param dataToSign the data to be signed.
+     * @param keyMaterialLen the length of keying materials to be generated.
+     * @return the byte array of keying materials
+     */
+    public static byte[] prfPlus(
+            ByteSigner byteSigner, byte[] keyBytes, byte[] dataToSign, int keyMaterialLen) {
+        ByteBuffer keyMatBuffer = ByteBuffer.allocate(keyMaterialLen);
+
+        byte[] previousMac = new byte[0];
+        final int padLen = 1;
+        byte padValue = 1;
+
+        while (keyMatBuffer.remaining() > 0) {
+            ByteBuffer dataToSignBuffer =
+                    ByteBuffer.allocate(previousMac.length + dataToSign.length + padLen);
+            dataToSignBuffer.put(previousMac).put(dataToSign).put(padValue);
+
+            previousMac = byteSigner.signBytes(keyBytes, dataToSignBuffer.array());
+
+            keyMatBuffer.put(
+                    previousMac, 0, Math.min(previousMac.length, keyMatBuffer.remaining()));
+
+            padValue++;
+        }
+
+        return keyMatBuffer.array();
+    }
+
+    /**
+     * ByteSigner is an interface to be used for implementing the byte-signing for generating keys
+     * using {@link KeyGenerationUtils#prfPlus(ByteSigner, byte[], byte[], int)}.
+     */
+    public interface ByteSigner {
+        /**
+         * Signs the given data using the key given.
+         *
+         * <p>Caller is responsible for providing a valid key according to their use cases.
+         *
+         * @param keyBytes the key to sign data.
+         * @param dataToSign the data to be signed.
+         * @return the signed value.
+         */
+        byte[] signBytes(byte[] keyBytes, byte[] dataToSign);
+    }
+}
diff --git a/src/java/com/android/ike/eap/crypto/HmacSha256ByteSigner.java b/src/java/com/android/ike/eap/crypto/HmacSha256ByteSigner.java
new file mode 100644
index 0000000..db511eb
--- /dev/null
+++ b/src/java/com/android/ike/eap/crypto/HmacSha256ByteSigner.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 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.android.ike.eap.crypto;
+
+import static com.android.ike.eap.EapAuthenticator.LOG;
+
+import com.android.ike.crypto.KeyGenerationUtils;
+import com.android.ike.crypto.KeyGenerationUtils.ByteSigner;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * HmacSha256ByteSigner is a {@link ByteSigner} to be used for computing HMAC-SHA-256 values for
+ * specific keys and data.
+ */
+public class HmacSha256ByteSigner implements KeyGenerationUtils.ByteSigner {
+    private static final String TAG = HmacSha256ByteSigner.class.getSimpleName();
+    private static final String MAC_ALGORITHM_STRING = "HmacSHA256";
+    private static final HmacSha256ByteSigner sInstance = new HmacSha256ByteSigner();
+
+    /**
+     * Gets instance of HmacSha256ByteSigner.
+     *
+     * @return HmacSha256ByteSigner instance.
+     */
+    public static HmacSha256ByteSigner getInstance() {
+        return sInstance;
+    }
+
+    @Override
+    public byte[] signBytes(byte[] keyBytes, byte[] dataToSign) {
+        try {
+            Mac mac = Mac.getInstance(MAC_ALGORITHM_STRING);
+            mac.init(new SecretKeySpec(keyBytes, MAC_ALGORITHM_STRING));
+            return mac.doFinal(dataToSign);
+        } catch (NoSuchAlgorithmException | InvalidKeyException ex) {
+            LOG.wtf(TAG, "Error computing HMAC-SHA-256", ex);
+            throw new IllegalArgumentException(ex);
+        }
+    }
+}
diff --git a/src/java/com/android/ike/eap/statemachine/EapAkaMethodStateMachine.java b/src/java/com/android/ike/eap/statemachine/EapAkaMethodStateMachine.java
index a0f1312..8940c33 100644
--- a/src/java/com/android/ike/eap/statemachine/EapAkaMethodStateMachine.java
+++ b/src/java/com/android/ike/eap/statemachine/EapAkaMethodStateMachine.java
@@ -29,6 +29,7 @@
 import static com.android.ike.eap.message.simaka.EapAkaTypeData.EAP_AKA_SYNCHRONIZATION_FAILURE;
 import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_ANY_ID_REQ;
 import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_AUTN;
+import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_BIDDING;
 import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_ENCR_DATA;
 import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_FULLAUTH_ID_REQ;
 import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_IV;
@@ -36,6 +37,7 @@
 import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_PERMANENT_ID_REQ;
 import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_RAND;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.telephony.TelephonyManager;
 
@@ -59,6 +61,7 @@
 import com.android.ike.eap.message.simaka.EapSimAkaAttribute;
 import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtAutn;
 import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtAuts;
+import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtBidding;
 import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode;
 import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtIdentity;
 import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtRandAka;
@@ -103,13 +106,24 @@
     private static final String AKA_IDENTITY_PREFIX = "0";
 
     private final EapAkaTypeDataDecoder mEapAkaTypeDataDecoder;
+    private final boolean mSupportsEapAkaPrime;
 
-    EapAkaMethodStateMachine(Context context, byte[] eapIdentity, EapAkaConfig eapAkaConfig) {
+    protected EapAkaMethodStateMachine(
+            Context context, byte[] eapIdentity, EapAkaConfig eapAkaConfig) {
+        this(context, eapIdentity, eapAkaConfig, false);
+    }
+
+    EapAkaMethodStateMachine(
+            Context context,
+            byte[] eapIdentity,
+            EapAkaConfig eapAkaConfig,
+            boolean supportsEapAkaPrime) {
         this(
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
                 eapIdentity,
                 eapAkaConfig,
-                EapAkaTypeData.getEapAkaTypeDataDecoder());
+                EapAkaTypeData.getEapAkaTypeDataDecoder(),
+                supportsEapAkaPrime);
     }
 
     @VisibleForTesting
@@ -117,12 +131,14 @@
             TelephonyManager telephonyManager,
             byte[] eapIdentity,
             EapAkaConfig eapAkaConfig,
-            EapAkaTypeDataDecoder eapAkaTypeDataDecoder) {
+            EapAkaTypeDataDecoder eapAkaTypeDataDecoder,
+            boolean supportsEapAkaPrime) {
         super(
                 telephonyManager.createForSubscriptionId(eapAkaConfig.subId),
                 eapIdentity,
                 eapAkaConfig);
         mEapAkaTypeDataDecoder = eapAkaTypeDataDecoder;
+        mSupportsEapAkaPrime = supportsEapAkaPrime;
 
         transitionTo(new CreatedState());
     }
@@ -374,23 +390,17 @@
             } catch (EapSimAkaInvalidLengthException | BufferUnderflowException ex) {
                 LOG.e(mTAG, "Invalid response returned from SIM", ex);
                 return buildClientErrorResponse(
-                        message.eapIdentifier,
-                        EAP_TYPE_AKA,
-                        AtClientErrorCode.UNABLE_TO_PROCESS);
+                        message.eapIdentifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS);
             } catch (EapSimAkaAuthenticationFailureException ex) {
                 // Return EAP-Response/AKA-Authentication-Reject when the AUTN is rejected
                 // (RFC 4187#6.3.1)
-                return buildResponseMessage(
-                        EAP_TYPE_AKA,
-                        EAP_AKA_AUTHENTICATION_REJECT,
-                        message.eapIdentifier,
-                        new ArrayList<>());
+                return buildAuthenticationRejectMessage(message.eapIdentifier);
             }
 
             if (!result.isSuccessfulResult()) {
                 try {
                     return buildResponseMessage(
-                            EAP_TYPE_AKA,
+                            getEapMethod(),
                             EAP_AKA_SYNCHRONIZATION_FAILURE,
                             message.eapIdentifier,
                             Arrays.asList(new AtAuts(result.auts)));
@@ -400,21 +410,17 @@
                 }
             }
 
-            try {
-                MessageDigest sha1 = MessageDigest.getInstance(MASTER_KEY_GENERATION_ALG);
-                byte[] mkInputData = getMkInputData(result);
-                generateAndPersistKeys(mTAG, sha1, new Fips186_2Prf(), mkInputData);
-            } catch (NoSuchAlgorithmException | BufferUnderflowException ex) {
-                LOG.e(mTAG, "Error while creating keys", ex);
-                return buildClientErrorResponse(
-                        message.eapIdentifier, EAP_TYPE_AKA, AtClientErrorCode.UNABLE_TO_PROCESS);
+            EapResult eapResult =
+                    generateAndPersistEapAkaKeys(result, message.eapIdentifier, eapAkaTypeData);
+            if (eapResult != null) {
+                return eapResult;
             }
 
             try {
                 if (!isValidMac(mTAG, message, eapAkaTypeData, new byte[0])) {
                     return buildClientErrorResponse(
                             message.eapIdentifier,
-                            EAP_TYPE_AKA,
+                            getEapMethod(),
                             AtClientErrorCode.UNABLE_TO_PROCESS);
                 }
             } catch (GeneralSecurityException
@@ -425,6 +431,18 @@
                 return new EapError(ex);
             }
 
+            // before sending a response, check for bidding-down attacks (RFC 5448#4)
+            if (mSupportsEapAkaPrime) {
+                AtBidding atBidding = (AtBidding) eapAkaTypeData.attributeMap.get(EAP_AT_BIDDING);
+                if (atBidding != null && atBidding.doesServerSupportEapAkaPrime) {
+                    LOG.w(
+                            mTAG,
+                            "Potential bidding down attack. AT_BIDDING attr included and EAP-AKA'"
+                                + " is supported");
+                    return buildAuthenticationRejectMessage(message.eapIdentifier);
+                }
+            }
+
             // server has been authenticated, so we can send a response
             try {
                 mHadSuccessfulChallenge = true;
@@ -551,6 +569,29 @@
             return new RandChallengeResult(res, ik, ck);
         }
 
+        protected EapResult buildAuthenticationRejectMessage(int eapIdentifier) {
+            return buildResponseMessage(
+                    getEapMethod(),
+                    EAP_AKA_AUTHENTICATION_REJECT,
+                    eapIdentifier,
+                    new ArrayList<>());
+        }
+
+        @Nullable
+        protected EapResult generateAndPersistEapAkaKeys(
+                RandChallengeResult result, int eapIdentifier, EapAkaTypeData eapAkaTypeData) {
+            try {
+                MessageDigest sha1 = MessageDigest.getInstance(MASTER_KEY_GENERATION_ALG);
+                byte[] mkInputData = getMkInputData(result);
+                generateAndPersistKeys(mTAG, sha1, new Fips186_2Prf(), mkInputData);
+                return null;
+            } catch (NoSuchAlgorithmException | BufferUnderflowException ex) {
+                LOG.e(mTAG, "Error while creating keys", ex);
+                return buildClientErrorResponse(
+                        eapIdentifier, EAP_TYPE_AKA, AtClientErrorCode.UNABLE_TO_PROCESS);
+            }
+        }
+
         private byte[] getMkInputData(RandChallengeResult result) {
             int numInputBytes = mIdentity.length + result.ik.length + result.ck.length;
             ByteBuffer buffer = ByteBuffer.allocate(numInputBytes);
diff --git a/src/java/com/android/ike/eap/statemachine/EapAkaPrimeMethodStateMachine.java b/src/java/com/android/ike/eap/statemachine/EapAkaPrimeMethodStateMachine.java
index 38eafb3..9ba7dc2 100644
--- a/src/java/com/android/ike/eap/statemachine/EapAkaPrimeMethodStateMachine.java
+++ b/src/java/com/android/ike/eap/statemachine/EapAkaPrimeMethodStateMachine.java
@@ -16,25 +16,44 @@
 
 package com.android.ike.eap.statemachine;
 
+import static com.android.ike.eap.EapAuthenticator.LOG;
 import static com.android.ike.eap.message.EapData.EAP_TYPE_AKA_PRIME;
 import static com.android.ike.eap.message.simaka.EapAkaTypeData.EAP_AKA_CLIENT_ERROR;
+import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_AUTN;
+import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_KDF;
+import static com.android.ike.eap.message.simaka.EapSimAkaAttribute.EAP_AT_KDF_INPUT;
 
+import android.annotation.Nullable;
 import android.content.Context;
 
+import com.android.ike.crypto.KeyGenerationUtils;
 import com.android.ike.eap.EapResult;
 import com.android.ike.eap.EapSessionConfig.EapAkaPrimeConfig;
+import com.android.ike.eap.crypto.HmacSha256ByteSigner;
 import com.android.ike.eap.message.EapData.EapMethod;
 import com.android.ike.eap.message.EapMessage;
 import com.android.ike.eap.message.simaka.EapAkaPrimeTypeData;
 import com.android.ike.eap.message.simaka.EapAkaPrimeTypeData.EapAkaPrimeTypeDataDecoder;
 import com.android.ike.eap.message.simaka.EapAkaTypeData;
 import com.android.ike.eap.message.simaka.EapSimAkaAttribute;
+import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtAutn;
 import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode;
+import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtKdf;
+import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtKdfInput;
 import com.android.ike.eap.message.simaka.EapSimAkaTypeData.DecodeResult;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
 
 /**
  * EapAkaPrimeMethodStateMachine represents the valid paths possible for the EAP-AKA' protocol.
@@ -58,9 +77,24 @@
  *     Protocol Method for 3rd Generation Authentication and Key Agreement (EAP-AKA')</a>
  */
 public class EapAkaPrimeMethodStateMachine extends EapAkaMethodStateMachine {
+    public static final int K_AUT_LEN = 32;
+    public static final int K_RE_LEN = 32;
+
     // EAP-AKA' identity prefix (RFC 5448#3)
     private static final String AKA_PRIME_IDENTITY_PREFIX = "6";
+    private static final int SUPPORTED_KDF = 1;
+    private static final int FC = 0x20; // Required by TS 133 402 Annex A.2
+    private static final int SQN_XOR_AK_LEN = 6;
+    private static final String MAC_ALGORITHM_STRING = "HmacSHA256";
+    private static final String MK_DATA_PREFIX = "EAP-AKA'";
 
+    // MK_LEN_BYTES = len(K_encr | K_aut | K_re | MSK | EMSK)
+    private static final int MK_LEN_BYTES =
+            KEY_LEN + K_AUT_LEN + K_RE_LEN + (2 * SESSION_KEY_LENGTH);
+
+    public final byte[] mKRe = new byte[getKReLen()];
+
+    private final EapAkaPrimeConfig mEapAkaPrimeConfig;
     private final EapAkaPrimeTypeDataDecoder mEapAkaPrimeTypeDataDecoder;
 
     EapAkaPrimeMethodStateMachine(
@@ -79,6 +113,7 @@
             EapAkaPrimeConfig eapAkaPrimeConfig,
             EapAkaPrimeTypeDataDecoder eapAkaPrimeTypeDataDecoder) {
         super(context, eapIdentity, eapAkaPrimeConfig);
+        mEapAkaPrimeConfig = eapAkaPrimeConfig;
         mEapAkaPrimeTypeDataDecoder = eapAkaPrimeTypeDataDecoder;
 
         transitionTo(new CreatedState());
@@ -91,6 +126,15 @@
     }
 
     @Override
+    protected int getKAutLength() {
+        return K_AUT_LEN;
+    }
+
+    protected int getKReLen() {
+        return K_RE_LEN;
+    }
+
+    @Override
     protected DecodeResult<EapAkaTypeData> decode(byte[] typeData) {
         return mEapAkaPrimeTypeDataDecoder.decode(typeData);
     }
@@ -110,7 +154,14 @@
         return new ChallengeState(identity);
     }
 
+    @Override
+    protected String getMacAlgorithm() {
+        return MAC_ALGORITHM_STRING;
+    }
+
     protected class ChallengeState extends EapAkaMethodStateMachine.ChallengeState {
+        private final String mTAG = ChallengeState.class.getSimpleName();
+
         ChallengeState() {
             super();
         }
@@ -122,7 +173,156 @@
         @Override
         protected EapResult handleChallengeAuthentication(
                 EapMessage message, EapAkaTypeData eapAkaTypeData) {
-            return null;
+            EapAkaPrimeTypeData eapAkaPrimeTypeData = (EapAkaPrimeTypeData) eapAkaTypeData;
+
+            if (!isValidChallengeAttributes(eapAkaPrimeTypeData)) {
+                return buildAuthenticationRejectMessage(message.eapIdentifier);
+            }
+            return super.handleChallengeAuthentication(message, eapAkaPrimeTypeData);
+        }
+
+        @VisibleForTesting
+        boolean isValidChallengeAttributes(EapAkaPrimeTypeData eapAkaPrimeTypeData) {
+            Map<Integer, EapSimAkaAttribute> attrs = eapAkaPrimeTypeData.attributeMap;
+
+            if (!attrs.containsKey(EAP_AT_KDF) || !attrs.containsKey(EAP_AT_KDF_INPUT)) {
+                return false;
+            }
+
+            // TODO(b/143073851): implement KDF resolution specified in RFC 5448#3.2
+            // This is safe, as there only exists one defined KDF.
+            AtKdf atKdf = (AtKdf) attrs.get(EAP_AT_KDF);
+            if (atKdf.kdf != SUPPORTED_KDF) {
+                return false;
+            }
+
+            AtKdfInput atKdfInput = (AtKdfInput) attrs.get(EAP_AT_KDF_INPUT);
+            if (atKdfInput.networkName.length == 0) {
+                return false;
+            }
+
+            boolean hasMatchingNetworkNames =
+                    hasMatchingNetworkNames(
+                            mEapAkaPrimeConfig.networkName,
+                            new String(atKdfInput.networkName, StandardCharsets.UTF_8));
+            return mEapAkaPrimeConfig.allowMismatchedNetworkNames || hasMatchingNetworkNames;
+        }
+
+        /**
+         * Compares the peer's network name against the server's network name.
+         *
+         * <p>RFC 5448#3.1 describes how the network names are to be compared: "each name is broken
+         * down to the fields separated by colons. If one of the names has more colons and fields
+         * than the other one, the additional fields are ignored. The remaining sequences of fields
+         * are compared. This algorithm allows a prefix match".
+         *
+         * @return true iff one network name matches the other, as defined by RC 5448#3.1
+         */
+        @VisibleForTesting
+        boolean hasMatchingNetworkNames(String peerNetworkName, String serverNetworkName) {
+            // compare network names according to RFC 5448#3.1
+            if (peerNetworkName.isEmpty() || serverNetworkName.isEmpty()) {
+                return true;
+            }
+
+            String[] peerNetworkNameFields = peerNetworkName.split(":");
+            String[] serverNetworkNameFields = serverNetworkName.split(":");
+            int numFieldsToCompare =
+                    Math.min(peerNetworkNameFields.length, serverNetworkNameFields.length);
+            for (int i = 0; i < numFieldsToCompare; i++) {
+                if (!peerNetworkNameFields[i].equals(serverNetworkNameFields[i])) {
+                    LOG.i(
+                            mTAG,
+                            "EAP-AKA' network names don't match."
+                                    + " Peer: " + LOG.pii(peerNetworkName)
+                                    + ", Server: " + LOG.pii(serverNetworkName));
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        @Nullable
+        @Override
+        protected EapResult generateAndPersistEapAkaKeys(
+                RandChallengeResult result, int eapIdentifier, EapAkaTypeData eapAkaTypeData) {
+            try {
+                AtKdfInput atKdfInput =
+                        (AtKdfInput) eapAkaTypeData.attributeMap.get(EAP_AT_KDF_INPUT);
+                AtAutn atAutn = (AtAutn) eapAkaTypeData.attributeMap.get(EAP_AT_AUTN);
+                byte[] ckIkPrime = deriveCkIkPrime(result, atKdfInput, atAutn);
+
+                int dataToSignLen = MK_DATA_PREFIX.length() + mIdentity.length;
+                ByteBuffer dataToSign = ByteBuffer.allocate(dataToSignLen);
+                dataToSign.put(MK_DATA_PREFIX.getBytes(StandardCharsets.US_ASCII));
+                dataToSign.put(mIdentity);
+
+                ByteBuffer mk =
+                        ByteBuffer.wrap(
+                                KeyGenerationUtils.prfPlus(
+                                        HmacSha256ByteSigner.getInstance(),
+                                        ckIkPrime,
+                                        dataToSign.array(),
+                                        MK_LEN_BYTES));
+
+                mk.get(mKEncr);
+                mk.get(mKAut);
+                mk.get(mKRe);
+                mk.get(mMsk);
+                mk.get(mEmsk);
+
+                // Log as hash unless PII debug mode enabled
+                LOG.d(mTAG, "K_encr=" + LOG.pii(mKEncr));
+                LOG.d(mTAG, "K_aut=" + LOG.pii(mKAut));
+                LOG.d(mTAG, "K_re=" + LOG.pii(mKRe));
+                LOG.d(mTAG, "MSK=" + LOG.pii(mMsk));
+                LOG.d(mTAG, "EMSK=" + LOG.pii(mEmsk));
+                return null;
+            } catch (GeneralSecurityException
+                    | BufferOverflowException
+                    | BufferUnderflowException ex) {
+                LOG.e(mTAG, "Error while generating keys", ex);
+                return buildClientErrorResponse(
+                        eapIdentifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS);
+            }
+        }
+
+        /**
+         * Derives CK' and IK' values from CK and IK
+         *
+         * <p>CK' and IK' generation is specified in TS 133 402 Annex A.2, which relies on the key
+         * derivation function KDF specified in TS 133 220 Annex B.2.
+         */
+        @VisibleForTesting
+        byte[] deriveCkIkPrime(
+                RandChallengeResult randChallengeResult, AtKdfInput atKdfInput, AtAutn atAutn)
+                throws GeneralSecurityException {
+            final int fcLen = 1;
+            int lengthFieldLen = 2;
+
+            // SQN ^ AK is the first 6B of the AUTN value
+            byte[] sqnXorAk = Arrays.copyOf(atAutn.autn, SQN_XOR_AK_LEN);
+            int sLength =
+                    fcLen
+                            + atKdfInput.networkName.length + lengthFieldLen
+                            + SQN_XOR_AK_LEN + lengthFieldLen;
+
+            ByteBuffer dataToSign = ByteBuffer.allocate(sLength);
+            dataToSign.put((byte) FC);
+            dataToSign.put(atKdfInput.networkName);
+            dataToSign.putShort((short) atKdfInput.networkName.length);
+            dataToSign.put(sqnXorAk);
+            dataToSign.putShort((short) SQN_XOR_AK_LEN);
+
+            int keyLen = randChallengeResult.ck.length + randChallengeResult.ik.length;
+            ByteBuffer key = ByteBuffer.allocate(keyLen);
+            key.put(randChallengeResult.ck);
+            key.put(randChallengeResult.ik);
+
+            Mac mac = Mac.getInstance(MAC_ALGORITHM_STRING);
+            mac.init(new SecretKeySpec(key.array(), MAC_ALGORITHM_STRING));
+            return mac.doFinal(dataToSign.array());
         }
     }
 
diff --git a/src/java/com/android/ike/eap/statemachine/EapSimAkaMethodStateMachine.java b/src/java/com/android/ike/eap/statemachine/EapSimAkaMethodStateMachine.java
index e792033..7f1933a 100644
--- a/src/java/com/android/ike/eap/statemachine/EapSimAkaMethodStateMachine.java
+++ b/src/java/com/android/ike/eap/statemachine/EapSimAkaMethodStateMachine.java
@@ -73,10 +73,10 @@
     // Session Key lengths are 64 bytes (RFC 4186#7, RFC 4187#7)
     public static final int SESSION_KEY_LENGTH = 64;
 
-    public final byte[] mKEncr = new byte[KEY_LEN];
-    public final byte[] mKAut = new byte[KEY_LEN];
-    public final byte[] mMsk = new byte[SESSION_KEY_LENGTH];
-    public final byte[] mEmsk = new byte[SESSION_KEY_LENGTH];
+    public final byte[] mKEncr = new byte[getKEncrLength()];
+    public final byte[] mKAut = new byte[getKAutLength()];
+    public final byte[] mMsk = new byte[getMskLength()];
+    public final byte[] mEmsk = new byte[getEmskLength()];
 
     @VisibleForTesting boolean mHasReceivedSimAkaNotification = false;
 
@@ -106,11 +106,31 @@
                         + " apptype=" + mEapUiccConfig.apptype);
     }
 
+    protected int getKEncrLength() {
+        return KEY_LEN;
+    }
+
+    protected int getKAutLength() {
+        return KEY_LEN;
+    }
+
+    protected int getMskLength() {
+        return SESSION_KEY_LENGTH;
+    }
+
+    protected int getEmskLength() {
+        return SESSION_KEY_LENGTH;
+    }
+
     @Override
     EapResult handleEapNotification(String tag, EapMessage message) {
         return EapStateMachine.handleNotification(tag, message);
     }
 
+    protected String getMacAlgorithm() {
+        return MAC_ALGORITHM_STRING;
+    }
+
     @VisibleForTesting
     EapResult buildClientErrorResponse(
             int eapIdentifier,
@@ -146,11 +166,8 @@
     }
 
     @VisibleForTesting
-    void generateAndPersistKeys(
-            String tag,
-            MessageDigest sha1,
-            Fips186_2Prf prf,
-            byte[] mkInput) {
+    protected void generateAndPersistKeys(
+            String tag, MessageDigest sha1, Fips186_2Prf prf, byte[] mkInput) {
         byte[] mk = sha1.digest(mkInput);
 
         // run mk through FIPS 186-2
@@ -193,8 +210,8 @@
     boolean isValidMac(String tag, EapMessage message, EapSimAkaTypeData typeData, byte[] extraData)
             throws GeneralSecurityException, EapSimAkaInvalidAttributeException,
                     EapSilentException {
-        mMacAlgorithm = Mac.getInstance(MAC_ALGORITHM_STRING);
-        mMacAlgorithm.init(new SecretKeySpec(mKAut, MAC_ALGORITHM_STRING));
+        mMacAlgorithm = Mac.getInstance(getMacAlgorithm());
+        mMacAlgorithm.init(new SecretKeySpec(mKAut, getMacAlgorithm()));
 
         byte[] mac = getMac(message.eapCode, message.eapIdentifier, typeData, extraData);
         // attributes are 'valid', so must have AtMac
@@ -206,8 +223,8 @@
             LOG.e(
                     tag,
                     "Received message with invalid Mac."
-                            + " expected=" + Log.byteArrayToHexString(mac)
-                            + ", actual=" + Log.byteArrayToHexString(atMac.mac));
+                            + " received=" + Log.byteArrayToHexString(atMac.mac)
+                            + ", computed=" + Log.byteArrayToHexString(mac));
         }
 
         return isValidMac;
diff --git a/src/java/com/android/ike/eap/statemachine/EapStateMachine.java b/src/java/com/android/ike/eap/statemachine/EapStateMachine.java
index e1d793c..f2030a1 100644
--- a/src/java/com/android/ike/eap/statemachine/EapStateMachine.java
+++ b/src/java/com/android/ike/eap/statemachine/EapStateMachine.java
@@ -325,8 +325,13 @@
                             mContext, mEapSessionConfig.eapIdentity, eapSimConfig, mSecureRandom);
                 case EAP_TYPE_AKA:
                     EapAkaConfig eapAkaConfig = (EapAkaConfig) eapMethodConfig;
+                    boolean supportsEapAkaPrime =
+                            mEapSessionConfig.eapConfigs.containsKey(EAP_TYPE_AKA_PRIME);
                     return new EapAkaMethodStateMachine(
-                            mContext, mEapSessionConfig.eapIdentity, eapAkaConfig);
+                            mContext,
+                            mEapSessionConfig.eapIdentity,
+                            eapAkaConfig,
+                            supportsEapAkaPrime);
                 case EAP_TYPE_AKA_PRIME:
                     EapAkaPrimeConfig eapAkaPrimeConfig = (EapAkaPrimeConfig) eapMethodConfig;
                     return new EapAkaPrimeMethodStateMachine(
diff --git a/src/java/com/android/ike/ikev2/IkeSession.java b/src/java/com/android/ike/ikev2/IkeSession.java
index 19d9cb9..fbfe640 100644
--- a/src/java/com/android/ike/ikev2/IkeSession.java
+++ b/src/java/com/android/ike/ikev2/IkeSession.java
@@ -26,7 +26,24 @@
 
 import java.util.concurrent.Executor;
 
-/** This class represents an IKE Session management object. */
+/**
+ * This class represents an IKE Session management object that allows for keying and management of
+ * {@link IpSecTransform}s.
+ *
+ * <p>An IKE/Child Session represents an IKE/Child SA as well as its rekeyed successors. A Child
+ * Session is bounded by the lifecycle of the IKE Session under which it is set up. Closing an IKE
+ * Session implicitly closes any remaining Child Sessions under it.
+ *
+ * <p>An IKE procedure is one or multiple IKE message exchanges that are used to create, delete or
+ * rekey an IKE Session or Child Session.
+ *
+ * <p>This class provides methods for user to initiate IKE procedures, such as the Creation and
+ * Deletion of a Child Session, or the Deletion of the IKE session. All procedures (except for IKE
+ * deletion) will be initiated sequentially after IKE Session is set up.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc7296">RFC 7296, Internet Key Exchange Protocol
+ *     Version 2 (IKEv2)</a>
+ */
 public final class IkeSession implements AutoCloseable {
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
@@ -97,11 +114,14 @@
     // TODO: b/133340675 Destroy the worker thread when there is no more alive {@link IkeSession}.
 
     /**
-     * Initiate Create Child exchange on the IKE worker thread.
+     * Asynchronously request a new Child Session.
      *
      * <p>Users MUST provide a unique {@link ChildSessionCallback} instance for each new Child
      * Session.
      *
+     * <p>Upon setup, the {@link ChildSessionCallback#onOpened(ChildSessionConfiguration)} will be
+     * fired.
+     *
      * @param childSessionOptions the {@link ChildSessionOptions} that contains the Child Session
      *     configurations to negotiate.
      * @param childSessionCallback the {@link ChildSessionCallback} interface to notify users the
@@ -114,10 +134,12 @@
     }
 
     /**
-     * Initiate Delete Child exchange on the IKE worker thread.
+     * Asynchronously delete a Child Session.
      *
-     * @param childSessionCallback the callback of the Child Session to delete as well as the
-     *     interface to notify users the deletion result.
+     * <p>Upon closing, the {@link ChildSessionCallback#onClosed()} will be fired.
+     *
+     * @param childSessionCallback The {@link ChildSessionCallback} instance that uniquely identify
+     *     the Child Session.
      * @throws IllegalArgumentException if no Child Session found bound with this callback.
      */
     public void closeChildSession(ChildSessionCallback childSessionCallback) {
@@ -125,23 +147,21 @@
     }
 
     /**
-     * Initiate Delete IKE exchange on the IKE worker thread.
+     * Close the IKE session gracefully.
      *
-     * <p>Users must stop all outbound traffic that uses the Child Sessions that under this IKE
-     * Session before calling this method.
-     */
-    public void closeSafely() {
-        mCloseGuard.close();
-        mIkeSessionStateMachine.closeSession();
-    }
-
-    /**
-     * Notify the remote server and close the IKE Session.
+     * <p>Implements {@link AutoCloseable#close()}
      *
-     * <p>Implement {@link AutoCloseable#close()}
+     * <p>Upon closing, the {@link IkeSessionCallback#onClosed()} will be fired.
      *
-     * <p>Users must stop all outbound traffic that uses the Child Sessions that under this IKE
-     * Session before calling this method.
+     * <p>Closing an IKE Session implicitly closes any remaining Child Sessions negotiated under it.
+     * Users SHOULD stop all outbound traffic that uses these Child Sessions({@link IpSecTransform}
+     * pairs) before calling this method. Otherwise IPsec packets will be dropped due to the lack of
+     * a valid {@link IpSecTransform}.
+     *
+     * <p>Closure of an IKE session will take priority over, and cancel other procedures waiting in
+     * the queue (but will wait for ongoing locally initiated procedures to complete). After sending
+     * the Delete request, the IKE library will wait until a Delete response is received or
+     * retransmission timeout occurs.
      */
     @Override
     public void close() throws Exception {
@@ -149,6 +169,21 @@
         mIkeSessionStateMachine.closeSession();
     }
 
-    // TODO: Add methods to retrieve negotiable and non-negotiable configurations of IKE Session and
-    // its Child Sessions.
+    /**
+     * Terminate (forcibly close) the IKE session.
+     *
+     * <p>Upon closing, the {@link IkeSessionCallback#onClosed()} will be fired.
+     *
+     * <p>Closing an IKE Session implicitly closes any remaining Child Sessions negotiated under it.
+     * Users SHOULD stop all outbound traffic that uses these Child Sessions({@link IpSecTransform}
+     * pairs) before calling this method. Otherwise IPsec packets will be dropped due to the lack of
+     * a valid {@link IpSecTransform}.
+     *
+     * <p>Forcible closure of an IKE session will take priority over, and cancel other procedures
+     * waiting in the queue. It will also interrupt any ongoing locally initiated procedure.
+     */
+    public void kill() throws Exception {
+        mCloseGuard.close();
+        mIkeSessionStateMachine.killSession();
+    }
 }
diff --git a/src/java/com/android/ike/ikev2/IkeSessionOptions.java b/src/java/com/android/ike/ikev2/IkeSessionOptions.java
index 173c3ea..d722225 100644
--- a/src/java/com/android/ike/ikev2/IkeSessionOptions.java
+++ b/src/java/com/android/ike/ikev2/IkeSessionOptions.java
@@ -26,8 +26,10 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.net.InetAddress;
+import java.security.PrivateKey;
 import java.security.cert.TrustAnchor;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -125,7 +127,7 @@
     }
 
     /**
-     * Package private class that contains configuration to do IKEv2 pre-shared-key-based
+     * Package private class that contains configuration for IKEv2 pre-shared-key-based
      * authentication of local or remote side.
      */
     static class IkeAuthPskConfig extends IkeAuthConfig {
@@ -138,8 +140,8 @@
     }
 
     /**
-     * Package private class that contains configuration to do IKEv2 public-key-based authentication
-     * of the remote side.
+     * Package private class that contains configuration for IKEv2 public-key-signature-based
+     * authentication of the remote side.
      */
     static class IkeAuthDigitalSignRemoteConfig extends IkeAuthConfig {
         final TrustAnchor mTrustAnchor;
@@ -150,8 +152,25 @@
         }
     }
 
-    // TODO: Create IkeAuthDigitalSignLocalConfig to store signature hash algorithm and
-    // certificates to do authentication of local side to the remote.
+    /**
+     * Package private class that contains configuration to do IKEv2 public-key-signature-based
+     * authentication of the local side.
+     */
+    static class IkeAuthDigitalSignLocalConfig extends IkeAuthConfig {
+        final X509Certificate mEndCert;
+        final List<X509Certificate> mIntermediateCerts;
+        final PrivateKey mPrivateKey;
+
+        private IkeAuthDigitalSignLocalConfig(
+                X509Certificate clientEndCert,
+                List<X509Certificate> clientIntermediateCerts,
+                PrivateKey privateKey) {
+            super(IKE_AUTH_METHOD_PUB_KEY_SIGNATURE);
+            mEndCert = clientEndCert;
+            mIntermediateCerts = clientIntermediateCerts;
+            mPrivateKey = privateKey;
+        }
+    }
 
     /**
      * Package private class that contains configuration to do EAP authentication of the local side.
@@ -280,17 +299,13 @@
          * <p>Users MUST declare only one authentication method. Calling this function will override
          * the previously set authentication configuration.
          *
-         * <p>TODO: Add input to take EAP configucations.
-         *
-         * <p>TODO: Investigate if we need to support the name constraints extension.
-         *
          * @see <a href="https://tools.ietf.org/html/rfc5280">RFC 5280, Internet X.509 Public Key
          *     Infrastructure Certificate and Certificate Revocation List (CRL) Profile</a>
-         * @param caCert the CA certificate for validating the received server certificate(s).
+         * @param serverCaCert the CA certificate for validating the received server certificate(s).
          * @return Builder this, to facilitate chaining.
          */
         public Builder setAuthEap(
-                @NonNull X509Certificate caCert, @NonNull EapSessionConfig eapConfig) {
+                @NonNull X509Certificate serverCaCert, @NonNull EapSessionConfig eapConfig) {
             mLocalAuthConfig = new IkeAuthEapConfig(eapConfig);
 
             // The name constraints extension, defined in RFC 5280, indicates a name space within
@@ -298,11 +313,77 @@
             // located.
             mRemoteAuthConfig =
                     new IkeAuthDigitalSignRemoteConfig(
-                            new TrustAnchor(caCert, null /*nameConstraints*/));
+                            new TrustAnchor(serverCaCert, null /*nameConstraints*/));
+
+            // TODO: Investigate if we need to support the name constraints extension.
+
             return this;
         }
 
-        // TODO: Add methods to set authentication method to public key signature and EAP.
+        /**
+         * Uses certificate and digital signature to do IKE authentication.
+         *
+         * <p>The public key included by the client end certificate and the signature private key
+         * MUST come from the same key pair.
+         *
+         * <p>The IKE library will use the strongest signature algorithm supported by both sides.
+         *
+         * <p>Currenly only RSA digital signature is supported.
+         *
+         * @param serverCaCert the CA certificate for validating the received server certificate(s).
+         * @param clientEndCert the end certificate for remote server to verify the locally
+         *     generated signature.
+         * @param clientPrivateKey private key to generate outbound digital signature. Only {@link
+         *     RSAPrivateKey} is supported.
+         * @return Builder this, to facilitate chaining.
+         */
+        public Builder setAuthDigitalSignature(
+                @NonNull X509Certificate serverCaCert,
+                @NonNull X509Certificate clientEndCert,
+                @NonNull PrivateKey clientPrivateKey) {
+            return setAuthDigitalSignature(
+                    serverCaCert,
+                    clientEndCert,
+                    new LinkedList<X509Certificate>(),
+                    clientPrivateKey);
+        }
+
+        /**
+         * Uses certificate and digital signature to do IKE authentication.
+         *
+         * <p>The public key included by the client end certificate and the signature private key
+         * MUST come from the same key pair.
+         *
+         * <p>The IKE library will use the strongest signature algorithm supported by both sides.
+         *
+         * <p>Currenly only RSA digital signature is supported.
+         *
+         * @param serverCaCert the CA certificate for validating the received server certificate(s).
+         * @param clientEndCert the end certificate for remote server to verify locally generated
+         *     signature.
+         * @param clientIntermediateCerts intermediate certificates for the remote server to
+         *     validate the end certificate.
+         * @param clientPrivateKey private key to generate outbound digital signature. Only {@link
+         *     RSAPrivateKey} is supported.
+         * @return Builder this, to facilitate chaining.
+         */
+        public Builder setAuthDigitalSignature(
+                @NonNull X509Certificate serverCaCert,
+                @NonNull X509Certificate clientEndCert,
+                @NonNull List<X509Certificate> clientIntermediateCerts,
+                @NonNull PrivateKey clientPrivateKey) {
+            if (!(clientPrivateKey instanceof RSAPrivateKey)) {
+                throw new IllegalArgumentException("Unsupported private key type");
+            }
+
+            mLocalAuthConfig =
+                    new IkeAuthDigitalSignLocalConfig(
+                            clientEndCert, clientIntermediateCerts, clientPrivateKey);
+            mRemoteAuthConfig =
+                    new IkeAuthDigitalSignRemoteConfig(
+                            new TrustAnchor(serverCaCert, null /*nameConstraints*/));
+            return this;
+        }
 
         /**
          * Validates, builds and returns the IkeSessionOptions
diff --git a/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java b/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
index 7172dac..3e5d979 100644
--- a/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
+++ b/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
@@ -538,6 +538,10 @@
         sendMessage(CMD_LOCAL_REQUEST_DELETE_IKE, new LocalRequest(CMD_LOCAL_REQUEST_DELETE_IKE));
     }
 
+    void killSession() {
+        // TODO: b/142977160 Support closing IKE Sesison immediately.
+    }
+
     private void scheduleRekeySession(LocalRequest rekeyRequest) {
         // TODO: Make rekey timeout fuzzy
         sendMessageDelayed(CMD_LOCAL_REQUEST_REKEY_IKE, rekeyRequest, SA_SOFT_LIFETIME_MS);
diff --git a/src/java/com/android/ike/ikev2/TunnelModeChildSessionOptions.java b/src/java/com/android/ike/ikev2/TunnelModeChildSessionOptions.java
index f43a6ea..541930e 100644
--- a/src/java/com/android/ike/ikev2/TunnelModeChildSessionOptions.java
+++ b/src/java/com/android/ike/ikev2/TunnelModeChildSessionOptions.java
@@ -24,6 +24,7 @@
 
 import com.android.ike.ikev2.message.IkeConfigPayload.ConfigAttribute;
 import com.android.ike.ikev2.message.IkeConfigPayload.ConfigAttributeIpv4Address;
+import com.android.ike.ikev2.message.IkeConfigPayload.ConfigAttributeIpv4Dhcp;
 import com.android.ike.ikev2.message.IkeConfigPayload.ConfigAttributeIpv4Dns;
 import com.android.ike.ikev2.message.IkeConfigPayload.ConfigAttributeIpv4Netmask;
 import com.android.ike.ikev2.message.IkeConfigPayload.ConfigAttributeIpv4Subnet;
@@ -85,10 +86,10 @@
         }
 
         /**
-         * Adds internal address requests to TunnelModeChildSessionOptions being built.
+         * Adds internal IP address requests to TunnelModeChildSessionOptions being built.
          *
-         * @param addressFamily the address family. Only OsConstants.AF_INET and
-         *     OsConstants.AF_INET6 are allowed.
+         * @param addressFamily the address family. Only {@link OsConstants.AF_INET} and {@link
+         *     OsConstants.AF_INET6} are allowed.
          * @param numOfRequest the number of requests for this type of address.
          * @return Builder this, to facilitate chaining.
          */
@@ -110,7 +111,7 @@
         }
 
         /**
-         * Adds specific internal address request to TunnelModeChildSessionOptions being built.
+         * Adds specific internal IP address request to TunnelModeChildSessionOptions being built.
          *
          * @param address the requested address.
          * @param prefixLen length of the InetAddress prefix. When requesting an IPv4 address,
@@ -137,8 +138,8 @@
         /**
          * Adds internal DNS server requests to TunnelModeChildSessionOptions being built.
          *
-         * @param addressFamily the address family. Only OsConstants.AF_INET and
-         *     OsConstants.AF_INET6 are allowed.
+         * @param addressFamily the address family. Only {@link OsConstants.AF_INET} and {@link
+         *     OsConstants.AF_INET6} are allowed.
          * @param numOfRequest the number of requests for this type of address.
          * @return Builder this, to facilitate chaining.
          */
@@ -179,8 +180,8 @@
         /**
          * Adds internal subnet requests to TunnelModeChildSessionOptions being built.
          *
-         * @param addressFamily the address family. Only OsConstants.AF_INET and
-         *     OsConstants.AF_INET6 are allowed.
+         * @param addressFamily the address family. Only {@link OsConstants.AF_INET} and {@link
+         *     OsConstants.AF_INET6} are allowed.
          * @param numOfRequest the number of requests for this type of address.
          * @return Builder this, to facilitate chaining.
          */
@@ -201,6 +202,43 @@
         }
 
         /**
+         * Adds internal DHCP server requests to TunnelModeChildSessionOptions being built.
+         *
+         * <p>Only DHCP4 server requests are supported.
+         *
+         * @param addressFamily the address family. Only {@link OsConstants.AF_INET} is allowed.
+         * @param numOfRequest the number of requests for this type of address.
+         * @return Builder this, to facilitate chaining.
+         */
+        public Builder addInternalDhcpServerRequest(int addressFamily, int numOfRequest) {
+            if (addressFamily == AF_INET) {
+                for (int i = 0; i < numOfRequest; i++) {
+                    mConfigRequestList.add(new ConfigAttributeIpv4Dhcp());
+                }
+                return this;
+            } else {
+                throw new IllegalArgumentException("Invalid address family: " + addressFamily);
+            }
+        }
+
+        /**
+         * Adds internal DHCP server requests to TunnelModeChildSessionOptions being built.
+         *
+         * <p>Only DHCP4 server requests are supported.
+         *
+         * @param address the requested DHCP server address.
+         * @return Builder this, to facilitate chaining.
+         */
+        public Builder addInternalDhcpServerRequest(@NonNull InetAddress address) {
+            if (address instanceof Inet4Address) {
+                mConfigRequestList.add(new ConfigAttributeIpv4Dhcp((Inet4Address) address));
+                return this;
+            } else {
+                throw new IllegalArgumentException("Invalid address " + address);
+            }
+        }
+
+        /**
          * Validates, builds and returns the TunnelModeChildSessionOptions.
          *
          * @return the validated TunnelModeChildSessionOptions.
@@ -220,6 +258,4 @@
                     mConfigRequestList.toArray(new ConfigAttribute[mConfigRequestList.size()]));
         }
     }
-
-    // TODO: b/140644654 Add API for configuration requests.
 }
diff --git a/src/java/com/android/ike/ikev2/crypto/IkeMac.java b/src/java/com/android/ike/ikev2/crypto/IkeMac.java
index 4f23345..028f746 100644
--- a/src/java/com/android/ike/ikev2/crypto/IkeMac.java
+++ b/src/java/com/android/ike/ikev2/crypto/IkeMac.java
@@ -16,6 +16,8 @@
 
 package com.android.ike.ikev2.crypto;
 
+import com.android.ike.crypto.KeyGenerationUtils.ByteSigner;
+
 import java.nio.ByteBuffer;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
@@ -30,7 +32,7 @@
  * IkeMac is an abstract class that represents common information for all negotiated algorithms that
  * generates Message Authentication Code (MAC), e.g. PRF and integrity algorithm.
  */
-abstract class IkeMac extends IkeCrypto {
+abstract class IkeMac extends IkeCrypto implements ByteSigner {
     // STOPSHIP: b/130190639 Catch unchecked exceptions, notify users and close the IKE session.
     private final boolean mIsEncryptAlgo;
     private final Mac mMac;
@@ -69,6 +71,7 @@
      * @param dataToSign the data to be signed.
      * @return the calculated MAC.
      */
+    @Override
     public byte[] signBytes(byte[] keyBytes, byte[] dataToSign) {
         try {
             SecretKeySpec secretKey = new SecretKeySpec(keyBytes, getAlgorithmName());
diff --git a/src/java/com/android/ike/ikev2/crypto/IkeMacPrf.java b/src/java/com/android/ike/ikev2/crypto/IkeMacPrf.java
index 34ef3bf..b25ba34 100644
--- a/src/java/com/android/ike/ikev2/crypto/IkeMacPrf.java
+++ b/src/java/com/android/ike/ikev2/crypto/IkeMacPrf.java
@@ -16,6 +16,7 @@
 
 package com.android.ike.ikev2.crypto;
 
+import com.android.ike.crypto.KeyGenerationUtils;
 import com.android.ike.ikev2.SaProposal;
 import com.android.ike.ikev2.message.IkeSaPayload.PrfTransform;
 
@@ -135,26 +136,7 @@
      * @return the byte array of keying materials
      */
     public byte[] generateKeyMat(byte[] keyBytes, byte[] dataToSign, int keyMaterialLen) {
-        ByteBuffer keyMatBuffer = ByteBuffer.allocate(keyMaterialLen);
-
-        byte[] previousMac = new byte[0];
-        final int padLen = 1;
-        byte padValue = 1;
-
-        while (keyMatBuffer.remaining() > 0) {
-            ByteBuffer dataToSignBuffer =
-                    ByteBuffer.allocate(previousMac.length + dataToSign.length + padLen);
-            dataToSignBuffer.put(previousMac).put(dataToSign).put(padValue);
-
-            previousMac = signBytes(keyBytes, dataToSignBuffer.array());
-
-            keyMatBuffer.put(
-                    previousMac, 0, Math.min(previousMac.length, keyMatBuffer.remaining()));
-
-            padValue++;
-        }
-
-        return keyMatBuffer.array();
+        return KeyGenerationUtils.prfPlus(this, keyBytes, dataToSign, keyMaterialLen);
     }
 
     /**
diff --git a/tests/iketests/src/java/com/android/ike/eap/crypto/HmacSha256ByteSignerTest.java b/tests/iketests/src/java/com/android/ike/eap/crypto/HmacSha256ByteSignerTest.java
new file mode 100644
index 0000000..d178a45
--- /dev/null
+++ b/tests/iketests/src/java/com/android/ike/eap/crypto/HmacSha256ByteSignerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 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.android.ike.eap.crypto;
+
+import static com.android.ike.TestUtils.hexStringToByteArray;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * HmacSha256ByteSignerTest tests that {@link HmacSha256ByteSigner} correctly signs data using the
+ * HMAC-SHA-256 algorithm.
+ *
+ * <p>These test vectors are defined in RFC 4231#4.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4231#section-4">Test Vectors</a>
+ */
+public class HmacSha256ByteSignerTest {
+    private static final String[] KEYS = {
+        "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+        "4a656665",
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+        "0102030405060708090a0b0c0d0e0f10111213141516171819",
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+                + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+                + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+                + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+                + "aaaaaa",
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+                + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+                + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+                + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+                + "aaaaaa"
+    };
+    private static final String[] DATA = {
+        "4869205468657265",
+        "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
+        "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
+                + "dddddddddddddddddddddddddddddddddddd",
+        "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"
+                + "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
+        "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a"
+                + "65204b6579202d2048617368204b6579204669727374",
+        "5468697320697320612074657374207573696e672061206c6172676572207468"
+                + "616e20626c6f636b2d73697a65206b657920616e642061206c61726765722074"
+                + "68616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565"
+                + "647320746f20626520686173686564206265666f7265206265696e6720757365"
+                + "642062792074686520484d414320616c676f726974686d2e"
+    };
+    private static final String[] MACS = {
+        "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
+        "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
+        "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe",
+        "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b",
+        "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54",
+        "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"
+    };
+
+    private HmacSha256ByteSigner mMacByteSigner;
+
+    @Before
+    public void setUp() {
+        mMacByteSigner = HmacSha256ByteSigner.getInstance();
+    }
+
+    @Test
+    public void testSignBytes() {
+        for (int i = 0; i < KEYS.length; i++) {
+            byte[] key = hexStringToByteArray(KEYS[i]);
+            byte[] data = hexStringToByteArray(DATA[i]);
+
+            byte[] expected = hexStringToByteArray(MACS[i]);
+
+            assertArrayEquals(expected, mMacByteSigner.signBytes(key, data));
+        }
+    }
+}
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 3ddbe54..30ca917 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
@@ -318,6 +318,10 @@
     public static final byte[] EAP_AKA_PRIME_IDENTITY_RESPONSE =
             hexStringToByteArray(
                     "02" + ID + "001C" // EAP-Response | ID | length in bytes
-                            + "32050000" // EAP-AKA | Identity | 2B padding
+                            + "32050000" // EAP-AKA' | Identity | 2B padding
                             + "0E050010" + EAP_AKA_PRIME_IDENTITY); // AT_IDENTITY ("6" + IMSI)
+    public static final byte[] EAP_AKA_PRIME_AUTHENTICATION_REJECT =
+            hexStringToByteArray(
+                    "02" + ID + "0008" // EAP-Response | ID | length in bytes
+                            + "32020000"); // EAP-AKA' | Authentication Reject | 2B padding
 }
diff --git a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaChallengeStateTest.java b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaChallengeStateTest.java
index 4deecf3..feb0243 100644
--- a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaChallengeStateTest.java
+++ b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaChallengeStateTest.java
@@ -64,6 +64,7 @@
 import com.android.ike.eap.message.EapMessage;
 import com.android.ike.eap.message.simaka.EapAkaTypeData;
 import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtAutn;
+import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtBidding;
 import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtMac;
 import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtRandAka;
 import com.android.ike.eap.message.simaka.EapSimAkaTypeData.DecodeResult;
@@ -92,13 +93,24 @@
      *              020500000123456789ABCDEFFEDCBA9876543210 | AT_AUTN
      *              0B05000000000000000000000000000000000000 | AT_MAC (zeroed out)
      *
-     * MK = SHA-1(message)
+     * MK = SHA-1(Identity | IK | CK)
      * K_encr, K_aut, MSK, EMSK = PRF(MK)
      * MAC = HMAC-SHA-1(K_aut, message)
      */
     private static final byte[] REQUEST_MAC_BYTES =
             hexStringToByteArray("3EB97A1D0E62894FD0DA384D24D8983C");
 
+    /**
+     * message =    01100048 | EAP-Request, ID, length in bytes
+     *              17010000 | EAP-AKA, AKA-Challenge, padding
+     *              0105000000112233445566778899AABBCCDDEEFF | AT_RAND
+     *              020500000123456789ABCDEFFEDCBA9876543210 | AT_AUTN
+     *              88018000 | AT_BIDDING
+     *              0B05000000000000000000000000000000000000 | AT_MAC (zeroed out)
+     */
+    private static final byte[] BIDDING_DOWN_MAC =
+            hexStringToByteArray("9CB543894A5EFDC32DF6A6CE1AB0E01A");
+
     @Before
     public void setUp() {
         super.setUp();
@@ -352,8 +364,6 @@
 
     @Test
     public void testProcessValidChallenge() throws Exception {
-        // TODO(b/140258387): update test vectors with externally generated values
-
         EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA);
         EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData);
 
@@ -383,4 +393,38 @@
                         BASE_64_CHALLENGE);
         verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager);
     }
+
+    @Test
+    public void testProcessBiddingDownAttack() throws Exception {
+        EapData eapData = new EapData(EAP_TYPE_AKA, DUMMY_EAP_TYPE_DATA);
+        EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData);
+
+        AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES);
+        AtAutn atAutn = new AtAutn(AUTN_BYTES);
+        AtBidding atBidding = new AtBidding(true);
+        AtMac atMac = new AtMac(BIDDING_DOWN_MAC);
+
+        DecodeResult<EapAkaTypeData> decodeResult =
+                new DecodeResult<>(
+                        new EapAkaTypeData(
+                                EAP_AKA_CHALLENGE,
+                                Arrays.asList(atRandAka, atAutn, atBidding, atMac)));
+        when(mMockEapAkaTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult);
+        when(mMockTelephonyManager.getIccAuthentication(
+                TelephonyManager.APPTYPE_USIM,
+                TelephonyManager.AUTHTYPE_EAP_AKA,
+                BASE_64_CHALLENGE))
+                .thenReturn(EAP_AKA_UICC_RESP_SUCCESS_BASE_64);
+
+        EapResponse eapResponse = (EapResponse) mEapAkaMethodStateMachine.process(eapMessage);
+        assertArrayEquals(EAP_AKA_AUTHENTICATION_REJECT, eapResponse.packet);
+
+        verify(mMockEapAkaTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA));
+        verify(mMockTelephonyManager)
+                .getIccAuthentication(
+                        TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_AKA,
+                        BASE_64_CHALLENGE);
+        verifyNoMoreInteractions(mMockEapAkaTypeDataDecoder, mMockTelephonyManager);
+    }
 }
diff --git a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaMethodStateMachineTest.java b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaMethodStateMachineTest.java
index 23c2649..12e33c8 100644
--- a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaMethodStateMachineTest.java
+++ b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaMethodStateMachineTest.java
@@ -87,7 +87,8 @@
                         mMockTelephonyManager,
                         EAP_IDENTITY_BYTES,
                         mEapAkaConfig,
-                        mMockEapAkaTypeDataDecoder);
+                        mMockEapAkaTypeDataDecoder,
+                        false);
 
         verify(mMockTelephonyManager).createForSubscriptionId(SUB_ID);
     }
diff --git a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaPrimeChallengeStateTest.java b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaPrimeChallengeStateTest.java
index 8eac86b..a768376 100644
--- a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaPrimeChallengeStateTest.java
+++ b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaPrimeChallengeStateTest.java
@@ -16,42 +16,94 @@
 
 package com.android.ike.eap.statemachine;
 
+import static android.telephony.TelephonyManager.APPTYPE_USIM;
+
+import static com.android.ike.TestUtils.hexStringToByteArray;
 import static com.android.ike.eap.message.EapData.EAP_TYPE_AKA_PRIME;
 import static com.android.ike.eap.message.EapMessage.EAP_CODE_REQUEST;
+import static com.android.ike.eap.message.EapTestMessageDefinitions.CK_BYTES;
+import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_AKA_PRIME_AUTHENTICATION_REJECT;
 import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_AKA_PRIME_IDENTITY_BYTES;
 import static com.android.ike.eap.message.EapTestMessageDefinitions.EAP_AKA_PRIME_IDENTITY_RESPONSE;
 import static com.android.ike.eap.message.EapTestMessageDefinitions.ID_INT;
+import static com.android.ike.eap.message.EapTestMessageDefinitions.IK_BYTES;
 import static com.android.ike.eap.message.EapTestMessageDefinitions.IMSI;
 import static com.android.ike.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE;
 import static com.android.ike.eap.message.simaka.EapAkaTypeData.EAP_AKA_IDENTITY;
+import static com.android.ike.eap.message.simaka.attributes.EapTestAttributeDefinitions.AUTN_BYTES;
+import static com.android.ike.eap.message.simaka.attributes.EapTestAttributeDefinitions.MAC_BYTES;
+import static com.android.ike.eap.message.simaka.attributes.EapTestAttributeDefinitions.RAND_1_BYTES;
+import static com.android.ike.eap.message.simaka.attributes.EapTestAttributeDefinitions.RES_BYTES;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import com.android.ike.eap.EapResult.EapResponse;
+import com.android.ike.eap.EapSessionConfig;
 import com.android.ike.eap.message.EapData;
 import com.android.ike.eap.message.EapMessage;
 import com.android.ike.eap.message.simaka.EapAkaPrimeTypeData;
 import com.android.ike.eap.message.simaka.EapAkaTypeData;
 import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtAnyIdReq;
+import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtAutn;
+import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtKdf;
+import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtKdfInput;
+import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtMac;
+import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtRandAka;
 import com.android.ike.eap.message.simaka.EapSimAkaTypeData.DecodeResult;
+import com.android.ike.eap.statemachine.EapAkaMethodStateMachine.ChallengeState.RandChallengeResult;
 import com.android.ike.eap.statemachine.EapAkaPrimeMethodStateMachine.ChallengeState;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 
 public class EapAkaPrimeChallengeStateTest extends EapAkaPrimeStateTest {
+    private static final String SERVER_NETWORK_NAME_STRING = "foo:bar:buzz";
+    private static final byte[] SERVER_NETWORK_NAME =
+            SERVER_NETWORK_NAME_STRING.getBytes(StandardCharsets.UTF_8);
+    private static final String INCORRECT_NETWORK_NAME = "foo:buzz";
+    private static final byte[] INCORRECT_SERVER_NETWORK_NAME =
+            INCORRECT_NETWORK_NAME.getBytes(StandardCharsets.UTF_8);
+    private static final int VALID_KDF = 1;
+    private static final int INVALID_KDF = 10;
+
+    private static final byte[] EXPECTED_CK_IK_PRIME =
+            hexStringToByteArray(
+                    "A0B37E7C7E9CC4F37A5C0AAA55DC87BE51FDA70A9D8F37E62E23B15F1B3941E6");
+    private static final byte[] K_ENCR = hexStringToByteArray("15a5bb098528210cde9e8d4a1bd63850");
+    private static final byte[] K_AUT =
+            hexStringToByteArray(
+                    "957b3d518ac9ff028f2cc5177fedad841f5f812cb06e2b88aceaa98129680f35");
+    private static final byte[] K_RE =
+            hexStringToByteArray(
+                    "3c15cf7112935a8170d0904622ecbb67c49dcba5d50814bdd81958e045e42f9c");
+    private static final byte[] MSK =
+            hexStringToByteArray(
+                    "1dcca0351a58d2b858e6cf2380551470d67cc8749d1915409793171abd360118"
+                            + "e3ae271bf088ca5a41bb1b9b8f7028bcba888298bfbf64d7b8a4f53a6c2cdf18");
+    private static final byte[] EMSK =
+            hexStringToByteArray(
+                    "a5e6b66a9cb2daa9fe3867d41145848e7bf50d749bfd1bb0d090257402e6a555"
+                            + "da6d538e76b71e9f80afe60709965a63a355bdccc4e3a8b358e098e41545fa67");
+
+    private ChallengeState mState;
+
     @Before
     public void setUp() {
         super.setUp();
 
-        mStateMachine.transitionTo(mStateMachine.new ChallengeState());
+        mState = mStateMachine.new ChallengeState();
+        mStateMachine.transitionTo(mState);
     }
 
     @Test
@@ -108,4 +160,194 @@
         // decode() called again in IdentityState and ChallengeState
         verify(mMockTypeDataDecoder, times(4)).decode(eq(DUMMY_EAP_TYPE_DATA));
     }
+
+    @Test
+    public void testProcessMissingAtKdf() throws Exception {
+        EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA);
+        EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData);
+
+        AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES);
+        AtAutn atAutn = new AtAutn(AUTN_BYTES);
+        AtMac atMac = new AtMac(MAC_BYTES);
+        AtKdfInput atKdfInput = new AtKdfInput(0, SERVER_NETWORK_NAME);
+
+        DecodeResult<EapAkaTypeData> decodeResult =
+                new DecodeResult<>(
+                        new EapAkaPrimeTypeData(
+                                EAP_AKA_CHALLENGE,
+                                Arrays.asList(atRandAka, atAutn, atMac, atKdfInput)));
+        when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult);
+
+        EapResponse eapResponse = (EapResponse) mStateMachine.process(eapMessage);
+        assertArrayEquals(EAP_AKA_PRIME_AUTHENTICATION_REJECT, eapResponse.packet);
+        verify(mMockTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA));
+    }
+
+    @Test
+    public void testProcessMissingAtKdfInput() throws Exception {
+        EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA);
+        EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData);
+
+        AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES);
+        AtAutn atAutn = new AtAutn(AUTN_BYTES);
+        AtMac atMac = new AtMac(MAC_BYTES);
+        AtKdf atKdf = new AtKdf(VALID_KDF);
+
+        DecodeResult<EapAkaTypeData> decodeResult =
+                new DecodeResult<>(
+                        new EapAkaPrimeTypeData(
+                                EAP_AKA_CHALLENGE, Arrays.asList(atRandAka, atAutn, atMac, atKdf)));
+        when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult);
+
+        EapResponse eapResponse = (EapResponse) mStateMachine.process(eapMessage);
+        assertArrayEquals(EAP_AKA_PRIME_AUTHENTICATION_REJECT, eapResponse.packet);
+        verify(mMockTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA));
+    }
+
+    @Test
+    public void testProcessUnsupportedKdf() throws Exception {
+        EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA);
+        EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData);
+
+        AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES);
+        AtAutn atAutn = new AtAutn(AUTN_BYTES);
+        AtMac atMac = new AtMac(MAC_BYTES);
+        AtKdfInput atKdfInput = new AtKdfInput(0, SERVER_NETWORK_NAME);
+        AtKdf atKdf = new AtKdf(INVALID_KDF);
+
+        DecodeResult<EapAkaTypeData> decodeResult =
+                new DecodeResult<>(
+                        new EapAkaPrimeTypeData(
+                                EAP_AKA_CHALLENGE,
+                                Arrays.asList(atRandAka, atAutn, atMac, atKdfInput, atKdf)));
+        when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult);
+
+        EapResponse eapResponse = (EapResponse) mStateMachine.process(eapMessage);
+        assertArrayEquals(EAP_AKA_PRIME_AUTHENTICATION_REJECT, eapResponse.packet);
+        verify(mMockTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA));
+    }
+
+    @Test
+    public void testProcessIncorrectNetworkName() throws Exception {
+        EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, DUMMY_EAP_TYPE_DATA);
+        EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData);
+
+        AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES);
+        AtAutn atAutn = new AtAutn(AUTN_BYTES);
+        AtMac atMac = new AtMac(MAC_BYTES);
+        AtKdfInput atKdfInput = new AtKdfInput(0, INCORRECT_SERVER_NETWORK_NAME);
+        AtKdf atKdf = new AtKdf(VALID_KDF);
+
+        DecodeResult<EapAkaTypeData> decodeResult =
+                new DecodeResult<>(
+                        new EapAkaPrimeTypeData(
+                                EAP_AKA_CHALLENGE,
+                                Arrays.asList(atRandAka, atAutn, atMac, atKdfInput, atKdf)));
+        when(mMockTypeDataDecoder.decode(eq(DUMMY_EAP_TYPE_DATA))).thenReturn(decodeResult);
+
+        EapResponse eapResponse = (EapResponse) mStateMachine.process(eapMessage);
+        assertArrayEquals(EAP_AKA_PRIME_AUTHENTICATION_REJECT, eapResponse.packet);
+        verify(mMockTypeDataDecoder).decode(eq(DUMMY_EAP_TYPE_DATA));
+    }
+
+    @Test
+    public void testProcessIncorrectNetworkNameIsIgnored() throws Exception {
+        // Create state machine with configs allowing invalid network name to be ignored
+        mStateMachine =
+                new EapAkaPrimeMethodStateMachine(
+                        mMockContext,
+                        EAP_IDENTITY_BYTES,
+                        new EapSessionConfig.EapAkaPrimeConfig(
+                                SUB_ID, APPTYPE_USIM, PEER_NETWORK_NAME, true),
+                        mMockTypeDataDecoder);
+        mState = mStateMachine.new ChallengeState();
+        mStateMachine.transitionTo(mState);
+
+        AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES);
+        AtAutn atAutn = new AtAutn(AUTN_BYTES);
+        AtMac atMac = new AtMac(MAC_BYTES);
+        AtKdfInput atKdfInput = new AtKdfInput(0, INCORRECT_SERVER_NETWORK_NAME);
+        AtKdf atKdf = new AtKdf(VALID_KDF);
+
+        EapAkaPrimeTypeData eapAkaPrimeTypeData =
+                new EapAkaPrimeTypeData(
+                        EAP_AKA_CHALLENGE,
+                        Arrays.asList(atRandAka, atAutn, atMac, atKdfInput, atKdf));
+        assertTrue(
+                "Incorrect network names should be ignored",
+                mState.isValidChallengeAttributes(eapAkaPrimeTypeData));
+    }
+
+    @Test
+    public void testHasMatchingNetworkNames() {
+        // "" should match anything
+        assertTrue(mState.hasMatchingNetworkNames("", SERVER_NETWORK_NAME_STRING));
+        assertTrue(mState.hasMatchingNetworkNames(SERVER_NETWORK_NAME_STRING, ""));
+
+        // "foo:bar" should match "foo:bar:buzz"
+        assertTrue(mState.hasMatchingNetworkNames(PEER_NETWORK_NAME, SERVER_NETWORK_NAME_STRING));
+        assertTrue(mState.hasMatchingNetworkNames(SERVER_NETWORK_NAME_STRING, PEER_NETWORK_NAME));
+
+        // "foo:buzz" shouldn't match "foo:bar:buzz"
+        assertFalse(
+                mState.hasMatchingNetworkNames(SERVER_NETWORK_NAME_STRING, INCORRECT_NETWORK_NAME));
+        assertFalse(
+                mState.hasMatchingNetworkNames(INCORRECT_NETWORK_NAME, SERVER_NETWORK_NAME_STRING));
+    }
+
+    @Test
+    public void testDeriveCkIkPrime() throws Exception {
+        RandChallengeResult randChallengeResult =
+                mState.new RandChallengeResult(RES_BYTES, IK_BYTES, CK_BYTES);
+        AtKdfInput atKdfInput =
+                new AtKdfInput(0, PEER_NETWORK_NAME.getBytes(StandardCharsets.UTF_8));
+        AtAutn atAutn = new AtAutn(AUTN_BYTES);
+
+        // S = FC | Network Name | len(Network Name) | SQN ^ AK | len(SQN ^ AK)
+        //   = 20666F6F3A62617200070123456789AB0006
+        // K = CK | IK
+        //   = FFEEDDCCBBAA9988776655443322110000112233445566778899AABBCCDDEEFF
+        // CK' | IK' = HMAC-SHA256(K, S)
+        //           = A0B37E7C7E9CC4F37A5C0AAA55DC87BE51FDA70A9D8F37E62E23B15F1B3941E6
+        byte[] result = mState.deriveCkIkPrime(randChallengeResult, atKdfInput, atAutn);
+        assertArrayEquals(EXPECTED_CK_IK_PRIME, result);
+    }
+
+    @Test
+    public void testGenerateAndPersistEapAkaKeys() throws Exception {
+        RandChallengeResult randChallengeResult =
+                mState.new RandChallengeResult(RES_BYTES, IK_BYTES, CK_BYTES);
+
+        AtRandAka atRandAka = new AtRandAka(RAND_1_BYTES);
+        AtAutn atAutn = new AtAutn(AUTN_BYTES);
+        AtMac atMac = new AtMac(MAC_BYTES);
+        AtKdfInput atKdfInput =
+                new AtKdfInput(0, PEER_NETWORK_NAME.getBytes(StandardCharsets.UTF_8));
+        AtKdf atKdf = new AtKdf(VALID_KDF);
+
+        EapAkaPrimeTypeData eapAkaPrimeTypeData =
+                new EapAkaPrimeTypeData(
+                        EAP_AKA_CHALLENGE,
+                        Arrays.asList(atRandAka, atAutn, atMac, atKdfInput, atKdf));
+
+        // CK' | IK' = A0B37E7C7E9CC4F37A5C0AAA55DC87BE51FDA70A9D8F37E62E23B15F1B3941E6
+        // data = "EAP-AKA'" | Identity
+        //      = 4541502D414B41277465737440616E64726F69642E6E6574
+        // prf+(CK' | IK', data) = T1 | T2 | T3 | T4 | T5 | T6 | T7
+        // T1 = 15a5bb098528210cde9e8d4a1bd63850957b3d518ac9ff028f2cc5177fedad84
+        // T2 = 1f5f812cb06e2b88aceaa98129680f353c15cf7112935a8170d0904622ecbb67
+        // T3 = c49dcba5d50814bdd81958e045e42f9c1dcca0351a58d2b858e6cf2380551470
+        // T4 = d67cc8749d1915409793171abd360118e3ae271bf088ca5a41bb1b9b8f7028bc
+        // T5 = ba888298bfbf64d7b8a4f53a6c2cdf18a5e6b66a9cb2daa9fe3867d41145848e
+        // T6 = 7bf50d749bfd1bb0d090257402e6a555da6d538e76b71e9f80afe60709965a63
+        // T7 = a355bdccc4e3a8b358e098e41545fa677897d8341c4a107a2343f393ec966181
+        // K_encr | K_aut | K_re | MSK | EMSK = prf+(CK' | IK', data)
+        assertNull(
+                mState.generateAndPersistEapAkaKeys(randChallengeResult, 0, eapAkaPrimeTypeData));
+        assertArrayEquals(K_ENCR, mStateMachine.mKEncr);
+        assertArrayEquals(K_AUT, mStateMachine.mKAut);
+        assertArrayEquals(K_RE, mStateMachine.mKRe);
+        assertArrayEquals(MSK, mStateMachine.mMsk);
+        assertArrayEquals(EMSK, mStateMachine.mEmsk);
+    }
 }
diff --git a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaPrimeMethodStateMachineTest.java b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaPrimeMethodStateMachineTest.java
index c96f3fe..ce46dc4 100644
--- a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaPrimeMethodStateMachineTest.java
+++ b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaPrimeMethodStateMachineTest.java
@@ -16,15 +16,59 @@
 
 package com.android.ike.eap.statemachine;
 
+import static com.android.ike.TestUtils.hexStringToByteArray;
+import static com.android.ike.eap.message.EapData.EAP_TYPE_AKA_PRIME;
+import static com.android.ike.eap.message.EapMessage.EAP_CODE_REQUEST;
+import static com.android.ike.eap.message.EapTestMessageDefinitions.ID_INT;
+import static com.android.ike.eap.message.simaka.EapAkaTypeData.EAP_AKA_CHALLENGE;
+import static com.android.ike.eap.statemachine.EapAkaPrimeMethodStateMachine.K_AUT_LEN;
+import static com.android.ike.eap.statemachine.EapAkaPrimeMethodStateMachine.K_RE_LEN;
+import static com.android.ike.eap.statemachine.EapSimAkaMethodStateMachine.KEY_LEN;
+import static com.android.ike.eap.statemachine.EapSimAkaMethodStateMachine.SESSION_KEY_LENGTH;
+
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.ike.eap.message.EapData;
+import com.android.ike.eap.message.EapMessage;
+import com.android.ike.eap.message.simaka.EapAkaPrimeTypeData;
+import com.android.ike.eap.message.simaka.EapSimAkaAttribute.AtMac;
 import com.android.ike.eap.statemachine.EapAkaMethodStateMachine.CreatedState;
 
 import org.junit.Test;
 
+import java.util.Arrays;
+
 public class EapAkaPrimeMethodStateMachineTest extends EapAkaPrimeTest {
+    private static final String TAG = EapAkaPrimeMethodStateMachineTest.class.getSimpleName();
+    private static final byte[] K_AUT =
+            hexStringToByteArray(
+                    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F");
+    private static final byte[] MAC = hexStringToByteArray("0322b08b59cae2df8f766162ac76f30b");
+
     @Test
     public void testEapAkaPrimeMethodStateMachineStartState() {
         assertTrue(mStateMachine.getState() instanceof CreatedState);
     }
+
+    @Test
+    public void testKeyLengths() {
+        assertEquals(KEY_LEN, mStateMachine.getKEncrLength());
+        assertEquals(K_AUT_LEN, mStateMachine.getKAutLength());
+        assertEquals(K_RE_LEN, mStateMachine.getKReLen());
+        assertEquals(SESSION_KEY_LENGTH, mStateMachine.getMskLength());
+        assertEquals(SESSION_KEY_LENGTH, mStateMachine.getEmskLength());
+    }
+
+    @Test
+    public void testIsValidMacUsesHmacSha256() throws Exception {
+        System.arraycopy(K_AUT, 0, mStateMachine.mKAut, 0, K_AUT.length);
+
+        EapData eapData = new EapData(EAP_TYPE_AKA_PRIME, new byte[0]);
+        EapMessage eapMessage = new EapMessage(EAP_CODE_REQUEST, ID_INT, eapData);
+        EapAkaPrimeTypeData eapAkaPrimeTypeData =
+                new EapAkaPrimeTypeData(EAP_AKA_CHALLENGE, Arrays.asList(new AtMac(MAC)));
+
+        assertTrue(mStateMachine.isValidMac(TAG, eapMessage, eapAkaPrimeTypeData, new byte[0]));
+    }
 }
diff --git a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaStateTest.java b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaStateTest.java
index 02ea920..0347b66 100644
--- a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaStateTest.java
+++ b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapAkaStateTest.java
@@ -85,7 +85,8 @@
                         mMockTelephonyManager,
                         EAP_IDENTITY_BYTES,
                         mEapAkaConfig,
-                        mMockEapAkaTypeDataDecoder);
+                        mMockEapAkaTypeDataDecoder,
+                        true);
 
         verify(mMockTelephonyManager).createForSubscriptionId(SUB_ID);
     }
diff --git a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapSimAkaMethodStateMachineTest.java b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapSimAkaMethodStateMachineTest.java
index f3cc5a0..47721da 100644
--- a/tests/iketests/src/java/com/android/ike/eap/statemachine/EapSimAkaMethodStateMachineTest.java
+++ b/tests/iketests/src/java/com/android/ike/eap/statemachine/EapSimAkaMethodStateMachineTest.java
@@ -65,6 +65,7 @@
 import static com.android.ike.eap.statemachine.EapSimAkaMethodStateMachine.SESSION_KEY_LENGTH;
 
 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.junit.Assert.fail;
@@ -463,4 +464,12 @@
         assertArrayEquals(EAP_SIM_CLIENT_ERROR_UNABLE_TO_PROCESS, eapResponse.packet);
         verify(mStateMachine, never()).transitionTo(any(EapMethodState.class));
     }
+
+    @Test
+    public void testKeyLengths() {
+        assertEquals(KEY_LEN, mStateMachine.getKEncrLength());
+        assertEquals(KEY_LEN, mStateMachine.getKAutLength());
+        assertEquals(SESSION_KEY_LENGTH, mStateMachine.getMskLength());
+        assertEquals(SESSION_KEY_LENGTH, mStateMachine.getEmskLength());
+    }
 }
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionOptionsTest.java b/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionOptionsTest.java
index 7abcc58..88ff2f1 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionOptionsTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionOptionsTest.java
@@ -17,6 +17,7 @@
 package com.android.ike.ikev2;
 
 import static com.android.ike.ikev2.IkeSessionOptions.IkeAuthConfig;
+import static com.android.ike.ikev2.IkeSessionOptions.IkeAuthDigitalSignLocalConfig;
 import static com.android.ike.ikev2.IkeSessionOptions.IkeAuthDigitalSignRemoteConfig;
 import static com.android.ike.ikev2.IkeSessionOptions.IkeAuthEapConfig;
 import static com.android.ike.ikev2.IkeSessionOptions.IkeAuthPskConfig;
@@ -44,7 +45,10 @@
 import org.junit.Test;
 
 import java.net.Inet4Address;
+import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.RSAPrivateKey;
 
 public final class IkeSessionOptionsTest {
     private static final String PSK_HEX_STRING = "6A756E69706572313233";
@@ -60,6 +64,10 @@
     private IkeIdentification mLocalIdentification;
     private IkeIdentification mRemoteIdentification;
 
+    private X509Certificate mMockServerCaCert;
+    private X509Certificate mMockClientEndCert;
+    private PrivateKey mMockRsaPrivateKey;
+
     @Before
     public void setUp() throws Exception {
         Context context = InstrumentationRegistry.getContext();
@@ -76,6 +84,10 @@
                         .build();
         mLocalIdentification = new IkeIpv4AddrIdentification(LOCAL_IPV4_ADDRESS);
         mRemoteIdentification = new IkeIpv4AddrIdentification(REMOTE_IPV4_ADDRESS);
+
+        mMockServerCaCert = mock(X509Certificate.class);
+        mMockClientEndCert = mock(X509Certificate.class);
+        mMockRsaPrivateKey = mock(RSAPrivateKey.class);
     }
 
     @After
@@ -121,7 +133,6 @@
 
     @Test
     public void testBuildWithEap() throws Exception {
-        X509Certificate mockCert = mock(X509Certificate.class);
         EapSessionConfig eapConfig = mock(EapSessionConfig.class);
 
         IkeSessionOptions sessionOptions =
@@ -131,7 +142,7 @@
                         .addSaProposal(mIkeSaProposal)
                         .setLocalIdentification(mLocalIdentification)
                         .setRemoteIdentification(mRemoteIdentification)
-                        .setAuthEap(mockCert, eapConfig)
+                        .setAuthEap(mMockServerCaCert, eapConfig)
                         .build();
 
         verifyIkeSessionOptionsCommon(sessionOptions);
@@ -145,11 +156,65 @@
         assertTrue(remoteConfig instanceof IkeAuthDigitalSignRemoteConfig);
         assertEquals(IkeSessionOptions.IKE_AUTH_METHOD_PUB_KEY_SIGNATURE, remoteConfig.mAuthMethod);
         assertEquals(
-                mockCert,
+                mMockServerCaCert,
                 ((IkeAuthDigitalSignRemoteConfig) remoteConfig).mTrustAnchor.getTrustedCert());
     }
 
     @Test
+    public void testBuildWithDigitalSignatureAuth() throws Exception {
+        IkeSessionOptions sessionOptions =
+                new IkeSessionOptions.Builder()
+                        .setServerAddress(REMOTE_IPV4_ADDRESS)
+                        .setUdpEncapsulationSocket(mUdpEncapSocket)
+                        .addSaProposal(mIkeSaProposal)
+                        .setLocalIdentification(mLocalIdentification)
+                        .setRemoteIdentification(mRemoteIdentification)
+                        .setAuthDigitalSignature(
+                                mMockServerCaCert, mMockClientEndCert, mMockRsaPrivateKey)
+                        .build();
+
+        verifyIkeSessionOptionsCommon(sessionOptions);
+
+        IkeAuthConfig localConfig = sessionOptions.getLocalAuthConfig();
+        assertTrue(localConfig instanceof IkeAuthDigitalSignLocalConfig);
+
+        IkeAuthDigitalSignLocalConfig localAuthConfig = (IkeAuthDigitalSignLocalConfig) localConfig;
+        assertEquals(
+                IkeSessionOptions.IKE_AUTH_METHOD_PUB_KEY_SIGNATURE, localAuthConfig.mAuthMethod);
+        assertEquals(mMockClientEndCert, localAuthConfig.mEndCert);
+        assertTrue(localAuthConfig.mIntermediateCerts.isEmpty());
+        assertEquals(mMockRsaPrivateKey, localAuthConfig.mPrivateKey);
+
+        IkeAuthConfig remoteConfig = sessionOptions.getRemoteAuthConfig();
+        assertTrue(remoteConfig instanceof IkeAuthDigitalSignRemoteConfig);
+        assertEquals(IkeSessionOptions.IKE_AUTH_METHOD_PUB_KEY_SIGNATURE, remoteConfig.mAuthMethod);
+        assertEquals(
+                mMockServerCaCert,
+                ((IkeAuthDigitalSignRemoteConfig) remoteConfig).mTrustAnchor.getTrustedCert());
+    }
+
+    @Test
+    public void testBuildWithDsaDigitalSignatureAuth() throws Exception {
+        try {
+            IkeSessionOptions sessionOptions =
+                    new IkeSessionOptions.Builder()
+                            .setServerAddress(REMOTE_IPV4_ADDRESS)
+                            .setUdpEncapsulationSocket(mUdpEncapSocket)
+                            .addSaProposal(mIkeSaProposal)
+                            .setLocalIdentification(mLocalIdentification)
+                            .setRemoteIdentification(mRemoteIdentification)
+                            .setAuthDigitalSignature(
+                                    mMockServerCaCert,
+                                    mMockClientEndCert,
+                                    mock(DSAPrivateKey.class))
+                            .build();
+            fail("Expected to fail because DSA is not supported");
+        } catch (IllegalArgumentException expected) {
+
+        }
+    }
+
+    @Test
     public void testBuildWithoutSaProposal() throws Exception {
         try {
             new IkeSessionOptions.Builder()
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/TunnelModeChildSessionOptionsTest.java b/tests/iketests/src/java/com/android/ike/ikev2/TunnelModeChildSessionOptionsTest.java
index 5ed6480..e14e6e8 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/TunnelModeChildSessionOptionsTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/TunnelModeChildSessionOptionsTest.java
@@ -20,6 +20,7 @@
 import static android.system.OsConstants.AF_INET6;
 
 import static com.android.ike.ikev2.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_ADDRESS;
+import static com.android.ike.ikev2.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_DHCP;
 import static com.android.ike.ikev2.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_DNS;
 import static com.android.ike.ikev2.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_NETMASK;
 import static com.android.ike.ikev2.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_SUBNET;
@@ -51,6 +52,8 @@
     private static final int IP4_PREFIX_LEN = 32;
     private static final int IP6_PREFIX_LEN = 64;
 
+    private static final int INVALID_ADDR_FAMILY = 5;
+
     private static final Inet4Address IPV4_ADDRESS =
             (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100"));
     private static final Inet6Address IPV6_ADDRESS =
@@ -61,6 +64,8 @@
     private static final Inet6Address IPV6_DNS_SERVER =
             (Inet6Address) (InetAddressUtils.parseNumericAddress("2001:4860:4860::8888"));
 
+    private static final Inet4Address IPV4_DHCP_SERVER =
+            (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200"));
     private ChildSaProposal mSaProposal;
 
     @Before
@@ -81,10 +86,10 @@
     }
 
     private void verifyAttrTypes(
-            SparseArray exptectedAttrCntMap, TunnelModeChildSessionOptions childOptions) {
+            SparseArray expectedAttrCntMap, TunnelModeChildSessionOptions childOptions) {
         ConfigAttribute[] configAttributes = childOptions.getConfigurationRequests();
 
-        SparseArray<Integer> atrrCntMap = exptectedAttrCntMap.clone();
+        SparseArray<Integer> atrrCntMap = expectedAttrCntMap.clone();
 
         for (int i = 0; i < configAttributes.length; i++) {
             int attType = configAttributes[i].attributeType;
@@ -119,12 +124,12 @@
 
         verifyCommon(childOptions);
 
-        SparseArray<Integer> exptectedAttrCntMap = new SparseArray<>();
-        exptectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP4_ADDRESS, 2);
-        exptectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP6_ADDRESS, 3);
-        exptectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP4_NETMASK, 1);
+        SparseArray<Integer> expectedAttrCntMap = new SparseArray<>();
+        expectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP4_ADDRESS, 2);
+        expectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP6_ADDRESS, 3);
+        expectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP4_NETMASK, 1);
 
-        verifyAttrTypes(exptectedAttrCntMap, childOptions);
+        verifyAttrTypes(expectedAttrCntMap, childOptions);
     }
 
     @Test
@@ -153,11 +158,11 @@
 
         verifyCommon(childOptions);
 
-        SparseArray<Integer> exptectedAttrCntMap = new SparseArray<>();
-        exptectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP4_DNS, 2);
-        exptectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP6_DNS, 2);
+        SparseArray<Integer> expectedAttrCntMap = new SparseArray<>();
+        expectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP4_DNS, 2);
+        expectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP6_DNS, 2);
 
-        verifyAttrTypes(exptectedAttrCntMap, childOptions);
+        verifyAttrTypes(expectedAttrCntMap, childOptions);
     }
 
     @Test
@@ -171,11 +176,54 @@
 
         verifyCommon(childOptions);
 
-        SparseArray<Integer> exptectedAttrCntMap = new SparseArray<>();
-        exptectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP4_SUBNET, 1);
-        exptectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP6_SUBNET, 1);
+        SparseArray<Integer> expectedAttrCntMap = new SparseArray<>();
+        expectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP4_SUBNET, 1);
+        expectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP6_SUBNET, 1);
 
-        verifyAttrTypes(exptectedAttrCntMap, childOptions);
+        verifyAttrTypes(expectedAttrCntMap, childOptions);
+    }
+
+    @Test
+    public void testBuildChildSessionOptionsWithDhcpServerReq() {
+        TunnelModeChildSessionOptions childOptions =
+                new TunnelModeChildSessionOptions.Builder()
+                        .addSaProposal(mSaProposal)
+                        .addInternalDhcpServerRequest(AF_INET, 3)
+                        .addInternalDhcpServerRequest(IPV4_DHCP_SERVER)
+                        .build();
+
+        verifyCommon(childOptions);
+
+        SparseArray<Integer> expectedAttrCntMap = new SparseArray<>();
+        expectedAttrCntMap.put(CONFIG_ATTR_INTERNAL_IP4_DHCP, 4);
+
+        verifyAttrTypes(expectedAttrCntMap, childOptions);
+    }
+
+    @Test
+    public void testBuildChildSessionOptionsWithDhcp6SeverReq() {
+        try {
+            new TunnelModeChildSessionOptions.Builder()
+                    .addSaProposal(mSaProposal)
+                    .addInternalDhcpServerRequest(AF_INET6, 3)
+                    .build();
+            fail("Expected to fail because DHCP6 is not supported.");
+        } catch (IllegalArgumentException expected) {
+
+        }
+    }
+
+    @Test
+    public void testBuildChildSessionOptionsWithInvalidDhcpReq() {
+        try {
+            new TunnelModeChildSessionOptions.Builder()
+                    .addSaProposal(mSaProposal)
+                    .addInternalDhcpServerRequest(INVALID_ADDR_FAMILY, 3)
+                    .build();
+            fail("Expected to fail due to invalid address family value");
+        } catch (IllegalArgumentException expected) {
+
+        }
     }
 }