blob: 8d1674608a5034696792681ac4e196f27615d013 [file] [log] [blame]
/**
* Copyright (C) 2020 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;
import android.os.Build;
import android.util.Log;
import co.nstant.in.cbor.CborBuilder;
import co.nstant.in.cbor.CborDecoder;
import co.nstant.in.cbor.CborEncoder;
import co.nstant.in.cbor.CborException;
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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class CborUtils {
private static final int RESPONSE_CERT_ARRAY_INDEX = 0;
private static final int RESPONSE_ARRAY_SIZE = 1;
private static final int SHARED_CERTIFICATES_INDEX = 0;
private static final int UNIQUE_CERTIFICATES_INDEX = 1;
private static final int CERT_ARRAY_ENTRIES = 2;
private static final int EEK_INDEX = 0;
private static final int CHALLENGE_INDEX = 1;
private static final int EEK_ARRAY_ENTRIES = 2;
private static final String TAG = "RemoteProvisioningService";
/**
* Parses the signed certificate chains returned by the server. In order to reduce data use over
* the wire, shared certificate chain prefixes are separated from the remaining unique portions
* of each individual certificate chain. This method first parses the shared prefix certificates
* and then prepends them to each unique certificate chain. Each PEM-encoded certificate chain
* is returned in a byte array.
*
* @param serverResp The CBOR blob received from the server which contains all signed
* certificate chains.
*
* @return A List object where each byte[] entry is an entire DER-encoded certificate chain.
*/
public static List<byte[]> parseSignedCertificates(byte[] serverResp) {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(serverResp);
List<DataItem> dataItems = new CborDecoder(bais).decode();
if (dataItems.size() != RESPONSE_ARRAY_SIZE
|| dataItems.get(RESPONSE_CERT_ARRAY_INDEX).getMajorType() != MajorType.ARRAY) {
Log.e(TAG, "Improper formatting of CBOR response. Expected size 1. Actual: "
+ dataItems.size() + "\nExpected major type: Array. Actual: "
+ dataItems.get(0).getMajorType().name());
return null;
}
dataItems = ((Array) dataItems.get(RESPONSE_CERT_ARRAY_INDEX)).getDataItems();
if (dataItems.size() != CERT_ARRAY_ENTRIES) {
Log.e(TAG, "Incorrect number of certificate array entries. Expected: 2. Actual: "
+ dataItems.size());
return null;
}
if (dataItems.get(SHARED_CERTIFICATES_INDEX).getMajorType() != MajorType.BYTE_STRING
|| dataItems.get(UNIQUE_CERTIFICATES_INDEX).getMajorType() != MajorType.ARRAY) {
Log.e(TAG, "Incorrect CBOR types. Expected 'Byte String' and 'Array'. Got: "
+ dataItems.get(SHARED_CERTIFICATES_INDEX).getMajorType().name()
+ " and "
+ dataItems.get(UNIQUE_CERTIFICATES_INDEX).getMajorType().name());
return null;
}
byte[] sharedCertificates =
((ByteString) dataItems.get(SHARED_CERTIFICATES_INDEX)).getBytes();
Array uniqueCertificates = (Array) dataItems.get(UNIQUE_CERTIFICATES_INDEX);
List<byte[]> uniqueCertificateChains = new ArrayList<byte[]>();
for (DataItem entry : uniqueCertificates.getDataItems()) {
if (entry.getMajorType() != MajorType.BYTE_STRING) {
Log.e(TAG, "Incorrect CBOR type. Expected: 'Byte String'. Actual:"
+ entry.getMajorType().name());
return null;
}
ByteArrayOutputStream concat = new ByteArrayOutputStream();
// DER encoding specifies certificate chains ordered from leaf to root.
concat.write(((ByteString) entry).getBytes());
concat.write(sharedCertificates);
uniqueCertificateChains.add(concat.toByteArray());
}
return uniqueCertificateChains;
} catch (CborException e) {
Log.e(TAG, "CBOR decoding failed.", e);
} catch (IOException e) {
Log.e(TAG, "Writing bytes failed.", e);
}
return null;
}
/**
* Parses the Google Endpoint Encryption Key response provided by the server which contains a
* Google signed EEK and a challenge for use by the underlying IRemotelyProvisionedComponent HAL
*/
public static GeekResponse parseGeekResponse(byte[] serverResp) {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(serverResp);
List<DataItem> dataItems = new CborDecoder(bais).decode();
if (dataItems.size() != RESPONSE_ARRAY_SIZE
|| dataItems.get(RESPONSE_CERT_ARRAY_INDEX).getMajorType() != MajorType.ARRAY) {
Log.e(TAG, "Improper formatting of CBOR response. Expected size 1. Actual: "
+ dataItems.size() + "\nExpected major type: Array. Actual: "
+ dataItems.get(0).getMajorType().name());
return null;
}
dataItems = ((Array) dataItems.get(RESPONSE_CERT_ARRAY_INDEX)).getDataItems();
if (dataItems.size() != EEK_ARRAY_ENTRIES) {
Log.e(TAG, "Incorrect number of certificate array entries. Expected: 2. Actual: "
+ dataItems.size());
return null;
}
if (dataItems.get(EEK_INDEX).getMajorType() != MajorType.ARRAY
|| dataItems.get(CHALLENGE_INDEX).getMajorType() != MajorType.BYTE_STRING) {
Log.e(TAG, "Incorrect CBOR types. Expected 'Array' and 'Byte String'. Got: "
+ dataItems.get(EEK_INDEX).getMajorType().name()
+ " and "
+ dataItems.get(CHALLENGE_INDEX).getMajorType().name());
return null;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(dataItems.get(EEK_INDEX));
return new GeekResponse(baos.toByteArray(),
((ByteString) dataItems.get(CHALLENGE_INDEX)).getBytes());
} catch (CborException e) {
Log.e(TAG, "CBOR parsing/serializing failed.", e);
return null;
}
}
/**
* Gathers information from system properties to populate and serialize a CBOR Map to be
* sent to the server.
*/
public static byte[] getDeviceInfo() {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
new CborEncoder(os).encode(new CborBuilder()
.addMap()
.put("brand", Build.BRAND.getBytes(StandardCharsets.UTF_8))
.put("manufacturer", Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8))
.put("product", Build.PRODUCT.getBytes(StandardCharsets.UTF_8))
.put("model", Build.MODEL.getBytes(StandardCharsets.UTF_8))
.put("board", Build.BOARD.getBytes(StandardCharsets.UTF_8))
.put("vb_state", System.getProperty("ro.boot.verifiedbootstate"))
.put("bootloader_state", System.getProperty("ro.boot.vbmeta.device_state"))
.end()
.build());
return os.toByteArray();
} catch (CborException e) {
Log.e(TAG, "Failed to seerialize DeviceInfo", e);
return null;
}
}
}