/*
 * This file is part of the Pico OpenPGP distribution (https://github.com/polhenarejos/pico-openpgp).
 * Copyright (c) 2022 Pol Henarejos.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

#ifdef ESP_PLATFORM
#include "esp_compat.h"
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
#else
#include "common.h"
#endif
#include "openpgp.h"
#include "do.h"
#include "random.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/asn1.h"

int cmd_pso() {
    uint16_t algo_fid = 0x0, pk_fid = 0x0;
    bool is_aes = false;
    if (P1(apdu) == 0x9E && P2(apdu) == 0x9A) {
        if (!has_pw3 && !has_pw1) {
            return SW_SECURITY_STATUS_NOT_SATISFIED();
        }
        algo_fid = EF_ALGO_PRIV1;
        pk_fid = EF_PK_SIG;
    }
    else if (P1(apdu) == 0x80 && P2(apdu) == 0x86) {
        if (!has_pw3 && !has_pw2) {
            return SW_SECURITY_STATUS_NOT_SATISFIED();
        }
        algo_fid = algo_dec;
        pk_fid = pk_dec;
    }
    else {
        return SW_INCORRECT_P1P2();
    }
    file_t *algo_ef = search_by_fid(algo_fid, NULL, SPECIFY_EF);
    if (!algo_ef) {
        return SW_REFERENCE_NOT_FOUND();
    }
    const uint8_t *algo = algorithm_attr_rsa2k + 1;
    if (algo_ef && algo_ef->data) {
        algo = file_get_data(algo_ef);
    }
    if (apdu.data[0] == 0x2) { //AES PSO?
        if (((apdu.nc - 1) % 16 == 0 && P1(apdu) == 0x80 && P2(apdu) == 0x86) ||
            (apdu.nc % 16 == 0 && P1(apdu) == 0x86 && P2(apdu) == 0x80)) {
            pk_fid = EF_AES_KEY;
            is_aes = true;
        }
    }
    file_t *ef = search_by_fid(pk_fid, NULL, SPECIFY_EF);
    if (!ef) {
        return SW_REFERENCE_NOT_FOUND();
    }
    if (wait_button_pressed_fid(pk_fid == EF_PK_SIG ? EF_UIF_SIG : EF_UIF_DEC) == true) {
        return SW_SECURE_MESSAGE_EXEC_ERROR();
    }
    int r = PICOKEY_OK;
    int key_size = file_get_size(ef);
    if (is_aes) {
        uint8_t aes_key[32];
        r = load_aes_key(aes_key, ef);
        if (r != PICOKEY_OK) {
            memset(aes_key, 0, sizeof(aes_key));
            return SW_EXEC_ERROR();
        }
        if (P1(apdu) == 0x80 && P2(apdu) == 0x86) { //decipher
            r = aes_decrypt(aes_key, NULL, key_size, PICO_KEYS_AES_MODE_CBC, apdu.data + 1, apdu.nc - 1);
            memset(aes_key, 0, sizeof(aes_key));
            if (r != PICOKEY_OK) {
                return SW_EXEC_ERROR();
            }
            memcpy(res_APDU, apdu.data + 1, apdu.nc - 1);
            res_APDU_size = apdu.nc - 1;
        }
        else if (P1(apdu) == 0x86 && P2(apdu) == 0x80) { //encipher
            r = aes_encrypt(aes_key, NULL, key_size, PICO_KEYS_AES_MODE_CBC, apdu.data, apdu.nc);
            memset(aes_key, 0, sizeof(aes_key));
            if (r != PICOKEY_OK) {
                return SW_EXEC_ERROR();
            }
            res_APDU[0] = 0x2;
            memcpy(res_APDU + 1, apdu.data, apdu.nc);
            res_APDU_size = apdu.nc + 1;
        }
        return SW_OK();
    }
    if (algo[0] == ALGO_RSA) {
        mbedtls_rsa_context ctx;
        mbedtls_rsa_init(&ctx);
        r = load_private_key_rsa(&ctx, ef, true);
        if (r != PICOKEY_OK) {
            mbedtls_rsa_free(&ctx);
            return SW_EXEC_ERROR();
        }
        if (P1(apdu) == 0x9E && P2(apdu) == 0x9A) {
            size_t olen = 0;
            r = rsa_sign(&ctx, apdu.data, apdu.nc, res_APDU, &olen);
            mbedtls_rsa_free(&ctx);
            if (r != 0) {
                return SW_EXEC_ERROR();
            }
            res_APDU_size = olen;
            //apdu.ne = key_size;
            inc_sig_count();
        }
        else if (P1(apdu) == 0x80 && P2(apdu) == 0x86) {
            if (apdu.nc < key_size) { //needs padding
                memset(apdu.data + apdu.nc, 0, key_size - apdu.nc);
            }
            size_t olen = 0;
            r = mbedtls_rsa_pkcs1_decrypt(&ctx,
                                          random_gen,
                                          NULL,
                                          &olen,
                                          apdu.data + 1,
                                          res_APDU,
                                          key_size);
            mbedtls_rsa_free(&ctx);
            if (r != 0) {
                return SW_EXEC_ERROR();
            }
            res_APDU_size = olen;
        }
    }
    else if (algo[0] == ALGO_ECDH || algo[0] == ALGO_ECDSA || algo[0] == ALGO_EDDSA) {
        if (P1(apdu) == 0x9E && P2(apdu) == 0x9A) {
            mbedtls_ecp_keypair ctx;
            mbedtls_ecp_keypair_init(&ctx);
            r = load_private_key_ecdsa(&ctx, ef, true);
            if (r != PICOKEY_OK) {
                mbedtls_ecp_keypair_free(&ctx);
                return SW_EXEC_ERROR();
            }
            size_t olen = 0;
            r = ecdsa_sign(&ctx, apdu.data, apdu.nc, res_APDU, &olen);
            mbedtls_ecp_keypair_free(&ctx);
            if (r != 0) {
                return SW_EXEC_ERROR();
            }
            res_APDU_size = olen;
            inc_sig_count();
        }
        else if (P1(apdu) == 0x80 && P2(apdu) == 0x86) {
            mbedtls_ecdh_context ctx;
            uint8_t kdata[67];
            uint8_t *data = apdu.data, *end = data + apdu.nc;
            size_t len = 0;
            if (mbedtls_asn1_get_tag(&data, end, &len, 0xA6) != 0) {
                return SW_WRONG_DATA();
            }
            if (*data++ != 0x7f) {
                return SW_WRONG_DATA();
            }
            if (mbedtls_asn1_get_tag(&data, end, &len,
                                     0x49) != 0 ||
                mbedtls_asn1_get_tag(&data, end, &len, 0x86) != 0) {
                return SW_WRONG_DATA();
            }
            //if (len != 2*key_size-1)
            //    return SW_WRONG_LENGTH();
            memcpy(kdata, file_get_data(ef), key_size);
            if (dek_decrypt(kdata, key_size) != 0) {
                return SW_EXEC_ERROR();
            }
            mbedtls_ecdh_init(&ctx);
            mbedtls_ecp_group_id gid = kdata[0];
            r = mbedtls_ecdh_setup(&ctx, gid);
            if (r != 0) {
                mbedtls_ecdh_free(&ctx);
                return SW_DATA_INVALID();
            }
            r = mbedtls_ecp_read_key(gid, (mbedtls_ecdsa_context *)&ctx.ctx.mbed_ecdh, kdata + 1, key_size - 1);
            if (r != 0) {
                mbedtls_ecdh_free(&ctx);
                return SW_DATA_INVALID();
            }
            r = mbedtls_ecdh_read_public(&ctx, data - 1, len + 1);
            if (r != 0) {
                mbedtls_ecdh_free(&ctx);
                return SW_DATA_INVALID();
            }
            size_t olen = 0;
            r = mbedtls_ecdh_calc_secret(&ctx,
                                         &olen,
                                         res_APDU,
                                         MBEDTLS_ECP_MAX_BYTES,
                                         random_gen,
                                         NULL);
            if (r != 0) {
                mbedtls_ecdh_free(&ctx);
                return SW_EXEC_ERROR();
            }
            res_APDU_size = olen;
            mbedtls_ecdh_free(&ctx);
        }
    }
    return SW_OK();
}
