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

   Copyright (C) 2005-2015 Per Allansson <pallansson@gmail.com>
   Copyright (C) 2018-2019 Michał Trojnara <Michal.Trojnara@stunnel.org>

   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.
*/

/*
   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
#define NOCRYPT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
typedef unsigned char u_char;
#endif

#include <stdio.h>
#include <stdlib.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#ifndef _WIN32
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif

#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#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
#ifdef __CYGWIN__
#ifndef SOCKET
#define SOCKET UINT_PTR
#endif
#endif
#include <curl/curl.h>

#define MAX_TS_SERVERS 256

#endif

#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE 1
#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_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 = 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 = ASN1_IA5STRING_new();
		ASN1_STRING_set((ASN1_STRING *)info->moreInfo->value.url,
						(const unsigned char*)url, strlen(url));
	}

	return info;
}

static size_t asn1_simple_hdr_len(const unsigned char *p, size_t 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, char *b, int len)
{
	int i;
	for(i=0; i<len; i++)
		sprintf(b+i*2, "%02X", v[i]);
}

static int add_unauthenticated_blob(PKCS7 *sig)
{
	u_char *p = NULL;
	int len = 1024+4;
	char prefix[] = "\x0c\x82\x04\x00---BEGIN_BLOB---"; /* Length data for ASN1 attribute plus prefix */
	char postfix[] = "---END_BLOB---";

	PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(sig->d.sign->signer_info, 0);

	p = OPENSSL_malloc(len);
	memset(p, 0, len);
	memcpy(p, prefix, sizeof(prefix));
	memcpy(p+len-sizeof(postfix), postfix, sizeof(postfix));

	ASN1_STRING *astr = ASN1_STRING_new();
	ASN1_STRING_set(astr, p, len);

	int nid = OBJ_create("1.3.6.1.4.1.42921.1.2.1",
		"unauthenticatedData", "unauthenticatedData");

	PKCS7_add_attribute (si, nid, V_ASN1_SEQUENCE, astr);

	OPENSSL_free(p);

	return 0;
}


static int g_verbose = 0;

#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);
}

static void print_timestamp_error(const char *url, long http_code)
{
	if (http_code != -1) {
		fprintf(stderr, "Failed to convert timestamp reply from %s; "
				"HTTP status %ld\n", url, http_code);
	} else {
		fprintf(stderr, "Failed to convert timestamp reply from %s; "
				"no HTTP status available", url);
	}

	ERR_print_errors_fp(stderr);
}

/*
  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, int noverifypeer)
{
	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 (noverifypeer)
		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);

	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;

		mdctx = EVP_MD_CTX_new();
		EVP_DigestInit(mdctx, md);
		EVP_DigestUpdate(mdctx, si->enc_digest->data, si->enc_digest->length);
		EVP_DigestFinal(mdctx, mdbuf, NULL);
		EVP_MD_CTX_free(mdctx);

		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;
		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);

		long http_code = -1;
		curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

		/*
		 * At this point we could also look at the response body (and perhaps
		 * log it if we fail to decode the response):
		 *
		 *     char *resp_body = NULL;
		 *     long resp_body_len = BIO_get_mem_data(bin, &resp_body);
		 */

		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)
					print_timestamp_error(url, http_code);
				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)
					print_timestamp_error(url, http_code);
				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 noverifypeer)
{
	int i;
	for (i=0; i<nurls; i++) {
		int res = add_timestamp(sig, url[i], proxy, 0, NULL, g_verbose || nurls == 1, noverifypeer);
		if (!res) return 0;
	}
	return -1;
}

static int add_timestamp_rfc3161(PKCS7 *sig, char **url, int nurls,
	char *proxy, const EVP_MD *md, int noverifypeer)
{
	int i;
	for (i=0; i<nurls; i++) {
		int res = add_timestamp(sig, url[i], proxy, 1, md, g_verbose || nurls == 1, noverifypeer);
		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();
	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  [ -pkcs11engine <engine> ] -pkcs11module <module> -certs <certfile> -key <pkcs11 key id>)\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> ] [ -noverifypeer ] ]\n"
			"\t\t[ -ts <timestampurl> [ -ts ... ] [ -p <proxy> ] [ -noverifypeer ] ]\n"
