diff --git a/appx.c b/appx.c index 617a1d2..712c346 100644 --- a/appx.c +++ b/appx.c @@ -587,12 +587,16 @@ static PKCS7 *appx_pkcs7_prepare(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata) printf("Creating a new signature failed\n"); return NULL; /* FAILED */ } - if (!add_indirect_data_object(p7, hashes, ctx)) { + if (!add_indirect_data_object(p7)) { printf("Adding SPC_INDIRECT_DATA_OBJID failed\n"); BIO_free_all(hashes); PKCS7_free(p7); return NULL; /* FAILED */ } + if (!sign_spc_indirect_data_content(p7, hashes, ctx)) { + printf("Failed to set signed content\n"); + return NULL; /* FAILED */ + } BIO_free_all(hashes); } return p7; /* OK */ diff --git a/cab.c b/cab.c index cc8e745..3933e65 100644 --- a/cab.c +++ b/cab.c @@ -498,11 +498,15 @@ static PKCS7 *cab_pkcs7_prepare(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata) PKCS7_free(p7); return NULL; /* FAILED */ } - if (!add_indirect_data_object(p7, hash, ctx)) { + if (!add_indirect_data_object(p7)) { printf("Adding SPC_INDIRECT_DATA_OBJID failed\n"); PKCS7_free(p7); return NULL; /* FAILED */ } + if (!sign_spc_indirect_data_content(p7, hash, ctx)) { + printf("Failed to set signed content\n"); + return NULL; /* FAILED */ + } } if (ctx->options->nest) ctx->options->prevsig = cursig; diff --git a/cat.c b/cat.c index 5ec37d4..4ebab13 100644 --- a/cat.c +++ b/cat.c @@ -19,10 +19,13 @@ struct cat_ctx_st { uint32_t sigpos; uint32_t siglen; uint32_t fileend; + PKCS7 *p7; }; /* FILE_FORMAT method prototypes */ static FILE_FORMAT_CTX *cat_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *outdata); +static int cat_check_file(FILE_FORMAT_CTX *ctx, int detached); +static int cat_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7); static PKCS7 *cat_pkcs7_extract(FILE_FORMAT_CTX *ctx); static PKCS7 *cat_pkcs7_prepare(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata); static int cat_append_pkcs7(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7); @@ -31,6 +34,8 @@ static void cat_ctx_cleanup(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata); FILE_FORMAT file_format_cat = { .ctx_new = cat_ctx_new, + .check_file = cat_check_file, + .verify_digests = cat_verify_digests, .pkcs7_extract = cat_pkcs7_extract, .pkcs7_prepare = cat_pkcs7_prepare, .append_pkcs7 = cat_append_pkcs7, @@ -40,6 +45,8 @@ FILE_FORMAT file_format_cat = { /* Prototypes */ static CAT_CTX *cat_ctx_get(char *indata, uint32_t filesize); +static int cat_add_ms_ctl_object(PKCS7 *p7); +static int cat_sign_ms_ctl_content(PKCS7 *p7, PKCS7 *contents); /* * FILE_FORMAT method definitions @@ -58,17 +65,12 @@ static FILE_FORMAT_CTX *cat_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *out CAT_CTX *cat_ctx; uint32_t filesize; - /* squash unused parameter warnings */ - (void)outdata; - (void)hash; - if (options->cmd == CMD_REMOVE || options->cmd==CMD_ATTACH) { printf("Unsupported command\n"); return NULL; /* FAILED */ } if (options->cmd == CMD_VERIFY) { - printf("Use -catalog option\n"); - return NULL; /* FAILED */ + printf("Warning: Use -catalog option to verify that a file, listed in catalog file, is signed\n\n"); } filesize = get_file_size(options->infile); if (filesize == 0) @@ -109,6 +111,45 @@ static FILE_FORMAT_CTX *cat_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *out return ctx; } +static int cat_check_file(FILE_FORMAT_CTX *ctx, int detached) +{ + STACK_OF(PKCS7_SIGNER_INFO) *signer_info; + PKCS7_SIGNER_INFO *si; + + if (!ctx) { + printf("Init error\n\n"); + return 0; /* FAILED */ + } + if (detached) { + printf("CAT format does not support detached PKCS#7 signature\n\n"); + return 0; /* FAILED */ + } + signer_info = PKCS7_get_signer_info(ctx->cat_ctx->p7); + if (!signer_info) { + printf("Failed catalog file\n\n"); + return 0; /* FAILED */ + } + si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0); + if (!si) { + printf("No signature found\n\n"); + return 0; /* FAILED */ + } + return 1; /* OK */ +} + +/* + * ContentInfo value is the inner content of pkcs7-signedData. + * An extra verification is not necessary when a content type data + * is the inner content of the signed-data type. + */ +static int cat_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7) +{ + /* squash unused parameter warnings */ + (void)ctx; + (void)p7; + return 1; /* OK */ +} + /* * Extract existing signature in DER format. * [in] ctx: structure holds input and output data @@ -116,7 +157,7 @@ static FILE_FORMAT_CTX *cat_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *out */ static PKCS7 *cat_pkcs7_extract(FILE_FORMAT_CTX *ctx) { - return pkcs7_get(ctx->options->indata, ctx->cat_ctx->sigpos, ctx->cat_ctx->siglen); + return PKCS7_dup(ctx->cat_ctx->p7); } /* @@ -128,36 +169,33 @@ static PKCS7 *cat_pkcs7_extract(FILE_FORMAT_CTX *ctx) */ static PKCS7 *cat_pkcs7_prepare(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata) { - PKCS7 *cursig = NULL, *p7 = NULL; + PKCS7 *p7 = NULL; /* squash unused parameter warnings */ (void)outdata; (void)hash; /* Obtain an existing signature */ - cursig = pkcs7_get(ctx->options->indata, ctx->cat_ctx->sigpos, ctx->cat_ctx->siglen); - if (!cursig) { - printf("Unable to extract existing signature\n"); - return NULL; /* FAILED */ - } if (ctx->options->cmd == CMD_ADD || ctx->options->cmd == CMD_ATTACH) { - p7 = cursig; + p7 = PKCS7_dup(ctx->cat_ctx->p7); } else if (ctx->options->cmd == CMD_SIGN) { /* Create a new signature */ p7 = pkcs7_create(ctx); if (!p7) { printf("Creating a new signature failed\n"); - PKCS7_free(cursig); return NULL; /* FAILED */ } - if (!add_ms_ctl_object(p7, cursig)) { + if (!cat_add_ms_ctl_object(p7)) { printf("Adding MS_CTL_OBJID failed\n"); PKCS7_free(p7); - PKCS7_free(cursig); return NULL; /* FAILED */ } - PKCS7_free(cursig); - } + if (!cat_sign_ms_ctl_content(p7, ctx->cat_ctx->p7->d.sign->contents)) { + printf("Failed to set signed content\n"); + PKCS7_free(p7); + return 0; /* FAILED */ + } + } return p7; /* OK */ } @@ -224,6 +262,7 @@ static void cat_ctx_cleanup(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata) } } unmap_file(ctx->options->indata, ctx->cat_ctx->fileend); + PKCS7_free(ctx->cat_ctx->p7); OPENSSL_free(ctx->cat_ctx); OPENSSL_free(ctx); } @@ -233,7 +272,7 @@ static void cat_ctx_cleanup(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata) */ /* - * Verify mapped CAT file TODO and create CAT format specific structure. + * Verify mapped CAT file and create CAT format specific structure. * [in] indata: mapped CAT file (unused) * [in] filesize: size of CAT file * [returns] pointer to CAT format specific structure @@ -241,17 +280,69 @@ static void cat_ctx_cleanup(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata) static CAT_CTX *cat_ctx_get(char *indata, uint32_t filesize) { CAT_CTX *cat_ctx; + PKCS7 *p7 = pkcs7_get(indata, 0, filesize); - /* squash the unused parameter warning */ - (void)indata; - + if (!p7) + return NULL; /* FAILED */ cat_ctx = OPENSSL_zalloc(sizeof(CAT_CTX)); + cat_ctx->p7 = p7; cat_ctx->sigpos = 0; cat_ctx->siglen = filesize; cat_ctx->fileend = filesize; return cat_ctx; /* OK */ } +/* + * Add "1.3.6.1.4.1.311.10.1" MS_CTL_OBJID signed attribute + * [in, out] p7: new PKCS#7 signature + * [returns] 0 on error or 1 on success + */ +static int cat_add_ms_ctl_object(PKCS7 *p7) +{ + STACK_OF(PKCS7_SIGNER_INFO) *signer_info; + PKCS7_SIGNER_INFO *si; + + 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 */ + if (!PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, + V_ASN1_OBJECT, OBJ_txt2obj(MS_CTL_OBJID, 1))) + return 0; /* FAILED */ + return 1; /* OK */ +} + +/* + * Sign the MS CTL blob. + * Certificate Trust List (CTL) is a list of file names or thumbprints. + * All the items in this list are authenticated (approved) by the signing entity. + * [in, out] p7: new PKCS#7 signature + * [in] contents: Certificate Trust List (CTL) + * [returns] 0 on error or 1 on success + */ +static int cat_sign_ms_ctl_content(PKCS7 *p7, PKCS7 *contents) +{ + u_char *content; + int seqhdrlen, content_length; + + seqhdrlen = asn1_simple_hdr_len(contents->d.other->value.sequence->data, + contents->d.other->value.sequence->length); + content = contents->d.other->value.sequence->data + seqhdrlen; + content_length = contents->d.other->value.sequence->length - seqhdrlen; + + if (!pkcs7_sign_content(p7, content, content_length)) { + printf("Failed to sign content\n"); + return 0; /* FAILED */ + } + if (!PKCS7_set_content(p7, PKCS7_dup(contents))) { + printf("PKCS7_set_content failed\n"); + return 0; /* FAILED */ + } + return 1; /* OK */ +} + /* Local Variables: c-basic-offset: 4 diff --git a/helpers.c b/helpers.c index 8b25acf..4256969 100644 --- a/helpers.c +++ b/helpers.c @@ -9,12 +9,12 @@ #include "helpers.h" /* Prototypes */ -static int pkcs7_set_content_blob(PKCS7 *sig, PKCS7 *cursig); static SpcSpOpusInfo *spc_sp_opus_info_create(FILE_FORMAT_CTX *ctx); static int spc_indirect_data_content_get(u_char **blob, int *len, FILE_FORMAT_CTX *ctx); -static int pkcs7_set_spc_indirect_data_content(PKCS7 *p7, BIO *hash, u_char *buf, int len, FILE_FORMAT_CTX *ctx); static int pkcs7_signer_info_add_spc_sp_opus_info(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx); static int pkcs7_signer_info_add_purpose(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx); +static STACK_OF(X509) *X509_chain_get_sorted(FILE_FORMAT_CTX *ctx, int signer); +static int X509_compare(const X509 *const *a, const X509 *const *b); /* * Common functions @@ -108,60 +108,6 @@ void unmap_file(char *indata, const size_t size) #endif /* WIN32 */ } -/* - * [in, out] si: PKCS7_SIGNER_INFO structure - * [in] ctx: FILE_FORMAT_CTX structure - * [returns] 0 on error or 1 on success - */ -static int pkcs7_signer_info_add_spc_sp_opus_info(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx) -{ - SpcSpOpusInfo *opus; - ASN1_STRING *astr; - int len; - u_char *p = NULL; - - opus = spc_sp_opus_info_create(ctx); - if ((len = i2d_SpcSpOpusInfo(opus, NULL)) <= 0 - || (p = OPENSSL_malloc((size_t)len)) == NULL) { - SpcSpOpusInfo_free(opus); - return 0; /* FAILED */ - } - i2d_SpcSpOpusInfo(opus, &p); - p -= len; - astr = ASN1_STRING_new(); - ASN1_STRING_set(astr, p, len); - OPENSSL_free(p); - SpcSpOpusInfo_free(opus); - return PKCS7_add_signed_attribute(si, OBJ_txt2nid(SPC_SP_OPUS_INFO_OBJID), - V_ASN1_SEQUENCE, astr); -} - -/* - * [in, out] si: PKCS7_SIGNER_INFO structure - * [in] ctx: structure holds input and output data - * [returns] 0 on error or 1 on success - */ -static int pkcs7_signer_info_add_purpose(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx) -{ - static const u_char purpose_ind[] = { - 0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, - 0x01, 0x82, 0x37, 0x02, 0x01, 0x15 - }; - static const u_char purpose_comm[] = { - 0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, - 0x01, 0x82, 0x37, 0x02, 0x01, 0x16 - }; - ASN1_STRING *purpose = ASN1_STRING_new(); - - if (ctx->options->comm) { - ASN1_STRING_set(purpose, purpose_comm, sizeof purpose_comm); - } else { - ASN1_STRING_set(purpose, purpose_ind, sizeof purpose_ind); - } - return PKCS7_add_signed_attribute(si, OBJ_txt2nid(SPC_STATEMENT_TYPE_OBJID), - V_ASN1_SEQUENCE, purpose); -} - /* * Add a custom, non-trusted time to the PKCS7 structure to prevent OpenSSL * adding the _current_ time. This allows to create a deterministic signature @@ -216,81 +162,6 @@ PKCS7 *pkcs7_get_sigfile(FILE_FORMAT_CTX *ctx) return p7; } -/* - * X.690-compliant certificate comparison function - * Windows requires catalog files to use PKCS#7 - * content ordering specified in X.690 section 11.6 - * https://support.microsoft.com/en-us/topic/october-13-2020-kb4580358-security-only-update-d3f6eb3c-d7c4-a9cb-0de6-759386bf7113 - * This algorithm is different from X509_cmp() - * [in] a_ptr, b_ptr: pointers to X509 certificates - * [returns] certificates order - */ -static int X509_compare(const X509 *const *a, const X509 *const *b) -{ - u_char *a_data, *b_data, *a_tmp, *b_tmp; - size_t a_len, b_len; - int ret; - - a_len = (size_t)i2d_X509(*a, NULL); - a_tmp = a_data = OPENSSL_malloc(a_len); - i2d_X509(*a, &a_tmp); - - b_len = (size_t)i2d_X509(*b, NULL); - b_tmp = b_data = OPENSSL_malloc(b_len); - i2d_X509(*b, &b_tmp); - - ret = memcmp(a_data, b_data, MIN(a_len, b_len)); - OPENSSL_free(a_data); - OPENSSL_free(b_data); - - if (ret == 0 && a_len != b_len) /* identical up to the length of the shorter DER */ - ret = a_len < b_len ? -1 : 1; /* shorter is smaller */ - return ret; -} - -/* - * Create certificate chain sorted in ascending order by their DER encoding. - * [in] ctx: structure holds input and output data - * [in] signer: signer's certificate number in the certificate chain - * [returns] sorted certificate chain - */ -static STACK_OF(X509) *X509_chain_get_sorted(FILE_FORMAT_CTX *ctx, int signer) -{ - int i; - STACK_OF(X509) *chain = sk_X509_new(X509_compare); - - /* add the signer's certificate */ - if (ctx->options->cert != NULL && !sk_X509_push(chain, ctx->options->cert)) { - sk_X509_free(chain); - return NULL; - } - if (signer != -1 && !sk_X509_push(chain, sk_X509_value(ctx->options->certs, signer))) { - sk_X509_free(chain); - return NULL; - } - /* add the certificate chain */ - for (i=0; ioptions->certs); i++) { - if (i == signer) - continue; - if (!sk_X509_push(chain, sk_X509_value(ctx->options->certs, i))) { - sk_X509_free(chain); - return NULL; - } - } - /* add all cross certificates */ - if (ctx->options->xcerts) { - for (i=0; ioptions->xcerts); i++) { - if (!sk_X509_push(chain, sk_X509_value(ctx->options->xcerts, i))) { - sk_X509_free(chain); - return NULL; - } - } - } - /* sort certificate chain using the supplied comparison function */ - sk_X509_sort(chain); - return chain; -} - /* * Allocate, set type, add content and return a new PKCS#7 signature * [in] ctx: structure holds input and output data @@ -305,7 +176,7 @@ PKCS7 *pkcs7_create(FILE_FORMAT_CTX *ctx) p7 = PKCS7_new(); PKCS7_set_type(p7, NID_pkcs7_signed); - + PKCS7_content_new(p7, NID_pkcs7_data); if (ctx->options->cert != NULL) { /* * the private key and corresponding certificate are parsed from the PKCS12 @@ -335,17 +206,14 @@ PKCS7 *pkcs7_create(FILE_FORMAT_CTX *ctx) } } pkcs7_signer_info_add_signing_time(si, ctx); - - if (!pkcs7_signer_info_add_purpose(si, ctx)) + if (!pkcs7_signer_info_add_purpose(si, ctx)) { return NULL; /* FAILED */ - + } if ((ctx->options->desc || ctx->options->url) && !pkcs7_signer_info_add_spc_sp_opus_info(si, ctx)) { printf("Couldn't allocate memory for opus info\n"); return NULL; /* FAILED */ } - PKCS7_content_new(p7, NID_pkcs7_data); - /* create X509 chain sorted in ascending order by their DER encoding */ chain = X509_chain_get_sorted(ctx, signer); if (chain == NULL) { @@ -366,12 +234,12 @@ PKCS7 *pkcs7_create(FILE_FORMAT_CTX *ctx) } /* + * PE, MSI, CAB and APPX file specific + * Add "1.3.6.1.4.1.311.2.1.4" SPC_INDIRECT_DATA_OBJID signed attribute * [in, out] p7: new PKCS#7 signature - * [in] hash: message digest BIO - * [in] ctx: structure holds input and output data * [returns] 0 on error or 1 on success */ -int add_indirect_data_object(PKCS7 *p7, BIO *hash, FILE_FORMAT_CTX *ctx) +int add_indirect_data_object(PKCS7 *p7) { STACK_OF(PKCS7_SIGNER_INFO) *signer_info; PKCS7_SIGNER_INFO *si; @@ -385,67 +253,85 @@ int add_indirect_data_object(PKCS7 *p7, BIO *hash, FILE_FORMAT_CTX *ctx) if (!PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, 1))) return 0; /* FAILED */ - if (!pkcs7_set_data_content(p7, hash, ctx)) { - printf("Signing failed\n"); + return 1; /* OK */ +} + +/* + * PE, MSI, CAB and APPX format specific + * Sign the MS Authenticode spcIndirectDataContent blob. + * The spcIndirectDataContent structure is used in Authenticode signatures + * to store the digest and other attributes of the signed file. + * [in, out] p7: new PKCS#7 signature + * [in] hash: message digest BIO + * [in] ctx: structure holds input and output data + * [returns] 0 on error or 1 on success + */ +int sign_spc_indirect_data_content(PKCS7 *p7, BIO *hash, FILE_FORMAT_CTX *ctx) +{ + u_char mdbuf[5 * EVP_MAX_MD_SIZE + 24]; + int mdlen, seqhdrlen, hashlen; + PKCS7 *td7; + u_char *p = NULL; + int len = 0; + u_char *buf; + + hashlen = ctx->format->hash_length_get(ctx); + if (hashlen > EVP_MAX_MD_SIZE) { + /* APPX format specific */ + mdlen = BIO_read(hash, (char*)mdbuf, hashlen); + } else { + mdlen = BIO_gets(hash, (char*)mdbuf, EVP_MAX_MD_SIZE); + } + if (!spc_indirect_data_content_get(&p, &len, ctx)) + return 0; /* FAILED */ + + buf = OPENSSL_malloc(SIZE_64K); + memcpy(buf, p, (size_t)len); + OPENSSL_free(p); + memcpy(buf + len, mdbuf, (size_t)mdlen); + seqhdrlen = asn1_simple_hdr_len(buf, len); + + if (!pkcs7_sign_content(p7, buf + seqhdrlen, len - seqhdrlen + mdlen)) { + printf("Failed to sign content\n"); + OPENSSL_free(buf); + return 0; /* FAILED */ + } + td7 = PKCS7_new(); + td7->type = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, 1); + td7->d.other = ASN1_TYPE_new(); + td7->d.other->type = V_ASN1_SEQUENCE; + td7->d.other->value.sequence = ASN1_STRING_new(); + ASN1_STRING_set(td7->d.other->value.sequence, buf, len + mdlen); + OPENSSL_free(buf); + if (!PKCS7_set_content(p7, td7)) { + printf("PKCS7_set_content failed\n"); + PKCS7_free(td7); return 0; /* FAILED */ } return 1; /* OK */ } /* + * Signs the data and place the signature in p7 * [in, out] p7: new PKCS#7 signature - * [in] cursig: current PKCS#7 signature - * [returns] 0 on error or 1 on success + * [in] data: content data + * [in] len: content length */ -int add_ms_ctl_object(PKCS7 *p7, PKCS7 *cursig) +int pkcs7_sign_content(PKCS7 *p7, u_char *data, int len) { - STACK_OF(PKCS7_SIGNER_INFO) *signer_info; - PKCS7_SIGNER_INFO *si; + BIO *p7bio; - 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 */ - if (!PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, - V_ASN1_OBJECT, OBJ_txt2obj(MS_CTL_OBJID, 1))) - return 0; /* FAILED */ - if (!pkcs7_set_content_blob(p7, cursig)) { - printf("Signing failed\n"); - return 0; /* FAILED */ - } - return 1; /* OK */ -} - -static int pkcs7_set_content_blob(PKCS7 *sig, PKCS7 *cursig) -{ - PKCS7 *contents; - u_char *content; - int seqhdrlen, content_length; - BIO *sigbio; - - contents = cursig->d.sign->contents; - seqhdrlen = asn1_simple_hdr_len(contents->d.other->value.sequence->data, - contents->d.other->value.sequence->length); - content = contents->d.other->value.sequence->data + seqhdrlen; - content_length = contents->d.other->value.sequence->length - seqhdrlen; - - if ((sigbio = PKCS7_dataInit(sig, NULL)) == NULL) { + if ((p7bio = PKCS7_dataInit(p7, NULL)) == NULL) { printf("PKCS7_dataInit failed\n"); return 0; /* FAILED */ } - BIO_write(sigbio, content, content_length); - (void)BIO_flush(sigbio); - if (!PKCS7_dataFinal(sig, sigbio)) { + BIO_write(p7bio, data, len); + (void)BIO_flush(p7bio); + if (!PKCS7_dataFinal(p7, p7bio)) { printf("PKCS7_dataFinal failed\n"); return 0; /* FAILED */ } - BIO_free_all(sigbio); - if (!PKCS7_set_content(sig, PKCS7_dup(contents))) { - printf("PKCS7_set_content failed\n"); - return 0; /* FAILED */ - } + BIO_free_all(p7bio); return 1; /* OK */ } @@ -525,32 +411,6 @@ int is_content_type(PKCS7 *p7, const char *objid) return ret; } -/* - * [out] p7: new PKCS#7 signature - * [in] hash: message digest BIO - * [in] ctx: structure holds input and output data - * [returns] 0 on error or 1 on success - */ -int pkcs7_set_data_content(PKCS7 *p7, BIO *hash, FILE_FORMAT_CTX *ctx) -{ - u_char *p = NULL; - int len = 0; - u_char *buf; - - if (!spc_indirect_data_content_get(&p, &len, ctx)) - return 0; /* FAILED */ - buf = OPENSSL_malloc(SIZE_64K); - memcpy(buf, p, (size_t)len); - OPENSSL_free(p); - if (!pkcs7_set_spc_indirect_data_content(p7, hash, buf, len, ctx)) { - OPENSSL_free(buf); - return 0; /* FAILED */ - } - OPENSSL_free(buf); - - return 1; /* OK */ -} - /* * PE and CAB format specific * [in] none @@ -673,56 +533,132 @@ static int spc_indirect_data_content_get(u_char **blob, int *len, FILE_FORMAT_CT } /* - * Replace the data part with the MS Authenticode spcIndirectDataContent blob - * [out] p7: new PKCS#7 signature - * [in] hash: message digest BIO - * [in] blob: SpcIndirectDataContent data - * [in] len: SpcIndirectDataContent data length + * [in, out] si: PKCS7_SIGNER_INFO structure * [in] ctx: FILE_FORMAT_CTX structure * [returns] 0 on error or 1 on success */ -static int pkcs7_set_spc_indirect_data_content(PKCS7 *p7, BIO *hash, u_char *buf, int len, FILE_FORMAT_CTX *ctx) +static int pkcs7_signer_info_add_spc_sp_opus_info(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx) { - u_char mdbuf[5 * EVP_MAX_MD_SIZE + 24]; - int mdlen, seqhdrlen, hashlen; - BIO *bio; - PKCS7 *td7; + SpcSpOpusInfo *opus; + ASN1_STRING *astr; + int len; + u_char *p = NULL; - hashlen = ctx->format->hash_length_get(ctx); - if (hashlen > EVP_MAX_MD_SIZE) { - /* APPX format specific */ - mdlen = BIO_read(hash, (char*)mdbuf, hashlen); + opus = spc_sp_opus_info_create(ctx); + if ((len = i2d_SpcSpOpusInfo(opus, NULL)) <= 0 + || (p = OPENSSL_malloc((size_t)len)) == NULL) { + SpcSpOpusInfo_free(opus); + return 0; /* FAILED */ + } + i2d_SpcSpOpusInfo(opus, &p); + p -= len; + astr = ASN1_STRING_new(); + ASN1_STRING_set(astr, p, len); + OPENSSL_free(p); + SpcSpOpusInfo_free(opus); + return PKCS7_add_signed_attribute(si, OBJ_txt2nid(SPC_SP_OPUS_INFO_OBJID), + V_ASN1_SEQUENCE, astr); +} + +/* + * [in, out] si: PKCS7_SIGNER_INFO structure + * [in] ctx: structure holds input and output data + * [returns] 0 on error or 1 on success + */ +static int pkcs7_signer_info_add_purpose(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx) +{ + static const u_char purpose_ind[] = { + 0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, + 0x01, 0x82, 0x37, 0x02, 0x01, 0x15 + }; + static const u_char purpose_comm[] = { + 0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, + 0x01, 0x82, 0x37, 0x02, 0x01, 0x16 + }; + ASN1_STRING *purpose = ASN1_STRING_new(); + + if (ctx->options->comm) { + ASN1_STRING_set(purpose, purpose_comm, sizeof purpose_comm); } else { - mdlen = BIO_gets(hash, (char*)mdbuf, EVP_MAX_MD_SIZE); + ASN1_STRING_set(purpose, purpose_ind, sizeof purpose_ind); } - memcpy(buf + len, mdbuf, (size_t)mdlen); - seqhdrlen = asn1_simple_hdr_len(buf, len); + return PKCS7_add_signed_attribute(si, OBJ_txt2nid(SPC_STATEMENT_TYPE_OBJID), + V_ASN1_SEQUENCE, purpose); +} - if ((bio = PKCS7_dataInit(p7, NULL)) == NULL) { - printf("PKCS7_dataInit failed\n"); - return 0; /* FAILED */ - } - BIO_write(bio, buf + seqhdrlen, len - seqhdrlen + mdlen); - (void)BIO_flush(bio); +/* + * Create certificate chain sorted in ascending order by their DER encoding. + * [in] ctx: structure holds input and output data + * [in] signer: signer's certificate number in the certificate chain + * [returns] sorted certificate chain + */ +static STACK_OF(X509) *X509_chain_get_sorted(FILE_FORMAT_CTX *ctx, int signer) +{ + int i; + STACK_OF(X509) *chain = sk_X509_new(X509_compare); - if (!PKCS7_dataFinal(p7, bio)) { - printf("PKCS7_dataFinal failed\n"); - return 0; /* FAILED */ + /* add the signer's certificate */ + if (ctx->options->cert != NULL && !sk_X509_push(chain, ctx->options->cert)) { + sk_X509_free(chain); + return NULL; } - BIO_free_all(bio); + if (signer != -1 && !sk_X509_push(chain, sk_X509_value(ctx->options->certs, signer))) { + sk_X509_free(chain); + return NULL; + } + /* add the certificate chain */ + for (i=0; ioptions->certs); i++) { + if (i == signer) + continue; + if (!sk_X509_push(chain, sk_X509_value(ctx->options->certs, i))) { + sk_X509_free(chain); + return NULL; + } + } + /* add all cross certificates */ + if (ctx->options->xcerts) { + for (i=0; ioptions->xcerts); i++) { + if (!sk_X509_push(chain, sk_X509_value(ctx->options->xcerts, i))) { + sk_X509_free(chain); + return NULL; + } + } + } + /* sort certificate chain using the supplied comparison function */ + sk_X509_sort(chain); + return chain; +} - td7 = PKCS7_new(); - td7->type = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, 1); - td7->d.other = ASN1_TYPE_new(); - td7->d.other->type = V_ASN1_SEQUENCE; - td7->d.other->value.sequence = ASN1_STRING_new(); - ASN1_STRING_set(td7->d.other->value.sequence, buf, len + mdlen); - if (!PKCS7_set_content(p7, td7)) { - PKCS7_free(td7); - printf("PKCS7_set_content failed\n"); - return 0; /* FAILED */ - } - return 1; /* OK */ +/* + * X.690-compliant certificate comparison function + * Windows requires catalog files to use PKCS#7 + * content ordering specified in X.690 section 11.6 + * https://support.microsoft.com/en-us/topic/october-13-2020-kb4580358-security-only-update-d3f6eb3c-d7c4-a9cb-0de6-759386bf7113 + * This algorithm is different from X509_cmp() + * [in] a_ptr, b_ptr: pointers to X509 certificates + * [returns] certificates order + */ +static int X509_compare(const X509 *const *a, const X509 *const *b) +{ + u_char *a_data, *b_data, *a_tmp, *b_tmp; + size_t a_len, b_len; + int ret; + + a_len = (size_t)i2d_X509(*a, NULL); + a_tmp = a_data = OPENSSL_malloc(a_len); + i2d_X509(*a, &a_tmp); + + b_len = (size_t)i2d_X509(*b, NULL); + b_tmp = b_data = OPENSSL_malloc(b_len); + i2d_X509(*b, &b_tmp); + + ret = memcmp(a_data, b_data, MIN(a_len, b_len)); + OPENSSL_free(a_data); + OPENSSL_free(b_data); + + if (ret == 0 && a_len != b_len) /* identical up to the length of the shorter DER */ + ret = a_len < b_len ? -1 : 1; /* shorter is smaller */ + return ret; } /* diff --git a/helpers.h b/helpers.h index fa0c13c..6ea5967 100644 --- a/helpers.h +++ b/helpers.h @@ -12,14 +12,13 @@ void unmap_file(char *indata, const size_t size); int pkcs7_signer_info_add_signing_time(PKCS7_SIGNER_INFO *si, FILE_FORMAT_CTX *ctx); PKCS7 *pkcs7_get_sigfile(FILE_FORMAT_CTX *ctx); PKCS7 *pkcs7_create(FILE_FORMAT_CTX *ctx); -void add_content_type(PKCS7 *p7); -int add_indirect_data_object(PKCS7 *p7, BIO *hash, FILE_FORMAT_CTX *ctx); -int add_ms_ctl_object(PKCS7 *p7, PKCS7 *cursig); +int add_indirect_data_object(PKCS7 *p7); +int sign_spc_indirect_data_content(PKCS7 *p7, BIO *hash, FILE_FORMAT_CTX *ctx); +int pkcs7_sign_content(PKCS7 *p7, u_char *data, int len); int asn1_simple_hdr_len(const u_char *p, int len); int bio_hash_data(BIO *hash, char *indata, size_t idx, size_t fileend); void print_hash(const char *descript1, const char *descript2, const u_char *hashbuf, int length); int is_content_type(PKCS7 *p7, const char *objid); -int pkcs7_set_data_content(PKCS7 *sig, BIO *hash, FILE_FORMAT_CTX *ctx); SpcLink *spc_link_obsolete_get(void); PKCS7 *pkcs7_get(char *indata, uint32_t sigpos, uint32_t siglen); int compare_digests(u_char *mdbuf, u_char *cmdbuf, int mdtype); diff --git a/msi.c b/msi.c index beadfc9..a53e935 100644 --- a/msi.c +++ b/msi.c @@ -668,12 +668,16 @@ static PKCS7 *msi_pkcs7_prepare(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata) printf("Creating a new signature failed\n"); return NULL; /* FAILED */ } - if (!add_indirect_data_object(p7, hash, ctx)) { + if (!add_indirect_data_object(p7)) { printf("Adding SPC_INDIRECT_DATA_OBJID failed\n"); PKCS7_free(p7); return NULL; /* FAILED */ } - } + if (!sign_spc_indirect_data_content(p7, hash, ctx)) { + printf("Failed to set signed content\n"); + return NULL; /* FAILED */ + } + } if (ctx->options->nest) ctx->options->prevsig = cursig; return p7; diff --git a/osslsigncode.c b/osslsigncode.c index e4657ec..c652baa 100644 --- a/osslsigncode.c +++ b/osslsigncode.c @@ -1236,7 +1236,7 @@ static int trusted_cert(X509 *cert, int error) { BIO_gets(bhash, (char *)mdbuf, EVP_MD_size(md)); BIO_free_all(bhash); - hex = OPENSSL_buf2hexstr(mdbuf, (size_t)EVP_MD_size(md)); + hex = OPENSSL_buf2hexstr(mdbuf, (long)EVP_MD_size(md)); if (!hex) { return 0; /* FAILED */ } @@ -1746,6 +1746,13 @@ static int verify_authenticode(FILE_FORMAT_CTX *ctx, PKCS7 *p7, time_t time, X50 p7->d.sign->contents->d.other->value.sequence->length); } printf("\nSigning Certificate Chain:\n"); + /* + * 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_verify(p7, NULL, store, bio, NULL, 0)) { printf("\nPKCS7_verify error\n"); X509_STORE_free(store); @@ -2304,21 +2311,25 @@ static int verify_member(FILE_FORMAT_CTX *ctx, CatalogAuthAttr *attribute) ASN1_TYPE_free(content); if (mdtype == -1) { printf("Failed to extract current message digest\n\n"); + SpcIndirectDataContent_free(idc); return 1; /* FAILED */ } if (!ctx->format->digest_calc) { printf("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) { printf("Failed to compute a message digest value\n\n"); - return 1; /* Failed */ + 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)); diff --git a/pe.c b/pe.c index c93daa6..3ae4b1e 100644 --- a/pe.c +++ b/pe.c @@ -462,11 +462,15 @@ static PKCS7 *pe_pkcs7_prepare(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata) printf("Creating a new signature failed\n"); return NULL; /* FAILED */ } - if (!add_indirect_data_object(p7, hash, ctx)) { + if (!add_indirect_data_object(p7)) { printf("Adding SPC_INDIRECT_DATA_OBJID failed\n"); PKCS7_free(p7); return NULL; /* FAILED */ } + if (!sign_spc_indirect_data_content(p7, hash, ctx)) { + printf("Failed to set signed content\n"); + return NULL; /* FAILED */ + } } if (ctx->options->nest) ctx->options->prevsig = cursig;