/*
   OpenSSL based Authenticode signing for PE, CAB, CAT, MSI, APPX and script files.

   Copyright (C) 2005-2015 Per Allansson <pallansson@gmail.com>
   Copyright (C) 2018-2023 Michał Trojnara <Michal.Trojnara@stunnel.org>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

   In addition, as a special exception, the copyright holders give
   permission to link the code of portions of this program with the
   OpenSSL library under certain conditions as described in each
   individual source file, and distribute linked combinations
   including the two.
   You must obey the GNU General Public License in all respects
   for all of the code used other than OpenSSL.  If you modify
   file(s) with this exception, you may extend this exception to your
   version of the file(s), but you are not obligated to do so.  If you
   do not wish to do so, delete this exception statement from your
   version.  If you delete this exception statement from all source
   files in the program, then also delete it here.
*/

/*
   Implemented with good help from:

   * Peter Gutmann's analysis of Authenticode:

     https://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt

   * MS CAB SDK documentation

     https://docs.microsoft.com/en-us/previous-versions/ms974336(v=msdn.10)

   * MS PE/COFF documentation

     https://docs.microsoft.com/en-us/windows/win32/debug/pe-format

   * MS Windows Authenticode PE Signature Format

     http://msdn.microsoft.com/en-US/windows/hardware/gg463183

     (Although the part of how the actual checksumming is done is not
     how it is done inside Windows. The end result is however the same
     on all "normal" PE files.)

   * tail -c, tcpdump, mimencode & openssl asn1parse :)

*/

#include "osslsigncode.h"
#include "helpers.h"

/*
 * $ echo -n 3006030200013000 | xxd -r -p | openssl asn1parse -i -inform der
 * 0:d=0  hl=2 l=   6 cons: SEQUENCE
 * 2:d=1  hl=2 l=   2 prim:  BIT STRING
 * 6:d=1  hl=2 l=   0 cons:  SEQUENCE
*/
const u_char java_attrs_low[] = {
    0x30, 0x06, 0x03, 0x02, 0x00, 0x01, 0x30, 0x00
};

/*
 * $ echo -n 300c060a2b060104018237020115 | xxd -r -p | openssl asn1parse -i -inform der
 * 0:d=0  hl=2 l=  12 cons: SEQUENCE
 * 2:d=1  hl=2 l=  10 prim:  OBJECT     :Microsoft Individual Code Signing
*/
const u_char purpose_ind[] = {
    0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04,
    0x01, 0x82, 0x37, 0x02, 0x01, 0x15
};

/*
 * $ echo -n 300c060a2b060104018237020116 | xxd -r -p | openssl asn1parse -i -inform der
 * 0:d=0  hl=2 l=  12 cons: SEQUENCE
 * 2:d=1  hl=2 l=  10 prim:  OBJECT     :Microsoft Commercial Code Signing
*/
const u_char purpose_comm[] = {
    0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04,
    0x01, 0x82, 0x37, 0x02, 0x01, 0x16
};

/*
 * ASN.1 definitions (more or less from official MS Authenticode docs)
 */
ASN1_CHOICE(SpcString) = {
    ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING, 0),
    ASN1_IMP_OPT(SpcString, value.ascii, ASN1_IA5STRING, 1)
} ASN1_CHOICE_END(SpcString)

IMPLEMENT_ASN1_FUNCTIONS(SpcString)

ASN1_SEQUENCE(SpcSerializedObject) = {
    ASN1_SIMPLE(SpcSerializedObject, classId, ASN1_OCTET_STRING),
    ASN1_SIMPLE(SpcSerializedObject, serializedData, ASN1_OCTET_STRING)
} ASN1_SEQUENCE_END(SpcSerializedObject)

IMPLEMENT_ASN1_FUNCTIONS(SpcSerializedObject)

ASN1_CHOICE(SpcLink) = {
    ASN1_IMP_OPT(SpcLink, value.url, ASN1_IA5STRING, 0),
    ASN1_IMP_OPT(SpcLink, value.moniker, SpcSerializedObject, 1),
    ASN1_EXP_OPT(SpcLink, value.file, SpcString, 2)
} ASN1_CHOICE_END(SpcLink)

IMPLEMENT_ASN1_FUNCTIONS(SpcLink)

ASN1_SEQUENCE(SpcSpOpusInfo) = {
    ASN1_EXP_OPT(SpcSpOpusInfo, programName, SpcString, 0),
    ASN1_EXP_OPT(SpcSpOpusInfo, moreInfo, SpcLink, 1)
} ASN1_SEQUENCE_END(SpcSpOpusInfo)

IMPLEMENT_ASN1_FUNCTIONS(SpcSpOpusInfo)

ASN1_SEQUENCE(SpcSipInfo) = {
    ASN1_SIMPLE(SpcSipInfo, a, ASN1_INTEGER),
    ASN1_SIMPLE(SpcSipInfo, string, ASN1_OCTET_STRING),
    ASN1_SIMPLE(SpcSipInfo, b, ASN1_INTEGER),
    ASN1_SIMPLE(SpcSipInfo, c, ASN1_INTEGER),
    ASN1_SIMPLE(SpcSipInfo, d, ASN1_INTEGER),
    ASN1_SIMPLE(SpcSipInfo, e, ASN1_INTEGER),
    ASN1_SIMPLE(SpcSipInfo, f, ASN1_INTEGER),
} ASN1_SEQUENCE_END(SpcSipInfo)

IMPLEMENT_ASN1_FUNCTIONS(SpcSipInfo)

ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = {
    ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT),
    ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY)
} ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue)

IMPLEMENT_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue)

ASN1_SEQUENCE(AlgorithmIdentifier) = {
    ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT),
    ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY)
} ASN1_SEQUENCE_END(AlgorithmIdentifier)

IMPLEMENT_ASN1_FUNCTIONS(AlgorithmIdentifier)

ASN1_SEQUENCE(DigestInfo) = {
    ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier),
    ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING)
} ASN1_SEQUENCE_END(DigestInfo)

IMPLEMENT_ASN1_FUNCTIONS(DigestInfo)

ASN1_SEQUENCE(SpcIndirectDataContent) = {
    ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue),
    ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo)
} ASN1_SEQUENCE_END(SpcIndirectDataContent)

IMPLEMENT_ASN1_FUNCTIONS(SpcIndirectDataContent)

ASN1_SEQUENCE(CatalogAuthAttr) = {
    ASN1_SIMPLE(CatalogAuthAttr, type, ASN1_OBJECT),
    ASN1_OPT(CatalogAuthAttr, contents, ASN1_ANY)
} ASN1_SEQUENCE_END(CatalogAuthAttr)

IMPLEMENT_ASN1_FUNCTIONS(CatalogAuthAttr)

/*
 * Structures for Authenticode Timestamp
 */
ASN1_SEQUENCE(TimeStampRequestBlob) = {
    ASN1_SIMPLE(TimeStampRequestBlob, type, ASN1_OBJECT),
    ASN1_EXP_OPT(TimeStampRequestBlob, signature, ASN1_OCTET_STRING, 0)
} ASN1_SEQUENCE_END(TimeStampRequestBlob)

IMPLEMENT_ASN1_FUNCTIONS(TimeStampRequestBlob)

ASN1_SEQUENCE(TimeStampRequest) = {
    ASN1_SIMPLE(TimeStampRequest, type, ASN1_OBJECT),
    ASN1_SIMPLE(TimeStampRequest, blob, TimeStampRequestBlob)
} ASN1_SEQUENCE_END(TimeStampRequest)

IMPLEMENT_ASN1_FUNCTIONS(TimeStampRequest)


ASN1_SEQUENCE(CatalogInfo) = {
    ASN1_SIMPLE(CatalogInfo, digest, ASN1_OCTET_STRING),
    ASN1_SET_OF(CatalogInfo, attributes, CatalogAuthAttr)
} ASN1_SEQUENCE_END(CatalogInfo)

IMPLEMENT_ASN1_FUNCTIONS(CatalogInfo)

ASN1_SEQUENCE(MsCtlContent) = {
    ASN1_SIMPLE(MsCtlContent, type, SpcAttributeTypeAndOptionalValue),
    ASN1_SIMPLE(MsCtlContent, identifier, ASN1_OCTET_STRING),
    ASN1_SIMPLE(MsCtlContent, time, ASN1_UTCTIME),
    ASN1_SIMPLE(MsCtlContent, version, SpcAttributeTypeAndOptionalValue),
    ASN1_SEQUENCE_OF(MsCtlContent, header_attributes, CatalogInfo),
    ASN1_OPT(MsCtlContent, filename, ASN1_ANY)
} ASN1_SEQUENCE_END(MsCtlContent)

IMPLEMENT_ASN1_FUNCTIONS(MsCtlContent)


#ifndef OPENSSL_NO_ENGINE
ASN1_SEQUENCE(EngineControl) = {
    ASN1_SIMPLE(EngineControl, cmd, ASN1_OCTET_STRING),
    ASN1_SIMPLE(EngineControl, param, ASN1_OCTET_STRING)
} ASN1_SEQUENCE_END(EngineControl)

IMPLEMENT_ASN1_FUNCTIONS(EngineControl)
#endif /* OPENSSL_NO_ENGINE */

#if OPENSSL_VERSION_NUMBER>=0x30000000L
DEFINE_STACK_OF(OSSL_PROVIDER)
static STACK_OF(OSSL_PROVIDER) *providers = NULL;

static void provider_free(OSSL_PROVIDER *prov);
static void providers_cleanup(void);
static int provider_load(const char *pname);
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */

/* Prototypes */
static ASN1_INTEGER *create_nonce(int bits);
static char *clrdp_url_get_x509(X509 *cert);
static time_t time_t_get_asn1_time(const ASN1_TIME *s);
static time_t time_t_get_si_time(PKCS7_SIGNER_INFO *si);
static ASN1_UTCTIME *asn1_time_get_si_time(PKCS7_SIGNER_INFO *si);
static time_t time_t_get_cms_time(CMS_ContentInfo *cms);
static CMS_ContentInfo *cms_get_timestamp(PKCS7_SIGNED *p7_signed,
    PKCS7_SIGNER_INFO *countersignature);
static int cursig_set_nested(PKCS7 *cursig, PKCS7 *p7);
static int nested_signatures_number_get(PKCS7 *p7);
static int X509_attribute_chain_append_object(STACK_OF(X509_ATTRIBUTE) **unauth_attr,
    u_char *p, int len, const char *oid);
static STACK_OF(PKCS7) *signature_list_create(PKCS7 *p7);
static int PKCS7_compare(const PKCS7 *const *a, const PKCS7 *const *b);
static PKCS7 *pkcs7_get_sigfile(FILE_FORMAT_CTX *ctx);
static void print_cert(X509 *cert, int i);
static int x509_store_load_crlfile(X509_STORE *store, char *cafile, char *crlfile);
static void load_objects_from_store(const char *url, char *pass, EVP_PKEY **pkey, STACK_OF(X509) *certs, STACK_OF(X509_CRL) *crls);
static BIO *bio_new_file(const char *filename, const char *mode);
#ifndef OPENSSL_NO_ENGINE
static void engine_control_set(GLOBAL_OPTIONS *options, const char *arg);
#endif /* OPENSSL_NO_ENGINE */


/*
  A timestamp request looks like this:

  POST <someurl> HTTP/1.1
  Content-Type: application/octet-stream
  Content-Length: ...
  Accept: application/octet-stream
  User-Agent: Transport
  Host: ...
  Cache-Control: no-cache

  <base64encoded blob>

  .. and the blob has the following ASN1 structure:

  0:d=0  hl=4 l= 291 cons: SEQUENCE
  4:d=1  hl=2 l=  10 prim:  OBJECT         :1.3.6.1.4.1.311.3.2.1
  16:d=1 hl=4 l= 275 cons:  SEQUENCE
  20:d=2 hl=2 l=   9 prim:   OBJECT        :pkcs7-data
  31:d=2 hl=4 l= 260 cons:   cont [ 0 ]
  35:d=3 hl=4 l= 256 prim:    OCTET STRING
  <signature>

  .. and it returns a base64 encoded PKCS#7 structure.
*/

/*
 * Encode RFC3161 timestamp request and write it into BIO
 * [in] p7: new PKCS#7 signature
 * [in] md: message digest algorithm type
 * [returns] pointer to BIO with RFC3161 Timestamp Request
 */
static BIO *bio_encode_rfc3161_request(PKCS7 *p7, const EVP_MD *md)
{
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info;
    PKCS7_SIGNER_INFO *si;
    u_char mdbuf[EVP_MAX_MD_SIZE];
    TS_MSG_IMPRINT *msg_imprint = NULL;
    ASN1_INTEGER *nonce = NULL;
    X509_ALGOR *alg = NULL;
    TS_REQ *req = NULL;
    BIO *bout = NULL, *bhash = NULL;
    u_char *p;
    int len;

    signer_info = PKCS7_get_signer_info(p7);
    if (!signer_info)
        goto out;

    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si)
        goto out;

    bhash = BIO_new(BIO_f_md());
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
    if (!BIO_set_md(bhash, md)) {
        fprintf(stderr, "Unable to set the message digest of BIO\n");
        goto out;
    }
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
    BIO_push(bhash, BIO_new(BIO_s_null()));
    BIO_write(bhash, si->enc_digest->data, si->enc_digest->length);
    BIO_gets(bhash, (char*)mdbuf, EVP_MD_size(md));

    req = TS_REQ_new();
    if (!req)
        goto out;
    if (!TS_REQ_set_version(req, 1))
        goto out;

    msg_imprint = TS_MSG_IMPRINT_new();
    if (!msg_imprint)
        goto out;
    alg = X509_ALGOR_new();
    if (!alg)
        goto out;
    X509_ALGOR_set_md(alg, md);
    if (!X509_ALGOR_set0(alg, OBJ_nid2obj(EVP_MD_nid(md)), V_ASN1_NULL, NULL))
        goto out;
    if (!TS_MSG_IMPRINT_set_algo(msg_imprint, alg))
        goto out;
    if (!TS_MSG_IMPRINT_set_msg(msg_imprint, mdbuf, EVP_MD_size(md)))
        goto out;
    if (!TS_REQ_set_msg_imprint(req, msg_imprint))
        goto out;
    /* Setting nonce */
    nonce = create_nonce(NONCE_LENGTH);
    if (!nonce)
        goto out;
    if (!TS_REQ_set_nonce(req, nonce))
        goto out;
    /* TSA is expected to include its signing certificate in the response, flag 0xFF */
    if (!TS_REQ_set_cert_req(req, 1))
        goto out;

    len = i2d_TS_REQ(req, NULL);
    p = OPENSSL_malloc((size_t)len);
    len = i2d_TS_REQ(req, &p);
    p -= len;

    bout = BIO_new(BIO_s_mem());
    BIO_write(bout, p, len);
    OPENSSL_free(p);
    (void)BIO_flush(bout);

out:
    BIO_free_all(bhash);
    ASN1_INTEGER_free(nonce);
    TS_MSG_IMPRINT_free(msg_imprint);
    X509_ALGOR_free(alg);
    TS_REQ_free(req);

    return bout;
}

static ASN1_INTEGER *create_nonce(int bits)
{
    unsigned char buf[20];
    ASN1_INTEGER *nonce = NULL;
    int len = (bits - 1) / 8 + 1;
    int i;

    if (len > (int)sizeof(buf)) {
        fprintf(stderr, "Invalid nonce size\n");
        return NULL;
    }
    if (RAND_bytes(buf, len) <= 0) {
        fprintf(stderr, "Random nonce generation failed\n");
        return NULL;
    }
    /* Find the first non-zero byte and creating ASN1_INTEGER object. */
    for (i = 0; i < len && !buf[i]; ++i) {
    }
    nonce = ASN1_INTEGER_new();
    if (!nonce) {
        fprintf(stderr, "Could not create nonce\n");
        return NULL;
    }
    OPENSSL_free(nonce->data);
    nonce->length = len - i;
    nonce->data = OPENSSL_malloc((size_t)nonce->length + 1);
    memcpy(nonce->data, buf + i, (size_t)nonce->length);
    return nonce;
}

/*
 * Encode authenticode timestamp request and write it into BIO
 * [in] p7: new PKCS#7 signature
 * [returns] pointer to BIO with authenticode Timestamp Request
 */
static BIO *bio_encode_authenticode_request(PKCS7 *p7)
{
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info;
    PKCS7_SIGNER_INFO *si;
    TimeStampRequest *req;
    BIO *bout, *b64;
    u_char *p;
    int len;

    signer_info = PKCS7_get_signer_info(p7);
    if (!signer_info)
        return 0; /* FAILED */

    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si)
        return 0; /* FAILED */

    req = TimeStampRequest_new();
    req->type = OBJ_txt2obj(SPC_TIME_STAMP_REQUEST_OBJID, 1);
    req->blob->type = OBJ_nid2obj(NID_pkcs7_data);
    req->blob->signature = si->enc_digest;

    len = i2d_TimeStampRequest(req, NULL);
    p = OPENSSL_malloc((size_t)len);
    len = i2d_TimeStampRequest(req, &p);
    p -= len;
    req->blob->signature = NULL;
    TimeStampRequest_free(req);

    bout = BIO_new(BIO_s_mem());
    b64 = BIO_new(BIO_f_base64());
    bout = BIO_push(b64, bout);
    BIO_write(bout, p, len);
    OPENSSL_free(p);
    (void)BIO_flush(bout);
    return bout;
}

/*
 * If successful the RFC 3161 timestamp will be written into
 * the PKCS7 SignerInfo structure as an unauthenticated attribute - cont[1].
 * [in, out] p7: new PKCS#7 signature
 * [in] response: RFC3161 response
 * [in] verbose: additional output mode
 * [returns] 1 on error or 0 on success
 */
static int attach_rfc3161_response(PKCS7 *p7, TS_RESP *response, int verbose)
{
    PKCS7_SIGNER_INFO *si;
    TS_STATUS_INFO *status;
    PKCS7 *token;
    u_char *p;
    int i, len;
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info = PKCS7_get_signer_info(p7);

    if (!signer_info)
        return 1; /* FAILED */
    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si)
        return 1; /* FAILED */
    if (!response)
        return 1; /* FAILED */

    status = TS_RESP_get_status_info(response);
    if (ASN1_INTEGER_get(TS_STATUS_INFO_get0_status(status)) != 0) {
        if (verbose) {
            const STACK_OF(ASN1_UTF8STRING) *reasons = TS_STATUS_INFO_get0_text(status);
            fprintf(stderr, "Timestamping failed: status %ld\n", ASN1_INTEGER_get(TS_STATUS_INFO_get0_status(status)));
            for (i = 0; i < sk_ASN1_UTF8STRING_num(reasons); i++) {
                ASN1_UTF8STRING *reason = sk_ASN1_UTF8STRING_value(reasons, i);
                fprintf(stderr, "%s\n", ASN1_STRING_get0_data(reason));
            }
        }
        return 1; /* FAILED */
    }
    token = TS_RESP_get_token(response);
    if (((len = i2d_PKCS7(token, NULL)) <= 0) || (p = OPENSSL_malloc((size_t)len)) == NULL) {
        if (verbose) {
            fprintf(stderr, "Failed to convert pkcs7: %d\n", len);
            ERR_print_errors_fp(stderr);
        }
        return 1; /* FAILED */
    }
    len = i2d_PKCS7(token, &p);
    p -= len;
    if (!X509_attribute_chain_append_object(&(si->unauth_attr), p, len, SPC_RFC3161_OBJID)) {
        OPENSSL_free(p);
        return 1; /* FAILED */
    }
    OPENSSL_free(p);
    return 0; /* OK */
}

/*
 * If successful the authenticode timestamp will be written into
 * the PKCS7 SignerInfo structure as an unauthenticated attribute - cont[1]:
 * p7->d.sign->signer_info->unauth_attr
 * [in, out] p7: new PKCS#7 signature
 * [in] resp: PKCS#7 authenticode response
 * [in] verbose: additional output mode
 * [returns] 1 on error or 0 on success
 */
static int attach_authenticode_response(PKCS7 *p7, PKCS7 *resp, int verbose)
{
    PKCS7_SIGNER_INFO *info, *si;
    u_char *p;
    int len, i;
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info;

    if (!resp) {
        return 1; /* FAILED */
    }
    for(i = sk_X509_num(resp->d.sign->cert)-1; i>=0; i--) {
        PKCS7_add_certificate(p7, sk_X509_value(resp->d.sign->cert, i));
    }
    signer_info = PKCS7_get_signer_info(resp);
    if (!signer_info) {
        PKCS7_free(resp);
        return 1; /* FAILED */
    }
    info = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!info) {
        PKCS7_free(resp);
        return 1; /* FAILED */
    }
    if (((len = i2d_PKCS7_SIGNER_INFO(info, NULL)) <= 0) || (p = OPENSSL_malloc((size_t)len)) == NULL) {
        if (verbose) {
            fprintf(stderr, "Failed to convert signer info: %d\n", len);
            ERR_print_errors_fp(stderr);
        }
        PKCS7_free(resp);
        return 1; /* FAILED */
    }
    len = i2d_PKCS7_SIGNER_INFO(info, &p);
    p -= len;
    PKCS7_free(resp);
    signer_info = PKCS7_get_signer_info(p7);
    if (!signer_info)
        return 1; /* FAILED */
    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si)
        return 1; /* FAILED */
    if (!X509_attribute_chain_append_object(&(si->unauth_attr), p, len, PKCS9_COUNTER_SIGNATURE)) {
        OPENSSL_free(p);
        return 1; /* FAILED */
    }
    OPENSSL_free(p);
    return 0; /* OK */
}

#if defined(ENABLE_CURL) || OPENSSL_VERSION_NUMBER >= 0x30000000L

static void print_proxy(char *proxy)
{
    if (proxy) {
        printf ("Using configured proxy: %s\n", proxy);
    } else {
        char *http_proxy, *https_proxy;

        http_proxy = getenv("http_proxy");
        if (!http_proxy)
            http_proxy = getenv("HTTP_PROXY");
        if (http_proxy && *http_proxy != '\0')
            printf ("Using environmental HTTP proxy: %s\n", http_proxy);
        https_proxy = getenv("https_proxy");
        if (!https_proxy)
            https_proxy = getenv("HTTPS_PROXY");
        if (https_proxy && *https_proxy != '\0')
            printf ("Using environmental HTTPS proxy: %s\n", https_proxy);
    }
}

#endif /* ENABLE_CURL || OPENSSL_VERSION_NUMBER < 0x30000000L */

#if OPENSSL_VERSION_NUMBER<0x30000000L
#ifdef ENABLE_CURL

static int blob_has_nl = 0;

/*
 * Callback for writing received data
 */
static size_t curl_write(void *ptr, size_t sz, size_t nmemb, void *stream)
{
    size_t written, len = sz * nmemb;

    if (len > 0 && !blob_has_nl) {
        if (memchr(ptr, '\n', len))
            blob_has_nl = 1;
    }
    if (!BIO_write_ex((BIO*)stream, ptr, len, &written) || written != len)
        return 0; /* FAILED */
    return written;
}

/*
 * Get data from HTTP server.
 * [out] http_code: HTTP status
 * [in] url: URL of the CRL distribution point or Time-Stamp Authority HTTP server
 * [in] req: timestamp request
 * [in] proxy: proxy to getting the timestamp through
 * [in] noverifypeer: do not verify the Time-Stamp Authority's SSL certificate
 * [in] verbose: additional output mode
 * [in] rfc3161: Authenticode / RFC3161 Timestamp switch
 * [returns] pointer to BIO with X509 Certificate Revocation List or timestamp response
 */
static BIO *bio_get_http_curl(long *http_code, char *url, BIO *req, char *proxy,
    int noverifypeer, int verbose, int rfc3161)
{
    CURL *curl;
    struct curl_slist *slist = NULL;
    CURLcode res;
    BIO *bin;
    u_char *p = NULL;
    long len = 0;

    if (!url) {
        return NULL; /* FAILED */
    }
    print_proxy(proxy);
    /* Start a libcurl easy session and set options for a curl easy handle */
    printf("Connecting to %s\n", url);
    curl = curl_easy_init();
    if (!curl) {
        fprintf(stderr, "CURL initialization failed\n");
        return NULL; /* FAILED */
    }
    if (proxy) {
        res = curl_easy_setopt(curl, CURLOPT_PROXY, proxy);
        if (res != CURLE_OK) {
            fprintf(stderr, "CURL failure: %s %s\n", curl_easy_strerror(res), url);
        }
        if (!strncmp("http:", proxy, 5)) {
            res = curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
            if (res != CURLE_OK) {
                fprintf(stderr, "CURL failure: %s %s\n", curl_easy_strerror(res), url);
            }
        }
        if (!strncmp("socks:", proxy, 6)) {
            res = curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
            if (res != CURLE_OK) {
                fprintf(stderr, "CURL failure: %s %s\n", curl_easy_strerror(res), url);
            }
        }
    }
    res = curl_easy_setopt(curl, CURLOPT_URL, url);
    if (res != CURLE_OK) {
        fprintf(stderr, "CURL failure: %s %s\n", curl_easy_strerror(res), url);
    }
    /*
     * ask libcurl to show us the verbose output
     * curl_easy_setopt(curl, CURLOPT_VERBOSE, 42);
     */
    if (noverifypeer) {
        res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        if (res != CURLE_OK) {
            fprintf(stderr, "CURL failure: %s %s\n", curl_easy_strerror(res), url);
        }
    }
    if (req) { /* POST */
        if (rfc3161) {
            /* RFC3161 Timestamp */
            slist = curl_slist_append(slist, "Content-Type: application/timestamp-query");
            slist = curl_slist_append(slist, "Accept: application/timestamp-reply");
        } else {
            /* Authenticode Timestamp */
            slist = curl_slist_append(slist, "Content-Type: application/octet-stream");
            slist = curl_slist_append(slist, "Accept: application/octet-stream");
        }
        slist = curl_slist_append(slist, "User-Agent: Transport");
        slist = curl_slist_append(slist, "Cache-Control: no-cache");
        res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
        if (res != CURLE_OK) {
            fprintf(stderr, "CURL failure: %s %s\n", curl_easy_strerror(res), url);
        }
        len = BIO_get_mem_data(req, &p);
        res = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
        if (res != CURLE_OK) {
            fprintf(stderr, "CURL failure: %s %s\n", curl_easy_strerror(res), url);
        }
        res = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (char*)p);
        if (res != CURLE_OK) {
            fprintf(stderr, "CURL failure: %s %s\n", curl_easy_strerror(res), url);
        }
        res = curl_easy_setopt(curl, CURLOPT_POST, 1);
        if (res != CURLE_OK) {
            fprintf(stderr, "CURL failure: %s %s\n", curl_easy_strerror(res), url);
        }
    }
    bin = BIO_new(BIO_s_mem());
    BIO_set_mem_eof_return(bin, 0);
    res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, bin);
    if (res != CURLE_OK) {
        fprintf(stderr, "CURL failure: %s %s\n", curl_easy_strerror(res), url);
    }
    res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write);
    if (res != CURLE_OK) {
        fprintf(stderr, "CURL failure: %s %s\n", curl_easy_strerror(res), url);
    }
    /* Perform the request */
    res = curl_easy_perform(curl);
    curl_slist_free_all(slist);

    if (res != CURLE_OK) {
        BIO_free_all(bin);
        if (verbose)
            fprintf(stderr, "CURL failure: %s %s\n", curl_easy_strerror(res), url);
        curl_easy_cleanup(curl);
        return NULL; /* FAILED */
    } else {
        /* CURLE_OK (0) */
        (void)BIO_flush(bin);
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_code);
    }
    /* End a libcurl easy handle */
    curl_easy_cleanup(curl);
    if (req && !rfc3161) {
        /* BASE64 encoded Authenticode Timestamp */
        BIO *b64 = BIO_new(BIO_f_base64());

        if (!blob_has_nl)
            BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
        bin = BIO_push(b64, bin);
    }
    return bin;
}
#endif /* ENABLE_CURL */

