| /* |
| * 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.assertNotEquals; |
| import static org.junit.Assert.fail; |
| |
| import com.android.apksig.internal.util.HexEncoding; |
| import java.math.BigInteger; |
| import java.nio.ByteBuffer; |
| import java.util.Arrays; |
| import java.util.List; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| @RunWith(JUnit4.class) |
| public class Asn1DerEncoderTest { |
| @Test |
| public void testInteger() throws Exception { |
| assertEquals("3003020100", encodeToHex(new SequenceWithInteger(0))); |
| assertEquals("300302010c", encodeToHex(new SequenceWithInteger(12))); |
| assertEquals("300302017f", encodeToHex(new SequenceWithInteger(0x7f))); |
| assertEquals("3004020200ff", encodeToHex(new SequenceWithInteger(0xff))); |
| assertEquals("30030201ff", encodeToHex(new SequenceWithInteger(-1))); |
| assertEquals("3003020180", encodeToHex(new SequenceWithInteger(-128))); |
| assertEquals("3005020300ffee", encodeToHex(new SequenceWithInteger(0xffee))); |
| assertEquals("300602047fffffff", encodeToHex(new SequenceWithInteger(Integer.MAX_VALUE))); |
| assertEquals("3006020480000000", encodeToHex(new SequenceWithInteger(Integer.MIN_VALUE))); |
| } |
| |
| @Test |
| public void testOctetString() throws Exception { |
| assertEquals( |
| "30050403010203", |
| encodeToHex( |
| new SequenceWithByteBufferOctetString( |
| ByteBuffer.wrap(new byte[] {1, 2, 3})))); |
| assertEquals( |
| "30030401ff", |
| encodeToHex( |
| new SequenceWithByteBufferOctetString( |
| ByteBuffer.wrap(new byte[] {(byte) 0xff})))); |
| |
| assertEquals( |
| "30020400", |
| encodeToHex( |
| new SequenceWithByteBufferOctetString(ByteBuffer.wrap(new byte[0])))); |
| } |
| |
| @Test |
| public void testBitString() throws Exception { |
| assertEquals( |
| "30050303010203", |
| encodeToHex( |
| new SequenceWithByteBufferBitString( |
| ByteBuffer.wrap(new byte[] {1, 2, 3})))); |
| assertEquals( |
| "30030301ff", |
| encodeToHex( |
| new SequenceWithByteBufferBitString( |
| ByteBuffer.wrap(new byte[] {(byte) 0xff})))); |
| |
| assertEquals( |
| "30020300", |
| encodeToHex( |
| new SequenceWithByteBufferBitString(ByteBuffer.wrap(new byte[0])))); |
| } |
| |
| |
| @Test |
| public void testOid() throws Exception { |
| assertEquals("3003060100", encodeToHex(new SequenceWithOid("0.0"))); |
| assertEquals( |
| "300b06092b0601040182371514", |
| encodeToHex(new SequenceWithOid("1.3.6.1.4.1.311.21.20"))); |
| assertEquals( |
| "300b06092a864886f70d010701", |
| encodeToHex(new SequenceWithOid("1.2.840.113549.1.7.1"))); |
| assertEquals( |
| "300b0609608648016503040201", |
| encodeToHex(new SequenceWithOid("2.16.840.1.101.3.4.2.1"))); |
| } |
| |
| @Test |
| public void testChoice() throws Exception { |
| assertEquals("0201ff", encodeToHex(Choice.of(-1))); |
| assertEquals("80092b0601040182371514", encodeToHex(Choice.of("1.3.6.1.4.1.311.21.20"))); |
| } |
| |
| @Test(expected = Asn1EncodingException.class) |
| public void testChoiceWithNoFieldsSet() throws Exception { |
| // CHOICE is required to have exactly one field set |
| encode(new Choice(null, null)); |
| } |
| |
| @Test(expected = Asn1EncodingException.class) |
| public void testChoiceWithMultipleFieldsSet() throws Exception { |
| // CHOICE is required to have exactly one field set |
| encode(new Choice(123, "1.3.6.1.4.1.311.21.20")); |
| } |
| |
| @Test |
| public void testSetOf() throws Exception { |
| assertEquals("3009310702010a020200ff", encodeToHex(SetOfIntegers.of(0x0a, 0xff))); |
| // Reordering the elements of the set should not make a difference to the resulting encoding |
| assertEquals("3009310702010a020200ff", encodeToHex(SetOfIntegers.of(0xff, 0x0a))); |
| |
| assertEquals( |
| "300e310c02010a020200ff0203112233", |
| encodeToHex(SetOfIntegers.of(0xff, 0x0a, 0x112233))); |
| } |
| |
| @Test |
| public void testSequence() throws Exception { |
| assertEquals( |
| "30080201000601000400", |
| encodeToHex(new Sequence(BigInteger.ZERO, "0.0", new byte[0]))); |
| // Optional OBJECT IDENTIFIER not set |
| assertEquals( |
| "30050201000400", |
| encodeToHex(new Sequence(BigInteger.ZERO, null, new byte[0]))); |
| // Required INTEGER not set |
| try { |
| assertEquals( |
| "30050201000400", |
| encodeToHex(new Sequence(null, "0.0", new byte[0]))); |
| fail(); |
| } catch (Asn1EncodingException expected) {} |
| } |
| |
| @Test |
| public void testAsn1Class() throws Exception { |
| assertEquals( |
| "30053003060100", |
| encodeToHex(new SequenceWithAsn1Class(new SequenceWithOid("0.0")))); |
| } |
| |
| @Test |
| public void testOpaque() throws Exception { |
| assertEquals( |
| "3003060100", |
| encodeToHex(new SequenceWithOpaque( |
| new Asn1OpaqueObject(new byte[] {0x06, 0x01, 0x00})))); |
| } |
| |
| @Test |
| public void testBoolean() throws Exception { |
| assertEquals("3003010100", encodeToHex(new SequenceWithBoolean(false))); |
| String value = encodeToHex(new SequenceWithBoolean(true)); |
| // The encoding of a true value can be any non-zero value so verify the static portion of |
| // the encoding of a sequeuence with a boolean, then verify the last byte is non-zero |
| assertEquals("The encoding of a sequence with a boolean is not the expected length.", 10, |
| value.length()); |
| assertEquals( |
| "The prefix of the encoding of a sequence with a boolean is not the expected " |
| + "value.", |
| "30030101", value.substring(0, 8)); |
| assertNotEquals("The encoding of true should be non-zero.", "00", value.substring(8)); |
| } |
| |
| @Test |
| public void testUTCTime() throws Exception { |
| assertEquals("300d170b313231323231313232315a", |
| encodeToHex(new SequenceWithUTCTime("1212211221Z"))); |
| assertEquals("300d170b393931323331323335395a", |
| encodeToHex(new SequenceWithUTCTime("9912312359Z"))); |
| } |
| |
| @Test |
| public void testGeneralizedTime() throws Exception { |
| assertEquals("301518133230313231323231313232302e3939392d3037", |
| encodeToHex(new SequenceWithGeneralizedTime("201212211220.999-07"))); |
| assertEquals("3017181532303338303131393033313430372e3030302b3030", |
| encodeToHex(new SequenceWithGeneralizedTime("20380119031407.000+00"))); |
| } |
| |
| @Test |
| public void testUnencodedContainer() throws Exception { |
| assertEquals("30233021310b30030201003004020200ff310830060204800000003108300602047fffffff", |
| encodeToHex( |
| new SequenceWithSequenceOfUnencodedContainers( |
| Arrays.asList( |
| new UnencodedContainerWithSetOfIntegers( |
| Arrays.asList( |
| new SequenceWithInteger(0), |
| new SequenceWithInteger(255))), |
| new UnencodedContainerWithSetOfIntegers( |
| Arrays.asList( |
| new SequenceWithInteger( |
| Integer.MIN_VALUE))), |
| new UnencodedContainerWithSetOfIntegers( |
| Arrays.asList( |
| new SequenceWithInteger( |
| Integer.MAX_VALUE))))))); |
| } |
| |
| private static byte[] encode(Object obj) throws Asn1EncodingException { |
| return Asn1DerEncoder.encode(obj); |
| } |
| |
| private static String encodeToHex(Object obj) throws Asn1EncodingException { |
| return HexEncoding.encode(encode(obj)); |
| } |
| |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithInteger { |
| |
| @Asn1Field(index = 1, type = Asn1Type.INTEGER) |
| public int num; |
| |
| public SequenceWithInteger(int num) { |
| this.num = num; |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithOid { |
| |
| @Asn1Field(index = 1, type = Asn1Type.OBJECT_IDENTIFIER) |
| public String oid; |
| |
| public SequenceWithOid(String oid) { |
| this.oid = oid; |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithByteBufferOctetString { |
| |
| @Asn1Field(index = 1, type = Asn1Type.OCTET_STRING) |
| public ByteBuffer data; |
| |
| public SequenceWithByteBufferOctetString(ByteBuffer data) { |
| this.data = data; |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithByteBufferBitString { |
| |
| @Asn1Field(index = 1, type = Asn1Type.BIT_STRING) |
| public ByteBuffer data; |
| |
| public SequenceWithByteBufferBitString(ByteBuffer data) { |
| this.data = data; |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.CHOICE) |
| public static class Choice { |
| |
| @Asn1Field(type = Asn1Type.INTEGER) |
| public Integer num; |
| |
| @Asn1Field(type = Asn1Type.OBJECT_IDENTIFIER, tagging = Asn1Tagging.IMPLICIT, tagNumber = 0) |
| public String oid; |
| |
| public Choice(Integer num, String oid) { |
| this.num = num; |
| this.oid = oid; |
| } |
| |
| public static Choice of(int num) { |
| return new Choice(num, null); |
| } |
| |
| public static Choice of(String oid) { |
| return new Choice(null, oid); |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SetOfIntegers { |
| @Asn1Field(type = Asn1Type.SET_OF, elementType = Asn1Type.INTEGER) |
| public List<Integer> values; |
| |
| public static SetOfIntegers of(Integer... values) { |
| SetOfIntegers result = new SetOfIntegers(); |
| result.values = Arrays.asList(values); |
| return result; |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class Sequence { |
| @Asn1Field(type = Asn1Type.INTEGER, index = 0) |
| public BigInteger num; |
| |
| @Asn1Field(type = Asn1Type.OBJECT_IDENTIFIER, index = 1, optional = true) |
| public String oid; |
| |
| @Asn1Field(type = Asn1Type.OCTET_STRING, index = 2) |
| public byte[] octets; |
| |
| public Sequence(BigInteger num, String oid, byte[] octets) { |
| this.num = num; |
| this.oid = oid; |
| this.octets = octets; |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithAsn1Class { |
| @Asn1Field(type = Asn1Type.SEQUENCE) |
| public SequenceWithOid seqWithOid; |
| |
| public SequenceWithAsn1Class(SequenceWithOid seqWithOid) { |
| this.seqWithOid = seqWithOid; |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithOpaque { |
| @Asn1Field(type = Asn1Type.ANY) |
| public Asn1OpaqueObject obj; |
| |
| public SequenceWithOpaque(Asn1OpaqueObject obj) { |
| this.obj = obj; |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithBoolean { |
| |
| @Asn1Field(index = 1, type = Asn1Type.BOOLEAN) |
| public boolean value; |
| |
| public SequenceWithBoolean(boolean value) { |
| this.value = value; |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithUTCTime { |
| |
| @Asn1Field(index = 1, type = Asn1Type.UTC_TIME) |
| public String utcTime; |
| |
| public SequenceWithUTCTime(String utcTime) { |
| this.utcTime = utcTime; |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithGeneralizedTime { |
| |
| @Asn1Field(index = 1, type = Asn1Type.GENERALIZED_TIME) |
| public String generalizedTime; |
| |
| public SequenceWithGeneralizedTime(String generalizedTime) { |
| this.generalizedTime = generalizedTime; |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public static class SequenceWithSequenceOfUnencodedContainers { |
| @Asn1Field(index = 1, type = Asn1Type.SEQUENCE_OF) |
| public List<UnencodedContainerWithSetOfIntegers> containers; |
| |
| public SequenceWithSequenceOfUnencodedContainers( |
| List<UnencodedContainerWithSetOfIntegers> containers) { |
| this.containers = containers; |
| } |
| } |
| |
| @Asn1Class(type = Asn1Type.UNENCODED_CONTAINER) |
| public static class UnencodedContainerWithSetOfIntegers { |
| @Asn1Field(index = 1, type = Asn1Type.SET_OF) |
| public List<SequenceWithInteger> values; |
| |
| public UnencodedContainerWithSetOfIntegers(List<SequenceWithInteger> values) { |
| this.values = values; |
| } |
| } |
| } |