mirror of
https://github.com/mtrojnar/osslsigncode.git
synced 2025-04-04 17:00:11 -05:00
876 lines
28 KiB
C
876 lines
28 KiB
C
/*
|
|
* Script file support library
|
|
*
|
|
* Copyright (C) 2021-2024 Michał Trojnara <Michal.Trojnara@stunnel.org>
|
|
*/
|
|
|
|
#include "osslsigncode.h"
|
|
#include "helpers.h"
|
|
#include "utf.h"
|
|
|
|
typedef enum {comment_hash, comment_xml, comment_c, comment_not_found} comment_style;
|
|
|
|
typedef struct {
|
|
const char *extension;
|
|
comment_style comment;
|
|
} SCRIPT_FORMAT;
|
|
|
|
const SCRIPT_FORMAT supported_formats[] = {
|
|
{".ps1", comment_hash},
|
|
{".ps1xml", comment_xml},
|
|
{".psc1", comment_xml},
|
|
{".psd1", comment_hash},
|
|
{".psm1", comment_hash},
|
|
{".cdxml", comment_xml},
|
|
{".mof", comment_c},
|
|
{NULL, comment_not_found},
|
|
};
|
|
|
|
const char *signature_header = "SIG # Begin signature block";
|
|
const char *signature_footer = "SIG # End signature block";
|
|
|
|
typedef struct {
|
|
const char *open;
|
|
const char *close;
|
|
} SCRIPT_COMMENT;
|
|
|
|
const SCRIPT_COMMENT comment_text[] = {
|
|
[comment_hash] = {"# ", ""},
|
|
[comment_xml] = {"<!-- ", " -->"},
|
|
[comment_c] = {"/* ", " */"}
|
|
};
|
|
|
|
struct script_ctx_st {
|
|
const SCRIPT_COMMENT *comment_text;
|
|
int utf;
|
|
uint32_t sigpos;
|
|
uint32_t fileend;
|
|
};
|
|
|
|
#define LINE_MAX_LEN 100
|
|
|
|
/* FILE_FORMAT method prototypes */
|
|
static FILE_FORMAT_CTX *script_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *outdata);
|
|
static ASN1_OBJECT *script_spc_sip_info_get(u_char **p, int *plen, FILE_FORMAT_CTX *ctx);
|
|
static PKCS7 *script_pkcs7_contents_get(FILE_FORMAT_CTX *ctx, BIO *hash, const EVP_MD *md);
|
|
static int script_hash_length_get(FILE_FORMAT_CTX *ctx);
|
|
static u_char *script_digest_calc(FILE_FORMAT_CTX *ctx, const EVP_MD *md);
|
|
static int script_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7);
|
|
static PKCS7 *script_pkcs7_extract(FILE_FORMAT_CTX *ctx);
|
|
static PKCS7 *script_pkcs7_extract_to_nest(FILE_FORMAT_CTX *ctx);
|
|
static int script_remove_pkcs7(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata);
|
|
static int script_process_data(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata);
|
|
static PKCS7 *script_pkcs7_signature_new(FILE_FORMAT_CTX *ctx, BIO *hash);
|
|
static int script_append_pkcs7(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7);
|
|
static void script_bio_free(BIO *hash, BIO *outdata);
|
|
static void script_ctx_cleanup(FILE_FORMAT_CTX *ctx);
|
|
static int script_is_detaching_supported(void);
|
|
|
|
FILE_FORMAT file_format_script = {
|
|
.ctx_new = script_ctx_new,
|
|
.data_blob_get = script_spc_sip_info_get,
|
|
.pkcs7_contents_get = script_pkcs7_contents_get,
|
|
.hash_length_get = script_hash_length_get,
|
|
.digest_calc = script_digest_calc,
|
|
.verify_digests = script_verify_digests,
|
|
.pkcs7_extract = script_pkcs7_extract,
|
|
.pkcs7_extract_to_nest = script_pkcs7_extract_to_nest,
|
|
.remove_pkcs7 = script_remove_pkcs7,
|
|
.process_data = script_process_data,
|
|
.pkcs7_signature_new = script_pkcs7_signature_new,
|
|
.append_pkcs7 = script_append_pkcs7,
|
|
.bio_free = script_bio_free,
|
|
.ctx_cleanup = script_ctx_cleanup,
|
|
.is_detaching_supported = script_is_detaching_supported
|
|
};
|
|
|
|
/* helper functions */
|
|
static SCRIPT_CTX *script_ctx_get(char *indata, uint32_t filesize, const SCRIPT_COMMENT *comment, int utf);
|
|
static int write_commented(FILE_FORMAT_CTX *ctx, BIO *outdata, const char *data, size_t length);
|
|
static int write_in_encoding(FILE_FORMAT_CTX *ctx, BIO *outdata, const char *line, size_t length);
|
|
static size_t utf8_to_utf16(const char *data, size_t len, uint16_t **out_utf16);
|
|
static size_t utf16_to_utf8(const uint16_t *data, size_t len, char **out_utf8);
|
|
static BIO *script_digest_calc_bio(FILE_FORMAT_CTX *ctx, const EVP_MD *md);
|
|
static int script_digest_convert(BIO *hash, FILE_FORMAT_CTX *ctx, size_t len);
|
|
static int script_write_bio(BIO *data, char *indata, size_t len);
|
|
static int script_check_file(FILE_FORMAT_CTX *ctx);
|
|
|
|
/*
|
|
* Allocate and return a script file format context.
|
|
* [in, out] options: structure holds the input data
|
|
* [out] hash: message digest BIO
|
|
* [in] outdata: outdata file BIO (unused)
|
|
* [returns] pointer to script file format context
|
|
*/
|
|
static FILE_FORMAT_CTX *script_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *outdata)
|
|
{
|
|
FILE_FORMAT_CTX *ctx;
|
|
SCRIPT_CTX *script_ctx;
|
|
const SCRIPT_FORMAT *fmt;
|
|
uint32_t filesize;
|
|
const uint8_t utf16_bom[] = {0xff, 0xfe};
|
|
size_t name_len;
|
|
int utf;
|
|
|
|
/* squash the unused parameter warning */
|
|
(void)outdata;
|
|
|
|
/* find out whether our format is supported */
|
|
name_len = strlen(options->infile);
|
|
for (fmt = supported_formats; fmt->comment != comment_not_found; fmt++) {
|
|
size_t ext_len = strlen(fmt->extension);
|
|
if(name_len > ext_len && !strcasecmp(options->infile + name_len - ext_len, fmt->extension))
|
|
break;
|
|
}
|
|
if (fmt->comment == comment_not_found)
|
|
return NULL;
|
|
printf("Script file format: %s\n", fmt->extension);
|
|
|
|
filesize = get_file_size(options->infile);
|
|
if (filesize == 0)
|
|
return NULL; /* FAILED */
|
|
|
|
options->indata = map_file(options->infile, filesize);
|
|
if (!options->indata) {
|
|
return NULL; /* FAILED */
|
|
}
|
|
utf = memcmp(options->indata, utf16_bom, sizeof utf16_bom) ? 8 : 16;
|
|
|
|
/* initialize script context */
|
|
script_ctx = script_ctx_get(options->indata, filesize, comment_text + fmt->comment, utf);
|
|
if (!script_ctx) {
|
|
unmap_file(options->indata, filesize);
|
|
return NULL; /* FAILED */
|
|
}
|
|
|
|
/* initialize file format context */
|
|
ctx = OPENSSL_malloc(sizeof(FILE_FORMAT_CTX));
|
|
memset(ctx, 0, sizeof(FILE_FORMAT_CTX));
|
|
ctx->format = &file_format_script;
|
|
ctx->options = options;
|
|
ctx->script_ctx = script_ctx;
|
|
|
|
if (hash)
|
|
BIO_push(hash, BIO_new(BIO_s_null()));
|
|
|
|
/* FIXME: user interface logic belongs to osslsigncode.c */
|
|
if (options->pagehash == 1)
|
|
printf("Warning: -ph option is only valid for PE files\n");
|
|
if (options->jp >= 0)
|
|
printf("Warning: -jp option is only valid for CAB files\n");
|
|
return ctx;
|
|
}
|
|
|
|
/*
|
|
* Allocate and return SpcSipInfo object.
|
|
* Subject Interface Package (SIP) is an internal Microsoft API for
|
|
* transforming arbitrary files into a digestible stream.
|
|
* These ClassIDs are found in the indirect data section and identify
|
|
* the type of processor needed to validate the signature.
|
|
* https://github.com/sassoftware/relic/blob/620d0b75ec67c0158a8a9120950abe04327d922f/lib/authenticode/structs.go#L154
|
|
* [out] p: SpcSipInfo data
|
|
* [out] plen: SpcSipInfo data length
|
|
* [in] ctx: structure holds input and output data
|
|
* [returns] pointer to ASN1_OBJECT structure corresponding to SPC_SIPINFO_OBJID
|
|
*/
|
|
static ASN1_OBJECT *script_spc_sip_info_get(u_char **p, int *plen, FILE_FORMAT_CTX *ctx)
|
|
{
|
|
const u_char SpcUUIDSipInfoPs[] = {
|
|
0x1f, 0xcc, 0x3b, 0x60, 0x59, 0x4b, 0x08, 0x4e,
|
|
0xb7, 0x24, 0xd2, 0xc6, 0x29, 0x7e, 0xf3, 0x51
|
|
};
|
|
ASN1_OBJECT *dtype;
|
|
SpcSipInfo *si = SpcSipInfo_new();
|
|
|
|
/* squash the unused parameter warning */
|
|
(void)ctx;
|
|
|
|
ASN1_INTEGER_set(si->a, 65536);
|
|
ASN1_INTEGER_set(si->b, 0);
|
|
ASN1_INTEGER_set(si->c, 0);
|
|
ASN1_INTEGER_set(si->d, 0);
|
|
ASN1_INTEGER_set(si->e, 0);
|
|
ASN1_INTEGER_set(si->f, 0);
|
|
ASN1_OCTET_STRING_set(si->string, SpcUUIDSipInfoPs, sizeof SpcUUIDSipInfoPs);
|
|
*plen = i2d_SpcSipInfo(si, NULL);
|
|
*p = OPENSSL_malloc((size_t)*plen);
|
|
i2d_SpcSipInfo(si, p);
|
|
*p -= *plen;
|
|
dtype = OBJ_txt2obj(SPC_SIPINFO_OBJID, 1);
|
|
SpcSipInfo_free(si);
|
|
return dtype; /* OK */
|
|
}
|
|
|
|
/*
|
|
* Allocate and return a data content to be signed.
|
|
* [in] ctx: structure holds input and output data
|
|
* [in] hash: message digest BIO
|
|
* [in] md: message digest algorithm
|
|
* [returns] data content
|
|
*/
|
|
static PKCS7 *script_pkcs7_contents_get(FILE_FORMAT_CTX *ctx, BIO *hash, const EVP_MD *md)
|
|
{
|
|
ASN1_OCTET_STRING *content;
|
|
BIO *bhash;
|
|
|
|
/* squash the unused parameter warning */
|
|
(void)hash;
|
|
|
|
bhash = script_digest_calc_bio(ctx, md);
|
|
if (!bhash) {
|
|
return NULL; /* FAILED */
|
|
}
|
|
content = spc_indirect_data_content_get(bhash, ctx);
|
|
BIO_free_all(bhash);
|
|
return pkcs7_set_content(content);
|
|
}
|
|
|
|
static int script_hash_length_get(FILE_FORMAT_CTX *ctx)
|
|
{
|
|
return EVP_MD_size(ctx->options->md);
|
|
}
|
|
|
|
/*
|
|
* Compute a simple sha1/sha256 message digest of the MSI file
|
|
* for use with a catalog file.
|
|
* [in] ctx: structure holds input and output data
|
|
* [in] md: message digest algorithm
|
|
* [returns] pointer to calculated message digest
|
|
*/
|
|
static u_char *script_digest_calc(FILE_FORMAT_CTX *ctx, const EVP_MD *md)
|
|
{
|
|
u_char *mdbuf;
|
|
BIO *hash = BIO_new(BIO_f_md());
|
|
|
|
if (!BIO_set_md(hash, md)) {
|
|
fprintf(stderr, "Unable to set the message digest of BIO\n");
|
|
BIO_free_all(hash);
|
|
return NULL; /* FAILED */
|
|
}
|
|
BIO_push(hash, BIO_new(BIO_s_null()));
|
|
if (!script_write_bio(hash, ctx->options->indata, ctx->script_ctx->fileend)) {
|
|
BIO_free_all(hash);
|
|
return NULL; /* FAILED */
|
|
}
|
|
mdbuf = OPENSSL_malloc((size_t)EVP_MD_size(md));
|
|
BIO_gets(hash, (char*)mdbuf, EVP_MD_size(md));
|
|
BIO_free_all(hash);
|
|
return mdbuf; /* OK */
|
|
}
|
|
|
|
/*
|
|
* Calculate the hash and compare to PKCS#7 signedData.
|
|
* [in] ctx: structure holds input and output data
|
|
* [in] p7: PKCS#7 signature
|
|
* [returns] 0 on error or 1 on success
|
|
*/
|
|
static int script_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7)
|
|
{
|
|
int mdtype = -1;
|
|
u_char mdbuf[EVP_MAX_MD_SIZE];
|
|
u_char *cmdbuf = NULL;
|
|
const EVP_MD *md;
|
|
BIO *bhash;
|
|
|
|
/* FIXME: this shared code most likely belongs in osslsigncode.c */
|
|
if (is_content_type(p7, SPC_INDIRECT_DATA_OBJID)) {
|
|
ASN1_STRING *content_val = p7->d.sign->contents->d.other->value.sequence;
|
|
const u_char *p = content_val->data;
|
|
SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, content_val->length);
|
|
if (idc) {
|
|
if (idc->messageDigest && idc->messageDigest->digest && idc->messageDigest->digestAlgorithm) {
|
|
mdtype = OBJ_obj2nid(idc->messageDigest->digestAlgorithm->algorithm);
|
|
memcpy(mdbuf, idc->messageDigest->digest->data, (size_t)idc->messageDigest->digest->length);
|
|
}
|
|
SpcIndirectDataContent_free(idc);
|
|
}
|
|
}
|
|
if (mdtype == -1) {
|
|
fprintf(stderr, "Failed to extract current message digest\n\n");
|
|
return 0; /* FAILED */
|
|
}
|
|
md = EVP_get_digestbynid(mdtype);
|
|
bhash = script_digest_calc_bio(ctx, md);
|
|
if (!bhash)
|
|
return 0; /* FAILED */
|
|
|
|
cmdbuf = OPENSSL_malloc((size_t)EVP_MD_size(md));
|
|
BIO_gets(bhash, (char*)cmdbuf, EVP_MD_size(md));
|
|
BIO_free_all(bhash);
|
|
|
|
if (!compare_digests(mdbuf, cmdbuf, mdtype)) {
|
|
fprintf(stderr, "Signature verification: failed\n\n");
|
|
OPENSSL_free(cmdbuf);
|
|
return 0; /* FAILED */
|
|
}
|
|
OPENSSL_free(cmdbuf);
|
|
return 1; /* OK */
|
|
}
|
|
|
|
/*
|
|
* Extract existing signature in DER format.
|
|
* [in] ctx: structure holds input and output data
|
|
* [returns] pointer to PKCS#7 structure
|
|
*/
|
|
static PKCS7 *script_pkcs7_extract(FILE_FORMAT_CTX *ctx)
|
|
{
|
|
const char *signature_data = ctx->options->indata + ctx->script_ctx->sigpos;
|
|
size_t signature_len = ctx->script_ctx->fileend - ctx->script_ctx->sigpos;
|
|
size_t base64_len;
|
|
char *ptr;
|
|
BIO *bio_mem, *bio_b64 = NULL;
|
|
char *base64_data = NULL;
|
|
char *clean_base64 = NULL;
|
|
int clean_base64_len = 0;
|
|
const char *open_tag = ctx->script_ctx->comment_text->open;
|
|
const char *close_tag = ctx->script_ctx->comment_text->close;
|
|
size_t open_tag_len = strlen(open_tag);
|
|
size_t close_tag_len = strlen(close_tag);
|
|
size_t signature_header_len = strlen(signature_header);
|
|
size_t signature_footer_len = strlen(signature_footer);
|
|
PKCS7 *retval = NULL;
|
|
|
|
if (!script_check_file(ctx)) {
|
|
return NULL; /* FAILED, no signature */
|
|
}
|
|
/* extract Base64 signature */
|
|
if (ctx->script_ctx->utf == 8) {
|
|
base64_len = signature_len;
|
|
base64_data = OPENSSL_malloc(base64_len);
|
|
memcpy(base64_data, signature_data, base64_len);
|
|
} else {
|
|
base64_len = utf16_to_utf8((const void *)signature_data,
|
|
signature_len, &base64_data);
|
|
}
|
|
|
|
/* allocate memory for cleaned Base64 */
|
|
clean_base64 = OPENSSL_malloc(base64_len);
|
|
if (!clean_base64) {
|
|
fprintf(stderr, "Malloc failed\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
/* copy clean Base64 data */
|
|
for (ptr = base64_data;;) {
|
|
/* find the opening tag */
|
|
for(;;) {
|
|
if (ptr + open_tag_len >= base64_data + base64_len) {
|
|
fprintf(stderr, "Signature line too long\n");
|
|
goto cleanup;
|
|
}
|
|
if (!memcmp(ptr, open_tag, (size_t)open_tag_len)) {
|
|
ptr += open_tag_len;
|
|
break;
|
|
}
|
|
ptr++;
|
|
}
|
|
/* process signature_header and signature_footer */
|
|
if (ptr + signature_header_len < base64_data + base64_len &&
|
|
!memcmp(ptr, signature_header, signature_header_len))
|
|
ptr += signature_header_len;
|
|
if (ptr + signature_footer_len <= base64_data + base64_len &&
|
|
!memcmp(ptr, signature_footer, signature_footer_len))
|
|
break; /* success */
|
|
|
|
/* copy until the closing tag */
|
|
for(;;) {
|
|
if (ptr + close_tag_len >= base64_data + base64_len) {
|
|
fprintf(stderr, "Signature line too long\n");
|
|
goto cleanup;
|
|
}
|
|
if (close_tag_len) {
|
|
if (!memcmp(ptr, close_tag, (size_t)close_tag_len)) {
|
|
ptr += close_tag_len;
|
|
break;
|
|
}
|
|
}
|
|
if (*ptr == '\r') {
|
|
ptr++;
|
|
} else if (*ptr == '\n') {
|
|
ptr++;
|
|
break;
|
|
} else {
|
|
clean_base64[clean_base64_len++] = *ptr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* prepare for Base64 decoding */
|
|
bio_mem = BIO_new_mem_buf(clean_base64, clean_base64_len);
|
|
bio_b64 = BIO_new(BIO_f_base64());
|
|
BIO_push(bio_b64, bio_mem);
|
|
BIO_set_flags(bio_b64, BIO_FLAGS_BASE64_NO_NL);
|
|
|
|
/* decode DER */
|
|
retval = d2i_PKCS7_bio(bio_b64, NULL);
|
|
|
|
cleanup:
|
|
OPENSSL_free(base64_data);
|
|
OPENSSL_free(clean_base64);
|
|
BIO_free_all(bio_b64);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Extract existing signature in DER format.
|
|
* [in] ctx: structure holds input and output data
|
|
* [returns] pointer to PKCS#7 structure
|
|
*/
|
|
static PKCS7 *script_pkcs7_extract_to_nest(FILE_FORMAT_CTX *ctx)
|
|
{
|
|
return script_pkcs7_extract(ctx);
|
|
}
|
|
|
|
/*
|
|
* Remove existing signature.
|
|
* [in, out] ctx: structure holds input and output data
|
|
* [out] hash: message digest BIO
|
|
* [out] outdata: outdata file BIO
|
|
* [returns] 1 on error or 0 on success
|
|
*/
|
|
static int script_remove_pkcs7(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata)
|
|
{
|
|
/* squash the unused parameter warning */
|
|
(void)hash;
|
|
if (!script_check_file(ctx)) {
|
|
return 1; /* FAILED, no signature */
|
|
}
|
|
if (!script_write_bio(outdata, ctx->options->indata, ctx->script_ctx->sigpos)) {
|
|
return 1; /* FAILED */
|
|
}
|
|
return 0; /* OK */
|
|
}
|
|
|
|
/*
|
|
* Initialize outdata file and calculate a hash (message digest) of data.
|
|
* [in, out] ctx: structure holds input and output data
|
|
* [out] hash: message digest BIO
|
|
* [out] outdata: outdata file BIO
|
|
* [returns] 1 on error or 0 on success
|
|
*/
|
|
static int script_process_data(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata)
|
|
{
|
|
if (ctx->script_ctx->sigpos > 0) {
|
|
/* Strip current signature */
|
|
ctx->script_ctx->fileend = ctx->script_ctx->sigpos;
|
|
}
|
|
if (!script_write_bio(outdata, ctx->options->indata, ctx->script_ctx->fileend))
|
|
return 0; /* FAILED */
|
|
if (!script_digest_convert(hash, ctx, ctx->script_ctx->fileend))
|
|
return 0; /* FAILED */
|
|
return 1; /* OK */
|
|
}
|
|
|
|
/*
|
|
* Create a new PKCS#7 signature.
|
|
* [in, out] ctx: structure holds input and output data
|
|
* [out] hash: message digest BIO
|
|
* [returns] pointer to PKCS#7 structure
|
|
*/
|
|
static PKCS7 *script_pkcs7_signature_new(FILE_FORMAT_CTX *ctx, BIO *hash)
|
|
{
|
|
ASN1_OCTET_STRING *content;
|
|
PKCS7 *p7 = pkcs7_create(ctx);
|
|
|
|
if (!p7) {
|
|
fprintf(stderr, "Creating a new signature failed\n");
|
|
return NULL; /* FAILED */
|
|
}
|
|
if (!add_indirect_data_object(p7)) {
|
|
fprintf(stderr, "Adding SPC_INDIRECT_DATA_OBJID failed\n");
|
|
PKCS7_free(p7);
|
|
return NULL; /* FAILED */
|
|
}
|
|
content = spc_indirect_data_content_get(hash, ctx);
|
|
if (!content) {
|
|
fprintf(stderr, "Failed to get spcIndirectDataContent\n");
|
|
return NULL; /* FAILED */
|
|
}
|
|
if (!sign_spc_indirect_data_content(p7, content)) {
|
|
fprintf(stderr, "Failed to set signed content\n");
|
|
PKCS7_free(p7);
|
|
ASN1_OCTET_STRING_free(content);
|
|
return NULL; /* FAILED */
|
|
}
|
|
ASN1_OCTET_STRING_free(content);
|
|
return p7;
|
|
}
|
|
|
|
/*
|
|
* Append signature to the outfile.
|
|
* [in, out] ctx: structure holds input and output data
|
|
* [out] outdata: outdata file BIO
|
|
* [in] p7: PKCS#7 signature
|
|
* [returns] 1 on error or 0 on success
|
|
*/
|
|
static int script_append_pkcs7(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7)
|
|
{
|
|
BIO *bio, *b64;
|
|
BUF_MEM *buffer;
|
|
size_t i;
|
|
static const char crlf[] = {0x0d, 0x0a};
|
|
int ret = 1;
|
|
|
|
/* convert to BASE64 */
|
|
b64 = BIO_new(BIO_f_base64()); /* BIO for base64 encoding */
|
|
if (!b64)
|
|
return 1; /* FAILED */
|
|
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
|
bio = BIO_new(BIO_s_mem()); /* BIO to hold the base64 data */
|
|
if (!bio) {
|
|
BIO_free(b64);
|
|
return 1; /* FAILED */
|
|
}
|
|
bio = BIO_push(b64, bio); /* chain base64 BIO onto memory BIO */
|
|
if (!i2d_PKCS7_bio(bio, p7)) {
|
|
BIO_free_all(bio);
|
|
return 1; /* FAILED */
|
|
}
|
|
(void)BIO_flush(bio);
|
|
BIO_get_mem_ptr(bio, &buffer);
|
|
(void)BIO_set_close(bio, BIO_NOCLOSE);
|
|
|
|
/* split to individual lines and write to outdata */
|
|
if (!write_commented(ctx, outdata, signature_header, strlen(signature_header)))
|
|
goto cleanup;
|
|
for (i = 0; i < buffer->length; i += 64) {
|
|
if (!write_commented(ctx, outdata, buffer->data + i,
|
|
buffer->length - i < 64 ? buffer->length - i : 64)) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
if (!write_commented(ctx, outdata, signature_footer, strlen(signature_footer)))
|
|
goto cleanup;
|
|
|
|
/* signtool expects CRLF terminator at the end of the text file */
|
|
if (!write_in_encoding(ctx, outdata, crlf, sizeof crlf))
|
|
goto cleanup;
|
|
ret = 0; /* OK */
|
|
|
|
cleanup:
|
|
BUF_MEM_free(buffer);
|
|
BIO_free_all(bio);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Free up an entire outdata BIO chain.
|
|
* [out] hash: message digest BIO
|
|
* [out] outdata: outdata file BIO
|
|
* [returns] none
|
|
*/
|
|
static void script_bio_free(BIO *hash, BIO *outdata)
|
|
{
|
|
BIO_free_all(hash);
|
|
BIO_free_all(outdata);
|
|
}
|
|
|
|
/*
|
|
* Deallocate a FILE_FORMAT_CTX structure and script format specific structures.
|
|
* [in, out] ctx: structure holds input and output data
|
|
* [out] hash: message digest BIO
|
|
* [out] outdata: outdata file BIO
|
|
* [returns] none
|
|
*/
|
|
static void script_ctx_cleanup(FILE_FORMAT_CTX *ctx)
|
|
{
|
|
unmap_file(ctx->options->indata, ctx->script_ctx->fileend);
|
|
OPENSSL_free(ctx->script_ctx);
|
|
OPENSSL_free(ctx);
|
|
}
|
|
|
|
static int script_is_detaching_supported(void)
|
|
{
|
|
return 1; /* OK */
|
|
}
|
|
|
|
/*
|
|
* Script helper functions
|
|
*/
|
|
|
|
static SCRIPT_CTX *script_ctx_get(char *indata, uint32_t filesize, const SCRIPT_COMMENT *comment, int utf)
|
|
{
|
|
SCRIPT_CTX *script_ctx;
|
|
|
|
const char *input_pos, *signature_pos, *ptr;
|
|
uint32_t line[LINE_MAX_LEN], commented_header[40], cr, lf;
|
|
size_t sig_pos = 0, line_pos = 0, commented_header_len = 0;
|
|
size_t commented_header_size = sizeof commented_header / sizeof(uint32_t);
|
|
|
|
utf8DecodeRune("\r", 1, &cr);
|
|
utf8DecodeRune("\n", 1, &lf);
|
|
|
|
/* compute runes for the commented signature header */
|
|
for (ptr = comment->open;
|
|
*ptr && commented_header_len < commented_header_size;
|
|
commented_header_len++)
|
|
ptr = utf8DecodeRune(ptr, 1, commented_header + commented_header_len);
|
|
for (ptr = signature_header;
|
|
*ptr && commented_header_len < commented_header_size;
|
|
commented_header_len++)
|
|
ptr = utf8DecodeRune(ptr, 1, commented_header + commented_header_len);
|
|
for (ptr = comment->close;
|
|
*ptr && commented_header_len < commented_header_size;
|
|
commented_header_len++)
|
|
ptr = utf8DecodeRune(ptr, 1, commented_header + commented_header_len);
|
|
|
|
/* find the signature header */
|
|
for (signature_pos = input_pos = indata; input_pos < indata + filesize; ) {
|
|
const char *input_prev = input_pos;
|
|
|
|
input_pos = utf == 8 ?
|
|
utf8DecodeRune(input_pos,
|
|
(size_t)(indata + filesize - input_pos),
|
|
line + line_pos) :
|
|
(const char *)utf16DecodeRune((const void *)input_pos,
|
|
(size_t)(indata + filesize - input_pos)/2,
|
|
line + line_pos);
|
|
|
|
if (!memcmp(line + line_pos, &lf, sizeof lf)) {
|
|
if (line_pos >= commented_header_len &&
|
|
!memcmp(line, commented_header, commented_header_len * sizeof(uint32_t))) {
|
|
sig_pos = (size_t)(signature_pos - indata);
|
|
if (!memcmp(line + line_pos - 1, &cr, sizeof cr))
|
|
sig_pos -= (size_t)utf / 8;
|
|
break; /* SUCCEEDED */
|
|
}
|
|
line_pos = 0;
|
|
signature_pos = input_prev; /* previous line */
|
|
} else if (line_pos < LINE_MAX_LEN - 1) {
|
|
line_pos++; /* we can ignore lines longer than our buffer */
|
|
}
|
|
}
|
|
printf("Signature position: %zu\n", sig_pos);
|
|
|
|
script_ctx = OPENSSL_malloc(sizeof(SCRIPT_CTX));
|
|
script_ctx->comment_text = comment;
|
|
script_ctx->utf = utf;
|
|
script_ctx->fileend = filesize;
|
|
script_ctx->sigpos = (uint32_t)sig_pos;
|
|
return script_ctx; /* OK */
|
|
}
|
|
|
|
/* write a commented line to the bio:
|
|
* - prepend with CRLF ("\r\n")
|
|
* - add opening/closing comment tags
|
|
* - adjust encoding if needed
|
|
* [returns] 0 on error or 1 on success
|
|
*/
|
|
static int write_commented(FILE_FORMAT_CTX *ctx, BIO *outdata, const char *data, size_t length)
|
|
{
|
|
const char *open_tag = ctx->script_ctx->comment_text->open;
|
|
const char *close_tag = ctx->script_ctx->comment_text->close;
|
|
size_t open_tag_len = strlen(open_tag);
|
|
size_t close_tag_len = strlen(close_tag);
|
|
char *line;
|
|
|
|
/* the buffer needs to be long enough for:
|
|
* - CRLF ("\r\n")
|
|
* - opening tag
|
|
* - up to 64 bytes of data
|
|
* - closing tag
|
|
* - trailing NUL ("\0") */
|
|
line = OPENSSL_malloc(2 + open_tag_len + length + close_tag_len + 1);
|
|
strcpy(line, "\r\n");
|
|
strcat(line, open_tag);
|
|
memcpy(line + 2 + open_tag_len, data, length);
|
|
line[2 + open_tag_len + length] = '\0';
|
|
strcat(line, close_tag);
|
|
|
|
/* adjust encoding */
|
|
if (!write_in_encoding(ctx, outdata, line, strlen(line))) {
|
|
OPENSSL_free(line);
|
|
return 0; /* FAILED */
|
|
}
|
|
OPENSSL_free(line);
|
|
return 1; /* OK */
|
|
}
|
|
|
|
/* adjust encoding if needed
|
|
* [returns] 0 on error or 1 on success
|
|
*/
|
|
static int write_in_encoding(FILE_FORMAT_CTX *ctx, BIO *outdata, const char *line, size_t length)
|
|
{
|
|
size_t written;
|
|
if (ctx->script_ctx->utf == 8) {
|
|
if (!BIO_write_ex(outdata, line, length, &written)
|
|
|| written != length) {
|
|
return 0; /* FAILED */
|
|
}
|
|
} else {
|
|
uint16_t *utf16_data = NULL;
|
|
size_t utf16_len = utf8_to_utf16(line, length, &utf16_data);
|
|
|
|
if (!BIO_write_ex(outdata, utf16_data, utf16_len, &written)
|
|
|| written != utf16_len) {
|
|
OPENSSL_free(utf16_data);
|
|
return 0; /* FAILED */
|
|
}
|
|
OPENSSL_free(utf16_data);
|
|
}
|
|
return 1; /* OK */
|
|
}
|
|
|
|
/* convert len bytes of UTF-8 to UTF-16
|
|
* return the number of output bytes
|
|
*/
|
|
static size_t utf8_to_utf16(const char *data, size_t len, uint16_t **out_utf16)
|
|
{
|
|
size_t utf16_len = utf8UTF16Count(data, len);
|
|
*out_utf16 = OPENSSL_malloc(utf16_len * sizeof(uint16_t));
|
|
if (!*out_utf16)
|
|
return 0; /* memory allocation failed */
|
|
|
|
const char *s = data;
|
|
uint16_t *d = *out_utf16;
|
|
uint32_t rune;
|
|
size_t remaining_len = len;
|
|
|
|
while (remaining_len > 0) {
|
|
s = utf8DecodeRune(s, remaining_len, &rune);
|
|
if (!s || s < data)
|
|
break; /* invalid UTF-8 sequence */
|
|
size_t consumed = (size_t)(s - data);
|
|
|
|
remaining_len -= consumed;
|
|
data = s;
|
|
d += utf16EncodeRune(rune, d);
|
|
}
|
|
return (size_t)(2 * (d - *out_utf16));
|
|
}
|
|
|
|
/* convert len bytes of UTF-16 to UTF-8
|
|
* return the number of output bytes
|
|
*/
|
|
static size_t utf16_to_utf8(const uint16_t *data, size_t len, char **out_utf8)
|
|
{
|
|
size_t utf8_len = utf16UTF8Count(data, len/2);
|
|
*out_utf8 = OPENSSL_malloc(utf8_len);
|
|
if (!*out_utf8)
|
|
return 0; /* memory allocation failed */
|
|
|
|
const uint16_t *s = data;
|
|
char *d = *out_utf8;
|
|
uint32_t rune;
|
|
size_t remaining_len = len/2;
|
|
|
|
while (remaining_len > 0) {
|
|
s = utf16DecodeRune(s, remaining_len, &rune);
|
|
if (!s || s < data)
|
|
break; /* invalid UTF-16 sequence */
|
|
size_t consumed = (size_t)(s - data);
|
|
|
|
remaining_len -= consumed;
|
|
data = s;
|
|
d += utf8EncodeRune(rune, d);
|
|
}
|
|
return (size_t)(d - *out_utf8);
|
|
}
|
|
|
|
/*
|
|
* Compute a message digest value of a signed or unsigned script file.
|
|
* [in] ctx: structure holds input and output data
|
|
* [in] md: message digest algorithm
|
|
* [returns] calculated message digest BIO
|
|
*/
|
|
static BIO *script_digest_calc_bio(FILE_FORMAT_CTX *ctx, const EVP_MD *md)
|
|
{
|
|
size_t fileend;
|
|
BIO *hash = BIO_new(BIO_f_md());
|
|
|
|
if (ctx->script_ctx->sigpos)
|
|
fileend = ctx->script_ctx->sigpos;
|
|
else
|
|
fileend = ctx->script_ctx->fileend;
|
|
|
|
if (!BIO_set_md(hash, md)) {
|
|
fprintf(stderr, "Unable to set the message digest of BIO\n");
|
|
BIO_free_all(hash);
|
|
return NULL; /* FAILED */
|
|
}
|
|
BIO_push(hash, BIO_new(BIO_s_null()));
|
|
if (!script_digest_convert(hash, ctx, fileend)) {
|
|
fprintf(stderr, "Unable calc a message digest value\n");
|
|
BIO_free_all(hash);
|
|
return NULL; /* FAILED */
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
/*
|
|
* Compute a message digest value
|
|
* [in, out] hash: message digest BIO
|
|
* [in] ctx: structure holds input and output data
|
|
* [in] len: mapped file length
|
|
* [returns] 0 on error or 1 on success
|
|
*/
|
|
static int script_digest_convert(BIO *hash, FILE_FORMAT_CTX *ctx, size_t len)
|
|
{
|
|
if (ctx->script_ctx->utf == 8) { /* need to convert to UTF-16 */
|
|
uint16_t *utf16_data = NULL;
|
|
size_t utf16_len = utf8_to_utf16(ctx->options->indata,
|
|
len, &utf16_data);
|
|
|
|
if (!script_write_bio(hash, (char *)utf16_data, utf16_len)) {
|
|
OPENSSL_free(utf16_data);
|
|
return 0; /* FAILED */
|
|
}
|
|
OPENSSL_free(utf16_data);
|
|
} else { /* already UTF-16 -> no need to convert */
|
|
if (!script_write_bio(hash, ctx->options->indata, len)) {
|
|
return 0; /* FAILED */
|
|
}
|
|
}
|
|
return 1; /* OK */
|
|
}
|
|
|
|
/*
|
|
* Write len bytes from data to BIO
|
|
* [in, out] bio: message digest or outdata BIO
|
|
* [in] indata: mapped file
|
|
* [in] len: indata length
|
|
* [returns] 0 on error or 1 on success
|
|
*/
|
|
static int script_write_bio(BIO *bio, char *indata, size_t len)
|
|
{
|
|
size_t i = 0, written;
|
|
|
|
while (len > 0) {
|
|
if (!BIO_write_ex(bio, indata + i, len, &written))
|
|
return 0; /* FAILED */
|
|
len -= written;
|
|
i += written;
|
|
}
|
|
return 1; /* OK */
|
|
}
|
|
|
|
/*
|
|
* Check if the signature exists.
|
|
* [in, out] ctx: structure holds input and output data
|
|
* [returns] 0 on error or 1 on success
|
|
*/
|
|
static int script_check_file(FILE_FORMAT_CTX *ctx)
|
|
{
|
|
if (!ctx) {
|
|
fprintf(stderr, "Init error\n");
|
|
return 0; /* FAILED */
|
|
}
|
|
if (ctx->script_ctx->sigpos == 0
|
|
|| ctx->script_ctx->sigpos > ctx->script_ctx->fileend) {
|
|
fprintf(stderr, "No signature found\n");
|
|
return 0; /* FAILED */
|
|
}
|
|
|
|
return 1; /* OK */
|
|
}
|
|
|
|
/*
|
|
Local Variables:
|
|
c-basic-offset: 4
|
|
tab-width: 4
|
|
indent-tabs-mode: nil
|
|
End:
|
|
|
|
vim: set ts=4 expandtab:
|
|
*/
|