Merge "Add DevicePolicyManager API to install a client cert chain." into nyc-dev

This commit is contained in:
Rubin Xu
2016-03-30 17:46:16 +00:00
committed by Android (Google) Code Review
8 changed files with 75 additions and 22 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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.
*
* <p>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.
*
* <p>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) {

View File

@@ -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);

View File

@@ -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

View File

@@ -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.
* <p>
* <strong>Note:</strong> 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).
*
* <p> 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<X509Certificate> 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<X509Certificate> chain = toCertificates(certChainBytes);
ArrayList<X509Certificate> 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<X509Certificate> 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<X509Certificate> toCertificates(@NonNull byte[] bytes) {
if (bytes == null) {
throw new IllegalArgumentException("bytes == null");
}
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
return (Collection<X509Certificate>) certFactory.generateCertificates(
new ByteArrayInputStream(bytes));
} catch (CertificateException e) {
throw new AssertionError(e);
}
}
/**
* @hide for reuse by CertInstaller and Settings.
* @see KeyChain#bind

View File

@@ -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) {