/*
 * 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.annotation.Nullable;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
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 IKeystoreUserManager AIDL.
 * It shall only be used by the 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(@NonNull int userId) {
        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;
        }
    }

    /**
     * Informs Keystore 2.0 about removing a usergit mer
     *
     * @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) {
        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;
        }
    }

    /**
     * Informs Keystore 2.0 about changing user's password
     *
     * @param userId   - Android user id of the user
     * @param password - a secret derived from the synthetic password provided by the
     *                 LockSettingService
     * @return 0 if successful or a {@code ResponseCode}
     * @hide
     */
    public static int onUserPasswordChanged(int userId, @Nullable byte[] password) {
        try {
            getService().onUserPasswordChanged(userId, password);
            return 0;
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "onUserPasswordChanged 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 namspace is to
     * be cleared.
     */
    public static int clearNamespace(@Domain int domain, long namespace) {
        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;
        }
    }

    /**
     * Queries user state from Keystore 2.0.
     *
     * @param userId - Android user id of the user.
     * @return UserState enum variant as integer if successful or an error
     */
    public static int getState(int userId) {
        try {
            return getService().getState(userId);
        } catch (ServiceSpecificException e) {
            Log.e(TAG, "getState 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 off body event was detected.
     */
    public static void onDeviceOffBody() {
        try {
            getService().onDeviceOffBody();
        } catch (Exception e) {
            // TODO This fails open. This is not a regression with respect to keystore1 but it
            //      should get fixed.
            Log.e(TAG, "Error while reporting device off body event.", e);
        }
    }

    /**
     * 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 exists.
     *         * 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 once were specified.
     *         * SYSTEM_ERROR if an unexpected error occurred.
     */
    public static int migrateKeyNamespace(KeyDescriptor source, KeyDescriptor destination) {
        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;
        }
    }
}