diff --git a/osslsigncode.c b/osslsigncode.c index 41c2667..4577b23 100644 --- a/osslsigncode.c +++ b/osslsigncode.c @@ -239,6 +239,7 @@ typedef struct { #endif /* ENABLE_CURL */ int addBlob; int nest; + int timestamp_expiration; int verbose; #ifdef WITH_GSF int add_msi_dse; @@ -246,6 +247,7 @@ typedef struct { char *cafile; char *crlfile; char *untrusted; + char *crluntrusted; char *leafhash; int jp; } GLOBAL_OPTIONS; @@ -1026,6 +1028,7 @@ static void usage(const char *argv0, const char *cmd) printf("%12s[ -CAfile ]\n", ""); printf("%12s[ -CRLfile ]\n", ""); printf("%12s[ -untrusted ]\n", ""); + printf("%12s[ -CRLuntrusted ]\n", ""); printf("%12s[ -nest ]\n", ""); printf("%12s[ -in ] [ -out ] \n\n", ""); } @@ -1040,7 +1043,9 @@ static void usage(const char *argv0, const char *cmd) printf("%12s[ -CAfile ]\n", ""); printf("%12s[ -CRLfile ]\n", ""); printf("%12s[ -untrusted ]\n", ""); + printf("%12s[ -CRLuntrusted ]\n", ""); printf("%12s[ -require-leaf-hash {md5,sha1,sha2(56),sha384,sha512}:XXXXXXXXXXXX... ]\n", ""); + printf("%12s[ -timestamp-expiration ]\n", ""); printf("%12s[ -verbose ]\n\n", ""); } cleanup_lib_state(); @@ -1068,6 +1073,7 @@ static void help_for(const char *argv0, const char *cmd) const char *cmds_certs[] = {"sign", NULL}; const char *cmds_comm[] = {"sign", NULL}; const char *cmds_CRLfile[] = {"attach-signature", "verify", NULL}; + const char *cmds_CRLuntrusted[] = {"attach-signature", "verify", NULL}; const char *cmds_h[] = {"sign", NULL}; const char *cmds_i[] = {"sign", NULL}; const char *cmds_in[] = {"add", "attach-signature", "extract-signature", "remove-signature", "sign", "verify", NULL}; @@ -1092,6 +1098,7 @@ static void help_for(const char *argv0, const char *cmd) const char *cmds_require_leaf_hash[] = {"verify", NULL}; const char *cmds_sigin[] = {"attach-signature", NULL}; const char *cmds_st[] = {"sign", NULL}; + const char *cmds_timestamp_expiration[] = {"verify", NULL}; #ifdef ENABLE_CURL const char *cmds_t[] = {"add", "sign", NULL}; const char *cmds_ts[] = {"add", "sign", NULL}; @@ -1170,6 +1177,8 @@ static void help_for(const char *argv0, const char *cmd) 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_CRLuntrusted)) + printf("%-24s= the file containing one or more additional untrusted CRLs in PEM format\n", "-CRLuntrusted"); if (on_list(cmd, cmds_h)) { printf("%-24s= {md5|sha1|sha2(56)|sha384|sha512}\n", "-h"); printf("%26sset of cryptographic hash functions\n", ""); @@ -1223,6 +1232,8 @@ static void help_for(const char *argv0, const char *cmd) printf("%-24s= a file containing the signature to be attached\n", "-sigin"); if (on_list(cmd, cmds_st)) printf("%-24s= the unix-time to set the signing time\n", "-st"); + if (on_list(cmd, cmds_timestamp_expiration)) + printf("%-24s= verify a finite lifetime of the TSA private key\n", "-st"); #ifdef ENABLE_CURL if (on_list(cmd, cmds_t)) { printf("%-24s= specifies that the digital signature will be timestamped\n", "-t"); @@ -1741,10 +1752,10 @@ static int print_cert(X509 *cert, int i) return 1; /* OK */ } -static int find_signer(PKCS7 *p7, char *leafhash, int *leafok) +static X509 *find_signer(PKCS7 *p7, char *leafhash, int *leafok) { STACK_OF(X509) *signers; - X509 *cert; + X509 *cert = NULL; int ret = 0; /* @@ -1768,7 +1779,7 @@ out: if (!ret) ERR_print_errors_fp(stderr); sk_X509_free(signers); - return ret; + return cert; } static int print_certs(PKCS7 *p7) @@ -1835,31 +1846,26 @@ static time_t cms_get_time(CMS_ContentInfo *cms) return posix_time; } -static int load_crlfile_lookup(X509_STORE *store, char *crl) +static int verify_callback(int ok, X509_STORE_CTX *ctx) { - X509_LOOKUP *lookup; - X509_VERIFY_PARAM *param; + int error = X509_STORE_CTX_get_error(ctx); + int depth = X509_STORE_CTX_get_error_depth(ctx); - lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); - if (!lookup) - return 0; /* FAILED */ - if (!X509_load_crl_file(lookup, crl, X509_FILETYPE_PEM)) { - fprintf(stderr, "\nError: no CRL found in %s\n", crl); - return 0; /* FAILED */ + if (!ok && error == X509_V_ERR_CERT_HAS_EXPIRED) { + if (depth == 0) { + printf("\nWarning: Ignoring expired signer certificate for CRL validation\n"); + return 1; + } else { + X509 *current_cert = X509_STORE_CTX_get_current_cert(ctx); + printf("\nErrror: Expired CA certificate:\n"); + print_cert(current_cert, 0); + printf("\n"); + } } - param = X509_STORE_get0_param(store); - if (param == NULL) - return 0; /* FAILED */ - if (!X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK)) - return 0; /* FAILED */ - if (!X509_VERIFY_PARAM_set_purpose(param, X509_PURPOSE_CRL_SIGN)) - return 0; /* FAILED */ - if (!X509_STORE_set1_param(store, param)) - return 0; /* FAILED */ - return 1; /* OK */ + return ok; } -static int load_file_lookup(X509_STORE *store, char *certs, int purpose) +static int load_crlfile_lookup(X509_STORE *store, char *certs, char *crl) { X509_LOOKUP *lookup; X509_VERIFY_PARAM *param; @@ -1871,13 +1877,45 @@ static int load_file_lookup(X509_STORE *store, char *certs, int purpose) fprintf(stderr, "\nError: no certificate found\n"); return 0; /* FAILED */ } + if (!X509_load_crl_file(lookup, crl, X509_FILETYPE_PEM)) { + fprintf(stderr, "\nError: no CRL found in %s\n", crl); + return 0; /* FAILED */ + } + param = X509_STORE_get0_param(store); if (param == NULL) return 0; /* FAILED */ - if (!X509_VERIFY_PARAM_set_purpose(param, purpose)) + /* 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_callback); + + return 1; /* OK */ +} + +static int load_file_lookup(X509_STORE *store, char *certs) +{ + X509_LOOKUP *lookup; + X509_VERIFY_PARAM *param; + + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + if (!lookup) + return 0; /* FAILED */ + if (!X509_load_cert_file(lookup, certs, 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 */ + return 1; /* OK */ } @@ -1885,15 +1923,12 @@ static int set_store_time(X509_STORE *store, time_t time) { X509_VERIFY_PARAM *param; - param = X509_VERIFY_PARAM_new(); + 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)) { - X509_VERIFY_PARAM_free(param); + if (!X509_STORE_set1_param(store, param)) return 0; /* FAILED */ - } - X509_VERIFY_PARAM_free(param); return 1; /* OK */ } @@ -2227,40 +2262,141 @@ static int pkcs7_set_nested_signature(PKCS7 *p7, PKCS7 *p7nest, time_t signing_t return 1; } +static const char *get_clrdp_url(X509 *cert) +{ + STACK_OF(DIST_POINT) *crldp; + DIST_POINT *dp; + GENERAL_NAMES *gens; + GENERAL_NAME *gen; + int i, j, gtype; + ASN1_STRING *uri; + const char *url; + + 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) + return NULL; + 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, >ype); + if (gtype == GEN_URI && ASN1_STRING_length(uri) > 6) { + url = (const char *)ASN1_STRING_get0_data(uri); + if (strncmp(url, "http://", 7) == 0) + return url; + } + } + } + return NULL; +} + +static int verify_crl(char *ca_file, char *crl_file, 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 (!load_crlfile_lookup(store, ca_file, crl_file)) + 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; + + if (X509_verify_cert(ctx) <= 0) { + int error = X509_STORE_CTX_get_error(ctx); + printf("\nX509_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; +} + static int verify_timestamp(SIGNATURE *signature, GLOBAL_OPTIONS *options) { X509_STORE *store; - STACK_OF(X509) *signers; + STACK_OF(CMS_SignerInfo) *sinfos; + CMS_SignerInfo *cmssi; + X509 *signer; + const char *url; PKCS7_SIGNER_INFO *si; int verok = 0; store = X509_STORE_new(); if (!store) goto out; - if (load_file_lookup(store, options->untrusted, X509_PURPOSE_ANY)) - printf("TSA's certificates file: %s\n", options->untrusted); - else { + if (load_file_lookup(store, options->untrusted)) { + /* + * 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://tools.ietf.org/html/rfc3161 + */ + if (!options->timestamp_expiration) + /* verify timestamp against the time of its creation */ + if (!set_store_time(store, signature->time)) { + fprintf(stderr, "Failed to set store time\n"); + X509_STORE_free(store); + goto out; + } + } else { printf("Use the \"-untrusted\" option to add the CA cert bundle to verify timestamp server.\n"); + X509_STORE_free(store); goto out; } /* verify a CMS SignedData structure */ if (!CMS_verify(signature->timestamp, NULL, store, 0, NULL, 0)) { fprintf(stderr, "\nCMS_verify error\n"); + X509_STORE_free(store); goto out; } + X509_STORE_free(store); + + sinfos = CMS_get0_SignerInfos(signature->timestamp); + cmssi = sk_CMS_SignerInfo_value(sinfos, 0); + CMS_SignerInfo_get0_algs(cmssi, NULL, &signer, NULL, NULL); + + url = get_clrdp_url(signer); + if (url) + printf("TSA's CRL distribution point: %s\n", url); + printf("\n"); + + /* verify a Certificate Revocation List */ + if (options->crluntrusted) { + STACK_OF(X509) *chain = CMS_get1_certs(signature->timestamp); + int crlok = verify_crl(options->untrusted, options->crluntrusted, 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 */ - signers = CMS_get0_signers(signature->timestamp); - if (!signers) { - fprintf(stderr, "\nCMS_get0_signers error\n"); - goto out; - } - if (!(X509_get_extended_key_usage(sk_X509_value(signers, 0)) & XKU_TIMESTAMP)) { + if (!(X509_get_extended_key_usage(signer) & XKU_TIMESTAMP)) { fprintf(stderr, "Unsupported Signer's certificate purpose XKU_TIMESTAMP\n"); goto out; } - sk_X509_free(signers); /* verify the hash provided from the trusted timestamp */ si = sk_PKCS7_SIGNER_INFO_value(signature->p7->d.sign->signer_info, 0); @@ -2271,28 +2407,27 @@ static int verify_timestamp(SIGNATURE *signature, GLOBAL_OPTIONS *options) out: if (!verok) ERR_print_errors_fp(stderr); - /* NULL is a valid parameter value for X509_STORE_free() */ - X509_STORE_free(store); return verok; } -static int verify_authenticode(SIGNATURE *signature, GLOBAL_OPTIONS *options) +static int verify_authenticode(SIGNATURE *signature, GLOBAL_OPTIONS *options, X509 *signer) { - X509_STORE *store = NULL; + X509_STORE *store; size_t seqhdrlen; BIO *bio = NULL; - STACK_OF(X509) *signers; int verok = 0; store = X509_STORE_new(); if (!store) goto out; - if (!load_file_lookup(store, options->cafile, X509_PURPOSE_ANY)) { + if (!load_file_lookup(store, options->cafile)) { fprintf(stderr, "Failed to add store lookup file\n"); + X509_STORE_free(store); goto out; } if (signature->time != INVALID_TIME && !set_store_time(store, signature->time)) { fprintf(stderr, "Failed to set store time\n"); + X509_STORE_free(store); goto out; } @@ -2303,56 +2438,46 @@ static int verify_authenticode(SIGNATURE *signature, GLOBAL_OPTIONS *options) signature->p7->d.sign->contents->d.other->value.sequence->length - seqhdrlen); if (!PKCS7_verify(signature->p7, NULL, store, bio, NULL, 0)) { fprintf(stderr, "\nPKCS7_verify error\n"); + X509_STORE_free(store); BIO_free(bio); goto out; } + X509_STORE_free(store); + BIO_free(bio); /* verify a Certificate Revocation List */ if (options->crlfile) { - if (!load_crlfile_lookup(store, options->crlfile)) { - fprintf(stderr, "Failed to add CRL store lookup file\nCRL verification: failed\n"); - BIO_free(bio); + STACK_OF(X509) *chain = signature->p7->d.sign->cert; + int crlok = verify_crl(options->cafile, options->crlfile, signer, chain); + printf("Signature CRL verification: %s\n", crlok ? "ok" : "failed"); + if (!crlok) goto out; - } - if (PKCS7_verify(signature->p7, NULL, store, bio, NULL, 0)) - printf("CRL verification: ok\n"); - else { - printf("CRL verification: failed\n"); - BIO_free(bio); - goto out; - } } - BIO_free(bio); /* check extended key usage flag XKU_CODE_SIGN */ - signers = PKCS7_get0_signers(signature->p7, NULL, 0); - if (!signers || sk_X509_num(signers) != 1) { - fprintf(stderr, "PKCS7_get0_signers error\n"); - sk_X509_free(signers); - goto out; - } - if (!(X509_get_extended_key_usage(sk_X509_value(signers, 0)) & XKU_CODE_SIGN)) { + if (!(X509_get_extended_key_usage(signer) & XKU_CODE_SIGN)) { fprintf(stderr, "Unsupported Signer's certificate purpose XKU_CODE_SIGN\n"); - sk_X509_free(signers); goto out; } - sk_X509_free(signers); verok = 1; /* OK */ out: if (!verok) ERR_print_errors_fp(stderr); - /* NULL is a valid parameter value for X509_STORE_free() */ - X509_STORE_free(store); return verok; } static int verify_signature(SIGNATURE *signature, GLOBAL_OPTIONS *options) { int leafok = 0, verok; + X509 *signer; + const char *url; - if (!find_signer(signature->p7, options->leafhash, &leafok)) - printf("Find signers error\n"); + signer = find_signer(signature->p7, options->leafhash, &leafok); + if (!signer) { + printf("Find signer error\n"); + return 1; /* FAILED */ + } if (!print_certs(signature->p7)) printf("Print certs error\n"); if (!print_attributes(signature, options->verbose)) @@ -2367,15 +2492,23 @@ static int verify_signature(SIGNATURE *signature, GLOBAL_OPTIONS *options) printf("\nCAfile: %s\n", options->cafile); if (options->crlfile) printf("CRLfile: %s\n", options->crlfile); + if (options->untrusted) + printf("TSA's certificates file: %s\n", options->untrusted); + if (options->crluntrusted) + printf("TSA's CRL file: %s\n", options->crluntrusted); + url = get_clrdp_url(signer); + if (url) + printf("CRL distribution point: %s\n", url); + if (signature->timestamp) { int timeok = verify_timestamp(signature, options); - printf("\nTimestamp Server Signature verification: %s\n", timeok ? "ok" : "failed"); + printf("Timestamp Server Signature verification: %s\n", timeok ? "ok" : "failed"); if (!timeok) { signature->time = INVALID_TIME; } } else printf("\nTimestamp is not available\n\n"); - verok = verify_authenticode(signature, options); + verok = verify_authenticode(signature, options, signer); printf("Signature verification: %s\n\n", verok ? "ok" : "failed"); if (!verok) return 1; /* FAILED */ @@ -4674,6 +4807,8 @@ static void free_options(GLOBAL_OPTIONS *options) OPENSSL_free(options->untrusted); if (options->crlfile) OPENSSL_free(options->crlfile); + if (options->crluntrusted) + OPENSSL_free(options->crluntrusted); if (options->pass) OPENSSL_free(options->pass); } @@ -5047,6 +5182,8 @@ static int main_configure(int argc, char **argv, cmd_type_t *cmd, GLOBAL_OPTIONS options->addBlob = 1; } else if ((*cmd == CMD_SIGN || *cmd == CMD_ATTACH) && !strcmp(*argv, "-nest")) { options->nest = 1; + } else if ((*cmd == CMD_VERIFY) && !strcmp(*argv, "-timestamp-expiration")) { + options->timestamp_expiration = 1; } else if ((*cmd == CMD_SIGN || *cmd == CMD_ADD || *cmd == CMD_VERIFY) && !strcmp(*argv, "-verbose")) { options->verbose = 1; #ifdef WITH_GSF @@ -5060,6 +5197,9 @@ static int main_configure(int argc, char **argv, cmd_type_t *cmd, GLOBAL_OPTIONS } else if ((*cmd == CMD_VERIFY || *cmd == CMD_ATTACH) && !strcmp(*argv, "-CRLfile")) { if (--argc < 1) usage(argv0, "all"); options->crlfile = OPENSSL_strdup(*++argv); + } else if ((*cmd == CMD_VERIFY || *cmd == CMD_ATTACH) && !strcmp(*argv, "-CRLuntrusted")) { + if (--argc < 1) usage(argv0, "all"); + options->crluntrusted = OPENSSL_strdup(*++argv); } else if ((*cmd == CMD_VERIFY || *cmd == CMD_ATTACH) && !strcmp(*argv, "-untrusted")) { if (--argc < 1) usage(argv0, "all"); OPENSSL_free(options->untrusted); diff --git a/tests/certs/makecerts.sh b/tests/certs/makecerts.sh index 61bccc1..6af05e1 100755 --- a/tests/certs/makecerts.sh +++ b/tests/certs/makecerts.sh @@ -45,7 +45,7 @@ make_certs() { script_path=$(pwd) OPENSSL=openssl CONF="${script_path}/openssltest.cnf" - $OPENSSL req -config $CONF -new -x509 -days 1800 -key demoCA/CA.key -out tmp/CACert.pem \ + $OPENSSL req -config $CONF -new -x509 -days 3600 -key demoCA/CA.key -out tmp/CACert.pem \ -subj "/C=PL/O=osslsigncode/OU=Root CA/CN=CA/emailAddress=CA@example.com" \ 2>> "makecerts.log" 1>&2' test_result $? diff --git a/tests/certs/openssltest.cnf b/tests/certs/openssltest.cnf index 6521df0..347539c 100644 --- a/tests/certs/openssltest.cnf +++ b/tests/certs/openssltest.cnf @@ -19,7 +19,7 @@ x509_extensions = usr_cert private_key = $dir/demoCA/CA.key certificate = $dir/tmp/CACert.pem default_startdate = 180101000000Z -default_enddate = 280101000000Z +default_enddate = 210101000000Z [ req ] encrypt_key = no