diff options
-rw-r--r-- | Android.mk | 1 | ||||
-rw-r--r-- | keystore/java/android/security/IKeyChainAliasResponse.aidl | 26 | ||||
-rw-r--r-- | keystore/java/android/security/IKeyChainService.aidl | 2 | ||||
-rw-r--r-- | keystore/java/android/security/KeyChain.java | 183 | ||||
-rw-r--r-- | keystore/java/android/security/KeyChainAliasResponse.java | 35 | ||||
-rw-r--r-- | keystore/java/android/security/KeyChainResult.java | 72 |
6 files changed, 206 insertions, 113 deletions
@@ -162,6 +162,7 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/view/IInputMethodSession.aidl \ core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \ core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl \ + keystore/java/android/security/IKeyChainAliasResponse.aidl \ keystore/java/android/security/IKeyChainService.aidl \ location/java/android/location/ICountryDetector.aidl \ location/java/android/location/ICountryListener.aidl \ diff --git a/keystore/java/android/security/IKeyChainAliasResponse.aidl b/keystore/java/android/security/IKeyChainAliasResponse.aidl new file mode 100644 index 0000000..e042001 --- /dev/null +++ b/keystore/java/android/security/IKeyChainAliasResponse.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 IKeyChainAliasResponse { + + void alias(String alias); +} diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl index be59f23..2763e46 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -15,8 +15,6 @@ */ package android.security; -import android.os.Bundle; - /** * Caller is required to ensure that {@link KeyStore#unlock * KeyStore.unlock} was successful. diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index 08e05ef..ec820cf 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -17,9 +17,11 @@ package android.security; import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; +import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -56,64 +58,123 @@ public final class KeyChain { public static final String ACCOUNT_TYPE = "com.android.keychain"; /** - * Returns an {@code Intent} for use with {@link - * android.app.Activity#startActivityForResult - * startActivityForResult}. The result will be returned via {@link - * android.app.Activity#onActivityResult onActivityResult} with - * {@link android.app.Activity#RESULT_OK RESULT_OK} and the alias - * in the returned {@code Intent}'s extra data with key {@link - * android.content.Intent#EXTRA_TEXT Intent.EXTRA_TEXT}. + * @hide Also used by KeyChainActivity implementation */ - public static Intent chooseAlias() { - return new Intent("com.android.keychain.CHOOSER"); - } + public static final String EXTRA_RESPONSE = "response"; /** - * Returns a new {@code KeyChainResult} instance. + * 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. */ - public static KeyChainResult get(Context context, String alias) - throws InterruptedException, RemoteException { - if (alias == null) { - throw new NullPointerException("alias == null"); + public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasResponse response) { + if (activity == null) { + throw new NullPointerException("activity == null"); } - KeyChainConnection keyChainConnection = bind(context); - try { - // Account is created if necessary during binding of the IKeyChainService - AccountManager accountManager = AccountManager.get(context); - Account account = accountManager.getAccountsByType(ACCOUNT_TYPE)[0]; - AccountManagerFuture<Bundle> future = accountManager.getAuthToken(account, - alias, - false, - null, - null); + if (response == null) { + throw new NullPointerException("response == null"); + } + Intent intent = new Intent("com.android.keychain.CHOOSER"); + intent.putExtra(EXTRA_RESPONSE, new AliasResponse(activity, response)); + activity.startActivity(intent); + } + + private static class AliasResponse extends IKeyChainAliasResponse.Stub { + private final Activity activity; + private final KeyChainAliasResponse keyChainAliasResponse; + private AliasResponse(Activity activity, KeyChainAliasResponse keyChainAliasResponse) { + this.activity = activity; + this.keyChainAliasResponse = keyChainAliasResponse; + } + @Override public void alias(String alias) { + if (alias == null) { + keyChainAliasResponse.alias(null); + return; + } + AccountManager accountManager = AccountManager.get(activity); + accountManager.getAuthToken(getAccount(activity), + alias, + null, + activity, + new AliasAccountManagerCallback(keyChainAliasResponse, + alias), + null); + } + } + + private static class AliasAccountManagerCallback implements AccountManagerCallback<Bundle> { + private final KeyChainAliasResponse keyChainAliasResponse; + private final String alias; + private AliasAccountManagerCallback(KeyChainAliasResponse keyChainAliasResponse, + String alias) { + this.keyChainAliasResponse = keyChainAliasResponse; + this.alias = alias; + } + @Override public void run(AccountManagerFuture<Bundle> future) { Bundle bundle; try { bundle = future.getResult(); } catch (OperationCanceledException e) { - throw new AssertionError(e); + keyChainAliasResponse.alias(null); + return; } catch (IOException e) { - throw new AssertionError(e); + keyChainAliasResponse.alias(null); + return; } catch (AuthenticatorException e) { - throw new AssertionError(e); + keyChainAliasResponse.alias(null); + return; } - Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT); - if (intent != null) { - Bundle result = new Bundle(); - // we don't want this Eclair compatability flag, - // it will prevent onActivityResult from being called - intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK); - return new KeyChainResult(intent); + String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN); + if (authToken != null) { + keyChainAliasResponse.alias(alias); + } else { + keyChainAliasResponse.alias(null); } + } + } - String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN); + /** + * Returns the {@code PrivateKey} for the requested alias, or null + * if no there is no result. + */ + public static PrivateKey getPrivateKey(Context context, String alias) + throws InterruptedException, RemoteException { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + KeyChainConnection keyChainConnection = bind(context); + try { + String authToken = authToken(context, alias); if (authToken == null) { - throw new AssertionError("Invalid authtoken"); + return null; } IKeyChainService keyChainService = keyChainConnection.getService(); byte[] privateKeyBytes = keyChainService.getPrivateKey(alias, authToken); + return toPrivateKey(privateKeyBytes); + } finally { + keyChainConnection.close(); + } + } + + /** + * Returns the {@code X509Certificate} chain for the requested + * alias, or null if no there is no result. + */ + public static X509Certificate[] getCertificateChain(Context context, String alias) + throws InterruptedException, RemoteException { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + KeyChainConnection keyChainConnection = bind(context); + try { + String authToken = authToken(context, alias); + if (authToken == null) { + return null; + } + IKeyChainService keyChainService = keyChainConnection.getService(); byte[] certificateBytes = keyChainService.getCertificate(alias, authToken); - return new KeyChainResult(toPrivateKey(privateKeyBytes), - toCertificate(certificateBytes)); + return new X509Certificate[] { toCertificate(certificateBytes) }; } finally { keyChainConnection.close(); } @@ -146,6 +207,50 @@ public final class KeyChain { } } + private static String authToken(Context context, String alias) { + AccountManager accountManager = AccountManager.get(context); + AccountManagerFuture<Bundle> future = accountManager.getAuthToken(getAccount(context), + alias, + false, + null, + null); + Bundle bundle; + try { + bundle = future.getResult(); + } catch (OperationCanceledException e) { + throw new AssertionError(e); + } catch (IOException e) { + // KeyChainAccountAuthenticator doesn't do I/O + throw new AssertionError(e); + } catch (AuthenticatorException e) { + throw new AssertionError(e); + } + Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT); + if (intent != null) { + return null; + } + String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN); + if (authToken == null) { + throw new AssertionError("Invalid authtoken"); + } + return authToken; + } + + private static Account getAccount(Context context) { + AccountManager accountManager = AccountManager.get(context); + Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE); + if (accounts.length == 0) { + try { + // Account is created if necessary during binding of the IKeyChainService + bind(context).close(); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + accounts = accountManager.getAccountsByType(ACCOUNT_TYPE); + } + return accounts[0]; + } + /** * @hide for reuse by CertInstaller and Settings. * @see KeyChain#bind diff --git a/keystore/java/android/security/KeyChainAliasResponse.java b/keystore/java/android/security/KeyChainAliasResponse.java new file mode 100644 index 0000000..bcca123 --- /dev/null +++ b/keystore/java/android/security/KeyChainAliasResponse.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 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/KeyChainResult.java b/keystore/java/android/security/KeyChainResult.java deleted file mode 100644 index 85a2921..0000000 --- a/keystore/java/android/security/KeyChainResult.java +++ /dev/null @@ -1,72 +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 KeyChainResult is the complex result value from {@link - * KeyChain#get}. The caller should first inspect {@link #getIntent} - * to determine if the user needs to grant the application access to - * the protected contents. If {@code getIntent} returns null, access - * has been granted and the methods {@link #getPrivateKey} and {@link - * #getCertificate} can be used to access the credentials. - * - * @hide - */ -public final class KeyChainResult { - - private final Intent intent; - private final PrivateKey privateKey; - private final X509Certificate certificate; - - KeyChainResult(Intent intent) { - this(intent, null, null); - } - - KeyChainResult(PrivateKey privateKey, X509Certificate certificate) { - this(null, privateKey, certificate); - } - - private KeyChainResult(Intent intent, PrivateKey privateKey, X509Certificate certificate) { - this.intent = intent; - this.privateKey = privateKey; - this.certificate = certificate; - } - - public Intent getIntent() { - return intent; - } - - public PrivateKey getPrivateKey() { - checkIntent(); - return privateKey; - } - - public X509Certificate getCertificate() { - checkIntent(); - return certificate; - } - - private void checkIntent() { - if (intent != null) { - throw new IllegalStateException("non-null Intent, check getIntent()"); - } - } - -} |