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

Commit 42cc0967 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "ECDH SPI interface" am: e43b86d5 am: 5e786864

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1542804

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Ie718a218f85265627c2e8088806bbc950cb2eb9e
parents 8062b53a 5e786864
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -36325,6 +36325,7 @@ package android.security.keystore {
    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_AGREE_KEY = 64; // 0x40
    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
+1 −0
Original line number Diff line number Diff line
@@ -177,6 +177,7 @@ public final class KeymasterDefs {
    public static final int KM_PURPOSE_SIGN = KeyPurpose.SIGN;
    public static final int KM_PURPOSE_VERIFY = KeyPurpose.VERIFY;
    public static final int KM_PURPOSE_WRAP = KeyPurpose.WRAP_KEY;
    public static final int KM_PURPOSE_AGREE_KEY = KeyPurpose.AGREE_KEY;

    // Key formats.
    public static final int KM_KEY_FORMAT_X509 = KeyFormat.X509;
+10 −0
Original line number Diff line number Diff line
@@ -67,6 +67,7 @@ public abstract class KeyProperties {
            PURPOSE_SIGN,
            PURPOSE_VERIFY,
            PURPOSE_WRAP_KEY,
            PURPOSE_AGREE_KEY,
    })
    public @interface PurposeEnum {}

@@ -95,6 +96,11 @@ public abstract class KeyProperties {
     */
    public static final int PURPOSE_WRAP_KEY = 1 << 5;

    /**
     * Purpose of key: creating a shared ECDH secret through key agreement.
     */
    public static final int PURPOSE_AGREE_KEY = 1 << 6;

    /**
     * @hide
     */
@@ -113,6 +119,8 @@ public abstract class KeyProperties {
                    return KeymasterDefs.KM_PURPOSE_VERIFY;
                case PURPOSE_WRAP_KEY:
                    return KeymasterDefs.KM_PURPOSE_WRAP;
                case PURPOSE_AGREE_KEY:
                    return KeymasterDefs.KM_PURPOSE_AGREE_KEY;
                default:
                    throw new IllegalArgumentException("Unknown purpose: " + purpose);
            }
@@ -130,6 +138,8 @@ public abstract class KeyProperties {
                    return PURPOSE_VERIFY;
                case KeymasterDefs.KM_PURPOSE_WRAP:
                    return PURPOSE_WRAP_KEY;
                case KeymasterDefs.KM_PURPOSE_AGREE_KEY:
                    return PURPOSE_AGREE_KEY;
                default:
                    throw new IllegalArgumentException("Unknown purpose: " + purpose);
            }
+240 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.keystore2;

import android.hardware.security.keymint.Algorithm;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.KeyPurpose;
import android.hardware.security.keymint.Tag;
import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keystore.KeyStoreCryptoOperation;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.KeyAgreementSpi;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;

/**
 * {@link KeyAgreementSpi} which provides an ECDH implementation backed by Android KeyStore.
 *
 * @hide
 */
public class AndroidKeyStoreKeyAgreementSpi extends KeyAgreementSpi
        implements KeyStoreCryptoOperation {

    private static final String TAG = "AndroidKeyStoreKeyAgreementSpi";

    /**
     * ECDH implementation.
     *
     * @hide
     */
    public static class ECDH extends AndroidKeyStoreKeyAgreementSpi {
        public ECDH() {
            super(Algorithm.EC);
        }
    }

    private final int mKeymintAlgorithm;

    // Fields below are populated by engineInit and should be preserved after engineDoFinal.
    private AndroidKeyStorePrivateKey mKey;
    private PublicKey mOtherPartyKey;

    // Fields below are reset when engineDoFinal succeeds.
    private KeyStoreOperation mOperation;
    private long mOperationHandle;

    protected AndroidKeyStoreKeyAgreementSpi(int keymintAlgorithm) {
        resetAll();

        mKeymintAlgorithm = keymintAlgorithm;
    }

    @Override
    protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException {
        resetAll();

        if (key == null) {
            throw new InvalidKeyException("key == null");
        } else if (!(key instanceof AndroidKeyStorePrivateKey)) {
            throw new InvalidKeyException(
                    "Only Android KeyStore private keys supported. Key: " + key);
        }
        // Checking the correct KEY_PURPOSE and algorithm is done by the Keymint implementation in
        // ensureKeystoreOperationInitialized() below.
        mKey = (AndroidKeyStorePrivateKey) key;

        boolean success = false;
        try {
            ensureKeystoreOperationInitialized();
            success = true;
        } finally {
            if (!success) {
                resetAll();
            }
        }
    }

    @Override
    protected void engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random)
            throws InvalidKeyException, InvalidAlgorithmParameterException {
        if (params != null) {
            throw new InvalidAlgorithmParameterException(
                    "Unsupported algorithm parameters: " + params);
        }
        engineInit(key, random);
    }

    @Override
    protected Key engineDoPhase(Key key, boolean lastPhase)
            throws InvalidKeyException, IllegalStateException {
        ensureKeystoreOperationInitialized();

        if (key == null) {
            throw new InvalidKeyException("key == null");
        } else if (!(key instanceof PublicKey)) {
            throw new InvalidKeyException("Only public keys supported. Key: " + key);
        } else if (!lastPhase) {
            throw new IllegalStateException(
                    "Only one other party supported. lastPhase must be set to true.");
        } else if (mOtherPartyKey != null) {
            throw new IllegalStateException(
                    "Only one other party supported. doPhase() must only be called exactly once.");
        }
        // The other party key will be passed as part of the doFinal() call, to prevent an
        // additional IPC.
        mOtherPartyKey = (PublicKey) key;

        return null; // No intermediate key
    }

    @Override
    protected byte[] engineGenerateSecret() throws IllegalStateException {
        try {
            ensureKeystoreOperationInitialized();
        } catch (InvalidKeyException e) {
            throw new IllegalStateException("Not initialized", e);
        }

        if (mOtherPartyKey == null) {
            throw new IllegalStateException("Other party key not provided. Call doPhase() first.");
        }
        byte[] otherPartyKeyEncoded = mOtherPartyKey.getEncoded();

        try {
            return mOperation.finish(otherPartyKeyEncoded, null);
        } catch (KeyStoreException e) {
            throw new ProviderException("Keystore operation failed", e);
        } finally {
            resetWhilePreservingInitState();
        }
    }

    @Override
    protected SecretKey engineGenerateSecret(String algorithm)
            throws IllegalStateException, NoSuchAlgorithmException, InvalidKeyException {
        byte[] generatedSecret = engineGenerateSecret();

        return new SecretKeySpec(generatedSecret, algorithm);
    }

    @Override
    protected int engineGenerateSecret(byte[] sharedSecret, int offset)
            throws IllegalStateException, ShortBufferException {
        byte[] generatedSecret = engineGenerateSecret();

        if (generatedSecret.length > sharedSecret.length - offset) {
            throw new ShortBufferException("Needed: " + generatedSecret.length);
        }
        System.arraycopy(generatedSecret, 0, sharedSecret, offset, generatedSecret.length);
        return generatedSecret.length;
    }

    @Override
    public long getOperationHandle() {
        return mOperationHandle;
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            resetAll();
        } finally {
            super.finalize();
        }
    }

    private void resetWhilePreservingInitState() {
        KeyStoreCryptoOperationUtils.abortOperation(mOperation);
        mOperationHandle = 0;
        mOperation = null;
        mOtherPartyKey = null;
    }

    private void resetAll() {
        resetWhilePreservingInitState();
        mKey = null;
    }

    private void ensureKeystoreOperationInitialized()
            throws InvalidKeyException, IllegalStateException {
        if (mKey == null) {
            throw new IllegalStateException("Not initialized");
        }
        if (mOperation != null) {
            return;
        }

        // We don't need to explicitly pass in any other parameters here, as they're part of the
        // private key that is available to Keymint.
        List<KeyParameter> parameters = new ArrayList<>();
        parameters.add(KeyStore2ParameterUtils.makeEnum(
                Tag.PURPOSE, KeyPurpose.AGREE_KEY
        ));

        try {
            mOperation =
                    mKey.getSecurityLevel().createOperation(mKey.getKeyIdDescriptor(), parameters);
        } catch (KeyStoreException keyStoreException) {
            // If necessary, throw an exception due to KeyStore operation having failed.
            InvalidKeyException e =
                    KeyStoreCryptoOperationUtils.getInvalidKeyException(mKey, keyStoreException);
            if (e != null) {
                throw e;
            }
        }

        // Set the operation handle. This will be a random number, or the operation challenge if
        // user authentication is required. If we got a challenge we check if the authorization can
        // possibly succeed.
        mOperationHandle =
                KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(mOperation, mKey);
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -94,6 +94,9 @@ public class AndroidKeyStoreProvider extends Provider {
            put("KeyGenerator.DESede", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$DESede");
        }

        // javax.crypto.KeyAgreement
        put("KeyAgreement.ECDH", PACKAGE_NAME + ".AndroidKeyStoreKeyAgreementSpi$ECDH");

        // java.security.SecretKeyFactory
        putSecretKeyFactoryImpl("AES");
        if (supports3DES) {