Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 274a4ee3 authored by Alex Klyubin's avatar Alex Klyubin Committed by Gerrit Code Review
Browse files

Merge "Symmetric key import for AndroidKeyStore."

parents ee80414d baf2838f
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -136,6 +136,8 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi {
            throw new IllegalStateException("could not generate key in keystore");
        }

        Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);

        final PrivateKey privKey;
        final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
        try {
+253 −15
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@ package android.security;
import com.android.org.conscrypt.OpenSSLEngine;
import com.android.org.conscrypt.OpenSSLKeyHolder;

import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.util.Log;

import java.io.ByteArrayInputStream;
@@ -31,6 +34,7 @@ import java.security.KeyStore.Entry;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStore.ProtectionParameter;
import java.security.KeyStore;
import java.security.KeyStore.SecretKeyEntry;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
@@ -50,6 +54,8 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.crypto.SecretKey;

/**
 * A java.security.KeyStore interface for the Android KeyStore. An instance of
 * it can be created via the {@link java.security.KeyStore#getInstance(String)
@@ -77,10 +83,7 @@ public class AndroidKeyStore extends KeyStoreSpi {
    @Override
    public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
            UnrecoverableKeyException {
        if (!isKeyEntry(alias)) {
            return null;
        }

        if (isPrivateKeyEntry(alias)) {
            final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
            try {
                return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias);
@@ -89,6 +92,63 @@ public class AndroidKeyStore extends KeyStoreSpi {
                t.initCause(e);
                throw t;
            }
        } else if (isSecretKeyEntry(alias)) {
            KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
            String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias;
            int errorCode = mKeyStore.getKeyCharacteristics(
                    keyAliasInKeystore, null, null, keyCharacteristics);
            if ((errorCode != KeymasterDefs.KM_ERROR_OK)
                    && (errorCode != android.security.KeyStore.NO_ERROR)) {
                throw new UnrecoverableKeyException("Failed to load information about key."
                        + " Error code: " + errorCode);
            }

            int keymasterAlgorithm =
                    keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
            if (keymasterAlgorithm == -1) {
                keymasterAlgorithm =
                        keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
            }
            if (keymasterAlgorithm == -1) {
                throw new UnrecoverableKeyException("Key algorithm unknown");
            }
            @KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm;
            try {
                keyAlgorithm = KeyStoreKeyConstraints.Algorithm.fromKeymaster(keymasterAlgorithm);
            } catch (IllegalArgumentException e) {
                throw (UnrecoverableKeyException)
                        new UnrecoverableKeyException("Unsupported key algorithm").initCause(e);
            }

            int keymasterDigest =
                    keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_DIGEST, -1);
            if (keymasterDigest == -1) {
                keymasterDigest =
                        keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_DIGEST, -1);
            }
            @KeyStoreKeyConstraints.DigestEnum Integer digest = null;
            if (keymasterDigest != -1) {
                try {
                    digest = KeyStoreKeyConstraints.Digest.fromKeymaster(keymasterDigest);
                } catch (IllegalArgumentException e) {
                    throw (UnrecoverableKeyException)
                            new UnrecoverableKeyException("Unsupported digest").initCause(e);
                }
            }

            String keyAlgorithmString;
            try {
                keyAlgorithmString = KeyStoreKeyConstraints.Algorithm.toJCASecretKeyAlgorithm(
                    keyAlgorithm, digest);
            } catch (IllegalArgumentException e) {
                throw (UnrecoverableKeyException)
                        new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
            }

            return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString);
        }

        return null;
    }

    @Override
@@ -186,6 +246,11 @@ public class AndroidKeyStore extends KeyStoreSpi {
            return d;
        }

        d = getModificationDate(Credentials.USER_SECRET_KEY + alias);
        if (d != null) {
            return d;
        }

        d = getModificationDate(Credentials.USER_CERTIFICATE + alias);
        if (d != null) {
            return d;
@@ -203,8 +268,10 @@ public class AndroidKeyStore extends KeyStoreSpi {

        if (key instanceof PrivateKey) {
            setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
        } else if (key instanceof SecretKey) {
            setSecretKeyEntry(alias, (SecretKey) key, null);
        } else {
            throw new KeyStoreException("Only PrivateKeys are supported");
            throw new KeyStoreException("Only PrivateKey and SecretKey are supported");
        }
    }

@@ -319,6 +386,7 @@ public class AndroidKeyStore extends KeyStoreSpi {
            Credentials.deleteAllTypesForAlias(mKeyStore, alias);
        } else {
            Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
            Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
        }

        final int flags = (params == null) ? 0 : params.getFlags();
@@ -340,6 +408,160 @@ public class AndroidKeyStore extends KeyStoreSpi {
        }
    }

    private void setSecretKeyEntry(String entryAlias, SecretKey key, KeyStoreParameter params)
            throws KeyStoreException {
        if (key instanceof KeyStoreSecretKey) {
            // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot
            // overwrite its own entry.
            String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias();
            if (keyAliasInKeystore == null) {
                throw new KeyStoreException("KeyStore-backed secret key does not have an alias");
            }
            if (!keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
                throw new KeyStoreException("KeyStore-backed secret key has invalid alias: "
                        + keyAliasInKeystore);
            }
            String keyEntryAlias =
                    keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
            if (!entryAlias.equals(keyEntryAlias)) {
                throw new KeyStoreException("Can only replace KeyStore-backed keys with same"
                        + " alias: " + entryAlias + " != " + keyEntryAlias);
            }
            // This is the entry where this key is already stored. No need to do anything.
            if (params != null) {
                throw new KeyStoreException("Modifying KeyStore-backed key using protection"
                        + " parameters not supported");
            }
            return;
        }

        if (params == null) {
            throw new KeyStoreException(
                    "Protection parameters must be specified when importing a symmetric key");
        }

        // Not a KeyStore-backed secret key -- import its key material into keystore.
        String keyExportFormat = key.getFormat();
        if (keyExportFormat == null) {
            throw new KeyStoreException(
                    "Only secret keys that export their key material are supported");
        } else if (!"RAW".equals(keyExportFormat)) {
            throw new KeyStoreException(
                    "Unsupported secret key material export format: " + keyExportFormat);
        }
        byte[] keyMaterial = key.getEncoded();
        if (keyMaterial == null) {
            throw new KeyStoreException("Key did not export its key material despite supporting"
                    + " RAW format export");
        }

        String keyAlgorithmString = key.getAlgorithm();
        @KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm;
        @KeyStoreKeyConstraints.AlgorithmEnum Integer digest;
        try {
            keyAlgorithm =
                    KeyStoreKeyConstraints.Algorithm.fromJCASecretKeyAlgorithm(keyAlgorithmString);
            digest = KeyStoreKeyConstraints.Digest.fromJCASecretKeyAlgorithm(keyAlgorithmString);
        } catch (IllegalArgumentException e) {
            throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString);
        }

        if ((params.getAlgorithm() != null) && (params.getAlgorithm() != keyAlgorithm)) {
            throw new KeyStoreException("Key algorithm mismatch. Key: " + keyAlgorithmString
                    + ", parameter spec: "
                    + KeyStoreKeyConstraints.Algorithm.toString(params.getAlgorithm()));
        }

        KeymasterArguments args = new KeymasterArguments();
        args.addInt(KeymasterDefs.KM_TAG_ALGORITHM,
                KeyStoreKeyConstraints.Algorithm.toKeymaster(keyAlgorithm));

        if (digest != null) {
            // Digest available from JCA key algorithm
            if (params.getDigest() != null) {
                // Digest also specified in parameters -- check that these two match
                if (digest != params.getDigest()) {
                    throw new KeyStoreException("Key digest mismatch. Key: " + keyAlgorithmString
                            + ", parameter spec: "
                            + KeyStoreKeyConstraints.Digest.toString(params.getDigest()));
                }
            }
        } else {
            // Digest not available from JCA key algorithm
            digest = params.getDigest();
        }
        if (digest != null) {
            args.addInt(KeymasterDefs.KM_TAG_DIGEST,
                    KeyStoreKeyConstraints.Digest.toKeymaster(digest));
        }

        @KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null)
                ? params.getPurposes()
                : (KeyStoreKeyConstraints.Purpose.ENCRYPT
                        | KeyStoreKeyConstraints.Purpose.DECRYPT
                        | KeyStoreKeyConstraints.Purpose.SIGN
                        | KeyStoreKeyConstraints.Purpose.VERIFY);
        for (int keymasterPurpose :
            KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
            args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
        }
        if (params.getBlockMode() != null) {
            args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE,
                    KeyStoreKeyConstraints.BlockMode.toKeymaster(params.getBlockMode()));
        }
        if (params.getPadding() != null) {
            args.addInt(KeymasterDefs.KM_TAG_PADDING,
                    KeyStoreKeyConstraints.Padding.toKeymaster(params.getPadding()));
        }
        if (params.getMaxUsesPerBoot() != null) {
            args.addInt(KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT, params.getMaxUsesPerBoot());
        }
        if (params.getMinSecondsBetweenOperations() != null) {
            args.addInt(KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS,
                    params.getMinSecondsBetweenOperations());
        }
        if (params.getUserAuthenticators().isEmpty()) {
            args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
        } else {
        // TODO: Pass-in user authenticator IDs once the Keymaster API has stabilized
//            for (int userAuthenticatorId : params.getUserAuthenticators()) {
//                args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_ID, userAuthenticatorId);
//            }
        }
        if (params.getUserAuthenticationValidityDurationSeconds() != null) {
            args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
                    params.getUserAuthenticationValidityDurationSeconds());
        }
        if (params.getKeyValidityStart() != null) {
            args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, params.getKeyValidityStart());
        }
        if (params.getKeyValidityForOriginationEnd() != null) {
            args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
                    params.getKeyValidityForOriginationEnd());
        }
        if (params.getKeyValidityForConsumptionEnd() != null) {
            args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
                    params.getKeyValidityForConsumptionEnd());
        }

        // TODO: Remove this once keymaster does not require us to specify the size of imported key.
        args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8);

        Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias);
        String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
        int errorCode = mKeyStore.importKey(
                keyAliasInKeystore,
                args,
                KeymasterDefs.KM_KEY_FORMAT_RAW,
                keyMaterial,
                params.getFlags(),
                new KeyCharacteristics());
        if (errorCode != android.security.KeyStore.NO_ERROR) {
            throw new KeyStoreException("Failed to import secret key. Keystore error code: "
                + errorCode);
        }
    }

    @Override
    public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)
            throws KeyStoreException {
@@ -413,6 +635,7 @@ public class AndroidKeyStore extends KeyStoreSpi {
        }

        return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias)
                || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias)
                || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias)
                || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
    }
@@ -428,6 +651,10 @@ public class AndroidKeyStore extends KeyStoreSpi {
    }

    private boolean isKeyEntry(String alias) {
        return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias);
    }

    private boolean isPrivateKeyEntry(String alias) {
        if (alias == null) {
            throw new NullPointerException("alias == null");
        }
@@ -435,6 +662,14 @@ public class AndroidKeyStore extends KeyStoreSpi {
        return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias);
    }

    private boolean isSecretKeyEntry(String alias) {
        if (alias == null) {
            throw new NullPointerException("alias == null");
        }

        return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias);
    }

    private boolean isCertificateEntry(String alias) {
        if (alias == null) {
            throw new NullPointerException("alias == null");
@@ -554,11 +789,14 @@ public class AndroidKeyStore extends KeyStoreSpi {
            PrivateKeyEntry prE = (PrivateKeyEntry) entry;
            setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(),
                    (KeyStoreParameter) param);
            return;
        }

        } else if (entry instanceof SecretKeyEntry) {
            SecretKeyEntry secE = (SecretKeyEntry) entry;
            setSecretKeyEntry(alias, secE.getSecretKey(), (KeyStoreParameter) param);
        } else {
            throw new KeyStoreException(
                "Entry must be a PrivateKeyEntry or TrustedCertificateEntry; was " + entry);
                    "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry"
                    + "; was " + entry);
        }
    }

}
+21 −1
Original line number Diff line number Diff line
@@ -61,6 +61,9 @@ public class Credentials {
    /** Key prefix for user private keys. */
    public static final String USER_PRIVATE_KEY = "USRPKEY_";

    /** Key prefix for user secret keys. */
    public static final String USER_SECRET_KEY = "USRSKEY_";

    /** Key prefix for VPN. */
    public static final String VPN = "VPN_";

@@ -218,7 +221,8 @@ public class Credentials {
         * Make sure every type is deleted. There can be all three types, so
         * don't use a conditional here.
         */
        return keystore.delKey(Credentials.USER_PRIVATE_KEY + alias)
        return keystore.delete(Credentials.USER_PRIVATE_KEY + alias)
                | keystore.delete(Credentials.USER_SECRET_KEY + alias)
                | deleteCertificateTypesForAlias(keystore, alias);
    }

@@ -235,4 +239,20 @@ public class Credentials {
        return keystore.delete(Credentials.USER_CERTIFICATE + alias)
                | keystore.delete(Credentials.CA_CERTIFICATE + alias);
    }

    /**
     * Delete private key for a particular {@code alias}.
     * Returns {@code true} if an entry was was deleted.
     */
    static boolean deletePrivateKeyTypeForAlias(KeyStore keystore, String alias) {
        return keystore.delete(Credentials.USER_PRIVATE_KEY + alias);
    }

    /**
     * Delete secret key for a particular {@code alias}.
     * Returns {@code true} if an entry was was deleted.
     */
    static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias) {
        return keystore.delete(Credentials.USER_SECRET_KEY + alias);
    }
}
+429 −0

File added.

Preview size limit exceeded, changes collapsed.

+391 −4

File changed.

Preview size limit exceeded, changes collapsed.

Loading