/*
   OpenSSL based Authenticode signing for PE/MSI/Java CAB files.

	 Copyright (C) 2005-2014 Per Allansson <pallansson@gmail.com>


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

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

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

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

static const char *rcsid = "$Id: osslsigncode.c,v 1.7.1 2014/07/11 14:14:14 mfive Exp $";

/*
   Implemented with good help from:

   * Peter Gutmann's analysis of Authenticode:

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

   * MS CAB SDK documentation

	  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncabsdk/html/cabdl.asp

   * MS PE/COFF documentation

	  http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx

   * MS Windows Authenticode PE Signature Format

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

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

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

*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif

#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif

#ifdef WITH_GSF
#include <gsf/gsf-infile-msole.h>
#include <gsf/gsf-infile.h>
#include <gsf/gsf-input-stdio.h>
#include <gsf/gsf-outfile-msole.h>
#include <gsf/gsf-outfile.h>
#include <gsf/gsf-output-stdio.h>
#include <gsf/gsf-utils.h>
#endif

#include <openssl/err.h>
#include <openssl/objects.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/pkcs7.h>
#include <openssl/pkcs12.h>
#include <openssl/pem.h>
#include <openssl/asn1t.h>
#include <openssl/conf.h>
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif

#ifdef ENABLE_CURL
#include <curl/curl.h>

#define MAX_TS_SERVERS 256

#endif

#if defined (HAVE_TERMIOS_H) || defined (HAVE_GETPASS)
#define PROVIDE_ASKPASS 1
#endif

/* MS Authenticode object ids */
#define SPC_INDIRECT_DATA_OBJID	 "1.3.6.1.4.1.311.2.1.4"
#define SPC_STATEMENT_TYPE_OBJID "1.3.6.1.4.1.311.2.1.11"
#define SPC_SP_OPUS_INFO_OBJID	 "1.3.6.1.4.1.311.2.1.12"
#define SPC_INDIVIDUAL_SP_KEY_PURPOSE_OBJID "1.3.6.1.4.1.311.2.1.21"
#define SPC_COMMERCIAL_SP_KEY_PURPOSE_OBJID "1.3.6.1.4.1.311.2.1.22"
#define SPC_MS_JAVA_SOMETHING	 "1.3.6.1.4.1.311.15.1"
#define SPC_PE_IMAGE_DATA_OBJID	 "1.3.6.1.4.1.311.2.1.15"
#define SPC_CAB_DATA_OBJID		 "1.3.6.1.4.1.311.2.1.25"
#define SPC_TIME_STAMP_REQUEST_OBJID "1.3.6.1.4.1.311.3.2.1"
#define SPC_SIPINFO_OBJID		 "1.3.6.1.4.1.311.2.1.30"

#define SPC_PE_IMAGE_PAGE_HASHES_V1 "1.3.6.1.4.1.311.2.3.1" /* Page hash using SHA1 */
#define SPC_PE_IMAGE_PAGE_HASHES_V2 "1.3.6.1.4.1.311.2.3.2" /* Page hash using SHA256 */

#define SPC_NESTED_SIGNATURE_OBJID  "1.3.6.1.4.1.311.2.4.1"

#define SPC_RFC3161_OBJID "1.3.6.1.4.1.311.3.3.1"

/* 1.3.6.1.4.1.311.4... MS Crypto 2.0 stuff... */


#define WIN_CERT_REVISION_2             0x0200
#define WIN_CERT_TYPE_PKCS_SIGNED_DATA  0x0002


/*
  ASN.1 definitions (more or less from official MS Authenticode docs)
*/

typedef struct {
	int type;
	union {
		ASN1_BMPSTRING *unicode;
		ASN1_IA5STRING *ascii;
	} value;
} SpcString;

DECLARE_ASN1_FUNCTIONS(SpcString)

ASN1_CHOICE(SpcString) = {
	ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING , 0),
	ASN1_IMP_OPT(SpcString, value.ascii,   ASN1_IA5STRING,	1)
} ASN1_CHOICE_END(SpcString)

IMPLEMENT_ASN1_FUNCTIONS(SpcString)


typedef struct {
	ASN1_OCTET_STRING *classId;
	ASN1_OCTET_STRING *serializedData;
} SpcSerializedObject;

DECLARE_ASN1_FUNCTIONS(SpcSerializedObject)

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

IMPLEMENT_ASN1_FUNCTIONS(SpcSerializedObject)


typedef struct {
	int type;
	union {
		ASN1_IA5STRING *url;
		SpcSerializedObject *moniker;
		SpcString *file;
	} value;
} SpcLink;

DECLARE_ASN1_FUNCTIONS(SpcLink)

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

IMPLEMENT_ASN1_FUNCTIONS(SpcLink)


typedef struct {
	SpcString *programName;
	SpcLink	  *moreInfo;
} SpcSpOpusInfo;

DECLARE_ASN1_FUNCTIONS(SpcSpOpusInfo)

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

IMPLEMENT_ASN1_FUNCTIONS(SpcSpOpusInfo)


typedef struct {
	ASN1_OBJECT *type;
	ASN1_TYPE *value;
} SpcAttributeTypeAndOptionalValue;

DECLARE_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue)

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

IMPLEMENT_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue)


typedef struct {
	ASN1_OBJECT *algorithm;
	ASN1_TYPE *parameters;
} AlgorithmIdentifier;

DECLARE_ASN1_FUNCTIONS(AlgorithmIdentifier)

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

IMPLEMENT_ASN1_FUNCTIONS(AlgorithmIdentifier)


typedef struct {
	AlgorithmIdentifier *digestAlgorithm;
	ASN1_OCTET_STRING *digest;
} DigestInfo;

DECLARE_ASN1_FUNCTIONS(DigestInfo)

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

IMPLEMENT_ASN1_FUNCTIONS(DigestInfo)


typedef struct {
	SpcAttributeTypeAndOptionalValue *data;
	DigestInfo *messageDigest;
} SpcIndirectDataContent;

DECLARE_ASN1_FUNCTIONS(SpcIndirectDataContent)

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

IMPLEMENT_ASN1_FUNCTIONS(SpcIndirectDataContent)


typedef struct {
	ASN1_BIT_STRING* flags;
	SpcLink *file;
} SpcPeImageData;

DECLARE_ASN1_FUNCTIONS(SpcPeImageData)

ASN1_SEQUENCE(SpcPeImageData) = {
	ASN1_SIMPLE(SpcPeImageData, flags, ASN1_BIT_STRING),
	ASN1_EXP_OPT(SpcPeImageData, file, SpcLink, 0)
} ASN1_SEQUENCE_END(SpcPeImageData)

IMPLEMENT_ASN1_FUNCTIONS(SpcPeImageData)


typedef struct {
	ASN1_INTEGER *a;
	ASN1_OCTET_STRING *string;
	ASN1_INTEGER *b;
	ASN1_INTEGER *c;
	ASN1_INTEGER *d;
	ASN1_INTEGER *e;
	ASN1_INTEGER *f;
} SpcSipInfo;

DECLARE_ASN1_FUNCTIONS(SpcSipInfo)

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

IMPLEMENT_ASN1_FUNCTIONS(SpcSipInfo)


#ifdef ENABLE_CURL

typedef struct {
	ASN1_OBJECT *type;
	ASN1_OCTET_STRING *signature;
} TimeStampRequestBlob;

DECLARE_ASN1_FUNCTIONS(TimeStampRequestBlob)

ASN1_SEQUENCE(TimeStampRequestBlob) = {
	ASN1_SIMPLE(TimeStampRequestBlob, type, ASN1_OBJECT),
	ASN1_EXP_OPT(TimeStampRequestBlob, signature, ASN1_OCTET_STRING, 0)
} ASN1_SEQUENCE_END(TimeStampRequestBlob)

IMPLEMENT_ASN1_FUNCTIONS(TimeStampRequestBlob)



typedef struct {
	ASN1_OBJECT *type;
	TimeStampRequestBlob *blob;
} TimeStampRequest;

DECLARE_ASN1_FUNCTIONS(TimeStampRequest)

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

IMPLEMENT_ASN1_FUNCTIONS(TimeStampRequest)


/* RFC3161 Time stamping */

typedef struct {
	ASN1_INTEGER *status;
	STACK_OF(ASN1_UTF8STRING) *statusString;
	ASN1_BIT_STRING *failInfo;
} PKIStatusInfo;

DECLARE_ASN1_FUNCTIONS(PKIStatusInfo)

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)


typedef struct {
	PKIStatusInfo *status;
	PKCS7 *token;
} TimeStampResp;

DECLARE_ASN1_FUNCTIONS(TimeStampResp)

ASN1_SEQUENCE(TimeStampResp) = {
	ASN1_SIMPLE(TimeStampResp, status, PKIStatusInfo),
	ASN1_OPT(TimeStampResp, token, PKCS7)
} ASN1_SEQUENCE_END(TimeStampResp)

IMPLEMENT_ASN1_FUNCTIONS(TimeStampResp)

typedef struct {
	AlgorithmIdentifier *digestAlgorithm;
	ASN1_OCTET_STRING *digest;
} MessageImprint;

DECLARE_ASN1_FUNCTIONS(MessageImprint)

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

IMPLEMENT_ASN1_FUNCTIONS(MessageImprint)

typedef struct {
	ASN1_INTEGER *version;
	MessageImprint *messageImprint;
	ASN1_OBJECT *reqPolicy;
	ASN1_INTEGER *nonce;
	ASN1_BOOLEAN *certReq;
	STACK_OF(X509_EXTENSION) *extensions;
} TimeStampReq;

DECLARE_ASN1_FUNCTIONS(TimeStampReq)

ASN1_SEQUENCE(TimeStampReq) = {
	ASN1_SIMPLE(TimeStampReq, version, ASN1_INTEGER),
	ASN1_SIMPLE(TimeStampReq, messageImprint, MessageImprint),
	ASN1_OPT   (TimeStampReq, reqPolicy, ASN1_OBJECT),
	ASN1_OPT   (TimeStampReq, nonce, ASN1_INTEGER),
	ASN1_SIMPLE(TimeStampReq, certReq, ASN1_BOOLEAN),
	ASN1_IMP_SEQUENCE_OF_OPT(TimeStampReq, extensions, X509_EXTENSION, 0)
} ASN1_SEQUENCE_END(TimeStampReq)

IMPLEMENT_ASN1_FUNCTIONS(TimeStampReq)

#endif /* ENABLE_CURL */


static SpcSpOpusInfo* createOpus(const char *desc, const char *url)
{
	SpcSpOpusInfo *info = SpcSpOpusInfo_new();

	if (desc) {
		info->programName = SpcString_new();
		info->programName->type = 1;
		info->programName->value.ascii = M_ASN1_IA5STRING_new();
		ASN1_STRING_set((ASN1_STRING *)info->programName->value.ascii,
						(const unsigned char*)desc, strlen(desc));
	}

	if (url) {
		info->moreInfo = SpcLink_new();
		info->moreInfo->type = 0;
		info->moreInfo->value.url = M_ASN1_IA5STRING_new();
		ASN1_STRING_set((ASN1_STRING *)info->moreInfo->value.url,
						(const unsigned char*)url, strlen(url));
	}

	return info;
}

static unsigned int asn1_simple_hdr_len(const unsigned char *p, unsigned int len) {
	if (len <= 2 || p[0] > 0x31)
		return 0;
	return (p[1]&0x80) ? (2 + (p[1]&0x7f)) : 2;
}

static void tohex(const unsigned char *v, unsigned char *b, int len)
{
	int i;
	for(i=0; i<len; i++)
		sprintf((char*)b+i*2, "%02X", v[i]);
	b[i*2] = 0x00;
}

#ifdef ENABLE_CURL

static int blob_has_nl = 0;
static size_t curl_write( void *ptr, size_t sz, size_t nmemb, void *stream)
{
	if (sz*nmemb > 0 && !blob_has_nl) {
		if (memchr(ptr, '\n', sz*nmemb))
			blob_has_nl = 1;
	}
	return BIO_write((BIO*)stream, ptr, sz*nmemb);
}

/*
  A timestamp request looks like this:

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

  <base64encoded blob>


  .. and the blob has the following ASN1 structure:

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



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

*/

