Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d480cc91 authored by Mingming Cai's avatar Mingming Cai
Browse files

Merge "Fix IMS Authentication leakage" am: 2ce733e0

Change-Id: Ieb42a3aabfd77cd4aed065c481d91d4805773cd5
parents ef1791c4 2ce733e0
Loading
Loading
Loading
Loading
+84 −59
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.os.Handler;
import android.os.Message;
import android.os.Registrant;
import android.os.RegistrantList;
import android.os.SystemClock;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -138,8 +139,6 @@ public abstract class IccRecords extends Handler implements IccConstants {
    protected boolean mIsVoiceMailFixed = false;
    @UnsupportedAppUsage
    protected String mImsi; // IMSI must be only valid numeric characters 0-9 without padding 'f's
    @UnsupportedAppUsage
    private IccIoResult auth_rsp;

    @UnsupportedAppUsage
    protected int mMncLength = UNINITIALIZED;
@@ -174,9 +173,6 @@ public abstract class IccRecords extends Handler implements IccConstants {
    protected String[] mEhplmns;
    protected String[] mFplmns;

    @UnsupportedAppUsage
    private final Object mLock = new Object();

    CarrierTestOverride mCarrierTestOverride;

    //Arbitrary offset for the Handler
@@ -231,6 +227,20 @@ public abstract class IccRecords extends Handler implements IccConstants {
    public static final int DEFAULT_VOICE_MESSAGE_COUNT = -2;
    public static final int UNKNOWN_VOICE_MESSAGE_COUNT = -1;

    // Maximum time in millisecond to wait for a IccSim Challenge before assuming it will not
    // arrive and returning null to the callers.
    private static final long ICC_SIM_CHALLENGE_TIMEOUT_MILLIS = 2500;

    /**
     * There are two purposes for this class. First, each instance of AuthAsyncResponse acts as a
     * lock to for calling thead to wait in getIccSimChallengeResponse(). Second, pass the IMS
     * authentication response to the getIccSimChallengeResponse().
     */
    private static class AuthAsyncResponse {
        public IccIoResult authRsp;
        public Throwable exception;
    }

    @Override
    public String toString() {
        String iccIdToPrint = SubscriptionInfo.givePrintableIccid(mFullIccId);
@@ -311,13 +321,6 @@ public abstract class IccRecords extends Handler implements IccConstants {
    public void dispose() {
        mDestroyed.set(true);

        // It is possible that there is another thread waiting for the response
        // to requestIccSimAuthentication() in getIccSimChallengeResponse().
        auth_rsp = null;
        synchronized (mLock) {
            mLock.notifyAll();
        }

        mCi.unregisterForIccRefresh(this);
        mParentApp.unregisterForReady(this);
        mParentApp.unregisterForLocked(this);
@@ -890,20 +893,27 @@ public abstract class IccRecords extends Handler implements IccConstants {

            case EVENT_AKA_AUTHENTICATE_DONE:
                ar = (AsyncResult) msg.obj;
                auth_rsp = null;
                AuthAsyncResponse rsp = (AuthAsyncResponse) ar.userObj;
                if (DBG) log("EVENT_AKA_AUTHENTICATE_DONE");

                synchronized (rsp) {
                    if (ar.exception != null) {
                        rsp.exception = ar.exception;
                        loge("Exception ICC SIM AKA: " + ar.exception);
                    } else if (ar.result == null) {
                        rsp.exception = new NullPointerException(
                                "Null SIM authentication response");
                        loge("EVENT_AKA_AUTHENTICATE_DONE: null response");
                    } else {
                        try {
                        auth_rsp = (IccIoResult)ar.result;
                        if (DBG) log("ICC SIM AKA: auth_rsp = " + auth_rsp);
                    } catch (Exception e) {
                            rsp.authRsp = (IccIoResult) ar.result;
                            if (VDBG) log("ICC SIM AKA: authRsp = " + rsp.authRsp);
                        } catch (ClassCastException e) {
                            rsp.exception = e;
                            loge("Failed to parse ICC SIM AKA contents: " + e);
                        }
                    }
                synchronized (mLock) {
                    mLock.notifyAll();
                    rsp.notifyAll();
                }

                break;
@@ -1182,54 +1192,69 @@ public abstract class IccRecords extends Handler implements IccConstants {
    }

    /**
     * Solve authentication leakage issue. See b/147463955.
     * Returns the response of the SIM application on the UICC to authentication
     * challenge/response algorithm. The data string and challenge response are
     * Base64 encoded Strings.
     * Can support EAP-SIM, EAP-AKA with results encoded per 3GPP TS 31.102.
     *
     * @param authContext parameter P2 that specifies the authentication context per 3GPP TS 31.102 (Section 7.1.2)
     * @param authContext parameter P2 that specifies the authentication context
     * per 3GPP TS 31.102 (Section 7.1.2)
     * @param data authentication challenge data
     * @return challenge response
     */
    @UnsupportedAppUsage
    @Nullable
    public String getIccSimChallengeResponse(int authContext, String data) {
        if (DBG) log("getIccSimChallengeResponse:");
        if (VDBG) log("getIccSimChallengeResponse:");

        //final here is for defensive copy.
        final CommandsInterface ci = mCi;
        final UiccCardApplication parentApp = mParentApp;
        if (ci == null || parentApp == null) {
            loge("getIccSimChallengeResponse: Fail, ci or parentApp is null");
            return null;
        }

        AuthAsyncResponse rsp = new AuthAsyncResponse();

        synchronized (rsp) {
            ci.requestIccSimAuthentication(authContext, data, parentApp.getAid(),
                    obtainMessage(EVENT_AKA_AUTHENTICATE_DONE, 0, 0, rsp));
            //TODO: factor wait with timeout into a separate method
            final long startTime = SystemClock.elapsedRealtime();
            do {
                try {
            synchronized(mLock) {
                CommandsInterface ci = mCi;
                UiccCardApplication parentApp = mParentApp;
                if (ci != null && parentApp != null) {
                    ci.requestIccSimAuthentication(authContext, data,
                            parentApp.getAid(),
                            obtainMessage(EVENT_AKA_AUTHENTICATE_DONE));
                    try {
                        mLock.wait();
                    long sleepTime = startTime + ICC_SIM_CHALLENGE_TIMEOUT_MILLIS
                            - SystemClock.elapsedRealtime();
                    if (sleepTime > 0) rsp.wait(sleepTime);
                } catch (InterruptedException e) {
                        loge("getIccSimChallengeResponse: Fail, interrupted"
                                + " while trying to request Icc Sim Auth");
                        return null;
                    Rlog.w("IccRecords", "getIccSimChallengeResponse: InterruptedException.");
                }
                } else {
                    loge( "getIccSimChallengeResponse: "
                            + "Fail, ci or parentApp is null");
            } while (SystemClock.elapsedRealtime() - startTime < ICC_SIM_CHALLENGE_TIMEOUT_MILLIS
                    && rsp.authRsp == null && rsp.exception == null);

            if (SystemClock.elapsedRealtime() - startTime >= ICC_SIM_CHALLENGE_TIMEOUT_MILLIS
                    && rsp.authRsp == null && rsp.exception == null) {
                loge("getIccSimChallengeResponse timeout!");
                return null;
            }
            }
        } catch(Exception e) {
            loge( "getIccSimChallengeResponse: "
                    + "Fail while trying to request Icc Sim Auth");

            if (rsp.exception != null) {
                loge("getIccSimChallengeResponse exception: " + rsp.exception);
                //TODO: propagate better exceptions up to the user now that we have them available
                //in the call stack.
                return null;
            }

        if (auth_rsp == null) {
            if (rsp.authRsp == null) {
                loge("getIccSimChallengeResponse: No authentication response");
                return null;
            }
        }
        if (VDBG) log("getIccSimChallengeResponse: return rsp.authRsp");

        if (DBG) log("getIccSimChallengeResponse: return auth_rsp");

        return android.util.Base64.encodeToString(auth_rsp.payload, android.util.Base64.NO_WRAP);
        return android.util.Base64.encodeToString(rsp.authRsp.payload,
                android.util.Base64.NO_WRAP);
    }

    /**
+57 −17
Original line number Diff line number Diff line
@@ -79,7 +79,7 @@ import java.util.concurrent.atomic.AtomicInteger;

public class SimulatedCommands extends BaseCommands
        implements CommandsInterface, SimulatedRadioControl {
    private final static String LOG_TAG = "SimulatedCommands";
    private static final String LOG_TAG = "SimulatedCommands";

    private enum SimLockState {
        NONE,
@@ -95,21 +95,27 @@ public class SimulatedCommands extends BaseCommands
        SIM_PERM_LOCKED
    }

    private final static SimLockState INITIAL_LOCK_STATE = SimLockState.NONE;
    public final static String DEFAULT_SIM_PIN_CODE = "1234";
    private final static String SIM_PUK_CODE = "12345678";
    private final static SimFdnState INITIAL_FDN_STATE = SimFdnState.NONE;
    public final static String DEFAULT_SIM_PIN2_CODE = "5678";
    private final static String SIM_PUK2_CODE = "87654321";
    public final static String FAKE_LONG_NAME = "Fake long name";
    public final static String FAKE_SHORT_NAME = "Fake short name";
    public final static String FAKE_MCC_MNC = "310260";
    public final static String FAKE_IMEI = "012345678901234";
    public final static String FAKE_IMEISV = "99";
    public final static String FAKE_ESN = "1234";
    public final static String FAKE_MEID = "1234";
    public final static int DEFAULT_PIN1_ATTEMPT = 5;
    public final static int DEFAULT_PIN2_ATTEMPT = 5;
    private static final SimLockState INITIAL_LOCK_STATE = SimLockState.NONE;
    public static final String DEFAULT_SIM_PIN_CODE = "1234";
    private static final String SIM_PUK_CODE = "12345678";
    private static final SimFdnState INITIAL_FDN_STATE = SimFdnState.NONE;
    public static final String DEFAULT_SIM_PIN2_CODE = "5678";
    private static final String SIM_PUK2_CODE = "87654321";
    public static final String FAKE_LONG_NAME = "Fake long name";
    public static final String FAKE_SHORT_NAME = "Fake short name";
    public static final String FAKE_MCC_MNC = "310260";
    public static final String FAKE_IMEI = "012345678901234";
    public static final String FAKE_IMEISV = "99";
    public static final String FAKE_ESN = "1234";
    public static final String FAKE_MEID = "1234";
    public static final int DEFAULT_PIN1_ATTEMPT = 5;
    public static final int DEFAULT_PIN2_ATTEMPT = 5;
    public static final int ICC_AUTHENTICATION_MODE_DEFAULT = 0;
    public static final int ICC_AUTHENTICATION_MODE_NULL = 1;
    public static final int ICC_AUTHENTICATION_MODE_TIMEOUT = 2;
    // Maximum time in millisecond to wait for a IccSim Challenge before assuming it will not
    // arrive and returning null to the callers.
    public static final  long ICC_SIM_CHALLENGE_TIMEOUT_MILLIS = 2500;

    private String mImei;
    private String mImeiSv;
@@ -162,6 +168,8 @@ public class SimulatedCommands extends BaseCommands
    private SetupDataCallResult mSetupDataCallResult;
    private boolean mIsRadioPowerFailResponse = false;

    // mode for Icc Sim Authentication
    private int mAuthenticationMode;
    //***** Constructor
    public
    SimulatedCommands() {
@@ -179,6 +187,7 @@ public class SimulatedCommands extends BaseCommands
        mSimFdnEnabledState = INITIAL_FDN_STATE;
        mSimFdnEnabled = (mSimFdnEnabledState != SimFdnState.NONE);
        mPin2Code = DEFAULT_SIM_PIN2_CODE;
        mAuthenticationMode = ICC_AUTHENTICATION_MODE_DEFAULT;
    }

    public void dispose() {
@@ -1896,7 +1905,38 @@ public class SimulatedCommands extends BaseCommands

    @Override
    public void requestIccSimAuthentication(int authContext, String data, String aid, Message response) {
        unimplemented(response);
        switch (mAuthenticationMode) {
            case ICC_AUTHENTICATION_MODE_TIMEOUT:
                break;

            case ICC_AUTHENTICATION_MODE_NULL:
                sendMessageResponse(response, null);
                break;

            default:
                if (data == null || data.length() == 0) {
                    sendMessageResponse(response,  null);
                } else {
                    sendMessageResponse(response, new IccIoResult(0, 0, (byte[]) data.getBytes()));
                }
                break;
        }
    }

    /**
     * Helper function to send response msg
     * @param msg Response message to be sent
     * @param ret Return object to be included in the response message
     */
    private void sendMessageResponse(Message msg, Object ret) {
        if (msg != null) {
            AsyncResult.forMessage(msg, ret, null);
            msg.sendToTarget();
        }
    }

    public void setAuthenticationMode(int authenticationMode) {
        mAuthenticationMode = authenticationMode;
    }

    @Override
+94 −1
Original line number Diff line number Diff line
@@ -31,12 +31,14 @@ package com.android.internal.telephony.uicc;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;

import android.content.Context;
import android.os.AsyncResult;
import android.os.HandlerThread;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.util.Pair;

import com.android.internal.telephony.TelephonyTest;
@@ -124,4 +126,95 @@ public class IccRecordsTest extends TelephonyTest {
        waitForLastHandlerAction(mIccRecords);
        assertEquals(mIccRecords.getSmsCapacityOnIcc(), 500);
    }

    @Test
    public void testGetIccSimChallengeResponseNull() {
        long startTime;
        long timeSpent;

        // EAP-SIM rand is 16 bytes.
        String base64Challenge = "ECcTqwuo6OfY8ddFRboD9WM=";

        // Test for null result
        mSimulatedCommands.setAuthenticationMode(mSimulatedCommands.ICC_AUTHENTICATION_MODE_NULL);

        startTime = SystemClock.elapsedRealtime();
        assertNull("getIccAuthentication should return null for empty data.",
                mIccRecords.getIccSimChallengeResponse(UiccCardApplication.AUTH_CONTEXT_EAP_AKA,
                      base64Challenge));
        timeSpent = SystemClock.elapsedRealtime() - startTime;
        Log.d("IccRecordsTest", "Time (ms) for getIccSimChallengeResponse is " + timeSpent);
        assertTrue("getIccAuthentication should not timeout",
                timeSpent < mSimulatedCommands.ICC_SIM_CHALLENGE_TIMEOUT_MILLIS);
    }

    @Test
    public void testGetIccSimChallengeResponseTimeout() {
        long startTime;
        long timeSpent;

        // EAP-SIM rand is 16 bytes.
        String base64Challenge = "ECcTqwuo6OfY8ddFRboD9WM=";

        mSimulatedCommands.setAuthenticationMode(
                mSimulatedCommands.ICC_AUTHENTICATION_MODE_TIMEOUT);
        startTime = SystemClock.elapsedRealtime();
        assertNull("getIccAuthentication should return null for empty data.",
                mIccRecords.getIccSimChallengeResponse(UiccCardApplication.AUTH_CONTEXT_EAP_AKA,
                      base64Challenge));
        timeSpent = SystemClock.elapsedRealtime() - startTime;
        Log.d("IccRecordsTest", "Time (ms) for getIccSimChallengeResponse is " + timeSpent);
        assertTrue("getIccAuthentication should timeout",
                timeSpent >= mSimulatedCommands.ICC_SIM_CHALLENGE_TIMEOUT_MILLIS);
    }

    @Test
    public void testGetIccSimChallengeResponseDefault() {
        long startTime;
        long timeSpent;

        // EAP-SIM rand is 16 bytes.
        String base64Challenge = "ECcTqwuo6OfY8ddFRboD9WM=";
        String base64Challenge2 = "EMNxjsFrPCpm+KcgCmQGnwQ=";

        // Test for default setup
        mSimulatedCommands.setAuthenticationMode(
                mSimulatedCommands.ICC_AUTHENTICATION_MODE_DEFAULT);

        // Test for null input
        startTime = SystemClock.elapsedRealtime();
        assertNull("getIccAuthentication should return null for empty data.",
                mIccRecords.getIccSimChallengeResponse(
                        UiccCardApplication.AUTH_CONTEXT_EAP_AKA, ""));
        timeSpent = SystemClock.elapsedRealtime() - startTime;
        Log.d("IccRecordsTest", "Time (ms) for getIccSimChallengeResponse is " + timeSpent);
        assertTrue("getIccAuthentication should not timeout",
                timeSpent < mSimulatedCommands.ICC_SIM_CHALLENGE_TIMEOUT_MILLIS);

        // EAP-SIM
        startTime = SystemClock.elapsedRealtime();
        String response = mIccRecords.getIccSimChallengeResponse(
                UiccCardApplication.AUTH_CONTEXT_EAP_SIM, base64Challenge);
        timeSpent = SystemClock.elapsedRealtime() - startTime;
        Log.d("IccRecordsTest", "Time (ms) for getIccSimChallengeResponse is " + timeSpent);
        Log.d("IccRecordsTest", "Result of getIccSimChallengeResponse is " + response);
        assertTrue("Response to EAP-SIM Challenge must not be Null.", response != null);

        startTime = SystemClock.elapsedRealtime();
        String response1 = mIccRecords.getIccSimChallengeResponse(
                UiccCardApplication.AUTH_CONTEXT_EAP_SIM, base64Challenge);
        timeSpent = SystemClock.elapsedRealtime() - startTime;
        Log.d("IccRecordsTest", "Time (ms) for getIccSimChallengeResponse is " + timeSpent);
        Log.d("IccRecordsTest", "Result of getIccSimChallengeResponse is " + response1);
        assertTrue("Response to EAP-SIM Challenge must be consistent.",
                response.equals(response1));

        startTime = SystemClock.elapsedRealtime();
        String response2 = mIccRecords.getIccSimChallengeResponse(
                UiccCardApplication.AUTH_CONTEXT_EAP_SIM, base64Challenge2);
        timeSpent = SystemClock.elapsedRealtime() - startTime;
        Log.d("IccRecordsTest", "Time (ms) for getIccSimChallengeResponse is " + timeSpent);
        assertTrue("Two responses must be different.", !response.equals(response2));
    }

}