Export KeyFactory backed by Android Keystore.

The KeyFactory can be used to obtain information (KeyInfo) about
Android Keystore private keys.

Bug: 18088752
Change-Id: Ied1a69928f391537de6765cef7dc7d7241cf62bb
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java
new file mode 100644
index 0000000..20db41b
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 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.keystore;
+
+import android.security.Credentials;
+import android.security.KeyStore;
+
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+
+/**
+ * {@link KeyFactorySpi} backed by Android KeyStore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi {
+
+    private final KeyStore mKeyStore = KeyStore.getInstance();
+
+    @Override
+    protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpecClass)
+            throws InvalidKeySpecException {
+        if (keySpecClass == null) {
+            throw new InvalidKeySpecException("keySpecClass == null");
+        }
+        if (!(key instanceof AndroidKeyStorePrivateKey)) {
+            throw new InvalidKeySpecException("Only Android KeyStore private keys supported: " +
+                    ((key != null) ? key.getClass().getName() : "null"));
+        }
+        if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpecClass)) {
+            throw new InvalidKeySpecException(
+                    "Key material export of Android KeyStore keys is not supported");
+        }
+        if (!KeyInfo.class.equals(keySpecClass)) {
+            throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
+        }
+        String keyAliasInKeystore = ((AndroidKeyStoreKey) key).getAlias();
+        String entryAlias;
+        if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) {
+            entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length());
+        } else {
+            throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore);
+        }
+
+        @SuppressWarnings("unchecked")
+        T result = (T) AndroidKeyStoreSecretKeyFactorySpi.getKeyInfo(
+                mKeyStore, entryAlias, keyAliasInKeystore);
+        return result;
+    }
+
+    @Override
+    protected PrivateKey engineGeneratePrivate(KeySpec spec) throws InvalidKeySpecException {
+        throw new UnsupportedOperationException(
+                "To generate a key pair in Android KeyStore, use KeyPairGenerator initialized with"
+                + " " + KeyGenParameterSpec.class.getName());
+    }
+
+    @Override
+    protected PublicKey engineGeneratePublic(KeySpec spec) throws InvalidKeySpecException {
+        throw new UnsupportedOperationException(
+                "To generate a key pair in Android KeyStore, use KeyPairGenerator initialized with"
+                + " " + KeyGenParameterSpec.class.getName());
+    }
+
+    @Override
+    protected Key engineTranslateKey(Key arg0) throws InvalidKeyException {
+        throw new UnsupportedOperationException(
+                "To import a key into Android KeyStore, use KeyStore.setEntry with "
+                + KeyProtection.class.getName());
+    }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index 649a515..1cd49d7 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -52,6 +52,10 @@
         put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
         put("KeyPairGenerator.RSA", PACKAGE_NAME +  ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
 
+        // java.security.KeyFactory
+        putKeyFactoryImpl("EC");
+        putKeyFactoryImpl("RSA");
+
         // javax.crypto.KeyGenerator
         put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
         put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA1");
@@ -100,6 +104,10 @@
         put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreSecretKeyFactorySpi");
     }
 
+    private void putKeyFactoryImpl(String algorithm) {
+        put("KeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreKeyFactorySpi");
+    }
+
     /**
      * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto
      * primitive.
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 455f170..8b00821 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -21,9 +21,8 @@
 import android.security.keymaster.KeyCharacteristics;
 import android.security.keymaster.KeymasterDefs;
 
-import libcore.util.EmptyArray;
-
 import java.security.InvalidKeyException;
+import java.security.ProviderException;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.KeySpec;
 import java.util.ArrayList;
@@ -35,7 +34,7 @@
 import javax.crypto.spec.SecretKeySpec;
 
 /**
- * {@link SecretKeyFactorySpi} backed by Android KeyStore.
+ * {@link SecretKeyFactorySpi} backed by Android Keystore.
  *
  * @hide
  */
@@ -60,7 +59,7 @@
         if (!KeyInfo.class.equals(keySpecClass)) {
             throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
         }
-        String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias();
+        String keyAliasInKeystore = ((AndroidKeyStoreKey) key).getAlias();
         String entryAlias;
         if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
             entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
@@ -68,11 +67,15 @@
             throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore);
         }
 
