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

Commit e43b86d5 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "ECDH SPI interface"

parents ce075560 b3c66645
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) {