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

Commit 079aada1 authored by Hunter Knepshield's avatar Hunter Knepshield
Browse files

Additional SGP.22 v2.1+ BPP validity checking.

As of SGP.22 version 2.1 and newer, prior to sending any APDU to the
eSIM, the bound profile package should be validated with additional
criteria. Mainly, it should not contain any extraneous ASN.1 tags or
have zero profile elements.

SGP.22 version 2.0 does not mention what should happen in this case, so
we leave that behavior unmodified.

Added unit tests to EuiccCardTest and verified with Comprion (SGP.23
testing tool).

Test: atest EuiccCardTest
Test: SGP.23 4.4.25.2.1_03 and 4.4.25.2.1_05
Bug: 140314189
Change-Id: I6ebe6d64496cc62d86652a833d04805bff202862
parent ebdbec74
Loading
Loading
Loading
Loading
+48 −3
Original line number Diff line number Diff line
@@ -752,17 +752,62 @@ public class EuiccCard extends UiccCard {
        sendApdu(
                newRequestProvider((RequestBuilder requestBuilder) -> {
                    Asn1Node bppNode = new Asn1Decoder(boundProfilePackage).nextNode();
                    int actualLength = bppNode.getDataLength();
                    int segmentedLength = 0;
                    // initialiseSecureChannelRequest (ES8+.InitialiseSecureChannel)
                    Asn1Node initialiseSecureChannelRequest = bppNode.getChild(
                            Tags.TAG_INITIALISE_SECURE_CHANNEL);
                    segmentedLength += initialiseSecureChannelRequest.getEncodedLength();
                    // firstSequenceOf87 (ES8+.ConfigureISDP)
                    Asn1Node firstSequenceOf87 = bppNode.getChild(Tags.TAG_CTX_COMP_0);
                    segmentedLength += firstSequenceOf87.getEncodedLength();
                    // sequenceOf88 (ES8+.StoreMetadata)
                    Asn1Node sequenceOf88 = bppNode.getChild(Tags.TAG_CTX_COMP_1);
                    List<Asn1Node> metaDataSeqs = sequenceOf88.getChildren(Tags.TAG_CTX_8);
                    // sequenceOf86 (ES8+.LoadProfileElements #1)
                    segmentedLength += sequenceOf88.getEncodedLength();
                    // secondSequenceOf87 (ES8+.ReplaceSessionKeys), optional
                    Asn1Node secondSequenceOf87 = null;
                    if (bppNode.hasChild(Tags.TAG_CTX_COMP_2)) {
                        secondSequenceOf87 = bppNode.getChild(Tags.TAG_CTX_COMP_2);
                        segmentedLength += secondSequenceOf87.getEncodedLength();
                    }
                    // sequenceOf86 (ES8+.LoadProfileElements)
                    Asn1Node sequenceOf86 = bppNode.getChild(Tags.TAG_CTX_COMP_3);
                    List<Asn1Node> elementSeqs = sequenceOf86.getChildren(Tags.TAG_CTX_6);
                    segmentedLength += sequenceOf86.getEncodedLength();

                    if (mSpecVersion.compareTo(SGP22_V_2_1) >= 0) {
                        // Per SGP.22 v2.1+ section 2.5.5, it's the LPA's job to "segment" the BPP
                        // before sending it to the eUICC. This check was only instituted in SGP.22
                        // v2.1 and higher. SGP.22 v2.0 doesn't mention this "segmentation" process
                        // at all, or what the LPA should do in the case of unrecognized or missing
                        // tags. Per section 3.1.3.3: "If the LPAd is unable to perform the
                        // segmentation (e.g., because of an error in the BPP structure), ... the
                        // LPAd SHALL perform the Sub-procedure "Profile Download and installation -
                        // Download rejection" with reason code 'Load BPP execution error'." This
                        // implies that if we detect an invalid BPP, we should short-circuit before
                        // sending anything to the eUICC. There are two cases to account for:
                        if (elementSeqs == null || elementSeqs.isEmpty()) {
                            // 1. The BPP is missing a required tag. Upon calling bppNode.getChild,
                            // an exception will occur if the expected tag is missing, though we
                            // should make sure that the sequences are non-empty when appropriate as
                            // well. A profile with no profile elements is invalid. This is
                            // explicitly tested by SGP.23 case 4.4.25.2.1_03.
                            throw new EuiccCardException("No profile elements in BPP");
                        } else if (actualLength != segmentedLength) {
                            // 2. The BPP came with extraneous tags other than what the spec
                            // mandates. We keep track of the total length of the BPP and compare it
                            // to the length of the segments we care about. If they're different,
                            // we'll throw an exception to indicate this. This is explicitly tested
                            // by SGP.23 case 4.4.25.2.1_05.
                            throw new EuiccCardException(
                                    "Actual BPP length ("
                                            + actualLength
                                            + ") does not match segmented length ("
                                            + segmentedLength
                                            + "), this must be due to a malformed BPP");
                        }
                    }

                    requestBuilder.addStoreData(bppNode.getHeadAsHex()
                            + initialiseSecureChannelRequest.toHex());
@@ -775,8 +820,8 @@ public class EuiccCard extends UiccCard {
                        requestBuilder.addStoreData(metaDataSeqs.get(i).toHex());
                    }

                    if (bppNode.hasChild(Tags.TAG_CTX_COMP_2)) {
                        requestBuilder.addStoreData(bppNode.getChild(Tags.TAG_CTX_COMP_2).toHex());
                    if (secondSequenceOf87 != null) {
                        requestBuilder.addStoreData(secondSequenceOf87.toHex());
                    }

                    requestBuilder.addStoreData(sequenceOf86.getHeadAsHex());
+69 −0
Original line number Diff line number Diff line
@@ -22,7 +22,9 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -858,6 +860,65 @@ public class EuiccCardTest extends TelephonyTest {
        verifyStoreData(channel, "8803040506"); // ES8+.StoreMetadata
    }

    @Test
    public void testLoadBoundProfilePackage_NoProfileElements() {
        int channel = mockLogicalChannelResponses_sgp22v210();

        ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>();
        mEuiccCard.loadBoundProfilePackage(
                Asn1Node.newBuilder(0xBF36)
                        .addChild(Asn1Node.newBuilder(0xBF23))
                        .addChild(Asn1Node.newBuilder(0xA0)
                                .addChildAsBytes(0x87, new byte[] {1, 2, 3}))
                        .addChild(Asn1Node.newBuilder(0xA1)
                                .addChildAsBytes(0x88, new byte[] {4, 5, 6}))
                        .addChild(Asn1Node.newBuilder(0xA2))
                        // No children
                        .addChild(Asn1Node.newBuilder(0xA3))
                        .build().toBytes(),
                resultCaptor, mHandler);
        resultCaptor.await();

        EuiccCardException e = (EuiccCardException) resultCaptor.exception;
        assertEquals("No profile elements in BPP", e.getCause().getMessage());
        verify(mMockCi, never())
                .iccTransmitApduLogicalChannel(
                        eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(),
                        any());
    }

    @Test
    public void testLoadBoundProfilePackage_UnrecognizedTag() {
        int channel = mockLogicalChannelResponses_sgp22v210();

        ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>();
        mEuiccCard.loadBoundProfilePackage(
                Asn1Node.newBuilder(0xBF36)
                        .addChild(Asn1Node.newBuilder(0xBF23))
                        .addChild(Asn1Node.newBuilder(0xA0)
                                .addChildAsBytes(0x87, new byte[] {1, 2, 3}))
                        .addChild(Asn1Node.newBuilder(0xA1)
                                .addChildAsBytes(0x88, new byte[] {4, 5, 6}))
                        .addChild(Asn1Node.newBuilder(0xA2))
                        .addChild(Asn1Node.newBuilder(0xA3)
                                .addChildAsBytes(0x86, new byte[] {7, 8, 9})
                                .addChildAsBytes(0x86, new byte[] {0xA, 0xB, 0xC}))
                        // Unrecognized tag
                        .addChild(Asn1Node.newBuilder(0xA4))
                        .build().toBytes(),
                resultCaptor, mHandler);
        resultCaptor.await();

        EuiccCardException e = (EuiccCardException) resultCaptor.exception;
        assertEquals(
                "Actual BPP length (33) does not match segmented length (31), this must be due to a"
                        + " malformed BPP",
                e.getCause().getMessage());
        verify(mMockCi, never())
                .iccTransmitApduLogicalChannel(
                        eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(),
                        any());
    }

    @Test
    public void testCancelSession() {
@@ -1124,4 +1185,12 @@ public class EuiccCardTest extends TelephonyTest {
        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
        return channel;
    }

    private int mockLogicalChannelResponses_sgp22v210(Object... responses) {
        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi,
                "E00582030201009000");
        LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, responses);
        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
        return channel;
    }
}