+        return getKeyInfo(mKeyStore, entryAlias, keyAliasInKeystore);
+    }
+
+    static KeyInfo getKeyInfo(KeyStore keyStore, String entryAlias, String keyAliasInKeystore) {
         KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
         int errorCode =
-                mKeyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics);
+                keyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics);
         if (errorCode != KeyStore.NO_ERROR) {
-            throw new InvalidKeySpecException("Failed to obtain information about key."
+            throw new ProviderException("Failed to obtain information about key."
                     + " Keystore error: " + errorCode);
         }
 
@@ -81,6 +84,7 @@
         int keySize;
         @KeyProperties.PurposeEnum int purposes;
         String[] encryptionPaddings;
+        String[] signaturePaddings;
         @KeyProperties.DigestEnum String[] digests;
         @KeyProperties.BlockModeEnum String[] blockModes;
         int keymasterSwEnforcedUserAuthenticators;
@@ -95,29 +99,40 @@
                 origin = KeyProperties.Origin.fromKeymaster(
                         keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1));
             } else {
-                throw new InvalidKeySpecException("Key origin not available");
+                throw new ProviderException("Key origin not available");
             }
             Integer keySizeInteger = keyCharacteristics.getInteger(KeymasterDefs.KM_TAG_KEY_SIZE);
             if (keySizeInteger == null) {
-                throw new InvalidKeySpecException("Key size not available");
+                throw new ProviderException("Key size not available");
             }
             keySize = keySizeInteger;
             purposes = KeyProperties.Purpose.allFromKeymaster(
                     keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PURPOSE));
 
             List<String> encryptionPaddingsList = new ArrayList<String>();
+            List<String> signaturePaddingsList = new ArrayList<String>();
+            // Keymaster stores both types of paddings in the same array -- we split it into two.
             for (int keymasterPadding : keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PADDING)) {
-                @KeyProperties.EncryptionPaddingEnum String jcaPadding;
                 try {
-                    jcaPadding = KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding);
+                    @KeyProperties.EncryptionPaddingEnum String jcaPadding =
+                            KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding);
+                    encryptionPaddingsList.add(jcaPadding);
                 } catch (IllegalArgumentException e) {
-                    throw new InvalidKeySpecException(
-                            "Unsupported encryption padding: " + keymasterPadding);
+                    try {
+                        @KeyProperties.SignaturePaddingEnum String padding =
+                                KeyProperties.SignaturePadding.fromKeymaster(keymasterPadding);
+                        signaturePaddingsList.add(padding);
+                    } catch (IllegalArgumentException e2) {
+                        throw new ProviderException(
+                                "Unsupported encryption padding: " + keymasterPadding);
+                    }
                 }
-                encryptionPaddingsList.add(jcaPadding);
+
             }
             encryptionPaddings =
                     encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]);
+            signaturePaddings =
+                    signaturePaddingsList.toArray(new String[signaturePaddingsList.size()]);
 
             digests = KeyProperties.Digest.allFromKeymaster(
                     keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST));
@@ -128,7 +143,7 @@
             keymasterHwEnforcedUserAuthenticators =
                     keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
         } catch (IllegalArgumentException e) {
-            throw new InvalidKeySpecException("Unsupported key characteristic", e);
+            throw new ProviderException("Unsupported key characteristic", e);
         }
 
         Date keyValidityStart = keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME);
@@ -164,7 +179,7 @@
                 keyValidityForConsumptionEnd,
                 purposes,
                 encryptionPaddings,
-                EmptyArray.STRING, // no signature paddings -- this is symmetric crypto
+                signaturePaddings,
                 digests,
                 blockModes,
                 userAuthenticationRequired,
@@ -175,12 +190,14 @@
     @Override
     protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException {
         throw new UnsupportedOperationException(
-                "Key import into Android KeyStore is not supported");
+                "To generate secret key in Android KeyStore, use KeyGenerator initialized with "
+                        + KeyGenParameterSpec.class.getName());
     }
 
     @Override
     protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException {
         throw new UnsupportedOperationException(
-                "Key import into Android KeyStore is not supported");
+                "To import a secret key into Android KeyStore, use KeyStore.setEntry with "
+                + KeyProtection.class.getName());
     }
 }