1 /*
2  * Copyright (C) 2022 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.rkpdapp.unittest;
18 
19 
20 import static com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding.IEEE_P1363;
21 import static com.google.crypto.tink.subtle.Enums.HashType.SHA256;
22 
23 import android.content.Context;
24 import android.net.ConnectivityManager;
25 import android.net.NetworkCapabilities;
26 
27 import com.google.crypto.tink.subtle.EcdsaSignJce;
28 import com.google.crypto.tink.subtle.Ed25519Sign;
29 import com.google.crypto.tink.subtle.EllipticCurves;
30 
31 import org.bouncycastle.asn1.x509.BasicConstraints;
32 import org.bouncycastle.asn1.x509.Extension;
33 import org.bouncycastle.asn1.x509.KeyUsage;
34 import org.bouncycastle.x509.X509V3CertificateGenerator;
35 import org.mockito.Mockito;
36 
37 import java.io.ByteArrayOutputStream;
38 import java.math.BigInteger;
39 import java.security.AlgorithmParameters;
40 import java.security.KeyFactory;
41 import java.security.KeyPair;
42 import java.security.KeyPairGenerator;
43 import java.security.MessageDigest;
44 import java.security.PublicKey;
45 import java.security.cert.X509Certificate;
46 import java.security.interfaces.ECPrivateKey;
47 import java.security.interfaces.ECPublicKey;
48 import java.security.spec.ECGenParameterSpec;
49 import java.security.spec.ECParameterSpec;
50 import java.security.spec.ECPoint;
51 import java.security.spec.ECPublicKeySpec;
52 import java.time.Duration;
53 import java.time.Instant;
54 import java.util.Date;
55 import java.util.List;
56 
57 import javax.security.auth.x500.X500Principal;
58 
59 import co.nstant.in.cbor.CborBuilder;
60 import co.nstant.in.cbor.CborEncoder;
61 import co.nstant.in.cbor.builder.MapBuilder;
62 import co.nstant.in.cbor.model.Array;
63 import co.nstant.in.cbor.model.DataItem;
64 
65 /**
66  * Utility class for unit testing.
67  */
68 public class Utils {
69     private static final int KEY_TYPE = 1;
70     private static final int KEY_TYPE_OKP = 1;
71     private static final int KEY_TYPE_EC2 = 2;
72     private static final int KID = 2;
73     private static final int ALGORITHM = 3;
74     private static final int ALGORITHM_EDDSA = -8;
75     private static final int ALGORITHM_ES256 = -7;
76     private static final int ALGORITHM_ECDH_ES_HKDF_256 = -25;
77     private static final int CURVE = -1;
78     public  static final int CURVE_X25519 = 4;
79     public static final int CURVE_ED25519 = 6;
80     public static final int CURVE_P256 = 1;
81     private static final int X_COORDINATE = -2;
82     private static final int Y_COORDINATE = -3;
83 
getP256PubKeyFromBytes(byte[] xPub, byte[] yPub)84     public static PublicKey getP256PubKeyFromBytes(byte[] xPub, byte[] yPub) throws Exception {
85         BigInteger x = new BigInteger(1, xPub);
86         BigInteger y = new BigInteger(1, yPub);
87         AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
88         parameters.init(new ECGenParameterSpec("secp256r1"));
89         ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class);
90         ECPoint point = new ECPoint(x, y);
91         ECPublicKeySpec keySpec = new ECPublicKeySpec(point, ecParameters);
92         KeyFactory keyFactory = KeyFactory.getInstance("EC");
93         return keyFactory.generatePublic(keySpec);
94     }
95 
getBytesFromP256PrivateKey(ECPrivateKey privateKey)96     public static byte[] getBytesFromP256PrivateKey(ECPrivateKey privateKey) throws Exception {
97         int keySizeBytes = (privateKey.getParams().getOrder().bitLength() + Byte.SIZE - 1)
98                 / Byte.SIZE;
99         final byte[] rawPublicKey = new byte[keySizeBytes];
100 
101         final byte[] priv = privateKey.getS().toByteArray();
102         if (priv.length <= keySizeBytes) {
103             System.arraycopy(priv, 0, rawPublicKey,  keySizeBytes
104                     - priv.length, priv.length);
105         } else if (priv.length == keySizeBytes + 1 && priv[0] == 0) {
106             System.arraycopy(priv, 1, rawPublicKey, 0, keySizeBytes);
107         } else {
108             throw new IllegalStateException("private value is too large");
109         }
110         return rawPublicKey;
111     }
112 
getBytesFromP256PublicKey(ECPublicKey publicKey)113     public static byte[] getBytesFromP256PublicKey(ECPublicKey publicKey) throws Exception {
114         int keySizeBytes =
115                 (publicKey.getParams().getOrder().bitLength() + Byte.SIZE - 1) / Byte.SIZE;
116 
117         final byte[] rawPublicKey = new byte[2 * keySizeBytes];
118         int offset = 0;
119 
120         final byte[] x = publicKey.getW().getAffineX().toByteArray();
121         if (x.length <= keySizeBytes) {
122             System.arraycopy(x, 0, rawPublicKey, offset + keySizeBytes
123                     - x.length, x.length);
124         } else if (x.length == keySizeBytes + 1 && x[0] == 0) {
125             System.arraycopy(x, 1, rawPublicKey, offset, keySizeBytes);
126         } else {
127             throw new IllegalStateException("x value is too large");
128         }
129         offset += keySizeBytes;
130 
131         final byte[] y = publicKey.getW().getAffineY().toByteArray();
132         if (y.length <= keySizeBytes) {
133             System.arraycopy(y, 0, rawPublicKey, offset + keySizeBytes
134                     - y.length, y.length);
135         } else if (y.length == keySizeBytes + 1 && y[0] == 0) {
136             System.arraycopy(y, 1, rawPublicKey, offset, keySizeBytes);
137         } else {
138             throw new IllegalStateException("y value is too large");
139         }
140         return rawPublicKey;
141     }
142 
generateEcdsaKeyPair()143     public static KeyPair generateEcdsaKeyPair() throws Exception {
144         KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
145         ECGenParameterSpec params = new ECGenParameterSpec("secp256r1");
146         generator.initialize(params);
147         return generator.generateKeyPair();
148     }
149 
signPublicKey(KeyPair issuerKeyPair, PublicKey publicKeyToSign)150     public static X509Certificate signPublicKey(KeyPair issuerKeyPair, PublicKey publicKeyToSign)
151             throws Exception {
152         return signPublicKey(issuerKeyPair, publicKeyToSign,
153                 Instant.now().plus(Duration.ofDays(1)));
154     }
155 
signPublicKey(KeyPair issuerKeyPair, PublicKey publicKeyToSign, Instant expirationInstant)156     public static X509Certificate signPublicKey(KeyPair issuerKeyPair, PublicKey publicKeyToSign,
157             Instant expirationInstant) throws Exception {
158         Instant now = Instant.now();
159         return signPublicKey(issuerKeyPair, publicKeyToSign, now, expirationInstant);
160     }
161 
162     /**
163      * Generates a certificate for given key and issuer.
164      */
signPublicKey(KeyPair issuerKeyPair, PublicKey publicKeyToSign, Instant creationInstant, Instant expirationInstant)165     public static X509Certificate signPublicKey(KeyPair issuerKeyPair, PublicKey publicKeyToSign,
166             Instant creationInstant, Instant expirationInstant) throws Exception {
167         X500Principal issuer = new X500Principal("CN=TEE");
168         BigInteger serial = BigInteger.ONE;
169         X500Principal subject = new X500Principal("CN=TEE");
170 
171         X509V3CertificateGenerator certificateBuilder = new X509V3CertificateGenerator();
172         certificateBuilder.setIssuerDN(issuer);
173         certificateBuilder.setSerialNumber(serial);
174         certificateBuilder.setNotBefore(Date.from(creationInstant));
175         certificateBuilder.setNotAfter(Date.from(expirationInstant));
176         certificateBuilder.setSignatureAlgorithm("SHA256WITHECDSA");
177         certificateBuilder.setSubjectDN(subject);
178         certificateBuilder.setPublicKey(publicKeyToSign);
179         certificateBuilder.addExtension(
180                 Extension.basicConstraints, /*isCritical=*/ true, new BasicConstraints(true));
181         certificateBuilder.addExtension(
182                 Extension.keyUsage, /*isCritical=*/ true, new KeyUsage(KeyUsage.keyCertSign));
183         return certificateBuilder.generate(issuerKeyPair.getPrivate());
184     }
185 
encodeAndSignSign1Ed25519(byte[] encodedPublicKey, byte[] privateKey)186     public static Array encodeAndSignSign1Ed25519(byte[] encodedPublicKey, byte[] privateKey)
187             throws Exception {
188         byte[] encodedProtectedHeaders = encodeSimpleMap(1, -8);
189         return (Array) (new CborBuilder()
190             .addArray()
191                 .add(encodedProtectedHeaders)      // Protected headers
192                 .addMap()                          // Empty unprotected Headers
193                     .end()
194                 .add(encodedPublicKey)
195                 .add(encodeAndSignSigStructure(
196                         encodedProtectedHeaders, encodedPublicKey, privateKey, CURVE_ED25519))
197             .end()
198             .build().get(0));
199     }
200 
encodeAndSignSign1Ecdsa256(byte[] encodedPublicKey, byte[] privateKey)201     public static Array encodeAndSignSign1Ecdsa256(byte[] encodedPublicKey, byte[] privateKey)
202             throws Exception {
203         byte[] encodedProtectedHeaders = encodeSimpleMap(1, -7);
204         return (Array) (new CborBuilder()
205             .addArray()
206                 .add(encodedProtectedHeaders)      // Protected headers
207                 .addMap()                          // Empty unprotected Headers
208                     .end()
209                 .add(encodedPublicKey)
210                 .add(encodeAndSignSigStructure(
211                         encodedProtectedHeaders, encodedPublicKey, privateKey, CURVE_P256))
212             .end()
213             .build().get(0));
214     }
215 
encodeAndSignSigStructure( byte[] protectedHeaders, byte[] payload, byte[] privateKey, int curve)216     private static byte[] encodeAndSignSigStructure(
217             byte[] protectedHeaders, byte[] payload, byte[] privateKey,
218             int curve) throws Exception {
219         return encodeAndSignSigStructure(protectedHeaders, null, payload,
220                 privateKey, curve);
221     }
222 
encodeAndSignSigStructure(byte[] protectedHeaders, byte[] externalAad, byte[] payload, byte[] privateKey, int curve)223     private static byte[] encodeAndSignSigStructure(byte[] protectedHeaders, byte[] externalAad,
224             byte[] payload, byte[] privateKey, int curve)
225             throws Exception {
226         ByteArrayOutputStream baos = new ByteArrayOutputStream();
227         new CborEncoder(baos).encode(new CborBuilder()
228                 .addArray()
229                 .add("Signature1")                                      // context string
230                 .add(protectedHeaders)                                  // protected headers
231                 .add(null == externalAad ? new byte[0] : externalAad)   // external aad
232                 .add(payload)                                           // payload
233                 .end()
234                 .build());
235         if (curve == CURVE_ED25519) {
236             Ed25519Sign signer = new Ed25519Sign(privateKey);
237             return signer.sign(baos.toByteArray());
238         } else {
239             ECPrivateKey privKey = EllipticCurves.getEcPrivateKey(
240                     EllipticCurves.CurveType.NIST_P256, privateKey);
241             EcdsaSignJce ecdsaSigner = new EcdsaSignJce(privKey, SHA256, IEEE_P1363);
242             return ecdsaSigner.sign(baos.toByteArray());
243         }
244     }
245 
encodeEd25519PubKey(byte[] publicKey)246     public static byte[] encodeEd25519PubKey(byte[] publicKey) throws Exception {
247         ByteArrayOutputStream baos = new ByteArrayOutputStream();
248         new CborEncoder(baos).encode(new CborBuilder()
249                 .addMap()
250                     .put(KEY_TYPE, KEY_TYPE_OKP)
251                     .put(ALGORITHM, ALGORITHM_EDDSA)
252                     .put(CURVE, CURVE_ED25519)
253                     .put(X_COORDINATE, publicKey)
254                     .end()
255                 .build());
256         return baos.toByteArray();
257     }
258 
encodeP256PubKey(byte[] pubX, byte[] pubY, boolean isEek)259     public static byte[] encodeP256PubKey(byte[] pubX, byte[] pubY, boolean isEek)
260             throws Exception {
261         ByteArrayOutputStream baos = new ByteArrayOutputStream();
262         MapBuilder<CborBuilder> cborBuilder = new CborBuilder()
263                 .addMap()
264                     .put(KEY_TYPE, KEY_TYPE_EC2)
265                     .put(ALGORITHM, isEek ? ALGORITHM_ECDH_ES_HKDF_256 : ALGORITHM_ES256)
266                     .put(CURVE, CURVE_P256)
267                     .put(X_COORDINATE, pubX)
268                     .put(Y_COORDINATE, pubY);
269         List<DataItem> coseKey;
270         if (isEek) {
271             MessageDigest digest = MessageDigest.getInstance("SHA-256");
272             digest.update(pubX);
273             byte[] kid = digest.digest(pubY);
274             coseKey = cborBuilder.put(KID, kid).end().build();
275         } else {
276             coseKey = cborBuilder.end().build();
277         }
278         new CborEncoder(baos).encode(coseKey);
279         return baos.toByteArray();
280     }
281 
282 
encodeX25519PubKey(byte[] publicKey)283     public static byte[] encodeX25519PubKey(byte[] publicKey) throws Exception {
284         ByteArrayOutputStream baos = new ByteArrayOutputStream();
285         MessageDigest digest = MessageDigest.getInstance("SHA-256");
286         byte[] kid = digest.digest(publicKey);
287         new CborEncoder(baos).encode(new CborBuilder()
288                 .addMap()
289                     .put(KEY_TYPE, KEY_TYPE_OKP)
290                     .put(KID, kid)
291                     .put(ALGORITHM, ALGORITHM_ECDH_ES_HKDF_256)
292                     .put(CURVE, CURVE_X25519)
293                     .put(X_COORDINATE, publicKey)
294                     .end()
295                 .build());
296         return baos.toByteArray();
297     }
298 
encodeSimpleMap(int key, int value)299     private static byte[] encodeSimpleMap(int key, int value) throws Exception {
300         ByteArrayOutputStream baos = new ByteArrayOutputStream();
301         new CborEncoder(baos).encode(new CborBuilder()
302                 .addMap()
303                     .put(key, value)
304                     .end()
305                 .build());
306         return baos.toByteArray();
307     }
308 
309     /**
310      * Mocks out the connectivity status for unit tests.
311      */
mockConnectivityState(Context context, ConnectivityState state)312     public static void mockConnectivityState(Context context, ConnectivityState state) {
313         ConnectivityManager mockedConnectivityManager = Mockito.mock(ConnectivityManager.class);
314 
315         Mockito.when(context.getSystemService(ConnectivityManager.class))
316                 .thenReturn(mockedConnectivityManager);
317         NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
318         builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
319         if (state == ConnectivityState.CONNECTED) {
320             builder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
321         }
322         Mockito.when(mockedConnectivityManager.getNetworkCapabilities(Mockito.any()))
323                 .thenReturn(builder.build());
324     }
325 
326     public enum ConnectivityState {
327         DISCONNECTED,
328         CONNECTED
329     }
330 }
331