diff options
Diffstat (limited to 'keystore/java/android')
| -rw-r--r-- | keystore/java/android/security/Credentials.java | 4 | ||||
| -rw-r--r-- | keystore/java/android/security/IKeyChainService.aidl | 36 | ||||
| -rw-r--r-- | keystore/java/android/security/KeyChain.java | 209 | ||||
| -rw-r--r-- | keystore/java/android/security/KeyChainResult.java | 72 | ||||
| -rw-r--r-- | keystore/java/android/security/KeyStore.java | 19 |
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); } } |