#endif
			"\t\t[ -addUnauthenticatedBlob ]\n\n"
			"\t\t[ -nest ]\n\n"
			"\t\t[ -verbose ]\n\n"
#ifdef WITH_GSF
			"\t\tMSI specific:\n"
			"\t\t[ -add-msi-dse ]\n\n"
#endif
			"\t\t[ -in ] <infile> [-out ] <outfile>\n\n"
			"\textract-signature [ -pem ] [ -in ] <infile> [ -out ] <outfile>\n\n"
			"\tremove-signature [ -in ] <infile> [ -out ] <outfile>\n\n"
			"\tattach-signature [ -sigin ] <sigfile> [ -in ] <infile> [ -out ] <outfile>\n\n"
			"\tverify [ -in ] <infile>\n"
			"\t\t[ -require-leaf-hash {md5,sha1,sha2(56),sha384,sha512}:XXXXXXXXXXXX... ]\n\n"
			"\tadd [-addUnauthenticatedBlob] [ -in ] <infile> [ -out ] <outfile>\n"
#ifdef ENABLE_CURL
			"\t\t[ -t <timestampurl> [ -t ... ] [ -p <proxy> ] [ -noverifypeer ] ]\n"
			"\t\t[ -ts <timestampurl> [ -ts ... ] [ -p <proxy> ] [ -noverifypeer ] ]\n"
#endif
			"\n"
			"",
			argv0);
	cleanup_lib_state();
	exit(-1);
}

#define DO_EXIT_0(x)	{ fprintf(stderr, x); 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_ADD,
	CMD_ATTACH,
} 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, size_t peheader,
	int pe32plus, size_t sigpos, int phtype, size_t *rphlen)
{
	unsigned short nsections = GET_UINT16_LE(indata + peheader + 6);
	size_t pagesize = GET_UINT32_LE(indata + peheader + 56);
	size_t 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 = OPENSSL_malloc(phlen);
	unsigned char *zeroes = OPENSSL_zalloc(pagesize);
	EVP_MD_CTX *mdctx;

	mdctx = EVP_MD_CTX_new();
	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;
	size_t lastpos = 0;
	for (i=0; i<nsections; i++) {
		size_t rs = GET_UINT32_LE(sections + 16);
		size_t ro = GET_UINT32_LE(sections + 20);
		size_t 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;
	}
	EVP_MD_CTX_free(mdctx);
	PUT_UINT32_LE(lastpos, res + pi*pphlen);
	memset(res + pi*pphlen + 4, 0, EVP_MD_size(md));
	pi++;
	OPENSSL_free(zeroes);
	*rphlen = pi*pphlen;
	return res;
}

