From 93201f545b67da15cb69830a5988810aef52c0b2 Mon Sep 17 00:00:00 2001 From: Brian Carlstrom Date: Thu, 9 Jun 2011 15:05:35 -0700 Subject: KeyChain API refinements Change-Id: I177ab4642e6cd1aa13526c14f0a707175fd79655 --- .../android/security/IKeyChainAliasCallback.aidl | 26 +++++ .../android/security/IKeyChainAliasResponse.aidl | 26 ----- keystore/java/android/security/KeyChain.java | 124 +++++++++++++++++++-- .../android/security/KeyChainAliasCallback.java | 35 ++++++ .../android/security/KeyChainAliasResponse.java | 35 ------ .../java/android/security/KeyChainException.java | 67 +++++++++++ 6 files changed, 242 insertions(+), 71 deletions(-) create mode 100644 keystore/java/android/security/IKeyChainAliasCallback.aidl delete mode 100644 keystore/java/android/security/IKeyChainAliasResponse.aidl create mode 100644 keystore/java/android/security/KeyChainAliasCallback.java delete mode 100644 keystore/java/android/security/KeyChainAliasResponse.java create mode 100644 keystore/java/android/security/KeyChainException.java (limited to 'keystore/java/android') diff --git a/keystore/java/android/security/IKeyChainAliasCallback.aidl b/keystore/java/android/security/IKeyChainAliasCallback.aidl new file mode 100644 index 0000000..1ea9521 --- /dev/null +++ b/keystore/java/android/security/IKeyChainAliasCallback.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2011 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 android.security; + +/** + * Used by the {@code KeyChainActivity} to return alias for {@link KeyStore#chooseAlias}. + * + * @hide + */ +interface IKeyChainAliasCallback { + + void alias(String alias); +} diff --git a/keystore/java/android/security/IKeyChainAliasResponse.aidl b/keystore/java/android/security/IKeyChainAliasResponse.aidl deleted file mode 100644 index e042001..0000000 --- a/keystore/java/android/security/IKeyChainAliasResponse.aidl +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2011 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 android.security; - -/** - * Used by the {@code KeyChainActivity} to return alias for {@link KeyStore#chooseAlias}. - * - * @hide - */ -interface IKeyChainAliasResponse { - - void alias(String alias); -} diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index ba784ed..39d65be 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -36,6 +36,7 @@ import java.io.IOException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; +import java.security.Principal; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; @@ -47,8 +48,38 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; /** - * @hide + * The {@code KeyChain} class provides access to private keys and + * their corresponding certificate chains in credential storage. + * + *

Applications accessing the {@code KeyChain} normally go through + * these steps: + * + *

    + * + *
  1. Receive a callback from an {@link javax.net.ssl.X509KeyManager + * X509KeyManager} that a private key is requested. + * + *
  2. Call {@link #choosePrivateKeyAlias + * choosePrivateKeyAlias} to allow the user to select from a + * list of currently available private keys and corresponding + * certificate chains. The chosen alias will be returned by the + * callback {@link KeyChainAliasCallback#alias}, or null if no private + * key is available or the user cancels the request. + * + *
  3. Call {@link #getPrivateKey} and {@link #getCertificateChain} to + * retrieve the credentials to return to the corresponding {@link + * javax.net.ssl.X509KeyManager} callbacks. + * + *
+ * + *

An application may remember the value of a selected alias to + * avoid prompting the user with {@link #choosePrivateKeyAlias + * choosePrivateKeyAlias} on subsequent connections. If the alias is + * no longer valid, null will be returned on lookups using that value + * + * @hide to be unhidden as part of KeyChain API */ +// TODO reference intent for credential installation when public public final class KeyChain { private static final String TAG = "KeyChain"; @@ -67,9 +98,58 @@ public final class KeyChain { * Launches an {@code Activity} for the user to select the alias * for a private key and certificate pair for authentication. The * selected alias or null will be returned via the - * IKeyChainAliasResponse callback. + * KeyChainAliasCallback callback. + * + *