static int add_timestamp(PKCS7 *sig, char *url, char *proxy, int rfc3161, const EVP_MD *md, int verbose)
{
	CURL *curl;
	struct curl_slist *slist = NULL;
	CURLcode c;
	BIO *bout, *bin, *b64;
	u_char *p = NULL;
	int len = 0;
	PKCS7_SIGNER_INFO *si =
		sk_PKCS7_SIGNER_INFO_value
		(sig->d.sign->signer_info, 0);

	if (!url) return -1;

	curl = curl_easy_init();

	if (proxy) {
		curl_easy_setopt(curl, CURLOPT_PROXY, proxy);
		if (!strncmp("http:", proxy, 5))
			curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
		if (!strncmp("socks:", proxy, 6))
			curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
	}

	curl_easy_setopt(curl, CURLOPT_URL, url);
/*	  curl_easy_setopt(curl, CURLOPT_VERBOSE, 42);	*/

	if (rfc3161) {
		slist = curl_slist_append(slist, "Content-Type: application/timestamp-query");
		slist = curl_slist_append(slist, "Accept: application/timestamp-reply");
	} else {
		slist = curl_slist_append(slist, "Content-Type: application/octet-stream");
		slist = curl_slist_append(slist, "Accept: application/octet-stream");
	}
	slist = curl_slist_append(slist, "User-Agent: Transport");
	slist = curl_slist_append(slist, "Cache-Control: no-cache");
	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);

	if (rfc3161) {
		unsigned char mdbuf[EVP_MAX_MD_SIZE];
		EVP_MD_CTX mdctx;

		EVP_MD_CTX_init(&mdctx);
		EVP_DigestInit(&mdctx, md);
		EVP_DigestUpdate(&mdctx, si->enc_digest->data, si->enc_digest->length);
		EVP_DigestFinal(&mdctx, mdbuf, NULL);

		TimeStampReq *req = TimeStampReq_new();
		ASN1_INTEGER_set(req->version, 1);
		req->messageImprint->digestAlgorithm->algorithm = OBJ_nid2obj(EVP_MD_nid(md));
		req->messageImprint->digestAlgorithm->parameters = ASN1_TYPE_new();
		req->messageImprint->digestAlgorithm->parameters->type = V_ASN1_NULL;
		M_ASN1_OCTET_STRING_set(req->messageImprint->digest, mdbuf, EVP_MD_size(md));
		req->certReq = (void*)0x1;

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

		TimeStampReq_free(req);
	} else {
		TimeStampRequest *req = TimeStampRequest_new();
		req->type = OBJ_txt2obj(SPC_TIME_STAMP_REQUEST_OBJID, 1);
		req->blob->type = OBJ_nid2obj(NID_pkcs7_data);
		req->blob->signature = si->enc_digest;

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

		req->blob->signature = NULL;
		TimeStampRequest_free(req);
	}

	bout = BIO_new(BIO_s_mem());
	if (!rfc3161) {
		b64 = BIO_new(BIO_f_base64());
		bout = BIO_push(b64, bout);
	}
	BIO_write(bout, p, len);
	(void)BIO_flush(bout);
	OPENSSL_free(p);
	p = NULL;

	len = BIO_get_mem_data(bout, &p);

	curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (char*)p);

	bin = BIO_new(BIO_s_mem());
	BIO_set_mem_eof_return(bin, 0);
	curl_easy_setopt(curl, CURLOPT_POST, 1);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, bin);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write);

	c = curl_easy_perform(curl);

	curl_slist_free_all(slist);
	BIO_free_all(bout);

	if (c) {
		BIO_free_all(bin);
		if (verbose)
			fprintf(stderr, "CURL failure: %s\n", curl_easy_strerror(c));
	} else {
		(void)BIO_flush(bin);

		if (rfc3161) {
			TimeStampResp *reply;
			(void)BIO_flush(bin);
			reply = ASN1_item_d2i_bio(ASN1_ITEM_rptr(TimeStampResp), bin, NULL);
			BIO_free_all(bin);
			if (!reply) {
				if (verbose) {
					fprintf(stderr, "Failed to convert timestamp reply\n");
					ERR_print_errors_fp(stderr);
				}
				return -1;
			}
			if (ASN1_INTEGER_get(reply->status->status) != 0) {
				if (verbose)
					fprintf(stderr, "Timestamping failed: %ld\n", ASN1_INTEGER_get(reply->status->status));
				TimeStampResp_free(reply);
				return -1;
			}

			if (((len = i2d_PKCS7(reply->token, NULL)) <= 0) ||
				(p = OPENSSL_malloc(len)) == NULL) {
				if (verbose) {
					fprintf(stderr, "Failed to convert pkcs7: %d\n", len);
					ERR_print_errors_fp(stderr);
				}
				TimeStampResp_free(reply);
				return -1;
			}
			len = i2d_PKCS7(reply->token, &p);
			p -= len;
			TimeStampResp_free(reply);

			STACK_OF(X509_ATTRIBUTE) *attrs = sk_X509_ATTRIBUTE_new_null();
			attrs = X509at_add1_attr_by_txt
				(&attrs, SPC_RFC3161_OBJID, V_ASN1_SET, p, len);
			OPENSSL_free(p);
			PKCS7_set_attributes(si, attrs);
			sk_X509_ATTRIBUTE_pop_free(attrs, X509_ATTRIBUTE_free);
		} else {
			int i;
			PKCS7 *p7;
			PKCS7_SIGNER_INFO *info;
			ASN1_STRING *astr;
			BIO* b64_bin;
			b64 = BIO_new(BIO_f_base64());
			if (!blob_has_nl)
				BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
			b64_bin = BIO_push(b64, bin);
			p7 = d2i_PKCS7_bio(b64_bin, NULL);
			if (p7 == NULL) {
				BIO_free_all(b64_bin);
				if (verbose) {
					fprintf(stderr, "Failed to convert timestamp reply\n");
					ERR_print_errors_fp(stderr);
				}
				return -1;
			}
			BIO_free_all(b64_bin);

			for(i = sk_X509_num(p7->d.sign->cert)-1; i>=0; i--)
				PKCS7_add_certificate(sig, sk_X509_value(p7->d.sign->cert, i));

			info = sk_PKCS7_SIGNER_INFO_value(p7->d.sign->signer_info, 0);
			if (((len = i2d_PKCS7_SIGNER_INFO(info, NULL)) <= 0) ||
				(p = OPENSSL_malloc(len)) == NULL) {
				if (verbose) {
					fprintf(stderr, "Failed to convert signer info: %d\n", len);
					ERR_print_errors_fp(stderr);
				}
				PKCS7_free(p7);
				return -1;
			}
			len = i2d_PKCS7_SIGNER_INFO(info, &p);
			p -= len;
			astr = ASN1_STRING_new();
			ASN1_STRING_set(astr, p, len);
			OPENSSL_free(p);
			PKCS7_add_attribute
				(si, NID_pkcs9_countersignature,
				 V_ASN1_SEQUENCE, astr);

			PKCS7_free(p7);
		}
	}

	curl_easy_cleanup(curl);
	curl_global_cleanup();

	return (int)c;
}

static int add_timestamp_authenticode(PKCS7 *sig, char **url, int nurls, char *proxy)
{
	int i;
	for (i=0; i<nurls; i++) {
		int res = add_timestamp(sig, url[i], proxy, 0, NULL, nurls == 1);
		if (!res) return 0;
	}
	return -1;
}

static int add_timestamp_rfc3161(PKCS7 *sig, char **url, int nurls, char *proxy, const EVP_MD *md)
{
	int i;
	for (i=0; i<nurls; i++) {
		int res = add_timestamp(sig, url[i], proxy, 1, md, nurls == 1);
		if (!res) return 0;
	}
	return -1;
}

#endif /* ENABLE_CURL */

#ifdef WITH_GSF
static int gsf_initialized = 0;
#endif

static void cleanup_lib_state(void)
{
#ifdef WITH_GSF
	if (gsf_initialized)
		gsf_shutdown();
#endif
	OBJ_cleanup();
#ifndef OPENSSL_NO_ENGINE
    ENGINE_cleanup();
#endif
    EVP_cleanup();
	CONF_modules_free();
    CRYPTO_cleanup_all_ex_data();
#if OPENSSL_VERSION_NUMBER > 0x10000000
	ERR_remove_thread_state(NULL);
#endif
    ERR_free_strings();
}

static void usage(const char *argv0)
{
	fprintf(stderr,
			"Usage: %s\n\n\t[ --version | -v ]\n\n"
			"\t[ sign ]\n"
			"\t\t( -certs <certfile> -key <keyfile> | -pkcs12 <pkcs12file> )\n"
			"\t\t[ -pass <password> ] "
#ifdef PROVIDE_ASKPASS
			"[ -askpass ]"
#endif
			"[ -readpass <file> ]\n"
			"\t\t[ -ac <crosscertfile> ]\n"
			"\t\t[ -h {md5,sha1,sha2(56),sha384,sha512} ]\n"
			"\t\t[ -n <desc> ] [ -i <url> ] [ -jp <level> ] [ -comm ]\n"
			"\t\t[ -ph ]\n"
#ifdef ENABLE_CURL
			"\t\t[ -t <timestampurl> [ -t ... ] [ -p <proxy> ]]\n"
			"\t\t[ -ts <timestampurl> [ -ts ... ] [ -p <proxy> ]]\n"
#endif
			"\t\t[ -nest ]\n\n"
			"\t\tMSI specific:\n"
			"\t\t[ -add-msi-dse ]\n\n"
			"\t\t[ -in ] <infile> [-out ] <outfile>\n\n"
			"\textract-signature [ -in ] <infile> [ -out ] <outfile>\n\n"
			"\tremove-signature [ -in ] <infile> [ -out ] <outfile>\n\n"
			"\tverify [ -in ] <infile>\n"
			"\t\t[ -require-leaf-hash {md5,sha1,sha2(56),sha384,sha512}:XXXXXXXXXXXX... ]\n"
			"\n"
			"",
			argv0);
	cleanup_lib_state();
	exit(-1);
}

#define DO_EXIT_0(x)	{ fputs(x, stderr); goto err_cleanup; }
#define DO_EXIT_1(x, y) { fprintf(stderr, x, y); goto err_cleanup; }
#define DO_EXIT_2(x, y, z) { fprintf(stderr, x, y, z); goto err_cleanup; }

#define GET_UINT16_LE(p) (((u_char*)(p))[0] | (((u_char*)(p))[1]<<8))

#define GET_UINT32_LE(p) (((u_char*)(p))[0] | (((u_char*)(p))[1]<<8) |	\
						  (((u_char*)(p))[2]<<16) | (((u_char*)(p))[3]<<24))

#define PUT_UINT16_LE(i,p)						\
	((u_char*)(p))[0] = (i) & 0xff;				\
	((u_char*)(p))[1] = ((i)>>8) & 0xff

#define PUT_UINT32_LE(i,p)						\
	((u_char*)(p))[0] = (i) & 0xff;				\
	((u_char*)(p))[1] = ((i)>>8) & 0xff;		\
	((u_char*)(p))[2] = ((i)>>16) & 0xff;		\
	((u_char*)(p))[3] = ((i)>>24) & 0xff


typedef enum {
	FILE_TYPE_CAB,
	FILE_TYPE_PE,
	FILE_TYPE_MSI,
} file_type_t;

typedef enum {
	CMD_SIGN,
	CMD_EXTRACT,
	CMD_REMOVE,
	CMD_VERIFY,
} cmd_type_t;


static SpcLink *get_obsolete_link(void)
{
	static const unsigned char obsolete[] = {
		0x00, 0x3c, 0x00, 0x3c, 0x00, 0x3c, 0x00, 0x4f, 0x00, 0x62,
		0x00, 0x73, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x74,
		0x00, 0x65, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x3e
	};
	SpcLink *link = SpcLink_new();
	link->type = 2;
	link->value.file = SpcString_new();
	link->value.file->type = 0;
	link->value.file->value.unicode = ASN1_BMPSTRING_new();
	ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof(obsolete));
	return link;
}

static const unsigned char classid_page_hash[] = {
	0xA6, 0xB5, 0x86, 0xD5, 0xB4, 0xA1, 0x24, 0x66,
	0xAE, 0x05, 0xA2, 0x17, 0xDA, 0x8E, 0x60, 0xD6
};

static unsigned char *calc_page_hash(char *indata, unsigned int peheader, int pe32plus,
									 unsigned int sigpos, int phtype, unsigned int *phlen);

DECLARE_STACK_OF(ASN1_OCTET_STRING)
#ifndef sk_ASN1_OCTET_STRING_new_null
#define sk_ASN1_OCTET_STRING_new_null() SKM_sk_new_null(ASN1_OCTET_STRING)
#define sk_ASN1_OCTET_STRING_free(st) SKM_sk_free(ASN1_OCTET_STRING, (st))
#define sk_ASN1_OCTET_STRING_push(st, val) SKM_sk_push(ASN1_OCTET_STRING, (st), (val))
#define i2d_ASN1_SET_OF_ASN1_OCTET_STRING(st, pp, i2d_func, ex_tag, ex_class, is_set) \
	SKM_ASN1_SET_OF_i2d(ASN1_OCTET_STRING, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set))
#endif

DECLARE_STACK_OF(SpcAttributeTypeAndOptionalValue)
#ifndef sk_SpcAttributeTypeAndOptionalValue_new_null
#define sk_SpcAttributeTypeAndOptionalValue_new_null() SKM_sk_new_null(SpcAttributeTypeAndOptionalValue)
#define sk_SpcAttributeTypeAndOptionalValue_free(st) SKM_sk_free(SpcAttributeTypeAndOptionalValue, (st))
#define sk_SpcAttributeTypeAndOptionalValue_push(st, val) SKM_sk_push(SpcAttributeTypeAndOptionalValue, (st), (val))
#define i2d_SpcAttributeTypeAndOptionalValue(st, pp, i2d_func, ex_tag, ex_class, is_set) \
	SKM_ASN1_SET_OF_i2d(SpcAttributeTypeAndOptionalValue, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set))
#endif

static SpcLink *get_page_hash_link(int phtype, char *indata, unsigned int peheader, int pe32plus, unsigned int sigpos)
{
	unsigned int phlen;
	unsigned char *ph = calc_page_hash(indata, peheader, pe32plus, sigpos, phtype, &phlen);
	if (!ph) {
		fprintf(stderr, "Failed to calculate page hash\n");
		exit(-1);
	}

	ASN1_OCTET_STRING *ostr = M_ASN1_OCTET_STRING_new();
	M_ASN1_OCTET_STRING_set(ostr, ph, phlen);
	free(ph);

	STACK_OF(ASN1_OCTET_STRING) *oset = sk_ASN1_OCTET_STRING_new_null();
	sk_ASN1_OCTET_STRING_push(oset, ostr);
	unsigned char *p, *tmp;
	unsigned int l;
	l = i2d_ASN1_SET_OF_ASN1_OCTET_STRING(oset, NULL, i2d_ASN1_OCTET_STRING,
										  V_ASN1_SET, V_ASN1_UNIVERSAL, IS_SET);
	tmp = p = OPENSSL_malloc(l);
	i2d_ASN1_SET_OF_ASN1_OCTET_STRING(oset, &tmp, i2d_ASN1_OCTET_STRING,
									  V_ASN1_SET, V_ASN1_UNIVERSAL, IS_SET);
	ASN1_OCTET_STRING_free(ostr);
	sk_ASN1_OCTET_STRING_free(oset);

	SpcAttributeTypeAndOptionalValue *aval = SpcAttributeTypeAndOptionalValue_new();
	aval->type = OBJ_txt2obj((phtype == NID_sha1) ? SPC_PE_IMAGE_PAGE_HASHES_V1 : SPC_PE_IMAGE_PAGE_HASHES_V2, 1);
	aval->value = ASN1_TYPE_new();
	aval->value->type = V_ASN1_SET;
	aval->value->value.set = ASN1_STRING_new();
	ASN1_STRING_set(aval->value->value.set, p, l);
	OPENSSL_free(p);

	STACK_OF(SpcAttributeTypeAndOptionalValue) *aset = sk_SpcAttributeTypeAndOptionalValue_new_null();
	sk_SpcAttributeTypeAndOptionalValue_push(aset, aval);
	l = i2d_SpcAttributeTypeAndOptionalValue(aset, NULL, i2d_SpcAttributeTypeAndOptionalValue,
											 V_ASN1_SET, V_ASN1_UNIVERSAL, IS_SET);
	tmp = p = OPENSSL_malloc(l);
	l = i2d_SpcAttributeTypeAndOptionalValue(aset, &tmp, i2d_SpcAttributeTypeAndOptionalValue,
											 V_ASN1_SET, V_ASN1_UNIVERSAL, IS_SET);
	sk_SpcAttributeTypeAndOptionalValue_free(aset);
	SpcAttributeTypeAndOptionalValue_free(aval);

	SpcSerializedObject *so = SpcSerializedObject_new();
	M_ASN1_OCTET_STRING_set(so->classId, classid_page_hash, sizeof(classid_page_hash));
	M_ASN1_OCTET_STRING_set(so->serializedData, p, l);
	OPENSSL_free(p);

	SpcLink *link = SpcLink_new();
	link->type = 1;
	link->value.moniker = so;
	return link;
}

