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

Commit 8e7df37c authored by Alex Klyubin's avatar Alex Klyubin Committed by Android Git Automerger
Browse files

am d880dc2f: am 274a4ee3: Merge "Symmetric key import for AndroidKeyStore."

* commit 'd880dc2f':
  Symmetric key import for AndroidKeyStore.
parents c1c3b88e d880dc2f
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