/*
 * Copyright 2020, 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.
 */
/******************************************************************************
*
*  The original Work has been changed by NXP.
*
*  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.
*
*  Copyright 2022 NXP
*
******************************************************************************/
#define LOG_TAG "javacard.keymint.device.strongbox-impl"
#include "JavacardKeyMintDevice.h"
#include "JavacardKeyMintOperation.h"
#include "JavacardSharedSecret.h"
#include <JavacardKeyMintUtils.h>
#include <algorithm>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <hardware/hw_auth_token.h>
#include <iostream>
#include <iterator>
#include <keymaster/android_keymaster_messages.h>
#include <keymaster/wrapped_key.h>
#include <memory>
#include <regex.h>
#include <string>
#include <vector>

namespace aidl::android::hardware::security::keymint {
using km_utils::KmParamSet;
using namespace ::keymaster;
using namespace ::keymint::javacard;

ScopedAStatus JavacardKeyMintDevice::defaultHwInfo(KeyMintHardwareInfo* info) {
    info->versionNumber = 1;
    info->keyMintAuthorName = "Google";
    info->keyMintName = "JavacardKeymintDevice";
    info->securityLevel = securitylevel_;
    info->timestampTokenRequired = true;
    return ScopedAStatus::ok();
}


ScopedAStatus JavacardKeyMintDevice::getHardwareInfo(KeyMintHardwareInfo* info) {
    uint64_t tsRequired = 1;
    auto [item, err] = card_->sendRequest(Instruction::INS_GET_HW_INFO_CMD);
    uint32_t secLevel;
    uint32_t version;
    if (err != KM_ERROR_OK || !cbor_.getUint64<uint32_t>(item, 1, version) ||
        !cbor_.getUint64<uint32_t>(item, 2, secLevel) ||
        !cbor_.getBinaryArray(item, 3, info->keyMintName) ||
        !cbor_.getBinaryArray(item, 4, info->keyMintAuthorName) ||
        !cbor_.getUint64<uint64_t>(item, 5, tsRequired)) {
        // TODO should we return HARDWARE_NOT_YET_AVAILABLE instead of default Hardware Info.
        LOG(ERROR) << "Error in response of getHardwareInfo.";
        LOG(INFO) << "Returning defaultHwInfo in getHardwareInfo.";
        return defaultHwInfo(info);
    }
    card_->initializeJavacard();
    info->timestampTokenRequired = (tsRequired == 1);
    info->securityLevel = static_cast<SecurityLevel>(secLevel);
    info->versionNumber = static_cast<int32_t>(version);
    return ScopedAStatus::ok();
}

ScopedAStatus JavacardKeyMintDevice::generateKey(const vector<KeyParameter>& keyParams,
                                                 const optional<AttestationKey>& attestationKey,
                                                 KeyCreationResult* creationResult) {
    cppbor::Array array;
    // add key params
    cbor_.addKeyparameters(array, keyParams);
    // add attestation key if any
    cbor_.addAttestationKey(array, attestationKey);
    auto [item, err] = card_->sendRequest(Instruction::INS_GENERATE_KEY_CMD, array);
    if (err != KM_ERROR_OK) {
        LOG(ERROR) << "Error in sending generateKey.";
        return km_utils::kmError2ScopedAStatus(err);
    }
    if (!cbor_.getBinaryArray(item, 1, creationResult->keyBlob) ||
        !cbor_.getKeyCharacteristics(item, 2, creationResult->keyCharacteristics) ||
        !cbor_.getCertificateChain(item, 3, creationResult->certificateChain)) {
        LOG(ERROR) << "Error in decoding og response in generateKey.";
        return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR);
    }
    return ScopedAStatus::ok();
}

ScopedAStatus JavacardKeyMintDevice::addRngEntropy(const vector<uint8_t>& data) {
    cppbor::Array request;
    // add key data
    request.add(Bstr(data));
    auto [item, err] = card_->sendRequest(Instruction::INS_ADD_RNG_ENTROPY_CMD, request);
    if (err != KM_ERROR_OK) {
        LOG(ERROR) << "Error in sending addRngEntropy.";
        return km_utils::kmError2ScopedAStatus(err);
    }
    return ScopedAStatus::ok();
}

