/*
 * Copyright 2019 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 androidx.security.crypto;

import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;

import androidx.annotation.NonNull;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.Arrays;

import javax.crypto.KeyGenerator;

/**
 * Convenient methods to create and obtain master keys in Android Keystore.
 *
 * <p>The master keys are used to encrypt data encryption keys for encrypting files and preferences.
 *
 */
public final class MasterKeys {
    private MasterKeys() {
    }

    private static final int KEY_SIZE = 256;

    private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
    static final String KEYSTORE_PATH_URI = "android-keystore://";
    static final String MASTER_KEY_ALIAS = "_androidx_security_master_key_";

    @NonNull
    public static final KeyGenParameterSpec AES256_GCM_SPEC =
            createAES256GCMKeyGenParameterSpec(MASTER_KEY_ALIAS);

    /**
     * Provides a safe and easy to use KenGenParameterSpec with the settings.
     * Algorithm: AES
     * Block Mode: GCM
     * Padding: No Padding
     * Key Size: 256
     *
     * @param keyAlias The alias for the master key
     * @return The spec for the master key with the specified keyAlias
     */
    @NonNull
    private static KeyGenParameterSpec createAES256GCMKeyGenParameterSpec(
            @NonNull String keyAlias) {
        KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
                keyAlias,
                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                .setKeySize(KEY_SIZE);
        return builder.build();
    }

    /**
     * Creates or gets the master key provided
     *
     * The encryption scheme is required fields to ensure that the type of
     * encryption used is clear to developers.
     *
     * @param keyGenParameterSpec The key encryption scheme
     * @return The key alias for the master key
     */
    @NonNull
    public static String getOrCreate(
            @NonNull KeyGenParameterSpec keyGenParameterSpec)
            throws GeneralSecurityException, IOException {
        validate(keyGenParameterSpec);
        if (!MasterKeys.keyExists(keyGenParameterSpec.getKeystoreAlias())) {
            generateKey(keyGenParameterSpec);
        }
        return keyGenParameterSpec.getKeystoreAlias();
    }

    @SuppressWarnings("ArrayEquals") // TODO(b/141960406): Suppressed during upgrade to AGP 3.6.
    private static void validate(KeyGenParameterSpec spec) {
        if (spec.getKeySize() != KEY_SIZE) {
            throw new IllegalArgumentException(
                    "invalid key size, want " + KEY_SIZE + " bits got " + spec.getKeySize()
                            + " bits");
        }
        if (spec.getBlockModes().equals(new String[] { KeyProperties.BLOCK_MODE_GCM })) {
            throw new IllegalArgumentException(
                    "invalid block mode, want " + KeyProperties.BLOCK_MODE_GCM + " got "
                            + Arrays.toString(spec.getBlockModes()));
        }
        if (spec.getPurposes() != (KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)) {
            throw new IllegalArgumentException(
                    "invalid purposes mode, want PURPOSE_ENCRYPT | PURPOSE_DECRYPT got "
                            + spec.getPurposes());
        }
        if (spec.getEncryptionPaddings().equals(new String[]
                { KeyProperties.ENCRYPTION_PADDING_NONE })) {
            throw new IllegalArgumentException(
                    "invalid padding mode, want " + KeyProperties.ENCRYPTION_PADDING_NONE + " got "
                            + Arrays.toString(spec.getEncryptionPaddings()));
        }
    }

    private static void generateKey(@NonNull KeyGenParameterSpec keyGenParameterSpec)
            throws GeneralSecurityException {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(
                KeyProperties.KEY_ALGORITHM_AES,
                ANDROID_KEYSTORE);
        keyGenerator.init(keyGenParameterSpec);
        keyGenerator.generateKey();
    }

    private static boolean keyExists(@NonNull String keyAlias)
            throws GeneralSecurityException, IOException {
        KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
        keyStore.load(null);
        return keyStore.containsAlias(keyAlias);
    }

}

@@ -1136,19 +1136,16 @@
 rences.%0A
- *%0A
  */%0Apubl
@@ -3172,105 +3172,8 @@
  %7D%0A%0A
-    @SuppressWarnings(%22ArrayEquals%22) // TODO(b/141960406): Suppressed during upgrade to AGP 3.6.%0A
     
@@ -3225,24 +3225,24 @@
 pec spec) %7B%0A
+
         if (
@@ -3467,32 +3467,47 @@
    %7D%0A        if 
+(!Arrays.equals
 (spec.getBlockMo
@@ -3511,24 +3511,18 @@
 kModes()
-.equals(
+, 
 new Stri
@@ -3525,19 +3525,17 @@
 String%5B%5D
- %7B 
+%7B
 KeyPrope
@@ -3554,17 +3554,16 @@
 MODE_GCM
- 
 %7D)) %7B%0A  
@@ -4079,32 +4079,47 @@
    %7D%0A        if 
+(!Arrays.equals
 (spec.getEncrypt
@@ -4131,24 +4131,18 @@
 ddings()
-.equals(
+, 
 new Stri
@@ -4163,17 +4163,16 @@
        %7B
- 
 KeyPrope
@@ -4200,17 +4200,16 @@
 ING_NONE
- 
 %7D)) %7B%0A  

Fix use of <array>.equals.