#else /* OPENSSL_VERSION_NUMBER<0x30000000L */

/* HTTP callback function that supports TLS connection also via HTTPS proxy */
static BIO *http_tls_cb(BIO *bio, void *arg, int connect, int detail)
{
    HTTP_TLS_Info *info = (HTTP_TLS_Info *)arg;
    SSL_CTX *ssl_ctx = info->ssl_ctx;

    if (ssl_ctx == NULL) {
        /* not using TLS */
        return bio;
    }
    if (connect && detail) {
        /* connecting with TLS */
        SSL *ssl;
        BIO *sbio = NULL;

        if (info->use_proxy && !OSSL_HTTP_proxy_connect(bio, info->server,
            info->port, NULL, NULL, info->timeout, NULL, NULL)) {
            return NULL;
        }
        sbio = BIO_new(BIO_f_ssl());
        if (sbio == NULL) {
            return NULL;
        }
        ssl = SSL_new(ssl_ctx);
        if (ssl == NULL) {
            BIO_free(sbio);
            return NULL;
        }
        SSL_set_tlsext_host_name(ssl, info->server);
        SSL_set_connect_state(ssl);
        BIO_set_ssl(sbio, ssl, BIO_CLOSE);
        bio = BIO_push(sbio, bio);
    }
    return bio;
}

static int verify_callback(int ok, X509_STORE_CTX *ctx)
{
    if (!ok) {
        int error = X509_STORE_CTX_get_error(ctx);

        print_cert(X509_STORE_CTX_get_current_cert(ctx), 0);
        if (error == X509_V_ERR_UNABLE_TO_GET_CRL) {
            char *url = clrdp_url_get_x509(X509_STORE_CTX_get_current_cert(ctx));

            printf("\tWarning: Ignoring \'%s\' error for CRL validation\n",
                X509_verify_cert_error_string(error));
            printf("\nUse the \"-HTTPS-CRLfile\" option to verify CRL\n");
            if (url) {
                printf("HTTPS's CRL distribution point: %s\n", url);
                OPENSSL_free(url);
            }
            return 1;
        } else {
            printf("\tError: %s\n\n", X509_verify_cert_error_string(error));
        }
    }
    return ok;
}

/*
 * Read data from socket BIO
 * [in] s_bio: socket BIO
 * [returns] memory BIO
 */
static BIO *socket_bio_read(BIO *s_bio)
{
    int retry = 1, ok = 0;
    char *buf = OPENSSL_malloc(OSSL_HTTP_DEFAULT_MAX_RESP_LEN);
    BIO *resp = BIO_new(BIO_s_mem());

    while (retry) {
        int n;

        errno = 0;
        n = BIO_read(s_bio, buf, OSSL_HTTP_DEFAULT_MAX_RESP_LEN);
        if (n > 0) {
            (void)BIO_write(resp, buf, n);
        } else if (BIO_eof(s_bio) == 1) {
            ok = 1;
            retry = 0; /* HTTP EOF */
        } else if (BIO_should_retry(s_bio)) {
        } else {
            unsigned long err = ERR_get_error();

            if (err == 0) {
                ok = 1;
                retry = 0; /* HTTPS EOF */
            } else {
                fprintf(stderr, "\nHTTP failure: error %ld: %s\n", err, ERR_reason_error_string(err));
                retry = 0; /* FAILED */
            }
        }
    }
    OPENSSL_free(buf);
    if (!ok) {
        BIO_free_all(resp);
        resp = NULL;
    }

    return resp;
}

/*
 * pkcs7-signedData bytes found indicates DER form
 * in otherwise BASE64 encoded
 * '\n' newline character means BASE64 line with newline at the end
 * in otherwise BIO_FLAGS_BASE64_NO_NL flag must me set
 * [in, out] resp: memory BIO with Authenticode Timestamp data
 * [returns] none
 */
static void check_authenticode_timestamp(BIO **resp)
{
    u_char *ptr = NULL;
    const u_char pkcs7_signed[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02};
    int i, len, pkcs7_signed_len, found = 0;

    len = (int)BIO_get_mem_data(*resp, &ptr);
    if (len <= 0) {
        return;
    }
    pkcs7_signed_len = (int)sizeof pkcs7_signed;
    for (i = 0; i <= len - pkcs7_signed_len; i++) {
        if (memcmp(ptr + i, pkcs7_signed, (size_t)pkcs7_signed_len) == 0) {
            found = 1;
            break;
        }
    }
    if (!found) {
        /* BASE64 encoded Authenticode Timestamp */
        BIO *b64 = BIO_new(BIO_f_base64());

        if (!memchr(ptr, '\n', (size_t)len)) {
            BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
        } else {
            BIO *bio_mem = BIO_new_mem_buf(ptr, len);
            BIO_push(b64, bio_mem);
        }
        *resp = BIO_push(b64, *resp);
    }
}

/*
 * Get data from HTTP server.
 * [in] url: URL of the CRL distribution point or Time-Stamp Authority HTTP server
 * [in] req: timestamp request
 * [in] proxy: proxy to getting the timestamp through
 * [in] rfc3161: Authenticode / RFC3161 Timestamp switch
 * [in] cafile: file contains concatenated CA certificates in PEM format
 * [in] crlfile: file contains Certificate Revocation List (CRLs)
 * [returns] pointer to BIO with X509 Certificate Revocation List or timestamp response
 */
static BIO *bio_get_http(char *url, BIO *req, char *proxy, int rfc3161, char *cafile, char *crlfile)
{
    BIO *tmp_bio = NULL, *s_bio = NULL, *resp = NULL;
    HTTP_TLS_Info info;
    SSL_CTX *ssl_ctx = NULL;
    char *server = NULL, *port = NULL, *path = NULL;
    int timeout = -1; /* blocking mode, exactly one try, see BIO_do_connect_retry() */
    int use_ssl = 0;

    if (!url) {
        return NULL; /* FAILED */
    }
    print_proxy(proxy);
    printf("Connecting to %s\n", url);

    if (!OSSL_HTTP_parse_url(url, &use_ssl, NULL, &server, &port, NULL, &path, NULL, NULL)) {
        return NULL; /* FAILED */
    }
    if (use_ssl) {
        X509_STORE *store = NULL;

        ssl_ctx = SSL_CTX_new(TLS_client_method());
        if (cafile) {
            printf("HTTPS-CAfile: %s\n", cafile);
            if (crlfile)
                printf("HTTPS-CRLfile: %s\n", crlfile);
            store = SSL_CTX_get_cert_store(ssl_ctx);
            if (x509_store_load_crlfile(store, cafile, crlfile))
                SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, verify_callback);
            else
                printf("Warning: HTTPS verification was skipped\n");
        } else {
            printf("Warning: HTTPS verification was skipped\n");
        }
    }
    info.server = server;
    info.port = port;
    info.use_proxy = OSSL_HTTP_adapt_proxy(proxy, NULL, server, use_ssl) != NULL;
    info.timeout = timeout;
    info.ssl_ctx = ssl_ctx;

    if (!req) { /* GET */
        /*
         * HTTP server implementations accessed via the URI SHOULD specify the
         * media type application/pkix-crl in the Content-Type header field of
         * the response (RFC 5280, section 4.2.1.13).
         * In practice, some CRL distribution points return "application/octet-stream"
         * instead. Therefore, do not enforce the Content-Type and rely on the CRL
         * parser to validate the response content.
         */
        s_bio = OSSL_HTTP_get(url, proxy, NULL, NULL, NULL, http_tls_cb, &info, 0,
            NULL, NULL, 0, 0, timeout);
    } else { /* POST */
        const char *content_type = "application/timestamp-query"; /* RFC3161 Timestamp */
        const char *expected_content_type = "application/timestamp-reply";

        if (!rfc3161) {
            u_char *p = NULL;
            long len = BIO_get_mem_data(req, &p);

            tmp_bio = BIO_new(BIO_s_mem());
            BIO_write(tmp_bio, p, (int)len);
            req = BIO_push(tmp_bio, req);
            content_type = "application/octet-stream"; /* Authenticode Timestamp */
            expected_content_type = "application/octet-stream";
        }
        s_bio = OSSL_HTTP_transfer(NULL, server, port, path, use_ssl, proxy, NULL,
            NULL, NULL, http_tls_cb, &info, 0, NULL, content_type, req,
            expected_content_type, 0, 0, timeout, 0);
        BIO_free(tmp_bio);
    }
    OPENSSL_free(server);
    OPENSSL_free(port);
    OPENSSL_free(path);
    SSL_CTX_free(ssl_ctx);

    if (s_bio) {
        resp = socket_bio_read(s_bio);
        BIO_free_all(s_bio);
        if (resp && req && !rfc3161)
            check_authenticode_timestamp(&resp);
    } else {
        fprintf(stderr, "\nHTTP failure: Failed to get data from %s\n", url);
    }

    return resp;
}
#endif /* OPENSSL_VERSION_NUMBER<0x30000000L */

/*
 * Decode an HTTP response from BIO and write it into the PKCS7 structure
 * Add timestamp to the PKCS7 SignerInfo structure:
 * sig->d.sign->signer_info->unauth_attr
 * [in, out] p7: new PKCS#7 signature
 * [in] ctx: structure holds input and output data
 * [in] url: URL of the Time-Stamp Authority server
 * [in] rfc3161: Authenticode / RFC3161 Timestamp switch
 * [returns] 1 on error or 0 on success
 */
static int add_timestamp(PKCS7 *p7, FILE_FORMAT_CTX *ctx, char *url, int rfc3161)
{
    BIO *req, *resp;
    int verbose = ctx->options->verbose || ctx->options->ntsurl == 1;
    int res = 1;
#if OPENSSL_VERSION_NUMBER<0x30000000L
#ifdef ENABLE_CURL
    long http_code = -1;
#endif /* ENABLE_CURL */
#endif /* OPENSSL_VERSION_NUMBER<0x30000000L */

    /* Encode timestamp request */
    if (rfc3161) {
        req = bio_encode_rfc3161_request(p7, ctx->options->md);
    } else {
        req = bio_encode_authenticode_request(p7);
    }
    if (!req) {
        return 1; /* FAILED */
    }
#if OPENSSL_VERSION_NUMBER<0x30000000L
#ifndef ENABLE_CURL
    (void)url;
    (void)rfc3161;
    fprintf(stderr, "Could NOT find CURL\n");
    BIO_free_all(req);
    return 1; /* FAILED */
#else /* ENABLE_CURL */
    if (rfc3161) {
        resp = bio_get_http_curl(&http_code, url, req, ctx->options->proxy,
            ctx->options->noverifypeer, verbose, 1);
    } else {
        resp = bio_get_http_curl(&http_code, url, req, ctx->options->proxy,
            ctx->options->noverifypeer, verbose, 0);
    }
#endif /* ENABLE_CURL */
#else /* OPENSSL_VERSION_NUMBER<0x30000000L */
    if (rfc3161) {
        resp = bio_get_http(url, req, ctx->options->proxy, 1,
            ctx->options->noverifypeer ? NULL : ctx->options->https_cafile,
            ctx->options->noverifypeer ? NULL : ctx->options->https_crlfile);
    } else {
        resp = bio_get_http(url, req, ctx->options->proxy, 0,
            ctx->options->noverifypeer ? NULL : ctx->options->https_cafile,
            ctx->options->noverifypeer ? NULL : ctx->options->https_crlfile);
    }
#endif /* OPENSSL_VERSION_NUMBER<0x30000000L */
    BIO_free_all(req);
    if (resp != NULL) {
        if (rfc3161) {
            /* decode a RFC 3161 response from BIO */
            TS_RESP *response = d2i_TS_RESP_bio(resp, NULL);

            res = attach_rfc3161_response(p7, response, verbose);
            TS_RESP_free(response);
        } else {
            /* decode an authenticode response from BIO */
            PKCS7 *response = d2i_PKCS7_bio(resp, NULL);

            res = attach_authenticode_response(p7, response, verbose);
        }
        if (res && verbose) {
#if OPENSSL_VERSION_NUMBER<0x30000000L
#ifdef ENABLE_CURL
            if (http_code != -1)
                fprintf(stderr, "Failed to convert timestamp reply from %s; "
                "HTTP status %ld\n", url, http_code);
            else
#endif /* ENABLE_CURL */
#endif /* OPENSSL_VERSION_NUMBER<0x30000000L */
                fprintf(stderr, "Failed to convert timestamp reply from %s\n", url);
            ERR_print_errors_fp(stderr);
        }
        BIO_free_all(resp);
    }
    return res;
}

/*
 * [in, out] p7: new PKCS#7 signature
 * [in] ctx: structure holds input and output data
 * [returns] 0 on error or 1 on success
 */
static int add_timestamp_authenticode(PKCS7 *p7, FILE_FORMAT_CTX *ctx)
{
    int i;
    for (i=0; i<ctx->options->nturl; i++) {
        if (!add_timestamp(p7, ctx, ctx->options->turl[i], 0))
            return 1; /* OK */
    }
    return 0; /* FAILED */
}

/*
 * [in, out] p7: new PKCS#7 signature
 * [in] ctx: structure holds input and output data
 * [returns] 0 on error or 1 on success
 */
static int add_timestamp_rfc3161(PKCS7 *p7, FILE_FORMAT_CTX *ctx)
{
    int i;
    for (i=0; i<ctx->options->ntsurl; i++) {
        if (!add_timestamp(p7, ctx, ctx->options->tsurl[i], 1))
            return 1; /* OK */
    }
    return 0; /* FAILED */
}

/*
 * [in] resp_ctx: a response context that can be used for generating responses
 * [in] data: unused
 * [returns] hexadecimal serial number
 */
static ASN1_INTEGER *serial_cb(TS_RESP_CTX *resp_ctx, void *data)
{
    int ret = 0;
    uint64_t buf;
    ASN1_INTEGER *serial = NULL;

    /* squash unused parameter warning */
    (void)data;

    if (RAND_bytes((unsigned char *)&buf, sizeof buf) <= 0) {
        fprintf(stderr, "RAND_bytes failed\n");
        goto out;
    }
    serial = ASN1_INTEGER_new();
    if (!serial)
        goto out;
    ASN1_INTEGER_set_uint64(serial, buf);
    ret = 1;
out:
     if (!ret) {
        TS_RESP_CTX_set_status_info(resp_ctx, TS_STATUS_REJECTION,
            "Error during serial number generation.");
        TS_RESP_CTX_add_failure_info(resp_ctx, TS_INFO_ADD_INFO_NOT_AVAILABLE);
        ASN1_INTEGER_free(serial);
        return NULL; /* FAILED */
    }
    return serial;
}

/*
 * This must return the seconds and microseconds since Jan 1, 1970 in the sec
 * and usec variables allocated by the caller.
 * [in] resp_ctx: a response context that can be used for generating responses
 * [in] data: timestamping time
 * [out] sec: total of seconds since Jan 1, 1970
 * [out] usec: microseconds (unused)
 * [returns] 0 on error or 1 on success
 */
static int time_cb(TS_RESP_CTX *resp_ctx, void *data, long *sec, long *usec)
{
    time_t *time = (time_t *)data;
    if(!*time) {
        TS_RESP_CTX_set_status_info(resp_ctx, TS_STATUS_REJECTION,
            "Time is not available.");
        TS_RESP_CTX_add_failure_info(resp_ctx, TS_INFO_TIME_NOT_AVAILABLE);
        return 0; /* FAILED */
    }
    *sec = (long int)*time;
    *usec = 0;
    return 1; /* OK */
}

/*
 * [in] ctx: structure holds input and output data
 * [in] signer_cert: the signer certificate of the TSA in PEM format
 * [in] signer_key: the private key of the TSA in PEM format
 * [in] chain: the certificate chain that will all be included in the response
 * [in] bout: timestamp request
 * [returns] RFC3161 response
 */
static TS_RESP *get_rfc3161_response(FILE_FORMAT_CTX *ctx, X509 *signer_cert,
    EVP_PKEY *signer_key, STACK_OF(X509) *chain, BIO *bout)
{
    TS_RESP_CTX *resp_ctx = NULL;
    TS_RESP *response = NULL;
    ASN1_OBJECT *policy_obj = NULL;

    resp_ctx = TS_RESP_CTX_new();
    if (!resp_ctx)
        goto out;

    TS_RESP_CTX_set_serial_cb(resp_ctx, serial_cb, NULL);
    if (!TS_RESP_CTX_set_signer_cert(resp_ctx, signer_cert)) {
        goto out;
    }
    if (!TS_RESP_CTX_set_signer_key(resp_ctx, signer_key)) {
        goto out;
    }
    if (!TS_RESP_CTX_set_certs(resp_ctx, chain)) {
        goto out;
    }
    /* message digest algorithm that the TSA accepts */
    if (!TS_RESP_CTX_add_md(resp_ctx, ctx->options->md)) {
        goto out;
    }
    /* signing digest to use */
    if (!TS_RESP_CTX_set_signer_digest(resp_ctx, ctx->options->md)) {
        goto out;
    }
    /* default policy to use when the request does not mandate any policy
     * tsa_policy1 = 1.2.3.4.1 */
    policy_obj = OBJ_txt2obj(TSA_POLICY1, 0);
    if (!policy_obj) {
        goto out;
    }
    if (!TS_RESP_CTX_set_def_policy(resp_ctx, policy_obj)) {
        goto out;
    }
    /* the accuracy of the time source of the TSA in seconds, milliseconds
     * and microseconds; e.g. secs:1, millisecs:500, microsecs:100;
     * 0 means not specified */
    if (!TS_RESP_CTX_set_accuracy(resp_ctx, 1, 500, 100)) {
        goto out;
    }
    if (ctx->options->tsa_time) {
        TS_RESP_CTX_set_time_cb(resp_ctx, time_cb, &(ctx->options->tsa_time));
    }
    /* generate RFC3161 response with embedded TS_TST_INFO structure */
    response = TS_RESP_create_response(resp_ctx, bout);
    if (!response) {
        fprintf(stderr, "Failed to create RFC3161 response\n");
    }

out:
    ASN1_OBJECT_free(policy_obj);
    TS_RESP_CTX_free(resp_ctx);

    return response;
}

/*
 * [in] bin: certfile BIO
 * [in] certpass: NULL
 * [returns] pointer to STACK_OF(X509) structure
 */
static STACK_OF(X509) *X509_chain_read_certs(BIO *bin, char *certpass)
{
    STACK_OF(X509) *certs = sk_X509_new_null();
    X509 *x509;
    (void)BIO_seek(bin, 0);
    x509 = PEM_read_bio_X509(bin, NULL, NULL, certpass);
    while (x509) {
        sk_X509_push(certs, x509);
        x509 = PEM_read_bio_X509(bin, NULL, NULL, certpass);
    }
    ERR_clear_error();
    if (!sk_X509_num(certs)) {
        sk_X509_free(certs);
        return NULL;
    }
    return certs;
}

/*
 * [in, out] p7: new PKCS#7 signature
 * [in] ctx: structure holds input and output data
 * [returns] 1 on error or 0 on success
 */
static int add_timestamp_builtin(PKCS7 *p7, FILE_FORMAT_CTX *ctx)
{
    BIO *btmp, *bout;
    STACK_OF(X509) *chain;
    X509 *signer_cert = NULL;
    EVP_PKEY *signer_key;
    TS_RESP *response = NULL;
    int i, res = 1;

    btmp = bio_new_file(ctx->options->tsa_certfile, "rb");
    if (!btmp) {
        fprintf(stderr, "Failed to read Time-Stamp Authority certificate file: %s\n", ctx->options->tsa_certfile);
        return 0; /* FAILED */
    }
    /* .pem certificate file */
    chain = X509_chain_read_certs(btmp, NULL);
    BIO_free(btmp);
    btmp = bio_new_file(ctx->options->tsa_keyfile, "rb");
    if (!btmp) {
        fprintf(stderr, "Failed to read private key file: %s\n", ctx->options->tsa_keyfile);
        return 0; /* FAILED */
    }
    signer_key = PEM_read_bio_PrivateKey(btmp, NULL, NULL, NULL);
    BIO_free(btmp);
    if(!chain || !signer_key) {
        fprintf(stderr, "Failed to load Time-Stamp Authority crypto parameters\n");
        return 0; /* FAILED */
    }
    /* find the signer's certificate located somewhere in the whole certificate chain */
    for (i=0; i<sk_X509_num(chain); i++) {
        X509 *cert = sk_X509_value(chain, i);
        if (X509_check_private_key(cert, signer_key)) {
            signer_cert = cert;
            break;
        }
    }
    if(!signer_cert) {
        fprintf(stderr, "Failed to checking the consistency of a TSA private key with a public key in any X509 certificate\n");
        goto out;
    }

    /* The TSA signing certificate must have exactly one extended key usage
     * assigned to it: timeStamping. The extended key usage must also be critical,
     * otherwise the certificate is going to be refused. */

    /* check X509_PURPOSE_TIMESTAMP_SIGN certificate purpose */
    if (X509_check_purpose(signer_cert, X509_PURPOSE_TIMESTAMP_SIGN, 0) != 1) {
        fprintf(stderr, "Unsupported TSA signer's certificate purpose X509_PURPOSE_TIMESTAMP_SIGN\n");
        goto out;
    }
    /* check extended key usage flag XKU_TIMESTAMP */
    if (!(X509_get_extended_key_usage(signer_cert) & XKU_TIMESTAMP)) {
        fprintf(stderr, "Unsupported Signer's certificate purpose XKU_TIMESTAMP\n");
        goto out;
    }
    /* encode timestamp request */
    bout = bio_encode_rfc3161_request(p7, ctx->options->md);
    if (!bout) {
        fprintf(stderr, "Failed to encode timestamp request\n");
        goto out;
    }

    response = get_rfc3161_response(ctx, signer_cert, signer_key, chain, bout);
    BIO_free_all(bout);

    if (response) {
        res = attach_rfc3161_response(p7, response, ctx->options->verbose);
        if (res) {
            fprintf(stderr, "Failed to convert timestamp reply\n");
            ERR_print_errors_fp(stderr);
        }
    } else {
        fprintf(stderr, "Failed to obtain RFC3161 response\n");
    }
out:
    sk_X509_pop_free(chain, X509_free);
    EVP_PKEY_free(signer_key);
    TS_RESP_free(response);
    return res;
}

/*
 * Add an unauthenticated blob as an unauthenticated attribute - cont[1]
 * to the PKCS7 SignerInfo structure: p7->d.sign->signer_info->unauth_attr
 * [in, out] p7: new PKCS#7 signature
 * [in] blob_file: optional, path to the file containing the blob
 * [returns] 0 on error or 1 on success
 */