static void get_indirect_data_blob(u_char **blob, int *len, const EVP_MD *md, file_type_t type,
								   int pagehash, char *indata, unsigned int peheader, int pe32plus,
								   unsigned int sigpos)
{
	static const unsigned char msistr[] = {
		0xf1, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
		0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46
	};

	u_char *p;
	int hashlen, l;
	void *hash;
	ASN1_OBJECT *dtype;
	SpcIndirectDataContent *idc = SpcIndirectDataContent_new();
	idc->data->value = ASN1_TYPE_new();
	idc->data->value->type = V_ASN1_SEQUENCE;
	idc->data->value->value.sequence = ASN1_STRING_new();
	if (type == FILE_TYPE_CAB) {
		SpcLink *link = get_obsolete_link();
		l = i2d_SpcLink(link, NULL);
		p = OPENSSL_malloc(l);
		i2d_SpcLink(link, &p);
		p -= l;
		dtype = OBJ_txt2obj(SPC_CAB_DATA_OBJID, 1);
		SpcLink_free(link);
	} else if (type == FILE_TYPE_PE) {
		SpcPeImageData *pid = SpcPeImageData_new();
		ASN1_BIT_STRING_set(pid->flags, (unsigned char*)"0", 0);
		if (pagehash) {
			int phtype = NID_sha1;
			if (EVP_MD_size(md) > EVP_MD_size(EVP_sha1()))
				phtype = NID_sha256;
			pid->file = get_page_hash_link(phtype, indata, peheader, pe32plus, sigpos);
		} else {
			pid->file = get_obsolete_link();
		}
		l = i2d_SpcPeImageData(pid, NULL);
		p = OPENSSL_malloc(l);
		i2d_SpcPeImageData(pid, &p);
		p -= l;
		dtype = OBJ_txt2obj(SPC_PE_IMAGE_DATA_OBJID, 1);
		SpcPeImageData_free(pid);
	} else if (type == FILE_TYPE_MSI) {
		SpcSipInfo *si = SpcSipInfo_new();
		ASN1_INTEGER_set(si->a, 1);
		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);
		M_ASN1_OCTET_STRING_set(si->string, msistr, sizeof(msistr));
		l = i2d_SpcSipInfo(si, NULL);
		p = OPENSSL_malloc(l);
		i2d_SpcSipInfo(si, &p);
		p -= l;
		dtype = OBJ_txt2obj(SPC_SIPINFO_OBJID, 1);
		SpcSipInfo_free(si);
	} else {
		fprintf(stderr, "Unexpected file type: %d\n", type);
		exit(1);
	}

	idc->data->type = dtype;
	idc->data->value->value.sequence->data = p;
	idc->data->value->value.sequence->length = l;
	idc->messageDigest->digestAlgorithm->algorithm = OBJ_nid2obj(EVP_MD_nid(md));
	idc->messageDigest->digestAlgorithm->parameters = ASN1_TYPE_new();
	idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL;

	hashlen = EVP_MD_size(md);
	hash = OPENSSL_malloc(hashlen);
	memset(hash, 0, hashlen);
	M_ASN1_OCTET_STRING_set(idc->messageDigest->digest, hash, hashlen);
	OPENSSL_free(hash);

	*len  = i2d_SpcIndirectDataContent(idc, NULL);
	*blob = OPENSSL_malloc(*len);
	p = *blob;
	i2d_SpcIndirectDataContent(idc, &p);
	SpcIndirectDataContent_free(idc);
}

static unsigned int calc_pe_checksum(BIO *bio, unsigned int peheader)
{
	unsigned int checkSum = 0;
	unsigned short	val;
	unsigned int size = 0;

	/* recalc checksum. */
	(void)BIO_seek(bio, 0);
	while (BIO_read(bio, &val, 2) == 2) {
		if (size == peheader + 88 || size == peheader + 90)
			val = 0;
		checkSum += val;
		checkSum = 0xffff & (checkSum + (checkSum >> 0x10));
		size += 2;
	}

	checkSum = 0xffff & (checkSum + (checkSum >> 0x10));
	checkSum += size;

	return checkSum;
}

static void recalc_pe_checksum(BIO *bio, unsigned int peheader)
{
	unsigned int checkSum = calc_pe_checksum(bio, peheader);
	char buf[4];

	/* write back checksum. */
	(void)BIO_seek(bio, peheader + 88);
	PUT_UINT32_LE(checkSum, buf);
	BIO_write(bio, buf, 4);
}

static unsigned char nib2val(unsigned char c) {
	if (c >= '0' && c <= '9') {
		return c - '0';
	} else if (c >= 'a' && c <= 'f') {
		return c - 'a' + 10;
	} else if (c >= 'A' && c <= 'F') {
		return c - 'A' + 10;
	}

	printf("Illegal hex value: '%x'\n", c);
	return 0;
}

static int verify_leaf_hash(X509 *leaf, const char *leafhash) {
	char *lhdup = NULL;
	char *orig = NULL;
	char *mdid = NULL;
	char *hash = NULL;
	int ret = 0;

	lhdup = strdup(leafhash);
	orig = lhdup;
	mdid = lhdup;
	while (lhdup != NULL && *lhdup != '\0') {
		if (*lhdup == ':') {
			*lhdup = '\0';
			++lhdup;
			hash = lhdup;
			break;
		}
		++lhdup;
	}
	lhdup = orig;

	if (hash == NULL) {
		printf("Unable to parse -require-leaf-hash parameter: %s\n\n", orig);
		ret = 1;
		goto out;
	}

	const EVP_MD *md = EVP_get_digestbyname(mdid);
	if (md == NULL) {
		printf("Unable to lookup digest by name '%s'\n", mdid);
		ret = 1;
		goto out;
	}

	unsigned long sz = EVP_MD_size(md);
	unsigned long actual = strlen(hash);
	if (actual%2 != 0) {
		printf("Hash length mismatch: length is uneven.\n");
		ret = 1;
		goto out;
	}
	actual /= 2;
	if (actual != sz) {
		printf("Hash length mismatch: '%s' digest must be %lu bytes long (got %lu bytes)\n", mdid, sz, actual);
		ret = 1;
		goto out;
	}

	unsigned char mdbuf[EVP_MAX_MD_SIZE];
	unsigned char cmdbuf[EVP_MAX_MD_SIZE];
	int i = 0, j = 0;
	while (i < sz*2) {
		unsigned char x;
		x = nib2val(hash[i+1]);
		x |= nib2val(hash[i]) << 4;
		mdbuf[j] = x;
		i += 2;
		j += 1;
	}

	unsigned long certlen = i2d_X509(leaf, NULL);
	unsigned char *certbuf = malloc(certlen);
	unsigned char *tmp = certbuf;
	i2d_X509(leaf, &tmp);

	EVP_MD_CTX *ctx = EVP_MD_CTX_create();
	EVP_DigestInit_ex(ctx, md, NULL);
	EVP_DigestUpdate(ctx, certbuf, certlen);
	EVP_DigestFinal_ex(ctx, cmdbuf, NULL);
	EVP_MD_CTX_destroy(ctx);

	free(certbuf);

	if (memcmp(mdbuf, cmdbuf, EVP_MD_size(md))) {
		ret = 1;
		goto out;
	}

out:
	free(lhdup);
	return ret;
}

// pkcs7_get_nested_signature exctracts a nested signature from p7.
// The caller is responsible for freeing the returned object.
//
// If has_sig is provided, it will be set to either 1 if there is a
// SPC_NESTED_SIGNATURE attribute in p7 at all or 0 if not.
// This allows has_sig to be used to distinguish two possible scenarios
// when the functon returns NULL: if has_sig is 1, it means d2i_PKCS7
// failed to decode the nested signature. However, if has_sig is 0, it
// simply means the given p7 does not have a nested signature.
static PKCS7 *pkcs7_get_nested_signature(PKCS7 *p7, int *has_sig) {
	PKCS7 *ret = NULL;
	PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(p7->d.sign->signer_info, 0);
	ASN1_TYPE *nestedSignature = PKCS7_get_attribute(si, OBJ_txt2nid(SPC_NESTED_SIGNATURE_OBJID));
	if (nestedSignature) {
		ASN1_STRING *astr = nestedSignature->value.sequence;
		const unsigned char *p = astr->data;
		ret = d2i_PKCS7(NULL, &p, astr->length);
	}
	if (has_sig)
		*has_sig = (nestedSignature != NULL);
	return ret;
}

// pkcs7_set_nested_signature adds the p7nest signature to p7
// as a nested signature (SPC_NESTED_SIGNATURE).
static int pkcs7_set_nested_signature(PKCS7 *p7, PKCS7 *p7nest) {
	u_char *p = NULL;
	int len = 0;

	if (((len = i2d_PKCS7(p7nest, NULL)) <= 0) ||
		(p = OPENSSL_malloc(len)) == NULL)
		return 0;

	i2d_PKCS7(p7nest, &p);
	p -= len;
	ASN1_STRING *astr = ASN1_STRING_new();
	ASN1_STRING_set(astr, p, len);
	OPENSSL_free(p);

	PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(p7->d.sign->signer_info, 0);
	if (PKCS7_add_attribute(si, OBJ_txt2nid(SPC_NESTED_SIGNATURE_OBJID), V_ASN1_SEQUENCE, astr) == 0)
		return 0;

	return 1;
}

#ifdef WITH_GSF
static gint msi_base64_decode(gint x)
{
	if (x < 10)
		return x + '0';
	if (x < (10 + 26))
		return x - 10 + 'A';
	if (x < (10 + 26 + 26))
		return x - 10 - 26 + 'a';
	if (x == (10 + 26 + 26))
		return '.';
	return 1;
}

static void msi_decode(const guint8 *in, gchar *out)
{
	guint count = 0;
	guint8 *q = (guint8 *)out;

	/* utf-8 encoding of 0x4840 */
	if (in[0] == 0xe4 && in[1] == 0xa1 && in[2] == 0x80)
		in += 3;

	while (*in) {
		guint8 ch = *in;
		if ((ch == 0xe3 && in[1] >= 0xa0) || (ch == 0xe4 && in[1] < 0xa0)) {
			*q++ = msi_base64_decode(in[2] & 0x7f);
			*q++ = msi_base64_decode(in[1] ^ 0xa0);
			in += 3;
			count += 2;
			continue;
		}
		if (ch == 0xe4 && in[1] == 0xa0) {
			*q++ = msi_base64_decode(in[2] & 0x7f);
			in += 3;
			count++;
			continue;
		}
		*q++ = *in++;
		if (ch >= 0xc1)
			*q++ = *in++;
		if (ch >= 0xe0)
			*q++ = *in++;
		if (ch >= 0xf0)
			*q++ = *in++;
		count++;
	}
	*q = 0;
}

/*
 * Sorry if this code looks a bit silly, but that seems
 * to be the best solution so far...
 */
static gint msi_cmp(gpointer a, gpointer b)
{
	glong anc = 0, bnc = 0;
	gchar *pa = (gchar*)g_utf8_to_utf16(a, -1, NULL, &anc, NULL);
	gchar *pb = (gchar*)g_utf8_to_utf16(b, -1, NULL, &bnc, NULL);
	gint diff;

	diff = memcmp(pa, pb, MIN(2*anc, 2*bnc));
	/* apparently the longer wins */
	if (diff == 0)
		return 2*anc > 2*bnc ? 1 : -1;
	g_free(pa);
	g_free(pb);

	return diff;
}

/*
 * msi_sorted_infile_children returns a sorted list of all
 * of the children of the given infile. The children are
 * sorted according to the msi_cmp.
 *
 * The returned list must be freed with g_slist_free.
 */
static GSList *msi_sorted_infile_children(GsfInfile *infile)
{
	GSList *sorted = NULL;
	gchar decoded[0x40];
	int i;

	for (i = 0; i < gsf_infile_num_children(infile); i++) {
		GsfInput *child = gsf_infile_child_by_index(infile, i);
		const guint8 *name = (const guint8*)gsf_input_name(child);
		msi_decode(name, decoded);

		if (!g_strcmp0(decoded, "\05DigitalSignature"))
			continue;
		if (!g_strcmp0(decoded, "\05MsiDigitalSignatureEx"))
			continue;
 
		sorted = g_slist_insert_sorted(sorted, (gpointer)name, (GCompareFunc)msi_cmp);
	}

	return sorted;
}

/*
 * msi_prehash_utf16_name converts an UTF-8 representation of
 * an MSI filename to its on-disk UTF-16 representation and
 * writes it to the hash BIO.  It is used when calculating the
 * pre-hash used for MsiDigitalSignatureEx signatures in MSI files.
 */
