/*
 * Copyright (C) 2017 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 com.android.server.locksettings.recoverablekeystore.storage;

import static android.security.keystore.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;

import android.annotation.Nullable;
import android.os.ServiceSpecificException;
import android.security.Credentials;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.KeyStore;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.locksettings.recoverablekeystore.KeyStoreProxy;
import com.android.server.locksettings.recoverablekeystore.KeyStoreProxyImpl;

import java.security.KeyStore.SecretKeyEntry;
import java.security.KeyStoreException;
import javax.crypto.spec.SecretKeySpec;

/**
 * Storage for Application keys in LockSettings service KeyStore namespace.
 *
 * <p> Uses KeyStore's grant mechanism to make keys usable by application process without
 * revealing key material
 */
public class ApplicationKeyStorage {
    private static final String APPLICATION_KEY_ALIAS_PREFIX =
            "com.android.server.locksettings.recoverablekeystore/application/";

    KeyStoreProxy mKeyStore;
    KeyStore mKeystoreService;

    public static ApplicationKeyStorage getInstance(KeyStore keystoreService)
            throws KeyStoreException {
        return new ApplicationKeyStorage(
                new KeyStoreProxyImpl(KeyStoreProxyImpl.getAndLoadAndroidKeyStore()),
                keystoreService);
    }

    @VisibleForTesting
    ApplicationKeyStorage(KeyStoreProxy keyStore, KeyStore keystoreService) {
        mKeyStore = keyStore;
        mKeystoreService = keystoreService;
    }

    /**
     * Returns grant alias, valid in Applications namespace.
     */
    public @Nullable String getGrantAlias(int userId, int uid, String alias) {
        // Aliases used by {@link KeyStore} are different than used by public API.
        // {@code USER_PRIVATE_KEY} prefix is used secret keys.
        String keystoreAlias = Credentials.USER_PRIVATE_KEY + getInternalAlias(userId, uid, alias);
        return mKeystoreService.grant(keystoreAlias, uid);
    }

    public void setSymmetricKeyEntry(int userId, int uid, String alias, byte[] secretKey)
            throws KeyStoreException {
        try {
            mKeyStore.setEntry(
                getInternalAlias(userId, uid, alias),
                new SecretKeyEntry(
                    new SecretKeySpec(secretKey, KeyProperties.KEY_ALGORITHM_AES)),
                new KeyProtection.Builder(
                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                    .build());
        } catch (KeyStoreException e) {
            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
        }
    }

    public void deleteEntry(int userId, int uid, String alias) {
        try {
            mKeyStore.deleteEntry(getInternalAlias(userId, uid, alias));
        } catch (KeyStoreException e) {
            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
        }
    }

    /**
     * Returns the alias in locksettins service's KeyStore namespace used for given application key.
     *
     * <p>These IDs look as follows:
     * {@code com.security.recoverablekeystore/application/<userId>/<uid>/<alias>}
     *
     * @param userId The ID of the user
     * @param uid The uid
     * @param alias - alias in application's namespace
     * @return The alias.
     */
    private String getInternalAlias(int userId, int uid, String alias) {
        return APPLICATION_KEY_ALIAS_PREFIX + userId + "/" + uid + "/" + alias;
    }
}