static int add_unauthenticated_blob(PKCS7 *p7, const char *blob_file)
{
    u_char *p = NULL, *buffer = NULL;
    int blob_len, len;
    ASN1_UTF8STRING *value;
    PKCS7_SIGNER_INFO *si;
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info = PKCS7_get_signer_info(p7);

    if (!signer_info) {
        fprintf(stderr, "Failed to obtain PKCS#7 signer info list\n");
        return 0; /* FAILED */
    }
    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si) {
        fprintf(stderr, "Failed to obtain signer info from PKCS#7 structure\n");
        return 0; /* FAILED */
    }
    if (blob_file == NULL) {
        /* Generate a placeholder blob */
        const char prefix[] = "---BEGIN_BLOB---";
        const char postfix[] = "---END_BLOB---";
        size_t prefix_len = strlen(prefix);
        size_t postfix_len = strlen(postfix);

        blob_len = (int)(prefix_len + 1024 + postfix_len);
        buffer = OPENSSL_zalloc((size_t)blob_len);
        if (!buffer) {
            fprintf(stderr, "Memory allocation failed\n");
            return 0; /* FAILED */
        }
        memcpy(buffer, prefix, sizeof prefix);
        memcpy(buffer + prefix_len + 1024, postfix, postfix_len);
    } else {
        /* Copy blob content from the specified file */
        int64_t file_size;
        size_t size;
        FILE *file = fopen(blob_file, "rb");

        if (!file) {
            fprintf(stderr, "Failed to open blob file: %s\n", blob_file);
            return 0; /* FAILED */
        }
        if (fseeko(file, 0, SEEK_END) < 0
                || (file_size = ftello(file)) < 0
                || fseeko(file, 0, SEEK_SET) < 0) {
            fprintf(stderr, "Failed to seek in blob file: %s\n", blob_file);
            fclose(file);
            return 0; /* FAILED */
        }
        /* New unauthenticated attribute creation: X509_ATTRIBUTE_create_by_NID()
         * or X509_ATTRIBUTE_set1_data() function accepts data of up to INT_MAX
         * length, as the 'len' parameter is an 'int' */
        if (file_size > INT_MAX) {
            fprintf(stderr, "Blob file is too large: %s\n", blob_file);
            fclose(file);
            return 0; /* FAILED */
        }
        blob_len = (int)file_size;
        if (blob_len < 1024) {
            /* Ensure minimum size for the blob */
            blob_len = 1024;
        }
        buffer = OPENSSL_zalloc((size_t)blob_len);
        if (!buffer) {
            fprintf(stderr, "Memory allocation failed\n");
            fclose(file);
            return 0; /* FAILED */
        }
        size = fread(buffer, 1, (size_t)file_size, file);
        fclose(file);
        if (size != (size_t)file_size) {
            OPENSSL_free(buffer);
            return 0; /* FAILED */
        }
    }
    value = ASN1_UTF8STRING_new();
    if (!value) {
        fprintf(stderr, "Failed to create ASN1_UTF8STRING object\n");
        OPENSSL_free(buffer);
        return 0;
    }
    if (!ASN1_STRING_set(value, buffer, blob_len)) {
        fprintf(stderr, "Failed to set ASN1_UTF8STRING value\n");
        ASN1_UTF8STRING_free(value);
        OPENSSL_free(buffer);
        return 0;
    }
    OPENSSL_free(buffer);
    len = i2d_ASN1_UTF8STRING(value, NULL);
    p = OPENSSL_malloc((size_t)len);
    if (!p) {
        fprintf(stderr, "Memory allocation failed\n");
        ASN1_UTF8STRING_free(value);
        return 0; /* FAILED */
    }
    len = i2d_ASN1_UTF8STRING(value, &p);
    p -= len;
    if (!X509_attribute_chain_append_object(&(si->unauth_attr), p, len, SPC_UNAUTHENTICATED_DATA_BLOB_OBJID)) {
        fprintf(stderr, "Failed to append unauthenticated blob to signer info\n");
        OPENSSL_free(p);
        ASN1_UTF8STRING_free(value);
        return 0; /* FAILED */
    }
    OPENSSL_free(p);
    ASN1_UTF8STRING_free(value);
    return 1; /* OK */
}

/*
 * Add unauthenticated attributes (Countersignature, Unauthenticated Data Blob)
 * [in, out] p7: new PKCS#7 signature
 * [in, out] ctx: structure holds input and output data
 * [returns] 1 on error or 0 on success
 */
static int add_timestamp_and_blob(PKCS7 *p7, FILE_FORMAT_CTX *ctx)
{
    /* add counter-signature/timestamp */
    if (ctx->options->nturl && !add_timestamp_authenticode(p7, ctx)) {
        fprintf(stderr, "%s\n%s\n", "Authenticode timestamping failed",
            "Use the \"-ts\" option to add the RFC3161 Time-Stamp Authority or choose another one Authenticode Time-Stamp Authority");
        return 1; /* FAILED */
    }
    if (ctx->options->ntsurl && !add_timestamp_rfc3161(p7, ctx)) {
        fprintf(stderr, "%s\n%s\n", "RFC 3161 timestamping failed",
            "Use the \"-t\" option to add the Authenticode Time-Stamp Authority or choose another one RFC3161 Time-Stamp Authority");
        return 1; /* FAILED */
    }
    if (ctx->options->tsa_certfile && ctx->options->tsa_keyfile && add_timestamp_builtin(p7, ctx)) {
        fprintf(stderr, "Built-in timestamping failed\n");
        return 1; /* FAILED */
    }
    if (ctx->options->addBlob && !add_unauthenticated_blob(p7, ctx->options->blob_file)) {
        fprintf(stderr, "Adding unauthenticated blob failed\n");
        return 1; /* FAILED */
    }
    return 0; /* OK */
}

/*
 * Add unauthenticated attributes to the signature at a certain position
 * [in, out] p7: new PKCS#7 signature
 * [in, out] ctx: structure holds input and output data
 * [in] index: signature index
 * [returns] 1 on error or 0 on success
 */
static int add_nested_timestamp_and_blob(PKCS7 *p7, FILE_FORMAT_CTX *ctx, int index)
{
    STACK_OF(PKCS7) *signatures;
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info;
    STACK_OF(X509_ATTRIBUTE) *unauth_attr;
    PKCS7_SIGNER_INFO *si;
    PKCS7 *p7_tmp;
    int i;

    p7_tmp = PKCS7_dup(p7);
    if (!p7_tmp) {
        return 1; /* FAILED */
    }
    signer_info = PKCS7_get_signer_info(p7);
    if (!signer_info) {
        fprintf(stderr, "Failed to obtain PKCS#7 signer info list\n");
        return 1; /* FAILED */
    }
    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si) {
        fprintf(stderr, "Failed to obtain PKCS#7 signer info value\n");
        return 1; /* FAILED */
    }
    unauth_attr = PKCS7_get_attributes(si); /* cont[1] */
    if (unauth_attr) {
        /* try to find and remove SPC_NESTED_SIGNATURE_OBJID attribute */
        for (i=0; i<X509at_get_attr_count(unauth_attr); i++) {
            int nid = OBJ_txt2nid(SPC_NESTED_SIGNATURE_OBJID);
            X509_ATTRIBUTE *attr = X509at_get_attr(unauth_attr, i);
            if (OBJ_obj2nid(X509_ATTRIBUTE_get0_object(attr)) == nid) {
                X509at_delete_attr(unauth_attr, i);
                X509_ATTRIBUTE_free(attr);
                break;
            }
        }
    }
    signatures = signature_list_create(p7_tmp);
    if (!signatures) {
        fprintf(stderr, "Failed to create signature list\n\n");
        return 1; /* FAILED */
    }
    /* append all nested signature to the primary signature */
    for (i=1; i<sk_PKCS7_num(signatures); i++) {
        PKCS7 *sig = sk_PKCS7_value(signatures, i);
        if (i == index) {
            printf("Use the signature at index %d\n", i);
            if (add_timestamp_and_blob(sig, ctx)) {
                fprintf(stderr, "Unable to set unauthenticated attributes\n");
                sk_PKCS7_pop_free(signatures, PKCS7_free);
                return 1; /* FAILED */
            }
        }
        if (!cursig_set_nested(p7, sig)) {
            fprintf(stderr, "Unable to append the nested signature to the current signature\n");
            sk_PKCS7_pop_free(signatures, PKCS7_free);
            return 1; /* FAILED */
        }
    }
    sk_PKCS7_pop_free(signatures, PKCS7_free);
    return 0; /* OK */
}

/*
 * Add the new signature to the current signature as a nested signature:
 * new unauthenticated SPC_NESTED_SIGNATURE_OBJID attribute
 * [out] cursig: current PKCS#7 signature
 * [in] p7: new PKCS#7 signature
 * [in] ctx: structure holds input and output data
 * [returns] 0 on error or 1 on success
 */
static int cursig_set_nested(PKCS7 *cursig, PKCS7 *p7)
{
    u_char *p = NULL;
    int len = 0;
    PKCS7_SIGNER_INFO *si;
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info;

    if (!cursig)
        return 0; /* FAILED */
    signer_info = PKCS7_get_signer_info(cursig);
    if (!signer_info)
        return 0; /* FAILED */
    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si)
        return 0; /* FAILED */
    if (((len = i2d_PKCS7(p7, NULL)) <= 0) ||
        (p = OPENSSL_malloc((size_t)len)) == NULL)
        return 0; /* FAILED */
    i2d_PKCS7(p7, &p);
    p -= len;
    if (!X509_attribute_chain_append_object(&(si->unauth_attr), p, len, SPC_NESTED_SIGNATURE_OBJID)) {
        OPENSSL_free(p);
        return 0; /* FAILED */
    }
    OPENSSL_free(p);
    return 1; /* OK */
}

/*
 * Return the number of objects in SPC_NESTED_SIGNATURE_OBJID attribute
 * [in] p7: existing PKCS#7 signature (Primary Signature)
 * [returns] -1 on error or the number of nested signatures
 */
static int nested_signatures_number_get(PKCS7 *p7)
{
    int i;
    STACK_OF(X509_ATTRIBUTE) *unauth_attr;
    PKCS7_SIGNER_INFO *si;
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info = PKCS7_get_signer_info(p7);

    if (!signer_info)
        return -1; /* FAILED */
    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si)
        return -1; /* FAILED */
    unauth_attr = PKCS7_get_attributes(si); /* cont[1] */
    if (!unauth_attr)
        return 0; /* OK, no unauthenticated attributes */
    for (i=0; i<X509at_get_attr_count(unauth_attr); i++) {
        int nid = OBJ_txt2nid(SPC_NESTED_SIGNATURE_OBJID);
        X509_ATTRIBUTE *attr = X509at_get_attr(unauth_attr, i);
        if (OBJ_obj2nid(X509_ATTRIBUTE_get0_object(attr)) == nid) {
            /* Nested Signature - Policy OID: 1.3.6.1.4.1.311.2.4.1 */
            return X509_ATTRIBUTE_count(attr);
        }
    }
    return 0; /* OK, no SPC_NESTED_SIGNATURE_OBJID attribute */
}

/*
 * [in, out] unauth_attr: unauthenticated attributes list
 * [in] p: PKCS#7 data
 * [in] len: PKCS#7 data length
 * [in] oid: unauthenticated attribute oid: SPC_UNAUTHENTICATED_DATA_BLOB_OBJID,
        PKCS9_COUNTER_SIGNATURE, SPC_RFC3161_OBJID or SPC_NESTED_SIGNATURE_OBJID
 * [returns] 0 on error or 1 on success
 */
static int X509_attribute_chain_append_object(STACK_OF(X509_ATTRIBUTE) **unauth_attr,
    u_char *p, int len, const char *oid)
{
    X509_ATTRIBUTE *attr = NULL;
    ASN1_OBJECT *object;
    char object_txt[128];

    if (*unauth_attr == NULL) {
        if ((*unauth_attr = sk_X509_ATTRIBUTE_new_null()) == NULL)
            return 0; /* FAILED */
    } else {
        /* try to find indicated unauthenticated attribute */
        int i;
        for (i = 0; i < X509at_get_attr_count(*unauth_attr); i++) {
            attr = X509at_get_attr(*unauth_attr, i);
            object = X509_ATTRIBUTE_get0_object(attr);
            if (object == NULL)
                continue;
            object_txt[0] = 0x00;
            OBJ_obj2txt(object_txt, sizeof object_txt, object, 1);
            if ((!strcmp(oid, PKCS9_COUNTER_SIGNATURE) || !strcmp(oid, SPC_RFC3161_OBJID))
                && (!strcmp(object_txt, PKCS9_COUNTER_SIGNATURE) || !strcmp(object_txt, SPC_RFC3161_OBJID))) {
                /* free up countersignature/timestamp in unauthenticated attributes
                 * to override the previous timestamp */
                X509at_delete_attr(*unauth_attr, i);
                X509_ATTRIBUTE_free(attr);
                continue;
            }
            if (!strcmp(oid, object_txt)) {
                /* append p to the V_ASN1_SEQUENCE */
                if (!X509_ATTRIBUTE_set1_data(attr, V_ASN1_SEQUENCE, p, len))
                    return 0; /* FAILED */
                return 1; /* OK */
            }
        }
    }
    /* create new unauthenticated attribute */
    attr = X509_ATTRIBUTE_create_by_NID(NULL, OBJ_txt2nid(oid), V_ASN1_SEQUENCE, p, len);
    if (!attr)
        return 0; /* FAILED */
    if (!sk_X509_ATTRIBUTE_push(*unauth_attr, attr)) {
        X509_ATTRIBUTE_free(attr);
        return 0; /* FAILED */
    }
    return 1; /* OK */
}


/*
 * [in, out] store: structure for holding information about X.509 certificates and CRLs
 * [in] time: time_t to set
 * [returns] 0 on error or 1 on success
 */
static int x509_store_set_time(X509_STORE *store, time_t time)
{
    X509_VERIFY_PARAM *param;

    param = X509_STORE_get0_param(store);
    if (param == NULL)
        return 0; /* FAILED */
    X509_VERIFY_PARAM_set_time(param, time);
    if (!X509_STORE_set1_param(store, param))
        return 0; /* FAILED */
    return 1; /* OK */
}

/*
 * Check the syntax of the time structure and print the time in human readable format
 * [in] time: time structure
 * [returns] 0 on error or 1 on success
 */
static int print_asn1_time(const ASN1_TIME *time)
{
    BIO *bp;

    if ((time == NULL) || (!ASN1_TIME_check(time))) {
        printf("N/A\n");
        return 0; /* FAILED */
    }
    bp = BIO_new_fp(stdout, BIO_NOCLOSE);
    ASN1_TIME_print(bp, time);
    BIO_free(bp);
    printf("\n");
    return 1; /* OK */
}

/*
 * Set the structure s to the time represented by the time_t value
 * to print this time in human readable format
 * [in] time: time_t value
 * [returns] 0 on error or 1 on success
 */
static int print_time_t(const time_t time)
{
    ASN1_TIME *s;
    int ret;

    if (time == INVALID_TIME) {
        printf("N/A\n");
        return 0; /* FAILED */
    }
    if ((s = ASN1_TIME_set(NULL, time)) == NULL) {
        printf("N/A\n");
        return 0; /* FAILED */
    }
    ret = print_asn1_time(s);
    ASN1_TIME_free(s);
    return ret;

}

/*
 * Convert an X509_NAME to a UTF-8 string
 * [in] name: X509 name to convert
 * [returns] allocated string; "N/A" on error
 */
static char *x509_name_to_utf8(const X509_NAME *name)
{
    BIO *bio;
    BUF_MEM *bptr;
    char *str;
    unsigned long flags;

    if (!name)
        return OPENSSL_strdup("N/A");

    flags = XN_FLAG_RFC2253 | ASN1_STRFLGS_UTF8_CONVERT |
            ASN1_STRFLGS_ESC_CTRL;
    flags &= ~(unsigned long)ASN1_STRFLGS_ESC_MSB;

    bio = BIO_new(BIO_s_mem());
    if (!bio)
        return OPENSSL_strdup("N/A");
    if (X509_NAME_print_ex(bio, name, 0, flags) < 0) {
        BIO_free(bio);
        return OPENSSL_strdup("N/A");
    }
    BIO_get_mem_ptr(bio, &bptr);
    str = OPENSSL_strndup(bptr->data, bptr->length);
    BIO_free(bio);
    if (!str)
        return OPENSSL_strdup("N/A");
    return str;
}

/*
 * Print certificate subject name, issuer name, serial number and expiration date
 * [in] cert: X509 certificate
 * [in] i: certificate number in order
 * [returns] none
 */
static void print_cert(X509 *cert, int i)
{
    char *subject, *issuer, *serial;
    BIGNUM *serialbn;

    if (!cert)
        return;
    subject = x509_name_to_utf8(X509_get_subject_name(cert));
    issuer = x509_name_to_utf8(X509_get_issuer_name(cert));
    serialbn = ASN1_INTEGER_to_BN(X509_get_serialNumber(cert), NULL);
    serial = BN_bn2hex(serialbn);
    printf("\t------------------\n");
    printf("\tSigner #%d:\n\t\tSubject: %s\n\t\tIssuer : %s\n\t\tSerial : %s\n\t\tCertificate expiration date:\n",
            i, subject, issuer, serial);
    printf("\t\t\tnotBefore : ");
    print_asn1_time(X509_get0_notBefore(cert));
    printf("\t\t\tnotAfter : ");
    print_asn1_time(X509_get0_notAfter(cert));
    printf("\n");

    OPENSSL_free(subject);
    OPENSSL_free(issuer);
    BN_free(serialbn);
    OPENSSL_free(serial);
}

/*
 * [in] certs: X509 certificate chain
 * [returns] none
 */
static void print_certs_chain(STACK_OF(X509) *certs)
{
    int i;

    for (i=0; i<sk_X509_num(certs); i++) {
        print_cert(sk_X509_value(certs, i), i);
    }
}

/*
 * [in] txt, list
 * [returns] 0 on error or 1 on success
 */
static int on_list(const char *txt, const char *list[])
{
    while (*list)
        if (!strcmp(txt, *list++))
            return 1; /* OK */
    return 0; /* FAILED */
}

/*
 * Check Windows certificate whitelist:
 * https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/trusted-root-certificates-are-required
 * For Microsoft Root Authority, serial number: 00C1008B3C3C8811D13EF663ECDF40,
 * fingerprint: "F3:84:06:E5:40:D7:A9:D9:0C:B4:A9:47:92:99:64:0F:FB:6D:F9:E2:24:EC:C7:A0:1C:0D:95:58:D8:DA:D7:7D"
 * expiration date: 12/31/2020, intended purposes: All,
 * ignore X509_V_ERR_INVALID_CA and X509_V_ERR_CERT_HAS_EXPIRED
 * [in] cert: X509 certificate
 * [in] error: error code
 * [returns] 0 on error or 1 on success
 */
static int trusted_cert(X509 *cert, int error) {
    const char *fingerprints[] = {
        "F3:84:06:E5:40:D7:A9:D9:0C:B4:A9:47:92:99:64:0F:FB:6D:F9:E2:24:EC:C7:A0:1C:0D:95:58:D8:DA:D7:7D",
        NULL
    };
    u_char mdbuf[EVP_MAX_MD_SIZE], *p;
    char *hex = NULL;
    int len;
    const EVP_MD *md = EVP_get_digestbynid(NID_sha256);
    BIO *bhash = BIO_new(BIO_f_md());

#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
    if (!BIO_set_md(bhash, md)) {
        BIO_free_all(bhash);
        return 0; /* FAILED */
    }
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
    BIO_push(bhash, BIO_new(BIO_s_null()));
    len = i2d_X509(cert, NULL);
    p = OPENSSL_malloc((size_t)len);
    i2d_X509(cert, &p);
    p -= len;
    BIO_write(bhash, p, len);
    OPENSSL_free(p);
    BIO_gets(bhash, (char *)mdbuf, EVP_MD_size(md));
    BIO_free_all(bhash);

    hex = OPENSSL_buf2hexstr(mdbuf, (long)EVP_MD_size(md));
    if (!hex) {
        return 0; /* FAILED */
    }
    if (on_list(hex, fingerprints)) {
        printf("\tWarning: Ignoring \'%s\' error for Windows certificate whitelist\n",
            X509_verify_cert_error_string(error));
        OPENSSL_free(hex);
        return 1; /* trusted */
    }
    OPENSSL_free(hex);
    return 0; /* untrusted */
}

/*
 * X509_STORE_CTX_verify_cb
 */
static int verify_ca_callback(int ok, X509_STORE_CTX *ctx)
{
    int error = X509_STORE_CTX_get_error(ctx);
    int depth = X509_STORE_CTX_get_error_depth(ctx);

    X509 *current_cert = X509_STORE_CTX_get_current_cert(ctx);
    print_cert(current_cert, depth);
    if (!ok) {
        if (trusted_cert(current_cert, error)) {
            return 1;
        } else if (error == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) {
            printf("\tError: Certificate not found in local repository: %s\n",
                X509_verify_cert_error_string(error));
        } else {
            printf("\tError: %s\n\n", X509_verify_cert_error_string(error));
        }
    }
    return ok;
}

static int verify_crl_callback(int ok, X509_STORE_CTX *ctx)
{
    X509_CRL *crl;
    int error = X509_STORE_CTX_get_error(ctx);
    int depth = X509_STORE_CTX_get_error_depth(ctx);
    X509 *current_cert = X509_STORE_CTX_get_current_cert(ctx);

    print_cert(current_cert, depth);

    crl = X509_STORE_CTX_get0_current_crl(ctx);
    if (crl) {
        BIO *bio = BIO_new_fp(stdout, BIO_NOCLOSE);

        X509_CRL_print(bio, crl);
        BIO_free(bio);
        printf("\n");
    }

    if (!ok) {
        if (trusted_cert(current_cert, error)) {
            return 1;
        } else if (error == X509_V_ERR_CERT_HAS_EXPIRED) {
            printf("\tWarning: Ignoring \'%s\' error for CRL validation\n",
                X509_verify_cert_error_string(error));
            return 1;
        } else if (error == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) {
            printf("\tError: Certificate not found in local repository: %s\n",
                X509_verify_cert_error_string(error));
        }
         else {
            printf("\tError: %s\n\n", X509_verify_cert_error_string(error));
        }
    }
    return ok;
}

/*
 * [in, out] store: structure for holding information about X.509 certificates and CRLs
 * [in] cafile: file contains concatenated CA certificates in PEM format
 * [returns] 0 on error or 1 on success
 */
static int x509_store_load_file(X509_STORE *store, char *cafile)
{
    X509_LOOKUP *lookup;
    X509_VERIFY_PARAM *param;

    lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
    if (!lookup || !cafile)
        return 0; /* FAILED */
    if (!X509_LOOKUP_load_file(lookup, cafile, X509_FILETYPE_PEM)) {
        fprintf(stderr, "\nError: no certificate found\n");
        return 0; /* FAILED */
    }
    param = X509_STORE_get0_param(store);
    if (param == NULL)
        return 0; /* FAILED */
    if (!X509_VERIFY_PARAM_set_purpose(param, X509_PURPOSE_ANY))
        return 0; /* FAILED */
    if (!X509_STORE_set1_param(store, param))
        return 0; /* FAILED */
    X509_STORE_set_verify_cb(store, verify_ca_callback);

    return 1; /* OK */
}

/*
 * [in, out] store: structure for holding information about X.509 certificates and CRLs
 * [in] cafile: file contains concatenated CA certificates in PEM format
 * [in] crlfile: file contains Certificate Revocation List (CRLs)
 * [returns] 0 on error or 1 on success
 */
static int x509_store_load_crlfile(X509_STORE *store, char *cafile, char *crlfile)
{
    X509_LOOKUP *lookup;
    X509_VERIFY_PARAM *param;

    lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
    if (!lookup)
        return 0; /* FAILED */
    if (!X509_LOOKUP_load_file(lookup, cafile, X509_FILETYPE_PEM)) {
        fprintf(stderr, "\nError: no certificate found\n");
        return 0; /* FAILED */
    }
    if (crlfile && !X509_load_crl_file(lookup, crlfile, X509_FILETYPE_PEM)) {
        fprintf(stderr, "\nError: no CRL found in %s\n", crlfile);
        return 0; /* FAILED */
    }
    param = X509_STORE_get0_param(store);
    if (param == NULL)
        return 0; /* FAILED */
    /* enable CRL checking for the certificate chain leaf certificate */
    if (!X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK))
        return 0; /* FAILED */
    if (!X509_STORE_set1_param(store, param))
        return 0; /* FAILED */
    X509_STORE_set_verify_cb(store, verify_crl_callback);

    return 1; /* OK */
}

/*
 * Initialise X509_STORE_CTX structure to discover and validate a certificate chain
 * based on given parameters
 * [in] cafile: file contains concatenated CA certificates in PEM format
 * [in] crlfile: file contains Certificate Revocation List (CRLs)
 * [in] crls: additional CRLs obtained from p7->d.sign->crl
 * [in] signer: signer's X509 certificate
 * [in] chain: list of additional certificates which will be untrusted but be used to build the chain
 * [returns] 0 on error or 1 on success
 */
static int verify_crl(char *cafile, char *crlfile, STACK_OF(X509_CRL) *crls,
    X509 *signer, STACK_OF(X509) *chain)
{
    X509_STORE *store = NULL;
    X509_STORE_CTX *ctx = NULL;
    int verok = 0;

    ctx = X509_STORE_CTX_new();
    if (!ctx)
        goto out;
    store = X509_STORE_new();
    if (!store)
        goto out;
    if (!x509_store_load_crlfile(store, cafile, crlfile))
        goto out;

    /* initialise an X509_STORE_CTX structure for subsequent use by X509_verify_cert()*/
    if (!X509_STORE_CTX_init(ctx, store, signer, chain))
        goto out;

    /* set an additional CRLs */
    if (crls)
        X509_STORE_CTX_set0_crls(ctx, crls);

    printf("\nCertificate Revocation List verified using:\n");
    if (X509_verify_cert(ctx) <= 0) {
        int error = X509_STORE_CTX_get_error(ctx);
        fprintf(stderr, "X509_verify_cert: certificate verify error: %s\n",
                X509_verify_cert_error_string(error));
        goto out;
    }
    verok = 1; /* OK */

out:
    if (!verok)
        ERR_print_errors_fp(stderr);
    /* NULL is a valid parameter value for X509_STORE_free() and X509_STORE_CTX_free() */
    X509_STORE_free(store);
    X509_STORE_CTX_free(ctx);
    return verok;
}

/*
 * [in] cert: X509 certificate
 * [returns] CRL distribution point url
 */
