android.security.keystore.KeyProperties.java Source code

Java tutorial

Introduction

Here is the source code for android.security.keystore.KeyProperties.java

Source

/*
 * 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.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.security.keymaster.KeymasterDefs;

import libcore.util.EmptyArray;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.Locale;

/**
 * Properties of <a href="{@docRoot}training/articles/keystore.html">Android Keystore</a> keys.
 */
public abstract class KeyProperties {
    private KeyProperties() {
    }

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true, prefix = { "PURPOSE_" }, value = { PURPOSE_ENCRYPT, PURPOSE_DECRYPT, PURPOSE_SIGN,
            PURPOSE_VERIFY, PURPOSE_WRAP_KEY, })
    public @interface PurposeEnum {
    }

    /**
     * Purpose of key: encryption.
     */
    public static final int PURPOSE_ENCRYPT = 1 << 0;

    /**
     * Purpose of key: decryption.
     */
    public static final int PURPOSE_DECRYPT = 1 << 1;

    /**
     * Purpose of key: signing or generating a Message Authentication Code (MAC).
     */
    public static final int PURPOSE_SIGN = 1 << 2;

    /**
     * Purpose of key: signature or Message Authentication Code (MAC) verification.
     */
    public static final int PURPOSE_VERIFY = 1 << 3;

    /**
     * Purpose of key: wrapping and unwrapping wrapped keys for secure import.
     */
    public static final int PURPOSE_WRAP_KEY = 1 << 5;

    /**
     * @hide
     */
    public static abstract class Purpose {
        private Purpose() {
        }

        public static int toKeymaster(@PurposeEnum int purpose) {
            switch (purpose) {
            case PURPOSE_ENCRYPT:
                return KeymasterDefs.KM_PURPOSE_ENCRYPT;
            case PURPOSE_DECRYPT:
                return KeymasterDefs.KM_PURPOSE_DECRYPT;
            case PURPOSE_SIGN:
                return KeymasterDefs.KM_PURPOSE_SIGN;
            case PURPOSE_VERIFY:
                return KeymasterDefs.KM_PURPOSE_VERIFY;
            case PURPOSE_WRAP_KEY:
                return KeymasterDefs.KM_PURPOSE_WRAP;
            default:
                throw new IllegalArgumentException("Unknown purpose: " + purpose);
            }
        }

        public static @PurposeEnum int fromKeymaster(int purpose) {
            switch (purpose) {
            case KeymasterDefs.KM_PURPOSE_ENCRYPT:
                return PURPOSE_ENCRYPT;
            case KeymasterDefs.KM_PURPOSE_DECRYPT:
                return PURPOSE_DECRYPT;
            case KeymasterDefs.KM_PURPOSE_SIGN:
                return PURPOSE_SIGN;
            case KeymasterDefs.KM_PURPOSE_VERIFY:
                return PURPOSE_VERIFY;
            case KeymasterDefs.KM_PURPOSE_WRAP:
                return PURPOSE_WRAP_KEY;
            default:
                throw new IllegalArgumentException("Unknown purpose: " + purpose);
            }
        }

        @NonNull
        public static int[] allToKeymaster(@PurposeEnum int purposes) {
            int[] result = getSetFlags(purposes);
            for (int i = 0; i < result.length; i++) {
                result[i] = toKeymaster(result[i]);
            }
            return result;
        }

        public static @PurposeEnum int allFromKeymaster(@NonNull Collection<Integer> purposes) {
            @PurposeEnum
            int result = 0;
            for (int keymasterPurpose : purposes) {
                result |= fromKeymaster(keymasterPurpose);
            }
            return result;
        }
    }

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @StringDef(prefix = { "KEY_" }, value = { KEY_ALGORITHM_RSA, KEY_ALGORITHM_EC, KEY_ALGORITHM_AES,
            KEY_ALGORITHM_HMAC_SHA1, KEY_ALGORITHM_HMAC_SHA224, KEY_ALGORITHM_HMAC_SHA256,
            KEY_ALGORITHM_HMAC_SHA384, KEY_ALGORITHM_HMAC_SHA512, })
    public @interface KeyAlgorithmEnum {
    }

    /** Rivest Shamir Adleman (RSA) key. */
    public static final String KEY_ALGORITHM_RSA = "RSA";

    /** Elliptic Curve (EC) Cryptography key. */
    public static final String KEY_ALGORITHM_EC = "EC";

    /** Advanced Encryption Standard (AES) key. */
    public static final String KEY_ALGORITHM_AES = "AES";

    /**
     * Triple Data Encryption Algorithm (3DES) key.
     *
     * @deprecated Included for interoperability with legacy systems. Prefer {@link
     * KeyProperties#KEY_ALGORITHM_AES} for new development.
     */
    @Deprecated
    public static final String KEY_ALGORITHM_3DES = "DESede";

    /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-1 as the hash. */
    public static final String KEY_ALGORITHM_HMAC_SHA1 = "HmacSHA1";

    /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-224 as the hash. */
    public static final String KEY_ALGORITHM_HMAC_SHA224 = "HmacSHA224";

    /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-256 as the hash. */
    public static final String KEY_ALGORITHM_HMAC_SHA256 = "HmacSHA256";

    /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-384 as the hash. */
    public static final String KEY_ALGORITHM_HMAC_SHA384 = "HmacSHA384";

    /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-512 as the hash. */
    public static final String KEY_ALGORITHM_HMAC_SHA512 = "HmacSHA512";

    /**
     * @hide
     */
    public static abstract class KeyAlgorithm {
        private KeyAlgorithm() {
        }

        public static int toKeymasterAsymmetricKeyAlgorithm(@NonNull @KeyAlgorithmEnum String algorithm) {
            if (KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) {
                return KeymasterDefs.KM_ALGORITHM_EC;
            } else if (KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) {
                return KeymasterDefs.KM_ALGORITHM_RSA;
            } else {
                throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm);
            }
        }

        @NonNull
        public static @KeyAlgorithmEnum String fromKeymasterAsymmetricKeyAlgorithm(int keymasterAlgorithm) {
            switch (keymasterAlgorithm) {
            case KeymasterDefs.KM_ALGORITHM_EC:
                return KEY_ALGORITHM_EC;
            case KeymasterDefs.KM_ALGORITHM_RSA:
                return KEY_ALGORITHM_RSA;
            default:
                throw new IllegalArgumentException("Unsupported key algorithm: " + keymasterAlgorithm);
            }
        }

        public static int toKeymasterSecretKeyAlgorithm(@NonNull @KeyAlgorithmEnum String algorithm) {
            if (KEY_ALGORITHM_AES.equalsIgnoreCase(algorithm)) {
                return KeymasterDefs.KM_ALGORITHM_AES;
            } else if (KEY_ALGORITHM_3DES.equalsIgnoreCase(algorithm)) {
                return KeymasterDefs.KM_ALGORITHM_3DES;
            } else if (algorithm.toUpperCase(Locale.US).startsWith("HMAC")) {
                return KeymasterDefs.KM_ALGORITHM_HMAC;
            } else {
                throw new IllegalArgumentException("Unsupported secret key algorithm: " + algorithm);
            }
        }

        @NonNull
        public static @KeyAlgorithmEnum String fromKeymasterSecretKeyAlgorithm(int keymasterAlgorithm,
                int keymasterDigest) {
            switch (keymasterAlgorithm) {
            case KeymasterDefs.KM_ALGORITHM_AES:
                return KEY_ALGORITHM_AES;
            case KeymasterDefs.KM_ALGORITHM_3DES:
                return KEY_ALGORITHM_3DES;
            case KeymasterDefs.KM_ALGORITHM_HMAC:
                switch (keymasterDigest) {
                case KeymasterDefs.KM_DIGEST_SHA1:
                    return KEY_ALGORITHM_HMAC_SHA1;
                case KeymasterDefs.KM_DIGEST_SHA_2_224:
                    return KEY_ALGORITHM_HMAC_SHA224;
                case KeymasterDefs.KM_DIGEST_SHA_2_256:
                    return KEY_ALGORITHM_HMAC_SHA256;
                case KeymasterDefs.KM_DIGEST_SHA_2_384:
                    return KEY_ALGORITHM_HMAC_SHA384;
                case KeymasterDefs.KM_DIGEST_SHA_2_512:
                    return KEY_ALGORITHM_HMAC_SHA512;
                default:
                    throw new IllegalArgumentException(
                            "Unsupported HMAC digest: " + Digest.fromKeymaster(keymasterDigest));
                }
            default:
                throw new IllegalArgumentException("Unsupported key algorithm: " + keymasterAlgorithm);
            }
        }

        /**
         * @hide
         *
         * @return keymaster digest or {@code -1} if the algorithm does not involve a digest.
         */
        public static int toKeymasterDigest(@NonNull @KeyAlgorithmEnum String algorithm) {
            String algorithmUpper = algorithm.toUpperCase(Locale.US);
            if (algorithmUpper.startsWith("HMAC")) {
                String digestUpper = algorithmUpper.substring("HMAC".length());
                switch (digestUpper) {
                case "SHA1":
                    return KeymasterDefs.KM_DIGEST_SHA1;
                case "SHA224":
                    return KeymasterDefs.KM_DIGEST_SHA_2_224;
                case "SHA256":
                    return KeymasterDefs.KM_DIGEST_SHA_2_256;
                case "SHA384":
                    return KeymasterDefs.KM_DIGEST_SHA_2_384;
                case "SHA512":
                    return KeymasterDefs.KM_DIGEST_SHA_2_512;
                default:
                    throw new IllegalArgumentException("Unsupported HMAC digest: " + digestUpper);
                }
            } else {
                return -1;
            }
        }
    }

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @StringDef(prefix = { "BLOCK_MODE_" }, value = { BLOCK_MODE_ECB, BLOCK_MODE_CBC, BLOCK_MODE_CTR,
            BLOCK_MODE_GCM, })
    public @interface BlockModeEnum {
    }

    /** Electronic Codebook (ECB) block mode. */
    public static final String BLOCK_MODE_ECB = "ECB";

    /** Cipher Block Chaining (CBC) block mode. */
    public static final String BLOCK_MODE_CBC = "CBC";

    /** Counter (CTR) block mode. */
    public static final String BLOCK_MODE_CTR = "CTR";

    /** Galois/Counter Mode (GCM) block mode. */
    public static final String BLOCK_MODE_GCM = "GCM";

    /**
     * @hide
     */
    public static abstract class BlockMode {
        private BlockMode() {
        }

        public static int toKeymaster(@NonNull @BlockModeEnum String blockMode) {
            if (BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) {
                return KeymasterDefs.KM_MODE_ECB;
            } else if (BLOCK_MODE_CBC.equalsIgnoreCase(blockMode)) {
                return KeymasterDefs.KM_MODE_CBC;
            } else if (BLOCK_MODE_CTR.equalsIgnoreCase(blockMode)) {
                return KeymasterDefs.KM_MODE_CTR;
            } else if (BLOCK_MODE_GCM.equalsIgnoreCase(blockMode)) {
                return KeymasterDefs.KM_MODE_GCM;
            } else {
                throw new IllegalArgumentException("Unsupported block mode: " + blockMode);
            }
        }

        @NonNull
        public static @BlockModeEnum String fromKeymaster(int blockMode) {
            switch (blockMode) {
            case KeymasterDefs.KM_MODE_ECB:
                return BLOCK_MODE_ECB;
            case KeymasterDefs.KM_MODE_CBC:
                return BLOCK_MODE_CBC;
            case KeymasterDefs.KM_MODE_CTR:
                return BLOCK_MODE_CTR;
            case KeymasterDefs.KM_MODE_GCM:
                return BLOCK_MODE_GCM;
            default:
                throw new IllegalArgumentException("Unsupported block mode: " + blockMode);
            }
        }

        @NonNull
        public static @BlockModeEnum String[] allFromKeymaster(@NonNull Collection<Integer> blockModes) {
            if ((blockModes == null) || (blockModes.isEmpty())) {
                return EmptyArray.STRING;
            }
            @BlockModeEnum
            String[] result = new String[blockModes.size()];
            int offset = 0;
            for (int blockMode : blockModes) {
                result[offset] = fromKeymaster(blockMode);
                offset++;
            }
            return result;
        }

        public static int[] allToKeymaster(@Nullable @BlockModeEnum String[] blockModes) {
            if ((blockModes == null) || (blockModes.length == 0)) {
                return EmptyArray.INT;
            }
            int[] result = new int[blockModes.length];
            for (int i = 0; i < blockModes.length; i++) {
                result[i] = toKeymaster(blockModes[i]);
            }
            return result;
        }
    }

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @StringDef(prefix = { "ENCRYPTION_PADDING_" }, value = { ENCRYPTION_PADDING_NONE, ENCRYPTION_PADDING_PKCS7,
            ENCRYPTION_PADDING_RSA_PKCS1, ENCRYPTION_PADDING_RSA_OAEP, })
    public @interface EncryptionPaddingEnum {
    }

    /**
     * No encryption padding.
     */
    public static final String ENCRYPTION_PADDING_NONE = "NoPadding";

    /**
     * PKCS#7 encryption padding scheme.
     */
    public static final String ENCRYPTION_PADDING_PKCS7 = "PKCS7Padding";

    /**
     * RSA PKCS#1 v1.5 padding scheme for encryption.
     */
    public static final String ENCRYPTION_PADDING_RSA_PKCS1 = "PKCS1Padding";

    /**
     * RSA Optimal Asymmetric Encryption Padding (OAEP) scheme.
     */
    public static final String ENCRYPTION_PADDING_RSA_OAEP = "OAEPPadding";

    /**
     * @hide
     */
    public static abstract class EncryptionPadding {
        private EncryptionPadding() {
        }

        public static int toKeymaster(@NonNull @EncryptionPaddingEnum String padding) {
            if (ENCRYPTION_PADDING_NONE.equalsIgnoreCase(padding)) {
                return KeymasterDefs.KM_PAD_NONE;
            } else if (ENCRYPTION_PADDING_PKCS7.equalsIgnoreCase(padding)) {
                return KeymasterDefs.KM_PAD_PKCS7;
            } else if (ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(padding)) {
                return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT;
            } else if (ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(padding)) {
                return KeymasterDefs.KM_PAD_RSA_OAEP;
            } else {
                throw new IllegalArgumentException("Unsupported encryption padding scheme: " + padding);
            }
        }

        @NonNull
        public static @EncryptionPaddingEnum String fromKeymaster(int padding) {
            switch (padding) {
            case KeymasterDefs.KM_PAD_NONE:
                return ENCRYPTION_PADDING_NONE;
            case KeymasterDefs.KM_PAD_PKCS7:
                return ENCRYPTION_PADDING_PKCS7;
            case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
                return ENCRYPTION_PADDING_RSA_PKCS1;
            case KeymasterDefs.KM_PAD_RSA_OAEP:
                return ENCRYPTION_PADDING_RSA_OAEP;
            default:
                throw new IllegalArgumentException("Unsupported encryption padding: " + padding);
            }
        }

        @NonNull
        public static int[] allToKeymaster(@Nullable @EncryptionPaddingEnum String[] paddings) {
            if ((paddings == null) || (paddings.length == 0)) {
                return EmptyArray.INT;
            }
            int[] result = new int[paddings.length];
            for (int i = 0; i < paddings.length; i++) {
                result[i] = toKeymaster(paddings[i]);
            }
            return result;
        }
    }

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @StringDef(prefix = { "SIGNATURE_PADDING_" }, value = { SIGNATURE_PADDING_RSA_PKCS1,
            SIGNATURE_PADDING_RSA_PSS, })
    public @interface SignaturePaddingEnum {
    }

    /**
     * RSA PKCS#1 v1.5 padding for signatures.
     */
    public static final String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";

    /**
     * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding.
     */
    public static final String SIGNATURE_PADDING_RSA_PSS = "PSS";

    static abstract class SignaturePadding {
        private SignaturePadding() {
        }

        static int toKeymaster(@NonNull @SignaturePaddingEnum String padding) {
            switch (padding.toUpperCase(Locale.US)) {
            case SIGNATURE_PADDING_RSA_PKCS1:
                return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN;
            case SIGNATURE_PADDING_RSA_PSS:
                return KeymasterDefs.KM_PAD_RSA_PSS;
            default:
                throw new IllegalArgumentException("Unsupported signature padding scheme: " + padding);
            }
        }

        @NonNull
        static @SignaturePaddingEnum String fromKeymaster(int padding) {
            switch (padding) {
            case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN:
                return SIGNATURE_PADDING_RSA_PKCS1;
            case KeymasterDefs.KM_PAD_RSA_PSS:
                return SIGNATURE_PADDING_RSA_PSS;
            default:
                throw new IllegalArgumentException("Unsupported signature padding: " + padding);
            }
        }

        @NonNull
        static int[] allToKeymaster(@Nullable @SignaturePaddingEnum String[] paddings) {
            if ((paddings == null) || (paddings.length == 0)) {
                return EmptyArray.INT;
            }
            int[] result = new int[paddings.length];
            for (int i = 0; i < paddings.length; i++) {
                result[i] = toKeymaster(paddings[i]);
            }
            return result;
        }
    }

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @StringDef(prefix = { "DIGEST_" }, value = { DIGEST_NONE, DIGEST_MD5, DIGEST_SHA1, DIGEST_SHA224, DIGEST_SHA256,
            DIGEST_SHA384, DIGEST_SHA512, })
    public @interface DigestEnum {
    }

    /**
     * No digest: sign/authenticate the raw message.
     */
    public static final String DIGEST_NONE = "NONE";

    /**
     * MD5 digest.
     */
    public static final String DIGEST_MD5 = "MD5";

    /**
     * SHA-1 digest.
     */
    public static final String DIGEST_SHA1 = "SHA-1";

    /**
     * SHA-2 224 (aka SHA-224) digest.
     */
    public static final String DIGEST_SHA224 = "SHA-224";

    /**
     * SHA-2 256 (aka SHA-256) digest.
     */
    public static final String DIGEST_SHA256 = "SHA-256";

    /**
     * SHA-2 384 (aka SHA-384) digest.
     */
    public static final String DIGEST_SHA384 = "SHA-384";

    /**
     * SHA-2 512 (aka SHA-512) digest.
     */
    public static final String DIGEST_SHA512 = "SHA-512";

    /**
     * @hide
     */
    public static abstract class Digest {
        private Digest() {
        }

        public static int toKeymaster(@NonNull @DigestEnum String digest) {
            switch (digest.toUpperCase(Locale.US)) {
            case DIGEST_SHA1:
                return KeymasterDefs.KM_DIGEST_SHA1;
            case DIGEST_SHA224:
                return KeymasterDefs.KM_DIGEST_SHA_2_224;
            case DIGEST_SHA256:
                return KeymasterDefs.KM_DIGEST_SHA_2_256;
            case DIGEST_SHA384:
                return KeymasterDefs.KM_DIGEST_SHA_2_384;
            case DIGEST_SHA512:
                return KeymasterDefs.KM_DIGEST_SHA_2_512;
            case DIGEST_NONE:
                return KeymasterDefs.KM_DIGEST_NONE;
            case DIGEST_MD5:
                return KeymasterDefs.KM_DIGEST_MD5;
            default:
                throw new IllegalArgumentException("Unsupported digest algorithm: " + digest);
            }
        }

        @NonNull
        public static @DigestEnum String fromKeymaster(int digest) {
            switch (digest) {
            case KeymasterDefs.KM_DIGEST_NONE:
                return DIGEST_NONE;
            case KeymasterDefs.KM_DIGEST_MD5:
                return DIGEST_MD5;
            case KeymasterDefs.KM_DIGEST_SHA1:
                return DIGEST_SHA1;
            case KeymasterDefs.KM_DIGEST_SHA_2_224:
                return DIGEST_SHA224;
            case KeymasterDefs.KM_DIGEST_SHA_2_256:
                return DIGEST_SHA256;
            case KeymasterDefs.KM_DIGEST_SHA_2_384:
                return DIGEST_SHA384;
            case KeymasterDefs.KM_DIGEST_SHA_2_512:
                return DIGEST_SHA512;
            default:
                throw new IllegalArgumentException("Unsupported digest algorithm: " + digest);
            }
        }

        @NonNull
        public static @DigestEnum String fromKeymasterToSignatureAlgorithmDigest(int digest) {
            switch (digest) {
            case KeymasterDefs.KM_DIGEST_NONE:
                return "NONE";
            case KeymasterDefs.KM_DIGEST_MD5:
                return "MD5";
            case KeymasterDefs.KM_DIGEST_SHA1:
                return "SHA1";
            case KeymasterDefs.KM_DIGEST_SHA_2_224:
                return "SHA224";
            case KeymasterDefs.KM_DIGEST_SHA_2_256:
                return "SHA256";
            case KeymasterDefs.KM_DIGEST_SHA_2_384:
                return "SHA384";
            case KeymasterDefs.KM_DIGEST_SHA_2_512:
                return "SHA512";
            default:
                throw new IllegalArgumentException("Unsupported digest algorithm: " + digest);
            }
        }

        @NonNull
        public static @DigestEnum String[] allFromKeymaster(@NonNull Collection<Integer> digests) {
            if (digests.isEmpty()) {
                return EmptyArray.STRING;
            }
            String[] result = new String[digests.size()];
            int offset = 0;
            for (int digest : digests) {
                result[offset] = fromKeymaster(digest);
                offset++;
            }
            return result;
        }

        @NonNull
        public static int[] allToKeymaster(@Nullable @DigestEnum String[] digests) {
            if ((digests == null) || (digests.length == 0)) {
                return EmptyArray.INT;
            }
            int[] result = new int[digests.length];
            int offset = 0;
            for (@DigestEnum
            String digest : digests) {
                result[offset] = toKeymaster(digest);
                offset++;
            }
            return result;
        }
    }

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = { "ORIGIN_" }, value = { ORIGIN_GENERATED, ORIGIN_IMPORTED, ORIGIN_UNKNOWN, })

    public @interface OriginEnum {
    }

    /** Key was generated inside AndroidKeyStore. */
    public static final int ORIGIN_GENERATED = 1 << 0;

    /** Key was imported into AndroidKeyStore. */
    public static final int ORIGIN_IMPORTED = 1 << 1;

    /**
     * Origin of the key is unknown. This can occur only for keys backed by an old TEE-backed
     * implementation which does not record origin information.
     */
    public static final int ORIGIN_UNKNOWN = 1 << 2;

    /**
     * Key was imported into the AndroidKeyStore in an encrypted wrapper. Unlike imported keys,
     * securely imported keys can be imported without appearing as plaintext in the device's host
     * memory.
     */
    public static final int ORIGIN_SECURELY_IMPORTED = 1 << 3;

    /**
     * @hide
     */
    public static abstract class Origin {
        private Origin() {
        }

        public static @OriginEnum int fromKeymaster(int origin) {
            switch (origin) {
            case KeymasterDefs.KM_ORIGIN_GENERATED:
                return ORIGIN_GENERATED;
            case KeymasterDefs.KM_ORIGIN_IMPORTED:
                return ORIGIN_IMPORTED;
            case KeymasterDefs.KM_ORIGIN_UNKNOWN:
                return ORIGIN_UNKNOWN;
            case KeymasterDefs.KM_ORIGIN_SECURELY_IMPORTED:
                return ORIGIN_SECURELY_IMPORTED;
            default:
                throw new IllegalArgumentException("Unknown origin: " + origin);
            }
        }
    }

    private static int[] getSetFlags(int flags) {
        if (flags == 0) {
            return EmptyArray.INT;
        }
        int result[] = new int[getSetBitCount(flags)];
        int resultOffset = 0;
        int flag = 1;
        while (flags != 0) {
            if ((flags & 1) != 0) {
                result[resultOffset] = flag;
                resultOffset++;
            }
            flags >>>= 1;
            flag <<= 1;
        }
        return result;
    }

    private static int getSetBitCount(int value) {
        if (value == 0) {
            return 0;
        }
        int result = 0;
        while (value != 0) {
            if ((value & 1) != 0) {
                result++;
            }
            value >>>= 1;
        }
        return result;
    }
}