Adding the initial draft for the RemoteProvisioner app

This change implements the bare bones Remote Provisioner application.
This application serves as the networking and business logic center for
remote provisioning. It is designed to wake up on boot, check the state
of the attestation key pool in keystore, and then take any required
action to refresh attestation keys before scheduling itself to check
back up on the state of the pool through the JobScheduler utility.

Test: TBD
Change-Id: I3a0714a30ea53a37df8c74fb0c2db6754b5bc206
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..a465016
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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.
+//
+android_app {
+    name: "RemoteProvisioner",
+    platform_apis: true,
+    privileged: true,
+    libs: [
+        "android.security.remoteprovisioning-java",
+        "android.system.keystore2-java",
+        "framework-annotations-lib",
+        "cbor-java",
+    ],
+    resource_dirs: ["res"],
+    srcs: ["src/**/*.java",
+           "src/**/I*.aidl",],
+    aidl: {
+        local_include_dirs: ["src"],
+    },
+}
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..709e638
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<!--
+  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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.remoteprovisioner">
+
+    <application
+        android:label="@string/app_name">
+        <receiver android:name=".BootReceiver">
+            <intent-filter >
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+            </intent-filter>
+        </receiver>
+        <service android:name="PeriodicProvisioner"
+            android:permission="android.permission.BIND_JOB_SERVICE"
+            android:exported="false">
+        </service>
+    </application>
+
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
+</manifest>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..4faecfa
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#6200EE</color>
+    <color name="colorPrimaryDark">#3700B3</color>
+    <color name="colorAccent">#03DAC5</color>
+</resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..d10080e
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">RemoteProvisioner</string>
+</resources>
\ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..6b30d8d
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,3 @@
+<resources>
+
+</resources>
\ No newline at end of file
diff --git a/src/com/android/remoteprovisioner/BootReceiver.java b/src/com/android/remoteprovisioner/BootReceiver.java
new file mode 100644
index 0000000..88fcb95
--- /dev/null
+++ b/src/com/android/remoteprovisioner/BootReceiver.java
@@ -0,0 +1,49 @@
+/**
+ * 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.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * A receiver class that listens for boot to be completed and then starts a recurring job that will
+ * monitor the status of the attestation key pool on device, purging old certificates and requesting
+ * new ones as needed.
+ */
+public class BootReceiver extends BroadcastReceiver {
+    private static final String TAG = "RemoteProvisioningBootReceiver";
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.d(TAG, "Caught boot intent, waking up.");
+        JobInfo info = new JobInfo
+                .Builder(1, new ComponentName(context, PeriodicProvisioner.class))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .setEstimatedNetworkBytes(1000, 1000)
+                .setPeriodic(1000*60*60*24)
+                .build();
+        if (((JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(info)
+                != JobScheduler.RESULT_SUCCESS) {
+            Log.e(TAG, "Could not start the job scheduler for provisioning");
+        }
+    }
+
+}
diff --git a/src/com/android/remoteprovisioner/CborUtils.java b/src/com/android/remoteprovisioner/CborUtils.java
new file mode 100644
index 0000000..b839bea
--- /dev/null
+++ b/src/com/android/remoteprovisioner/CborUtils.java
@@ -0,0 +1,139 @@
+/**
+ * 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.util.Log;
+
+import co.nstant.in.cbor.*;
+import co.nstant.in.cbor.model.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+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 PEM-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();
+                concat.write(sharedCertificates);
+                concat.write(((ByteString) entry).getBytes());
+                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;
+    }
+
+    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;
+        }
+    }
+}
diff --git a/src/com/android/remoteprovisioner/GeekResponse.java b/src/com/android/remoteprovisioner/GeekResponse.java
new file mode 100644
index 0000000..0a1c798
--- /dev/null
+++ b/src/com/android/remoteprovisioner/GeekResponse.java
@@ -0,0 +1,39 @@
+/**
+ * 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;
+
+/**
+ * Convenience class for packaging up the values returned by the server when initially requesting
+ * an Endpoint Encryption Key for remote provisioning. Those values are described by the following
+ * CDDL Schema:
+ *    GeekResponse = [
+ *        EekChain,
+ *        challenge : bstr,
+ *    ]
+ *
+ * The CDDL that defines EekChain is defined in the RemoteProvisioning HAL, but this app does not
+ * require any semantic understanding of the format to perform its function.
+ */
+public class GeekResponse {
+    public byte[] challenge;
+    public byte[] geek;
+
+    public GeekResponse(byte[] geek, byte[] challenge) {
+        this.geek = geek;
+        this.challenge = challenge;
+    }
+}
diff --git a/src/com/android/remoteprovisioner/PeriodicProvisioner.java b/src/com/android/remoteprovisioner/PeriodicProvisioner.java
new file mode 100644
index 0000000..8767f6c
--- /dev/null
+++ b/src/com/android/remoteprovisioner/PeriodicProvisioner.java
@@ -0,0 +1,88 @@
+/**
+ * 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.app.job.JobParameters;
+import android.app.job.JobService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.security.remoteprovisioning.AttestationPoolStatus;
+import android.security.remoteprovisioning.IRemoteProvisioning;
+import android.util.Log;
+
+import java.lang.System;
+
+/**
+ * A class that extends JobService in order to be scheduled to check the status of the attestation
+ * key pool at regular intervals. If the job determines that more keys need to be generated and
+ * signed, it drives that process.
+ */
+public class PeriodicProvisioner extends JobService {
+    //TODO(b/176249146): Replace default key amount with a value fetched from the server.
+    private static final int TOTAL_SIGNED_KEYS = 10;
+
+    private static final String SERVICE = "android.security.remoteprovisioning";
+    private static final String TAG = "RemoteProvisioningService";
+    private ProvisionerThread p;
+
+    public boolean onStartJob(JobParameters params) {
+        Log.d(TAG, "Starting provisioning job");
+        p = new ProvisionerThread(params);
+        p.start();
+        return true;
+    }
+
+    public boolean onStopJob(JobParameters params) {
+        p.stop();
+        return false;
+    }
+
+    private class ProvisionerThread extends Thread {
+        private JobParameters mParams;
+
+        public ProvisionerThread(JobParameters params) {
+            mParams = params;
+        }
+
+        public void run() {
+            try {
+                IRemoteProvisioning binder =
+                    IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE));
+                // TODO: Replace expiration date parameter with value fetched from server
+                AttestationPoolStatus pool =
+                    binder.getPoolStatus(1);
+                int generated = 0;
+                while (generated + pool.total - pool.expiring < TOTAL_SIGNED_KEYS) {
+                    generated++;
+                    binder.generateKeyPair(false /* isTestMode */);
+                    Thread.sleep(5000);
+                }
+                if (generated > 0) {
+                    Log.d(TAG, "Keys generated, moving to provisioning process.");
+                    Provisioner.provisionCerts(generated, binder);
+                }
+                jobFinished(mParams, false /* wantsReschedule */);
+            } catch (RemoteException e) {
+                jobFinished(mParams, true /* wantsReschedule */);
+                Log.e(TAG, "Remote exception during provisioning.", e);
+            } catch (InterruptedException e) {
+                jobFinished(mParams, true /* wantsReschedule */);
+                Log.e(TAG, "Provisioner thread interrupted.", e);
+            }
+        }
+    }
+}
diff --git a/src/com/android/remoteprovisioner/Provisioner.java b/src/com/android/remoteprovisioner/Provisioner.java
new file mode 100644
index 0000000..3aca315
--- /dev/null
+++ b/src/com/android/remoteprovisioner/Provisioner.java
@@ -0,0 +1,202 @@
+/**
+ * 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.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.security.remoteprovisioning.IRemoteProvisioning;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Provides an easy package to run the provisioning process from start to finish, interfacing
+ * with the remote provisioning system service and the server backend in order to provision
+ * attestation certificates to the device.
+ */
+public class Provisioner {
+    private static final String PROVISIONING_URL = "";
+    private static final String GEEK_URL = PROVISIONING_URL + "/v1/eekchain";
+    private static final String CERTIFICATE_SIGNING_URL =
+            PROVISIONING_URL + "/v1:signCertificates?challenge=";
+    private static final String TAG = "RemoteProvisioningService";
+
+    /**
+     * Takes a byte stream composed of PEM encoded certificates and returns the X.509 certificates
+     * contained within as an X509Certificate array.
+     */
+    private static X509Certificate[] formatX509Certs(byte[] certStream)
+            throws CertificateException {
+        CertificateFactory fact = CertificateFactory.getInstance("X.509");
+        ByteArrayInputStream in = new ByteArrayInputStream(certStream);
+        ArrayList<Certificate> certs = new ArrayList<Certificate>(fact.generateCertificates(in));
+        return certs.toArray(new X509Certificate[certs.size()]);
+    }
+
+    /**
+     * Calls out to the specified backend servers to retrieve an Endpoint Encryption Key and
+     * corresponding certificate chain to provide to KeyMint. This public key will be used to
+     * perform an ECDH computation, using the shared secret to encrypt privacy sensitive components
+     * in the bundle that the server needs from the device in order to provision certificates.
+     *
+     * A challenge is also returned from the server so that it can check freshness of the follow-up
+     * request to get keys signed.
+     */
+    private static GeekResponse fetchGeek() {
+        try {
+            URL url = new URL(GEEK_URL);
+            HttpURLConnection con = (HttpURLConnection) url.openConnection();
+            con.setRequestMethod("GET");
+            if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
+                Log.w(TAG, "Server connection for GEEK failed, response code: "
+                        + con.getResponseCode());
+            }
+
+            BufferedInputStream inputStream = new BufferedInputStream(con.getInputStream());
+            ByteArrayOutputStream cborBytes = new ByteArrayOutputStream();
+            byte[] buffer = new byte[1024];
+            int read = 0;
+            while((read = inputStream.read(buffer, 0, buffer.length)) != -1) {
+                cborBytes.write(buffer, 0, read);
+            }
+            inputStream.close();
+            return CborUtils.parseGeekResponse(cborBytes.toByteArray());
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to fetch GEEK from the servers.", e);
+            return null;
+        }
+    }
+
+    /**
+     * Ferries the CBOR blobs returned by KeyMint to the provisioning server. The data sent to the
+     * provisioning server contains the MAC'ed CSRs and encrypted bundle containing the MAC key and
+     * the hardware unique public key.
+     *
+     * @param cborBlob The CBOR encoded data containing the relevant pieces needed for the server to
+     *                    sign the CSRs. The data encoded within comes from Keystore / KeyMint.
+     *
+     * @param challenge The challenge that was sent from the server. It is included here even though
+     *                    it is also included in `cborBlob` in order to allow the server to more
+     *                    easily reject bad requests.
+     *
+     * @return A List of byte arrays, where each array contains an entire PEM-encoded certificate
+     *                    for one attestation key pair.
+     */
+    private static List<byte[]> requestSignedCertificates(byte[] cborBlob, byte[] challenge) {
+        try {
+            URL url = new URL(CERTIFICATE_SIGNING_URL + new String(challenge, "UTF-8"));
+            HttpURLConnection con = (HttpURLConnection) url.openConnection();
+            con.setRequestMethod("POST");
+            con.setDoOutput(true);
+            if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
+                Log.e(TAG, "Server connection for signing failed, response code: "
+                        + con.getResponseCode());
+            }
+            // May not be able to use try-with-resources here if the connection gets closed due to
+            // the output stream being automatically closed.
+            try (OutputStream os = con.getOutputStream()) {
+                os.write(cborBlob, 0, cborBlob.length);
+            } catch (Exception e) {
+                return null;
+            }
+
+            BufferedInputStream inputStream = new BufferedInputStream(con.getInputStream());
+            ByteArrayOutputStream cborBytes = new ByteArrayOutputStream();
+            byte[] buffer = new byte[1024];
+            int read = 0;
+            while((read = inputStream.read(buffer, 0, buffer.length)) != -1) {
+                cborBytes.write(buffer, 0, read);
+            }
+            return CborUtils.parseSignedCertificates(cborBytes.toByteArray());
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to request signed certificates from the server", e);
+            return null;
+        }
+    }
+
+    /**
+     * Drives the process of provisioning certs. The method first contacts the provided backend
+     * server to retrieve an Endpoing Encryption Key with an accompanying certificate chain and a
+     * challenge. It passes this data and the requested number of keys to the remote provisioning
+     * system backend, which then works with KeyMint in order to get a CSR bundle generated, along
+     * with an encrypted package containing metadata that the server needs in order to make
+     * decisions about provisioning.
+     *
+     * This method then passes that bundle back out to the server backend, waits for the response,
+     * and, if successful, passes the certificate chains back to the remote provisioning service to
+     * be stored and later assigned to apps requesting a key attestation.
+     *
+     * @param numKeys The number of keys to be signed. The service will do a best-effort to
+     *                     provision the number requested, but if the number requested is larger
+     *                     than the number of unsigned attestation key pairs available, it will
+     *                     only sign the number that is available at time of calling.
+     *
+     * @param binder The IRemoteProvisioning binder interface needed by the method to handle talking
+     *                     to the remote provisioning system component.
+     *
+     * @return True if certificates were successfully provisioned for the signing keys.
+     */
+    public static boolean provisionCerts(int numKeys, @NonNull IRemoteProvisioning binder) {
+        if (numKeys < 1) {
+            Log.e(TAG, "Request at least 1 key to be signed. Num requested: " + numKeys);
+            return false;
+        }
+        GeekResponse geek = fetchGeek();
+        if (geek == null) {
+            Log.e(TAG, "The geek is null");
+            return false;
+        }
+        byte[] payload = null;
+        try {
+            payload = binder.generateCsr(false /* testMode */,
+                    numKeys,
+                    geek.geek,
+                    geek.challenge);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to generate CSR blob", e);
+            return false;
+        }
+        if (payload == null) {
+            Log.e(TAG, "Keystore failed to generate a payload");
+            return false;
+        }
+        ArrayList<byte[]> certChains =
+            new ArrayList<byte[]>(requestSignedCertificates(payload, geek.challenge));
+        // TODO: Fix AIDL here to add a matching scheme, since public key in KeyStore is a CBOR
+        // blob
+        //binder.provisionCertChain();
+        return true;
+    }
+}
diff --git a/src/com/android/remoteprovisioner/service/GenerateKeyService.java b/src/com/android/remoteprovisioner/service/GenerateKeyService.java
new file mode 100644
index 0000000..d32f18e
--- /dev/null
+++ b/src/com/android/remoteprovisioner/service/GenerateKeyService.java
@@ -0,0 +1,66 @@
+/**
+ * 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.service;
+
+import com.android.remoteprovisioner.Provisioner;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.security.remoteprovisioning.AttestationPoolStatus;
+import android.security.remoteprovisioning.IRemoteProvisioning;
+import android.util.Log;
+
+/**
+ * Provides the implementation for IGenerateKeyService.aidl
+ */
+public class GenerateKeyService extends Service {
+    private static final String SERVICE = "android.security.remoteprovisioning";
+    private static final String TAG = "RemoteProvisioningService";
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return binder;
+    }
+
+    private final IGenerateKeyService.Stub binder = new IGenerateKeyService.Stub() {
+        @Override
+        public void generateKey() {
+            try {
+                IRemoteProvisioning binder =
+                        IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE));
+                AttestationPoolStatus pool =
+                        binder.getPoolStatus(1);
+                if (pool.unassigned == 0) {
+                    binder.generateKeyPair(false /* isTestMode */);
+                    Provisioner.provisionCerts(1 /* numCsr */, binder);
+                } else {
+                    Log.e(TAG, "generateKey() called, but signed certs are available.");
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Remote Exception: ", e);
+            }
+        }
+    };
+}
diff --git a/src/com/android/remoteprovisioner/service/IGenerateKeyService.aidl b/src/com/android/remoteprovisioner/service/IGenerateKeyService.aidl
new file mode 100644
index 0000000..c9ac488
--- /dev/null
+++ b/src/com/android/remoteprovisioner/service/IGenerateKeyService.aidl
@@ -0,0 +1,29 @@
+/**
+ * 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.service;
+
+/**
+ * Interface to allow the framework to notify the RemoteProvisioner app when keys are empty. This
+ * will be used if Keystore replies with an error code NO_KEYS_AVAILABLE in response to an
+ * attestation request. The framework can then synchronously call generateKey() to get more
+ * attestation keys generated and signed. Upon return, the caller can be certain an attestation key
+ * is available.
+ */
+interface IGenerateKeyService {
+    /** Ping the provisioner service to indicate there are no remaining attestation keys left. */
+    void generateKey();
+}