Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4c74c03f authored by Brian Carlstrom's avatar Brian Carlstrom Committed by Android (Google) Code Review
Browse files

Merge "Remove need for onActivityResult from KeyChain API"

parents feeba076 ba1a667b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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 \
+26 −0
Original line number Diff line number Diff line
/*
 * 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);
}
+0 −2
Original line number Diff line number Diff line
@@ -15,8 +15,6 @@
 */
package android.security;

import android.os.Bundle;

/**
 * Caller is required to ensure that {@link KeyStore#unlock
 * KeyStore.unlock} was successful.
+144 −39
Original line number Diff line number Diff line
@@ -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 {
    public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasResponse response) {
        if (activity == null) {
            throw new NullPointerException("activity == 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) {
            throw new NullPointerException("alias == null");
                keyChainAliasResponse.alias(null);
                return;
            }
        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,
            AccountManager accountManager = AccountManager.get(activity);
            accountManager.getAuthToken(getAccount(activity),
                                        alias,
                                                                              false,
                                        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;
            }
            String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
            if (authToken != null) {
                keyChainAliasResponse.alias(alias);
            } else {
                keyChainAliasResponse.alias(null);
            }
        }
            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);
    /**
     * 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
+35 −0
Original line number Diff line number Diff line
@@ -20,53 +20,16 @@ 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.
 * The KeyChainAliasResponse is the callback for {@link
 * KeyChain#chooseAlias}.
 *
 * @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()");
        }
    }
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);
}
Loading