static SpcLink *get_page_hash_link(int phtype, char *indata,
	size_t peheader, int pe32plus, size_t sigpos)
{
	unsigned char *p, *tmp;
	size_t l;

	size_t 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);
	}
	char hexbuf[EVP_MAX_MD_SIZE*2+1];
	tohex(ph, hexbuf, (phlen < 32) ? phlen : 32);
	printf("Calculated page hash            : %s ...\n", hexbuf);

	ASN1_TYPE *tostr = ASN1_TYPE_new();
	tostr->type = V_ASN1_OCTET_STRING;
	tostr->value.octet_string = ASN1_OCTET_STRING_new();
	ASN1_OCTET_STRING_set(tostr->value.octet_string, ph, phlen);
	OPENSSL_free(ph);

	STACK_OF(ASN1_TYPE) *oset = sk_ASN1_TYPE_new_null();
	sk_ASN1_TYPE_push(oset, tostr);
	l = i2d_ASN1_SET_ANY(oset, NULL);
	tmp = p = OPENSSL_malloc(l);
	i2d_ASN1_SET_ANY(oset, &tmp);
	ASN1_TYPE_free(tostr);
	sk_ASN1_TYPE_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);
	l = i2d_SpcAttributeTypeAndOptionalValue(aval, NULL);
	tmp = p = OPENSSL_malloc(l);
	i2d_SpcAttributeTypeAndOptionalValue(aval, &tmp);
	SpcAttributeTypeAndOptionalValue_free(aval);
	
	ASN1_TYPE *taval = ASN1_TYPE_new();
	taval->type = V_ASN1_SEQUENCE;
	taval->value.sequence = ASN1_STRING_new();
	ASN1_STRING_set(taval->value.sequence, p, l);
	OPENSSL_free(p);

	STACK_OF(ASN1_TYPE) *aset = sk_ASN1_TYPE_new_null();
	sk_ASN1_TYPE_push(aset, taval);
	l = i2d_ASN1_SET_ANY(aset, NULL);
	tmp = p = OPENSSL_malloc(l);
	l = i2d_ASN1_SET_ANY(aset, &tmp);
	ASN1_TYPE_free(taval);
	sk_ASN1_TYPE_free(aset);

	SpcSerializedObject *so = SpcSerializedObject_new();
	ASN1_OCTET_STRING_set(so->classId, classid_page_hash, sizeof(classid_page_hash));
	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, size_t peheader,
	int pe32plus, size_t 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);
		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);
	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, size_t peheader)
{
	unsigned int checkSum = 0;
	unsigned short val;
	size_t size = 0;
	unsigned short *buf;
	int nread;

	/* recalculate the checksum */
	buf = OPENSSL_malloc(sizeof(unsigned short)*32768);

	(void)BIO_seek(bio, 0);
	while ((nread = BIO_read(bio, buf, sizeof(unsigned short)*32768)) > 0) {
		int i;
		for (i = 0; i < nread / 2; i++) {
			val = buf[i];
			if (size == peheader + 88 || size == peheader + 90)
				val = 0;
			checkSum += val;
			checkSum = 0xffff & (checkSum + (checkSum >> 0x10));
			size += 2;
		}
	}

	OPENSSL_free(buf);

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

	return checkSum;
}

static void recalc_pe_checksum(BIO *bio, size_t 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 int verify_leaf_hash(X509 *leaf, const char *leafhash)
{
	int ret = 0;
	unsigned char *mdbuf = NULL;

	/* decode the provided hash */
	char *mdid = OPENSSL_strdup(leafhash);
	char *hash = strchr(mdid, ':');
	if (hash == NULL) {
		printf("Unable to parse -require-leaf-hash parameter: %s\n", leafhash);
		ret = 1;
		goto out;
	}
	*hash++ = '\0';
	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;
	}
	long mdlen = 0;
	mdbuf = OPENSSL_hexstr2buf(hash, &mdlen);
	if (mdlen != EVP_MD_size(md)) {
		printf("Hash length mismatch: '%s' digest must be %d bytes long (got %ld bytes)\n",
			mdid, EVP_MD_size(md), mdlen);
		ret = 1;
		goto out;
	}

	/* compute the leaf certificate hash */
	EVP_MD_CTX *ctx = EVP_MD_CTX_create();
	EVP_DigestInit_ex(ctx, md, NULL);
	size_t certlen = i2d_X509(leaf, NULL);
	unsigned char *certbuf = OPENSSL_malloc(certlen);
	unsigned char *tmp = certbuf;
	i2d_X509(leaf, &tmp);
	EVP_DigestUpdate(ctx, certbuf, certlen);
	OPENSSL_free(certbuf);
	unsigned char cmdbuf[EVP_MAX_MD_SIZE];
	EVP_DigestFinal_ex(ctx, cmdbuf, NULL);
	EVP_MD_CTX_destroy(ctx);

	/* compare the provided hash against the computed hash */
	if (memcmp(mdbuf, cmdbuf, EVP_MD_size(md))) {
		char hexbuf[EVP_MAX_MD_SIZE*2+1];
		tohex(cmdbuf, hexbuf, EVP_MD_size(md));
		printf("Hash value mismatch: %s computed\n", hexbuf);
		ret = 1;
		goto out;
	}

out:
	OPENSSL_free(mdid);
	OPENSSL_free(mdbuf);
	return ret;
}

