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