static char *clrdp_url_get_x509(X509 *cert)
{
    STACK_OF(DIST_POINT) *crldp;
    DIST_POINT *dp;
    GENERAL_NAMES *gens;
    GENERAL_NAME *gen;
    int i, j, gtype;
    ASN1_STRING *uri;
    char *url = NULL;

    crldp = X509_get_ext_d2i(cert, NID_crl_distribution_points, NULL, NULL);
    if (!crldp)
        return NULL;

    for (i = 0; i < sk_DIST_POINT_num(crldp); i++) {
        dp = sk_DIST_POINT_value(crldp, i);
        if (!dp->distpoint || dp->distpoint->type != 0)
            continue;
        gens = dp->distpoint->name.fullname;
        for (j = 0; j < sk_GENERAL_NAME_num(gens); j++) {
            gen = sk_GENERAL_NAME_value(gens, j);
            uri = GENERAL_NAME_get0_value(gen, &gtype);
            if (gtype == GEN_URI && ASN1_STRING_length(uri) > 6) {
                url = OPENSSL_strdup((const char *)ASN1_STRING_get0_data(uri));
                if (strncmp(url, "http://", 7) == 0)
                    goto out;
                OPENSSL_free(url);
                url = NULL;
            }
        }
    }
out:
    sk_DIST_POINT_pop_free(crldp, DIST_POINT_free);
    return url;
}

/*
 * Get Certificate Revocation List from a CRL distribution point
 * and write it into the X509_CRL structure.
 * [in] ctx: structure holds input and output data
 * [in] url: URL of the CRL distribution point server
 * [returns] X509 Certificate Revocation List
 */
static X509_CRL *x509_crl_get(FILE_FORMAT_CTX *ctx, char *url)
{
    X509_CRL *crl;
    BIO *bio = NULL;

#if OPENSSL_VERSION_NUMBER<0x30000000L
#ifndef ENABLE_CURL
    (void)ctx;
    fprintf(stderr, "Could NOT find CURL\n");
    return NULL; /* FAILED */
#else /* ENABLE_CURL */
    long http_code = -1;
    bio = bio_get_http_curl(&http_code, url, NULL, ctx->options->proxy, 0, 1, 0);
#endif /* ENABLE_CURL */
#else /* OPENSSL_VERSION_NUMBER<0x30000000L */
    bio = bio_get_http(url, NULL, ctx->options->proxy, 0,
        ctx->options->noverifypeer ? NULL : ctx->options->https_cafile,
        ctx->options->noverifypeer ? NULL : ctx->options->https_crlfile);
#endif /* OPENSSL_VERSION_NUMBER<0x30000000L */
    if (!bio) {
        fprintf(stderr, "Failed to get CRL from %s\n\n", url);
        return NULL; /* FAILED */
    }
    crl = d2i_X509_CRL_bio(bio, NULL);  /* DER format */
    if (!crl) {
        (void)BIO_seek(bio, 0);
        crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL); /* PEM format */
    }
    BIO_free_all(bio);
    if (!crl) {
         fprintf(stderr, "Failed to decode CRL from %s\n\n", url);
         return NULL; /* FAILED */
    }
    return crl; /* OK */
}

/*
 * Create CRLs from p7->d.sign->crl and x509_CRL (from CRL distribution point).
 * [in] p7: PKCS#7 signature
 * [in] crl: X509 Certificate Revocation List
 * [returns] X509 Certificate Revocation Lists (CRLs)
 */
static STACK_OF(X509_CRL) *x509_crl_list_get(PKCS7 *p7, X509_CRL *crl)
{
    int i;
    STACK_OF(X509_CRL) *crls = sk_X509_CRL_new_null();

    for (i = 0; i < sk_X509_CRL_num(p7->d.sign->crl); i++) {
        if (!sk_X509_CRL_push(crls, sk_X509_CRL_value(p7->d.sign->crl, i))) {
            sk_X509_CRL_pop_free(crls, X509_CRL_free);
            return NULL;
        }
    }
    if (crl && !sk_X509_CRL_push(crls, crl)) {
        sk_X509_CRL_pop_free(crls, X509_CRL_free);
        X509_CRL_free(crl);
        return NULL;
    }
    return crls;
}

static void print_timestamp_serial_number(TS_TST_INFO *token)
{
    BIGNUM *serialbn;
    char *number;

    if (!token)
        return;
    serialbn = ASN1_INTEGER_to_BN(TS_TST_INFO_get_serial(token), NULL);
    number = BN_bn2hex(serialbn);
    printf("Timestamp serial number: %s\n", number);
    BN_free(serialbn);
    OPENSSL_free(number);
}

/*
 * Compare the hash provided from the TSTInfo object against the hash computed
 * from the signature created by the signing certificate's private key
 * [in] p7: PKCS#7 signature
 * [in] timestamp: CMS_ContentInfo struct for Authenticode Timestamp or RFC 3161 Timestamp
 * [returns] 0 on error or 1 on success
 */
static int verify_timestamp_token(PKCS7 *p7, CMS_ContentInfo *timestamp)
{
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info;
    PKCS7_SIGNER_INFO *si;
    ASN1_OCTET_STRING **pos;

    signer_info = PKCS7_get_signer_info(p7);
    if (!signer_info)
        return 0; /* FAILED */
    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si)
        return 0; /* FAILED */

    /* get the embedded content */
    pos  = CMS_get0_content(timestamp);
    if (pos != NULL && *pos != NULL) {
        const u_char *p = (*pos)->data;
        TS_TST_INFO *token = d2i_TS_TST_INFO(NULL, &p, (*pos)->length);

        if (token) {
            BIO *bhash;
            u_char mdbuf[EVP_MAX_MD_SIZE];
            ASN1_OCTET_STRING *hash;
            const ASN1_OBJECT *aoid;
            int md_nid;
            const EVP_MD *md;
            TS_MSG_IMPRINT *msg_imprint = TS_TST_INFO_get_msg_imprint(token);
            const X509_ALGOR *alg = TS_MSG_IMPRINT_get_algo(msg_imprint);

            X509_ALGOR_get0(&aoid, NULL, NULL, alg);
            md_nid = OBJ_obj2nid(aoid);
            md = EVP_get_digestbynid(md_nid);

            /* compute a hash from the encrypted message digest value of the file */
            bhash = BIO_new(BIO_f_md());
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
            if (!BIO_set_md(bhash, md)) {
                fprintf(stderr, "Unable to set the message digest of BIO\n");
                BIO_free_all(bhash);
                TS_TST_INFO_free(token);
                return 0; /* FAILED */
            }
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
            BIO_push(bhash, BIO_new(BIO_s_null()));
            BIO_write(bhash, si->enc_digest->data, si->enc_digest->length);
            BIO_gets(bhash, (char*)mdbuf, EVP_MD_size(md));
            BIO_free_all(bhash);

            /* compare the provided hash against the computed hash */
            hash =TS_MSG_IMPRINT_get_msg(msg_imprint);
            if (memcmp(mdbuf, hash->data, (size_t)hash->length)) {
                printf("Hash value mismatch:\n\tMessage digest algorithm: %s\n",
                        (md_nid == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(md_nid));
                print_hash("\tComputed message digest", "", mdbuf, EVP_MD_size(md));
                print_hash("\tReceived message digest", "", hash->data, hash->length);
                printf("\nFile's message digest verification: failed\n");
                TS_TST_INFO_free(token);
                return 0; /* FAILED */
            } /* else Computed and received message digests matched */

            print_timestamp_serial_number(token);
            TS_TST_INFO_free(token);
        } else
            /* our CMS_ContentInfo struct created for Authenticode Timestamp
             * does not contain any TS_TST_INFO struct as specified in RFC 3161 */
            ERR_clear_error();
    }

    return 1; /* OK */
}

/*
 * [in] ctx: structure holds input and output data
 * [in] p7: PKCS#7 signature
 * [in] timestamp: CMS_ContentInfo struct for Authenticode Timestamp or RFC 3161 Timestamp
 * [in] time: timestamp verification time
 * [returns] 0 on error or 1 on success
 */
static int verify_timestamp(FILE_FORMAT_CTX *ctx, PKCS7 *p7, CMS_ContentInfo *timestamp, time_t time)
{
    STACK_OF(CMS_SignerInfo) *sinfos;
    CMS_SignerInfo *cmssi;
    X509 *signer;
    X509_CRL *crl = NULL;
    STACK_OF(X509_CRL) *crls = NULL;
    char *url = NULL;
    int verok = 0;
    X509_STORE *store = X509_STORE_new();

    if (!store)
        goto out;
    if (x509_store_load_file(store, ctx->options->tsa_cafile)) {
        /*
         * The TSA signing key MUST be of a sufficient length to allow for a sufficiently
         * long lifetime.  Even if this is done, the key will  have a finite lifetime.
         * Thus, any token signed by the TSA SHOULD  be time-stamped again or notarized
         * at a later date to renew the trust that exists in the TSA's signature.
         * https://datatracker.ietf.org/doc/html/rfc3161#section-4
         * Signtool does not respect this RFC and neither we do.
         * So verify timestamp against the time of its creation.
         */
        if (!x509_store_set_time(store, time)) {
            fprintf(stderr, "Failed to set store time\n");
            goto out;
        }
    } else {
        printf("Use the \"-TSA-CAfile\" option to add the Time-Stamp Authority certificates bundle to verify the Timestamp Server.\n");
        goto out;
    }

    /* verify a CMS SignedData structure */
    printf("\nTimestamp verified using:\n");
    if (!CMS_verify(timestamp, NULL, store, 0, NULL, 0)) {
        STACK_OF(X509) *cms_certs;

        printf("CMS_verify error\n");
        printf("\nFailed timestamp certificate chain retrieved from the signature:\n");
        cms_certs = CMS_get1_certs(timestamp);
        print_certs_chain(cms_certs);
        sk_X509_pop_free(cms_certs, X509_free);
        goto out;
    }

    sinfos = CMS_get0_SignerInfos(timestamp);
    cmssi = sk_CMS_SignerInfo_value(sinfos, 0);
    CMS_SignerInfo_get0_algs(cmssi, NULL, &signer, NULL, NULL);

    /* verify a Certificate Revocation List */
    if (!ctx->options->ignore_crl) {
        url = clrdp_url_get_x509(signer);
    } else {
        printf("CRL online verification disabled\n");
    }
    if (url) {
        if (ctx->options->ignore_cdp) {
            printf("Ignored TSA's CRL distribution point: %s\n", url);
        } else {
            printf("TSA's CRL distribution point: %s\n", url);
            crl = x509_crl_get(ctx, url);
        }
        OPENSSL_free(url);
        if (!crl && !ctx->options->tsa_crlfile) {
            printf("Use the \"-TSA-CRLfile\" option to add one or more Time-Stamp Authority CRLs in PEM format.\n");
            goto out;
        }
    }
    if (p7->d.sign->crl || crl) {
        crls = x509_crl_list_get(p7, crl);
        if (!crls) {
            fprintf(stderr, "Failed to use CRL distribution point\n");
            goto out;
        }
    }
    if (ctx->options->tsa_crlfile || crls) {
        STACK_OF(X509) *chain = CMS_get1_certs(timestamp);
        int crlok = verify_crl(ctx->options->tsa_cafile, ctx->options->tsa_crlfile,
            crls, signer, chain);
        sk_X509_pop_free(chain, X509_free);
        printf("Timestamp Server Signature CRL verification: %s\n", crlok ? "ok" : "failed");
        if (!crlok)
            goto out;
    } else {
        printf("\n");
    }
    /* check extended key usage flag XKU_TIMESTAMP */
    if (!(X509_get_extended_key_usage(signer) & XKU_TIMESTAMP)) {
        fprintf(stderr, "Unsupported Signer's certificate purpose XKU_TIMESTAMP\n");
        goto out;
    }
    /* verify the hash provided from the trusted timestamp */
    if (!verify_timestamp_token(p7, timestamp)) {
        goto out;
    }
    verok = 1; /* OK */
out:
    X509_STORE_free(store);
    sk_X509_CRL_pop_free(crls, X509_CRL_free);
    if (!verok)
        ERR_print_errors_fp(stderr);
    return verok;
}

#if OPENSSL_VERSION_NUMBER<0x30000000L
static int PKCS7_type_is_other(PKCS7 *p7)
{
    int isOther = 1;
    int nid = OBJ_obj2nid(p7->type);

    switch (nid) {
    case NID_pkcs7_data:
    case NID_pkcs7_signed:
    case NID_pkcs7_enveloped:
    case NID_pkcs7_signedAndEnveloped:
    case NID_pkcs7_digest:
    case NID_pkcs7_encrypted:
        isOther = 0;
        break;
    default:
        isOther = 1;
    }
    return isOther;
}
#endif /* OPENSSL_VERSION_NUMBER<0x30000000L */

/*
 * [in] p7: PKCS#7 signature
 * [in] store: X509_STORE
 * [returns] 1 on error or 0 on success
 */
static int verify_pkcs7_data(PKCS7 *p7, X509_STORE *store)
{
    int verok = 0;
#if OPENSSL_VERSION_NUMBER<0x30500000L
    BIO *bio = NULL;
    PKCS7 *contents = p7->d.sign->contents;

    /*
     * In the PKCS7_verify() function, the BIO *indata parameter refers to
     * the signed data if the content is detached from p7.
     * Otherwise, indata should be NULL, and then the signed data must be in p7.
     * The OpenSSL error workaround is to put the inner content into BIO *indata parameter
     * https://github.com/openssl/openssl/pull/22575
     */
    if (PKCS7_type_is_other(contents) && (contents->d.other != NULL)
        && (contents->d.other->value.sequence != NULL)
        && (contents->d.other->value.sequence->length > 0)) {
        if (contents->d.other->type == V_ASN1_SEQUENCE) {
            /* only verify the content of the sequence */
            const unsigned char *data = contents->d.other->value.sequence->data;
            long len;
            int inf, tag, class;

            inf = ASN1_get_object(&data, &len, &tag, &class,
                contents->d.other->value.sequence->length);
            if (inf != V_ASN1_CONSTRUCTED || tag != V_ASN1_SEQUENCE) {
                fprintf(stderr, "Corrupted data content\n");
                X509_STORE_free(store);
                return 0; /* FAILED */
            }
            bio = BIO_new_mem_buf(data, (int)len);
        } else {
            /* verify the entire value */
            bio = BIO_new_mem_buf(contents->d.other->value.sequence->data,
                contents->d.other->value.sequence->length);
        }
    } else {
        fprintf(stderr, "Corrupted data content\n");
        X509_STORE_free(store);
        return 0; /* FAILED */
    }
    verok = PKCS7_verify(p7, NULL, store, bio, NULL, 0);
    BIO_free(bio);
#else /* OPENSSL_VERSION_NUMBER<0x30500000L */
    verok = PKCS7_verify(p7, NULL, store, NULL, NULL, 0);
#endif /* OPENSSL_VERSION_NUMBER<0x30500000L */
    return verok;
}

/*
 * [in] ctx: structure holds input and output data
 * [in] p7: PKCS#7 signature
 * [in] time: signature verification time
 * [in] signer: signer's X509 certificate
 * [returns] 1 on error or 0 on success
 */
static int verify_authenticode(FILE_FORMAT_CTX *ctx, PKCS7 *p7, time_t time, X509 *signer)
{
    X509_CRL *crl = NULL;
    STACK_OF(X509_CRL) *crls = NULL;
    int verok = 0;
    char *url = NULL;
    X509_STORE *store = X509_STORE_new();

    if (!store)
        goto out;

    if (!x509_store_load_file(store, ctx->options->cafile)) {
        fprintf(stderr, "Failed to add store lookup file\n");
        goto out;
    }
    if (time != INVALID_TIME) {
        printf("Signature verification time: ");
        print_time_t(time);
        if (!x509_store_set_time(store, time)) {
            fprintf(stderr, "Failed to set signature time\n");
            goto out;
        }
    } else if (ctx->options->time != INVALID_TIME) {
        printf("Signature verification time: ");
        print_time_t(ctx->options->time);
        if (!x509_store_set_time(store, ctx->options->time)) {
            fprintf(stderr, "Failed to set verifying time\n");
            goto out;
        }
    }
    /* verify a PKCS#7 signedData structure */
    printf("Signing certificate chain verified using:\n");
    if (!verify_pkcs7_data(p7, store)) {
        printf("PKCS7_verify error\n");
        printf("Failed signing certificate chain retrieved from the signature:\n");
        print_certs_chain(p7->d.sign->cert);
        goto out;
    }

    /* verify a Certificate Revocation List */
    if (!ctx->options->ignore_crl) {
        url = clrdp_url_get_x509(signer);
    } else {
        printf("CRL online verification disabled\n");
    }
    if (url) {
        if (ctx->options->ignore_cdp) {
            printf("Ignored CRL distribution point: %s\n", url);
        } else {
            printf("CRL distribution point: %s\n", url);
            crl = x509_crl_get(ctx, url);
        }
        OPENSSL_free(url);
        if (!crl && !ctx->options->crlfile) {
            printf("Use the \"-CRLfile\" option to add one or more CRLs in PEM format.\n");
            goto out;
        }
    }
    if (p7->d.sign->crl || crl) {
        crls = x509_crl_list_get(p7, crl);
        if (!crls) {
            fprintf(stderr, "Failed to use CRL distribution point\n");
            goto out;
        }
    }
    if (ctx->options->crlfile || crls) {
        STACK_OF(X509) *chain = p7->d.sign->cert;
        int crlok = verify_crl(ctx->options->cafile, ctx->options->crlfile,
            crls, signer, chain);

        printf("Signature CRL verification: %s\n", crlok ? "ok" : "failed");
        if (!crlok)
            goto out;
    }
    /* keyUsage, if present, must permit digitalSignature (RFC 5280 section 4.2.1.3) */
    if (!(X509_get_key_usage(signer) & X509v3_KU_DIGITAL_SIGNATURE)) {
        fprintf(stderr, "Signer certificate rejected: keyUsage does not permit digitalSignature\n");
        goto out;
    }
    /* extendedKeyUsage, if present, must permit codeSigning (RFC 5280 section 4.2.1.12) */
    if (!(X509_get_extended_key_usage(signer) & XKU_CODE_SIGN)) {
        fprintf(stderr, "Signer certificate rejected: extendedKeyUsage does not permit codeSigning\n");
        goto out;
    }

    verok = 1; /* OK */
out:
    X509_STORE_free(store);
    sk_X509_CRL_pop_free(crls, X509_CRL_free);
    if (!verok)
        ERR_print_errors_fp(stderr);
    return verok;
}

/*
 * [in] leafhash: optional hash algorithm and the signer's certificate hash
 * [in] cert: signer's x509 certificate
 * [returns] 0 on error or 1 on success
 */
static int verify_leaf_hash(X509 *cert, const char *leafhash)
{
    u_char *mdbuf = NULL, *certbuf, *tmp;
    u_char cmdbuf[EVP_MAX_MD_SIZE];
    const EVP_MD *md;
    long mdlen = 0;
    size_t certlen, written;
    BIO *bhash;

    /* decode the provided hash */
    char *mdid = OPENSSL_strdup(leafhash);
    char *hash = strchr(mdid, ':');
    if (hash == NULL) {
        fprintf(stderr, "\nUnable to parse -require-leaf-hash parameter: %s\n", leafhash);
        OPENSSL_free(mdid);
        return 0; /* FAILED */
    }
    *hash++ = '\0';
    md = EVP_get_digestbyname(mdid);
    if (md == NULL) {
        fprintf(stderr, "\nUnable to lookup digest by name '%s'\n", mdid);
        OPENSSL_free(mdid);
        return 0; /* FAILED */
    }
    mdbuf = OPENSSL_hexstr2buf(hash, &mdlen);
    if (mdlen != EVP_MD_size(md)) {
        fprintf(stderr, "\nHash length mismatch: '%s' digest must be %d bytes long (got %ld bytes)\n",
            mdid, EVP_MD_size(md), mdlen);
        OPENSSL_free(mdid);
        OPENSSL_free(mdbuf);
        return 0; /* FAILED */
    }
    OPENSSL_free(mdid);

    /* compute the leaf certificate hash */
    bhash = BIO_new(BIO_f_md());
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
    if (!BIO_set_md(bhash, md)) {
        fprintf(stderr, "Unable to set the message digest of BIO\n");
        BIO_free_all(bhash);
        OPENSSL_free(mdbuf);
        return 0; /* FAILED */
    }
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
    BIO_push(bhash, BIO_new(BIO_s_null()));
    certlen = (size_t)i2d_X509(cert, NULL);
    certbuf = OPENSSL_malloc(certlen);
    tmp = certbuf;
    i2d_X509(cert, &tmp);
    if (!BIO_write_ex(bhash, certbuf, certlen, &written) || written != certlen) {
        BIO_free_all(bhash);
        OPENSSL_free(mdbuf);
        OPENSSL_free(certbuf);
        return 0; /* FAILED */
    }
    BIO_gets(bhash, (char*)cmdbuf, EVP_MD_size(md));
    BIO_free_all(bhash);
    OPENSSL_free(certbuf);

    /* compare the provided hash against the computed hash */
    if (memcmp(mdbuf, cmdbuf, (size_t)EVP_MD_size(md))) {
        print_hash("\nLeaf hash value mismatch", "computed", cmdbuf, EVP_MD_size(md));
        OPENSSL_free(mdbuf);
        return 0; /* FAILED */
    }
    OPENSSL_free(mdbuf);
    return 1; /* OK */
}

/*
 * [in] timestamp: CMS_ContentInfo struct for Authenticode Timestamp or RFC 3161 Timestamp
 * [in] time: timestamp verification time
 * [returns] 0 on error or 1 on success
 */
static int print_cms_timestamp(CMS_ContentInfo *timestamp, time_t time)
{
    STACK_OF(CMS_SignerInfo) *sinfos;
    CMS_SignerInfo *si;
    X509_ATTRIBUTE *attr;
    int md_nid;
    ASN1_INTEGER *serialno;
    char *issuer_name, *serial;
    BIGNUM *serialbn;
    X509_ALGOR *pdig;
    X509_NAME *issuer = NULL;

    sinfos = CMS_get0_SignerInfos(timestamp);
    if (sinfos == NULL)
        return 0; /* FAILED */
    si = sk_CMS_SignerInfo_value(sinfos, 0);
    if (si == NULL)
        return 0; /* FAILED */
    printf("\nCountersignatures:\n\tTimestamp time: ");
    print_time_t(time);

    /* PKCS#9 signing time - Policy OID: 1.2.840.113549.1.9.5 */
    attr = CMS_signed_get_attr(si, CMS_signed_get_attr_by_NID(si, NID_pkcs9_signingTime, -1));
    printf("\tSigning time: ");
    print_time_t(time_t_get_asn1_time(X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_UTCTIME, NULL)));

    CMS_SignerInfo_get0_algs(si, NULL, NULL, &pdig, NULL);
    if (pdig == NULL || pdig->algorithm == NULL)
        return 0; /* FAILED */
    md_nid = OBJ_obj2nid(pdig->algorithm);
    printf("\tHash Algorithm: %s\n", (md_nid == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(md_nid));

    if (!CMS_SignerInfo_get0_signer_id(si, NULL, &issuer, &serialno) || !issuer)
        return 0; /* FAILED */
    issuer_name = x509_name_to_utf8(issuer);
    serialbn = ASN1_INTEGER_to_BN(serialno, NULL);
    serial = BN_bn2hex(serialbn);
    printf("\tIssuer: %s\n\tSerial: %s\n",
           issuer_name, serial);
    OPENSSL_free(issuer_name);
    BN_free(serialbn);
    OPENSSL_free(serial);
    return 1; /* OK */
}

/*
 * RFC3852: the message-digest authenticated attribute type MUST be
 * present when there are any authenticated attributes present
 * [in] timestamp: CMS_ContentInfo struct for Authenticode Timestamp or RFC 3161 Timestamp
 * [in] p7: PKCS#7 signature
 * [in] verbose: additional output mode
 * [returns] 0 on error or 1 on success
 */
