1 /** 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.remoteprovisioner; 18 19 import android.util.Log; 20 21 import java.io.ByteArrayInputStream; 22 import java.math.BigInteger; 23 import java.security.PublicKey; 24 import java.security.cert.Certificate; 25 import java.security.cert.CertificateException; 26 import java.security.cert.CertificateFactory; 27 import java.security.cert.X509Certificate; 28 import java.security.interfaces.ECPublicKey; 29 import java.util.ArrayList; 30 31 /** 32 * Provides convenience methods for parsing certificates and extracting information. 33 */ 34 public class X509Utils { 35 36 private static final String TAG = "RemoteProvisionerX509Utils"; 37 38 /** 39 * Takes a byte array composed of DER encoded certificates and returns the X.509 certificates 40 * contained within as an X509Certificate array. 41 */ formatX509Certs(byte[] certStream)42 public static X509Certificate[] formatX509Certs(byte[] certStream) 43 throws CertificateException { 44 CertificateFactory fact = CertificateFactory.getInstance("X.509"); 45 ByteArrayInputStream in = new ByteArrayInputStream(certStream); 46 ArrayList<Certificate> certs = new ArrayList<Certificate>(fact.generateCertificates(in)); 47 return certs.toArray(new X509Certificate[certs.size()]); 48 } 49 50 /** 51 * Extracts an ECDSA-P256 key from a certificate and formats it so that it can be used to match 52 * the certificate chain to the proper key when passed into the keystore database. 53 */ getAndFormatRawPublicKey(X509Certificate cert)54 public static byte[] getAndFormatRawPublicKey(X509Certificate cert) { 55 PublicKey pubKey = cert.getPublicKey(); 56 if (!(pubKey instanceof ECPublicKey)) { 57 Log.e(TAG, "Certificate public key is not an instance of ECPublicKey"); 58 return null; 59 } 60 ECPublicKey key = (ECPublicKey) cert.getPublicKey(); 61 // Remote key provisioning internally supports the default, uncompressed public key 62 // format for ECDSA. This defines the format as (s | x | y), where s is the byte 63 // indicating if the key is compressed or not, and x and y make up the EC point. 64 // However, the key as stored in a COSE_Key object is just the two points. As such, 65 // the raw public key is stored in the database as (x | y), so the compression byte 66 // should be dropped here. Leading 0's must be preserved. 67 // 68 // s: 1 byte, x: 32 bytes, y: 32 bytes 69 BigInteger xCoord = key.getW().getAffineX(); 70 BigInteger yCoord = key.getW().getAffineY(); 71 byte[] formattedKey = new byte[64]; 72 byte[] xBytes = xCoord.toByteArray(); 73 // BigInteger returns the value as two's complement big endian byte encoding. This means 74 // that a positive, 32-byte value with a leading 1 bit will be converted to a byte array of 75 // length 33 in order to include a leading 0 bit. 76 if (xBytes.length == 33) { 77 System.arraycopy(xBytes, 1 /* offset */, formattedKey, 0 /* offset */, 32); 78 } else { 79 System.arraycopy(xBytes, 0 /* offset */, 80 formattedKey, 32 - xBytes.length, xBytes.length); 81 } 82 byte[] yBytes = yCoord.toByteArray(); 83 if (yBytes.length == 33) { 84 System.arraycopy(yBytes, 1 /* offset */, formattedKey, 32 /* offset */, 32); 85 } else { 86 System.arraycopy(yBytes, 0 /* offset */, 87 formattedKey, 64 - yBytes.length, yBytes.length); 88 } 89 return formattedKey; 90 } 91 } 92