| /* |
| * Copyright (C) 2017 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.apksig.internal.asn1; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.fail; |
| |
| import com.android.apksig.internal.util.HexEncoding; |
| import java.math.BigInteger; |
| import java.nio.ByteBuffer; |
| import java.util.List; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| @RunWith(JUnit4.class) |
| public class Asn1BerParserTest { |
| @Test(expected = NullPointerException.class) |
| public void testNullInput() throws Exception { |
| parse((ByteBuffer) null, EmptySequence.class); |
| } |
| |
| @Test(expected = Asn1DecodingException.class) |
| public void testEmptyInput() throws Exception { |
| parse("", EmptySequence.class); |
| } |
| |
| @Test |
| public void testEmptySequence() throws Exception { |
| // Empty SEQUENCE (0x3000) followed by garbage (0x12345678) |
| ByteBuffer input = ByteBuffer.wrap(HexEncoding.decode("300012345678")); |
| EmptySequence container = parse(input, EmptySequence.class); |
| assertNotNull(container); |
| // Check that input position has been advanced appropriately |
| assertEquals(2, input.position()); |
| } |
| |
| @Test |
| public void testOctetString() throws Exception { |
| assertEquals( |
| "123456", |
| HexEncoding.encode(parse("30050403123456", SequenceWithOctetString.class).buf)); |
| assertEquals( |
| "", HexEncoding.encode(parse("30020400", SequenceWithOctetString.class).buf)); |
| } |
| |
| @Test |
| public void testInteger() throws Exception { |
| // Various Java types decoded from INTEGER |
| // Empty SEQUENCE (0x3000) followed by garbage (0x12345678) |
| SequenceWithIntegers container = |
| parse("301e" |
| + "0201ff" // -1 |
| + "0207ff123456789abc" // -7f123456789abc |
| + "0200" // 0 |
| + "020280ff" // -255 |
| + "020a00000000000000001234", // 0x1234 |
| SequenceWithIntegers.class); |
| assertEquals(-1, container.n1); |
| } |
| |
| @Test |
| public void testOid() throws Exception { |
| // Empty OID |
| try { |
| parse("30020600", SequenceWithOid.class); |
| fail(); |
| } catch (Asn1DecodingException expected) {} |
| |
| |
| assertEquals("2.100.3", parse("30050603813403", SequenceWithOid.class).oid); |
| assertEquals( |
| "2.16.840.1.101.3.4.2.1", |
| parse("300b0609608648016503040201", SequenceWithOid.class).oid); |
| } |
| |
| @Test |
| public void testSequenceOf() throws Exception { |
| assertEquals(2, parse("3006300430003000", SequenceWithSequenceOf.class).values.size()); |
| } |
| |
| @Test |
| public void testSetOf() throws Exception { |
| assertEquals(2, parse("3006310430003000", SequenceWithSetOf.class).values.size()); |
| } |
| |
| @Test |
| public void testImplicitOptionalField() throws Exception { |
| // Optional field f2 missing in the input |
| SequenceWithImplicitOptionalField seq = |
| parse("300602010d02012a", SequenceWithImplicitOptionalField.class); |
| assertEquals(13, seq.f1.intValue()); |
| assertNull(seq.f2); |
| assertEquals(42, seq.f3.intValue()); |
| |
| // Optional field f2 present in the input |
| seq = parse("300a02010da102ffff02012a", SequenceWithImplicitOptionalField.class); |
| assertEquals(13, seq.f1.intValue()); |
| assertEquals(-1, seq.f2.intValue()); |
| assertEquals(42, seq.f3.intValue()); |
| } |
| |
| |
| @Test |
| public void testExplicitOptionalField() throws Exception { |
| // Optional field f2 missing in the input |
| SequenceWithExplicitOptionalField seq = |
| parse("300602010d02012a", SequenceWithExplicitOptionalField.class); |
| assertEquals(13, seq.f1.intValue()); |
| assertNull(seq.f2); |
| assertEquals(42, seq.f3.intValue()); |
| |
| // Optional field f2 present in the input |
| seq = parse("300c02010da1040202ffff02012a", SequenceWithExplicitOptionalField.class); |
| assertEquals(13, seq.f1.intValue()); |
| assertEquals(-1, seq.f2.intValue()); |
| assertEquals(42, seq.f3.intValue()); |
| } |
| |
| @Test |
| public void testChoiceWithDifferentTypedOptions() throws Exception { |
| // The CHOICE can be either an INTEGER or an OBJECT IDENTIFIER |
| |
| // INTEGER |
| ChoiceWithTwoOptions c = parse("0208ffffffffffffffff", ChoiceWithTwoOptions.class); |
| assertNull(c.oid); |
| assertEquals(-1, c.num.intValue()); |
| |
| // OBJECT IDENTIFIER |
| c = parse("060100", ChoiceWithTwoOptions.class); |
| assertEquals("0.0", c.oid); |
| assertNull(c.num); |
| |
| // Empty input |
| try { |
| parse("", ChoiceWithTwoOptions.class); |
| fail(); |
| } catch (Asn1DecodingException expected) {} |
| |
| // Neither of the options match |
| try { |
| // Empty SEQUENCE |
| parse("3000", ChoiceWithTwoOptions.class); |
| fail(); |
| } catch (Asn1DecodingException expected) {} |
| } |
| |
| @Test |
| public void testChoiceWithSameTypedOptions() throws Exception { |
| // The CHOICE can be either a SEQUENCE, an IMPLICIT SEQUENCE, or an EXPLICIT SEQUENCE |
| |
| // SEQUENCE |
| ChoiceWithThreeSequenceOptions c = parse("3000", ChoiceWithThreeSequenceOptions.class); |
| assertNotNull(c.s1); |
| assertNull(c.s2); |
| assertNull(c.s3); |
| |
| // IMPLICIT [0] SEQUENCE |
| c = parse("a000", ChoiceWithThreeSequenceOptions.class); |
| assertNull(c.s1); |
| assertNotNull(c.s2); |
| assertNull(c.s3); |
| |
| // EXPLICIT [0] SEQUENCE |
| c = parse("a1023000", ChoiceWithThreeSequenceOptions.class); |
| assertNull(c.s1); |
| assertNull(c.s2); |
| assertNotNull(c.s3); |
| |
| // INTEGER -- None of the options match |
| try { |
| parse("02010a", ChoiceWithThreeSequenceOptions.class); |
| fail(); |
| } catch (Asn1DecodingException expected) {} |
| } |
| |
| @Test(expected = Asn1DecodingException.class) |
| public void testChoiceWithClashingOptions() throws Exception { |
| // The CHOICE is between INTEGER and INTEGER which clash |
| parse("0200", ChoiceWithClashingOptions.class); |
| } |
| |
| @Test |
| public void testPrimitiveIndefiniteLengthEncodingWithGarbage() throws Exception { |
| // Indefinite length INTEGER containing what may look like a malformed definite length |
| // INTEGER, followed by an INTEGER. This tests that contents of indefinite length encoded |
| // primitive (i.e., not constructed) data values must not be parsed to locate the 0x00 0x00 |
| // terminator. |
| ByteBuffer input = ByteBuffer.wrap(HexEncoding.decode("0280020401000002010c")); |
| ChoiceWithTwoOptions c = parse(input, ChoiceWithTwoOptions.class); |
| // Check what's remaining in the input buffer |
| assertEquals("02010c", HexEncoding.encode(input)); |
| // Check what was consumed |
| assertEquals(0x020401, c.num.intValue()); |
| |
| // Indefinite length INTEGER containing what may look like a malformed indefinite length |
| // INTEGER, followed by an INTEGER |
| input = ByteBuffer.wrap(HexEncoding.decode("0280028001000002010c")); |
| c = parse(input, ChoiceWithTwoOptions.class); |
| // Check what's remaining in the input buffer |
| assertEquals("02010c", HexEncoding.encode(input)); |
| // Check what was consumed |
| assertEquals(0x028001, c.num.intValue()); |
| } |
| |
| @Test |
| public void testConstructedIndefiniteLengthEncodingWithoutNestedIndefiniteLengthDataValues() |
| throws Exception { |
| // Indefinite length SEQUENCE containing an INTEGER whose encoding contains 0x00 0x00 which |
| // can be misinterpreted as indefinite length encoding terminator of the SEQUENCE, followed |
| // by an INTEGER |
| ByteBuffer input = ByteBuffer.wrap(HexEncoding.decode("308002020000000002010c")); |
| SequenceWithAsn1Opaque c = parse(input, SequenceWithAsn1Opaque.class); |
| // Check what's remaining in the input buffer |
| assertEquals("02010c", HexEncoding.encode(input)); |
| // Check what was read |
| assertEquals("02020000", HexEncoding.encode(c.obj.getEncoded())); |
| } |
| |
| @Test |
| public void testConstructedIndefiniteLengthEncodingWithNestedIndefiniteLengthDataValues() |
| throws Exception { |
| // Indefinite length SEQUENCE containing two INTEGER fields using indefinite |
| // length encoding, followed by an INTEGER. This tests that the 0x00 0x00 terminators used |
| // by the encoding of the two INTEGERs are not confused for the 0x00 0x00 terminator of the |
| // SEQUENCE. |
| ByteBuffer input = |
| ByteBuffer.wrap(HexEncoding.decode("308002800300000280030000020103000002010c")); |
| SequenceWithAsn1Opaque c = parse(input, SequenceWithAsn1Opaque.class); |
| // Check what's remaining in the input buffer |
| assertEquals("02010c", HexEncoding.encode(input)); |
| // Check what was consumed |
| assertEquals("0280030000", HexEncoding.encode(c.obj.getEncoded())); |
| } |
| |
| @Test(expected = Asn1DecodingException.class) |
| public void testConstructedIndefiniteLengthEncodingWithGarbage() throws Exception { |
| // Indefinite length SEQUENCE containing an indefinite length encoded SEQUENCE containing |
| // garbage which doesn't parse as BER, followed by an INTEGER. This tests that contents of |
| // the SEQUENCEs must be parsed to establish where their 0x00 0x00 terminators are located. |
| ByteBuffer input = ByteBuffer.wrap(HexEncoding.decode("3080308002040000000002010c")); |
| parse(input, SequenceWithAsn1Opaque.class); |
| } |
| |
| private static <T> T parse(String hexEncodedInput, Class<T> containerClass) |
| throws Asn1DecodingException { |
| ByteBuffer input = |
| (hexEncodedInput == null) |
| ? null : ByteBuffer.wrap(HexEncoding.decode(hexEncodedInput)); |
| return parse(input, containerClass); |
| } |
| |
| private static <T> T parse(ByteBuffer input, Class<T> containerClass) |
| throws Asn1DecodingException { |
| return Asn1BerParser.parse(input, containerClass); |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class EmptySequence {} |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithIntegers { |
| @Asn1Field(index = 1, type = Asn1Type.INTEGER) |
| public int n1; |
| |
| @Asn1Field(index = 2, type = Asn1Type.INTEGER) |
| public long n2; |
| |
| @Asn1Field(index = 3, type = Asn1Type.INTEGER) |
| public Integer n3; |
| |
| @Asn1Field(index = 4, type = Asn1Type.INTEGER) |
| public Long n4; |
| |
| @Asn1Field(index = 5, type = Asn1Type.INTEGER) |
| public BigInteger n5; |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithOid { |
| @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) |
| public String oid; |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithImplicitOptionalField { |
| @Asn1Field(index = 1, type = Asn1Type.INTEGER) |
| public Integer f1; |
| |
| @Asn1Field(index = 2, type = Asn1Type.INTEGER, optional = true, |
| tagging = Asn1Tagging.IMPLICIT, tagNumber = 1) |
| public Integer f2; |
| |
| @Asn1Field(index = 3, type = Asn1Type.INTEGER) |
| public Integer f3; |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithExplicitOptionalField { |
| @Asn1Field(index = 1, type = Asn1Type.INTEGER) |
| public Integer f1; |
| |
| @Asn1Field(index = 2, type = Asn1Type.INTEGER, optional = true, |
| tagging = Asn1Tagging.EXPLICIT, tagNumber = 1) |
| public Integer f2; |
| |
| @Asn1Field(index = 3, type = Asn1Type.INTEGER) |
| public Integer f3; |
| } |
| |
| @Asn1Class(type = Asn1Type.CHOICE) |
| public static class ChoiceWithTwoOptions { |
| @Asn1Field(type = Asn1Type.OBJECT_IDENTIFIER) |
| public String oid; |
| |
| @Asn1Field(type = Asn1Type.INTEGER) |
| public Integer num; |
| } |
| |
| @Asn1Class(type = Asn1Type.CHOICE) |
| public static class ChoiceWithThreeSequenceOptions { |
| @Asn1Field(type = Asn1Type.SEQUENCE) |
| public EmptySequence s1; |
| |
| @Asn1Field(type = Asn1Type.SEQUENCE, tagging = Asn1Tagging.IMPLICIT, tagNumber = 0) |
| public EmptySequence s2; |
| |
| @Asn1Field(type = Asn1Type.SEQUENCE, tagging = Asn1Tagging.EXPLICIT, tagNumber = 1) |
| public EmptySequence s3; |
| } |
| |
| @Asn1Class(type = Asn1Type.CHOICE) |
| public static class ChoiceWithClashingOptions { |
| @Asn1Field(type = Asn1Type.INTEGER) |
| public int n1; |
| |
| @Asn1Field(type = Asn1Type.INTEGER) |
| public Integer n2; |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithOctetString { |
| @Asn1Field(index = 0, type = Asn1Type.OCTET_STRING) |
| public ByteBuffer buf; |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithSequenceOf { |
| @Asn1Field(index = 0, type = Asn1Type.SEQUENCE_OF) |
| public List<EmptySequence> values; |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithSetOf { |
| @Asn1Field(index = 0, type = Asn1Type.SET_OF) |
| public List<EmptySequence> values; |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithAsn1Opaque { |
| @Asn1Field(type = Asn1Type.ANY) |
| public Asn1OpaqueObject obj; |
| } |
| } |