ScopedAStatus JavacardKeyMintDevice::importKey(const vector<KeyParameter>& keyParams,
                                               KeyFormat keyFormat, const vector<uint8_t>& keyData,
                                               const optional<AttestationKey>& attestationKey,
                                               KeyCreationResult* creationResult) {

    cppbor::Array request;
    // add key params
    cbor_.addKeyparameters(request, keyParams);
    // add key format
    request.add(Uint(static_cast<uint8_t>(keyFormat)));
    // add key data
    request.add(Bstr(keyData));
    // add attestation key if any
    cbor_.addAttestationKey(request, attestationKey);

    auto [item, err] = card_->sendRequest(Instruction::INS_IMPORT_KEY_CMD, request);
    if (err != KM_ERROR_OK) {
        LOG(ERROR) << "Error in sending data in importKey.";
        return km_utils::kmError2ScopedAStatus(err);
    }
    if (!cbor_.getBinaryArray(item, 1, creationResult->keyBlob) ||
        !cbor_.getKeyCharacteristics(item, 2, creationResult->keyCharacteristics) ||
        !cbor_.getCertificateChain(item, 3, creationResult->certificateChain)) {
        LOG(ERROR) << "Error in decoding response in importKey.";
        return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR);
    }
    return ScopedAStatus::ok();
}

// import wrapped key is divided into 2 stage operation.
ScopedAStatus JavacardKeyMintDevice::importWrappedKey(const vector<uint8_t>& wrappedKeyData,
                                                      const vector<uint8_t>& wrappingKeyBlob,
                                                      const vector<uint8_t>& maskingKey,
                                                      const vector<KeyParameter>& unwrappingParams,
                                                      int64_t passwordSid, int64_t biometricSid,
                                                      KeyCreationResult* creationResult) {
    cppbor::Array request;
    std::unique_ptr<Item> item;
    vector<uint8_t> keyBlob;
    std::vector<uint8_t> response;
    vector<KeyCharacteristics> keyCharacteristics;
    std::vector<uint8_t> iv;
    std::vector<uint8_t> transitKey;
    std::vector<uint8_t> secureKey;
    std::vector<uint8_t> tag;
    vector<KeyParameter> authList;
    KeyFormat keyFormat;
    std::vector<uint8_t> wrappedKeyDescription;
    keymaster_error_t errorCode = parseWrappedKey(wrappedKeyData, iv, transitKey, secureKey, tag,
                                                  authList, keyFormat, wrappedKeyDescription);
    if (errorCode != KM_ERROR_OK) {
        LOG(ERROR) << "Error in parse wrapped key in importWrappedKey.";
        return km_utils::kmError2ScopedAStatus(errorCode);
    }

    // begin import
    std::tie(item, errorCode) =
        sendBeginImportWrappedKeyCmd(transitKey, wrappingKeyBlob, maskingKey, unwrappingParams);
    if (errorCode != KM_ERROR_OK) {
        LOG(ERROR) << "Error in send begin import wrapped key in importWrappedKey.";
        return km_utils::kmError2ScopedAStatus(errorCode);
    }
    // Finish the import
    std::tie(item, errorCode) = sendFinishImportWrappedKeyCmd(
        authList, keyFormat, secureKey, tag, iv, wrappedKeyDescription, passwordSid, biometricSid);
    if (errorCode != KM_ERROR_OK) {
        LOG(ERROR) << "Error in send finish import wrapped key in importWrappedKey.";
        return km_utils::kmError2ScopedAStatus(errorCode);
    }
    if (!cbor_.getBinaryArray(item, 1, creationResult->keyBlob) ||
        !cbor_.getKeyCharacteristics(item, 2, creationResult->keyCharacteristics) ||
        !cbor_.getCertificateChain(item, 3, creationResult->certificateChain)) {
        LOG(ERROR) << "Error in decoding the response in importWrappedKey.";
        return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR);
    }
    return ScopedAStatus::ok();
}

