| /* |
| * Copyright (C) 2021 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.remoteprovisioner.unittest; |
| |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNull; |
| |
| import android.platform.test.annotations.Presubmit; |
| |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.remoteprovisioner.CborUtils; |
| import com.android.remoteprovisioner.GeekResponse; |
| |
| import co.nstant.in.cbor.CborBuilder; |
| import co.nstant.in.cbor.CborDecoder; |
| import co.nstant.in.cbor.CborEncoder; |
| import co.nstant.in.cbor.model.Array; |
| import co.nstant.in.cbor.model.ByteString; |
| import co.nstant.in.cbor.model.DataItem; |
| import co.nstant.in.cbor.model.MajorType; |
| import co.nstant.in.cbor.model.Map; |
| import co.nstant.in.cbor.model.UnicodeString; |
| import co.nstant.in.cbor.model.UnsignedInteger; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| @RunWith(AndroidJUnit4.class) |
| public class CborUtilsTest { |
| |
| private ByteArrayOutputStream mBaos; |
| private Array mGeekChain1; |
| private byte[] mEncodedmGeekChain1; |
| private Array mGeekChain2; |
| private byte[] mEncodedGeekChain2; |
| private Map mDeviceConfig; |
| private static final byte[] CHALLENGE = new byte[]{0x0a, 0x0b, 0x0c}; |
| private static final int TEST_EXTRA_KEYS = 18; |
| private static final int TEST_TIME_TO_REFRESH_HOURS = 42; |
| private static final String TEST_URL = "https://www.wonderifthisisvalid.combutjustincase"; |
| |
| private byte[] encodeDataItem(DataItem toEncode) throws Exception { |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .add(toEncode) |
| .build()); |
| byte[] encoded = mBaos.toByteArray(); |
| mBaos.reset(); |
| return encoded; |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| mBaos = new ByteArrayOutputStream(); |
| |
| mGeekChain1 = new Array(); |
| mGeekChain1.add(new ByteString(new byte[] {0x01, 0x02, 0x03})) |
| .add(new ByteString(new byte[] {0x04, 0x05, 0x06})) |
| .add(new ByteString(new byte[] {0x07, 0x08, 0x09})); |
| mEncodedmGeekChain1 = encodeDataItem(mGeekChain1); |
| |
| mGeekChain2 = new Array(); |
| mGeekChain2.add(new ByteString(new byte[] {0x09, 0x08, 0x07})) |
| .add(new ByteString(new byte[] {0x06, 0x05, 0x04})) |
| .add(new ByteString(new byte[] {0x03, 0x02, 0x01})); |
| mEncodedGeekChain2 = encodeDataItem(mGeekChain2); |
| |
| mDeviceConfig = new Map(); |
| mDeviceConfig.put(new UnicodeString(CborUtils.EXTRA_KEYS), |
| new UnsignedInteger(TEST_EXTRA_KEYS)) |
| .put(new UnicodeString(CborUtils.TIME_TO_REFRESH), |
| new UnsignedInteger(TEST_TIME_TO_REFRESH_HOURS)) |
| .put(new UnicodeString(CborUtils.PROVISIONING_URL), |
| new UnicodeString(TEST_URL)); |
| } |
| |
| @Presubmit |
| @Test |
| public void testParseSignedCertificatesFakeData() throws Exception { |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .add(new byte[] {0x01, 0x02, 0x03}) |
| .addArray() |
| .add(new byte[] {0x04, 0x05, 0x06}) |
| .add(new byte[] {0x07, 0x08, 0x09}) |
| .end() |
| .end() |
| .build()); |
| byte[] encodedBytes = mBaos.toByteArray(); |
| ArrayList<byte[]> certChains = |
| new ArrayList<byte[]>(CborUtils.parseSignedCertificates(encodedBytes)); |
| assertArrayEquals(new byte[] {0x04, 0x05, 0x06, 0x01, 0x02, 0x03}, certChains.get(0)); |
| assertArrayEquals(new byte[] {0x07, 0x08, 0x09, 0x01, 0x02, 0x03}, certChains.get(1)); |
| } |
| |
| @Test |
| public void testParseSignedCertificatesWrongSize() throws Exception { |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .add(1) |
| .end() |
| .build()); |
| assertNull(CborUtils.parseSignedCertificates(mBaos.toByteArray())); |
| } |
| |
| @Test |
| public void testParseSignedCertificatesWrongTypeSharedCerts() throws Exception { |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .add("Should be a bstr") |
| .addArray() |
| .add(new byte[] {0x04, 0x05, 0x06}) |
| .add(new byte[] {0x07, 0x08, 0x09}) |
| .end() |
| .end() |
| .build()); |
| assertNull(CborUtils.parseSignedCertificates(mBaos.toByteArray())); |
| } |
| |
| @Test |
| public void testParseSignedCertificatesWrongTypeUniqueCerts() throws Exception { |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .add(new byte[] {0x01, 0x02, 0x03}) |
| .addArray() |
| .add(new byte[] {0x04, 0x05, 0x06}) |
| .add("Every entry should be a bstr") |
| .add(new byte[] {0x07, 0x08, 0x09}) |
| .end() |
| .end() |
| .build()); |
| assertNull(CborUtils.parseSignedCertificates(mBaos.toByteArray())); |
| } |
| |
| @Presubmit |
| @Test |
| public void testParseGeekResponseFakeData() throws Exception { |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .addArray() // GEEK Curve to Chains |
| .addArray() |
| .add(new UnsignedInteger(CborUtils.EC_CURVE_25519)) |
| .add(mGeekChain1) |
| .end() |
| .addArray() |
| .add(new UnsignedInteger(CborUtils.EC_CURVE_P256)) |
| .add(mGeekChain2) |
| .end() |
| .end() |
| .add(CHALLENGE) |
| .add(mDeviceConfig) |
| .end() |
| .build()); |
| GeekResponse resp = CborUtils.parseGeekResponse(mBaos.toByteArray()); |
| mBaos.reset(); |
| assertArrayEquals(mEncodedmGeekChain1, resp.getGeekChain(CborUtils.EC_CURVE_25519)); |
| assertArrayEquals(mEncodedGeekChain2, resp.getGeekChain(CborUtils.EC_CURVE_P256)); |
| assertArrayEquals(CHALLENGE, resp.getChallenge()); |
| assertEquals(TEST_EXTRA_KEYS, resp.numExtraAttestationKeys); |
| assertEquals(TEST_TIME_TO_REFRESH_HOURS, resp.timeToRefresh.toHours()); |
| assertEquals(TEST_URL, resp.provisioningUrl); |
| } |
| |
| @Test |
| public void testExtraDeviceConfigEntriesDontFail() throws Exception { |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .addArray() // GEEK Curve to Chains |
| .addArray() |
| .add(new UnsignedInteger(CborUtils.EC_CURVE_25519)) |
| .add(mGeekChain1) |
| .end() |
| .addArray() |
| .add(new UnsignedInteger(CborUtils.EC_CURVE_P256)) |
| .add(mGeekChain2) |
| .end() |
| .end() |
| .add(CHALLENGE) |
| .add(mDeviceConfig.put(new UnicodeString("new_field"), |
| new UnsignedInteger(84))) |
| .end() |
| .build()); |
| GeekResponse resp = CborUtils.parseGeekResponse(mBaos.toByteArray()); |
| mBaos.reset(); |
| assertArrayEquals(mEncodedmGeekChain1, resp.getGeekChain(CborUtils.EC_CURVE_25519)); |
| assertArrayEquals(mEncodedGeekChain2, resp.getGeekChain(CborUtils.EC_CURVE_P256)); |
| assertArrayEquals(CHALLENGE, resp.getChallenge()); |
| assertEquals(TEST_EXTRA_KEYS, resp.numExtraAttestationKeys); |
| assertEquals(TEST_TIME_TO_REFRESH_HOURS, resp.timeToRefresh.toHours()); |
| assertEquals(TEST_URL, resp.provisioningUrl); |
| } |
| |
| @Test |
| public void testMissingDeviceConfigDoesntFail() throws Exception { |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .addArray() // GEEK Curve to Chains |
| .addArray() |
| .add(new UnsignedInteger(CborUtils.EC_CURVE_25519)) |
| .add(mGeekChain1) |
| .end() |
| .addArray() |
| .add(new UnsignedInteger(CborUtils.EC_CURVE_P256)) |
| .add(mGeekChain2) |
| .end() |
| .end() |
| .add(CHALLENGE) |
| .end() |
| .build()); |
| GeekResponse resp = CborUtils.parseGeekResponse(mBaos.toByteArray()); |
| mBaos.reset(); |
| assertArrayEquals(mEncodedmGeekChain1, resp.getGeekChain(CborUtils.EC_CURVE_25519)); |
| assertArrayEquals(mEncodedGeekChain2, resp.getGeekChain(CborUtils.EC_CURVE_P256)); |
| assertArrayEquals(CHALLENGE, resp.getChallenge()); |
| assertEquals(GeekResponse.NO_EXTRA_KEY_UPDATE, resp.numExtraAttestationKeys); |
| assertEquals(null, resp.timeToRefresh); |
| assertEquals(null, resp.provisioningUrl); |
| } |
| |
| @Test |
| public void testMissingDeviceConfigEntriesDoesntFail() throws Exception { |
| mDeviceConfig.remove(new UnicodeString(CborUtils.TIME_TO_REFRESH)); |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .addArray() // GEEK Curve to Chains |
| .addArray() |
| .add(new UnsignedInteger(CborUtils.EC_CURVE_25519)) |
| .add(mGeekChain1) |
| .end() |
| .addArray() |
| .add(new UnsignedInteger(CborUtils.EC_CURVE_P256)) |
| .add(mGeekChain2) |
| .end() |
| .end() |
| .add(CHALLENGE) |
| .add(mDeviceConfig) |
| .end() |
| .build()); |
| GeekResponse resp = CborUtils.parseGeekResponse(mBaos.toByteArray()); |
| mBaos.reset(); |
| assertArrayEquals(mEncodedmGeekChain1, resp.getGeekChain(CborUtils.EC_CURVE_25519)); |
| assertArrayEquals(mEncodedGeekChain2, resp.getGeekChain(CborUtils.EC_CURVE_P256)); |
| assertArrayEquals(CHALLENGE, resp.getChallenge()); |
| assertEquals(TEST_EXTRA_KEYS, resp.numExtraAttestationKeys); |
| assertEquals(null, resp.timeToRefresh); |
| assertEquals(TEST_URL, resp.provisioningUrl); |
| } |
| |
| @Test |
| public void testParseGeekResponseFailsOnWrongType() throws Exception { |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .addArray() |
| .addArray() |
| .add("String instead of curve enum") |
| .add(mGeekChain1) |
| .end() |
| .end() |
| .add(CHALLENGE) |
| .add(mDeviceConfig) |
| .end() |
| .build()); |
| assertNull(CborUtils.parseGeekResponse(mBaos.toByteArray())); |
| mBaos.reset(); |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .addArray() |
| .addArray() |
| .add(new UnsignedInteger(CborUtils.EC_CURVE_25519)) |
| .add(new ByteString(CHALLENGE)) // Must be an array of bstrs |
| .end() |
| .end() |
| .add(CHALLENGE) |
| .add(mDeviceConfig) |
| .end() |
| .build()); |
| assertNull(CborUtils.parseGeekResponse(mBaos.toByteArray())); |
| mBaos.reset(); |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .addArray() |
| .addArray() |
| .add(new UnsignedInteger(CborUtils.EC_CURVE_25519)) |
| .add(mGeekChain1) |
| .end() |
| .end() |
| .add(new UnicodeString("tstr instead of bstr")) |
| .add(mDeviceConfig) |
| .end() |
| .build()); |
| assertNull(CborUtils.parseGeekResponse(mBaos.toByteArray())); |
| mBaos.reset(); |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .addArray() |
| .addArray() |
| .add(new UnsignedInteger(CborUtils.EC_CURVE_25519)) |
| .add(mGeekChain1) |
| .end() |
| .end() |
| .add(CHALLENGE) |
| .add(CHALLENGE) |
| .end() |
| .build()); |
| assertNull(CborUtils.parseGeekResponse(mBaos.toByteArray())); |
| } |
| |
| @Test |
| public void testParseGeekResponseWrongSize() throws Exception { |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .add("one entry") |
| .add("two entries") |
| .add("three entries") |
| .add("whoops") |
| .end() |
| .build()); |
| assertNull(CborUtils.parseGeekResponse(mBaos.toByteArray())); |
| } |
| |
| @Test |
| public void testCreateCertificateRequest() throws Exception { |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addMap() |
| .put("a", "b") |
| .put("cool", "yeah") |
| .put("testing", "123") |
| .put("str", "str") |
| .end() |
| .build()); |
| byte[] deviceInfo = mBaos.toByteArray(); |
| mBaos.reset(); |
| byte[] challenge = new byte[] {0x01, 0x02, 0x03}; |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .add("protected header") |
| .add("unprotected header") |
| .add("super secret payload") |
| .add("super not secret recipient") |
| .end() |
| .build()); |
| byte[] protectedDataPayload = mBaos.toByteArray(); |
| mBaos.reset(); |
| new CborEncoder(mBaos).encode(new CborBuilder() |
| .addArray() |
| .add("protected header") |
| .add("unprotected header") |
| .add("super not secret payload") |
| .add("mac tag") |
| .end() |
| .build()); |
| byte[] macedKeysToSign = mBaos.toByteArray(); |
| byte[] certReq = |
| CborUtils.buildCertificateRequest(deviceInfo, |
| challenge, |
| protectedDataPayload, |
| macedKeysToSign); |
| ByteArrayInputStream bais = new ByteArrayInputStream(certReq); |
| List<DataItem> dataItems = new CborDecoder(bais).decode(); |
| assertEquals(1, dataItems.size()); |
| assertEquals(MajorType.ARRAY, dataItems.get(0).getMajorType()); |
| dataItems = ((Array) dataItems.get(0)).getDataItems(); |
| assertEquals(4, dataItems.size()); |
| // Array: DeviceInfo |
| // Map: VerifiedDeviceInfo |
| // Map: UnverifiedDeviceInfo |
| assertEquals(MajorType.ARRAY, dataItems.get(0).getMajorType()); |
| assertEquals(MajorType.MAP, |
| ((Array) dataItems.get(0)).getDataItems().get(0).getMajorType()); |
| assertEquals(MajorType.MAP, |
| ((Array) dataItems.get(0)).getDataItems().get(1).getMajorType()); |
| // Challenge |
| assertEquals(MajorType.BYTE_STRING, dataItems.get(1).getMajorType()); |
| // ProtectedData |
| assertEquals(MajorType.ARRAY, dataItems.get(2).getMajorType()); |
| // MacedKeysToSign |
| assertEquals(MajorType.ARRAY, dataItems.get(3).getMajorType()); |
| } |
| } |