/* * Copyright (C) 2012 Google Inc. All rights reserved. * * 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 com.example.google.tv.anymotelibrary.connection; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Enumeration; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import android.content.Context; import android.os.Build; import android.provider.Settings; import android.util.Log; import com.google.polo.ssl.SslUtil; /** * Key store manager. It manages client and server certificates. */ public final class KeyStoreManager { private static final String LOG_TAG = "KeyStoreUtil"; private static final String KEYSTORE_FILENAME = "ipremote.keystore"; private static final char[] KEYSTORE_PASSWORD = "1234567890".toCharArray(); /** * Alias for the remote controller (local) identity in the {@link KeyStore}. */ private static final String LOCAL_IDENTITY_ALIAS = "anymote-remote"; /** * Alias pattern for anymote server identities in the {@link KeyStore} */ private static final String REMOTE_IDENTITY_ALIAS_PATTERN = "anymote-server-%X"; private Context mContext; private KeyManager[] mKeyManagers; private TrustManager[] mTrustManagers; private KeyStore mKeyStore; /** * Loads key store from storage, or creates new one if storage is missing * key store or corrupted. */ private void load() { KeyStore keyStore; try { keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); } catch (KeyStoreException e) { throw new IllegalStateException("Unable to get default instance of KeyStore", e); } try { FileInputStream fis = mContext.openFileInput(KEYSTORE_FILENAME); keyStore.load(fis, KEYSTORE_PASSWORD); } catch (IOException e) { Log.v(LOG_TAG, "Unable open keystore file", e); keyStore = null; } catch (GeneralSecurityException e) { Log.v(LOG_TAG, "Unable open keystore file", e); keyStore = null; } /* * No keys found: generate. */ if (keyStore == null) { try { keyStore = createKeyStore(); } catch (GeneralSecurityException e) { throw new IllegalStateException("Unable to create identity KeyStore", e); } } mKeyStore = keyStore; store(); } /** * Verify if local certificate is available. * * @return true, if certificate is available. */ private boolean hasLocalIdentityAlias() { try { if (!mKeyStore.containsAlias(LOCAL_IDENTITY_ALIAS)) { Log.e(LOG_TAG, "Key store missing identity for " + LOCAL_IDENTITY_ALIAS); return false; } } catch (KeyStoreException e) { Log.e(LOG_TAG, "Key store exception occurred", e); return false; } return true; } /** * Loads or otherwise creates keys for application. This call may take * substantial amount of time to complete. * * @param context Context of the Application * @throws GeneralSecurityException */ public void initialize(Context context) throws GeneralSecurityException { mContext = context; load(); if (!hasLocalIdentityAlias()) { generateAppCertificate(); } collectKeyManagers(); collectTrustManagers(); } /** * Create application-specific certificate that will be used to authenticate * user. */ private void generateAppCertificate() { clearKeyStore(); try { Log.v(LOG_TAG, "Generating key pair ..."); KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); KeyPair keyPair = kg.generateKeyPair(); Log.v(LOG_TAG, "Generating certificate ..."); String name = getCertificateName(getUniqueId()); X509Certificate cert = SslUtil.generateX509V3Certificate(keyPair, name); Certificate[] chain = { cert }; Log.v(LOG_TAG, "Adding key to keystore ..."); mKeyStore.setKeyEntry(LOCAL_IDENTITY_ALIAS, keyPair.getPrivate(), null, chain); Log.d(LOG_TAG, "Key added!"); } catch (GeneralSecurityException e) { throw new IllegalStateException("Unable to create identity KeyStore", e); } store(); } private KeyStore createKeyStore() throws GeneralSecurityException { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); try { keyStore.load(null, KEYSTORE_PASSWORD); } catch (IOException e) { throw new GeneralSecurityException("Unable to create empty keyStore", e); } return keyStore; } private synchronized void store() { try { FileOutputStream fos = mContext.openFileOutput(KEYSTORE_FILENAME, Context.MODE_PRIVATE); mKeyStore.store(fos, KEYSTORE_PASSWORD); fos.close(); } catch (IOException e) { throw new IllegalStateException("Unable to store keyStore", e); } catch (GeneralSecurityException e) { throw new IllegalStateException("Unable to store keyStore", e); } } /** * Returns the name that should be used in a new certificate. * <p> * The format is: "CN=anymote/PRODUCT/DEVICE/MODEL/unique identifier" */ private static final String getCertificateName(String id) { return "CN=anymote/" + Build.PRODUCT + "/" + Build.DEVICE + "/" + Build.MODEL + "/" + id; } /** * @return key managers loaded for this service. */ public KeyManager[] getKeyManagers() { return mKeyManagers; } /** * @throws GeneralSecurityException */ private synchronized void collectKeyManagers() throws GeneralSecurityException { if (mKeyStore == null) { throw new NullPointerException("null mKeyStore"); } KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory .getDefaultAlgorithm()); factory.init(mKeyStore, "".toCharArray()); mKeyManagers = factory.getKeyManagers(); } /** * @return trust managers loaded for this service. */ public TrustManager[] getTrustManagers() { return mTrustManagers; } /** * @throws GeneralSecurityException */ private synchronized void collectTrustManagers() throws GeneralSecurityException { // Build a new set of TrustManagers based on the KeyStore. TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory .getDefaultAlgorithm()); tmf.init(mKeyStore); mTrustManagers = tmf.getTrustManagers(); } /** * Stores the remote device certificate in keystore. * @param peerCert */ synchronized void storeCertificate(final Certificate peerCert) { try { String alias = String.format(KeyStoreManager.REMOTE_IDENTITY_ALIAS_PATTERN, peerCert.hashCode()); if (mKeyStore.containsAlias(alias)) { Log.w(LOG_TAG, "Deleting existing entry for " + alias); mKeyStore.deleteEntry(alias); } Log.i(LOG_TAG, "Adding cert to keystore: " + alias); mKeyStore.setCertificateEntry(alias, peerCert); store(); try { collectTrustManagers(); } catch (GeneralSecurityException e) { // ignore. } } catch (KeyStoreException e) { Log.e(LOG_TAG, "Storing cert failed", e); } } private void clearKeyStore() { try { for (Enumeration<String> e = mKeyStore.aliases(); e.hasMoreElements();) { final String alias = e.nextElement(); Log.v(LOG_TAG, "Deleting alias: " + alias); mKeyStore.deleteEntry(alias); } } catch (KeyStoreException e) { Log.e(LOG_TAG, "Clearing certificates failed", e); } store(); } private String getUniqueId() { String id = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.ANDROID_ID); // null ANDROID_ID is possible on emulator return id != null ? id : "emulator"; } }