static gboolean msi_prehash_utf16_name(gchar *name, BIO *hash)
{
	glong chars_written = 0;

	gchar *u16name = (gchar*)g_utf8_to_utf16(name, -1, NULL, &chars_written, NULL);
	if (u16name == NULL) {
		return FALSE;
	}

	BIO_write(hash, u16name, 2*chars_written);

	g_free(u16name);

	return TRUE;
}

/*
 * msi_prehash calculates the pre-hash used for 'MsiDigitalSignatureEx'
 * signatures in MSI files.  The pre-hash hashes only metadata (file names,
 * file sizes, creation times and modification times), whereas the basic
 * 'DigitalSignature' MSI signature only hashes file content.
 *
 * The hash is written to the hash BIO.
 */
static gboolean msi_prehash(GsfInfile *infile, gchar *dirname, BIO *hash)
{
	GSList *sorted = NULL;
	guint8 classid[16];
	guint8 zeroes[8];

	memset(&zeroes, 0, sizeof(zeroes));

	gsf_infile_msole_get_class_id(GSF_INFILE_MSOLE(infile), classid);

	if (dirname != NULL) {
		if (!msi_prehash_utf16_name(dirname, hash))
			return FALSE;
	}

	BIO_write(hash, classid, sizeof(classid));
	BIO_write(hash, zeroes, 4);

	if (dirname != NULL) {
		/*
		 * Creation time and modification time for the root directory.
		 * These are always zero. The ctime and mtime of the actual
		 * file itself takes precedence.
		 */ 
		BIO_write(hash, zeroes, 8); // ctime as Windows FILETIME.
		BIO_write(hash, zeroes, 8); // mtime as Windows FILETIME.
	}

	sorted = msi_sorted_infile_children(infile);

	for (; sorted; sorted = sorted->next) {
		gchar *name = (gchar*)sorted->data;
		GsfInput *child =  gsf_infile_child_by_name(infile, name);
		if (child == NULL)
			continue;

		gboolean is_dir = GSF_IS_INFILE(child) && gsf_infile_num_children(GSF_INFILE(child)) > 0;
		if (is_dir) {
			if (!msi_prehash(GSF_INFILE(child), name, hash))
				return FALSE;
		} else {
			if (!msi_prehash_utf16_name(name, hash))
				return FALSE;

			/*
			 * File size.
			 */
			gsf_off_t size = gsf_input_remaining(child);
			guint32 sizebuf = GUINT32_TO_LE((guint32)size);
			BIO_write(hash, &sizebuf, sizeof(sizebuf));

			/*
			 * Reserved - must be 0. Corresponds to
			 * offset 0x7c..0x7f in the CDFv2 file.
			 */
			BIO_write(hash, zeroes, 4);

			/*
			 * Creation time and modification time
			 * as Windows FILETIMEs. We keep them
			 * zeroed, because libgsf doesn't seem
			 * to support outputting them.
			 */
			BIO_write(hash, zeroes, 8); // ctime as a Windows FILETIME
			BIO_write(hash, zeroes, 8); // mtime as a Windows FILETIME
		}
	}

	g_slist_free(sorted);

	return TRUE;
}

/**
 * msi_handle_dir performs a direct copy of the input MSI file in infile to a new
 * output file in outfile.  While copying, it also writes all file content to the
 * hash BIO in order to calculate a 'basic' hash that can be used for an MSI
 * 'DigitalSignature' hash.
 *
 * msi_handle_dir is hierarchy aware: if any subdirectories are found, they will be
 * visited, copied and hashed as well.
 */
static gboolean msi_handle_dir(GsfInfile *infile, GsfOutfile *outole, BIO *hash)
{
	guint8 classid[16];
	GSList *sorted = NULL;

	gsf_infile_msole_get_class_id(GSF_INFILE_MSOLE(infile), classid);
	if (outole != NULL)
		gsf_outfile_msole_set_class_id(GSF_OUTFILE_MSOLE(outole), classid);

	sorted = msi_sorted_infile_children(infile);

	for (; sorted; sorted = sorted->next) {
		gchar *name = (gchar*)sorted->data;
		GsfInput *child =  gsf_infile_child_by_name(infile, name);
		if (child == NULL)
			continue;

		gboolean is_dir = GSF_IS_INFILE(child) && gsf_infile_num_children(GSF_INFILE(child)) > 0;
		GsfOutput *outchild = NULL;
		if (outole != NULL)
			outchild = gsf_outfile_new_child(outole, name, is_dir);
		if (is_dir) {
			if (!msi_handle_dir(GSF_INFILE(child), GSF_OUTFILE(outchild), hash)) {
				return FALSE;
			}
		} else {
			while (gsf_input_remaining(child) > 0) {
				gsf_off_t size = MIN(gsf_input_remaining(child), 4096);
				guint8 const *data = gsf_input_read(child, size, NULL);
				BIO_write(hash, data, size);
				if (outchild != NULL && !gsf_output_write(outchild, size, data)) {
					return FALSE;
				}
			}
		}

		if (outchild != NULL) {
			gsf_output_close(outchild);
			g_object_unref(outchild);
		}
	}

	BIO_write(hash, classid, sizeof(classid));

	g_slist_free(sorted);

	return TRUE;
}

/*
 * msi_verify_pkcs7 is a helper function for msi_verify_file.
 * It exists to make it easier to implement verification of nested signatures.
 */
static int msi_verify_pkcs7(PKCS7 *p7, GsfInfile *infile, unsigned char *exdata, unsigned int exlen, char *leafhash, int allownest) {
	int i = 0;
	int ret = 0;
	X509_STORE *store = NULL;
	int mdtype = -1;
	unsigned char mdbuf[EVP_MAX_MD_SIZE];
	unsigned char cmdbuf[EVP_MAX_MD_SIZE];
#ifdef GSF_CAN_READ_MSI_METADATA
	unsigned char cexmdbuf[EVP_MAX_MD_SIZE];
#endif
	unsigned char hexbuf[EVP_MAX_MD_SIZE*2+1];
	BIO *bio = NULL;

	ASN1_OBJECT *indir_objid = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, 1);
	if (p7 && PKCS7_type_is_signed(p7) && !OBJ_cmp(p7->d.sign->contents->type, indir_objid) && p7->d.sign->contents->d.other->type == V_ASN1_SEQUENCE) {
		ASN1_STRING *astr = p7->d.sign->contents->d.other->value.sequence;
		const unsigned char *p = astr->data;
		SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, astr->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, idc->messageDigest->digest->length);
			}
			SpcIndirectDataContent_free(idc);
		}
		ASN1_OBJECT_free(indir_objid);
	}

	if (mdtype == -1) {
		printf("Failed to extract current message digest\n\n");
		ret = 1;
		goto out;
	}

	if (p7 == NULL) {
		printf("Failed to read PKCS7 signature from DigitalSignature section\n\n");
		ret = 1;
		goto out;
	}

	printf("Message digest algorithm         : %s\n", OBJ_nid2sn(mdtype));

	const EVP_MD *md = EVP_get_digestbynid(mdtype);
	BIO *hash = BIO_new(BIO_f_md());
	BIO_set_md(hash, md);
	BIO_push(hash, BIO_new(BIO_s_null()));

	if (exdata) {
		/*
		 * Until libgsf can read more MSI metadata, we can't
		 * really verify them by plowing through the file.
		 * Verifying files signed by osslsigncode itself works,
		 * though!
		 *
		 * For now, the compromise is to use the hash given
		 * by the file, which is equivalent to verifying a
		 * non-MsiDigitalSignatureEx signature from a security
		 * pespective, because we'll only be calculating the
		 * file content hashes ourselves.
		 */
#ifdef GSF_CAN_READ_MSI_METADATA
		BIO *prehash = BIO_new(BIO_f_md());
		BIO_set_md(prehash, md);
		BIO_push(prehash, BIO_new(BIO_s_null()));

		if (!msi_prehash(infile, NULL, prehash)) {
			ret = 1;
			goto out;
		}

		BIO_gets(prehash, (char*)cexmdbuf, EVP_MAX_MD_SIZE);
		BIO_write(hash, (char*)cexmdbuf, EVP_MD_size(md));
#else
		BIO_write(hash, (char *)exdata, EVP_MD_size(md));
#endif
	}

	if (!msi_handle_dir(infile, NULL, hash)) {
		ret = 1;
		goto out;
	}

	BIO_gets(hash, (char*)cmdbuf, EVP_MAX_MD_SIZE);
	tohex(cmdbuf, hexbuf, EVP_MD_size(md));
	int mdok = !memcmp(mdbuf, cmdbuf, EVP_MD_size(md));
	if (!mdok) ret = 1;
	printf("Calculated DigitalSignature      : %s", hexbuf);
	if (mdok) {
		printf("\n");
	} else {
		tohex(mdbuf, hexbuf, EVP_MD_size(md));
		printf("    MISMATCH!!! FILE HAS %s\n", hexbuf);
	}

#ifdef GSF_CAN_READ_MSI_METADATA
	if (exdata) {
		tohex(cexmdbuf, hexbuf, EVP_MD_size(md));
		int exok = !memcmp(exdata, cexmdbuf, MIN(EVP_MD_size(md), exlen));
		if (!exok) ret = 1;
		printf("Calculated MsiDigitalSignatureEx : %s", hexbuf);
		if (exok) {
			printf("\n");
		} else {
			tohex(exdata, hexbuf, EVP_MD_size(md));
			printf("    MISMATCH!!! FILE HAS %s\n", hexbuf);
		}
	}
#endif

	printf("\n");

	int seqhdrlen = asn1_simple_hdr_len(p7->d.sign->contents->d.other->value.sequence->data,
										p7->d.sign->contents->d.other->value.sequence->length);
	bio = BIO_new_mem_buf(p7->d.sign->contents->d.other->value.sequence->data + seqhdrlen,
						  p7->d.sign->contents->d.other->value.sequence->length - seqhdrlen);
	store = X509_STORE_new();
	int verok = PKCS7_verify(p7, p7->d.sign->cert, store, bio, NULL, PKCS7_NOVERIFY);
	BIO_free(bio);
	/* XXX: add more checks here (attributes, timestamp, etc) */
	printf("Signature verification: %s\n\n", verok ? "ok" : "failed");
	if (!verok) {
		ERR_print_errors_fp(stdout);
		ret = 1;
	}

	int leafok = 0;
	STACK_OF(X509) *signers = PKCS7_get0_signers(p7, NULL, 0);
	printf("Number of signers: %d\n", sk_X509_num(signers));
	for (i=0; i<sk_X509_num(signers); i++) {
		X509 *cert = sk_X509_value(signers, i);
		char *subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
		char *issuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
		printf("\tSigner #%d:\n\t\tSubject : %s\n\t\tIssuer  : %s\n", i, subject, issuer);
		OPENSSL_free(subject);
		OPENSSL_free(issuer);

		if (leafhash != NULL && leafok == 0) {
			leafok = verify_leaf_hash(cert, leafhash) == 0;
		}
	}
	sk_X509_free(signers);

	printf("\nNumber of certificates: %d\n", sk_X509_num(p7->d.sign->cert));
	for (i=0; i<sk_X509_num(p7->d.sign->cert); i++) {
		X509 *cert = sk_X509_value(p7->d.sign->cert, i);
		char *subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
		char *issuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
		printf("\tCert #%d:\n\t\tSubject : %s\n\t\tIssuer  : %s\n", i, subject, issuer);
		OPENSSL_free(subject);
		OPENSSL_free(issuer);
    }

	printf("\n");

	if (leafhash != NULL) {
		printf("Leaf hash match: %s\n\n", leafok ? "ok" : "failed");
		if (!leafok)
			ret = 1;
	}

	if (allownest) {
		int has_sig = 0;
		PKCS7 *p7nest = pkcs7_get_nested_signature(p7, &has_sig);
		if (p7nest) {
			printf("\n");
			int nest_ret = msi_verify_pkcs7(p7nest, infile, exdata, exlen, leafhash, 0);
			if (ret == 0)
				ret = nest_ret;
			PKCS7_free(p7nest);
		} else if (!p7nest && has_sig) {
			printf("\nFailed to decode nested signature!\n");
			ret = 1;
		} else
			printf("\n");
	} else
		printf("\n");

out:
	if (store)
		X509_STORE_free(store);

	return ret;
}

/*
 * msi_verify_file checks whether or not the signature of infile is valid.
 */
static int msi_verify_file(GsfInfile *infile, char *leafhash) {
	GsfInput *sig = NULL;
	GsfInput *exsig = NULL;
	unsigned char *exdata = NULL;
	unsigned char *indata = NULL;
	gchar decoded[0x40];
	int i, ret = 0;
	PKCS7 *p7 = NULL;

	for (i = 0; i < gsf_infile_num_children(infile); i++) {
		GsfInput *child = gsf_infile_child_by_index(infile, i);
		const guint8 *name = (const guint8*)gsf_input_name(child);
		msi_decode(name, decoded);
		if (!g_strcmp0(decoded, "\05DigitalSignature"))
			sig = child;
		if (!g_strcmp0(decoded, "\05MsiDigitalSignatureEx"))
			exsig = child;
	}
	if (sig == NULL) {
		printf("MSI file has no signature\n\n");
		ret = 1;
		goto out;
	}

	unsigned long inlen = (unsigned long) gsf_input_remaining(sig);
	indata = malloc(inlen);
	if (gsf_input_read(sig, inlen, indata) == NULL) {
		ret = 1;
		goto out;
	}

	unsigned long exlen = 0;
	if (exsig != NULL) {
		exlen = (unsigned long) gsf_input_remaining(exsig);
		exdata = malloc(exlen);
		if (gsf_input_read(exsig, exlen, exdata) == NULL) {
			ret = 1;
			goto out;
		}
	}

	const unsigned char *blob = (unsigned char *)indata;
	p7 = d2i_PKCS7(NULL, &blob, inlen);

	ret = msi_verify_pkcs7(p7, infile, exdata, exlen, leafhash, 1);

out:
	free(indata);
	free(exdata);

	if (p7)
		PKCS7_free(p7);

	return ret;
}

