/*
 * 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;

import android.annotation.NonNull;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.os.StrictMode;
import android.security.maintenance.IKeystoreMaintenance;
import android.system.keystore2.Domain;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.ResponseCode;
import android.util.Log;

/**
 * @hide This is the client side for IKeystoreMaintenance AIDL.
 * It is used mainly by LockSettingsService.
 */
public class AndroidKeyStoreMaintenance {
    private static final String TAG = "AndroidKeyStoreMaintenance";

    public static final int SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR;
    public static final int INVALID_ARGUMENT = ResponseCode.INVALID_ARGUMENT;
    public static final int PERMISSION_DENIED = ResponseCode.PERMISSION_DENIED;
    public static final int KEY_NOT_FOUND = ResponseCode.KEY_NOT_FOUND;

    private static IKeystoreMaintenance getService() {
        return IKeystoreMaintenance.Stub.asInterface(
                ServiceManager.checkService("android.security.maintenance"));
    }

    /**
     * Informs Keystore 2.0 about adding a user
     *
     * @param userId - Android user id of the user being added
     * @return 0 if successful or a {@code ResponseCode}
     * @hide
     */
    public static int onUserAdded(int userId) {
        StrictMode.noteDiskWrite();
        try {
            getService().onUserAdded(userId);
            return 0;
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "onUserAdded failed", e);
            return e.errorCode;
        } catch (Exception e) {
            Log.e(TAG, "Can not connect to keystore", e);
            return SYSTEM_ERROR;
        }
    }

    /**
     * Tells Keystore to create a user's super keys and store them encrypted by the given secret.
     *
     * @param userId - Android user id of the user
     * @param password - a secret derived from the user's synthetic password
     * @param allowExisting - true if the keys already existing should not be considered an error
     * @return 0 if successful or a {@code ResponseCode}
     * @hide
     */
    public static int initUserSuperKeys(int userId, @NonNull byte[] password,
            boolean allowExisting) {
        StrictMode.noteDiskWrite();
        try {
            getService().initUserSuperKeys(userId, password, allowExisting);
            return 0;
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "initUserSuperKeys failed", e);
            return e.errorCode;
        } catch (Exception e) {
            Log.e(TAG, "Can not connect to keystore", e);
            return SYSTEM_ERROR;
        }
    }

    /**
     * Informs Keystore 2.0 about removing a user
     *
     * @param userId - Android user id of the user being removed
     * @return 0 if successful or a {@code ResponseCode}
     * @hide
     */
    public static int onUserRemoved(int userId) {
        StrictMode.noteDiskWrite();
        try {
            getService().onUserRemoved(userId);
            return 0;
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "onUserRemoved failed", e);
            return e.errorCode;
        } catch (Exception e) {
            Log.e(TAG, "Can not connect to keystore", e);
            return SYSTEM_ERROR;
        }
    }

    /**
     * Tells Keystore that a user's LSKF is being removed, ie the user's lock screen is changing to
     * Swipe or None.  Keystore uses this notification to delete the user's auth-bound keys.
     *
     * @param userId - Android user id of the user
     * @return 0 if successful or a {@code ResponseCode}
     * @hide
     */
    public static int onUserLskfRemoved(int userId) {
        StrictMode.noteDiskWrite();
        try {
            getService().onUserLskfRemoved(userId);
            return 0;
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "onUserLskfRemoved failed", e);
            return e.errorCode;
        } catch (Exception e) {
            Log.e(TAG, "Can not connect to keystore", e);
            return SYSTEM_ERROR;
        }
    }

    /**
     * Informs Keystore 2.0 that an app was uninstalled and the corresponding namespace is to
     * be cleared.
     */
    public static int clearNamespace(@Domain int domain, long namespace) {
        StrictMode.noteDiskWrite();
        try {
            getService().clearNamespace(domain, namespace);
            return 0;
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "clearNamespace failed", e);
            return e.errorCode;
        } catch (Exception e) {
            Log.e(TAG, "Can not connect to keystore", e);
            return SYSTEM_ERROR;
        }
    }

    /**
     * Migrates a key given by the source descriptor to the location designated by the destination
     * descriptor.
     *
     * @param source - The key to migrate may be specified by Domain.APP, Domain.SELINUX, or
     *               Domain.KEY_ID. The caller needs the permissions use, delete, and grant for the
     *               source namespace.
     * @param destination - The new designation for the key may be specified by Domain.APP or
     *                    Domain.SELINUX. The caller need the permission rebind for the destination
     *                    namespace.
     *
     * @return * 0 on success
     *         * KEY_NOT_FOUND if the source did not exist.
     *         * PERMISSION_DENIED if any of the required permissions was missing.
     *         * INVALID_ARGUMENT if the destination was occupied or any domain value other than
     *                   the allowed ones was specified.
     *         * SYSTEM_ERROR if an unexpected error occurred.
     */
    public static int migrateKeyNamespace(KeyDescriptor source, KeyDescriptor destination) {
        StrictMode.noteDiskWrite();
        try {
            getService().migrateKeyNamespace(source, destination);
            return 0;
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "migrateKeyNamespace failed", e);
            return e.errorCode;
        } catch (Exception e) {
            Log.e(TAG, "Can not connect to keystore", e);
            return SYSTEM_ERROR;
        }
    }

    /**
     * Returns the list of Application UIDs that have auth-bound keys that are bound to
     * the given SID. This enables warning the user when they are about to invalidate
     * a SID (for example, removing the LSKF).
     *
     * @param userId - The ID of the user the SID is associated with.
     * @param userSecureId - The SID in question.
     *
     * @return A list of app UIDs.
     */
    public static long[] getAllAppUidsAffectedBySid(int userId, long userSecureId)
            throws KeyStoreException {
        StrictMode.noteDiskWrite();
        try {
            return getService().getAppUidsAffectedBySid(userId, userSecureId);
        } catch (RemoteException | NullPointerException e) {
            throw new KeyStoreException(SYSTEM_ERROR,
                    "Failure to connect to Keystore while trying to get apps affected by SID.");
        } catch (ServiceSpecificException e) {
            throw new KeyStoreException(e.errorCode,
                    "Keystore error while trying to get apps affected by SID.");
        }
    }

    /**
    * Deletes all keys in all KeyMint devices.
    * Called by RecoverySystem before rebooting to recovery in order to delete all KeyMint keys,
    * including synthetic password protector keys (used by LockSettingsService), as well as keys
    * protecting DE and metadata encryption keys (used by vold). This ensures that FBE-encrypted
    * data is unrecoverable even if the data wipe in recovery is interrupted or skipped.
    */
    public static void deleteAllKeys() throws KeyStoreException {
        StrictMode.noteDiskWrite();
        try {
            getService().deleteAllKeys();
        } catch (RemoteException | NullPointerException e) {
            throw new KeyStoreException(SYSTEM_ERROR,
                    "Failure to connect to Keystore while trying to delete all keys.");
        } catch (ServiceSpecificException e) {
            throw new KeyStoreException(e.errorCode,
                    "Keystore error while trying to delete all keys.");
        }
    }
}
