Merge "Updating RemoteProvisioner to new AIDL definition"
diff --git a/Android.bp b/Android.bp
index a465016..b3a51a1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -18,11 +18,13 @@
platform_apis: true,
privileged: true,
libs: [
- "android.security.remoteprovisioning-java",
"android.system.keystore2-java",
"framework-annotations-lib",
"cbor-java",
],
+ static_libs: [
+ "android.security.remoteprovisioning-java"
+ ],
resource_dirs: ["res"],
srcs: ["src/**/*.java",
"src/**/I*.aidl",],
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 709e638..7295930 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -19,7 +19,8 @@
<application
android:label="@string/app_name">
- <receiver android:name=".BootReceiver">
+ <receiver android:name=".BootReceiver"
+ android:exported="false">
<intent-filter >
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..e4b8ca5
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,7 @@
+# Primary Owner and Reviewer
+jbires@google.com
+
+# Reviewers
+mpgroover@google.com
+swillden@google.com
+
diff --git a/src/com/android/remoteprovisioner/CborUtils.java b/src/com/android/remoteprovisioner/CborUtils.java
index 356ddfa..a0ac340 100644
--- a/src/com/android/remoteprovisioner/CborUtils.java
+++ b/src/com/android/remoteprovisioner/CborUtils.java
@@ -51,7 +51,7 @@
* @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.
+ * @return A List object where each byte[] entry is an entire DER-encoded certificate chain.
*/
public static List<byte[]> parseSignedCertificates(byte[] serverResp) {
try {
diff --git a/src/com/android/remoteprovisioner/PeriodicProvisioner.java b/src/com/android/remoteprovisioner/PeriodicProvisioner.java
index 8767f6c..e21224c 100644
--- a/src/com/android/remoteprovisioner/PeriodicProvisioner.java
+++ b/src/com/android/remoteprovisioner/PeriodicProvisioner.java
@@ -18,6 +18,7 @@
import android.app.job.JobParameters;
import android.app.job.JobService;
+import android.hardware.security.keymint.SecurityLevel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.security.remoteprovisioning.AttestationPoolStatus;
@@ -63,26 +64,31 @@
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);
- }
+ checkAndProvision(binder, 1, SecurityLevel.TRUSTED_ENVIRONMENT);
jobFinished(mParams, false /* wantsReschedule */);
} catch (RemoteException e) {
jobFinished(mParams, true /* wantsReschedule */);
- Log.e(TAG, "Remote exception during provisioning.", e);
+ Log.e(TAG, "Error on the binder side during provisioning.", e);
} catch (InterruptedException e) {
jobFinished(mParams, true /* wantsReschedule */);
Log.e(TAG, "Provisioner thread interrupted.", e);
}
}
+
+ private void checkAndProvision(IRemoteProvisioning binder, long expiringBy, int secLevel)
+ throws InterruptedException, RemoteException {
+ AttestationPoolStatus pool =
+ binder.getPoolStatus(expiringBy, secLevel);
+ int generated = 0;
+ while (generated + pool.total - pool.expiring < TOTAL_SIGNED_KEYS) {
+ generated++;
+ binder.generateKeyPair(false /* isTestMode */, secLevel);
+ Thread.sleep(5000);
+ }
+ if (generated > 0) {
+ Log.d(TAG, "Keys generated, moving to provisioning process.");
+ Provisioner.provisionCerts(generated, secLevel, binder);
+ }
+ }
}
}
diff --git a/src/com/android/remoteprovisioner/Provisioner.java b/src/com/android/remoteprovisioner/Provisioner.java
index 3aca315..f77acd3 100644
--- a/src/com/android/remoteprovisioner/Provisioner.java
+++ b/src/com/android/remoteprovisioner/Provisioner.java
@@ -20,6 +20,7 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.hardware.security.keymint.SecurityLevel;
import android.os.RemoteException;
import android.security.remoteprovisioning.IRemoteProvisioning;
import android.util.Log;
@@ -33,6 +34,7 @@
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.security.interfaces.ECPublicKey;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
@@ -40,6 +42,7 @@
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* Provides an easy package to run the provisioning process from start to finish, interfacing
@@ -53,8 +56,10 @@
PROVISIONING_URL + "/v1:signCertificates?challenge=";
private static final String TAG = "RemoteProvisioningService";
+ private static final byte ECDSA_UNCOMPRESSED_BYTE = 0x04;
+
/**
- * Takes a byte stream composed of PEM encoded certificates and returns the X.509 certificates
+ * Takes a byte array composed of DER encoded certificates and returns the X.509 certificates
* contained within as an X509Certificate array.
*/
private static X509Certificate[] formatX509Certs(byte[] certStream)
@@ -163,12 +168,14 @@
* than the number of unsigned attestation key pairs available, it will
* only sign the number that is available at time of calling.
*
+ * @param secLevel Which KM instance should be used to provision certs.
* @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) {
+ public static boolean provisionCerts(int numKeys, int secLevel,
+ @NonNull IRemoteProvisioning binder) {
if (numKeys < 1) {
Log.e(TAG, "Request at least 1 key to be signed. Num requested: " + numKeys);
return false;
@@ -183,7 +190,8 @@
payload = binder.generateCsr(false /* testMode */,
numKeys,
geek.geek,
- geek.challenge);
+ geek.challenge,
+ secLevel);
} catch (RemoteException e) {
Log.e(TAG, "Failed to generate CSR blob", e);
return false;
@@ -194,9 +202,45 @@
}
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();
+ for (byte[] certChain : certChains) {
+ // DER encoding specifies leaf to root ordering. Pull the public key and expiration
+ // date from the leaf.
+ X509Certificate cert;
+ try {
+ cert = formatX509Certs(certChain)[0];
+ } catch (CertificateException e) {
+ Log.e(TAG, "Failed to interpret DER encoded certificate chain", e);
+ return false;
+ }
+ // getTime returns the time in *milliseconds* since the epoch.
+ long expirationDate = cert.getNotAfter().getTime();
+ ECPublicKey key = (ECPublicKey) cert.getPublicKey();
+
+ // Remote key provisioning internally supports the default, uncompressed public key
+ // format for ECDSA. This defines the format as (s | x | y), where s is the byte
+ // indicating if the key is compressed or not, and x and y make up the EC point.
+ // However, the key as stored in a COSE_Key object is just the two points. As such,
+ // the raw public key is stored in the database is (x | y), so the compression byte
+ // should be dropped here.
+ //
+ // s: 1 byte, x: 32 bytes, y: 32 bytes
+ byte[] keyEncoding = key.getEncoded();
+ if (keyEncoding.length != 65) {
+ Log.e(TAG, "Key is not encoded as expected, or corrupted. Length: "
+ + keyEncoding.length);
+ return false;
+ } else if (keyEncoding[0] != ECDSA_UNCOMPRESSED_BYTE) {
+ Log.e(TAG, "Key is not uncompressed.");
+ }
+ byte[] rawPublicKey = Arrays.copyOfRange(keyEncoding, 1 /* from */, 65 /* to */);
+ try {
+ binder.provisionCertChain(rawPublicKey, certChain, expirationDate, secLevel);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error on the binder side when attempting to provision the signed chain",
+ e);
+ return false;
+ }
+ }
return true;
}
}
diff --git a/src/com/android/remoteprovisioner/service/GenerateKeyService.java b/src/com/android/remoteprovisioner/service/GenerateKeyService.java
index d32f18e..5124f45 100644
--- a/src/com/android/remoteprovisioner/service/GenerateKeyService.java
+++ b/src/com/android/remoteprovisioner/service/GenerateKeyService.java
@@ -20,6 +20,7 @@
import android.app.Service;
import android.content.Intent;
+import android.hardware.security.keymint.SecurityLevel;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -27,6 +28,8 @@
import android.security.remoteprovisioning.IRemoteProvisioning;
import android.util.Log;
+import java.lang.System;
+
/**
* Provides the implementation for IGenerateKeyService.aidl
*/
@@ -50,17 +53,23 @@
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.");
- }
+ // Iterate through each security level backend
+ checkAndFillPool(binder, SecurityLevel.TRUSTED_ENVIRONMENT);
} catch (RemoteException e) {
Log.e(TAG, "Remote Exception: ", e);
}
}
+
+ private void checkAndFillPool(IRemoteProvisioning binder, int secLevel)
+ throws RemoteException {
+ AttestationPoolStatus pool =
+ binder.getPoolStatus(System.currentTimeMillis(), secLevel);
+ if (pool.unassigned == 0) {
+ binder.generateKeyPair(false /* isTestMode */, secLevel);
+ Provisioner.provisionCerts(1 /* numCsr */, secLevel, binder);
+ } else {
+ Log.e(TAG, "generateKey() called, but signed certs are available.");
+ }
+ }
};
}