static int msi_extract_dse(GsfInfile *infile, unsigned char **dsebuf, unsigned long *dselen, int *has_dse) {
	GsfInput *exsig = NULL;
	gchar decoded[0x40];
	u_char *buf = NULL;
	gsf_off_t size = 0;
	int i, ret = 0;

	for (i = 0; i < gsf_infile_num_children(infile); i++) {
		GsfInput *child = gsf_infile_child_by_index(infile, i);
		const guint8 *name = (const guint8*)gsf_input_name(child);
		msi_decode(name, decoded);
		if (!g_strcmp0(decoded, "\05MsiDigitalSignatureEx"))
			exsig = child;
	}
	if (exsig == NULL) {
		ret = 1;
		goto out;
	}

	if (has_dse != NULL) {
		*has_dse = 1;
	}

	size = gsf_input_remaining(exsig);

	if (dselen != NULL) {
		*dselen = (unsigned long) size;
	}

	if (dsebuf != NULL) {
		buf = malloc(size);
		if (gsf_input_read(exsig, size, buf) == NULL) {
			ret = 1;
			goto out;
		}
		*dsebuf = (unsigned char *) buf;
	}

out:
	return ret;
}

/*
 * msi_extract_signature_to_file extracts the MSI DigitalSignaure from infile
 * to a file at the path given by outfile.
 */
static int msi_extract_signature_to_file(GsfInfile *infile, char *outfile) {
	unsigned char hexbuf[EVP_MAX_MD_SIZE*2+1];
	GsfInput *sig = NULL;
	GsfInput *exsig = NULL;
	unsigned char *exdata = NULL;
	unsigned long exlen = 0;
	BIO *outdata = NULL;
	gchar decoded[0x40];
	int i, ret = 0;

	for (i = 0; i < gsf_infile_num_children(infile); i++) {
		GsfInput *child = gsf_infile_child_by_index(infile, i);
		const guint8 *name = (const guint8*)gsf_input_name(child);
		msi_decode(name, decoded);
		if (!g_strcmp0(decoded, "\05DigitalSignature"))
			sig = child;
		if (!g_strcmp0(decoded, "\05MsiDigitalSignatureEx"))
			exsig = child;
	}
	if (sig == NULL) {
		printf("MSI file has no signature\n\n");
		ret = 1;
		goto out;
	}

	/* Create outdata file */
	outdata = BIO_new_file(outfile, "w+b");
	if (outdata == NULL) {
		printf("Unable to open %s\n\n", outfile);
		ret = 1;
		goto out;
	}

	while (gsf_input_remaining(sig) > 0) {
		gsf_off_t size = MIN(gsf_input_remaining(sig), 4096);
		guint8 const *data = gsf_input_read(sig, size, NULL);
		BIO_write(outdata, data, size);
	}

	if (exsig != NULL) {
		exlen = (unsigned long) gsf_input_remaining(exsig);
		if (exlen > EVP_MAX_MD_SIZE) {
			printf("MsiDigitalSignatureEx is larger than EVP_MAX_MD_SIZE. Aborting...\n\n");
			ret = 1;
			goto out;
		}

		exdata = malloc(exlen);
		if (gsf_input_read(exsig, exlen, exdata) == NULL) {
			printf("Unable to read MsiDigitalSignatureEx\n\n");
			ret = 1;
			goto out;
		}

		tohex(exdata, hexbuf, exlen);
		printf("Note: MSI includes a MsiDigitalSignatureEx section.\n");
		printf("MsiDigitalSignatureEx pre-hash: %s\n\n", hexbuf);
	}

out:
	free(exdata);
	if (outdata)
		BIO_free_all(outdata);

	return ret;
}

static PKCS7 *msi_extract_signature_to_pkcs7(GsfInfile *infile) {
	GsfInput *sig = NULL;
	gchar decoded[0x40];
	PKCS7 *p7 = NULL;
	u_char *buf = NULL;
	gsf_off_t size = 0;
	int i = 0;

	for (i = 0; i < gsf_infile_num_children(infile); i++) {
		GsfInput *child = gsf_infile_child_by_index(infile, i);
		const guint8 *name = (const guint8*)gsf_input_name(child);
		msi_decode(name, decoded);
		if (!g_strcmp0(decoded, "\05DigitalSignature"))
			sig = child;
	}
	if (sig == NULL) {
		goto out;
	}

	size = gsf_input_remaining(sig);
	buf = malloc(size);
	if (gsf_input_read(sig, size, buf) == NULL) {
		goto out;
	}

	const unsigned char *p7buf = buf;
	p7 = d2i_PKCS7(NULL, &p7buf, size);

out:
	free(buf);
	return p7;
}

#endif

static void calc_pe_digest(BIO *bio, const EVP_MD *md, unsigned char *mdbuf,
						   unsigned int peheader, int pe32plus, unsigned int fileend)
{
	static unsigned char bfb[16*1024*1024];
	EVP_MD_CTX mdctx;

	EVP_MD_CTX_init(&mdctx);
	EVP_DigestInit(&mdctx, md);

	memset(mdbuf, 0, EVP_MAX_MD_SIZE);

	(void)BIO_seek(bio, 0);
	BIO_read(bio, bfb, peheader + 88);
	EVP_DigestUpdate(&mdctx, bfb, peheader + 88);
	BIO_read(bio, bfb, 4);
	BIO_read(bio, bfb, 60+pe32plus*16);
	EVP_DigestUpdate(&mdctx, bfb, 60+pe32plus*16);
	BIO_read(bio, bfb, 8);

	unsigned int n = peheader + 88 + 4 + 60+pe32plus*16 + 8;
	while (n < fileend) {
		int want = fileend - n;
		if (want > sizeof(bfb))
			want = sizeof(bfb);
		int l = BIO_read(bio, bfb, want);
		if (l <= 0)
			break;
		EVP_DigestUpdate(&mdctx, bfb, l);
		n += l;
	}

	EVP_DigestFinal(&mdctx, mdbuf, NULL);
}


static void	extract_page_hash (SpcAttributeTypeAndOptionalValue *obj,
							   unsigned char **ph, unsigned int *phlen, int *phtype)
{
	*phlen = 0;

	const unsigned char *blob = obj->value->value.sequence->data;
	SpcPeImageData *id = d2i_SpcPeImageData(NULL, &blob, obj->value->value.sequence->length);
	if (id == NULL)
		return;

	if (id->file->type != 1) {
		SpcPeImageData_free(id);
		return;
	}

	SpcSerializedObject *so = id->file->value.moniker;
	if (so->classId->length != sizeof(classid_page_hash) ||
		memcmp(so->classId->data, classid_page_hash, sizeof (classid_page_hash))) {
		SpcPeImageData_free(id);
		return;
	}

	/* skip ASN.1 SET hdr */
	unsigned int l = asn1_simple_hdr_len(so->serializedData->data, so->serializedData->length);
	blob = so->serializedData->data + l;
	obj = d2i_SpcAttributeTypeAndOptionalValue(NULL, &blob, so->serializedData->length - l);
	SpcPeImageData_free(id);
	if (!obj)
		return;

	char buf[128];
	*phtype = 0;
	buf[0] = 0x00;
	OBJ_obj2txt(buf, sizeof(buf), obj->type, 1);
	if (!strcmp(buf, SPC_PE_IMAGE_PAGE_HASHES_V1)) {
		*phtype = NID_sha1;
	} else if (!strcmp(buf, SPC_PE_IMAGE_PAGE_HASHES_V2)) {
		*phtype = NID_sha256;
	} else {
		SpcAttributeTypeAndOptionalValue_free(obj);
		return;
	}

	/* Skip ASN.1 SET hdr */
	unsigned int l2 = asn1_simple_hdr_len(obj->value->value.sequence->data, obj->value->value.sequence->length);
	/* Skip ASN.1 OCTET STRING hdr */
	l =  asn1_simple_hdr_len(obj->value->value.sequence->data + l2, obj->value->value.sequence->length - l2);
	l += l2;
	*phlen = obj->value->value.sequence->length - l;
	*ph = malloc(*phlen);
	memcpy(*ph, obj->value->value.sequence->data + l, *phlen);
	SpcAttributeTypeAndOptionalValue_free(obj);
}

static unsigned char *calc_page_hash(char *indata, unsigned int peheader, int pe32plus,
									 unsigned int sigpos, int phtype, unsigned int *rphlen)
{
	unsigned short nsections = GET_UINT16_LE(indata + peheader + 6);
	unsigned int pagesize = GET_UINT32_LE(indata + peheader + 56);
	unsigned int hdrsize = GET_UINT32_LE(indata + peheader + 84);
	const EVP_MD *md = EVP_get_digestbynid(phtype);
	int pphlen = 4 + EVP_MD_size(md);
	int phlen = pphlen * (3 + nsections + sigpos / pagesize);
	unsigned char *res = malloc(phlen);
	unsigned char *zeroes = calloc(pagesize, 1);
	EVP_MD_CTX mdctx;

	EVP_MD_CTX_init(&mdctx);
	EVP_DigestInit(&mdctx, md);
	EVP_DigestUpdate(&mdctx, indata, peheader + 88);
	EVP_DigestUpdate(&mdctx, indata + peheader + 92, 60 + pe32plus*16);
	EVP_DigestUpdate(&mdctx, indata + peheader + 160 + pe32plus*16, hdrsize - (peheader + 160 + pe32plus*16));
	EVP_DigestUpdate(&mdctx, zeroes, pagesize - hdrsize);
	memset(res, 0, 4);
	EVP_DigestFinal(&mdctx, res + 4, NULL);

	unsigned short sizeofopthdr = GET_UINT16_LE(indata + peheader + 20);
	char *sections = indata + peheader + 24 + sizeofopthdr;
	int i, pi = 1;
	unsigned int lastpos = 0;
	for (i=0; i<nsections; i++) {
		unsigned int rs = GET_UINT32_LE(sections + 16);
		unsigned int ro = GET_UINT32_LE(sections + 20);
		unsigned int l;
		for (l=0; l < rs; l+=pagesize, pi++) {
			PUT_UINT32_LE(ro + l, res + pi*pphlen);
			EVP_DigestInit(&mdctx, md);
			if (rs - l < pagesize) {
				EVP_DigestUpdate(&mdctx, indata + ro + l, rs - l);
				EVP_DigestUpdate(&mdctx, zeroes, pagesize - (rs - l));
			} else {
				EVP_DigestUpdate(&mdctx, indata + ro + l, pagesize);
			}
			EVP_DigestFinal(&mdctx, res + pi*pphlen + 4, NULL);
		}
		lastpos = ro + rs;
		sections += 40;
	}
	PUT_UINT32_LE(lastpos, res + pi*pphlen);
	memset(res + pi*pphlen + 4, 0, EVP_MD_size(md));
	pi++;
	free(zeroes);
	*rphlen = pi*pphlen;
	return res;
}

static int verify_pe_pkcs7(PKCS7 *p7, char *indata, unsigned int peheader, int pe32plus,
						   unsigned int sigpos, unsigned int siglen, char *leafhash,
						   int allownest)
{
	int ret = 0;
	int mdtype = -1, phtype = -1;
	unsigned char mdbuf[EVP_MAX_MD_SIZE];
	unsigned char cmdbuf[EVP_MAX_MD_SIZE];
	unsigned char hexbuf[EVP_MAX_MD_SIZE*2+1];
	unsigned char *ph = NULL;
	unsigned int phlen = 0;
	BIO *bio = NULL;

	ASN1_OBJECT *indir_objid = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, 1);
	if (PKCS7_type_is_signed(p7) &&
		!OBJ_cmp(p7->d.sign->contents->type, indir_objid) &&
		p7->d.sign->contents->d.other->type == V_ASN1_SEQUENCE) {
		ASN1_STRING *astr = p7->d.sign->contents->d.other->value.sequence;
		const unsigned char *p = astr->data;
		SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, astr->length);
		if (idc) {
			extract_page_hash (idc->data, &ph, &phlen, &phtype);
			if (idc->messageDigest && idc->messageDigest->digest && idc->messageDigest->digestAlgorithm) {
				mdtype = OBJ_obj2nid(idc->messageDigest->digestAlgorithm->algorithm);
				memcpy(mdbuf, idc->messageDigest->digest->data, idc->messageDigest->digest->length);
			}
			SpcIndirectDataContent_free(idc);
		}
	}
	ASN1_OBJECT_free(indir_objid);

	if (mdtype == -1) {
		printf("Failed to extract current message digest\n\n");
		return -1;
	}

	printf("Message digest algorithm  : %s\n", OBJ_nid2sn(mdtype));

	const EVP_MD *md = EVP_get_digestbynid(mdtype);
	tohex(mdbuf, hexbuf, EVP_MD_size(md));
	printf("Current message digest    : %s\n", hexbuf);

	bio = BIO_new_mem_buf(indata, sigpos + siglen);
	calc_pe_digest(bio, md, cmdbuf, peheader, pe32plus, sigpos);
	BIO_free(bio);
	tohex(cmdbuf, hexbuf, EVP_MD_size(md));
	int mdok = !memcmp(mdbuf, cmdbuf, EVP_MD_size(md));
	if (!mdok) ret = 1;
	printf("Calculated message digest : %s%s\n\n", hexbuf, mdok?"":"    MISMATCH!!!");

	if (phlen > 0) {
		printf("Page hash algorithm  : %s\n", OBJ_nid2sn(phtype));
		tohex(ph, hexbuf, (phlen < 32) ? phlen : 32);
		printf("Page hash            : %s ...\n", hexbuf);
		unsigned int cphlen = 0;
		unsigned char *cph = calc_page_hash(indata, peheader, pe32plus, sigpos, phtype, &cphlen);
		tohex(cph, hexbuf, (cphlen < 32) ? cphlen : 32);
		printf("Calculated page hash : %s ...%s\n\n", hexbuf,
			   ((phlen != cphlen) || memcmp(ph, cph, phlen)) ? "    MISMATCH!!!":"");
		free(ph);
		free(cph);
	}

	int seqhdrlen = asn1_simple_hdr_len(p7->d.sign->contents->d.other->value.sequence->data,
										p7->d.sign->contents->d.other->value.sequence->length);
	bio = BIO_new_mem_buf(p7->d.sign->contents->d.other->value.sequence->data + seqhdrlen,
						  p7->d.sign->contents->d.other->value.sequence->length - seqhdrlen);
	X509_STORE *store = X509_STORE_new();
	int verok = PKCS7_verify(p7, p7->d.sign->cert, store, bio, NULL, PKCS7_NOVERIFY);
	BIO_free(bio);
	/* XXX: add more checks here (attributes, timestamp, etc) */
	printf("Signature verification: %s\n\n", verok ? "ok" : "failed");
	if (!verok) {
		ERR_print_errors_fp(stdout);
		ret = 1;
	}

	int i;
	int leafok = 0;
	STACK_OF(X509) *signers = PKCS7_get0_signers(p7, NULL, 0);
	printf("Number of signers: %d\n", sk_X509_num(signers));
	for (i=0; i<sk_X509_num(signers); i++) {
		X509 *cert = sk_X509_value(signers, i);
		char *subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
		char *issuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
		printf("\tSigner #%d:\n\t\tSubject: %s\n\t\tIssuer : %s\n", i, subject, issuer);
		OPENSSL_free(subject);
		OPENSSL_free(issuer);

		if (leafhash != NULL && leafok == 0) {
			leafok = verify_leaf_hash(cert, leafhash) == 0;
		}
	}
	sk_X509_free(signers);

	printf("\nNumber of certificates: %d\n", sk_X509_num(p7->d.sign->cert));
	for (i=0; i<sk_X509_num(p7->d.sign->cert); i++) {
		X509 *cert = sk_X509_value(p7->d.sign->cert, i);
		char *subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
		char *issuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
		printf("\tCert #%d:\n\t\tSubject: %s\n\t\tIssuer : %s\n", i, subject, issuer);
		OPENSSL_free(subject);
		OPENSSL_free(issuer);
	}

	if (leafhash != NULL) {
		printf("\nLeaf hash match: %s\n", leafok ? "ok" : "failed");
		if (!leafok)
			ret = 1;
	}

	if (allownest) {
		int has_sig = 0;
		PKCS7 *p7nest = pkcs7_get_nested_signature(p7, &has_sig);
		if (p7nest) {
			printf("\n");
			int nest_ret = verify_pe_pkcs7(p7nest, indata, peheader, pe32plus, sigpos, siglen, leafhash, 0);
			if (ret == 0)
				ret = nest_ret;
			PKCS7_free(p7nest);
		} else if (!p7nest && has_sig) {
			printf("\nFailed to decode nested signature!\n");
			ret = 1;
		} else
			printf("\n");
	} else
		printf("\n");

	X509_STORE_free(store);

	return ret;
}