static time_t time_t_timestamp_get_attributes(CMS_ContentInfo **timestamp, PKCS7 *p7, int verbose)
{
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info;
    PKCS7_SIGNER_INFO *si;
    int md_nid, i;
    STACK_OF(X509_ATTRIBUTE) *auth_attr, *unauth_attr;
    X509_ATTRIBUTE *attr;
    ASN1_OBJECT *object;
    ASN1_STRING *value;
    char object_txt[128];
    time_t time = INVALID_TIME;

    signer_info = PKCS7_get_signer_info(p7);
    if (!signer_info)
        return INVALID_TIME; /* FAILED */
    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si)
        return INVALID_TIME; /* FAILED */
    md_nid = OBJ_obj2nid(si->digest_alg->algorithm);
    printf("Message digest algorithm: %s\n",
        (md_nid == NID_undef) ? "UNKNOWN" : OBJ_nid2sn(md_nid));

    /* Authenticated attributes */
    auth_attr = PKCS7_get_signed_attributes(si); /* cont[0] */
    printf("\nAuthenticated attributes:\n");
    for (i=0; i<X509at_get_attr_count(auth_attr); i++) {
        attr = X509at_get_attr(auth_attr, i);
        object = X509_ATTRIBUTE_get0_object(attr);
        if (object == NULL)
            continue;
        object_txt[0] = 0x00;
        OBJ_obj2txt(object_txt, sizeof object_txt, object, 1);
        if (!strcmp(object_txt, PKCS9_MESSAGE_DIGEST)) {
            /* PKCS#9 message digest - Policy OID: 1.2.840.113549.1.9.4 */
            const u_char *mdbuf;
            int len;
            ASN1_STRING *digest  = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_OCTET_STRING, NULL);
            if (digest == NULL)
                continue;
            mdbuf = ASN1_STRING_get0_data(digest);
            len = ASN1_STRING_length(digest);
            print_hash("\tMessage digest", "", mdbuf, len);
        } else if (!strcmp(object_txt, PKCS9_SIGNING_TIME)) {
            /* PKCS#9 signing time - Policy OID: 1.2.840.113549.1.9.5 */
            ASN1_UTCTIME *signtime = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_UTCTIME, NULL);
            if (signtime == NULL)
                continue;
            printf("\tSigning time: ");
            print_time_t(time_t_get_asn1_time(signtime));
        } else if (!strcmp(object_txt, SPC_SP_OPUS_INFO_OBJID)) {
            /* Microsoft OID: 1.3.6.1.4.1.311.2.1.12 */
            SpcSpOpusInfo *opus;
            const u_char *data;
            value  = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_SEQUENCE, NULL);
            if (value == NULL)
                continue;
            data = ASN1_STRING_get0_data(value);
            opus = d2i_SpcSpOpusInfo(NULL, &data, ASN1_STRING_length(value));
            if (opus == NULL)
                continue;
            if (opus->moreInfo && opus->moreInfo->type == 0) {
                char *url = OPENSSL_strdup((char *)opus->moreInfo->value.url->data);
                printf("\tURL description: %s\n", url);
                OPENSSL_free(url);
            }
            if (opus->programName) {
                char *desc = NULL;
                if (opus->programName->type == 0) {
                    u_char *opusdata;
                    int len = ASN1_STRING_to_UTF8(&opusdata, opus->programName->value.unicode);
                    if (len >= 0) {
                        desc = OPENSSL_strndup((char *)opusdata, (size_t)len);
                        OPENSSL_free(opusdata);
                    }
                } else {
                    desc = OPENSSL_strdup((char *)opus->programName->value.ascii->data);
                }
                if (desc) {
                    printf("\tText description: %s\n", desc);
                    OPENSSL_free(desc);
                }
            }
            SpcSpOpusInfo_free(opus);
        } else if (!strcmp(object_txt, SPC_STATEMENT_TYPE_OBJID)) {
            /* Microsoft OID: 1.3.6.1.4.1.311.2.1.11 */
            const u_char *purpose;
            value  = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_SEQUENCE, NULL);
            if (value == NULL)
                continue;
            purpose = ASN1_STRING_get0_data(value);
            if (!memcmp(purpose, purpose_comm, sizeof purpose_comm))
                printf("\tMicrosoft Commercial Code Signing purpose\n");
            else if (!memcmp(purpose, purpose_ind, sizeof purpose_ind))
                printf("\tMicrosoft Individual Code Signing purpose\n");
            else
                printf("\tUnrecognized Code Signing purpose\n");
        } else if (!strcmp(object_txt, MS_JAVA_SOMETHING)) {
            /* Microsoft OID: 1.3.6.1.4.1.311.15.1 */
            const u_char *level;
            value  = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_SEQUENCE, NULL);
            if (value == NULL)
                continue;
            level = ASN1_STRING_get0_data(value);
            if (!memcmp(level, java_attrs_low, sizeof java_attrs_low))
                printf("\tLow level of permissions in Microsoft Internet Explorer 4.x for CAB files\n");
            else
                printf("\tUnrecognized level of permissions in Microsoft Internet Explorer 4.x for CAB files\n");
        } else if (!strcmp(object_txt, PKCS9_SEQUENCE_NUMBER)) {
            /* PKCS#9 sequence number - Policy OID: 1.2.840.113549.1.9.25.4 */
            ASN1_INTEGER *number = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_INTEGER, NULL);
            if (number == NULL)
                continue;
            printf("\tSequence number: %ld\n", ASN1_INTEGER_get(number));
         }
    }

    /* Unauthenticated attributes */
    unauth_attr = PKCS7_get_attributes(si); /* cont[1] */
    for (i=0; i<X509at_get_attr_count(unauth_attr); i++) {
        attr = X509at_get_attr(unauth_attr, i);
        object = X509_ATTRIBUTE_get0_object(attr);
        if (object == NULL)
            continue;
        object_txt[0] = 0x00;
        OBJ_obj2txt(object_txt, sizeof object_txt, object, 1);
        if (!strcmp(object_txt, PKCS9_COUNTER_SIGNATURE)) {
            /* Authenticode Timestamp - Policy OID: 1.2.840.113549.1.9.6 */
            const u_char *data;
            CMS_ContentInfo *cms;
            PKCS7_SIGNER_INFO *countersi;
            value = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_SEQUENCE, NULL);
            if (value == NULL)
                continue;
            data = ASN1_STRING_get0_data(value);
            countersi = d2i_PKCS7_SIGNER_INFO(NULL, &data, ASN1_STRING_length(value));
            if (countersi == NULL) {
                printf("Warning: Authenticode Timestamp could not be decoded correctly\n");
                ERR_print_errors_fp(stderr);
                continue;
            }
            time = time_t_get_si_time(countersi);
            if (time != INVALID_TIME) {
                cms = cms_get_timestamp(p7->d.sign, countersi);
                if (cms) {
                    if (!print_cms_timestamp(cms, time)) {
                        CMS_ContentInfo_free(cms);
                        printf("Warning: Authenticode Timestamp could not be decoded correctly\n");
                        ERR_print_errors_fp(stderr);
                        continue;
                    }
                    *timestamp = cms;
                } else {
                    printf("Warning: Corrupt Authenticode Timestamp embedded content\n");
                }
            } else {
                printf("Warning: PKCS9_TIMESTAMP_SIGNING_TIME attribute not found\n");
                PKCS7_SIGNER_INFO_free(countersi);
            }
        } else if (!strcmp(object_txt, SPC_RFC3161_OBJID)) {
            /* RFC3161 Timestamp - Policy OID: 1.3.6.1.4.1.311.3.3.1 */
            const u_char *data;
            CMS_ContentInfo *cms;
            value = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_SEQUENCE, NULL);
            if (value == NULL)
                continue;
            data = ASN1_STRING_get0_data(value);
            cms = d2i_CMS_ContentInfo(NULL, &data, ASN1_STRING_length(value));
            if (cms == NULL) {
                printf("Warning: RFC3161 Timestamp could not be decoded correctly\n");
                ERR_print_errors_fp(stderr);
                continue;
            }
            time = time_t_get_cms_time(cms);
            if (time != INVALID_TIME) {
                if (!print_cms_timestamp(cms, time)) {
                    CMS_ContentInfo_free(cms);
                    printf("Warning: RFC3161 Timestamp could not be decoded correctly\n");
                    ERR_print_errors_fp(stderr);
                    continue;
                }
                *timestamp = cms;
            } else {
                printf("Warning: Corrupt RFC3161 Timestamp embedded content\n");
                CMS_ContentInfo_free(cms);
                ERR_print_errors_fp(stderr);
            }
        } else if (!strcmp(object_txt, SPC_UNAUTHENTICATED_DATA_BLOB_OBJID)) {
            /* Unauthenticated Data Blob - Policy OID: 1.3.6.1.4.1.42921.1.2.1 */
            ASN1_STRING *blob = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_UTF8STRING, NULL);
            if (blob == NULL) {
                printf("Warning: Unauthenticated Data Blob could not be decoded correctly\n");
                continue;
            }
            if (verbose) {
                char *data_blob = OPENSSL_buf2hexstr(blob->data, blob->length);
                printf("\nUnauthenticated Data Blob:\n%s\n", data_blob);
                OPENSSL_free(data_blob);
            } else {
                printf("\nUnauthenticated Data Blob length: %d bytes\n", blob->length);
            }
        }
    }

    /* Signature */
    if (verbose) {
        md_nid = OBJ_obj2nid(si->digest_enc_alg->algorithm);
        printf("\nDigest encryption algorithm: %s\n",
            (md_nid == NID_undef) ? "UNKNOWN" : OBJ_nid2sn(md_nid));
        print_hash("Signature", "", ASN1_STRING_get0_data(si->enc_digest), ASN1_STRING_length(si->enc_digest));
    }

    return time;
}

/*
 * Convert ASN1_TIME to time_t
 * [in] s: ASN1_TIME structure
 * [returns] INVALID_TIME on error or time_t on success
 */
static time_t time_t_get_asn1_time(const ASN1_TIME *s)
{
    struct tm tm;

    if ((s == NULL) || (!ASN1_TIME_check(s))) {
        return INVALID_TIME;
    }
    if (ASN1_TIME_to_tm(s, &tm)) {
#ifdef _WIN32
        return _mkgmtime(&tm);
#else /* _WIN32 */
        return timegm(&tm);
#endif /* _WIN32 */
    } else {
        return INVALID_TIME;
    }
}

/*
 * Get signing time from authenticated attributes
 * [in] si: PKCS7_SIGNER_INFO structure
 * [returns] INVALID_TIME on error or time_t on success
 */
static time_t time_t_get_si_time(PKCS7_SIGNER_INFO *si)
{
    ASN1_UTCTIME *time = asn1_time_get_si_time(si);

    if (time == NULL)
        return INVALID_TIME; /* FAILED */
    return time_t_get_asn1_time(time);
}

/*
 * Get signing time from authenticated attributes cont[0]
 * [in] si: PKCS7_SIGNER_INFO structure
 * [returns] NULL on error or ASN1_UTCTIME on success
 */
static ASN1_UTCTIME *asn1_time_get_si_time(PKCS7_SIGNER_INFO *si)
{
    STACK_OF(X509_ATTRIBUTE) *auth_attr = PKCS7_get_signed_attributes(si);
    if (auth_attr) {
        int i;
        for (i=0; i<X509at_get_attr_count(auth_attr); i++) {
            int nid = OBJ_txt2nid(PKCS9_SIGNING_TIME);
            X509_ATTRIBUTE *attr = X509at_get_attr(auth_attr, i);
            if (OBJ_obj2nid(X509_ATTRIBUTE_get0_object(attr)) == nid) {
                /* PKCS#9 signing time - Policy OID: 1.2.840.113549.1.9.5 */
                return X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_UTCTIME, NULL);
            }
        }
    }
    return NULL;
}

/*
 * Get sequence number from authenticated attributes cont[0]
 * [in] si: PKCS7_SIGNER_INFO structure
 * [returns] NULL on error or ASN1_UTCTIME on success
 */
static long get_sequence_number(PKCS7_SIGNER_INFO *si)
{
    STACK_OF(X509_ATTRIBUTE) *auth_attr = PKCS7_get_signed_attributes(si);
    if (auth_attr) {
        int i;
        for (i=0; i<X509at_get_attr_count(auth_attr); i++) {
            int nid = OBJ_txt2nid(PKCS9_SEQUENCE_NUMBER);
            X509_ATTRIBUTE *attr = X509at_get_attr(auth_attr, i);
            if (OBJ_obj2nid(X509_ATTRIBUTE_get0_object(attr)) == nid) {
                /* PKCS#9 sequence number - Policy OID:1.2.840.113549.1.9.25.4 */
                return ASN1_INTEGER_get(X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_INTEGER, NULL));
            }
        }
    }
    return 0;
}

/*
 * Get timestamping time from embedded content in a CMS_ContentInfo structure
 * [in] si: CMS_ContentInfo structure
 * [returns] INVALID_TIME on error or time_t on success
 */
static time_t time_t_get_cms_time(CMS_ContentInfo *cms)
{
    time_t posix_time = INVALID_TIME;
    ASN1_OCTET_STRING **pos  = CMS_get0_content(cms);

    if (pos != NULL && *pos != NULL) {
        const u_char *p = (*pos)->data;
        TS_TST_INFO *token = d2i_TS_TST_INFO(NULL, &p, (*pos)->length);
        if (token) {
            const ASN1_GENERALIZEDTIME *asn1_time = TS_TST_INFO_get_time(token);
            posix_time = time_t_get_asn1_time(asn1_time);
            TS_TST_INFO_free(token);
        }
    }
    return posix_time;
}

/*
 * Create new CMS_ContentInfo struct for Authenticode Timestamp.
 * This struct does not contain any TS_TST_INFO as specified in RFC 3161.
 * [in] p7_signed: PKCS#7 signedData structure
 * [in] countersignature: Authenticode Timestamp decoded to PKCS7_SIGNER_INFO
 * [returns] pointer to CMS_ContentInfo structure
 */
static CMS_ContentInfo *cms_get_timestamp(PKCS7_SIGNED *p7_signed,
    PKCS7_SIGNER_INFO *countersignature)
{
    CMS_ContentInfo *cms = NULL;
    PKCS7_SIGNER_INFO *si;
    PKCS7 *p7 = NULL, *content = NULL;
    u_char *p = NULL;
    const u_char *q;
    int i, len = 0;

    p7 = PKCS7_new();
    si = sk_PKCS7_SIGNER_INFO_value(p7_signed->signer_info, 0);
    if (si == NULL)
        goto out;

    /* Create new signed PKCS7 timestamp structure. */
    if (!PKCS7_set_type(p7, NID_pkcs7_signed))
        goto out;
    if (!PKCS7_add_signer(p7, countersignature))
        goto out;
    for (i = 0; i < sk_X509_num(p7_signed->cert); i++) {
        if (!PKCS7_add_certificate(p7, sk_X509_value(p7_signed->cert, i)))
            goto out;
    }
    /* Create new encapsulated NID_id_smime_ct_TSTInfo content. */
    content = PKCS7_new();
    content->d.other = ASN1_TYPE_new();
    content->type = OBJ_nid2obj(NID_id_smime_ct_TSTInfo);
    ASN1_TYPE_set1(content->d.other, V_ASN1_OCTET_STRING, si->enc_digest);
    /* Add encapsulated content to signed PKCS7 timestamp structure:
       p7->d.sign->contents = content */
    if (!PKCS7_set_content(p7, content)) {
        PKCS7_free(content);
        goto out;
    }
    /* Convert PKCS7 into CMS_ContentInfo */
    if (((len = i2d_PKCS7(p7, NULL)) <= 0) || (p = OPENSSL_malloc((size_t)len)) == NULL) {
        fprintf(stderr, "Failed to convert pkcs7: %d\n", len);
        goto out;
    }
    len = i2d_PKCS7(p7, &p);
    p -= len;
    q = p;
    cms = d2i_CMS_ContentInfo(NULL, &q, len);
    OPENSSL_free(p);

out:
    if (!cms)
        ERR_print_errors_fp(stderr);
    PKCS7_free(p7);
    return cms;
}

/*
 * The attribute type is SPC_INDIRECT_DATA_OBJID, so get a digest algorithm and a message digest
 * from the content and compare the message digest against the computed message digest of the file
 * [in] ctx: structure holds input and output data
 * [in] content: catalog file content
 * [returns] 1 on error or 0 on success
 */
static int verify_content_member_digest(FILE_FORMAT_CTX *ctx, ASN1_TYPE *content)
{
    int mdlen, mdtype = -1;
    u_char mdbuf[EVP_MAX_MD_SIZE];
    SpcIndirectDataContent *idc;
    const u_char *data;
    ASN1_STRING *value;
    const EVP_MD *md;
    u_char *cmdbuf = NULL;

    value = content->value.sequence;
    data = ASN1_STRING_get0_data(value);
    idc = d2i_SpcIndirectDataContent(NULL, &data, ASN1_STRING_length(value));
    if (!idc) {
        fprintf(stderr, "Failed to extract SpcIndirectDataContent data\n");
        return 1; /* FAILED */
    }
    if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) {
        fprintf(stderr, "Failed to extract message digest from signature\n\n");
        SpcIndirectDataContent_free(idc);
        return 1; /* FAILED */
    }
    if (!ctx->format->digest_calc) {
        fprintf(stderr, "Unsupported method: digest_calc\n");
        SpcIndirectDataContent_free(idc);
        return 1; /* FAILED */
    }
    md = EVP_get_digestbynid(mdtype);
    cmdbuf = ctx->format->digest_calc(ctx, md);
    if (!cmdbuf) {
        fprintf(stderr, "Failed to compute a message digest value\n\n");
        SpcIndirectDataContent_free(idc);
        return 1; /* FAILED */
    }
    mdlen = EVP_MD_size(EVP_get_digestbynid(mdtype));
    if (memcmp(mdbuf, cmdbuf, (size_t)mdlen)) {
        OPENSSL_free(cmdbuf);
        SpcIndirectDataContent_free(idc);
        return 1; /* FAILED */
    } else {
        printf("Message digest algorithm  : %s\n", OBJ_nid2sn(mdtype));
        print_hash("Current message digest    ", "", mdbuf, mdlen);
        print_hash("Calculated message digest ", "\n", cmdbuf, mdlen);
    }
    OPENSSL_free(cmdbuf);

    if (idc->data && ctx->format->verify_indirect_data
        && !ctx->format->verify_indirect_data(ctx, idc->data)) {
        SpcIndirectDataContent_free(idc);
        return 1; /* FAILED */
    }
    SpcIndirectDataContent_free(idc);
    return 0; /* OK */
}

/*
 * Find the message digest of the file for all files added to the catalog file
 * CTL (MS_CTL_OBJID) is a list of hashes of certificates or a list of hashes files
 * [in] ctx: structure holds input and output data
 * [in] p7: PKCS#7 signature
 * [returns] 1 on error or 0 on success
 */
static int verify_content(FILE_FORMAT_CTX *ctx, PKCS7 *p7)
{
    MsCtlContent *ctlc;
    int i;

    ctlc = ms_ctl_content_get(p7);
    if (!ctlc) {
        fprintf(stderr, "Failed to extract MS_CTL_OBJID data\n");
        return 1; /* FAILED */
    }
    for (i = 0; i < sk_CatalogInfo_num(ctlc->header_attributes); i++) {
        int j;
        CatalogInfo *header_attr = sk_CatalogInfo_value(ctlc->header_attributes, i);
        if (header_attr == NULL)
            continue;
        for (j = 0; j < sk_CatalogAuthAttr_num(header_attr->attributes); j++) {
            char object_txt[128];
            CatalogAuthAttr *attribute;
            ASN1_TYPE *content;

            attribute = sk_CatalogAuthAttr_value(header_attr->attributes, j);
            if (!attribute)
                continue;
            content = catalog_content_get(attribute);
            if (!content)
                continue;
            object_txt[0] = 0x00;
            OBJ_obj2txt(object_txt, sizeof object_txt, attribute->type, 1);
            if (!strcmp(object_txt, SPC_INDIRECT_DATA_OBJID)) {
                /* SPC_INDIRECT_DATA_OBJID OID: 1.3.6.1.4.1.311.2.1.4 */
                if (!verify_content_member_digest(ctx, content)) {
                    /* computed message digest of the file is found in the catalog file */
                    ASN1_TYPE_free(content);
                    MsCtlContent_free(ctlc);
                    return 0; /* OK */
                }
            }
            ASN1_TYPE_free(content);
        }
    }
    MsCtlContent_free(ctlc);
    ERR_print_errors_fp(stderr);
    return 1; /* FAILED */
}

/*
 * [in] ctx: structure holds input and output data
 * [in] p7: PKCS#7 signature
 * [returns] 1 on error or 0 on success
 */
static int verify_signature(FILE_FORMAT_CTX *ctx, PKCS7 *p7)
{
    int leafok, verok;
    STACK_OF(X509) *signers;
    X509 *signer;
    CMS_ContentInfo *timestamp = NULL;
    time_t time;

    signers = PKCS7_get0_signers(p7, NULL, 0);
    if (!signers || sk_X509_num(signers) != 1) {
        fprintf(stderr, "PKCS7_get0_signers error\n");
        return 1; /* FAILED */
    }
    signer = sk_X509_value(signers, 0);
    sk_X509_free(signers);
    printf("Signer's certificate:\n");
    print_cert(signer, 0);

    time = time_t_timestamp_get_attributes(&timestamp, p7, ctx->options->verbose);
    if (ctx->options->leafhash != NULL) {
        leafok = verify_leaf_hash(signer, ctx->options->leafhash);
        printf("\nLeaf hash match: %s\n", leafok ? "ok" : "failed");
        if (!leafok) {
            printf("Signature verification: failed\n\n");
            return 1; /* FAILED */
        }
    }
    if (ctx->options->catalog)
        printf("\nFile is signed in catalog: %s\n", ctx->options->catalog);
    printf("\nCAfile: %s\n", ctx->options->cafile);
    if (ctx->options->crlfile)
        printf("CRLfile: %s\n", ctx->options->crlfile);
    if (ctx->options->tsa_cafile)
        printf("TSA's certificates file: %s\n", ctx->options->tsa_cafile);
    if (ctx->options->tsa_crlfile)
        printf("TSA's CRL file: %s\n", ctx->options->tsa_crlfile);
    if (timestamp) {
        if (ctx->options->ignore_timestamp) {
            printf("\nTimestamp Server Signature verification is disabled\n");
            time = INVALID_TIME;
        } else {
            int timeok = verify_timestamp(ctx, p7, timestamp, time);
            printf("\nTimestamp Server Signature verification: %s\n", timeok ? "ok" : "failed");
            if (!timeok) {
                time = INVALID_TIME;
            }
        }
        CMS_ContentInfo_free(timestamp);
        ERR_clear_error();
    } else
        printf("\nTimestamp is not available\n\n");
    verok = verify_authenticode(ctx, p7, time, signer);
    printf("Signature verification: %s\n\n", verok ? "ok" : "failed");
    if (!verok)
        return 1; /* FAILED */

    return 0; /* OK */
}

/*
 * [in] ctx: structure holds input and output data
 * [returns] 1 on error or 0 on success
 */
static int verify_signed_file(FILE_FORMAT_CTX *ctx, GLOBAL_OPTIONS *options)
{
    int i, ret = 1, verified = 0;
    PKCS7 *p7;
    STACK_OF(PKCS7) *signatures = NULL;
    int detached = options->catalog ? 1 : 0;

    if (detached) {
        GLOBAL_OPTIONS *cat_options;
        FILE_FORMAT_CTX *cat_ctx;

        if (!ctx->format->is_detaching_supported || !ctx->format->is_detaching_supported()) {
            fprintf(stderr, "This format does not support detached PKCS#7 signature\n");
            return 1; /* FAILED */
        }
        printf("Checking the specified catalog file\n\n");
        cat_options = OPENSSL_memdup(options, sizeof(GLOBAL_OPTIONS));
        if (!cat_options) {
            fprintf(stderr, "OPENSSL_memdup error.\n");
            return 1; /* FAILED */
        }
        cat_options->infile = options->catalog;
        cat_options->cmd = CMD_EXTRACT;
        cat_ctx = file_format_cat.ctx_new(cat_options, NULL, NULL);
        if (!cat_ctx) {
            fprintf(stderr, "CAT file initialization error\n");
            return 1; /* FAILED */
        }
        if (!cat_ctx->format->pkcs7_extract) {
            fprintf(stderr, "Unsupported command: extract-signature\n");
            return 1; /* FAILED */
        }
        p7 = cat_ctx->format->pkcs7_extract(cat_ctx);
        cat_ctx->format->ctx_cleanup(cat_ctx);
        OPENSSL_free(cat_options);
    } else {
        if (!ctx->format->pkcs7_extract) {
            fprintf(stderr, "Unsupported command: extract-signature\n");
            return 1; /* FAILED */
        }
        p7 = ctx->format->pkcs7_extract(ctx);
    }
    if (!p7) {
        fprintf(stderr, "Unable to extract existing signature\n");
        return 1; /* FAILED */
    }
    signatures = signature_list_create(p7);
    if (!signatures) {
        fprintf(stderr, "Failed to create signature list\n\n");
        sk_PKCS7_pop_free(signatures, PKCS7_free);
        return 1; /* FAILED */
    }
    for (i = 0; i < sk_PKCS7_num(signatures); i++) {
        PKCS7 *sig;

        if (options->index >= 0 && options->index != i) {
            printf("Warning: signature verification at index %d was skipped\n", i);
            continue;
        }
        sig = sk_PKCS7_value(signatures, i);
        if (detached) {
            if (!verify_content(ctx, sig)) {
                ret &= verify_signature(ctx, sig);
            } else {
                printf("Catalog verification: failed\n\n");
            }
            verified++;
        } else if (ctx->format->verify_digests) {
            printf("\nSignature Index: %d %s\n\n", i, i==0 ? " (Primary Signature)" : "");
            if (ctx->format->verify_digests(ctx, sig)) {
                ret &= verify_signature(ctx, sig);
            }
            verified++;
        } else {
            fprintf(stderr, "Unsupported method: verify_digests\n");
            return 1; /* FAILED */
        }
    }
    printf("Number of verified signatures: %d\n", verified);
    sk_PKCS7_pop_free(signatures, PKCS7_free);
    if (ret)
        ERR_print_errors_fp(stderr);
    return ret;
}

/*
 * Insert PKCS#7 signature and its nested signatures to the sorted signature list
 * [in] p7: PKCS#7 signature
 * [returns] sorted signature list
 */
