summaryrefslogtreecommitdiffstats
path: root/keystore/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'keystore/java/android')
-rw-r--r--keystore/java/android/security/Credentials.java4
-rw-r--r--keystore/java/android/security/IKeyChainService.aidl36
-rw-r--r--keystore/java/android/security/KeyChain.java209
-rw-r--r--keystore/java/android/security/KeyChainResult.java72
-rw-r--r--keystore/java/android/security/KeyStore.java19
5 files changed, 326 insertions, 14 deletions
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index c650141..6b69b8a 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -31,6 +31,8 @@ public class Credentials {
public static final String INSTALL_ACTION = "android.credentials.INSTALL";
+ public static final String UNLOCK_ACTION = "com.android.credentials.UNLOCK";
+
/** Key prefix for CA certificates. */
public static final String CA_CERTIFICATE = "CACERT_";
@@ -69,7 +71,7 @@ public class Credentials {
public void unlock(Context context) {
try {
- Intent intent = new Intent("com.android.credentials.UNLOCK");
+ Intent intent = new Intent(UNLOCK_ACTION);
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.w(LOGTAG, e.toString());
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
new file mode 100644
index 0000000..be59f23
--- /dev/null
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.os.Bundle;
+
+/**
+ * Caller is required to ensure that {@link KeyStore#unlock
+ * KeyStore.unlock} was successful.
+ *
+ * @hide
+ */
+interface IKeyChainService {
+ // APIs used by KeyChain
+ byte[] getPrivateKey(String alias, String authToken);
+ byte[] getCertificate(String alias, String authToken);
+
+ // APIs used by CertInstaller
+ void installCaCertificate(in byte[] caCertificate);
+
+ // APIs used by Settings
+ boolean reset();
+}
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
new file mode 100644
index 0000000..08e05ef
--- /dev/null
+++ b/keystore/java/android/security/KeyChain.java
@@ -0,0 +1,209 @@
+/*
+ * 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.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * @hide
+ */
+public final class KeyChain {
+
+ private static final String TAG = "KeyChain";
+
+ /**
+ * @hide Also used by KeyChainService implementation
+ */
+ 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}.
+ */
+ public static Intent chooseAlias() {
+ return new Intent("com.android.keychain.CHOOSER");
+ }
+
+ /**
+ * Returns a new {@code KeyChainResult} instance.
+ */
+ public static KeyChainResult get(Context context, String alias)
+ throws InterruptedException, RemoteException {
+ if (alias == null) {
+ throw new NullPointerException("alias == 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);
+ Bundle bundle;
+ try {
+ bundle = future.getResult();
+ } catch (OperationCanceledException e) {
+ throw new AssertionError(e);
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ } catch (AuthenticatorException e) {
+ throw new AssertionError(e);
+ }
+ 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) {
+ throw new AssertionError("Invalid authtoken");
+ }
+ IKeyChainService keyChainService = keyChainConnection.getService();
+ byte[] privateKeyBytes = keyChainService.getPrivateKey(alias, authToken);
+ byte[] certificateBytes = keyChainService.getCertificate(alias, authToken);
+ return new KeyChainResult(toPrivateKey(privateKeyBytes),
+ toCertificate(certificateBytes));
+ } finally {
+ keyChainConnection.close();
+ }
+ }
+
+ private static PrivateKey toPrivateKey(byte[] bytes) {
+ if (bytes == null) {
+ throw new IllegalArgumentException("bytes == null");
+ }
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ } catch (InvalidKeySpecException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static X509Certificate toCertificate(byte[] bytes) {
+ if (bytes == null) {
+ throw new IllegalArgumentException("bytes == null");
+ }
+ try {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
+ return (X509Certificate) cert;
+ } catch (CertificateException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * @hide for reuse by CertInstaller and Settings.
+ * @see KeyChain#bind
+ */
+ public final static class KeyChainConnection implements Closeable {
+ private final Context context;
+ private final ServiceConnection serviceConnection;
+ private final IKeyChainService service;
+ private KeyChainConnection(Context context,
+ ServiceConnection serviceConnection,
+ IKeyChainService service) {
+ this.context = context;
+ this.serviceConnection = serviceConnection;
+ this.service = service;
+ }
+ @Override public void close() {
+ context.unbindService(serviceConnection);
+ }
+ public IKeyChainService getService() {
+ return service;
+ }
+ }
+
+ /**
+ * @hide for reuse by CertInstaller and Settings.
+ *
+ * Caller should call unbindService on the result when finished.
+ */
+ public static KeyChainConnection bind(Context context) throws InterruptedException {
+ if (context == null) {
+ throw new NullPointerException("context == null");
+ }
+ ensureNotOnMainThread(context);
+ final BlockingQueue<IKeyChainService> q = new LinkedBlockingQueue<IKeyChainService>(1);
+ ServiceConnection keyChainServiceConnection = new ServiceConnection() {
+ @Override public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ q.put(IKeyChainService.Stub.asInterface(service));
+ } catch (InterruptedException e) {
+ throw new AssertionError(e);
+ }
+ }
+ @Override public void onServiceDisconnected(ComponentName name) {}
+ };
+ boolean isBound = context.bindService(new Intent(IKeyChainService.class.getName()),
+ keyChainServiceConnection,
+ Context.BIND_AUTO_CREATE);
+ if (!isBound) {
+ throw new AssertionError("could not bind to KeyChainService");
+ }
+ return new KeyChainConnection(context, keyChainServiceConnection, q.take());
+ }
+
+ private static void ensureNotOnMainThread(Context context) {
+ Looper looper = Looper.myLooper();
+ if (looper != null && looper == context.getMainLooper()) {
+ throw new IllegalStateException(
+ "calling this from your main thread can lead to deadlock");
+ }
+ }
+}
diff --git a/keystore/java/android/security/KeyChainResult.java b/keystore/java/android/security/KeyChainResult.java
new file mode 100644
index 0000000..85a2921
--- /dev/null
+++ b/keystore/java/android/security/KeyChainResult.java
@@ -0,0 +1,72 @@
+/*
+ * 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()");
+ }
+ }
+
+}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 4614b53..7183688 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -23,10 +23,13 @@ import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charsets;
import java.util.ArrayList;
/**
- * {@hide}
+ * @hide This should not be made public in its present form because it
+ * assumes that private and secret key bytes are available and would
+ * preclude the use of hardware crypto.
*/
public class KeyStore {
public static final int NO_ERROR = 1;
@@ -211,20 +214,10 @@ public class KeyStore {
}
private static byte[] getBytes(String string) {
- try {
- return string.getBytes("UTF-8");
- } catch (UnsupportedEncodingException e) {
- // will never happen
- throw new RuntimeException(e);
- }
+ return string.getBytes(Charsets.UTF_8);
}
private static String toString(byte[] bytes) {
- try {
- return new String(bytes, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- // will never happen
- throw new RuntimeException(e);
- }
+ return new String(bytes, Charsets.UTF_8);
}
}