/*
 * (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
 * and is covered by GPLv3 license found in COPYING.
 *
 * verify.c -- Check signatures on firmware images in flash.
 *
 */
#include "verify.h"
#include "sha256.h"
#include "assets/screens.h"
#include "oled.h"
#include "misc.h"
#include "ae.h"
#include "gpio.h"
#include "delay.h"
#include "storage.h"
#include <string.h>
#include "micro-ecc/uECC.h"
#include "firmware-keys.h"

// 1 megabyte
#define MAIN_FLASH_SIZE          (1<<20)

// actual qty of bytes we check
// - minus 64 because the firmware signature is skipped
// - 0x400 of OTP area
// - plus (0x28) twice for option bytes
// - plus complete "system meory" area (boot rom with DFU)
// - 12 bytes of device serial number
#define TOTAL_CHECKSUM_LEN      (MAIN_FLASH_SIZE - 64 + 0x400 + (0x28 *2) + 0x7000 + 12)


// checksum_more()
//
    static void
checksum_more(SHA256_CTX *ctx, uint32_t *total, const uint8_t *addr, int len)
{
    // do a nice progress bar as it works.
    while(len) {
        int here = MIN(len, 8196);
        int percent = ((*total) * 100) / TOTAL_CHECKSUM_LEN;

        oled_show_progress(screen_verify, percent);
        sha256_update(ctx, addr, here);

        *total += here;
        addr += here;
        len -= here;

        if(dfu_button_pressed()) dfu_by_request();
    }
}


// checksum_flash()
//
    void
checksum_flash(uint8_t fw_digest[32], uint8_t world_digest[32])
{
    const uint8_t *start = (const uint8_t *)FIRMWARE_START;

    SHA256_CTX  ctx;
    uint32_t    total_len = 0;
    uint8_t first[32];

    sha256_init(&ctx);

    // start of firmware (just after we end) to header
    checksum_more(&ctx, &total_len, start, FW_HEADER_OFFSET + FW_HEADER_SIZE - 64);

    // from after header to end
    checksum_more(&ctx, &total_len, start + FW_HEADER_OFFSET + FW_HEADER_SIZE, 
                            FW_HDR->firmware_length - (FW_HEADER_OFFSET + FW_HEADER_SIZE));

    sha256_final(&ctx, first);

    // double SHA256
    sha256_init(&ctx);
    sha256_update(&ctx, first, sizeof(first));
    sha256_final(&ctx, fw_digest);

    // start over, and get the rest of flash. All of it.
    sha256_init(&ctx);

    // .. and chain in what we have so far
    sha256_update(&ctx, fw_digest, 32);

    // bootloader, including pairing secret area.
    const uint8_t *base = (const uint8_t *)BL_FLASH_BASE;
    checksum_more(&ctx, &total_len, base, start-base);

    // probably-blank area after firmware, and filesystem area
    const uint8_t *fs = start + FW_HDR->firmware_length;
    const uint8_t *last = base + MAIN_FLASH_SIZE;
    checksum_more(&ctx, &total_len, fs, last-fs);

    // OTP area
    checksum_more(&ctx, &total_len, (void *)0x1fff7000, 0x400);

    // "just in case" ... the option bytes (2 banks)
    checksum_more(&ctx, &total_len, (void *)0x1fff7800, 0x28);
    checksum_more(&ctx, &total_len, (void *)0x1ffff800, 0x28);

    // System ROM (they say it can't change, but clearly
    // implemented as flash cells)
    checksum_more(&ctx, &total_len, (void *)0x1fff0000, 0x7000);

    // device serial number, just for kicks
    checksum_more(&ctx, &total_len, (void *)0x1fff7590, 12);

    ASSERT(total_len == TOTAL_CHECKSUM_LEN);
    
    sha256_final(&ctx, world_digest);

    // double SHA256 (a bitcoin fetish)
    sha256_init(&ctx);
    sha256_update(&ctx, world_digest, 32);
    sha256_final(&ctx, world_digest);
}

// get_min_version()
//
// Scan the OTP area and determine what the current min-version (timestamp)
// we can allow. All zeros if any if okay.
//
    void
get_min_version(uint8_t min_version[8])
{
    const uint8_t *otp = (const uint8_t *)OPT_FLASH_BASE;

    memset(min_version, 0, 8);

    for(int i=0; i<NUM_OPT_SLOTS; i++, otp+=8) {
        // is it programmed?
        if(otp[0] == 0xff) continue;

        // is it a timestamp value?
        if(otp[0] >= 0x40) continue;
        if(otp[0] < 0x10) continue;

        if(memcmp(otp, min_version, 8) > 0) {
            memcpy(min_version, otp, 8);
        }
    }
}

// check_is_downgrade()
//
    bool
check_is_downgrade(const uint8_t timestamp[8])
{
    // look at FW_HDR->timestamp and compare to a growing list in main flash OTP
    uint8_t min[8];

    get_min_version(min);

    return (memcmp(timestamp, min, 8) < 0);
}

// check_factory_key()
//
    void
check_factory_key(uint32_t pubkey_num)
{
    if(IS_FACTORY_KEY(pubkey_num)) return;

    // warn the victim/altcoin user/developer
#if RELEASE
    const int wait = 100;
#else
    const int wait = 10;
#endif
    
    for(int i=0; i < wait; i++) {
        oled_show_progress(screen_devmode, (i*100)/wait);

        delay_ms(250);
    }
}

// verify_header()
//
    bool
verify_header(const coldcardFirmwareHeader_t *hdr)
{
    if(hdr->magic_value != FW_HEADER_MAGIC) goto fail;
    if(hdr->version_string[0] == 0x0) goto fail;
    if(hdr->timestamp[0] >= 0x40) goto fail;        // 22 yr product lifetime
    if(hdr->firmware_length < FW_MIN_LENGTH) goto fail;
    if(hdr->firmware_length > FW_MAX_LENGTH) goto fail;
    if(hdr->pubkey_num >= NUM_KNOWN_PUBKEYS) goto fail;

    return true;
fail:
    return false;
}

// verify_signature()
//
// Given double-sha256 over the firmware bytes, check the signature.
//
    bool
verify_signature(const coldcardFirmwareHeader_t *hdr, const uint8_t fw_check[32])
{
    int ok = uECC_verify(approved_pubkeys[hdr->pubkey_num], fw_check, 32,
                                    hdr->signature, uECC_secp256k1());
    return ok;
}

// verify_firmware()
//
    void
verify_firmware(void)
{
    STATIC_ASSERT(sizeof(coldcardFirmwareHeader_t) == FW_HEADER_SIZE);

    // watch for unprogrammed header. and some 
    if(FW_HDR->version_string[0] == 0xff) goto blank;
    if(!verify_header(FW_HDR)) goto fail;

    // measure checksum
    uint8_t fw_check[32], world_check[32];
    checksum_flash(fw_check, world_check);

    // Verify the signature
    // - use pubkey_num to pick a specific key
    if(!verify_signature(FW_HDR, fw_check)) goto fail;
 
    // Push the hash to the 508a which might make the Genuine light green,
    // but only if we arrived at same hash before. It decides.
    int not_green = ae_set_gpio_secure(world_check);

    // maybe show big warning if not an "approved" key
    if(not_green) {
        check_factory_key(FW_HDR->pubkey_num);
    }

    oled_show_progress(screen_verify, 100);

    return;

fail:
    oled_show(screen_corrupt);
    enter_dfu();
    return;

blank:
    oled_show(screen_dfu);
    enter_dfu();
    return;
}

// EOF
