Signing EEK chain properly in tests.

This change is required in order to enforce signature checking of the
EEK certificate chain, regardless of if test mode is on or not. Test
mode is only supposed to be relevant for if KeyMint should check the
root certificate of the chain against the root of trust it has
provisioned.

This adds a utility to properly sign the certificate chain and makes the
appropriate changes. It also ups the number of keys generated in the key
provisioning test to avoid framework induced conflicts when the KeyStore
frameworks code would otherwise attempt to generate production
keys/certs when it noticed keys were running low.

Bug: 190942528
Test: atest RemoteProvisionerUnitTests
Change-Id: I89b1b41eb2290a4bdbad63b4d6c4faec7d281653
diff --git a/tests/unittests/src/com/android/remoteprovisioner/unittest/SystemInterfaceTest.java b/tests/unittests/src/com/android/remoteprovisioner/unittest/SystemInterfaceTest.java
index 5d0f7aa..2a2adc4 100644
--- a/tests/unittests/src/com/android/remoteprovisioner/unittest/SystemInterfaceTest.java
+++ b/tests/unittests/src/com/android/remoteprovisioner/unittest/SystemInterfaceTest.java
@@ -27,7 +27,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import android.hardware.security.keymint.DeviceInfo;
 import android.hardware.security.keymint.ProtectedData;
@@ -49,7 +48,6 @@
 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;
@@ -68,11 +66,10 @@
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.KeyStore;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
 
@@ -92,6 +89,7 @@
         mBinder =
               IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE));
         assertNotNull(mBinder);
+        mBinder.deleteAllKeys();
     }
 
     @After
@@ -99,82 +97,16 @@
         mBinder.deleteAllKeys();
     }
 