static int verify_pe_file(char *indata, unsigned int peheader, int pe32plus,
						  unsigned int sigpos, unsigned int siglen, char *leafhash)
{
	int ret = 0;
	unsigned int pe_checksum = GET_UINT32_LE(indata + peheader + 88);
	printf("Current PE checksum   : %08X\n", pe_checksum);

	BIO *bio = BIO_new_mem_buf(indata, sigpos + siglen);
	unsigned int real_pe_checksum = calc_pe_checksum(bio, peheader);
	BIO_free(bio);
	if (pe_checksum && pe_checksum != real_pe_checksum)
		ret = 1;
	printf("Calculated PE checksum: %08X%s\n\n", real_pe_checksum,
		   ret ? "     MISMATCH!!!!" : "");
	if (siglen == 0) {
		printf("No signature found.\n\n");
		return ret;
	}

	unsigned int pos = 0;
	PKCS7 *p7 = NULL;

	while (pos < siglen) {
		unsigned int l = GET_UINT32_LE(indata + sigpos + pos);
		unsigned short certrev  = GET_UINT16_LE(indata + sigpos + pos + 4);
		unsigned short certtype = GET_UINT16_LE(indata + sigpos + pos + 6);
		if (certrev == WIN_CERT_REVISION_2 && certtype == WIN_CERT_TYPE_PKCS_SIGNED_DATA) {
			const unsigned char *blob = (unsigned char*)indata + sigpos + pos + 8;
			p7 = d2i_PKCS7(NULL, &blob, l - 8);
		}
		if (l%8)
			l += (8 - l%8);
		pos += l;
	}

	if (p7 == NULL) {
		printf("Failed to extract PKCS7 data\n\n");
		return -1;
	}

	ret = verify_pe_pkcs7(p7, indata, peheader, pe32plus, sigpos, siglen, leafhash, 1);
	PKCS7_free(p7);
	return ret;
}

// extract_existing_pe_pkcs7 retreives a decoded PKCS7 struct corresponding to the
// existing signature of the PE file.
static PKCS7 *extract_existing_pe_pkcs7(char *indata, unsigned int peheader, int pe32plus,
									   unsigned int sigpos, unsigned int siglen)
{
	unsigned int pos = 0;
	PKCS7 *p7 = NULL;

	while (pos < siglen) {
		unsigned int l = GET_UINT32_LE(indata + sigpos + pos);
		unsigned short certrev  = GET_UINT16_LE(indata + sigpos + pos + 4);
		unsigned short certtype = GET_UINT16_LE(indata + sigpos + pos + 6);
		if (certrev == WIN_CERT_REVISION_2 && certtype == WIN_CERT_TYPE_PKCS_SIGNED_DATA) {
			const unsigned char *blob = (unsigned char*)indata + sigpos + pos + 8;
			p7 = d2i_PKCS7(NULL, &blob, l - 8);
		}
		if (l%8)
			l += (8 - l%8);
		pos += l;
	}

	return p7;
}

static STACK_OF(X509) *PEM_read_certs_with_pass(BIO *bin, char *certpass)
{
	STACK_OF(X509) *certs = sk_X509_new_null();
	X509 *x509;
	(void)BIO_seek(bin, 0);
	while((x509 = PEM_read_bio_X509(bin, NULL, NULL, certpass)))
		sk_X509_push(certs, x509);
	if (!sk_X509_num(certs)) {
		sk_X509_free(certs);
		return NULL;
	}
	return certs;
}

static STACK_OF(X509) *PEM_read_certs(BIO *bin, char *certpass)
{
	STACK_OF(X509) *certs = PEM_read_certs_with_pass(bin, certpass);
	if (!certs)
		certs = PEM_read_certs_with_pass(bin, NULL);
	return certs;
}


static off_t get_file_size(const char *infile)
{
#ifdef WIN32
	struct _stat st;
	if (_stat(infile, &st))
#else
	struct stat st;
	if (stat(infile, &st))
#endif
	{
		fprintf(stderr, "Failed to open file: %s\n", infile);
		return 0;
	}

	if (st.st_size < 4) {
		fprintf(stderr, "Unrecognized file type - file is too short: %s\n", infile);
		return 0;
	}
	return st.st_size;
}