static STACK_OF(PKCS7) *signature_list_create(PKCS7 *p7)
{
    STACK_OF(PKCS7) *signatures = NULL;
    PKCS7_SIGNER_INFO *si;
    STACK_OF(X509_ATTRIBUTE) *unauth_attr;
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info = PKCS7_get_signer_info(p7);

    if (!signer_info) {
        fprintf(stderr, "Failed to obtain PKCS#7 signer info list\n");
        return 0; /* FAILED */
    }
    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si) {
        fprintf(stderr, "Failed to obtain PKCS#7 signer info value\n");
        return 0; /* FAILED */
    }
    signatures = sk_PKCS7_new(PKCS7_compare);
    if (!signatures) {
        fprintf(stderr, "Failed to create new signature list\n");
        return 0; /* FAILED */
    }
    /* Unauthenticated attributes */
    unauth_attr = PKCS7_get_attributes(si); /* cont[1] */
    if (unauth_attr) {
        /* find Nested Signature - Policy OID: 1.3.6.1.4.1.311.2.4.1 */
        int i;
        for (i=0; i<X509at_get_attr_count(unauth_attr); i++) {
            int nid = OBJ_txt2nid(SPC_NESTED_SIGNATURE_OBJID);
            X509_ATTRIBUTE *attr = X509at_get_attr(unauth_attr, i);
            if (OBJ_obj2nid(X509_ATTRIBUTE_get0_object(attr)) == nid) {
                int j;

                for (j=0; j<X509_ATTRIBUTE_count(attr); j++) {
                    ASN1_STRING *value;
                    const u_char *data;
                    PKCS7 *nested;

                    value = X509_ATTRIBUTE_get0_data(attr, j, V_ASN1_SEQUENCE, NULL);
                    if (value == NULL)
                        continue;
                    data = ASN1_STRING_get0_data(value);
                    nested = d2i_PKCS7(NULL, &data, ASN1_STRING_length(value));
                    if (nested && !sk_PKCS7_push(signatures, nested)) {
                        fprintf(stderr, "Failed to add nested signature\n");
                        PKCS7_free(nested);
                        sk_PKCS7_pop_free(signatures, PKCS7_free);
                        return NULL; /* FAILED */
                    }
                }
            }
        }
    }
    /* sort signatures in ascending order by signing time */
    sk_PKCS7_sort(signatures);
    /* insert the prime signature at index 0 */
    sk_PKCS7_unshift(signatures, p7);
    return signatures; /* OK */
}

/*
 * PKCS#7 signature comparison function
 * [in] a_ptr, b_ptr: pointers to PKCS#7 signatures
 * [returns] signatures order
 */
static int PKCS7_compare(const PKCS7 *const *a, const PKCS7 *const *b)
{
    PKCS7 *p7_a = NULL, *p7_b = NULL;
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info;
    PKCS7_SIGNER_INFO *si;
    const ASN1_TIME *time_a, *time_b;
    long index_a, index_b;
    int ret = 0;

#if OPENSSL_VERSION_NUMBER<0x30000000L
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
#endif
#endif /* OPENSSL_VERSION_NUMBER<0x30000000L */
    p7_a = PKCS7_dup(*a);
#if OPENSSL_VERSION_NUMBER<0x30000000L
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
    if (!p7_a)
        goto out;
    signer_info = PKCS7_get_signer_info(p7_a);
    if (!signer_info)
        goto out;
    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si)
        goto out;
    time_a = asn1_time_get_si_time(si);
    index_a = get_sequence_number(si);

#if OPENSSL_VERSION_NUMBER<0x30000000L
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
#endif
#endif /* OPENSSL_VERSION_NUMBER<0x30000000L */
    p7_b = PKCS7_dup(*b);
#if OPENSSL_VERSION_NUMBER<0x30000000L
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
    if (!p7_b)
        goto out;
    signer_info = PKCS7_get_signer_info(p7_b);
    if (!signer_info)
        goto out;
    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si)
        goto out;
    time_b = asn1_time_get_si_time(si);
    index_b = get_sequence_number(si);

    if (index_a == index_b)
        ret = ASN1_TIME_compare(time_a, time_b);
    else
        ret = (index_a == 0 || index_a < index_b) ? 1 : -1;

out:
    PKCS7_free(p7_a);
    PKCS7_free(p7_b);
    return ret;
}

/*
 * Retrieve a decoded PKCS#7 structure corresponding to the signature
 * stored in the "sigin" file
 * CMD_ATTACH command specific
 * [in] ctx: structure holds input and output data
 * [returns] pointer to PKCS#7 structure
 */
static PKCS7 *pkcs7_get_sigfile(FILE_FORMAT_CTX *ctx)
{
    PKCS7 *p7 = NULL;
    uint32_t filesize;
    char *indata;

    filesize = get_file_size(ctx->options->sigfile);
    if (!filesize) {
        return NULL; /* FAILED */
    }
    indata = map_file(ctx->options->sigfile, filesize);
    if (!indata) {
        fprintf(stderr, "Failed to open file: %s\n", ctx->options->sigfile);
        return NULL; /* FAILED */
    }
    p7 = pkcs7_read_data(indata, filesize);
    unmap_file(indata, filesize);
    return p7;
}

/*
 * [in] options: structure holds the input data
 * [returns] 1 on error or 0 on success
 */
static int check_attached_data(GLOBAL_OPTIONS *options)
{
    FILE_FORMAT_CTX *ctx;
    GLOBAL_OPTIONS *tmp_options = NULL;

    tmp_options = OPENSSL_memdup(options, sizeof(GLOBAL_OPTIONS));
    if (!tmp_options) {
        fprintf(stderr, "OPENSSL_memdup error.\n");
        return 1; /* FAILED */
    }
    tmp_options->infile = options->outfile;
    tmp_options->cmd = CMD_VERIFY;

    ctx = file_format_script.ctx_new(tmp_options, NULL, NULL);
    if (!ctx)
        ctx = file_format_msi.ctx_new(tmp_options, NULL, NULL);
    if (!ctx)
        ctx = file_format_pe.ctx_new(tmp_options, NULL, NULL);
    if (!ctx)
        ctx = file_format_cab.ctx_new(tmp_options, NULL, NULL);
    if (!ctx)
        ctx = file_format_appx.ctx_new(tmp_options, NULL, NULL);
    if (!ctx)
        ctx = file_format_cat.ctx_new(tmp_options, NULL, NULL);
    if (!ctx) {
        fprintf(stderr, "Corrupt attached signature\n");
        OPENSSL_free(tmp_options);
        return 1; /* FAILED */
    }
    if (verify_signed_file(ctx, tmp_options)) {
        fprintf(stderr, "Signature mismatch\n");
        ctx->format->ctx_cleanup(ctx);
        OPENSSL_free(tmp_options);
        return 1; /* FAILED */
    }
    ctx->format->ctx_cleanup(ctx);
    OPENSSL_free(tmp_options);
    return 0; /* OK */
}

/*
 * [in, out] options: structure holds the input data
 * [returns] none
 */
static void free_options(GLOBAL_OPTIONS *options)
{
    /* If memory has not been allocated nothing is done */
    OPENSSL_free(options->cafile);
    OPENSSL_free(options->crlfile);
    OPENSSL_free(options->https_cafile);
    OPENSSL_free(options->https_crlfile);
    OPENSSL_free(options->tsa_cafile);
    OPENSSL_free(options->tsa_crlfile);
    if (options->pass) {
        /* reset password */
        memset(options->pass, 0, strlen(options->pass));
        OPENSSL_free(options->pass);
    }
    /* If key is NULL nothing is done */
    EVP_PKEY_free(options->pkey);
    options->pkey = NULL;
    /* Free up all elements of sk structure and sk itself */
    sk_X509_pop_free(options->certs, X509_free);
    options->certs = NULL;
    sk_X509_pop_free(options->xcerts, X509_free);
    options->xcerts = NULL;
    sk_X509_CRL_pop_free(options->crls, X509_CRL_free);
    options->crls = NULL;
#ifndef OPENSSL_NO_ENGINE
    sk_EngineControl_pop_free(options->engine_ctrls, EngineControl_free);
#endif /* OPENSSL_NO_ENGINE */
}

/*
 * [in] argv0, cmd
 * [returns] none
 */
static void usage(const char *argv0, const char *cmd)
{
    const char *cmds_all[] = {"all", NULL};
    const char *cmds_sign[] = {"all", "sign", NULL};
    const char *cmds_extract_data[] = {"all", "extract-data", NULL};
    const char *cmds_add[] = {"all", "add", NULL};
    const char *cmds_attach[] = {"all", "attach-signature", NULL};
    const char *cmds_extract[] = {"all", "extract-signature", NULL};
    const char *cmds_remove[] = {"all", "remove-signature", NULL};
    const char *cmds_verify[] = {"all", "verify", NULL};

    printf("\nUsage: %s", argv0);
    if (on_list(cmd, cmds_all)) {
        printf("\n\n%1s[ --version | -v ]\n", "");
        printf("%1s[ --help ]\n\n", "");
    }
    if (on_list(cmd, cmds_sign)) {
        printf("%1s[ sign ] -pkcs12 <file> | ( [ -certs <file|URI> | -spc <file> ]\n", "");
#if !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L
        printf("%12s( -key <file|URI> [ -pkcs11module <module> ] [ -pkcs11cert <pkcs11 cert URI> ] )\n", "");
#else /* !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L */
        printf("%12s-key <file|URI> )\n", "");
#endif /* !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L */
#if OPENSSL_VERSION_NUMBER>=0x30000000L
        printf("%12s[ -provider <provider> | ", "");
#else /* OPENSSL_VERSION_NUMBER>=0x30000000L */
#ifndef OPENSSL_NO_ENGINE
        printf("%12s[ ", "");
#endif /* OPENSSL_NO_ENGINE */
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
#ifndef OPENSSL_NO_ENGINE
        printf("%s( -engine <engine> [ -login ] [ -engineCtrl <command[:parameter]> ] ) ] )\n", "");
#endif /* OPENSSL_NO_ENGINE */
#if OPENSSL_VERSION_NUMBER>=0x30000000L
        printf("%12s[ -nolegacy ]\n", "");
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
        printf("%12s[ -pass <password>", "");
#ifdef PROVIDE_ASKPASS
        printf("%1s [ -askpass ]", "");
#endif /* PROVIDE_ASKPASS */
        printf("%1s[ -readpass <file> ]\n", "");
        printf("%12s(use \"-\" with readpass to read from stdin)\n", "");
        printf("%12s[ -ac <file> ]\n", "");
        printf("%12s[ -h {md5,sha1,sha2(56),sha384,sha512} ]\n", "");
        printf("%12s[ -n <desc> ] [ -i <url> ] [ -jp <level> ] [ -comm ]\n", "");
        printf("%12s[ -ph ]\n", "");
        printf("%12s[ -t <timestampurl> [ -t ... ] [ -p <proxy> ] [ -noverifypeer  ]\n", "");
        printf("%12s[ -ts <timestampurl> [ -ts ... ] [ -p <proxy> ] [ -noverifypeer ] ]\n", "");
        printf("%12s[ -TSA-certs <file> ] [ -TSA-key <file> ]\n", "");
        printf("%12s[ -TSA-time <unix-time> ]\n", "");
        printf("%12s[ -HTTPS-CAfile <file> ]\n", "");
        printf("%12s[ -HTTPS-CRLfile <file> ]\n", "");
        printf("%12s[ -time <unix-time> ]\n", "");
        printf("%12s[ -addUnauthenticatedBlob [ -blobFile <file> ] ]\n", "");
        printf("%12s[ -nest ]\n", "");
        printf("%12s[ -verbose ]\n", "");
        printf("%12s[ -add-msi-dse ]\n", "");
        printf("%12s[ -pem ]\n", "");
        printf("%12s[ -in ] <file> [-out ] <file>\n\n", "");
    }
    if (on_list(cmd, cmds_extract_data)) {
        printf("%1sextract-data [ -pem ]\n", "");
        printf("%12s[ -h {md5,sha1,sha2(56),sha384,sha512} ]\n", "");
        printf("%12s[ -ph ]\n", "");
        printf("%12s[ -add-msi-dse ]\n", "");
        printf("%12s[ -in ] <file> [ -out ] <file>\n\n", "");
    }
    if (on_list(cmd, cmds_add)) {
        printf("%1sadd [ -addUnauthenticatedBlob [ -blobFile <file> ] ]\n", "");
        printf("%12s[ -t <timestampurl> [ -t ... ] [ -p <proxy> ] [ -noverifypeer  ]\n", "");
        printf("%12s[ -ts <timestampurl> [ -ts ... ] [ -p <proxy> ] [ -noverifypeer ] ]\n", "");
        printf("%12s[ -TSA-certs <file> ] [ -TSA-key <file> ]\n", "");
        printf("%12s[ -TSA-time <unix-time> ]\n", "");
        printf("%12s[ -HTTPS-CAfile <file> ]\n", "");
        printf("%12s[ -HTTPS-CRLfile <file> ]\n", "");
        printf("%12s[ -h {md5,sha1,sha2(56),sha384,sha512} ]\n", "");
        printf("%12s[ -index <index> ]\n", "");
        printf("%12s[ -verbose ]\n", "");
        printf("%12s[ -add-msi-dse ]\n", "");
        printf("%12s[ -in ] <file> [ -out ] <file>\n\n", "");
    }
    if (on_list(cmd, cmds_attach)) {
        printf("%1sattach-signature [ -sigin ] <file>\n", "");
        printf("%12s[ -CAfile <file> ]\n", "");
        printf("%12s[ -CRLfile <file> ]\n", "");
        printf("%12s[ -TSA-CAfile <file> ]\n", "");
        printf("%12s[ -TSA-CRLfile <file> ]\n", "");
        printf("%12s[ -time <unix-time> ]\n", "");
        printf("%12s[ -h {md5,sha1,sha2(56),sha384,sha512} ]\n", "");
        printf("%12s[ -require-leaf-hash {md5,sha1,sha2(56),sha384,sha512}:XXXXXXXXXXXX... ]\n", "");
        printf("%12s[ -nest ]\n", "");
        printf("%12s[ -add-msi-dse ]\n", "");
        printf("%12s[ -in ] <file> [ -out ] <file>\n\n", "");
    }
    if (on_list(cmd, cmds_extract)) {
        printf("%1sextract-signature [ -pem ]\n", "");
        printf("%12s[ -in ] <file> [ -out ] <file>\n\n", "");
    }
    if (on_list(cmd, cmds_remove))
        printf("%1sremove-signature [ -in ] <file> [ -out ] <file>\n\n", "");
    if (on_list(cmd, cmds_verify)) {
        printf("%1sverify [ -in ] <file>\n", "");
        printf("%12s[ -c | -catalog <file> ]\n", "");
        printf("%12s[ -CAfile <file> ]\n", "");
        printf("%12s[ -CRLfile <file> ]\n", "");
        printf("%12s[ -HTTPS-CAfile <file> ]\n", "");
        printf("%12s[ -HTTPS-CRLfile <file> ]\n", "");
        printf("%12s[ -TSA-CAfile <file> ]\n", "");
        printf("%12s[ -TSA-CRLfile <file> ]\n", "");
        printf("%12s[ -p <proxy> ]\n", "");
        printf("%12s[ -index <index> ]\n", "");
        printf("%12s[ -ignore-timestamp ]\n", "");
        printf("%12s[ -ignore-cdp ]\n", "");
        printf("%12s[ -ignore-crl ]\n", "");
        printf("%12s[ -time <unix-time> ]\n", "");
        printf("%12s[ -require-leaf-hash {md5,sha1,sha2(56),sha384,sha512}:XXXXXXXXXXXX... ]\n", "");
        printf("%12s[ -verbose ]\n\n", "");
    }
}

/*
 * [in] argv0, cmd
 * [returns] none
 */
static void help_for(const char *argv0, const char *cmd)
{
    const char *cmds_all[] = {"all", NULL};
    const char *cmds_add[] = {"add", NULL};
    const char *cmds_attach[] = {"attach-signature", NULL};
    const char *cmds_extract[] = {"extract-signature", NULL};
    const char *cmds_remove[] = {"remove-signature", NULL};
    const char *cmds_sign[] = {"sign", NULL};
    const char *cmds_extract_data[] = {"extract-data", NULL};
    const char *cmds_verify[] = {"verify", NULL};
    const char *cmds_ac[] = {"sign", NULL};
    const char *cmds_add_msi_dse[] = {"add", "attach-signature", "sign", "extract-data", NULL};
    const char *cmds_addUnauthenticatedBlob[] = {"sign", "add", NULL};
#ifdef PROVIDE_ASKPASS
    const char *cmds_askpass[] = {"sign", NULL};
#endif /* PROVIDE_ASKPASS */
    const char *cmds_CAfile[] = {"attach-signature", "verify", NULL};
    const char *cmds_catalog[] = {"verify", NULL};
    const char *cmds_certs[] = {"sign", NULL};
    const char *cmds_comm[] = {"sign", NULL};
    const char *cmds_CRLfile[] = {"attach-signature", "verify", NULL};
    const char *cmds_CRLfileHTTPS[] = {"add", "sign", "verify", NULL};
    const char *cmds_CRLfileTSA[] = {"attach-signature", "verify", NULL};
    const char *cmds_h[] = {"add", "attach-signature", "sign", "extract-data", NULL};
    const char *cmds_i[] = {"sign", NULL};
    const char *cmds_in[] = {"add", "attach-signature", "extract-signature",
        "remove-signature", "sign", "extract-data", "verify", NULL};
    const char *cmds_index[] = {"add", "verify", NULL};
    const char *cmds_jp[] = {"sign", NULL};
    const char *cmds_key[] = {"sign", NULL};
#if OPENSSL_VERSION_NUMBER>=0x30000000L
    const char *cmds_nolegacy[] = {"sign", NULL};
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
    const char *cmds_n[] = {"sign", NULL};
    const char *cmds_nest[] = {"attach-signature", "sign", NULL};
    const char *cmds_noverifypeer[] = {"add", "sign", NULL};
    const char *cmds_out[] = {"add", "attach-signature", "extract-signature",
        "remove-signature", "sign", "extract-data", NULL};
    const char *cmds_p[] = {"add", "sign", "verify", NULL};
    const char *cmds_pass[] = {"sign", NULL};
    const char *cmds_pem[] = {"sign", "extract-data", "extract-signature", NULL};
    const char *cmds_ph[] = {"sign", "extract-data", NULL};
#if !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L
    const char *cmds_pkcs11cert[] = {"sign", NULL};
    const char *cmds_pkcs11module[] = {"sign", NULL};
#endif /* !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L */
#if OPENSSL_VERSION_NUMBER>=0x30000000L
    const char *cmds_provider[] = {"sign", NULL};
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
#ifndef OPENSSL_NO_ENGINE
    const char *cmds_engine[] = {"sign", NULL};
    const char *cmds_engineCtrl[] = {"sign", NULL};
    const char *cmds_login[] = {"sign", NULL};
#endif /* OPENSSL_NO_ENGINE */
    const char *cmds_pkcs12[] = {"sign", NULL};
    const char *cmds_readpass[] = {"sign", NULL};
    const char *cmds_require_leaf_hash[] = {"attach-signature", "verify", NULL};
    const char *cmds_sigin[] = {"attach-signature", NULL};
    const char *cmds_time[] = {"attach-signature", "sign", "verify", NULL};
    const char *cmds_ignore_timestamp[] = {"verify", NULL};
    const char *cmds_ignore_cdp[] = {"verify", NULL};
    const char *cmds_ignore_crl[] = {"verify", NULL};
    const char *cmds_t[] = {"add", "sign", NULL};
    const char *cmds_ts[] = {"add", "sign", NULL};
    const char *cmds_CAfileHTTPS[] = {"add", "sign", "verify", NULL};
    const char *cmds_CAfileTSA[] = {"attach-signature", "verify", NULL};
    const char *cmds_certsTSA[] = {"add", "sign", NULL};
    const char *cmds_keyTSA[] = {"add", "sign", NULL};
    const char *cmds_timeTSA[] = {"add", "sign", NULL};
    const char *cmds_verbose[] = {"add", "sign", "verify", NULL};

    if (on_list(cmd, cmds_all)) {
        printf("osslsigncode is a small tool that implements part of the functionality of the Microsoft\n");
        printf("tool signtool.exe - more exactly the Authenticode signing and timestamping.\n");
        printf("It can sign and timestamp PE (EXE/SYS/DLL/etc), CAB, CAT, MSI and APPX files,\n");
        printf("as well as script files with extensions .ps1, .ps1xml, .psc1, .psd1,\n");
        printf(".psm1, .cdxml, .mof and .js,\n");
        printf("supports getting the timestamp through a proxy as well.\n");
        printf("osslsigncode also supports signature verification, removal and extraction.\n\n");
        printf("%-22s = print osslsigncode version and usage\n", "--version | -v");
        printf("%-22s = print osslsigncode help menu\n\n", "--help");
        printf("Commands:\n");
        printf("%-22s = add an unauthenticated blob or a timestamp to a previously-signed file\n", "add");
        printf("%-22s = sign file using a given signature\n", "attach-signature");
        printf("%-22s = extract signature from a previously-signed file\n", "extract-signature");
        printf("%-22s = remove sections of the embedded signature on a file\n", "remove-signature");
        printf("%-22s = digitally sign a file\n", "sign");
        printf("%-22s = verifies the digital signature of a file\n\n", "verify");
        printf("For help on a specific command, enter %s <command> --help\n", argv0);
    }
    if (on_list(cmd, cmds_add)) {
        printf("\nUse the \"add\" command to add an unauthenticated blob or a timestamp to a previously-signed file.\n\n");
        printf("Options:\n");
    }
    if (on_list(cmd, cmds_attach)) {
        printf("\nUse the \"attach-signature\" command to attach the signature stored in the \"sigin\" file.\n");
        printf("In order to verify this signature you should specify how to find needed CA or TSA\n");
        printf("certificates, if appropriate.\n\n");
        printf("Options:\n");
    }
    if (on_list(cmd, cmds_extract)) {
        printf("\nUse the \"extract-signature\" command to extract the embedded signature from a previously-signed file.\n");
        printf("DER is the default format of the output file, but can be changed to PEM.\n\n");
        printf("Options:\n");
    }
    if (on_list(cmd, cmds_remove)) {
        printf("\nUse the \"remove-signature\" command to remove sections of the embedded signature on a file.\n\n");
        printf("Options:\n");
    }
    if (on_list(cmd, cmds_sign)) {
        printf("\nUse the \"sign\" command to sign files using embedded signatures.\n");
        printf("Signing  protects a file from tampering, and allows users to verify the signer\n");
        printf("based on a signing certificate. The options below allow you to specify signing\n");
        printf("parameters and to select the signing certificate you wish to use.\n\n");
        printf("Options:\n");
    }
    if (on_list(cmd, cmds_extract_data)) {
        printf("\nUse the \"extract-data\" command to extract a data content to be signed.\n\n");
        printf("Options:\n");
    }
    if (on_list(cmd, cmds_verify)) {
        printf("\nUse the \"verify\" command to verify embedded signatures.\n");
        printf("Verification determines if the signing certificate was issued by a trusted party,\n");
        printf("whether that certificate has been revoked, and whether the certificate is valid\n");
        printf("under a specific policy. Options allow you to specify requirements that must be met\n");
        printf("and to specify how to find needed CA or TSA certificates, if appropriate.\n\n");
        printf("Options:\n");
    }
    if (on_list(cmd, cmds_ac))
        printf("%-24s= additional certificates to be added to the signature block\n", "-ac");
    if (on_list(cmd, cmds_add_msi_dse))
        printf("%-24s= sign a MSI file with the add-msi-dse option\n", "-add-msi-dse");
    if (on_list(cmd, cmds_addUnauthenticatedBlob)) {
        printf("%-24s= add an unauthenticated blob to the PE/MSI file\n", "-addUnauthenticatedBlob");
        printf("%-24s= copy blob content from the specified file\n", "-blobFile");
    }
#ifdef PROVIDE_ASKPASS
    if (on_list(cmd, cmds_askpass))
        printf("%-24s= ask for the private key password\n", "-askpass");
#endif /* PROVIDE_ASKPASS */
    if (on_list(cmd, cmds_catalog))
        printf("%-24s= specifies the catalog file by name\n", "-c, -catalog");
    if (on_list(cmd, cmds_CAfile))
        printf("%-24s= the file containing one or more trusted certificates in PEM format\n", "-CAfile");
    if (on_list(cmd, cmds_certs))
        printf("%-24s= certificate chain (signing cert + intermediates)\n", "-certs, -spc");
    if (on_list(cmd, cmds_comm))
        printf("%-24s= set commercial purpose (default: individual purpose)\n", "-comm");
    if (on_list(cmd, cmds_CRLfile))
        printf("%-24s= the file containing one or more CRLs in PEM format\n", "-CRLfile");
    if (on_list(cmd, cmds_h)) {
        printf("%-24s= {md5|sha1|sha2(56)|sha384|sha512}\n", "-h");
        printf("%26sset of cryptographic hash functions\n", "");
    }
    if (on_list(cmd, cmds_i))
        printf("%-24s= specifies a URL for expanded description of the signed content\n", "-i");
    if (on_list(cmd, cmds_in))
        printf("%-24s= input file\n", "-in");
    if (on_list(cmd, cmds_index))
        printf("%-24s= use the signature at a certain position\n", "-index");
    if (on_list(cmd, cmds_jp)) {
        printf("%-24s= low | medium | high\n", "-jp");
        printf("%26slevels of permissions in Microsoft Internet Explorer 4.x for CAB files\n", "");
        printf("%26sonly \"low\" level is now supported\n", "");
    }
#if OPENSSL_VERSION_NUMBER>=0x30000000L
    if (on_list(cmd, cmds_nolegacy))
        printf("%-24s= disable legacy mode and don't automatically load the legacy provider\n", "-nolegacy");
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
    if (on_list(cmd, cmds_key))
        printf("%-24s= private key (optionally with signing cert) from file or URI\n", "-key");
    if (on_list(cmd, cmds_n))
        printf("%-24s= specifies a description of the signed content\n", "-n");
    if (on_list(cmd, cmds_nest))
        printf("%-24s= add the new nested signature instead of replacing the first one\n", "-nest");
    if (on_list(cmd, cmds_noverifypeer))
        printf("%-24s= do not verify the Time-Stamp Authority's SSL certificate\n", "-noverifypeer");
    if (on_list(cmd, cmds_out))
        printf("%-24s= output file\n", "-out");
    if (on_list(cmd, cmds_p))
        printf("%-24s= proxy to connect to the desired Time-Stamp Authority server or CRL distribution point\n", "-p");
    if (on_list(cmd, cmds_pass))
        printf("%-24s= the private key password\n", "-pass");
    if (on_list(cmd, cmds_pem))
        printf("%-24s= PKCS#7 output data format PEM to use (default: DER)\n", "-pem");
    if (on_list(cmd, cmds_ph))
        printf("%-24s= generate page hashes for executable files\n", "-ph");
#if !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L
    if (on_list(cmd, cmds_pkcs11cert))
        printf("%-24s= PKCS#11 URI identifies a certificate in the token\n", "-pkcs11cert");
    if (on_list(cmd, cmds_pkcs11module))
        printf("%-24s= PKCS#11 module\n", "-pkcs11module");
#endif /* !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L */
#if OPENSSL_VERSION_NUMBER>=0x30000000L
    if (on_list(cmd, cmds_provider))
        printf("%-24s= PKCS#11 provider\n", "-provider");
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
#ifndef OPENSSL_NO_ENGINE
    if (on_list(cmd, cmds_engine))
        printf("%-24s= PKCS#11 engine\n", "-engine");
    if (on_list(cmd, cmds_engineCtrl))
        printf("%-24s= control parameters for the PKCS#11 engine\n", "-engineCtrl");
    if (on_list(cmd, cmds_login))
        printf("%-24s= force login to the token for the PKCS#11 engine only\n", "-login");
#endif /* OPENSSL_NO_ENGINE */
    if (on_list(cmd, cmds_pkcs12))
        printf("%-24s= PKCS#12 container with the certificate and the private key\n", "-pkcs12");
    if (on_list(cmd, cmds_readpass))
        printf("%-24s= the private key password source\n", "-readpass");
    if (on_list(cmd, cmds_require_leaf_hash)) {
        printf("%-24s= {md5|sha1|sha2(56)|sha384|sha512}:XXXXXXXXXXXX...\n", "-require-leaf-hash");
        printf("%26sspecifies an optional hash algorithm to use when computing\n", "");
        printf("%26sthe leaf certificate (in DER form) hash and compares\n", "");
        printf("%26sthe provided hash against the computed hash\n", "");
    }
    if (on_list(cmd, cmds_sigin))
        printf("%-24s= a file containing the signature to be attached\n", "-sigin");
    if (on_list(cmd, cmds_ignore_timestamp))
        printf("%-24s= disable verification of the Timestamp Server signature\n", "-ignore-timestamp");
    if (on_list(cmd, cmds_ignore_cdp))
        printf("%-24s= disable fetching CRL Distribution Points\n", "-ignore-cdp");
    if (on_list(cmd, cmds_ignore_crl))
        printf("%-24s= disable fetching and verifying CRL Distribution Points\n", "-ignore-crl");
    if (on_list(cmd, cmds_t)) {
        printf("%-24s= specifies that the digital signature will be timestamped\n", "-t");
        printf("%26sby the Time-Stamp Authority (TSA) indicated by the URL\n", "");
        printf("%26sthis option cannot be used with the -ts option\n", "");
    }
    if (on_list(cmd, cmds_ts)) {
        printf("%-24s= specifies the URL of the RFC 3161 Time-Stamp Authority server\n", "-ts");
        printf("%26sthis option cannot be used with the -t option\n", "");
    }
    if (on_list(cmd, cmds_time))
        printf("%-24s= the unix-time to set the signing and/or verifying time\n", "-time");
    if (on_list(cmd, cmds_CAfileHTTPS))
        printf("%-24s= the file containing one or more HTTPS certificates in PEM format\n", "-HTTPS-CAfile");
    if (on_list(cmd, cmds_CRLfileHTTPS))
        printf("%-24s= the file containing one or more HTTPS CRLs in PEM format\n", "-HTTPS-CRLfile");
    if (on_list(cmd, cmds_CAfileTSA))
        printf("%-24s= the file containing one or more Time-Stamp Authority certificates in PEM format\n", "-TSA-CAfile");
    if (on_list(cmd, cmds_CRLfileTSA))
        printf("%-24s= the file containing one or more Time-Stamp Authority CRLs in PEM format\n", "-TSA-CRLfile");
    if (on_list(cmd, cmds_certsTSA))
        printf("%-24s= built-in Time-Stamp Authority signing certificate\n", "-TSA-certs");
    if (on_list(cmd, cmds_keyTSA))
        printf("%-24s= built-in Time-Stamp Authority private key or PKCS#11 URI identifies a key in the token\n", "-TSA-key");
    if (on_list(cmd, cmds_timeTSA))
        printf("%-24s= the unix-time to set the built-in Time-Stamp Authority signing\n", "-TSA-time");
    if (on_list(cmd, cmds_verbose))
        printf("%-24s= include additional output in the log\n", "-verbose");
    usage(argv0, cmd);
}

