From b43659170824dd8d753d9249fe6ccfd37c6221ae Mon Sep 17 00:00:00 2001 From: Rubin Xu Date: Wed, 23 Mar 2016 12:13:22 +0000 Subject: [PATCH] Add DevicePolicyManager API to install a client cert chain. When installing a keypair the caller will have the option to specify a certificate chain which will later be returned to whoever requests access to the keypair via KeyChain. Bug: 18239590 Change-Id: Id21ef026e31537db38d891cb9b712dd4fe7159c7 --- api/current.txt | 2 +- api/system-current.txt | 2 +- api/test-current.txt | 2 +- .../android/app/admin/DevicePolicyManager.java | 26 +++++++---- .../android/app/admin/IDevicePolicyManager.aidl | 2 +- .../java/android/security/IKeyChainService.aidl | 3 +- keystore/java/android/security/KeyChain.java | 54 +++++++++++++++++++--- .../devicepolicy/DevicePolicyManagerService.java | 6 +-- 8 files changed, 75 insertions(+), 22 deletions(-) diff --git a/api/current.txt b/api/current.txt index dd70a3298aeb..889564e0f6df 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5893,7 +5893,7 @@ package android.app.admin { method public boolean hasGrantedPolicy(android.content.ComponentName, int); method public boolean installCaCert(android.content.ComponentName, byte[]); method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String); - method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String, boolean); + method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean); method public boolean isActivePasswordSufficient(); method public boolean isAdminActive(android.content.ComponentName); method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String); diff --git a/api/system-current.txt b/api/system-current.txt index 0fe632d27d88..87f291ddf4d8 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6039,7 +6039,7 @@ package android.app.admin { method public boolean hasGrantedPolicy(android.content.ComponentName, int); method public boolean installCaCert(android.content.ComponentName, byte[]); method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String); - method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String, boolean); + method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean); method public boolean isActivePasswordSufficient(); method public boolean isAdminActive(android.content.ComponentName); method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String); diff --git a/api/test-current.txt b/api/test-current.txt index d59fa272088d..92116f35bfd9 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5897,7 +5897,7 @@ package android.app.admin { method public boolean hasGrantedPolicy(android.content.ComponentName, int); method public boolean installCaCert(android.content.ComponentName, byte[]); method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String); - method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String, boolean); + method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean); method public boolean isActivePasswordSufficient(); method public boolean isAdminActive(android.content.ComponentName); method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index e7427bfabe01..60e2731ad588 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -71,6 +71,7 @@ import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; @@ -2782,7 +2783,7 @@ public class DevicePolicyManager { * compromised, certificates it had already installed will be protected. * *

If the installer must have access to the credentials, call - * {@link #installKeyPair(ComponentName, PrivateKey, Certificate, String, boolean)} instead. + * {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, boolean)} instead. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or * {@code null} if calling from a delegated certificate installer. @@ -2796,13 +2797,14 @@ public class DevicePolicyManager { */ public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey, @NonNull Certificate cert, @NonNull String alias) { - return installKeyPair(admin, privKey, cert, alias, false); + return installKeyPair(admin, privKey, new Certificate[] {cert}, alias, false); } /** * Called by a device or profile owner, or delegated certificate installer, to install a - * certificate and corresponding private key. All apps within the profile will be able to access - * the certificate and use the private key, given direct user approval. + * certificate chain and corresponding private key for the leaf certificate. All apps within the + * profile will be able to access the certificate chain and use the private key, given direct + * user approval. * *

The caller of this API may grant itself access to the certificate and private key * immediately, without user approval. It is a best practice not to request this unless strictly @@ -2811,7 +2813,9 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or * {@code null} if calling from a delegated certificate installer. * @param privKey The private key to install. - * @param cert The certificate to install. + * @param certs The certificate chain to install. The chain should start with the leaf + * certificate and include the chain of trust in order. This will be returned by + * {@link android.security.KeyChain#getCertificateChain}. * @param alias The private key alias under which to install the certificate. If a certificate * with that alias already exists, it will be overwritten. * @param requestAccess {@code true} to request that the calling app be granted access to the @@ -2820,14 +2824,20 @@ public class DevicePolicyManager { * @return {@code true} if the keys were installed, {@code false} otherwise. * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile * owner. + * @see android.security.KeyChain#getCertificateChain */ public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey, - @NonNull Certificate cert, @NonNull String alias, boolean requestAccess) { + @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) { try { - final byte[] pemCert = Credentials.convertToPem(cert); + final byte[] pemCert = Credentials.convertToPem(certs[0]); + byte[] pemChain = null; + if (certs.length > 1) { + pemChain = Credentials.convertToPem(Arrays.copyOfRange(certs, 1, certs.length)); + } final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm()) .getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded(); - return mService.installKeyPair(admin, pkcs8Key, pemCert, alias, requestAccess); + return mService.installKeyPair(admin, pkcs8Key, pemCert, pemChain, alias, + requestAccess); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index aed220dd5705..c869a2a3f163 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -146,7 +146,7 @@ interface IDevicePolicyManager { void enforceCanManageCaCerts(in ComponentName admin); boolean installKeyPair(in ComponentName who, in byte[] privKeyBuffer, in byte[] certBuffer, - String alias, boolean requestAccess); + in byte[] certChainBuffer, String alias, boolean requestAccess); boolean removeKeyPair(in ComponentName who, String alias); void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback); diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl index cfcb4e0c16e1..deea9726403f 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -27,12 +27,13 @@ interface IKeyChainService { // APIs used by KeyChain String requestPrivateKey(String alias); byte[] getCertificate(String alias); + byte[] getCaCertificates(String alias); // APIs used by CertInstaller void installCaCertificate(in byte[] caCertificate); // APIs used by DevicePolicyManager - boolean installKeyPair(in byte[] privateKey, in byte[] userCert, String alias); + boolean installKeyPair(in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias); boolean removeKeyPair(String alias); // APIs used by Settings diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index 0886487f70cf..cce58c2096f3 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -42,6 +42,8 @@ import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.concurrent.BlockingQueue; @@ -389,7 +391,12 @@ public final class KeyChain { /** * Returns the {@code X509Certificate} chain for the requested - * alias, or null if no there is no result. + * alias, or null if there is no result. + *

+ * Note: If a certificate chain was explicitly specified when the alias was + * installed, this method will return that chain. If only the client certificate was specified + * at the installation time, this method will try to build a certificate chain using all + * available trust anchors (preinstalled and user-added). * *

This method may block while waiting for a connection to another process, and must never * be called from the main thread. @@ -413,11 +420,31 @@ public final class KeyChain { if (certificateBytes == null) { return null; } - - TrustedCertificateStore store = new TrustedCertificateStore(); - List chain = store - .getCertificateChain(toCertificate(certificateBytes)); - return chain.toArray(new X509Certificate[chain.size()]); + X509Certificate leafCert = toCertificate(certificateBytes); + final byte[] certChainBytes = keyChainService.getCaCertificates(alias); + // If the keypair is installed with a certificate chain by either + // DevicePolicyManager.installKeyPair or CertInstaller, return that chain. + if (certChainBytes != null && certChainBytes.length != 0) { + Collection chain = toCertificates(certChainBytes); + ArrayList fullChain = new ArrayList<>(chain.size() + 1); + fullChain.add(leafCert); + fullChain.addAll(chain); + return fullChain.toArray(new X509Certificate[fullChain.size()]); + } else { + // If there isn't a certificate chain, either due to a pre-existing keypair + // installed before N, or no chain is explicitly installed under the new logic, + // fall back to old behavior of constructing the chain from trusted credentials. + // + // This logic exists to maintain old behaviour for already installed keypair, at + // the cost of potentially returning extra certificate chain for new clients who + // explicitly installed only the client certificate without a chain. The latter + // case is actually no different from pre-N behaviour of getCertificateChain(), + // in that sense this change introduces no regression. Besides the returned chain + // is still valid so the consumer of the chain should have no problem verifying it. + TrustedCertificateStore store = new TrustedCertificateStore(); + List chain = store.getCertificateChain(leafCert); + return chain.toArray(new X509Certificate[chain.size()]); + } } catch (CertificateException e) { throw new KeyChainException(e); } catch (RemoteException e) { @@ -486,6 +513,21 @@ public final class KeyChain { } } + /** @hide */ + @NonNull + public static Collection toCertificates(@NonNull byte[] bytes) { + if (bytes == null) { + throw new IllegalArgumentException("bytes == null"); + } + try { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (Collection) certFactory.generateCertificates( + new ByteArrayInputStream(bytes)); + } catch (CertificateException e) { + throw new AssertionError(e); + } + } + /** * @hide for reuse by CertInstaller and Settings. * @see KeyChain#bind diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index b2cd69d9f371..02f3b74b8f82 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -4138,8 +4138,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public boolean installKeyPair(ComponentName who, byte[] privKey, byte[] cert, String alias, - boolean requestAccess) { + public boolean installKeyPair(ComponentName who, byte[] privKey, byte[] cert, byte[] chain, + String alias, boolean requestAccess) { enforceCanManageInstalledKeys(who); final int callingUid = mInjector.binderGetCallingUid(); @@ -4149,7 +4149,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { KeyChain.bindAsUser(mContext, UserHandle.getUserHandleForUid(callingUid)); try { IKeyChainService keyChain = keyChainConnection.getService(); - if (!keyChain.installKeyPair(privKey, cert, alias)) { + if (!keyChain.installKeyPair(privKey, cert, chain, alias)) { return false; } if (requestAccess) { -- 2.11.0