static char* map_file(const char *infile, const off_t size)
{
	char *indata = NULL;
#ifdef WIN32
	HANDLE fh, fm;
	fh = CreateFile(infile, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
	if (fh == INVALID_HANDLE_VALUE)
		return NULL;
	fm = CreateFileMapping(fh, NULL, PAGE_READONLY, 0, 0, NULL);
	if (fm == NULL)
		return NULL;
	indata = MapViewOfFile(fm, FILE_MAP_READ, 0, 0, 0);
#else
	int fd = open(infile, O_RDONLY);
	if (fd < 0)
		return NULL;
	indata = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
	if (indata == MAP_FAILED)
		return NULL;
#endif
	return indata;
}

#ifdef PROVIDE_ASKPASS
char *getpassword(const char *prompt)
{
#ifdef HAVE_TERMIOS_H
	struct termios ofl, nfl;
    char *p, passbuf[1024];

    fputs(prompt, stdout);

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

    if (tcsetattr(fileno(stdin), TCSANOW, &nfl) != 0) {
	    fprintf(stderr, "Failed to set terminal attributes\n");
		return NULL;
    }

    p = fgets(passbuf, sizeof(passbuf), stdin);

    if (tcsetattr(fileno(stdin), TCSANOW, &ofl) != 0)
	    fprintf(stderr, "Failed to restore terminal attributes\n");

	if (!p) {
	    fprintf(stderr, "Failed to read password\n");
		return NULL;
    }

	passbuf[strlen(passbuf)-1] = 0x00;
	char *pass = strdup(passbuf);
	memset(passbuf, 0, sizeof(passbuf));
	return pass;
#else
	return getpass(prompt);
#endif
}
#endif

int main(int argc, char **argv)
{
	BIO *btmp, *sigbio, *hash, *outdata;
	PKCS12 *p12;
	PKCS7 *p7 = NULL, *cursig = NULL, *outsig = NULL, *sig, *p7x = NULL;
	X509 *cert = NULL;
	STACK_OF(X509) *certs = NULL, *xcerts = NULL;
	EVP_PKEY *pkey = NULL;
	PKCS7_SIGNER_INFO *si;
	ASN1_STRING *astr;
	const EVP_MD *md;

	const char *argv0 = argv[0];
	static char buf[64*1024];
	char *xcertfile, *certfile, *keyfile, *pvkfile, *pkcs12file, *infile, *outfile, *desc, *url, *indata;
	char *pass = NULL, *readpass = NULL;
	int askpass = 0;
	char *leafhash = NULL;
#ifdef ENABLE_CURL
	char *turl[MAX_TS_SERVERS], *proxy = NULL, *tsurl[MAX_TS_SERVERS];
#endif
	int nest = 0;
	int add_msi_dse = 0;
	int nturl = 0, ntsurl = 0;
	u_char *p = NULL;
	int ret = 0, i, len = 0, jp = -1, pe32plus = 0, comm = 0, pagehash = 0;
	unsigned int tmp, peheader = 0, padlen = 0;
	off_t filesize, fileend;
	file_type_t type;
	cmd_type_t cmd = CMD_SIGN;
	char *failarg = NULL;

	static u_char purpose_ind[] = {
		0x30, 0x0c,
		0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x15
	};

	static u_char purpose_comm[] = {
		0x30, 0x0c,
		0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x16
	};

	static u_char msi_signature[] = {
		0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1
	};

#ifdef WITH_GSF
	GsfOutfile *outole = NULL;
	GsfOutput *sink = NULL;
	u_char *p_msiex = NULL;
	int len_msiex = 0;
#endif

	xcertfile = certfile = keyfile = pvkfile = pkcs12file = infile = outfile = desc = url = NULL;
	hash = outdata = NULL;

	/* Set up OpenSSL */
	ERR_load_crypto_strings();
	OPENSSL_add_all_algorithms_conf();

	/* create some MS Authenticode OIDS we need later on */
	if (!OBJ_create(SPC_STATEMENT_TYPE_OBJID, NULL, NULL) ||
		!OBJ_create(SPC_MS_JAVA_SOMETHING, NULL, NULL) ||
		!OBJ_create(SPC_SP_OPUS_INFO_OBJID, NULL, NULL) ||
		!OBJ_create(SPC_NESTED_SIGNATURE_OBJID, NULL, NULL))
		DO_EXIT_0("Failed to add objects\n");

	md = EVP_sha1();

	if (argc > 1) {
		if (!strcmp(argv[1], "sign")) {
			cmd = CMD_SIGN;
			argv++;
			argc--;
		} else if (!strcmp(argv[1], "extract-signature")) {
			cmd = CMD_EXTRACT;
			argv++;
			argc--;
		} else if (!strcmp(argv[1], "remove-signature")) {
			cmd = CMD_REMOVE;
			argv++;
			argc--;
		} else if (!strcmp(argv[1], "verify")) {
			cmd = CMD_VERIFY;
			argv++;
			argc--;
		}
	}

	for (argc--,argv++; argc >= 1; argc--,argv++) {
		if (!strcmp(*argv, "-in")) {
			if (--argc < 1) usage(argv0);
			infile = *(++argv);
		} else if (!strcmp(*argv, "-out")) {
			if (--argc < 1) usage(argv0);
			outfile = *(++argv);
		} else if ((cmd == CMD_SIGN) && (!strcmp(*argv, "-spc") || !strcmp(*argv, "-certs"))) {
			if (--argc < 1) usage(argv0);
			certfile = *(++argv);
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-ac")) {
			if (--argc < 1) usage(argv0);
			xcertfile = *(++argv);
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-key")) {
			if (--argc < 1) usage(argv0);
			keyfile = *(++argv);
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-pkcs12")) {
			if (--argc < 1) usage(argv0);
			pkcs12file = *(++argv);
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-pass")) {
			if (askpass || readpass) usage(argv0);
			if (--argc < 1) usage(argv0);
			pass = strdup(*(++argv));
			memset(*argv, 0, strlen(*argv));
#ifdef PROVIDE_ASKPASS
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-askpass")) {
			if (pass || readpass) usage(argv0);
			askpass = 1;
#endif
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-readpass")) {
			if (askpass || pass) usage(argv0);
			if (--argc < 1) usage(argv0);
			readpass = *(++argv);
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-comm")) {
			comm = 1;
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-ph")) {
			pagehash = 1;
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-n")) {
			if (--argc < 1) usage(argv0);
			desc = *(++argv);
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-h")) {
			if (--argc < 1) usage(argv0);
			++argv;
			if (!strcmp(*argv, "md5")) {
				md = EVP_md5();
			} else if (!strcmp(*argv, "sha1")) {
				md = EVP_sha1();
			} else if (!strcmp(*argv, "sha2") || !strcmp(*argv, "sha256")) {
				md = EVP_sha256();
			} else if (!strcmp(*argv, "sha384")) {
				md = EVP_sha384();
			} else if (!strcmp(*argv, "sha512")) {
				md = EVP_sha512();
			} else {
				usage(argv0);
			}
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-i")) {
			if (--argc < 1) usage(argv0);
			url = *(++argv);
#ifdef ENABLE_CURL
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-t")) {
			if (--argc < 1) usage(argv0);
			turl[nturl++] = *(++argv);
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-ts")) {
			if (--argc < 1) usage(argv0);
			tsurl[ntsurl++] = *(++argv);
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-p")) {
			if (--argc < 1) usage(argv0);
			proxy = *(++argv);
#endif
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-nest")) {
			nest = 1;
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-add-msi-dse")) {
			add_msi_dse = 1;
		} else if ((cmd == CMD_VERIFY) && !strcmp(*argv, "-require-leaf-hash")) {
			if (--argc < 1) usage(argv0);
			leafhash = (*++argv); 
		} else if (!strcmp(*argv, "-v") || !strcmp(*argv, "--version")) {
			printf(PACKAGE_STRING ", using:\n\t%s\n\t%s\n",
				   SSLeay_version(SSLEAY_VERSION),
#ifdef ENABLE_CURL
				   curl_version()
#else
				   "no libcurl available"
#endif
			   );
			printf(
#ifdef WITH_GSF
				   "\tlibgsf %d.%d.%d\n",
				   libgsf_major_version,
				   libgsf_minor_version,
				   libgsf_micro_version
#else
				   "\tno libgsf available\n"
#endif
				);
			printf("\nPlease send bug-reports to "
				   PACKAGE_BUGREPORT
				   "\n\n");

		} else if (!strcmp(*argv, "-jp")) {
			char *ap;
			if (--argc < 1) usage(argv0);
			ap = *(++argv);
			for (i=0; ap[i]; i++) ap[i] = tolower((int)ap[i]);
			if (!strcmp(ap, "low")) {
				jp = 0;
			} else if (!strcmp(ap, "medium")) {
				jp = 1;
			} else if (!strcmp(ap, "high")) {
				jp = 2;
			}
			if (jp != 0) usage(argv0); /* XXX */
		} else {
			failarg = *argv;
			break;
		}
	}

	if (!infile && argc > 0) {
		infile = *(argv++);
		argc--;
	}

	if (cmd != CMD_VERIFY && (!outfile && argc > 0)) {
		if (!strcmp(*argv, "-out")) {
			argv++;
			argc--;
		}
		if (argc > 0) {
			outfile = *(argv++);
			argc--;
		}
	}

	if (argc > 0 || (nturl && ntsurl) || !infile ||
		(cmd != CMD_VERIFY && !outfile) ||
		(cmd == CMD_SIGN && !((certfile && keyfile) || pkcs12file))) {
		if (failarg)
			fprintf(stderr, "Unknown option: %s\n", failarg);
		usage(argv0);
	}

	if (readpass) {
		char passbuf[4096];
		int passfd = open(readpass, O_RDONLY);
		if (passfd < 0)
			DO_EXIT_1("Failed to open password file: %s\n", readpass);
		ssize_t passlen = read(passfd, passbuf, sizeof(passbuf)-1);
		close(passfd);
		if (passlen <= 0)
			DO_EXIT_1("Failed to read password from file: %s\n", readpass);
		passbuf[passlen] = 0x00;
		pass = strdup(passbuf);
		memset(passbuf, 0, sizeof(passbuf));
#ifdef PROVIDE_ASKPASS
	} else if (askpass) {
		pass = getpassword("Password: ");
#endif
	}

	if (cmd == CMD_SIGN) {
		/* Read certificate and key */
		if (keyfile && (btmp = BIO_new_file(keyfile, "rb")) != NULL) {
			unsigned char magic[4];
			unsigned char pvkhdr[4] = { 0x1e, 0xf1, 0xb5, 0xb0 };
			magic[0] = 0x00;
			BIO_read(btmp, magic, 4);
			if (!memcmp(magic, pvkhdr, 4)) {
				pvkfile = keyfile;
				keyfile = NULL;
			}
			BIO_free(btmp);
		}

		if (pkcs12file != NULL) {
			if ((btmp = BIO_new_file(pkcs12file, "rb")) == NULL ||
				(p12 = d2i_PKCS12_bio(btmp, NULL)) == NULL)
				DO_EXIT_1("Failed to read PKCS#12 file: %s\n", pkcs12file);
			BIO_free(btmp);
			if (!PKCS12_parse(p12, pass ? pass : "", &pkey, &cert, &certs))
				DO_EXIT_1("Failed to parse PKCS#12 file: %s (Wrong password?)\n", pkcs12file);
			PKCS12_free(p12);
		} else if (pvkfile != NULL) {
#if OPENSSL_VERSION_NUMBER > 0x10000000
			if ((btmp = BIO_new_file(certfile, "rb")) == NULL ||
				((p7 = d2i_PKCS7_bio(btmp, NULL)) == NULL &&
				 (certs = PEM_read_certs(btmp, "")) == NULL))
				DO_EXIT_1("Failed to read certificate file: %s\n", certfile);
			BIO_free(btmp);
			if ((btmp = BIO_new_file(pvkfile, "rb")) == NULL ||
				( (pkey = b2i_PVK_bio(btmp, NULL, pass ? pass : "")) == NULL &&
				  (BIO_seek(btmp, 0) == 0) &&
				  (pkey = b2i_PVK_bio(btmp, NULL, NULL)) == NULL))
				DO_EXIT_1("Failed to read PVK file: %s\n", pvkfile);
			BIO_free(btmp);
#else
			DO_EXIT_1("Can not read keys from PVK files, must compile against a newer version of OpenSSL: %s\n", pvkfile);
#endif
		} else {
			if ((btmp = BIO_new_file(certfile, "rb")) == NULL ||
				((p7 = d2i_PKCS7_bio(btmp, NULL)) == NULL &&
				 (certs = PEM_read_certs(btmp, "")) == NULL))
				DO_EXIT_1("Failed to read certificate file: %s\n", certfile);
			BIO_free(btmp);
			if ((btmp = BIO_new_file(keyfile, "rb")) == NULL ||
				( (pkey = d2i_PrivateKey_bio(btmp, NULL)) == NULL &&
				  (BIO_seek(btmp, 0) == 0) &&
				  (pkey = PEM_read_bio_PrivateKey(btmp, NULL, NULL, pass ? pass : "")) == NULL &&
				  (BIO_seek(btmp, 0) == 0) &&
				  (pkey = PEM_read_bio_PrivateKey(btmp, NULL, NULL, NULL)) == NULL))
				DO_EXIT_1("Failed to read private key file: %s (Wrong password?)\n", keyfile);
			BIO_free(btmp);
		}

		if (xcertfile) {
			if ((btmp = BIO_new_file(xcertfile, "rb")) == NULL ||
				((p7x = d2i_PKCS7_bio(btmp, NULL)) == NULL &&
				 (xcerts = PEM_read_certs(btmp, "")) == NULL))
				DO_EXIT_1("Failed to read cross certificate file: %s\n", xcertfile);
			BIO_free(btmp);
		}

		if (pass) {
			memset (pass, 0, strlen(pass));
			pass = NULL;
	    }
	}

	if (certs == NULL && p7 != NULL)
		certs = p7->d.sign->cert;

	/* Check if indata is cab or pe */
	filesize = get_file_size(infile);
	if (filesize == 0)
		goto err_cleanup;

	indata = map_file(infile, filesize);
	if (indata == NULL)
		DO_EXIT_1("Failed to open file: %s\n", infile);

	fileend = filesize;

	if (!memcmp(indata, "MSCF", 4)) {
		type = FILE_TYPE_CAB;
	} else if (!memcmp(indata, "MZ", 2)) {
		type = FILE_TYPE_PE;
	} else if (!memcmp(indata, msi_signature, sizeof(msi_signature))) {
		type = FILE_TYPE_MSI;
#ifdef WITH_GSF
		gsf_init();
		gsf_initialized = 1;
#endif
	} else {
		DO_EXIT_1("Unrecognized file type: %s\n", infile);
	}

	if (cmd != CMD_SIGN && !(type == FILE_TYPE_PE || type == FILE_TYPE_MSI))
		DO_EXIT_1("Command is not supported for non-PE/non-MSI files: %s\n", infile);

	hash = BIO_new(BIO_f_md());
	BIO_set_md(hash, md);

	if (type == FILE_TYPE_CAB) {
		if (filesize < 44)
			DO_EXIT_1("Corrupt cab file - too short: %s\n", infile);
		if (indata[0x1e] != 0x00 || indata[0x1f] != 0x00)
			DO_EXIT_0("Cannot sign cab files with flag bits set!\n"); /* XXX */
	} else if (type == FILE_TYPE_PE) {
		if (filesize < 64)
			DO_EXIT_1("Corrupt DOS file - too short: %s\n", infile);
		peheader = GET_UINT32_LE(indata+60);
		if (filesize < peheader + 160)
			DO_EXIT_1("Corrupt PE file - too short: %s\n", infile);
		if (memcmp(indata+peheader, "PE\0\0", 4))
			DO_EXIT_1("Unrecognized DOS file type: %s\n", infile);
	} else if (type == FILE_TYPE_MSI) {
#ifdef WITH_GSF
		GsfInput *src;
		GsfInfile *ole;

		BIO_push(hash, BIO_new(BIO_s_null()));

		src = gsf_input_stdio_new(infile, NULL);
		if (!src)
			DO_EXIT_1("Error opening file %s", infile);
		ole = gsf_infile_msole_new(src, NULL);

		if (cmd == CMD_EXTRACT) {
			ret = msi_extract_signature_to_file(ole, outfile);
			goto skip_signing;
		} else if (cmd == CMD_VERIFY) {
			ret = msi_verify_file(ole, leafhash);
			goto skip_signing;
		} else if (cmd == CMD_SIGN) {
			if (nest) {
				// Perform a sanity check for the MsiDigitalSignatureEx section.
				// If the file we're attempting to sign has an MsiDigitalSignatureEx
				// section, we can't add a nested signature of a different MD type
				// without breaking the initial signature.
				{
					unsigned long dselen = 0;
					int has_dse = 0;
					if (msi_extract_dse(ole, NULL, &dselen, &has_dse) != 0 && has_dse) {
						DO_EXIT_0("Unable to extract MsiDigitalSigantureEx section.\n");
					}
					if (has_dse) {
						int mdlen = EVP_MD_size(md);
						if (dselen != (unsigned long)mdlen) {
							DO_EXIT_0("Unable to add nested signature with a different MD type (-h parameter) "
							          "than what exists in the MSI file already. This is due to the presence of "
							          "MsiDigitalSigantureEx (-add-msi-dse parameter).\n");
						}
					}
				}

				cursig = msi_extract_signature_to_pkcs7(ole);
				if (cursig == NULL) {
					DO_EXIT_0("Unable to extract existing signature in -nest mode");
				}
			}
		}

		sink = gsf_output_stdio_new(outfile, NULL);
		if (!sink)
			DO_EXIT_1("Error opening output file %s", outfile);
		outole = gsf_outfile_msole_new(sink);

		/*
		 * MsiDigitalSignatureEx is an enhanced signature type that
		 * can be used when signing MSI files.  In addition to
		 * file content, it also hashes some file metadata, specifically
		 * file names, file sizes, creation times and modification times.
		 *
		 * The file content hashing part stays the same, so the
		 * msi_handle_dir() function can be used across both variants.
		 *
		 * When an MsiDigitalSigntaureEx section is present in an MSI file,
		 * the meaning of the DigitalSignature section changes:  Instead
		 * of being merely a file content hash (as what is output by the
		 * msi_handle_dir() function), it is now hashes both content
		 * and metadata.
		 *
		 * Here is how it works:
		 *
		 * First, a "pre-hash" is calculated. This is the "metadata" hash.
		 * It iterates over the files in the MSI in the same order as the
		 * file content hashing method would - but it only processes the
		 * metadata.
		 * 
		 * Once the pre-hash is calculated, a new hash is created for
		 * calculating the hash of the file content.  The output of the
		 * pre-hash is added as the first element of the file content hash.  
		 *
		 * After the pre-hash is written, what follows is the "regular"
		 * stream of data that would normally be written when performing
		 * file content hashing.
		 *
		 * The output of this hash, which combines both metadata and file
		 * content, is what will be output in signed form to the
		 * DigitalSignature section when in 'MsiDigitalSignatureEx' mode.
		 *
		 * As mentioned previously, this new mode of operation is signalled
		 * by the presence of a 'MsiDigitalSignatureEx' section in the MSI
		 * file.  This section must come after the 'DigitalSignature'
		 * section, and its content must be the output of the pre-hash
		 * ("metadata") hash.
		 */
		if (add_msi_dse) {
			BIO *prehash = BIO_new(BIO_f_md());
			BIO_set_md(prehash, md);
			BIO_push(prehash, BIO_new(BIO_s_null()));

			if (!msi_prehash(ole, NULL, prehash))
				DO_EXIT_0("unable to calculate MSI pre-hash ('metadata') hash.\n");

			p_msiex = malloc(EVP_MAX_MD_SIZE);
			len_msiex = BIO_gets(prehash, (char*)p_msiex, EVP_MAX_MD_SIZE);

			BIO_write(hash, p_msiex, len_msiex);
		}

		if (!msi_handle_dir(ole, outole, hash)) {
			DO_EXIT_0("unable to msi_handle_dir()\n");
		}

		if (cmd == CMD_REMOVE) {
			gsf_output_close(GSF_OUTPUT(outole));
			g_object_unref(sink);
		}
#else
		DO_EXIT_1("libgsf is not available, msi support is disabled: %s\n", infile);
#endif
	}

	if (type == FILE_TYPE_CAB || type == FILE_TYPE_PE) {
		if (cmd != CMD_VERIFY) {
			/* Create outdata file */
			outdata = BIO_new_file(outfile, "w+b");
			if (outdata == NULL)
				DO_EXIT_1("Failed to create file: %s\n", outfile);
			BIO_push(hash, outdata);
		}
	}

	if (type == FILE_TYPE_CAB) {
		unsigned short nfolders;

		u_char cabsigned[] = {
			0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
			0xde, 0xad, 0xbe, 0xef, /* size of cab file */
			0xde, 0xad, 0xbe, 0xef, /* size of asn1 blob */
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
		};

		BIO_write(hash, indata, 4);
		BIO_write(outdata, indata+4, 4);

		tmp = GET_UINT32_LE(indata+8) + 24;
		PUT_UINT32_LE(tmp, buf);
		BIO_write(hash, buf, 4);

		BIO_write(hash, indata+12, 4);

		tmp = GET_UINT32_LE(indata+16) + 24;
		PUT_UINT32_LE(tmp, buf+4);
		BIO_write(hash, buf+4, 4);

		memcpy(buf+4, indata+20, 14);
		buf[4+10] = 0x04; /* RESERVE_PRESENT */

		BIO_write(hash, buf+4, 14);
		BIO_write(outdata, indata+34, 2);

		memcpy(cabsigned+8, buf, 4);
		BIO_write(outdata, cabsigned, 20);
		BIO_write(hash, cabsigned+20, 4); /* ??? or possibly the previous 4 bytes instead? */

		nfolders = indata[26] | (indata[27] << 8);
		for (i = 36; nfolders; nfolders--, i+=8) {
			tmp = GET_UINT32_LE(indata+i);
			tmp += 24;
			PUT_UINT32_LE(tmp, buf);
			BIO_write(hash, buf, 4);
			BIO_write(hash, indata+i+4, 4);
		}

		/* Write what's left */
		BIO_write(hash, indata+i, filesize-i);
	} else if (type == FILE_TYPE_PE) {
		unsigned int sigpos, siglen, nrvas;
		unsigned short magic;

		if (jp >= 0)
			fprintf(stderr, "Warning: -jp option is only valid "
					"for CAB files.\n");

		magic = GET_UINT16_LE(indata + peheader + 24);
		if (magic == 0x20b) {
			pe32plus = 1;
		} else if (magic == 0x10b) {
			pe32plus = 0;
		} else {
			DO_EXIT_2("Corrupt PE file - found unknown magic %x: %s\n", magic, infile);
		}

		nrvas = GET_UINT32_LE(indata + peheader + 116 + pe32plus*16);
		if (nrvas < 5)
			DO_EXIT_1("Can not handle PE files without certificate table resource: %s\n", infile);

		sigpos = GET_UINT32_LE(indata + peheader + 152 + pe32plus*16);
		siglen = GET_UINT32_LE(indata + peheader + 152 + pe32plus*16 + 4);

		/* Since fix for MS Bulletin MS12-024 we can really assume
		   that signature should be last part of file */
		if (sigpos > 0 && sigpos + siglen != filesize)
			DO_EXIT_1("Corrupt PE file - current signature not at end of file: %s\n", infile);

		if ((cmd == CMD_REMOVE || cmd == CMD_EXTRACT) && sigpos == 0)
			DO_EXIT_1("PE file does not have any signature: %s\n", infile);

		if (cmd == CMD_EXTRACT) {
			/* A lil' bit of ugliness. Reset stream, write signature and skip forward */
			(void)BIO_reset(outdata);
			BIO_write(outdata, indata + sigpos, siglen);
			goto skip_signing;
		}

		if (cmd == CMD_SIGN && nest) {
			cursig = extract_existing_pe_pkcs7(indata, peheader, pe32plus, sigpos ? sigpos : fileend, siglen);
			if (cursig == NULL) {
				DO_EXIT_0("Unable to extract existing signature in -nest mode");
			}
		}

		if (cmd == CMD_VERIFY) {
			ret = verify_pe_file(indata, peheader, pe32plus, sigpos ? sigpos : fileend, siglen, leafhash);
			goto skip_signing;
		}

		if (sigpos > 0) {
			/* Strip current signature */
			fileend = sigpos;
		}

		BIO_write(hash, indata, peheader + 88);
		i = peheader + 88;
		memset(buf, 0, 4);
		BIO_write(outdata, buf, 4); /* zero out checksum */
		i += 4;
		BIO_write(hash, indata + i, 60+pe32plus*16);
		i += 60+pe32plus*16;
		memset(buf, 0, 8);
		BIO_write(outdata, buf, 8); /* zero out sigtable offset + pos */
		i += 8;

		BIO_write(hash, indata + i, fileend - i);

		/* pad (with 0's) pe file to 8 byte boundary */
		len = 8 - fileend % 8;
		if (len > 0 && len != 8) {
			memset(buf, 0, len);
			BIO_write(hash, buf, len);
			fileend += len;
		}
	}

	if (cmd != CMD_SIGN)
		goto skip_signing;

	sig = PKCS7_new();
	PKCS7_set_type(sig, NID_pkcs7_signed);

	si = NULL;
	if (cert != NULL)
		si = PKCS7_add_signature(sig, cert, pkey, md);
	if (si == NULL) {
		for (i=0; i<sk_X509_num(certs); i++) {
			X509 *signcert = sk_X509_value(certs, i);
			/* X509_print_fp(stdout, signcert); */
			si = PKCS7_add_signature(sig, signcert, pkey, md);
			if (si != NULL) break;
		}
	}
	EVP_PKEY_free(pkey);
	pkey = NULL;

	if (si == NULL)
		DO_EXIT_0("Signing failed(PKCS7_add_signature)\n");

	PKCS7_add_signed_attribute
		(si, NID_pkcs9_contentType,
		 V_ASN1_OBJECT, OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, 1));

	if (type == FILE_TYPE_CAB && jp >= 0) {
		const u_char *attrs = NULL;
		static const u_char java_attrs_low[] = {
			0x30, 0x06, 0x03, 0x02, 0x00, 0x01, 0x30, 0x00
		};

		switch (jp) {
			case 0:
				attrs = java_attrs_low;
				len = sizeof(java_attrs_low);
				break;
			case 1:
				/* XXX */
			case 2:
				/* XXX */
			default:
				break;
		}

		if (attrs) {
			astr = ASN1_STRING_new();
			ASN1_STRING_set(astr, attrs, len);
			PKCS7_add_signed_attribute
				(si, OBJ_txt2nid(SPC_MS_JAVA_SOMETHING),
				 V_ASN1_SEQUENCE, astr);
		}
	}

	astr = ASN1_STRING_new();
	if (comm) {
		ASN1_STRING_set(astr, purpose_comm, sizeof(purpose_comm));
	} else {
		ASN1_STRING_set(astr, purpose_ind, sizeof(purpose_ind));
	}
	PKCS7_add_signed_attribute(si, OBJ_txt2nid(SPC_STATEMENT_TYPE_OBJID),
							   V_ASN1_SEQUENCE, astr);

	if (desc || url) {
		SpcSpOpusInfo *opus = createOpus(desc, url);
		if ((len = i2d_SpcSpOpusInfo(opus, NULL)) <= 0 ||
			(p = OPENSSL_malloc(len)) == NULL)
			DO_EXIT_0("Couldn't allocate memory for opus info\n");
		i2d_SpcSpOpusInfo(opus, &p);
		p -= len;
		astr = ASN1_STRING_new();
		ASN1_STRING_set(astr, p, len);
		OPENSSL_free(p);

		PKCS7_add_signed_attribute(si, OBJ_txt2nid(SPC_SP_OPUS_INFO_OBJID),
								   V_ASN1_SEQUENCE, astr);

		SpcSpOpusInfo_free(opus);
	}

	PKCS7_content_new(sig, NID_pkcs7_data);

	if (cert != NULL) {
		PKCS7_add_certificate(sig, cert);
		X509_free(cert);
		cert = NULL;
	}
	if (xcerts) {
		for(i = sk_X509_num(xcerts)-1; i>=0; i--)
			PKCS7_add_certificate(sig, sk_X509_value(xcerts, i));
	}
	for(i = sk_X509_num(certs)-1; i>=0; i--)
		PKCS7_add_certificate(sig, sk_X509_value(certs, i));

	if (p7 == NULL) {
		sk_X509_free(certs);
	} else {
		PKCS7_free(p7);
		p7 = NULL;
	}
	certs = NULL;
	if (p7x == NULL) {
		sk_X509_free(xcerts);
	} else {
		PKCS7_free(p7x);
		p7x = NULL;
	}
	xcerts = NULL;

	get_indirect_data_blob(&p, &len, md, type, pagehash, indata, peheader, pe32plus, fileend);
	len -= EVP_MD_size(md);
	memcpy(buf, p, len);
	OPENSSL_free(p);
	unsigned char mdbuf[EVP_MAX_MD_SIZE];
	int mdlen = BIO_gets(hash, (char*)mdbuf, EVP_MAX_MD_SIZE);
	memcpy(buf+len, mdbuf, mdlen);
	int seqhdrlen = asn1_simple_hdr_len((unsigned char*)buf, len);

	if ((sigbio = PKCS7_dataInit(sig, NULL)) == NULL)
		DO_EXIT_0("Signing failed(PKCS7_dataInit)\n");
	BIO_write(sigbio, buf+seqhdrlen, len-seqhdrlen+mdlen);
	(void)BIO_flush(sigbio);

	if (!PKCS7_dataFinal(sig, sigbio))
		DO_EXIT_0("Signing failed(PKCS7_dataFinal)\n");
	BIO_free_all(sigbio);

	/* replace the data part with the MS Authenticode
	   spcIndirectDataContext blob */
	PKCS7 *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);
	PKCS7_set_content(sig, td7);

