From c04b229ce288e9510ea36883244f0cbf1fd72af9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ma=C5=82gorzata=20Olsz=C3=B3wka=20=28she/her=29?=
 <Malgorzata.Olszowka@stunnel.org>
Date: Fri, 28 Jul 2023 16:03:04 +0200
Subject: [PATCH] Built-in TSA response generation (#281)

---
 NEWS.md        |   3 +
 osslsigncode.c | 383 ++++++++++++++++++++++++++++++++++++++++---------
 osslsigncode.h |   8 ++
 3 files changed, 325 insertions(+), 69 deletions(-)

diff --git a/NEWS.md b/NEWS.md
index 08327ca..c6f0b05 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -2,6 +2,9 @@
 
 ### 2.7 (unreleased)
 
+- added a built-in TSA response generation (-TSA-certs, -TSA-key
+  and -TSA-time options)
+
 ### 2.6 (2023.05.29)
 
 - modular architecture implemented to simplify adding file formats
diff --git a/osslsigncode.c b/osslsigncode.c
index a00beca..c845328 100644
--- a/osslsigncode.c
+++ b/osslsigncode.c
@@ -183,21 +183,6 @@ IMPLEMENT_ASN1_FUNCTIONS(TimeStampRequest)
 
 /* RFC3161 Time stamping */
 
-ASN1_SEQUENCE(PKIStatusInfo) = {
-    ASN1_SIMPLE(PKIStatusInfo, status, ASN1_INTEGER),
-    ASN1_SEQUENCE_OF_OPT(PKIStatusInfo, statusString, ASN1_UTF8STRING),
-    ASN1_OPT(PKIStatusInfo, failInfo, ASN1_BIT_STRING)
-} ASN1_SEQUENCE_END(PKIStatusInfo)
-
-IMPLEMENT_ASN1_FUNCTIONS(PKIStatusInfo)
-
-ASN1_SEQUENCE(TimeStampResp) = {
-    ASN1_SIMPLE(TimeStampResp, status, PKIStatusInfo),
-    ASN1_OPT(TimeStampResp, token, PKCS7)
-} ASN1_SEQUENCE_END(TimeStampResp)
-
-IMPLEMENT_ASN1_FUNCTIONS(TimeStampResp)
-
 ASN1_SEQUENCE(TimeStampReq) = {
     ASN1_SIMPLE(TimeStampReq, version, ASN1_INTEGER),
     ASN1_SIMPLE(TimeStampReq, messageImprint, MessageImprint),
@@ -406,19 +391,19 @@ static BIO *bio_encode_authenticode_request(PKCS7 *p7)
 }
 
 /*
- * Decode a RFC 3161 response from BIO.
  * If successful the RFC 3161 timestamp will be written into
  * the PKCS7 SignerInfo structure as an unauthorized attribute - cont[1].
  * [in, out] p7: new PKCS#7 signature
- * [in] bin: BIO with http data
+ * [in] response: RFC3161 response
  * [in] verbose: additional output mode
  * [returns] 1 on error or 0 on success
  */
-static int decode_rfc3161_response(PKCS7 *p7, BIO *bin, int verbose)
+static int attach_rfc3161_response(PKCS7 *p7, TS_RESP *response, int verbose)
 {
     PKCS7_SIGNER_INFO *si;
     STACK_OF(X509_ATTRIBUTE) *attrs;
-    TimeStampResp *reply;
+    TS_STATUS_INFO *status;
+    PKCS7 *token;
     u_char *p;
     int i, len;
     STACK_OF(PKCS7_SIGNER_INFO) *signer_info = PKCS7_get_signer_info(p7);
@@ -428,32 +413,31 @@ static int decode_rfc3161_response(PKCS7 *p7, BIO *bin, int verbose)
     si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
     if (!si)
         return 1; /* FAILED */
-
-    reply = ASN1_item_d2i_bio(ASN1_ITEM_rptr(TimeStampResp), bin, NULL);
-    if (!reply || !reply->status)
+    if (!response)
         return 1; /* FAILED */
-    if (ASN1_INTEGER_get(reply->status->status) != 0) {
+
+    status = TS_RESP_get_status_info(response);
+    if (ASN1_INTEGER_get(TS_STATUS_INFO_get0_status(status)) != 0) {
         if (verbose) {
-            printf("Timestamping failed: status %ld\n", ASN1_INTEGER_get(reply->status->status));
-            for (i = 0; i < sk_ASN1_UTF8STRING_num(reply->status->statusString); i++) {
-                ASN1_UTF8STRING *status = sk_ASN1_UTF8STRING_value(reply->status->statusString, i);
-                printf("%s\n", ASN1_STRING_get0_data(status));
+            const STACK_OF(ASN1_UTF8STRING) *reasons = TS_STATUS_INFO_get0_text(status);
+            printf("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);
+                printf("%s\n", ASN1_STRING_get0_data(reason));
             }
         }
-        TimeStampResp_free(reply);
         return 1; /* FAILED */
     }
-    if (((len = i2d_PKCS7(reply->token, NULL)) <= 0) || (p = OPENSSL_malloc((size_t)len)) == NULL) {
+    token = TS_RESP_get_token(response);
+    if (((len = i2d_PKCS7(token, NULL)) <= 0) || (p = OPENSSL_malloc((size_t)len)) == NULL) {
         if (verbose) {
             printf("Failed to convert pkcs7: %d\n", len);
             ERR_print_errors_fp(stdout);
         }
-        TimeStampResp_free(reply);
         return 1; /* FAILED */
     }
-    len = i2d_PKCS7(reply->token, &p);
+    len = i2d_PKCS7(token, &p);
     p -= len;
-    TimeStampResp_free(reply);
 
     attrs = sk_X509_ATTRIBUTE_new_null();
     attrs = X509at_add1_attr_by_txt(&attrs, SPC_RFC3161_OBJID, V_ASN1_SET, p, len);
@@ -465,26 +449,22 @@ static int decode_rfc3161_response(PKCS7 *p7, BIO *bin, int verbose)
 }
 
 /*
- * Decode an authenticode response from BIO.
  * If successful the authenticode timestamp will be written into
  * the PKCS7 SignerInfo structure as an unauthorized attribute - cont[1]:
  * p7->d.sign->signer_info->unauth_attr
  * [in, out] p7: new PKCS#7 signature
- * [in] bin: base64 BIO with http data
+ * [in] resp: PKCS#7 authenticode response
  * [in] verbose: additional output mode
  * [returns] 1 on error or 0 on success
  */
-static int decode_authenticode_response(PKCS7 *p7, BIO *b64_bin, int verbose)
+static int attach_authenticode_response(PKCS7 *p7, PKCS7 *resp, int verbose)
 {
-    PKCS7 *resp;
     PKCS7_SIGNER_INFO *info, *si;
     STACK_OF(X509_ATTRIBUTE) *attrs;
-
     u_char *p;
     int len, i;
     STACK_OF(PKCS7_SIGNER_INFO) *signer_info;
 
-    resp = d2i_PKCS7_bio(b64_bin, NULL);
     if (!resp) {
         return 1; /* FAILED */
     }
@@ -689,15 +669,25 @@ static int add_timestamp(PKCS7 *p7, FILE_FORMAT_CTX *ctx, char *url, int rfc3161
 
     if (bin) {
         if (rfc3161) {
-            res = decode_rfc3161_response(p7, bin, verbose);
+            /* decode a RFC 3161 response from BIO */
+            TS_RESP *response = d2i_TS_RESP_bio(bin, NULL);
             BIO_free_all(bin);
+
+            res = attach_rfc3161_response(p7, response, verbose);
+            TS_RESP_free(response);
         } else {
-            BIO *b64 = BIO_new(BIO_f_base64());
+            /* decode an authenticode response from BIO */
+            PKCS7 *response;
+            BIO *b64, *b64_bin;
+
+            b64 = BIO_new(BIO_f_base64());
             if (!blob_has_nl)
                 BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
-            BIO *b64_bin = BIO_push(b64, bin);
-            res = decode_authenticode_response(p7, b64_bin, verbose);
+            b64_bin = BIO_push(b64, bin);
+            response = d2i_PKCS7_bio(b64_bin, NULL);
             BIO_free_all(b64_bin);
+
+            res = attach_authenticode_response(p7, response, verbose);
         }
         if (res && verbose) {
             if (http_code != -1) {
@@ -744,6 +734,236 @@ static int add_timestamp_rfc3161(PKCS7 *p7, FILE_FORMAT_CTX *ctx)
 }
 #endif /* ENABLE_CURL */
 
+/*
+ * [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) {
+        printf("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;
+    }
+    /* 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 TimeStampToken */
+    response = TS_RESP_create_response(resp_ctx, bout);
+    if (!response) {
+        printf("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) {
+        printf("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) {
+        printf("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) {
+        printf("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) {
+        printf("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) {
+        printf("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)) {
+        printf("Unsupported Signer's certificate purpose XKU_TIMESTAMP\n");
+        goto out;
+    }
+    /* encode timestamp request */
+    bout = bio_encode_rfc3161_request(p7, ctx->options->md);
+    if (!bout) {
+        printf("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) {
+            printf("Failed to convert timestamp reply\n");
+            ERR_print_errors_fp(stdout);
+        }
+    } else {
+        printf("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;
+}
+
 /*
  * If successful the unauthenticated blob will be written into
  * the PKCS7 SignerInfo structure as an unauthorized attribute - cont[1]:
@@ -801,6 +1021,10 @@ static int add_timestamp_and_blob(PKCS7 *p7, FILE_FORMAT_CTX *ctx)
             "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)) {
+        printf("Built-in timestamping failed\n");
+        return 1; /* FAILED */
+    }
 #endif /* ENABLE_CURL */
     if (ctx->options->addBlob && !add_unauthenticated_blob(p7)) {
         printf("Adding unauthenticated blob failed\n");
@@ -1224,6 +1448,18 @@ static STACK_OF(X509_CRL) *x509_crl_list_get(PKCS7 *p7, X509_CRL *crl)
     return crls;
 }
 
+static void print_timestamp_serial_number(const ASN1_INTEGER *serial)
+{
+    BIGNUM *serialbn;
+    char *number;
+
+    serialbn = ASN1_INTEGER_to_BN(serial, 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
@@ -1256,6 +1492,7 @@ static int verify_timestamp_token(PKCS7 *p7, CMS_ContentInfo *timestamp)
         p = (*pos)->data;
         token = d2i_TimeStampToken(NULL, &p, (*pos)->length);
         if (token) {
+            print_timestamp_serial_number(token->serial);
             /* compute a hash from the encrypted message digest value of the file */
             md_nid = OBJ_obj2nid(token->messageImprint->digestAlgorithm->algorithm);
             md = EVP_get_digestbynid(md_nid);
@@ -1269,7 +1506,6 @@ static int verify_timestamp_token(PKCS7 *p7, CMS_ContentInfo *timestamp)
             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 = token->messageImprint->digest;
             /* hash->length == EVP_MD_size(md) */
@@ -1353,8 +1589,7 @@ static int verify_timestamp(FILE_FORMAT_CTX *ctx, PKCS7 *p7, CMS_ContentInfo *ti
         crl = x509_crl_get(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\n");
-            goto out;
+            printf("Use the \"-TSA-CRLfile\" option to add one or more Time-Stamp Authority CRLs in PEM format.\n");
         }
     }
 #endif /* ENABLE_CURL */
@@ -1465,7 +1700,7 @@ static int verify_authenticode(FILE_FORMAT_CTX *ctx, PKCS7 *p7, time_t time, X50
         crl = x509_crl_get(url);
         OPENSSL_free(url);
         if (!crl && !ctx->options->crlfile) {
-            printf("Use the \"-CRLfile\" option to add one or more CRLs in PEM format.\n\n");
+            printf("Use the \"-CRLfile\" option to add one or more CRLs in PEM format.\n");
             goto out;
         }
     }
@@ -2441,6 +2676,8 @@ static void usage(const char *argv0, const char *cmd)
         printf("%12s[ -t <timestampurl> [ -t ... ] [ -p <proxy> ] [ -noverifypeer  ]\n", "");
         printf("%12s[ -ts <timestampurl> [ -ts ... ] [ -p <proxy> ] [ -noverifypeer ] ]\n", "");
 #endif /* ENABLE_CURL */
+        printf("%12s[ -TSA-certs <TSA-certfile> ] [ -TSA-key <TSA-keyfile> ]\n", "");
+        printf("%12s[ -TSA-time <unix-time> ]\n", "");
         printf("%12s[ -time <unix-time> ]\n", "");
         printf("%12s[ -addUnauthenticatedBlob ]\n", "");
         printf("%12s[ -nest ]\n", "");
@@ -2454,6 +2691,8 @@ static void usage(const char *argv0, const char *cmd)
         printf("%12s[ -t <timestampurl> [ -t ... ] [ -p <proxy> ] [ -noverifypeer  ]\n", "");
         printf("%12s[ -ts <timestampurl> [ -ts ... ] [ -p <proxy> ] [ -noverifypeer ] ]\n", "");
 #endif /* ENABLE_CURL */
+        printf("%12s[ -TSA-certs <TSA-certfile> ] [ -TSA-key <TSA-keyfile> ]\n", "");
+        printf("%12s[ -TSA-time <unix-time> ]\n", "");
         printf("%12s[ -h {md5,sha1,sha2(56),sha384,sha512} ]\n", "");
         printf("%12s[ -verbose ]\n", "");
         printf("%12s[ -add-msi-dse ]\n", "");
@@ -2551,6 +2790,9 @@ static void help_for(const char *argv0, const char *cmd)
     const char *cmds_ts[] = {"add", "sign", NULL};
 #endif /* ENABLE_CURL */
     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)) {
@@ -2700,34 +2942,17 @@ static void help_for(const char *argv0, const char *cmd)
         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= Time-Stamp Authority signing certificate\n", "-TSA-certs");
+    if (on_list(cmd, cmds_keyTSA))
+        printf("%-24s= 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 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);
 }
 
-/*
- * [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;
-}
-
 #ifdef PROVIDE_ASKPASS
 /*
  * [in] prompt: "Password: "
@@ -3608,6 +3833,24 @@ static int main_configure(int argc, char **argv, GLOBAL_OPTIONS *options)
                 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;
@@ -3673,6 +3916,8 @@ static int main_configure(int argc, char **argv, GLOBAL_OPTIONS *options)
     if (argc > 0 ||
 #ifdef ENABLE_CURL
         (options->nturl && options->ntsurl) ||
+        (options->nturl && options->tsa_certfile && options->tsa_keyfile) ||
+        (options->ntsurl && options->tsa_certfile && options->tsa_keyfile) ||
 #endif
         !options->infile ||
         (cmd != CMD_VERIFY && !options->outfile) ||
diff --git a/osslsigncode.h b/osslsigncode.h
index b674774..9771086 100644
--- a/osslsigncode.h
+++ b/osslsigncode.h
@@ -60,7 +60,9 @@
 #if OPENSSL_VERSION_NUMBER>=0x30000000L
 #include <openssl/provider.h>
 #endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
+#include <openssl/rand.h>
 #include <openssl/safestack.h>
+#include <openssl/ts.h>
 #include <openssl/x509.h>
 #include <openssl/x509v3.h> /* X509_PURPOSE */
 
@@ -210,6 +212,9 @@
 #define DO_EXIT_1(x, y) { printf(x, y); goto err_cleanup; }
 #define DO_EXIT_2(x, y, z) { printf(x, y, z); goto err_cleanup; }
 
+/* Default policy if request did not specify it. */
+#define TSA_POLICY1 "1.2.3.4.1"
+
 typedef enum {
     CMD_SIGN,
     CMD_EXTRACT,
@@ -278,6 +283,9 @@ typedef struct {
     cmd_type_t cmd;
     char *indata;
     PKCS7 *prevsig;
+    char *tsa_certfile;
+    char *tsa_keyfile;
+    time_t tsa_time;
 } GLOBAL_OPTIONS;
 
 /*