{@code keyTypes} and {@code issuers} may be used to + * highlight suggested choices to the user, although to cope with + * sometimes erroneous values provided by servers, the user may be + * able to override these suggestions. + * + *

{@code host} and {@code port} may be used to give the user + * more context about the server requesting the credentials. + * + *

This method requires the caller to hold the permission + * {@link android.Manifest.permission#USE_CREDENTIALS}. + * + * @param activity The {@link Activity} context to use for + * launching the new sub-Activity to prompt the user to select + * a private key; used only to call startActivity(); must not + * be null. + * @param response Callback to invoke when the request completes; + * must not be null + * @param keyTypes The acceptable types of asymmetric keys such as + * "RSA" or "DSA", or a null array. + * @param issuers The acceptable certificate issuers for the + * certificate matching the private key, or null. + * @param host The host name of the server requesting the + * certificate, or null if unavailable. + * @param port The port number of the server requesting the + * certificate, or -1 if unavailable. */ - public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasResponse response) { + public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, + String[] keyTypes, Principal[] issuers, + String host, int port) { + /* + * TODO currently keyTypes, issuers, host, and port are + * unused. They are meant to follow the semantics and purpose + * of X509KeyManager method arguments. + * + * keyTypes would allow the list to be filtered and typically + * will be set correctly by the server. In practice today, + * most all users will want only RSA, rarely DSA, and usually + * only a small number of certs will be available. + * + * issuers is typically not useful. Some servers historically + * will send the entire list of public CAs known to the + * server. Others will send none. If this is used, if there + * are no matches after applying the constraint, it should be + * ignored. + * + * host and port may be shown to the user if available, but it + * should be clear that they are not validated values, perhaps + * shown along with requesting application identity to clarify + * the source of the request. + */ if (activity == null) { throw new NullPointerException("activity == null"); } @@ -81,10 +161,10 @@ public final class KeyChain { activity.startActivity(intent); } - private static class AliasResponse extends IKeyChainAliasResponse.Stub { + private static class AliasResponse extends IKeyChainAliasCallback.Stub { private final Activity activity; - private final KeyChainAliasResponse keyChainAliasResponse; - private AliasResponse(Activity activity, KeyChainAliasResponse keyChainAliasResponse) { + private final KeyChainAliasCallback keyChainAliasResponse; + private AliasResponse(Activity activity, KeyChainAliasCallback keyChainAliasResponse) { this.activity = activity; this.keyChainAliasResponse = keyChainAliasResponse; } @@ -105,9 +185,9 @@ public final class KeyChain { } private static class AliasAccountManagerCallback implements AccountManagerCallback { - private final KeyChainAliasResponse keyChainAliasResponse; + private final KeyChainAliasCallback keyChainAliasResponse; private final String alias; - private AliasAccountManagerCallback(KeyChainAliasResponse keyChainAliasResponse, + private AliasAccountManagerCallback(KeyChainAliasCallback keyChainAliasResponse, String alias) { this.keyChainAliasResponse = keyChainAliasResponse; this.alias = alias; @@ -138,9 +218,16 @@ public final class KeyChain { /** * Returns the {@code PrivateKey} for the requested alias, or null * if no there is no result. + * + *

This method requires the caller to hold the permission + * {@link android.Manifest.permission#USE_CREDENTIALS}. + * + * @param alias The alias of the desired private key, typically + * returned via {@link KeyChainAliasCallback#alias}. + * @throws KeyChainException if the alias was valid but there was some problem accessing it. */ public static PrivateKey getPrivateKey(Context context, String alias) - throws InterruptedException, RemoteException { + throws KeyChainException, InterruptedException { if (alias == null) { throw new NullPointerException("alias == null"); } @@ -153,6 +240,11 @@ public final class KeyChain { IKeyChainService keyChainService = keyChainConnection.getService(); byte[] privateKeyBytes = keyChainService.getPrivateKey(alias, authToken); return toPrivateKey(privateKeyBytes); + } catch (RemoteException e) { + throw new KeyChainException(e); + } catch (RuntimeException e) { + // only certain RuntimeExceptions can be propagated across the IKeyChainService call + throw new KeyChainException(e); } finally { keyChainConnection.close(); } @@ -161,9 +253,16 @@ public final class KeyChain { /** * Returns the {@code X509Certificate} chain for the requested * alias, or null if no there is no result. + * + *

This method requires the caller to hold the permission + * {@link android.Manifest.permission#USE_CREDENTIALS}. + * + * @param alias The alias of the desired certificate chain, typically + * returned via {@link KeyChainAliasCallback#alias}. + * @throws KeyChainException if the alias was valid but there was some problem accessing it. */ public static X509Certificate[] getCertificateChain(Context context, String alias) - throws InterruptedException, RemoteException { + throws KeyChainException, InterruptedException { if (alias == null) { throw new NullPointerException("alias == null"); } @@ -176,6 +275,11 @@ public final class KeyChain { IKeyChainService keyChainService = keyChainConnection.getService(); byte[] certificateBytes = keyChainService.getCertificate(alias, authToken); return new X509Certificate[] { toCertificate(certificateBytes) }; + } catch (RemoteException e) { + throw new KeyChainException(e); + } catch (RuntimeException e) { + // only certain RuntimeExceptions can be propagated across the IKeyChainService call + throw new KeyChainException(e); } finally { keyChainConnection.close(); } diff --git a/keystore/java/android/security/KeyChainAliasCallback.java b/keystore/java/android/security/KeyChainAliasCallback.java new file mode 100644 index 0000000..daa348b --- /dev/null +++ b/keystore/java/android/security/KeyChainAliasCallback.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 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 android.security; + +import android.content.Intent; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +/** + * The KeyChainAliasCallback is the callback for {@link + * KeyChain#choosePrivateKeyAlias}. + * + * @hide to be unhidden as part of KeyChain API + */ +public interface KeyChainAliasCallback { + + /** + * Called with the alias of the certificate chosen by the user, or + * null if no value was chosen. + */ + public void alias(String alias); +} diff --git a/keystore/java/android/security/KeyChainAliasResponse.java b/keystore/java/android/security/KeyChainAliasResponse.java deleted file mode 100644 index bcca123..0000000 --- a/keystore/java/android/security/KeyChainAliasResponse.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2011 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 android.security; - -import android.content.Intent; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; - -/** - * The KeyChainAliasResponse is the callback for {@link - * KeyChain#chooseAlias}. - * - * @hide - */ -public interface KeyChainAliasResponse { - - /** - * Called with the alias of the certificate chosen by the user, or - * null if no value was chosen. - */ - public void alias(String alias); -} diff --git a/keystore/java/android/security/KeyChainException.java b/keystore/java/android/security/KeyChainException.java new file mode 100644 index 0000000..3953f58 --- /dev/null +++ b/keystore/java/android/security/KeyChainException.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011 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 android.security; + +/** + * Thrown on problems accessing the {@link KeyChain}. + * + * @hide to be unhidden as part of KeyChain API + */ +public class KeyChainException extends Exception { + + /** + * Constructs a new {@code KeyChainException} that includes the + * current stack trace. + */ + public KeyChainException() { + } + + /** + * Constructs a new {@code KeyChainException} with the current stack + * trace and the specified detail message. + * + * @param detailMessage + * the detail message for this exception. + */ + public KeyChainException(String detailMessage) { + super(detailMessage); + } + + /** + * Constructs a new {@code KeyChainException} with the current stack + * trace, the specified detail message and the specified cause. + * + * @param message + * the detail message for this exception. + * @param cause + * the cause of this exception, may be {@code null}. + */ + public KeyChainException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new {@code KeyChainException} with the current stack + * trace and the specified cause. + * + * @param cause + * the cause of this exception, may be {@code null}. + */ + public KeyChainException(Throwable cause) { + super((cause == null ? null : cause.toString()), cause); + } +} -- cgit v1.1