#ifdef PROVIDE_ASKPASS
/*
 * [in] prompt: "Password: "
 * [returns] password
 */
static char *getpassword(const char *prompt)
{
#ifdef HAVE_TERMIOS_H
    struct termios ofl, nfl;
    char *p, passbuf[1024], *pass;

    fputs(prompt, stdout);

    tcgetattr(fileno(stdin), &ofl);
    nfl = ofl;
    nfl.c_lflag &= ~(unsigned int)ECHO;
    nfl.c_lflag |= ECHONL;

    if (tcsetattr(fileno(stdin), TCSANOW, &nfl) != 0) {
        fprintf(stderr, "Failed to set terminal attributes\n");
        return NULL; /* FAILED */
    }
    p = fgets(passbuf, sizeof passbuf, stdin);
    if (tcsetattr(fileno(stdin), TCSANOW, &ofl) != 0)
        printf("Warning: Failed to restore terminal attributes\n");
    if (!p) {
        fprintf(stderr, "Failed to read password\n");
        return NULL; /* FAILED */
    }
    passbuf[strlen(passbuf)-1] = 0x00;
    pass = OPENSSL_strdup(passbuf);
    memset(passbuf, 0, sizeof passbuf);
    return pass;
#else /* HAVE_TERMIOS_H */
    return getpass(prompt);
#endif /* HAVE_TERMIOS_H */
}
#endif /* PROVIDE_ASKPASS */

/*
 * [in, out] options: structure holds the input data
 * [returns] 0 on error or 1 on success
 */
static int read_password(GLOBAL_OPTIONS *options)
{
    char passbuf[4096] = {0};
    int passlen;
    const u_char utf8_bom[] = {0xef, 0xbb, 0xbf};

    if (options->readpass) {
        if (!strcmp(options->readpass, "-")) {
            passlen = (int)read(fileno(stdin), passbuf, sizeof(passbuf)-1);
        } else {
#ifdef WIN32
            HANDLE fhandle, fmap;
            LPVOID faddress;
            fhandle = CreateFile(options->readpass, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
            if (fhandle == INVALID_HANDLE_VALUE) {
                return 0; /* FAILED */
            }
            fmap = CreateFileMapping(fhandle, NULL, PAGE_READONLY, 0, 0, NULL);
            if (fmap == NULL) {
                return 0; /* FAILED */
            }
            faddress = MapViewOfFile(fmap, FILE_MAP_READ, 0, 0, 0);
            CloseHandle(fmap);
            if (faddress == NULL) {
                return 0; /* FAILED */
            }
            passlen = (int)GetFileSize(fhandle, NULL);
            memcpy(passbuf, faddress, passlen);
            UnmapViewOfFile(faddress);
            CloseHandle(fhandle);
#else /* WIN32 */
            int passfd = open(options->readpass, O_RDONLY);
            if (passfd < 0) {
                return 0; /* FAILED */
            }
            passlen = (int)read(passfd, passbuf, sizeof passbuf - 1);
            close(passfd);
#endif /* WIN32 */
        }
        if (passlen <= 0) {
            return 0; /* FAILED */
        }
        while (passlen > 0 && (passbuf[passlen-1] == 0x0a || passbuf[passlen-1] == 0x0d)) {
            passlen--;
        }
        passbuf[passlen] = 0x00;
        if (!memcmp(passbuf, utf8_bom, sizeof utf8_bom)) {
            options->pass = OPENSSL_strdup(passbuf + sizeof utf8_bom);
        } else {
            options->pass = OPENSSL_strdup(passbuf);
        }
        memset(passbuf, 0, sizeof passbuf);
#ifdef PROVIDE_ASKPASS
    } else if (options->askpass) {
        options->pass = getpassword("Password: ");
#endif /* PROVIDE_ASKPASS */
    }
    return 1; /* OK */
}

/*
 * Obtain a copy of the whole X509_CRL chain
 * [in] chain: STACK_OF(X509_CRL) structure
 * [returns] pointer to STACK_OF(X509_CRL) structure
 */
static STACK_OF(X509_CRL) *X509_CRL_chain_up_ref(STACK_OF(X509_CRL) *chain)
{
    int i;
    STACK_OF(X509_CRL) *ret = sk_X509_CRL_dup(chain);

    if (ret == NULL)
        return NULL;

    for (i = 0; i < sk_X509_CRL_num(ret); i++) {
        X509_CRL *x = sk_X509_CRL_value(ret, i);

        if (!X509_CRL_up_ref(x))
            goto err;
    }
    return ret;
err:
    while (i-- > 0)
        X509_CRL_free(sk_X509_CRL_value(ret, i));
    sk_X509_CRL_free(ret);
    return NULL;
}

#if OPENSSL_VERSION_NUMBER<0x1010108f
/*
 * Load the private key from a file in DER format.
 * Workaround for OpenSSL 1.1.1g and older
 * [in, out] options: structure holds the input data
 * [returns] 0 on error or 1 on success
 */
static int read_der_keyfile(GLOBAL_OPTIONS *options)
{
    BIO *btmp = bio_new_file(options->keyfile, "rb");

    if (!btmp) {
        fprintf(stderr, "Failed to read private key file: %s\n", options->keyfile);
        return 0; /* FAILED */
    }
    options->pkey = d2i_PrivateKey_bio(btmp, NULL);
    BIO_free(btmp);
    if (!options->pkey) {
        fprintf(stderr, "Failed to decode private key file: %s\n", options->keyfile);
        return 0; /* FAILED */
    }
    return 1; /* OK */
}
#endif /* OPENSSL_VERSION_NUMBER<0x1010108f */

/*
 * Load certificates from .spc or .p7b certificate file (PKCS#7 structure)
 * If successful all certificates will be written to options->certs
 * and optional CRLs will be written to options->crls.
 * [in, out] options: structure holds the input data
 * [returns] 0 on error or 1 on success
 */
static int read_pkcs7_certfile(GLOBAL_OPTIONS *options)
{
    PKCS7 *p7;
    BIO *btmp = bio_new_file(options->certfile, "rb");

    if (!btmp) {
        fprintf(stderr, "Failed to read certificate from: %s\n",
#if !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER >= 0x30000000L
                options->certfile ? options->certfile : options->p11cert);
#else
                options->certfile);
#endif
        return 0; /* FAILED */
    }
    p7 = d2i_PKCS7_bio(btmp, NULL);
    if (!p7) {
        fprintf(stderr, "No certificate found\n");
        BIO_free(btmp);
        return 0; /* FAILED */
    }
    sk_X509_pop_free(options->certs, X509_free);
    options->certs = X509_chain_up_ref(p7->d.sign->cert);
    if (p7->d.sign->crl) {
        printf("Loading Certificate Revocation List: %s\n", options->certfile);
        sk_X509_CRL_pop_free(options->crls, X509_CRL_free);
        options->crls = X509_CRL_chain_up_ref(p7->d.sign->crl);
    }
    PKCS7_free(p7);
    BIO_free(btmp);
    return 1; /* OK */
}

#ifndef OPENSSL_NO_ENGINE

/*
 * Load an engine in a shareable library
 * [in] options: structure holds the input data
 * [returns] pointer to ENGINE
 */
static ENGINE *engine_dynamic(const char *path)
{
    ENGINE *engine;
    char *id;

    engine = ENGINE_by_id("dynamic");
    if (!engine) {
        fprintf(stderr, "Failed to load 'dynamic' engine\n");
        return NULL; /* FAILED */
    }
    if (path) { /* strip directory and extension */
        const char *ptr;
        char *dot;

        ptr = strrchr(path, '/');
        if (!ptr) /* no slash -> try backslash */
            ptr = strrchr(path, '\\');
        if (ptr) { /* directory separator found */
            ptr++; /* skip it */
            if (!strncmp(ptr, "lib", 3))
                ptr += 3; /* skip the "lib" prefix */
        } else /* directory separator not found */
            ptr = path;
        id = OPENSSL_strdup(ptr);
        dot = strchr(id, '.');
        if (dot) /* file extensions found */
            *dot = '\0'; /* remove them */
    } else {
        id = OPENSSL_strdup("pkcs11");
    }
    if (!ENGINE_ctrl_cmd_string(engine, "SO_PATH", path, 0)
            || !ENGINE_ctrl_cmd_string(engine, "ID", id, 0)
            || !ENGINE_ctrl_cmd_string(engine, "LIST_ADD", "1", 0)
            || !ENGINE_ctrl_cmd_string(engine, "LOAD", NULL, 0)) {
        fprintf(stderr, "Failed to set 'dynamic' engine\n");
        ENGINE_free(engine);
        engine = NULL; /* FAILED */
    }
    OPENSSL_free(id);
    return engine;
}

/*
 * Load the private key and the signer certificate from a security token
 * [in, out] options: structure holds the input data
 * [in] engine: ENGINE structure
 * [returns] 0 on error or 1 on success
 */
static int read_token(GLOBAL_OPTIONS *options, ENGINE *engine)
{
    int i;

    if (options->p11module && !ENGINE_ctrl_cmd_string(engine, "MODULE_PATH", options->p11module, 0)) {
        fprintf(stderr, "Failed to set pkcs11 engine MODULE_PATH to '%s'\n", options->p11module);
        ENGINE_free(engine);
        return 0; /* FAILED */
    }
    if (options->pass != NULL && !ENGINE_ctrl_cmd_string(engine, "PIN", options->pass, 0)) {
        fprintf(stderr, "Failed to set pkcs11 PIN\n");
        ENGINE_free(engine);
        return 0; /* FAILED */
    }
    if (!ENGINE_init(engine)) {
        fprintf(stderr, "Failed to initialize pkcs11 engine\n");
        ENGINE_free(engine);
        return 0; /* FAILED */
    }
    if (options->login && !ENGINE_ctrl_cmd_string(engine, "FORCE_LOGIN", 0, 0)) {
        fprintf(stderr, "Failed to force a login to the pkcs11 engine\n");
        ENGINE_free(engine);
        return 0; /* FAILED */
    }
    for (i = 0; i < sk_EngineControl_num(options->engine_ctrls); i++) {
        EngineControl *engine_ctrl = sk_EngineControl_value(options->engine_ctrls, i);
        const u_char *cmd = ASN1_STRING_get0_data(engine_ctrl->cmd);
        const u_char *param = ASN1_STRING_get0_data(engine_ctrl->param);

        if (param)
            printf("Executing engine control command %s:%s\n", cmd, param);
        else
            printf("Executing engine control command %s\n", cmd);

        if(!ENGINE_ctrl_cmd_string(engine, (const char *)cmd, (const char *)param, 0)) {
            fprintf(stderr, "Failed to execute the engine control command\n");
        }
    }
    /*
     * ENGINE_init() returned a functional reference, so free the structural
     * reference from ENGINE_by_id().
     */
    ENGINE_free(engine);

    if (options->p11cert) {
        struct {
            const char *id;
            X509 *cert;
        } params;

        params.id = options->p11cert;
        params.cert = NULL;
        ENGINE_ctrl_cmd(engine, "LOAD_CERT_CTRL", 0, &params, NULL, 1);
        if (!params.cert) {
            fprintf(stderr, "Failed to load certificate %s\n", options->p11cert);
            ENGINE_finish(engine);
            return 0; /* FAILED */
        } else
            sk_X509_push(options->certs, params.cert);
    }

    options->pkey = ENGINE_load_private_key(engine, options->keyfile, NULL, NULL);
    /* Free the functional reference from ENGINE_init */
    ENGINE_finish(engine);
    if (!options->pkey) {
        fprintf(stderr, "Failed to load private key %s\n", options->keyfile);
        return 0; /* FAILED */
    }
    return 1; /* OK */
}

static int engine_load(GLOBAL_OPTIONS *options)
{
    const char *id = options->p11engine ? options->p11engine : "pkcs11";
    ENGINE *engine;

    if (strchr(id, '.')) {
        /* Treat strings with a dot as paths to dynamic engine modules */
        engine = engine_dynamic(id);
    } else {
        /* Treat strings without a dot as engine IDs */
        engine = ENGINE_by_id(id);
        if (!engine)
            fprintf(stderr, "Failed to find and load '%s' engine\n", id);
    }
    if (!engine)
        return 0; /* FAILED */
    printf("Engine \"%s\" set.\n", ENGINE_get_id(engine));

    /* Load the private key and the signer certificate from the security token */
    if (!read_token(options, engine))
        return 0; /* FAILED */
    return 1; /* OK */
}

#endif /* OPENSSL_NO_ENGINE */

/*
 * Support for security token and various certificate and key file formats:
 * PEM / DER / SPC / P7B / PVK
 * .spc and .p7b files contain a PKCS#7 certificate structure;
 * .pvk is a Microsoft-specific binary format for RSA and DSA private keys,
 * it may be passphrase-protected and can have an associated .spc file.
 * [in, out] options: structure holds the input data
 * [returns] 0 on error or 1 on success
 */
static int read_crypto_params(GLOBAL_OPTIONS *options)
{
    options->certs = sk_X509_new_null();
    options->xcerts = sk_X509_new_null();
    options->crls = sk_X509_CRL_new_null();

    /* Try to use PKCS#12 container with certificates and the private key ('-pkcs12' option) */
    if (options->pkcs12file) {
        load_objects_from_store(options->pkcs12file, options->pass, &options->pkey, options->certs, options->crls);
    }
#if !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L
    /* Security token */
#ifndef OPENSSL_NO_ENGINE
    /* PKCS#11 'dynamic' engine */
    else if (options->p11engine) {
        if(!engine_load(options))
            goto out;
    }
#endif /* OPENSSL_NO_ENGINE */
    else if (options->p11module) {
#if OPENSSL_VERSION_NUMBER>=0x30000000L
        /* Try to load PKCS#11 provider first */
        if ((options->provider && provider_load(options->provider)) || provider_load("pkcs11prov")) {
            load_objects_from_store(options->keyfile, options->pass, &options->pkey, NULL, NULL);
            load_objects_from_store(options->p11cert, options->pass, NULL, options->certs, NULL);
        }
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_VERSION_NUMBER>=0x30000000L
        else
#endif /* !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L */
#ifndef OPENSSL_NO_ENGINE
            /* try to find and load libp11 'pkcs11' engine */
            if (!engine_load(options))
                goto out;
#endif /* OPENSSL_NO_ENGINE */
    }
#endif /* !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L */
    else {
#if OPENSSL_VERSION_NUMBER>=0x30000000L
        if (options->provider) {
            /* Attempt to load a provider without a PKCS#11 module (e.g., for the CNG provider) */
            (void)provider_load(options->provider);
        }
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
        /* Load the private key and the signing certificate ('-key' option) */
        load_objects_from_store(options->keyfile, options->pass, &options->pkey, options->certs, NULL);
    }
#if OPENSSL_VERSION_NUMBER<0x1010108f
    /* Workaround for OpenSSL 1.1.1g and older, where the store API does not
     * support loading private key in DER format. */
    if (!options->pkey && !read_der_keyfile(options)) {
        goto out;
    }
#endif /* OPENSSL_VERSION_NUMBER<0x1010108f */

    /* Load additional (cross) certificates ('-ac' option) */
    load_objects_from_store(options->xcertfile, options->pass, NULL, options->xcerts, NULL);

    /* Load the certificate chain ('-certs' option) */
    load_objects_from_store(options->certfile, options->pass, NULL, options->certs, NULL);

    /* OpenSSL store API does not support PKCS#7 format */
    if (sk_X509_num(options->certs) == 0 && !read_pkcs7_certfile(options)) {
        return 0; /* FAILED */
    }
#if !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER<0x1010108f
out:
#endif /* !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER<0x1010108f */
    return (options->pkey && sk_X509_num(options->certs) > 0) ? 1 : 0;
}

/*
 * [in] none
 * [returns] default CAfile
 */
static char *get_cafile(void)
{
#ifndef WIN32
    const char *files[] = {
        "/etc/ssl/certs/ca-certificates.crt",
        "/etc/pki/tls/certs/ca-bundle.crt",
        "/usr/share/ssl/certs/ca-bundle.crt",
        "/usr/local/share/certs/ca-root-nss.crt",
        "/etc/ssl/cert.pem",
        NULL
    };
    int i;

    for (i=0; files[i]; i++) {
        if (!access(files[i], R_OK)) {
            return OPENSSL_strdup(files[i]);
        }
    }
#endif /* WIN32 */
    return NULL;
}

static int ui_read(UI *ui, UI_STRING *uis)
{
    char *pass = (char *)UI_get0_user_data(ui);
    int (*reader)(UI *ui, UI_STRING *uis) = NULL;

    if (pass) {
        UI_set_result(ui, uis, pass);
        return 1;
    }
    if (!UI_OpenSSL()) {
        return 0;
    }
    reader = UI_method_get_reader(UI_OpenSSL());
    if (reader != NULL) {
        return reader(ui, uis);
    }
    /* Default to the empty password if we've got nothing better */
    UI_set_result(ui, uis, "");
    return 1;
}

static UI_METHOD *ui_osslsigncode(void) {
    static UI_METHOD *ui_method=NULL;

    if (ui_method) /* already initialized */
        return ui_method;
    ui_method = UI_create_method("osslsigncode UI");
    if (!ui_method) {
        return NULL;
    }
    UI_method_set_opener(ui_method, UI_method_get_opener(UI_OpenSSL()));
    UI_method_set_writer(ui_method, UI_method_get_writer(UI_OpenSSL()));
    UI_method_set_reader(ui_method, ui_read);
    UI_method_set_closer(ui_method, UI_method_get_closer(UI_OpenSSL()));
    return ui_method;
}

 /* store_type == 0 means here multiple types of credentials are to be loaded */
static void load_objects_from_store(const char *url, char *pass, EVP_PKEY **pkey, STACK_OF(X509) *certs, STACK_OF(X509_CRL) *crls) {
    OSSL_STORE_CTX *store_ctx;
    int type;

    if (!url)
        return;

    store_ctx = OSSL_STORE_open(url, ui_osslsigncode(), pass, NULL, NULL);
    if (!store_ctx)
        return;

    while (!OSSL_STORE_eof(store_ctx)) {
        OSSL_STORE_INFO *object = OSSL_STORE_load(store_ctx);

        if (!object)
            continue;

        type = OSSL_STORE_INFO_get_type(object);
        switch (type) {
        case OSSL_STORE_INFO_PKEY:
            if (pkey)
                *pkey = OSSL_STORE_INFO_get1_PKEY(object);
            break;
        case OSSL_STORE_INFO_CERT:
            if (certs)
                sk_X509_push(certs, OSSL_STORE_INFO_get1_CERT(object));
            break;
        case OSSL_STORE_INFO_CRL:
            if (crls)
                sk_X509_CRL_push(crls, OSSL_STORE_INFO_get1_CRL(object));
            break;
        default:
            break; /* skip any other type */
        }
        OSSL_STORE_INFO_free(object);
    }
    OSSL_STORE_close(store_ctx);
}

static void print_version(void)
{
    char *cafile = get_cafile();

#ifdef PACKAGE_STRING
    printf("%s, using:\n", PACKAGE_STRING);
#else /* PACKAGE_STRING */
    printf("%s, using:\n", "osslsigncode custom build");
#endif /* PACKAGE_STRING */
    printf("\t%s (Library: %s)\n", OPENSSL_VERSION_TEXT, OpenSSL_version(OPENSSL_VERSION));
#if OPENSSL_VERSION_NUMBER<0x30000000L
#ifdef ENABLE_CURL
    printf("\t%s\n", curl_version());
#else /* ENABLE_CURL */
    printf("\t%s\n", "no libcurl available");
#endif /* ENABLE_CURL */
#endif /* OPENSSL_VERSION_NUMBER<0x30000000L */
    if (cafile) {
        printf("Default -CAfile location: %s\n", cafile);
        OPENSSL_free(cafile);
    } else {
        printf("No default -CAfile location detected\n");
    }
#ifdef PACKAGE_BUGREPORT
    printf("\nPlease send bug-reports to " PACKAGE_BUGREPORT "\n");
#endif /* PACKAGE_BUGREPORT */
    printf("\n");
}

/*
 * [in] argv
 * [returns] cmd_type_t: command
 */
static cmd_type_t get_command(char **argv)
{
    if (!strcmp(argv[1], "--help")) {
        print_version();
        help_for(argv[0], "all");
        return CMD_HELP;
    } else if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) {
        print_version();
        return CMD_HELP;
    } else if (!strcmp(argv[1], "sign"))
        return CMD_SIGN;
    else if (!strcmp(argv[1], "extract-data"))
        return CMD_EXTRACT_DATA;
    else if (!strcmp(argv[1], "extract-signature"))
        return CMD_EXTRACT;
    else if (!strcmp(argv[1], "attach-signature"))
        return CMD_ATTACH;
    else if (!strcmp(argv[1], "remove-signature"))
        return CMD_REMOVE;
    else if (!strcmp(argv[1], "verify"))
        return CMD_VERIFY;
    else if (!strcmp(argv[1], "add"))
        return CMD_ADD;
    return CMD_DEFAULT;
}

#if OPENSSL_VERSION_NUMBER>=0x30000000L
static void provider_free(OSSL_PROVIDER *prov)
{
    OSSL_PROVIDER_unload(prov);
}

static void providers_cleanup(void)
{
    sk_OSSL_PROVIDER_pop_free(providers, provider_free);
    providers = NULL;
    UI_destroy_method(ui_osslsigncode());
}