/*
 * pkcs7_get_nested_signature extracts 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 function 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 Windows FILETIME */
			BIO_write(hash, zeroes, 8); /* mtime as 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, size_t 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
	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
		 * perspective, 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");

	size_t 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 = OPENSSL_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 = OPENSSL_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:
	OPENSSL_free(indata);
	OPENSSL_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];
	unsigned 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 = OPENSSL_malloc(size);
		if (gsf_input_read(exsig, size, buf) == NULL) {
			ret = 1;
			goto out;
		}
		*dsebuf = 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)
{
	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+bx");
	if (outdata == NULL) {
		printf("Unable to create %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\n");
			ret = 1;
			goto out;
		}

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

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

out:
	OPENSSL_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 = OPENSSL_malloc(size);
	if (gsf_input_read(sig, size, buf) == NULL) {
		goto out;
	}

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

out:
	OPENSSL_free(buf);
	return p7;
}

#endif

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

	mdctx = EVP_MD_CTX_new();
	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);

	size_t n = peheader + 88 + 4 + 60+pe32plus*16 + 8;
	while (n < fileend) {
		size_t 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);
	EVP_MD_CTX_free(mdctx);
}

static void extract_page_hash(SpcAttributeTypeAndOptionalValue *obj,
	unsigned char **ph, size_t *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 */
	size_t 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 */
	size_t 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 = OPENSSL_malloc(*phlen);
	memcpy(*ph, obj->value->value.sequence->data + l, *phlen);
	SpcAttributeTypeAndOptionalValue_free(obj);
}

static int verify_pe_pkcs7(PKCS7 *p7, char *indata, size_t peheader,
	int pe32plus, size_t sigpos, size_t 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];
	char hexbuf[EVP_MAX_MD_SIZE*2+1];
	unsigned char *ph = NULL;
	size_t 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);
		size_t 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!!!":"");
		OPENSSL_free(ph);
		OPENSSL_free(cph);
	}

	size_t 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);
		BIGNUM *serialbn = ASN1_INTEGER_to_BN(X509_get_serialNumber(cert), NULL);
		char *serial = BN_bn2hex(serialbn);
		if (i > 0)
			printf("\t------------------\n");
		printf("\tSigner #%d:\n\t\tSubject: %s\n\t\tIssuer : %s\n\t\tSerial : %s\n",
			i, subject, issuer, serial);
		OPENSSL_free(subject);
		OPENSSL_free(issuer);
		OPENSSL_free(serial);
		BN_free(serialbn);

		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);
		BIGNUM *serialbn = ASN1_INTEGER_to_BN(X509_get_serialNumber(cert), NULL);
		char *serial = BN_bn2hex(serialbn);
		if (i > 0)
			printf("\t------------------\n");
		printf("\tCert #%d:\n\t\tSubject: %s\n\t\tIssuer : %s\n\t\tSerial : %s\n",
			i, subject, issuer, serial);
		OPENSSL_free(subject);
		OPENSSL_free(issuer);
		OPENSSL_free(serial);
		BN_free(serialbn);
	}

	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;
}

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

	while (pos < siglen) {
		size_t 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 int verify_pe_file(char *indata, size_t peheader, int pe32plus,
	size_t sigpos, size_t 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;
	}

	PKCS7 *p7 = extract_existing_pe_pkcs7(indata, sigpos, siglen);
	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;
}

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)
{
	int ret;
#ifdef _WIN32
	struct _stat st;
	ret = _stat(infile, &st);
#else
	struct stat st;
	ret = stat(infile, &st);
#endif
	if (ret) {
		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, FILE_SHARE_READ , 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
static 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 = NULL, *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, *sigfile, *desc, *url, *indata, *insigdata, *outdataverify;
	char *p11engine, *p11module;
	char *pass = NULL, *readpass = NULL;
	int output_pkcs7 = 0;
	int askpass = 0;
	char *leafhash = NULL;
#ifdef ENABLE_CURL
	char *turl[MAX_TS_SERVERS], *proxy = NULL, *tsurl[MAX_TS_SERVERS];
	int noverifypeer = 0;
#endif
	int nest = 0;
#ifdef WITH_GSF
	int add_msi_dse = 0;
#endif
	int nturl = 0, ntsurl = 0;
	int addBlob = 0;
	u_char *p = NULL;
	int ret = 0, i, len = 0, jp = -1, pe32plus = 0, comm = 0, pagehash = 0;
	size_t peheader = 0, padlen = 0;
	size_t filesize, fileend, sigfilesize, outdatasize;
	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 = p11module = p11engine = infile = outfile = sigfile = 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], "attach-signature")) {
			cmd = CMD_ATTACH;
			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--;
		} else if (!strcmp(argv[1], "add")) {
			cmd = CMD_ADD;
			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 (!strcmp(*argv, "-sigin")) {
			if (--argc < 1) usage(argv0);
			sigfile = *(++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_EXTRACT) && !strcmp(*argv, "-pem")) {
			output_pkcs7 = 1;
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-pkcs11engine")) {
			if (--argc < 1) usage(argv0);
			p11engine = *(++argv);
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-pkcs11module")) {
			if (--argc < 1) usage(argv0);
			p11module = *(++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 || cmd == CMD_ADD) && !strcmp(*argv, "-t")) {
			if (--argc < 1) usage(argv0);
			turl[nturl++] = *(++argv);
		} else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-ts")) {
			if (--argc < 1) usage(argv0);
			tsurl[ntsurl++] = *(++argv);
		} else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-p")) {
			if (--argc < 1) usage(argv0);
			proxy = *(++argv);
		} else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-noverifypeer")) {
			noverifypeer = 1;