-    private static byte[] serializeProtectedHeaders() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addMap()
-                    .put(1, -8) // Algorithm, EdDSA
-                    .end()
-                .build());
-        return baos.toByteArray();
-    }
-
-    private static byte[] buildSignatureKey() throws CborException {
-        byte[] key = new byte[32];
-        new Random().nextBytes(key);
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addMap()
-                    .put(1, 1) // Key type, OKP
-                    .put(3, -8) // Algorithm, EdDSA
-                    .put(-1, 6) // Curve, Ed25519
-                    .put(-2, key) // public key, bytes
-                    .end()
-                .build());
-        return baos.toByteArray();
-    }
-
-    private static Array buildSignedSignatureKey() throws CborException {
-        return (Array) (new CborBuilder()
-                .addArray()
-                    .add(serializeProtectedHeaders())
-                    .addMap()
-                        .end()
-                    .add(buildSignatureKey())
-                    //signature; test mode will instruct the HAL component to skip verification
-                    .add(new byte[0])
-                    .end()
-                .build().get(0));
-    }
-
-    private static byte[] buildEek(byte[] eek) throws CborException {
-        try {
-            MessageDigest digest = MessageDigest.getInstance("SHA-256");
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            new CborEncoder(baos).encode(new CborBuilder()
-                    .addMap()
-                        .put(1, 1) // Key type, OKP
-                        .put(2, digest.digest(eek)) // KID: EEK ID
-                        .put(3, -25) // Algorithm
-                        .put(-1, 4) // Curve, X25519
-                        .put(-2, eek) // public key, bytes
-                        .end()
-                    .build());
-            return baos.toByteArray();
-        } catch (NoSuchAlgorithmException e) {
-            fail("SHA-256 somehow not available");
-            return null;
-        }
-    }
-
-    private static Array buildSignedEek(byte[] eek) throws CborException {
-        return (Array) (new CborBuilder()
-                .addArray()
-                    .add(serializeProtectedHeaders())
-                    .addMap()
-                        .end()
-                    .add(buildEek(eek))
-                    .add(new byte[0]) //signature; test mode skips this
-                    .end()
-                .build().get(0));
-    }
-
-    private byte[] generateEekChain(byte[] eek) throws CborException {
+    private byte[] generateEekChain(byte[] eek) throws Exception {
+        com.google.crypto.tink.subtle.Ed25519Sign.KeyPair kp =
+                com.google.crypto.tink.subtle.Ed25519Sign.KeyPair.newKeyPair();
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         new CborEncoder(baos).encode(new CborBuilder()
                 .addArray()
-                    .add(buildSignedSignatureKey())
-                    .add(buildSignedEek(eek))
+                    .add(Utils.encodeAndSignSign1Ed25519(
+                            Utils.encodeEd25519PubKey(kp.getPublicKey()), kp.getPrivateKey()))
+                    .add(Utils.encodeAndSignSign1Ed25519(
+                            Utils.encodeX25519PubKey(eek), kp.getPrivateKey()))
                     .end()
                 .build());
         return baos.toByteArray();
@@ -221,11 +153,13 @@
     public void testGenerateCSRProvisionAndUseKey() throws Exception {
         DeviceInfo deviceInfo = new DeviceInfo();
         ProtectedData encryptedBundle = new ProtectedData();
-        int numKeys = 1;
+        int numKeys = 10;
         byte[] eek = new byte[32];
         new Random().nextBytes(eek);
         GeekResponse geek = new GeekResponse(generateEekChain(eek), new byte[] {0x02});
-        mBinder.generateKeyPair(true /* testMode */, SecurityLevel.TRUSTED_ENVIRONMENT);
+        for (int i = 0; i < numKeys; i++) {
+            mBinder.generateKeyPair(true /* testMode */, SecurityLevel.TRUSTED_ENVIRONMENT);
+        }
         byte[] bundle =
             SystemInterface.generateCsr(true /* testMode */, numKeys,
                                         SecurityLevel.TRUSTED_ENVIRONMENT,
@@ -246,40 +180,51 @@
         assertEquals(MajorType.ARRAY, publicKeysArr.get(0).getMajorType());
         Array publicKeys = (Array) publicKeysArr.get(0);
         assertEquals(numKeys, publicKeys.getDataItems().size());
-        Map publicKey = (Map) publicKeys.getDataItems().get(0);
-        byte[] xPub = ((ByteString) publicKey.get(new NegativeInteger(-2))).getBytes();
-        byte[] yPub = ((ByteString) publicKey.get(new NegativeInteger(-3))).getBytes();
-        assertEquals(xPub.length, 32);
-        assertEquals(yPub.length, 32);
         KeyPair rootKeyPair = generateEcdsaKeyPair();
         KeyPair intermediateKeyPair = generateEcdsaKeyPair();
-        PublicKey leafKeyToSign = getP256PubKeyFromBytes(xPub, yPub);
-        X509Certificate[] certChain = new X509Certificate[3];
-        certChain[0] = signPublicKey(intermediateKeyPair, leafKeyToSign);
-        certChain[1] = signPublicKey(rootKeyPair, intermediateKeyPair.getPublic());
-        certChain[2] = signPublicKey(rootKeyPair, rootKeyPair.getPublic());
-        ByteArrayOutputStream os = new ByteArrayOutputStream();
-        for (int i = 0; i < certChain.length; i++) {
-            os.write(certChain[i].getEncoded());
+        X509Certificate[][] certChain = new X509Certificate[numKeys][3];
+        for (int i = 0; i < numKeys; i++) {
+            Map publicKey = (Map) publicKeys.getDataItems().get(i);
+            byte[] xPub = ((ByteString) publicKey.get(new NegativeInteger(-2))).getBytes();
+            byte[] yPub = ((ByteString) publicKey.get(new NegativeInteger(-3))).getBytes();
+            assertEquals(xPub.length, 32);
+            assertEquals(yPub.length, 32);
+            PublicKey leafKeyToSign = getP256PubKeyFromBytes(xPub, yPub);
+            certChain[i][0] = signPublicKey(intermediateKeyPair, leafKeyToSign);
+            certChain[i][1] = signPublicKey(rootKeyPair, intermediateKeyPair.getPublic());
+            certChain[i][2] = signPublicKey(rootKeyPair, rootKeyPair.getPublic());
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            for (int j = 0; j < certChain[i].length; j++) {
+                os.write(certChain[i][j].getEncoded());
+            }
+            SystemInterface.provisionCertChain(X509Utils.getAndFormatRawPublicKey(certChain[i][0]),
+                                               certChain[i][0].getEncoded() /* leafCert */,
+                                               os.toByteArray() /* certChain */,
+                                               System.currentTimeMillis() + 2000 /* validity */,
+                                               SecurityLevel.TRUSTED_ENVIRONMENT,
+                                               mBinder);
         }
-        SystemInterface.provisionCertChain(X509Utils.getAndFormatRawPublicKey(certChain[0]),
-                                           certChain[0].getEncoded(),
-                                           os.toByteArray(),
-                                           System.currentTimeMillis() + 2000, // Valid for 2 seconds
-                                           SecurityLevel.TRUSTED_ENVIRONMENT,
-                                           mBinder);
         // getPoolStatus will clean the key pool before we go to assign a new provisioned key
         mBinder.getPoolStatus(0, SecurityLevel.TRUSTED_ENVIRONMENT);
         Certificate[] provisionedCerts1 = generateKeyStoreKey("alias");
         Certificate[] provisionedCerts2 = generateKeyStoreKey("alias2");
         assertEquals(4, provisionedCerts1.length);
         assertEquals(4, provisionedCerts2.length);
+        boolean matched = false;
         for (int i = 0; i < certChain.length; i++) {
-            assertArrayEquals("i = " + i,
-                    provisionedCerts1[i + 1].getEncoded(), certChain[i].getEncoded());
-            assertArrayEquals("i = " + i,
-                    provisionedCerts2[i + 1].getEncoded(), certChain[i].getEncoded());
+            if (Arrays.equals(provisionedCerts1[1].getEncoded(), certChain[i][0].getEncoded())) {
+                matched = true;
+                assertArrayEquals("Second key: j = 0",
+                        provisionedCerts2[1].getEncoded(), certChain[i][0].getEncoded());
+                for (int j = 1; j < certChain[i].length; j++) {
+                    assertArrayEquals("First key: j = " + j,
+                            provisionedCerts1[j + 1].getEncoded(), certChain[i][j].getEncoded());
+                    assertArrayEquals("Second key: j = " + j,
+                            provisionedCerts2[j + 1].getEncoded(), certChain[i][j].getEncoded());
+                }
+            }
         }
+        assertTrue(matched);
     }
 
     private static byte[] extractRecipientKey(Array recipients) {
diff --git a/tests/unittests/src/com/android/remoteprovisioner/unittest/Utils.java b/tests/unittests/src/com/android/remoteprovisioner/unittest/Utils.java
index ad49870..7ece397 100644
--- a/tests/unittests/src/com/android/remoteprovisioner/unittest/Utils.java
+++ b/tests/unittests/src/com/android/remoteprovisioner/unittest/Utils.java
@@ -16,16 +16,24 @@
 
 package com.android.remoteprovisioner.unittest;
 
+import com.google.crypto.tink.subtle.Ed25519Sign;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.model.Array;
+
 import org.bouncycastle.asn1.x509.BasicConstraints;
 import org.bouncycastle.asn1.x509.Extension;
 import org.bouncycastle.asn1.x509.KeyUsage;
 import org.bouncycastle.x509.X509V3CertificateGenerator;
 
+import java.io.ByteArrayOutputStream;
 import java.math.BigInteger;
 import java.security.AlgorithmParameters;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
 import java.security.PublicKey;
 import java.security.cert.X509Certificate;
 import java.security.spec.ECGenParameterSpec;
@@ -42,6 +50,16 @@
  * Utility class for unit testing.
  */
 public class Utils {
+    private static final int KEY_TYPE = 1;
+    private static final int KEY_TYPE_OKP = 1;
+    private static final int KID = 2;
+    private static final int ALGORITHM = 3;
+    private static final int ALGORITHM_EDDSA = -8;
+    private static final int ALGORITHM_ECDH_ES_HKDF_256 = -25;
+    private static final int CURVE = -1;
+    private static final int CURVE_X25519 = 4;
+    private static final int CURVE_ED25519 = 6;
+    private static final int X_COORDINATE = -2;
 
     public static PublicKey getP256PubKeyFromBytes(byte[] xPub, byte[] yPub) throws Exception {
         BigInteger x = new BigInteger(1, xPub);
@@ -83,4 +101,79 @@
                 Extension.keyUsage, /*isCritical=*/ true, new KeyUsage(KeyUsage.keyCertSign));
         return certificateBuilder.generate(issuerKeyPair.getPrivate());
     }
+
+    public static Array encodeAndSignSign1Ed25519(byte[] encodedPublicKey, byte[] privateKey)
+            throws Exception {
+        byte[] encodedProtectedHeaders = encodeSimpleMap(1, -8);
+        return (Array) (new CborBuilder()
+            .addArray()
+                .add(encodedProtectedHeaders)      // Protected headers
+                .addMap()                          // Empty unprotected Headers
+                    .end()
+                .add(encodedPublicKey)
+                .add(encodeAndSignSigStructure(
+                        encodedProtectedHeaders, encodedPublicKey, privateKey))
+            .end()
+            .build().get(0));
+    }
+
+    private static byte[] encodeAndSignSigStructure(
+            byte[] protectedHeaders, byte[] payload, byte[] privateKey) throws Exception {
+        return encodeAndSignSigStructure(protectedHeaders, null, payload, privateKey);
+    }
+
+    private static byte[] encodeAndSignSigStructure(byte[] protectedHeaders, byte[] externalAad,
+                                                    byte[] payload, byte[] privateKey)
+            throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        new CborEncoder(baos).encode(new CborBuilder()
+                .addArray()
+                    .add("Signature1")                                      // context string
+                    .add(protectedHeaders)                                  // protected headers
+                    .add(null == externalAad ? new byte[0] : externalAad)   // external aad
+                    .add(payload)                                           // payload
+                    .end()
+                .build());
+        Ed25519Sign signer = new Ed25519Sign(privateKey);
+        return signer.sign(baos.toByteArray());
+    }
+
+    public static byte[] encodeEd25519PubKey(byte[] publicKey) throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        new CborEncoder(baos).encode(new CborBuilder()
+                .addMap()
+                    .put(KEY_TYPE, KEY_TYPE_OKP)
+                    .put(ALGORITHM, ALGORITHM_EDDSA)
+                    .put(CURVE, CURVE_ED25519)
+                    .put(X_COORDINATE, publicKey)
+                    .end()
+                .build());
+        return baos.toByteArray();
+    }
+
+    public static byte[] encodeX25519PubKey(byte[] publicKey) throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        MessageDigest digest = MessageDigest.getInstance("SHA-256");
+        byte[] kid = digest.digest(publicKey);
+        new CborEncoder(baos).encode(new CborBuilder()
+                .addMap()
+                    .put(KEY_TYPE, KEY_TYPE_OKP)
+                    .put(KID, kid)
+                    .put(ALGORITHM, ALGORITHM_ECDH_ES_HKDF_256)
+                    .put(CURVE, CURVE_X25519)
+                    .put(X_COORDINATE, publicKey)
+                    .end()
+                .build());
+        return baos.toByteArray();
+    }
+
+    private static byte[] encodeSimpleMap(int key, int value) throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        new CborEncoder(baos).encode(new CborBuilder()
+                .addMap()
+                    .put(key, value)
+                    .end()
+                .build());
+        return baos.toByteArray();
+    }
 }