std::tuple<std::unique_ptr<Item>, keymaster_error_t>
JavacardKeyMintDevice::sendBeginImportWrappedKeyCmd(const std::vector<uint8_t>& transitKey,
                                                    const std::vector<uint8_t>& wrappingKeyBlob,
                                                    const std::vector<uint8_t>& maskingKey,
                                                    const vector<KeyParameter>& unwrappingParams) {
    Array request;
    request.add(std::vector<uint8_t>(transitKey));
    request.add(std::vector<uint8_t>(wrappingKeyBlob));
    request.add(std::vector<uint8_t>(maskingKey));
    cbor_.addKeyparameters(request, unwrappingParams);
    return card_->sendRequest(Instruction::INS_BEGIN_IMPORT_WRAPPED_KEY_CMD, request);
}

std::tuple<std::unique_ptr<Item>, keymaster_error_t>
JavacardKeyMintDevice::sendFinishImportWrappedKeyCmd(
    const vector<KeyParameter>& keyParams, KeyFormat keyFormat,
    const std::vector<uint8_t>& secureKey, const std::vector<uint8_t>& tag,
    const std::vector<uint8_t>& iv, const std::vector<uint8_t>& wrappedKeyDescription,
    int64_t passwordSid, int64_t biometricSid) {
    Array request;
    cbor_.addKeyparameters(request, keyParams);
    request.add(static_cast<uint64_t>(keyFormat));
    request.add(std::vector<uint8_t>(secureKey));
    request.add(std::vector<uint8_t>(tag));
    request.add(std::vector<uint8_t>(iv));
    request.add(std::vector<uint8_t>(wrappedKeyDescription));
    request.add(Uint(passwordSid));
    request.add(Uint(biometricSid));
    return card_->sendRequest(Instruction::INS_FINISH_IMPORT_WRAPPED_KEY_CMD, request);
}

ScopedAStatus JavacardKeyMintDevice::upgradeKey(const vector<uint8_t>& keyBlobToUpgrade,
                                                const vector<KeyParameter>& upgradeParams,
                                                vector<uint8_t>* keyBlob) {
    cppbor::Array request;
    // add key blob
    request.add(Bstr(keyBlobToUpgrade));
    // add key params
    cbor_.addKeyparameters(request, upgradeParams);
    auto [item, err] = card_->sendRequest(Instruction::INS_UPGRADE_KEY_CMD, request);
    if (err != KM_ERROR_OK) {
        LOG(ERROR) << "Error in sending in upgradeKey.";
        return km_utils::kmError2ScopedAStatus(err);
    }
    if (!cbor_.getBinaryArray(item, 1, *keyBlob)) {
        LOG(ERROR) << "Error in decoding the response in upgradeKey.";
        return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR);
    }
    return ScopedAStatus::ok();
}

ScopedAStatus JavacardKeyMintDevice::deleteKey(const vector<uint8_t>& keyBlob) {
    Array request;
    request.add(Bstr(keyBlob));
    auto [item, err] = card_->sendRequest(Instruction::INS_DELETE_KEY_CMD, request);
    if (err != KM_ERROR_OK) {
        LOG(ERROR) << "Error in sending in deleteKey.";
        return km_utils::kmError2ScopedAStatus(err);
    }
    return ScopedAStatus::ok();
}

ScopedAStatus JavacardKeyMintDevice::deleteAllKeys() {
    auto [item, err] = card_->sendRequest(Instruction::INS_DELETE_ALL_KEYS_CMD);
    if (err != KM_ERROR_OK) {
        LOG(ERROR) << "Error in sending in deleteAllKeys.";
        return km_utils::kmError2ScopedAStatus(err);
    }
    return ScopedAStatus::ok();
}

ScopedAStatus JavacardKeyMintDevice::destroyAttestationIds() {
    auto [item, err] = card_->sendRequest(Instruction::INS_DESTROY_ATT_IDS_CMD);
    if (err != KM_ERROR_OK) {
        LOG(ERROR) << "Error in sending in destroyAttestationIds.";
        return km_utils::kmError2ScopedAStatus(err);
    }
    return ScopedAStatus::ok();
}