#endif
		} else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-addUnauthenticatedBlob")) {
			addBlob = 1;
		} else if ((cmd == CMD_SIGN || cmd == CMD_ATTACH) && !strcmp(*argv, "-nest")) {
			nest = 1;
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-verbose")) {
			g_verbose = 1;
#ifdef WITH_GSF
		} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-add-msi-dse")) {
			add_msi_dse = 1;
#endif
		} 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 || p11module))) {
		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);
		int 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 && !p11module && (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 ((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 if (p11module != NULL) {
			const int CMD_MANDATORY = 0;
			if (p11engine != NULL) {
				ENGINE_load_dynamic();
				ENGINE * dyn = ENGINE_by_id("dynamic");
				if (!dyn)
					DO_EXIT_0("Failed to load 'dynamic' engine\n");
				if (1 != ENGINE_ctrl_cmd_string(dyn, "SO_PATH", p11engine, CMD_MANDATORY))
					DO_EXIT_1("Failed to set dyn SO_PATH to '%s'\n", p11engine);

				if (1 != ENGINE_ctrl_cmd_string(dyn, "ID", "pkcs11", CMD_MANDATORY))
					DO_EXIT_0("Failed to set dyn ID to 'pkcs11'\n");

				if (1 != ENGINE_ctrl_cmd(dyn, "LIST_ADD", 1, NULL, NULL, CMD_MANDATORY))
					DO_EXIT_0("Failed to set dyn LIST_ADD to '1'\n");

				if (1 != ENGINE_ctrl_cmd(dyn, "LOAD", 1, NULL, NULL, CMD_MANDATORY))
					DO_EXIT_0("Failed to set dyn LOAD to '1'\n");
			} else
				ENGINE_load_builtin_engines();

			ENGINE * pkcs11 = ENGINE_by_id("pkcs11");
			if (!pkcs11)
				DO_EXIT_0("Failed to find and load pkcs11 engine\n");

			if (1 != ENGINE_ctrl_cmd_string(pkcs11, "MODULE_PATH", p11module, CMD_MANDATORY))
				DO_EXIT_1("Failed to set pkcs11 engine MODULE_PATH to '%s'\n", p11module);
		
			if (pass != NULL) {
				if (1 != ENGINE_ctrl_cmd_string(pkcs11, "PIN", pass, CMD_MANDATORY))
					DO_EXIT_0("Failed to set pkcs11 PIN\n");
			}

			if (1 != ENGINE_init(pkcs11))
				DO_EXIT_0("Failed to initialized pkcs11 engine\n");

			pkey = ENGINE_load_private_key(pkcs11, keyfile, NULL, NULL);
			if (pkey == NULL)
				DO_EXIT_1("Failed to load private key %s\n", keyfile);
			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);
		} 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 = sk_X509_dup(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 (pagehash == 1)
			fprintf(stderr, "Warning: -ph option is only valid for PE files\n");
#ifdef WITH_GSF
		if (add_msi_dse == 1)
			fprintf(stderr, "Warning: -add-msi-dse option is only valid for MSI files\n");
#endif
		if (nest == 1)
			fprintf(stderr, "Error: -nest option is only valid for PE/MSI files\n");
		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"); /* FLAG_RESERVE_PRESENT */
	} 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) {
		if (pagehash == 1)
			fprintf(stderr, "Warning: -ph option is only valid for PE files\n");
		if (jp >= 0)
			fprintf(stderr, "Warning: -jp option is only valid for CAB files\n");

#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\n", infile);
		ole = gsf_infile_msole_new(src, NULL);

		if (cmd == CMD_EXTRACT) {
			if (output_pkcs7) {
				sig = msi_extract_signature_to_pkcs7(ole);
				if (!sig)
					DO_EXIT_0("Unable to extract existing signature\n");
				outdata = BIO_new_file(outfile, "w+bx");
				if (outdata == NULL)
					DO_EXIT_1("Unable to create %s\n", outfile);
				ret = !PEM_write_bio_PKCS7(outdata, sig);
				BIO_free_all(outdata);
			} else {
				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 || cmd == CMD_ADD || cmd == CMD_ATTACH) {
			if (nest || cmd == CMD_ADD) {
				/*
				 * 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\n");
				}
				if (cmd == CMD_ADD) {
					sig = cursig;
				}
			}
		}

		sink = gsf_output_stdio_new(outfile, NULL);
		if (!sink)
			DO_EXIT_1("Error opening output file %s\n", 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+bx");
			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;
		size_t tmp;

		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);

        /*
         * FLAG_RESERVE_PRESENT is set if this cabinet file contains any reserved fields.
         * When this bit is set, the cbCFHeader, cbCFFolder, and cbCFData fields are present in the CFHEADER.
         * https://www.file-recovery.com/cab-signature-format.htm
         */
		memcpy(buf+4, indata+20, 14);
		buf[4+10] = 0x04; /* FLAG_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) {
		size_t sigpos, siglen;
		unsigned int nrvas;
		unsigned short magic;

		if (jp >= 0)
			fprintf(stderr, "Warning: -jp option is only valid for CAB files\n");
#ifdef WITH_GSF
		if (add_msi_dse == 1)
			fprintf(stderr, "Warning: -add-msi-dse option is only valid for MSI files\n");
#endif
		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);
			if (output_pkcs7) {
				sig = extract_existing_pe_pkcs7(indata, sigpos ? sigpos : fileend, siglen);
				if (!sig)
					DO_EXIT_0("Unable to extract existing signature\n");
				PEM_write_bio_PKCS7(outdata, sig);
			} else {
				BIO_write(outdata, indata + sigpos, siglen);
			}
			goto skip_signing;
		}

		if ((cmd == CMD_SIGN && nest) || (cmd == CMD_ATTACH && nest) || cmd == CMD_ADD) {
			cursig = extract_existing_pe_pkcs7(indata, sigpos ? sigpos : fileend, siglen);
			if (cursig == NULL) {
				DO_EXIT_0("Unable to extract existing signature in -nest mode\n");
			}
			if (cmd == CMD_ADD) {
				sig = cursig;
			}
		}

		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_ADD)
		goto add_only;

	if (cmd == CMD_ATTACH) {
		const char pemhdr[] = "-----BEGIN PKCS7-----";
		sigfilesize = get_file_size(sigfile);
		if (!sigfilesize)
			goto err_cleanup;
		insigdata = map_file(sigfile, sigfilesize);
		if (insigdata == NULL)
			DO_EXIT_1("Failed to open file: %s\n", infile);

		if (sigfilesize >= sizeof(pemhdr) && !memcmp(insigdata, pemhdr, sizeof(pemhdr)-1)) {
			sigbio = BIO_new_mem_buf(insigdata, sigfilesize);
			sig = PEM_read_bio_PKCS7(sigbio, NULL, NULL, NULL);
			BIO_free_all(sigbio);
		} else {
			if (type == FILE_TYPE_PE) {
				sig = extract_existing_pe_pkcs7(insigdata, 0, sigfilesize);
			} else if (type == FILE_TYPE_MSI) {
#ifdef WITH_GSF
				const unsigned char *p = (unsigned char*)insigdata;
				sig = d2i_PKCS7(NULL, &p, sigfilesize);
#else
				DO_EXIT_1("libgsf is not available, msi support is disabled: %s\n", infile);
#endif
			}
		}
		if (!sig)
			DO_EXIT_0("No valid signature found\n");
		goto add_only;
	}

	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 (certs) {
		sk_X509_free(certs);
		certs = NULL;
	}
	if (p7) {
		PKCS7_free(p7);
		p7 = NULL;
	}
	if (xcerts) {
		sk_X509_free(xcerts);
		xcerts = NULL;
	}
	if (p7x) {
		PKCS7_free(p7x);
		p7x = 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);
	size_t 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);

add_only:

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

	if (addBlob && add_unauthenticated_blob(sig))
		DO_EXIT_0("Adding unauthenticated blob failed\n");


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

	if (nest) {
		if (cursig == NULL)
			DO_EXIT_0("Internal error: No 'cursig' was extracted\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 || cmd == CMD_ADD || cmd == CMD_ATTACH) {
			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\n", 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\n", 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 || cmd == CMD_ADD || cmd == CMD_ATTACH) {
			/* 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 || cmd == CMD_ADD || cmd == CMD_ATTACH)
			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;

	if (cmd == CMD_ATTACH) {
		if (type == FILE_TYPE_PE) {
			outdatasize = get_file_size(outfile);
			if (!outdatasize)
				DO_EXIT_0("Error verifying result\n");
			outdataverify = map_file(outfile, outdatasize);
			if (!outdataverify)
				DO_EXIT_0("Error verifying result\n");
			int sigpos = GET_UINT32_LE(outdataverify + peheader + 152 + pe32plus*16);
			int siglen = GET_UINT32_LE(outdataverify + peheader + 152 + pe32plus*16 + 4);
			ret = verify_pe_file(outdataverify, peheader, pe32plus, sigpos, siglen, leafhash);
			if (ret) {
				DO_EXIT_0("Signature mismatch\n");
			}
		} else if (type == FILE_TYPE_MSI) {
#ifdef WITH_GSF
			GsfInput *src;
			GsfInfile *ole;

			src = gsf_input_stdio_new(outfile, NULL);
			if (!src)
				DO_EXIT_1("Error opening file %s\n", outfile);
			ole = gsf_infile_msole_new(src, NULL);
			g_object_unref(src);
			ret = msi_verify_file(ole, leafhash);
			g_object_unref(ole);
			if (ret) {
				DO_EXIT_0("Signature mismatch\n");
			}
#else
			DO_EXIT_1("libgsf is not available, msi support is disabled: %s\n", infile);
#endif
		} else {
			DO_EXIT_1("Unknown input type for file: %s\n", infile);
		}
		printf("Signature successfully attached\n");
	} else {
		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 (certs)
		sk_X509_free(certs);
	if (p7)
		PKCS7_free(p7);
	if (p7x)
		PKCS7_free(p7x);
	if (xcerts)
		sk_X509_free(xcerts);
	if (cert)
		X509_free(cert);
	if (pkey)
		EVP_PKEY_free(pkey);
	if (hash)
		BIO_free_all(hash);
	if (outdata)
		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:

  vim: set ts=4 noexpandtab:
*/