static int provider_load(const char *pname)
{
    OSSL_PROVIDER *prov= OSSL_PROVIDER_load(NULL, pname);
    if (prov == NULL) {
        fprintf(stderr, "Unable to load provider: %s\n", pname);
        return 0; /* FAILED */
    }
    if (providers == NULL) {
        providers = sk_OSSL_PROVIDER_new_null();
    }
    if (providers == NULL || !sk_OSSL_PROVIDER_push(providers, prov)) {
        providers_cleanup();
        return 0; /* FAILED */
    }
    return 1; /* OK */
}

static int use_legacy(void)
{
    /* load the legacy provider if not loaded already */
    if (!OSSL_PROVIDER_available(NULL, "legacy")) {
        if (!provider_load("legacy"))
            return 0; /* FAILED */
        /* load the default provider explicitly */
        if (!provider_load("default"))
            return 0; /* FAILED */
    }
    return 1; /* OK */
}
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */

static int file_exists(const char *filename)
{
    if (filename) {
        FILE *file = fopen(filename, "rb");
        if (file) {
            fclose(file);
            return 1; /* File exists */
        }
    }
    return 0; /* File does not exist */
}

static BIO *bio_new_file(const char *filename, const char *mode)
{
    FILE *file;
    BIO *bio;

    if (!filename)
        return NULL;

    file = fopen(filename, mode);
    if (!file)
        return NULL;

    bio = BIO_new_fp(file, BIO_CLOSE);
    if (!bio) {
        fclose(file);
        return NULL;
    }
    return bio;
}

/*
 * [in] argc, argv
 * [in, out] options: structure holds the input data
 * [returns] 0 on error or 1 on success
 */
static int main_configure(int argc, char **argv, GLOBAL_OPTIONS *options)
{
    int i;
    char *failarg = NULL;
    const char *argv0;
    cmd_type_t cmd = CMD_SIGN;

    argv0 = argv[0];
    if (argc > 1) {
        cmd = get_command(argv);
        if (cmd == CMD_DEFAULT) {
            cmd = CMD_SIGN;
        } else {
            argv++;
            argc--;
        }
    }
    options->cmd = cmd;
    options->md = EVP_sha256();
    options->time = INVALID_TIME;
    options->jp = -1;
    options->index = -1;
    options->nested_number = -1;
#if OPENSSL_VERSION_NUMBER>=0x30000000L
/* Use legacy PKCS#12 container with RC2-40-CBC private key and certificate encryption algorithm */
    options->legacy = 1;
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
#ifndef OPENSSL_NO_ENGINE
    options->engine_ctrls = sk_EngineControl_new_null();
#endif /* OPENSSL_NO_ENGINE */

    if (cmd == CMD_HELP) {
        return 0; /* FAILED */
    }
    if (cmd == CMD_SIGN || cmd == CMD_VERIFY || cmd == CMD_ATTACH) {
        options->cafile = get_cafile();
        options->https_cafile = get_cafile();
        options->tsa_cafile = get_cafile();
    }
    for (argc--,argv++; argc >= 1; argc--,argv++) {
        if (!strcmp(*argv, "-in")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->infile = *(++argv);
        } else if (!strcmp(*argv, "-out")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->outfile = *(++argv);
        } else if (!strcmp(*argv, "-sigin")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->sigfile = *(++argv);
        } else if ((cmd == CMD_SIGN) && (!strcmp(*argv, "-spc") || !strcmp(*argv, "-certs"))) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->certfile = *(++argv);
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-ac")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->xcertfile = *(++argv);
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-key")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->keyfile = *(++argv);
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-pkcs12")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->pkcs12file = *(++argv);
        } else if ((cmd == CMD_SIGN || cmd == CMD_EXTRACT || cmd == CMD_EXTRACT_DATA)
                && !strcmp(*argv, "-pem")) {
            options->output_pkcs7 = 1;
#if !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-pkcs11cert")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->p11cert = *(++argv);
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-pkcs11module")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->p11module = *(++argv);
#if OPENSSL_VERSION_NUMBER>=0x30000000L
#ifdef _WIN32
            if (_putenv_s("PKCS11_MODULE_PATH", options->p11module))
#else
            if (setenv("PKCS11_MODULE_PATH", options->p11module, 1))
#endif
                return 0; /* FAILED */
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
#endif /* !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L */
#ifndef OPENSSL_NO_ENGINE
        } else if ((cmd == CMD_SIGN) && (!strcmp(*argv, "-engine") || !strcmp(*argv, "-pkcs11engine"))) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->p11engine = *(++argv);
        } else if (!strcmp(*argv, "-engineCtrl")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            engine_control_set(options, *(++argv));
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-login")) {
            options->login = 1;
#endif /* OPENSSL_NO_ENGINE */
#if OPENSSL_VERSION_NUMBER>=0x30000000L
        } else if (!strcmp(*argv, "-provider")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->provider = *(++argv);
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-nolegacy")) {
            options->legacy = 0;
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-pass")) {
            if (options->askpass || options->readpass) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->pass = OPENSSL_strdup(*(++argv));
            memset(*argv, 0, strlen(*argv));
#ifdef PROVIDE_ASKPASS
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-askpass")) {
            if (options->pass || options->readpass) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->askpass = 1;
#endif /* PROVIDE_ASKPASS */
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-readpass")) {
            if (options->askpass || options->pass) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->readpass = *(++argv);
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-comm")) {
            options->comm = 1;
        } else if ((cmd == CMD_SIGN || cmd == CMD_EXTRACT_DATA) && !strcmp(*argv, "-ph")) {
            options->pagehash = 1;
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-n")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->desc = *(++argv);
        } else if ((cmd == CMD_SIGN || cmd == CMD_ADD || cmd == CMD_ATTACH
                || cmd == CMD_EXTRACT_DATA) && !strcmp(*argv, "-h")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            ++argv;
            if (!strcmp(*argv, "md5")) {
                options->md = EVP_md5();
            } else if (!strcmp(*argv, "sha1")) {
                options->md = EVP_sha1();
            } else if (!strcmp(*argv, "sha2") || !strcmp(*argv, "sha256")) {
                options->md = EVP_sha256();
            } else if (!strcmp(*argv, "sha384")) {
                options->md = EVP_sha384();
            } else if (!strcmp(*argv, "sha512")) {
                options->md = EVP_sha512();
            } else {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-i")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->url = *(++argv);
        } else if ((cmd == CMD_ATTACH || cmd == CMD_SIGN || cmd == CMD_VERIFY)
                && (!strcmp(*argv, "-time") || !strcmp(*argv, "-st"))) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->time = (time_t)strtoull(*(++argv), NULL, 10);
        } else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-t")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->turl[options->nturl++] = *(++argv);
        } else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-ts")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->tsurl[options->ntsurl++] = *(++argv);
        } else if ((cmd == CMD_SIGN || cmd == CMD_ADD || cmd == CMD_VERIFY) && !strcmp(*argv, "-p")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->proxy = *(++argv);
        } else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-noverifypeer")) {
            options->noverifypeer = 1;
        } else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-addUnauthenticatedBlob")) {
            options->addBlob = 1;
        } else if (options->addBlob && !strcmp(*argv, "-blobFile")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->blob_file = *(++argv);
            if (!file_exists(options->blob_file)) {
                fprintf(stderr, "File does not exist: %s\n", options->blob_file);
                return 0; /* FAILED */
            }
        } else if ((cmd == CMD_SIGN || cmd == CMD_ATTACH) && !strcmp(*argv, "-nest")) {
            options->nest = 1;
        } else if ((cmd == CMD_ADD || cmd == CMD_VERIFY) && !strcmp(*argv, "-index")) {
            char *tmp_str;
            if (--argc < 1 ) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->index = (int)strtol(*(++argv), &tmp_str, 10);
            if (tmp_str == *argv ||  *tmp_str != '\0' || errno == ERANGE) { /* not a number */
                usage(argv0, "all");
                return 0; /* FAILED */
            }
        } else if ((cmd == CMD_VERIFY) && !strcmp(*argv, "-ignore-timestamp")) {
            options->ignore_timestamp = 1;
        } else if ((cmd == CMD_VERIFY) && !strcmp(*argv, "-ignore-cdp")) {
            options->ignore_cdp = 1;
        } else if ((cmd == CMD_VERIFY) && !strcmp(*argv, "-ignore-crl")) {
            options->ignore_crl = 1;
        } else if ((cmd == CMD_SIGN || cmd == CMD_ADD || cmd == CMD_VERIFY) && !strcmp(*argv, "-verbose")) {
            options->verbose = 1;
        } else if ((cmd == CMD_SIGN || cmd == CMD_EXTRACT_DATA || cmd == CMD_ADD || cmd == CMD_ATTACH)
                && !strcmp(*argv, "-add-msi-dse")) {
            options->add_msi_dse = 1;
        } else if ((cmd == CMD_VERIFY) && (!strcmp(*argv, "-c") || !strcmp(*argv, "-catalog"))) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->catalog = *(++argv);
        } else if ((cmd == CMD_VERIFY || cmd == CMD_ATTACH) && !strcmp(*argv, "-CAfile")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            OPENSSL_free(options->cafile);
            options->cafile = OPENSSL_strdup(*++argv);
        } else if ((cmd == CMD_VERIFY || cmd == CMD_ATTACH) && !strcmp(*argv, "-CRLfile")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->crlfile = OPENSSL_strdup(*++argv);
        } else if ((cmd == CMD_SIGN || cmd == CMD_ADD || cmd == CMD_VERIFY)
                && !strcmp(*argv, "-HTTPS-CAfile")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            OPENSSL_free(options->https_cafile);
            options->https_cafile = OPENSSL_strdup(*++argv);
        } else if ((cmd == CMD_SIGN || cmd == CMD_ADD || cmd == CMD_VERIFY)
                && !strcmp(*argv, "-HTTPS-CRLfile")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->https_crlfile = OPENSSL_strdup(*++argv);
        } else if ((cmd == CMD_VERIFY || cmd == CMD_ATTACH) && (!strcmp(*argv, "-untrusted") || !strcmp(*argv, "-TSA-CAfile"))) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            OPENSSL_free(options->tsa_cafile);
            options->tsa_cafile = OPENSSL_strdup(*++argv);
        } else if ((cmd == CMD_VERIFY || cmd == CMD_ATTACH) && (!strcmp(*argv, "-CRLuntrusted") || !strcmp(*argv, "-TSA-CRLfile"))) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->tsa_crlfile = OPENSSL_strdup(*++argv);
        } else if ((cmd == CMD_VERIFY || cmd == CMD_ATTACH) && !strcmp(*argv, "-require-leaf-hash")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->leafhash = (*++argv);
        } else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-TSA-certs")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->tsa_certfile = *(++argv);
        } else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-TSA-key")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->tsa_keyfile = *(++argv);
        } else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-TSA-time")) {
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            options->tsa_time = (time_t)strtoull(*(++argv), NULL, 10);
        } else if ((cmd == CMD_ADD) && !strcmp(*argv, "--help")) {
            help_for(argv0, "add");
            cmd = CMD_HELP;
            return 0; /* FAILED */
        } else if ((cmd == CMD_ATTACH) && !strcmp(*argv, "--help")) {
            help_for(argv0, "attach-signature");
            cmd = CMD_HELP;
            return 0; /* FAILED */
        } else if ((cmd == CMD_EXTRACT) && !strcmp(*argv, "--help")) {
            help_for(argv0, "extract-signature");
            cmd = CMD_HELP;
            return 0; /* FAILED */
        } else if ((cmd == CMD_REMOVE) && !strcmp(*argv, "--help")) {
            help_for(argv0, "remove-signature");
            cmd = CMD_HELP;
            return 0; /* FAILED */
        } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "--help")) {
            help_for(argv0, "sign");
            cmd = CMD_HELP;
            return 0; /* FAILED */
        } else if ((cmd == CMD_EXTRACT_DATA) && !strcmp(*argv, "--help")) {
            help_for(argv0, "extract-data");
            cmd = CMD_HELP;
            return 0; /* FAILED */
        } else if ((cmd == CMD_VERIFY) && !strcmp(*argv, "--help")) {
            help_for(argv0, "verify");
            cmd = CMD_HELP;
            return 0; /* FAILED */
        } else if (!strcmp(*argv, "-jp")) {
            char *ap;
            if (--argc < 1) {
                usage(argv0, "all");
                return 0; /* FAILED */
            }
            ap = *(++argv);
            for (i=0; ap[i]; i++) ap[i] = (char)tolower((int)ap[i]);
            if (!strcmp(ap, "low")) {
                options->jp = 0;
            } else if (!strcmp(ap, "medium")) {
                options->jp = 1;
            } else if (!strcmp(ap, "high")) {
                options->jp = 2;
            }
            if (options->jp != 0) { /* XXX */
                usage(argv0, "all");
                return 0; /* FAILED */
            }
        } else {
            failarg = *argv;
            break;
        }
    }
    if (!options->infile && argc > 0) {
        options->infile = *(argv++);
        argc--;
    }
    if (cmd != CMD_VERIFY && (!options->outfile && argc > 0)) {
        if (!strcmp(*argv, "-out")) {
            argv++;
            argc--;
        }
        if (argc > 0) {
            options->outfile = *(argv++);
            argc--;
        }
    }
    if (cmd != CMD_VERIFY && file_exists(options->outfile)) {
        fprintf(stderr, "Overwriting an existing file is not supported.\n");
        return 0; /* FAILED */
    }
    if (argc > 0 ||
        (options->nturl && options->ntsurl) ||
        (options->nturl && options->tsa_certfile && options->tsa_keyfile) ||
        (options->ntsurl && options->tsa_certfile && options->tsa_keyfile) ||
        !options->infile ||
        (cmd != CMD_VERIFY && !options->outfile) ||
        (cmd == CMD_SIGN && !((options->certfile && options->keyfile) ||
#if OPENSSL_VERSION_NUMBER>=0x30000000L
            options->provider ||
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
#ifndef OPENSSL_NO_ENGINE
            options->p11engine ||
#endif /* OPENSSL_NO_ENGINE */
#if !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L
            options->p11module ||
#endif /* !defined(OPENSSL_NO_ENGINE) || OPENSSL_VERSION_NUMBER>=0x30000000L */
            options->pkcs12file))) {
        if (failarg)
            fprintf(stderr, "Unknown option: %s\n", failarg);
        usage(argv0, "all");
        return 0; /* FAILED */
    }
#ifndef WIN32
    if ((cmd == CMD_VERIFY || cmd == CMD_ATTACH) && access(options->cafile, R_OK)) {
        printf("Use the \"-CAfile\" option to add one or more trusted CA certificates to verify the signature.\n");
        return 0; /* FAILED */
    }
#endif /* WIN32 */
#if OPENSSL_VERSION_NUMBER>=0x30000000L
    if (cmd == CMD_SIGN && options->legacy && !use_legacy()) {
        printf("Warning: Legacy mode disabled\n");
    }
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
    return 1; /* OK */
}

#ifndef OPENSSL_NO_ENGINE
static void engine_control_set(GLOBAL_OPTIONS *options, const char *arg)
{
    EngineControl *engine_ctrl = EngineControl_new();
    char *tmp_str=strchr(arg, ':');

    if(tmp_str) {
        *tmp_str++='\0';
        ASN1_STRING_set(engine_ctrl->param, tmp_str, (int)strlen(tmp_str));
    }
    ASN1_STRING_set(engine_ctrl->cmd, arg, (int)strlen(arg));
    sk_EngineControl_push(options->engine_ctrls, engine_ctrl);
}
#endif /* OPENSSL_NO_ENGINE */

static int main_execute(int argc, char **argv)
{
    FILE_FORMAT_CTX *ctx = NULL;
    GLOBAL_OPTIONS options;
    PKCS7 *p7 = NULL, *cursig = NULL;
    BIO *outdata = NULL;
    BIO *hash = NULL;
    int ret = -1;

    /* reset options */
    memset(&options, 0, sizeof(GLOBAL_OPTIONS));

    /* commands and options initialization */
    if (!main_configure(argc, argv, &options))
        goto err_cleanup;
    if (!read_password(&options)) {
        DO_EXIT_1("Failed to read password from file: %s\n", options.readpass);
    }

    /* read key and certificates */
    if (options.cmd == CMD_SIGN && !read_crypto_params(&options))
        DO_EXIT_0("Failed to read key or certificates\n");

    if (options.cmd != CMD_VERIFY) {
        /* Create message digest BIO */
        hash = BIO_new(BIO_f_md());
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
        if (!BIO_set_md(hash, options.md)) {
            DO_EXIT_0("Unable to set the message digest of BIO\n");
        }
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
        /* Create output file — file existence already verified via file_exists() */
        outdata = bio_new_file(options.outfile, "w+b");
        if (!outdata) {
            BIO_free_all(hash);
            DO_EXIT_1("Failed to create file: %s\n", options.outfile);
         }
    }
    ctx = file_format_script.ctx_new(&options, hash, outdata);
    if (!ctx)
        ctx = file_format_msi.ctx_new(&options, hash, outdata);
    if (!ctx)
        ctx = file_format_pe.ctx_new(&options, hash, outdata);
    if (!ctx)
        ctx = file_format_cab.ctx_new(&options, hash, outdata);
    if (!ctx)
        ctx = file_format_appx.ctx_new(&options, hash, outdata);
    if (!ctx)
        ctx = file_format_cat.ctx_new(&options, hash, outdata);
    if (!ctx) {
        if (outdata && options.outfile) {
            /* unlink outfile */
            remove_file(options.outfile);
        }
        BIO_free_all(hash);
        BIO_free_all(outdata);
        outdata = NULL;
        ret = 1; /* FAILED */
        DO_EXIT_0("Initialization error or unsupported input file type.\n");
    }
    if (options.cmd == CMD_VERIFY) {
        ret = verify_signed_file(ctx, &options);
        goto skip_signing;
    } else if (options.cmd == CMD_EXTRACT_DATA) {
        if (!ctx->format->pkcs7_contents_get) {
            DO_EXIT_0("Unsupported command: extract-data\n");
        }
        p7 = ctx->format->pkcs7_contents_get(ctx, hash, options.md);
        if (!p7) {
            DO_EXIT_0("Unable to extract pkcs7 contents\n");
        }
        ret = data_write_pkcs7(ctx, outdata, p7);
        PKCS7_free(p7);
        goto skip_signing;
    } else if (options.cmd == CMD_EXTRACT) {
        if (!ctx->format->pkcs7_extract) {
            DO_EXIT_0("Unsupported command: extract-signature\n");
        }
        p7 = ctx->format->pkcs7_extract(ctx);
        if (!p7) {
            DO_EXIT_0("Unable to extract existing signature\n");
        }
        ret = data_write_pkcs7(ctx, outdata, p7);
        PKCS7_free(p7);
        goto skip_signing;
    } else if (options.cmd == CMD_REMOVE) {
        if (!ctx->format->remove_pkcs7) {
            DO_EXIT_0("Unsupported command: remove-signature\n");
        }
        ret = ctx->format->remove_pkcs7(ctx, hash, outdata);
        if (ret) {
            DO_EXIT_0("Unable to remove existing signature\n");
        }
        if (ctx->format->update_data_size) {
            ctx->format->update_data_size(ctx, outdata, NULL);
        }
        goto skip_signing;
    } else if (options.cmd == CMD_ADD) {
        if (!ctx->format->pkcs7_extract) {
            DO_EXIT_0("Unsupported command: add\n");
        }
        /* Obtain a current signature from previously-signed file */
        p7 = ctx->format->pkcs7_extract(ctx);
        if (!p7) {
            DO_EXIT_0("Unable to extract existing signature\n");
        }
        if (ctx->format->process_data) {
            if (!ctx->format->process_data(ctx, hash, outdata)) {
                DO_EXIT_0("Unable to read input file\n");
            }
        }
    } else if (options.cmd == CMD_ATTACH) {
        if (options.nest) {
            if (!ctx->format->pkcs7_extract_to_nest) {
                printf("Warning: Unsupported nesting (multiple signature)\n");
            } else {
                /* Obtain a current signature from previously-signed file */
                cursig = ctx->format->pkcs7_extract_to_nest(ctx);
                if (!cursig) {
                    DO_EXIT_0("Unable to extract existing signature\n");
                }
                options.nested_number = nested_signatures_number_get(cursig);
                if (options.nested_number < 0) {
                    PKCS7_free(cursig);
                    DO_EXIT_0("Unable to get number of nested signatures\n");
                }
            }
        }
        /* Obtain an existing PKCS#7 signature from a "sigin" file */
        p7 = pkcs7_get_sigfile(ctx);
        if (!p7) {
            PKCS7_free(cursig);
            DO_EXIT_0("Unable to extract valid signature\n");
        }
        if (ctx->format->process_data) {
            if (!ctx->format->process_data(ctx, hash, outdata)) {
                DO_EXIT_0("Unable to read input file\n");
            }
        }
    } else if (options.cmd == CMD_SIGN) {
        if (options.nest) {
            if (!ctx->format->pkcs7_extract_to_nest) {
                printf("Warning: Unsupported nesting (multiple signature)\n");
            } else {
                /* Obtain a current signature from previously-signed file */
                cursig = ctx->format->pkcs7_extract_to_nest(ctx);
                if (!cursig) {
                    DO_EXIT_0("Unable to extract existing signature\n");
                }
                options.nested_number = nested_signatures_number_get(cursig);
                if (options.nested_number < 0) {
                    PKCS7_free(cursig);
                    DO_EXIT_0("Unable to get number of nested signatures\n");
                }
            }
        }
        if (ctx->format->process_data) {
            if (!ctx->format->process_data(ctx, hash, outdata)) {
                DO_EXIT_0("Unable to read input file\n");
            }
        }
        if (ctx->format->pkcs7_signature_new) {
            /* Create a new PKCS#7 signature */
            p7 = ctx->format->pkcs7_signature_new(ctx, hash);
            if (!p7) {
                DO_EXIT_0("Unable to prepare new signature\n");
            }
        }
    } else {
        DO_EXIT_0("Unsupported command\n");
    }
    if (options.index > 0) {
        /* CMD_ADD or CMD_VERIFY */
        ret = add_nested_timestamp_and_blob(p7, ctx, options.index);
    } else {
        ret = add_timestamp_and_blob(p7, ctx);
    }
    if (ret) {
        PKCS7_free(p7);
        DO_EXIT_0("Unable to set unauthenticated attributes\n");
    }
    if (cursig) {
        /* CMD_SIGN or CMD_ATTACH */
        if (!cursig_set_nested(cursig, p7))
            DO_EXIT_0("Unable to append the nested signature to the current signature\n");
        PKCS7_free(p7);
        p7 = cursig;
        cursig = NULL;
    }
    if (ctx->format->append_pkcs7) {
        ret = ctx->format->append_pkcs7(ctx, outdata, p7);
        if (ret) {
            PKCS7_free(p7);
            DO_EXIT_0("Append signature to outfile failed\n");
        }
    }
    if (ctx->format->update_data_size) {
        ctx->format->update_data_size(ctx, outdata, p7);
    }
    PKCS7_free(p7);

skip_signing:
    if (ctx->format->bio_free) {
        ctx->format->bio_free(hash, outdata);
        outdata = NULL;
    }
    if (!ret && options.cmd == CMD_ATTACH) {
        ret = check_attached_data(&options);
        if (!ret)
            printf("Signature successfully attached\n");
        /* else
         * the new PKCS#7 signature has been successfully appended to the outfile
         * but only its verification failed (incorrect verification parameters?)
         * so the output file is not deleted
         */
    }

err_cleanup:
    if (outdata) {
        BIO *head = hash;
        int outdata_in_hash = 0;

        while (head) {
            BIO *tail = BIO_pop(head);

            if (head == outdata)
                outdata_in_hash = 1;
            BIO_free(head);
            head = tail;
        }
        if (!outdata_in_hash)
            BIO_free_all(outdata);

        if (options.outfile) {
            /* unlink outfile */
            remove_file(options.outfile);
        }
    }
    if (ctx && ctx->format->ctx_cleanup) {
        ctx->format->ctx_cleanup(ctx);
    }
#if OPENSSL_VERSION_NUMBER>=0x30000000L
    providers_cleanup();
#endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
    if (ret)
        ERR_print_errors_fp(stderr);
    if (options.cmd == CMD_HELP)
        ret = 0; /* OK */
    else
        printf(ret ? "Failed\n" : "Succeeded\n");
    free_options(&options);
    return ret;
}

int main(int argc, char **argv)
{
    int ret = -1;

    /* one-time OpenSSL initialization */
    if (!OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS
        | OPENSSL_INIT_ADD_ALL_CIPHERS
        | OPENSSL_INIT_ADD_ALL_DIGESTS
        | OPENSSL_INIT_LOAD_CONFIG, NULL))
        DO_EXIT_0("Failed to init crypto\n");

    /* create some MS Authenticode OIDS we need later on */
    if (!OBJ_create(SPC_STATEMENT_TYPE_OBJID, NULL, NULL)
        /* PKCS9_COUNTER_SIGNATURE exists as OpenSSL OBJ_pkcs9_countersignature */
        || !OBJ_create(MS_JAVA_SOMETHING, NULL, NULL)
        || !OBJ_create(SPC_SP_OPUS_INFO_OBJID, NULL, NULL)
        || !OBJ_create(SPC_NESTED_SIGNATURE_OBJID, NULL, NULL)
        || !OBJ_create(SPC_UNAUTHENTICATED_DATA_BLOB_OBJID, NULL, NULL)
        || !OBJ_create(SPC_RFC3161_OBJID, NULL, NULL)
        || !OBJ_create(PKCS9_SEQUENCE_NUMBER, NULL, NULL))
        DO_EXIT_0("Failed to create objects\n");

    /* perform the requested operation */
    ret = main_execute(argc, argv);

err_cleanup:
    return ret;
}

/*
Local Variables:
   c-basic-offset: 4
   tab-width: 4
   indent-tabs-mode: nil
End:

  vim: set ts=4 expandtab:
*/
