/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *        http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
#include <openssl/pem.h>
#include <openssl/cms.h>
#include <openssl/err.h>
#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include "public.h"

const char *ca_cert      = "rootca.pem";
const char *crl_file     = "";
const char *content_file = "sign.txt";
const char *signed_file  = "signed.bin";
static int  verbose      = 0;

static void print_help(const char *exec_name)
{
    log_debug("Usage: %s [options]", exec_name);
    log_debug("Valid options are:");
    log_debug(" %-32s Root ca certificate file\n%*c  > default rootca.pem", "-r, --rootca=FILE", 32, ' ');
    log_debug(" %-32s CRL file\n%*c  > default: rootca.crl.pem", "-C, --crl=FILE", 32, ' ');
    log_debug(" %-32s Signed file\n%*c  > default: signed.bin", "-s, --signed=FILE", 32, ' ');
    log_debug(" %-32s Content file\n%*c  > default: sign.txt", "-c, --content=FILE", 32, ' ');
    log_debug(" %-32s Print debug messages", "-v, --verbose");
    log_debug(" %-32s Display this summary", "-h, --help");
}

static const char *opt_string = "r:C:c:s:hv";
static const struct option opts[]     = {
    {"rootca", required_argument, NULL, 'r'},
    {"crl", required_argument, NULL, 'C'},
    {"signed", required_argument, NULL, 's'},
    {"content", required_argument, NULL, 'c'},
    {"verbose", required_argument, NULL, 'v'},
    {"help", no_argument, NULL, 'h'},
    {NULL},
};

static int verify(void)
{
    BIO             *in = NULL, *out = NULL, *tbio = NULL, *cont = NULL, *crlbio = NULL;
    X509_STORE      *st     = NULL;
    X509            *cacert = NULL;
    X509_CRL        *crl    = NULL;
    CMS_ContentInfo *cms    = NULL;

    int ret = 1;

    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    do {
        log_debug("Set up trusted CA certificate store");
        st = X509_STORE_new();
        if (st == NULL) {
            break;
        }
        X509_STORE_set_purpose(st, X509_PURPOSE_OCSP_HELPER);
        unsigned int flags = CMS_DETACHED | CMS_NOSMIMECAP | CMS_BINARY;
        log_debug("Open CA certificate: %s", ca_cert);
        tbio = BIO_new_file(ca_cert, "r");
        if (tbio == NULL) {
            log_error("Open CA certificate failed: %s", ca_cert);
            break;
        }

        log_debug("Read CA certificate: %s", ca_cert);
        cacert = PEM_read_bio_X509(tbio, NULL, 0, NULL);
        if (cacert == NULL) {
            log_error("Read CA certificate failed");
            break;
        }

        uint32_t usage = X509_get_key_usage(cacert);
        if ((usage & KU_KEY_CERT_SIGN) == 0) {
            log_error("CA certificate missing KU_KEY_CERT_SIGN(Certificate Signing) usage");
            break;
        }
        log_debug("Add CA certificate: %s", ca_cert);
        if (X509_STORE_add_cert(st, cacert) == 0) {
            log_error("Add CA certificate failed: %s", ca_cert);
            break;
        }

        log_debug("Open crl certificate: %s", crl_file);

        if (((usage & KU_CRL_SIGN) != 0) && crl_file[0] != '\0') {
            crlbio = BIO_new_file(crl_file, "r");
            if (crlbio != NULL) {
                crl = PEM_read_bio_X509_CRL(crlbio, NULL, NULL, NULL);
                if (crl != NULL && (X509_STORE_add_crl(st, crl) != 0)) {
                    X509_STORE_set_flags(st, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
                } else {
                    log_error("Add crl certificate failed: %s", crl_file);
                    break;
                }
            }
        }

        log_debug("Open content being signed: %s", content_file);
        cont = BIO_new_file(content_file, "r");
        if (!cont) {
            log_error("Open content file failed: %s", content_file);
            break;
        }

        log_debug("Open message being verified: %s", signed_file);
        in = BIO_new_file(signed_file, "r");
        if (in == NULL || d2i_CMS_bio(in, &cms) == NULL || cms == NULL) {
            log_error("call d2i_CMS_bio failed");
            break;
        }

        log_debug("Verify content");
        if (CMS_verify(cms, NULL, st, cont, NULL, flags) == 0) {
            log_error("Verification Failure");
            break;
        }

        log_info("Verification successfully");
        ret = 0;
    } while (0);

    if (ret != 0) {
        print_ssl_error();
    }

    X509_STORE_free(st);
    CMS_ContentInfo_free(cms);
    X509_free(cacert);
    X509_CRL_free(crl);
    BIO_free(in);
    BIO_free(out);
    BIO_free(tbio);
    BIO_free(crlbio);
    BIO_free(cont);
    return ret;
}

int main(int argc, char **argv)
{
    int opt = 0;
    while ((opt = getopt_long(argc, argv, opt_string, opts, NULL)) != -1) {
        switch (opt) {
            case 'r':
                ca_cert = optarg;
                break;
            case 'C':
                crl_file = optarg;
                break;
            case 'c':
                content_file = optarg;
                break;
            case 's':
                signed_file = optarg;
                break;
            case 'v':
                verbose = 1;
                break;
            default:
                verbose = 1;
                print_help(argv[0]);
                return -1;
        }
    }

    if (verbose != 0) {
        log_debug("Parameters:");
        log_debug("  CA certificate:                 %s", ca_cert);
        log_debug("  CRL:                            %s", crl_file);
        log_debug("  Signed:                         %s", signed_file);
        log_debug("  Content:                        %s", content_file);
    }
    return verify();
}