#ifdef ENABLE_CURL
	/* add counter-signature/timestamp */
	if (nturl && add_timestamp_authenticode(sig, turl, nturl, proxy))
		DO_EXIT_0("authenticode timestamping failed\n");
	if (ntsurl && add_timestamp_rfc3161(sig, tsurl, ntsurl, proxy, md))
		DO_EXIT_0("RFC 3161 timestamping failed\n");
#endif

#if 0
	if (!PEM_write_PKCS7(stdout, sig))
		DO_EXIT_0("PKCS7 output failed\n");
#endif

	if (nest) {
		if (cursig == NULL) {
			DO_EXIT_0("no 'cursig' was extracted. this points to a bug in the code. aborting...\n")
		}
		if (pkcs7_set_nested_signature(cursig, sig) == 0)
			DO_EXIT_0("unable to append the nested signature to the current signature\n");
		outsig = cursig;
	} else {
		outsig = sig;
	}

	/* Append signature to outfile */
	if (((len = i2d_PKCS7(outsig, NULL)) <= 0) ||
		(p = OPENSSL_malloc(len)) == NULL)
		DO_EXIT_1("i2d_PKCS - memory allocation failed: %d\n", len);
	i2d_PKCS7(outsig, &p);
	p -= len;
	padlen = (8 - len%8) % 8;

	if (type == FILE_TYPE_PE) {
		PUT_UINT32_LE(len+8+padlen, buf);
		PUT_UINT16_LE(WIN_CERT_REVISION_2, buf + 4);
		PUT_UINT16_LE(WIN_CERT_TYPE_PKCS_SIGNED_DATA, buf + 6);
		BIO_write(outdata, buf, 8);
	}

	if (type == FILE_TYPE_PE || type == FILE_TYPE_CAB) {
		BIO_write(outdata, p, len);

		/* pad (with 0's) asn1 blob to 8 byte boundary */
		if (padlen > 0) {
			memset(p, 0, padlen);
			BIO_write(outdata, p, padlen);
		}
#ifdef WITH_GSF
	} else if (type == FILE_TYPE_MSI) {
		/* Only output signatures if we're signing. */
		if (cmd == CMD_SIGN) {
			GsfOutput *child = gsf_outfile_new_child(outole, "\05DigitalSignature", FALSE);
			if (!gsf_output_write(child, len, p))
				DO_EXIT_1("Failed to write MSI 'DigitalSignature' signature to %s", infile);
			gsf_output_close(child);

			if (p_msiex != NULL) {
				child = gsf_outfile_new_child(outole, "\05MsiDigitalSignatureEx", FALSE);
				if (!gsf_output_write(child, len_msiex, p_msiex)) {
					DO_EXIT_1("Failed to write MSI 'MsiDigitalSignatureEx' signature to %s", infile);
				}
				gsf_output_close(child);
			}

			gsf_output_close(GSF_OUTPUT(outole));
			g_object_unref(sink);
		}
#endif
	}

	PKCS7_free(sig);
	OPENSSL_free(p);

skip_signing:

	if (type == FILE_TYPE_PE) {
		if (cmd == CMD_SIGN) {
			/* Update signature position and size */
			(void)BIO_seek(outdata, peheader+152+pe32plus*16);
			PUT_UINT32_LE(fileend, buf); /* Previous file end = signature table start */
			BIO_write(outdata, buf, 4);
			PUT_UINT32_LE(len+8+padlen, buf);
			BIO_write(outdata, buf, 4);
		}
		if (cmd == CMD_SIGN || cmd == CMD_REMOVE)
			recalc_pe_checksum(outdata, peheader);
	} else if (type == FILE_TYPE_CAB) {
		(void)BIO_seek(outdata, 0x30);
		PUT_UINT32_LE(len+padlen, buf);
		BIO_write(outdata, buf, 4);
	}

	BIO_free_all(hash);
	hash = outdata = NULL;

	printf(ret ? "Failed\n" : "Succeeded\n");

	cleanup_lib_state();

	return ret;

err_cleanup:
	if (pass) {
		memset (pass, 0, strlen(pass));
		pass = NULL;
	}
	ERR_print_errors_fp(stderr);
	if (p7)
		PKCS7_free(p7);
	else if (certs)
		sk_X509_free(certs);
	if (p7x)
		PKCS7_free(p7x);
	else if (xcerts)
		sk_X509_free(xcerts);
	if (cert)
		X509_free(cert);
	if (certs)
		sk_X509_free(certs);
	if (pkey)
		EVP_PKEY_free(pkey);
	if (hash)
		BIO_free_all(hash);
	if (outfile)
		unlink(outfile);
	fprintf(stderr, "\nFailed\n");
	cleanup_lib_state();
	return -1;
}

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