ScopedAStatus JavacardKeyMintDevice::begin(KeyPurpose purpose, const std::vector<uint8_t>& keyBlob,
                                           const std::vector<KeyParameter>& params,
                                           const std::optional<HardwareAuthToken>& authToken,
                                           BeginResult* result) {

    cppbor::Array array;
    std::vector<uint8_t> response;
    // make request
    array.add(Uint(static_cast<uint64_t>(purpose)));
    array.add(Bstr(keyBlob));
    cbor_.addKeyparameters(array, params);
    HardwareAuthToken token = authToken.value_or(HardwareAuthToken());
    cbor_.addHardwareAuthToken(array, token);

    // Send earlyBootEnded if there is any pending earlybootEnded event.
    handleSendEarlyBootEndedEvent();

    auto [item, err] = card_->sendRequest(Instruction::INS_BEGIN_OPERATION_CMD, array);
    if (err != KM_ERROR_OK) {
        LOG(ERROR) << "Error in sending in begin.";
        return km_utils::kmError2ScopedAStatus(err);
    }
    // return the result
    uint64_t opHandle;
    uint8_t bufMode;
    uint16_t macLength;
    if (!cbor_.getKeyParameters(item, 1, result->params) ||
        !cbor_.getUint64<uint64_t>(item, 2, opHandle) ||
        !cbor_.getUint64<uint8_t>(item, 3, bufMode) ||
        !cbor_.getUint64<uint16_t>(item, 4, macLength)) {
        LOG(ERROR) << "Error in decoding the response in begin.";
        return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR);
    }
    result->challenge = opHandle;
    result->operation = ndk::SharedRefBase::make<JavacardKeyMintOperation>(
        static_cast<keymaster_operation_handle_t>(opHandle), static_cast<BufferingMode>(bufMode),
        macLength, card_);
    return ScopedAStatus::ok();
}

// TODO
ScopedAStatus
JavacardKeyMintDevice::deviceLocked(bool passwordOnly,
                                    const std::optional<TimeStampToken>& timestampToken) {
    Array request;
    int8_t password = 1;
    if (!passwordOnly) {
        password = 0;
    }
    request.add(Uint(password));
    cbor_.addTimeStampToken(request, timestampToken.value_or(TimeStampToken()));
    auto [item, err] = card_->sendRequest(Instruction::INS_DEVICE_LOCKED_CMD, request);
    if (err != KM_ERROR_OK) {
        return km_utils::kmError2ScopedAStatus(err);
    }
    return ScopedAStatus::ok();
}

void JavacardKeyMintDevice::handleSendEarlyBootEndedEvent() {
    if (isEarlyBootEventPending) {
        LOG(INFO) << "JavacardKeyMintDevice::handleSendEarlyBootEndedEvent send earlyBootEnded Event.";
        if (earlyBootEnded().isOk()) {
            isEarlyBootEventPending = false;
        }
    }
}

ScopedAStatus JavacardKeyMintDevice::earlyBootEnded() {
    auto [item, err] = card_->sendRequest(Instruction::INS_EARLY_BOOT_ENDED_CMD);
    if (err != KM_ERROR_OK) {
        // In case of failure cache the event and send in the next immediate request to Applet.
        isEarlyBootEventPending = true;
        return km_utils::kmError2ScopedAStatus(err);
    }
    isEarlyBootEventPending = false;
    return ScopedAStatus::ok();
}

ScopedAStatus JavacardKeyMintDevice::getKeyCharacteristics(
    const std::vector<uint8_t>& keyBlob, const std::vector<uint8_t>& appId,
    const std::vector<uint8_t>& appData, std::vector<KeyCharacteristics>* result) {
    cppbor::Array request;
    request.add(vector<uint8_t>(keyBlob));
    request.add(vector<uint8_t>(appId));
    request.add(vector<uint8_t>(appData));
    auto [item, err] = card_->sendRequest(Instruction::INS_GET_KEY_CHARACTERISTICS_CMD, request);
    if (err != KM_ERROR_OK) {
        LOG(ERROR) << "Error in sending in getKeyCharacteristics.";
        return km_utils::kmError2ScopedAStatus(err);
    }
    if (!cbor_.getKeyCharacteristics(item, 1, *result)) {
        LOG(ERROR) << "Error in sending in upgradeKey.";
        return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR);
    }
    return ScopedAStatus::ok();
}

