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

Commit 21d9c1d4 authored by Frank Salim's avatar Frank Salim
Browse files

Keystore APIs for Import Wrapped Key, Strongbox, 3DES

Import Wrapped Key:
Applications can import keys in a wrapped, encrypted format. Wrapped keys are
unwrapped inside of a Keymaster device.

Strongbox:
Applications can import and generate keys in secure hardware.

3DES:
Add KeyProperties and KeymasterDefs
Add AndroidKeyStore3DESCipherSpi and provider registrations

Bug: 63931634
Test: Keystore CTS tests in progress

Change-Id: I80b6db865b517fa108f14aced7402336212c441b
parent 504039b1
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -37691,6 +37691,7 @@ package android.security.keystore {
    method public boolean isDigestsSpecified();
    method public boolean isInvalidatedByBiometricEnrollment();
    method public boolean isRandomizedEncryptionRequired();
    method public boolean isStrongBoxBacked();
    method public boolean isUserAuthenticationRequired();
    method public boolean isUserAuthenticationValidWhileOnBody();
  }
@@ -37708,6 +37709,7 @@ package android.security.keystore {
    method public android.security.keystore.KeyGenParameterSpec.Builder setDigests(java.lang.String...);
    method public android.security.keystore.KeyGenParameterSpec.Builder setEncryptionPaddings(java.lang.String...);
    method public android.security.keystore.KeyGenParameterSpec.Builder setInvalidatedByBiometricEnrollment(boolean);
    method public android.security.keystore.KeyGenParameterSpec.Builder setIsStrongBoxBacked(boolean);
    method public android.security.keystore.KeyGenParameterSpec.Builder setKeySize(int);
    method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityEnd(java.util.Date);
    method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForConsumptionEnd(java.util.Date);
@@ -37768,6 +37770,7 @@ package android.security.keystore {
    field public static final java.lang.String ENCRYPTION_PADDING_PKCS7 = "PKCS7Padding";
    field public static final java.lang.String ENCRYPTION_PADDING_RSA_OAEP = "OAEPPadding";
    field public static final java.lang.String ENCRYPTION_PADDING_RSA_PKCS1 = "PKCS1Padding";
    field public static final deprecated java.lang.String KEY_ALGORITHM_3DES = "DESede";
    field public static final java.lang.String KEY_ALGORITHM_AES = "AES";
    field public static final java.lang.String KEY_ALGORITHM_EC = "EC";
    field public static final java.lang.String KEY_ALGORITHM_HMAC_SHA1 = "HmacSHA1";
@@ -37778,11 +37781,13 @@ package android.security.keystore {
    field public static final java.lang.String KEY_ALGORITHM_RSA = "RSA";
    field public static final int ORIGIN_GENERATED = 1; // 0x1
    field public static final int ORIGIN_IMPORTED = 2; // 0x2
    field public static final int ORIGIN_SECURELY_IMPORTED = 8; // 0x8
    field public static final int ORIGIN_UNKNOWN = 4; // 0x4
    field public static final int PURPOSE_DECRYPT = 2; // 0x2
    field public static final int PURPOSE_ENCRYPT = 1; // 0x1
    field public static final int PURPOSE_SIGN = 4; // 0x4
    field public static final int PURPOSE_VERIFY = 8; // 0x8
    field public static final int PURPOSE_WRAP_KEY = 32; // 0x20
    field public static final java.lang.String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";
    field public static final java.lang.String SIGNATURE_PADDING_RSA_PSS = "PSS";
  }
@@ -37822,12 +37827,24 @@ package android.security.keystore {
    method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int);
  }
  public class StrongBoxUnavailableException extends java.security.ProviderException {
    ctor public StrongBoxUnavailableException();
  }
  public class UserNotAuthenticatedException extends java.security.InvalidKeyException {
    ctor public UserNotAuthenticatedException();
    ctor public UserNotAuthenticatedException(java.lang.String);
    ctor public UserNotAuthenticatedException(java.lang.String, java.lang.Throwable);
  }
  public class WrappedKeyEntry implements java.security.KeyStore.Entry {
    ctor public WrappedKeyEntry(byte[], java.lang.String, java.lang.String, java.security.spec.AlgorithmParameterSpec);
    method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
    method public java.lang.String getTransformation();
    method public byte[] getWrappedKeyBytes();
    method public java.lang.String getWrappingKeyAlias();
  }
}
package android.service.autofill {
+3 −0
Original line number Diff line number Diff line
@@ -101,6 +101,7 @@ public final class KeymasterDefs {
    public static final int KM_ALGORITHM_RSA = 1;
    public static final int KM_ALGORITHM_EC = 3;
    public static final int KM_ALGORITHM_AES = 32;
    public static final int KM_ALGORITHM_3DES = 33;
    public static final int KM_ALGORITHM_HMAC = 128;

    // Block modes.
@@ -130,6 +131,7 @@ public final class KeymasterDefs {
    public static final int KM_ORIGIN_GENERATED = 0;
    public static final int KM_ORIGIN_IMPORTED = 2;
    public static final int KM_ORIGIN_UNKNOWN = 3;
    public static final int KM_ORIGIN_SECURELY_IMPORTED = 4;

    // Key usability requirements.
    public static final int KM_BLOB_STANDALONE = 0;
@@ -140,6 +142,7 @@ public final class KeymasterDefs {
    public static final int KM_PURPOSE_DECRYPT = 1;
    public static final int KM_PURPOSE_SIGN = 2;
    public static final int KM_PURPOSE_VERIFY = 3;
    public static final int KM_PURPOSE_WRAP = 5;

    // Key formats.
    public static final int KM_KEY_FORMAT_X509 = 0;
+13 −0
Original line number Diff line number Diff line
@@ -510,6 +510,19 @@ public class KeyStore {
        return importKey(alias, args, format, keyData, UID_SELF, flags, outCharacteristics);
    }

    public int importWrappedKey(String wrappedKeyAlias, byte[] wrappedKey,
            String wrappingKeyAlias,
            byte[] maskingKey, KeymasterArguments args, long rootSid, long fingerprintSid, int uid,
            KeyCharacteristics outCharacteristics) {
        try {
            return mBinder.importWrappedKey(wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
                    maskingKey, args, rootSid, fingerprintSid, outCharacteristics);
        } catch (RemoteException e) {
            Log.w(TAG, "Cannot connect to keystore", e);
            return SYSTEM_ERROR;
        }
    }

    public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
            KeymasterBlob appId, int uid) {
        try {
+298 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;

import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;

import javax.crypto.CipherSpi;
import javax.crypto.spec.IvParameterSpec;

/**
 * Base class for Android Keystore 3DES {@link CipherSpi} implementations.
 *
 * @hide
 */
public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase {

    private static final int BLOCK_SIZE_BYTES = 8;

    private final int mKeymasterBlockMode;
    private final int mKeymasterPadding;
    /** Whether this transformation requires an IV. */
    private final boolean mIvRequired;

    private byte[] mIv;

    /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
    private boolean mIvHasBeenUsed;

    AndroidKeyStore3DESCipherSpi(
            int keymasterBlockMode,
            int keymasterPadding,
            boolean ivRequired) {
        mKeymasterBlockMode = keymasterBlockMode;
        mKeymasterPadding = keymasterPadding;
        mIvRequired = ivRequired;
    }

    abstract static class ECB extends AndroidKeyStore3DESCipherSpi {
        protected ECB(int keymasterPadding) {
            super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false);
        }

        public static class NoPadding extends ECB {
            public NoPadding() {
                super(KeymasterDefs.KM_PAD_NONE);
            }
        }

        public static class PKCS7Padding extends ECB {
            public PKCS7Padding() {
                super(KeymasterDefs.KM_PAD_PKCS7);
            }
        }
    }

    abstract static class CBC extends AndroidKeyStore3DESCipherSpi {
        protected CBC(int keymasterPadding) {
            super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true);
        }

        public static class NoPadding extends CBC {
            public NoPadding() {
                super(KeymasterDefs.KM_PAD_NONE);
            }
        }

        public static class PKCS7Padding extends CBC {
            public PKCS7Padding() {
                super(KeymasterDefs.KM_PAD_PKCS7);
            }
        }
    }

    @Override
    protected void initKey(int i, Key key) throws InvalidKeyException {
        if (!(key instanceof AndroidKeyStoreSecretKey)) {
            throw new InvalidKeyException(
                    "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
        }
        if (!KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(key.getAlgorithm())) {
            throw new InvalidKeyException(
                    "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
                            KeyProperties.KEY_ALGORITHM_3DES + " supported");
        }
        setKey((AndroidKeyStoreSecretKey) key);
    }

    @Override
    protected int engineGetBlockSize() {
        return BLOCK_SIZE_BYTES;
    }

    @Override
    protected int engineGetOutputSize(int inputLen) {
        return inputLen + 3 * BLOCK_SIZE_BYTES;
    }

    @Override
    protected final byte[] engineGetIV() {
        return ArrayUtils.cloneIfNotEmpty(mIv);
    }

    @Override
    protected AlgorithmParameters engineGetParameters() {
        if (!mIvRequired) {
            return null;
        }
        if ((mIv != null) && (mIv.length > 0)) {
            try {
                AlgorithmParameters params = AlgorithmParameters.getInstance("DESede");
                params.init(new IvParameterSpec(mIv));
                return params;
            } catch (NoSuchAlgorithmException e) {
                throw new ProviderException(
                        "Failed to obtain 3DES AlgorithmParameters", e);
            } catch (InvalidParameterSpecException e) {
                throw new ProviderException(
                        "Failed to initialize 3DES AlgorithmParameters with an IV",
                        e);
            }
        }
        return null;
    }

    @Override
    protected void initAlgorithmSpecificParameters() throws InvalidKeyException {
        if (!mIvRequired) {
            return;
        }

        // IV is used
        if (!isEncrypting()) {
            throw new InvalidKeyException("IV required when decrypting"
                    + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
        }
    }

    @Override
    protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
            throws InvalidAlgorithmParameterException {
        if (!mIvRequired) {
            if (params != null) {
                throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
            }
            return;
        }

        // IV is used
        if (params == null) {
            if (!isEncrypting()) {
                // IV must be provided by the caller
                throw new InvalidAlgorithmParameterException(
                        "IvParameterSpec must be provided when decrypting");
            }
            return;
        }
        if (!(params instanceof IvParameterSpec)) {
            throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported");
        }
        mIv = ((IvParameterSpec) params).getIV();
        if (mIv == null) {
            throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec");
        }
    }

    @Override
    protected void initAlgorithmSpecificParameters(AlgorithmParameters params)
            throws InvalidAlgorithmParameterException {
        if (!mIvRequired) {
            if (params != null) {
                throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
            }
            return;
        }

        // IV is used
        if (params == null) {
            if (!isEncrypting()) {
                // IV must be provided by the caller
                throw new InvalidAlgorithmParameterException("IV required when decrypting"
                        + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
            }
            return;
        }

        if (!"DESede".equalsIgnoreCase(params.getAlgorithm())) {
            throw new InvalidAlgorithmParameterException(
                    "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm()
                            + ". Supported: DESede");
        }

        IvParameterSpec ivSpec;
        try {
            ivSpec = params.getParameterSpec(IvParameterSpec.class);
        } catch (InvalidParameterSpecException e) {
            if (!isEncrypting()) {
                // IV must be provided by the caller
                throw new InvalidAlgorithmParameterException("IV required when decrypting"
                        + ", but not found in parameters: " + params, e);
            }
            mIv = null;
            return;
        }
        mIv = ivSpec.getIV();
        if (mIv == null) {
            throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters");
        }
    }

    @Override
    protected final int getAdditionalEntropyAmountForBegin() {
        if ((mIvRequired) && (mIv == null) && (isEncrypting())) {
            // IV will need to be generated
            return BLOCK_SIZE_BYTES;
        }

        return 0;
    }

    @Override
    protected int getAdditionalEntropyAmountForFinish() {
        return 0;
    }

    @Override
    protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) {
        if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) {
            // IV is being reused for encryption: this violates security best practices.
            throw new IllegalStateException(
                    "IV has already been used. Reusing IV in encryption mode violates security best"
                            + " practices.");
        }

        keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_3DES);
        keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
        keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
        if ((mIvRequired) && (mIv != null)) {
            keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv);
        }
    }

    @Override
    protected void loadAlgorithmSpecificParametersFromBeginResult(
            KeymasterArguments keymasterArgs) {
        mIvHasBeenUsed = true;

        // NOTE: Keymaster doesn't always return an IV, even if it's used.
        byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null);
        if ((returnedIv != null) && (returnedIv.length == 0)) {
            returnedIv = null;
        }

        if (mIvRequired) {
            if (mIv == null) {
                mIv = returnedIv;
            } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
                throw new ProviderException("IV in use differs from provided IV");
            }
        } else {
            if (returnedIv != null) {
                throw new ProviderException(
                        "IV in use despite IV not being used by this transformation");
            }
        }
    }

    @Override
    protected final void resetAll() {
        mIv = null;
        mIvHasBeenUsed = false;
        super.resetAll();
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -93,6 +93,16 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider {
        putSymmetricCipherImpl("AES/CTR/NoPadding",
                PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding");

        putSymmetricCipherImpl("DESede/CBC/NoPadding",
                PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$CBC$NoPadding");
        putSymmetricCipherImpl("DESede/CBC/PKCS7Padding",
                PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$CBC$PKCS7Padding");

        putSymmetricCipherImpl("DESede/ECB/NoPadding",
                PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$ECB$NoPadding");
        putSymmetricCipherImpl("DESede/ECB/PKCS7Padding",
                PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$ECB$PKCS7Padding");

        putSymmetricCipherImpl("AES/GCM/NoPadding",
                PACKAGE_NAME + ".AndroidKeyStoreAuthenticatedAESCipherSpi$GCM$NoPadding");

Loading