summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kenny Root <kroot@google.com> 2011-11-14 08:43:13 -0800
committer Kenny Root <kroot@google.com> 2012-03-20 09:04:33 -0700
commit5423e68d5dbe048ec6f042cce52a33f94184e9fb (patch)
tree77d2cc39ea7f7a856d71d79233702af3dd71dccd
parentb2e822fc2ac87703ac12d062add62408574cbf7c (diff)
Add signing to keystore
Change the keystore to keep the private keys in keystore. When returned, it uses the OpenSSL representation of the key to allow users to use it in various operations through the OpenSSL ENGINE that connects to keystore. Change-Id: I3681f98cb2ec49ffc4a49f3821909313b4ab5735
-rw-r--r--keystore/java/android/security/Credentials.java32
-rw-r--r--keystore/java/android/security/IKeyChainService.aidl2
-rw-r--r--keystore/java/android/security/KeyChain.java28
-rw-r--r--keystore/java/android/security/KeyStore.java72
-rwxr-xr-xkeystore/tests/src/android/security/KeyStoreTest.java235
5 files changed, 352 insertions, 17 deletions
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index f75208dfd0aa..68ba2b11516e 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -26,11 +26,13 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charsets;
import java.security.KeyPair;
+import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
@@ -73,6 +75,36 @@ public class Credentials {
public static final String EXTENSION_PFX = ".pfx";
/**
+ * Intent extra: name for the user's private key.
+ */
+ public static final String EXTRA_USER_PRIVATE_KEY_NAME = "user_private_key_name";
+
+ /**
+ * Intent extra: data for the user's private key in PEM-encoded PKCS#8.
+ */
+ public static final String EXTRA_USER_PRIVATE_KEY_DATA = "user_private_key_data";
+
+ /**
+ * Intent extra: name for the user's certificate.
+ */
+ public static final String EXTRA_USER_CERTIFICATE_NAME = "user_certificate_name";
+
+ /**
+ * Intent extra: data for the user's certificate in PEM-encoded X.509.
+ */
+ public static final String EXTRA_USER_CERTIFICATE_DATA = "user_certificate_data";
+
+ /**
+ * Intent extra: name for CA certificate chain
+ */
+ public static final String EXTRA_CA_CERTIFICATES_NAME = "ca_certificates_name";
+
+ /**
+ * Intent extra: data for CA certificate chain in PEM-encoded X.509.
+ */
+ public static final String EXTRA_CA_CERTIFICATES_DATA = "ca_certificates_data";
+
+ /**
* Convert objects to a PEM format, which is used for
* CA_CERTIFICATE, USER_CERTIFICATE, and USER_PRIVATE_KEY
* entries.
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index f38f6ceb3ed7..60fd7f79318d 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -23,7 +23,7 @@ package android.security;
*/
interface IKeyChainService {
// APIs used by KeyChain
- byte[] getPrivateKey(String alias);
+ String requestPrivateKey(String alias);
byte[] getCertificate(String alias);
// APIs used by CertInstaller
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index fe03437b2966..483ccb258f1e 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -27,6 +27,7 @@ import android.os.RemoteException;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException;
+import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.Principal;
import java.security.PrivateKey;
@@ -39,6 +40,8 @@ import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import libcore.util.Objects;
+
+import org.apache.harmony.xnet.provider.jsse.OpenSSLEngine;
import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore;
/**
@@ -301,14 +304,21 @@ public final class KeyChain {
}
KeyChainConnection keyChainConnection = bind(context);
try {
- IKeyChainService keyChainService = keyChainConnection.getService();
- byte[] privateKeyBytes = keyChainService.getPrivateKey(alias);
- return toPrivateKey(privateKeyBytes);
+ final IKeyChainService keyChainService = keyChainConnection.getService();
+ final String keyId = keyChainService.requestPrivateKey(alias);
+ if (keyId == null) {
+ throw new KeyChainException("keystore had a problem");
+ }
+
+ final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
+ return engine.getPrivateKeyById(keyId);
} catch (RemoteException e) {
throw new KeyChainException(e);
} catch (RuntimeException e) {
// only certain RuntimeExceptions can be propagated across the IKeyChainService call
throw new KeyChainException(e);
+ } catch (InvalidKeyException e) {
+ throw new KeyChainException(e);
} finally {
keyChainConnection.close();
}
@@ -356,18 +366,6 @@ public final class KeyChain {
}
}
- private static PrivateKey toPrivateKey(byte[] bytes) {
- if (bytes == null) {
- throw new IllegalArgumentException("bytes == null");
- }
- try {
- KeyPair keyPair = (KeyPair) Credentials.convertFromPem(bytes).get(0);
- return keyPair.getPrivate();
- } catch (IOException e) {
- throw new AssertionError(e);
- }
- }
-
private static X509Certificate toCertificate(byte[] bytes) {
if (bytes == null) {
throw new IllegalArgumentException("bytes == null");
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 9058cae3b0da..a32e46943d41 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -155,6 +155,78 @@ public class KeyStore {
return mError == KEY_NOT_FOUND;
}
+ private boolean generate(byte[] key) {
+ execute('a', key);
+ return mError == NO_ERROR;
+ }
+
+ public boolean generate(String key) {
+ return generate(getBytes(key));
+ }
+
+ private boolean importKey(byte[] keyName, byte[] key) {
+ execute('m', keyName, key);
+ return mError == NO_ERROR;
+ }
+
+ public boolean importKey(String keyName, byte[] key) {
+ return importKey(getBytes(keyName), key);
+ }
+
+ private byte[] getPubkey(byte[] key) {
+ ArrayList<byte[]> values = execute('b', key);
+ return (values == null || values.isEmpty()) ? null : values.get(0);
+ }
+
+ public byte[] getPubkey(String key) {
+ return getPubkey(getBytes(key));
+ }
+
+ private boolean delKey(byte[] key) {
+ execute('k', key);
+ return mError == NO_ERROR;
+ }
+
+ public boolean delKey(String key) {
+ return delKey(getBytes(key));
+ }
+
+ private byte[] sign(byte[] keyName, byte[] data) {
+ final ArrayList<byte[]> values = execute('n', keyName, data);
+ return (values == null || values.isEmpty()) ? null : values.get(0);
+ }
+
+ public byte[] sign(String key, byte[] data) {
+ return sign(getBytes(key), data);
+ }
+
+ private boolean verify(byte[] keyName, byte[] data, byte[] signature) {
+ execute('v', keyName, data, signature);
+ return mError == NO_ERROR;
+ }
+
+ public boolean verify(String key, byte[] data, byte[] signature) {
+ return verify(getBytes(key), data, signature);
+ }
+
+ private boolean grant(byte[] key, byte[] uid) {
+ execute('x', key, uid);
+ return mError == NO_ERROR;
+ }
+
+ public boolean grant(String key, int uid) {
+ return grant(getBytes(key), Integer.toString(uid).getBytes());
+ }
+
+ private boolean ungrant(byte[] key, byte[] uid) {
+ execute('y', key, uid);
+ return mError == NO_ERROR;
+ }
+
+ public boolean ungrant(String key, int uid) {
+ return ungrant(getBytes(key), Integer.toString(uid).getBytes());
+ }
+
public int getLastError() {
return mError;
}
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
index 15e253cee33e..008d682799d5 100755
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ b/keystore/tests/src/android/security/KeyStoreTest.java
@@ -44,19 +44,73 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
private static final String TEST_I18N_KEY = "\u4F60\u597D, \u4E16\u754C";
private static final byte[] TEST_I18N_VALUE = TEST_I18N_KEY.getBytes(Charsets.UTF_8);
+ // Test vector data for signatures
+ private static final byte[] TEST_DATA = {
+ (byte) 0x00, (byte) 0xA0, (byte) 0xFF, (byte) 0x0A, (byte) 0x00, (byte) 0xFF,
+ (byte) 0xAA, (byte) 0x55, (byte) 0x05, (byte) 0x5A,
+ };
+
private KeyStore mKeyStore = null;
public KeyStoreTest() {
super(Activity.class);
}
+ private static final byte[] PRIVKEY_BYTES = hexToBytes(
+ "308204BE020100300D06092A864886F70D0101010500048204A8308204A4020100028201" +
+ "0100E0473E8AB8F2284FEB9E742FF9748FA118ED98633C92F52AEB7A2EBE0D3BE60329BE" +
+ "766AD10EB6A515D0D2CFD9BEA7930F0C306537899F7958CD3E85B01F8818524D312584A9" +
+ "4B251E3625B54141EDBFEE198808E1BB97FC7CB49B9EAAAF68E9C98D7D0EDC53BBC0FA00" +
+ "34356D6305FBBCC3C7001405386ABBC873CB0F3EF7425F3D33DF7B315AE036D2A0B66AFD" +
+ "47503B169BF36E3B5162515B715FDA83DEAF2C58AEB9ABFB3097C3CC9DD9DBE5EF296C17" +
+ "6139028E8A671E63056D45F40188D2C4133490845DE52C2534E9C6B2478C07BDAE928823" +
+ "B62D066C7770F9F63F3DBA247F530844747BE7AAA85D853B8BD244ACEC3DE3C89AB46453" +
+ "AB4D24C3AC6902030100010282010037784776A5F17698F5AC960DFB83A1B67564E648BD" +
+ "0597CF8AB8087186F2669C27A9ECBDD480F0197A80D07309E6C6A96F925331E57F8B4AC6" +
+ "F4D45EDA45A23269C09FC428C07A4E6EDF738A15DEC97FABD2F2BB47A14F20EA72FCFE4C" +
+ "36E01ADA77BD137CD8D4DA10BB162E94A4662971F175F985FA188F056CB97EE2816F43AB" +
+ "9D3747612486CDA8C16196C30818A995EC85D38467791267B3BF21F273710A6925862576" +
+ "841C5B6712C12D4BD20A2F3299ADB7C135DA5E9515ABDA76E7CAF2A3BE80551D073B78BF" +
+ "1162C48AD2B7F4743A0238EE4D252F7D5E7E6533CCAE64CCB39360075A2FD1E034EC3AE5" +
+ "CE9C408CCBF0E25E4114021687B3DD4754AE8102818100F541884BC3737B2922D4119EF4" +
+ "5E2DEE2CD4CBB75F45505A157AA5009F99C73A2DF0724AC46024306332EA898177634546" +
+ "5DC6DF1E0A6F140AFF3B7396E6A8994AC5DAA96873472FE37749D14EB3E075E629DBEB35" +
+ "83338A6F3649D0A2654A7A42FD9AB6BFA4AC4D481D390BB229B064BDC311CC1BE1B63189" +
+ "DA7C40CDECF2B102818100EA1A742DDB881CEDB7288C87E38D868DD7A409D15A43F445D5" +
+ "377A0B5731DDBFCA2DAF28A8E13CD5C0AFCEC3347D74A39E235A3CD9633F274DE2B94F92" +
+ "DF43833911D9E9F1CF58F27DE2E08FF45964C720D3EC2139DC7CAFC912953CDECB2F355A" +
+ "2E2C35A50FAD754CB3B23166424BA3B6E3112A2B898C38C5C15EDB238693390281805182" +
+ "8F1EC6FD996029901BAF1D7E337BA5F0AF27E984EAD895ACE62BD7DF4EE45A224089F2CC" +
+ "151AF3CD173FCE0474BCB04F386A2CDCC0E0036BA2419F54579262D47100BE931984A3EF" +
+ "A05BECF141574DC079B3A95C4A83E6C43F3214D6DF32D512DE198085E531E616B83FD7DD" +
+ "9D1F4E2607C3333D07C55D107D1D3893587102818100DB4FB50F50DE8EDB53FF34C80931" +
+ "88A0512867DA2CCA04897759E587C244010DAF8664D59E8083D16C164789301F67A9F078" +
+ "060D834A2ADBD367575B68A8A842C2B02A89B3F31FCCEC8A22FE395795C5C6C7422B4E5D" +
+ "74A1E9A8F30E7759B9FC2D639C1F15673E84E93A5EF1506F4315383C38D45CBD1B14048F" +
+ "4721DC82326102818100D8114593AF415FB612DBF1923710D54D07486205A76A3B431949" +
+ "68C0DFF1F11EF0F61A4A337D5FD3741BBC9640E447B8B6B6C47C3AC1204357D3B0C55BA9" +
+ "286BDA73F629296F5FA9146D8976357D3C751E75148696A40B74685C82CE30902D639D72" +
+ "4FF24D5E2E9407EE34EDED2E3B4DF65AA9BCFEB6DF28D07BA6903F165768");
+
+
+ private static byte[] hexToBytes(String s) {
+ int len = s.length();
+ byte[] data = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(
+ s.charAt(i + 1), 16));
+ }
+ return data;
+ }
+
@Override
protected void setUp() throws Exception {
mKeyStore = KeyStore.getInstance();
if (mKeyStore.state() != KeyStore.State.UNINITIALIZED) {
mKeyStore.reset();
}
- assertEquals(KeyStore.State.UNINITIALIZED, mKeyStore.state());
+ assertEquals("KeyStore should be in an uninitialized state",
+ KeyStore.State.UNINITIALIZED, mKeyStore.state());
super.setUp();
}
@@ -164,4 +218,183 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
mKeyStore.reset();
assertTrue(mKeyStore.isEmpty());
}
+
+ public void testGenerate_NotInitialized_Fail() throws Exception {
+ assertFalse("Should fail when keystore is not initialized",
+ mKeyStore.generate(TEST_KEYNAME));
+ }
+
+ public void testGenerate_Locked_Fail() throws Exception {
+ mKeyStore.password(TEST_PASSWD);
+ mKeyStore.lock();
+ assertFalse("Should fail when keystore is locked", mKeyStore.generate(TEST_KEYNAME));
+ }
+
+ public void testGenerate_Success() throws Exception {
+ mKeyStore.password(TEST_PASSWD);
+
+ assertTrue("Should be able to generate key when unlocked",
+ mKeyStore.generate(TEST_KEYNAME));
+ }
+
+ public void testImport_Success() throws Exception {
+ mKeyStore.password(TEST_PASSWD);
+
+ assertTrue("Should be able to import key when unlocked",
+ mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES));
+ }
+
+ public void testImport_Failure_BadEncoding() throws Exception {
+ mKeyStore.password(TEST_PASSWD);
+
+ assertFalse("Invalid DER-encoded key should not be imported",
+ mKeyStore.importKey(TEST_KEYNAME, TEST_DATA));
+ }
+
+ public void testSign_Success() throws Exception {
+ mKeyStore.password(TEST_PASSWD);
+
+ assertTrue(mKeyStore.generate(TEST_KEYNAME));
+ final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
+
+ assertNotNull("Signature should not be null", signature);
+ }
+
+ public void testVerify_Success() throws Exception {
+ mKeyStore.password(TEST_PASSWD);
+
+ assertTrue(mKeyStore.generate(TEST_KEYNAME));
+ final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
+
+ assertNotNull("Signature should not be null", signature);
+
+ assertTrue("Signature should verify with same data",
+ mKeyStore.verify(TEST_KEYNAME, TEST_DATA, signature));
+ }
+
+ public void testSign_NotInitialized_Failure() throws Exception {
+ assertNull("Should not be able to sign without first initializing the keystore",
+ mKeyStore.sign(TEST_KEYNAME, TEST_DATA));
+ }
+
+ public void testSign_NotGenerated_Failure() throws Exception {
+ mKeyStore.password(TEST_PASSWD);
+
+ assertNull("Should not be able to sign without first generating keys",
+ mKeyStore.sign(TEST_KEYNAME, TEST_DATA));
+ }
+
+ public void testGrant_Generated_Success() throws Exception {
+ assertTrue("Password should work for keystore",
+ mKeyStore.password(TEST_PASSWD));
+
+ assertTrue("Should be able to generate key for testcase",
+ mKeyStore.generate(TEST_KEYNAME));
+
+ assertTrue("Should be able to grant key to other user",
+ mKeyStore.grant(TEST_KEYNAME, 0));
+ }
+
+ public void testGrant_Imported_Success() throws Exception {
+ assertTrue("Password should work for keystore", mKeyStore.password(TEST_PASSWD));
+
+ assertTrue("Should be able to import key for testcase",
+ mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES));
+
+ assertTrue("Should be able to grant key to other user", mKeyStore.grant(TEST_KEYNAME, 0));
+ }
+
+ public void testGrant_NoKey_Failure() throws Exception {
+ assertTrue("Should be able to unlock keystore for test",
+ mKeyStore.password(TEST_PASSWD));
+
+ assertFalse("Should not be able to grant without first initializing the keystore",
+ mKeyStore.grant(TEST_KEYNAME, 0));
+ }
+
+ public void testGrant_NotInitialized_Failure() throws Exception {
+ assertFalse("Should not be able to grant without first initializing the keystore",
+ mKeyStore.grant(TEST_KEYNAME, 0));
+ }
+
+ public void testUngrant_Generated_Success() throws Exception {
+ assertTrue("Password should work for keystore",
+ mKeyStore.password(TEST_PASSWD));
+
+ assertTrue("Should be able to generate key for testcase",
+ mKeyStore.generate(TEST_KEYNAME));
+
+ assertTrue("Should be able to grant key to other user",
+ mKeyStore.grant(TEST_KEYNAME, 0));
+
+ assertTrue("Should be able to ungrant key to other user",
+ mKeyStore.ungrant(TEST_KEYNAME, 0));
+ }
+
+ public void testUngrant_Imported_Success() throws Exception {
+ assertTrue("Password should work for keystore",
+ mKeyStore.password(TEST_PASSWD));
+
+ assertTrue("Should be able to import key for testcase",
+ mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES));
+
+ assertTrue("Should be able to grant key to other user",
+ mKeyStore.grant(TEST_KEYNAME, 0));
+
+ assertTrue("Should be able to ungrant key to other user",
+ mKeyStore.ungrant(TEST_KEYNAME, 0));
+ }
+
+ public void testUngrant_NotInitialized_Failure() throws Exception {
+ assertFalse("Should fail to ungrant key when keystore not initialized",
+ mKeyStore.ungrant(TEST_KEYNAME, 0));
+ }
+
+ public void testUngrant_NoGrant_Failure() throws Exception {
+ assertTrue("Password should work for keystore",
+ mKeyStore.password(TEST_PASSWD));
+
+ assertTrue("Should be able to generate key for testcase",
+ mKeyStore.generate(TEST_KEYNAME));
+
+ assertFalse("Should not be able to revoke not existent grant",
+ mKeyStore.ungrant(TEST_KEYNAME, 0));
+ }
+
+ public void testUngrant_DoubleUngrant_Failure() throws Exception {
+ assertTrue("Password should work for keystore",
+ mKeyStore.password(TEST_PASSWD));
+
+ assertTrue("Should be able to generate key for testcase",
+ mKeyStore.generate(TEST_KEYNAME));
+
+ assertTrue("Should be able to grant key to other user",
+ mKeyStore.grant(TEST_KEYNAME, 0));
+
+ assertTrue("Should be able to ungrant key to other user",
+ mKeyStore.ungrant(TEST_KEYNAME, 0));
+
+ assertFalse("Should fail to ungrant key to other user second time",
+ mKeyStore.ungrant(TEST_KEYNAME, 0));
+ }
+
+ public void testUngrant_DoubleGrantUngrant_Failure() throws Exception {
+ assertTrue("Password should work for keystore",
+ mKeyStore.password(TEST_PASSWD));
+
+ assertTrue("Should be able to generate key for testcase",
+ mKeyStore.generate(TEST_KEYNAME));
+
+ assertTrue("Should be able to grant key to other user",
+ mKeyStore.grant(TEST_KEYNAME, 0));
+
+ assertTrue("Should be able to grant key to other user a second time",
+ mKeyStore.grant(TEST_KEYNAME, 0));
+
+ assertTrue("Should be able to ungrant key to other user",
+ mKeyStore.ungrant(TEST_KEYNAME, 0));
+
+ assertFalse("Should fail to ungrant key to other user second time",
+ mKeyStore.ungrant(TEST_KEYNAME, 0));
+ }
}