ScopedAStatus JavacardKeyMintDevice::getRootOfTrustChallenge(std::array<uint8_t, 16>* challenge) {
    auto [item, err] = card_->sendRequest(Instruction::INS_GET_ROT_CHALLENGE_CMD);
    if (err != KM_ERROR_OK) {
        LOG(ERROR) << "Error in getRootOfTrustChallenge.";
        return km_utils::kmError2ScopedAStatus(err);
    }
    std::vector<uint8_t> rotChallenge;
    if (!cbor_.getBinaryArray(item, 1, rotChallenge) ||
        (rotChallenge.size() != 16)) {
        LOG(ERROR) << "Error in RotChallenge Data";
        return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR);
    }
    std::copy_n(rotChallenge.begin(), 16, challenge->begin());
    return ScopedAStatus::ok();
}

ScopedAStatus JavacardKeyMintDevice::getRootOfTrust(__attribute__((unused)) const std::array<uint8_t, 16>& in_challenge,
                                  __attribute__((unused)) std::vector<uint8_t>* rootOfTrust) {
    return km_utils::kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
}

ScopedAStatus JavacardKeyMintDevice::sendRootOfTrust(const std::vector<uint8_t>& in_rootOfTrust) {
    std::vector<uint8_t> rootOfTrust(in_rootOfTrust);
    auto [item, err] = card_->sendRequest(Instruction::INS_SEND_ROT_DATA_CMD, rootOfTrust);
    if (err != KM_ERROR_OK) {
        LOG(ERROR) << "Error in sendRootOfTrust.";
        return km_utils::kmError2ScopedAStatus(err);
    }
    return ScopedAStatus::ok();
}

keymaster_error_t
JavacardKeyMintDevice::parseWrappedKey(const vector<uint8_t>& wrappedKeyData,
                                       std::vector<uint8_t>& iv, std::vector<uint8_t>& transitKey,
                                       std::vector<uint8_t>& secureKey, std::vector<uint8_t>& tag,
                                       vector<KeyParameter>& authList, KeyFormat& keyFormat,
                                       std::vector<uint8_t>& wrappedKeyDescription) {
    KeymasterBlob kmIv;
    KeymasterKeyBlob kmTransitKey;
    KeymasterKeyBlob kmSecureKey;
    KeymasterBlob kmTag;
    AuthorizationSet authSet;
    keymaster_key_format_t kmKeyFormat;
    KeymasterBlob kmWrappedKeyDescription;

    size_t keyDataLen = wrappedKeyData.size();
    uint8_t* keyData = dup_buffer(wrappedKeyData.data(), keyDataLen);
    keymaster_key_blob_t keyMaterial = {keyData, keyDataLen};
    keymaster_error_t error =
        parse_wrapped_key(KeymasterKeyBlob(keyMaterial), &kmIv, &kmTransitKey, &kmSecureKey, &kmTag,
                          &authSet, &kmKeyFormat, &kmWrappedKeyDescription);
    if (error != KM_ERROR_OK) {
        LOG(ERROR) << "Error parsing wrapped key.";
        return error;
    }
    iv = km_utils::kmBlob2vector(kmIv);
    transitKey = km_utils::kmBlob2vector(kmTransitKey);
    secureKey = km_utils::kmBlob2vector(kmSecureKey);
    tag = km_utils::kmBlob2vector(kmTag);
    authList = km_utils::kmParamSet2Aidl(authSet);
    keyFormat = static_cast<KeyFormat>(kmKeyFormat);
    wrappedKeyDescription = km_utils::kmBlob2vector(kmWrappedKeyDescription);
    return KM_ERROR_OK;
}

ScopedAStatus JavacardKeyMintDevice::convertStorageKeyToEphemeral(
    const std::vector<uint8_t>& /* storageKeyBlob */,
    std::vector<uint8_t>* /* ephemeralKeyBlob */) {
    return km_utils::kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
}
}  // namespace aidl::android::hardware::security::keymint