- added support for reading certificates from PEM files

- fixed compiler warnings
- renamed option -spc to -certs
- no need for -pvk option since we can detect pvk files anyway
- updated docs to reflect changes
- added simple test script
- updated RFC3161 timestamping (but still does not result in valid signature)
This commit is contained in:
Per Allansson 2013-03-11 20:09:31 +01:00
parent 58750a5265
commit d4392c2167
4 changed files with 201 additions and 99 deletions

View File

@ -7,6 +7,8 @@
- fixed problem with not being able to decode timestamps with no newlines
- added stricter checks for PE file validity
- added support for reading keys from PVK files (requires OpenSSL 1.0.0 or later)
- added support for reading certificates from PEM files
- renamed program option: -spc to -certs (old option name still valid)
=== 1.4 (2011-08-12)

45
README
View File

@ -5,25 +5,25 @@ osslsigncode
== WHAT IS IT?
osslsigncode is a small tool that implements part of the functionality
of the Microsoft tool signcode.exe - more exactly the Authenticode
of the Microsoft tool signtool.exe - more exactly the Authenticode
signing and timestamping. But osslsigncode is based on OpenSSL and cURL,
and thus should be able to compile on most platforms where these exist.
== WHY?
Why not use signcode.exe? Because I don't want to go to a Windows
Why not use signtool.exe? Because I don't want to go to a Windows
machine every time I need to sign a binary - I can compile and build
the binaries using Wine on my Linux machine, but I can't sign them
since the signcode.exe makes good use of the CryptoAPI in Windows, and
these APIs aren't (yet?) fully implemented in Wine, so the signcode.exe
since the signtool.exe makes good use of the CryptoAPI in Windows, and
these APIs aren't (yet?) fully implemented in Wine, so the signtool.exe
tool would fail. And, so, osslsigncode was born.
== WHAT CAN IT DO?
It can sign and timestamp EXE, CAB and MSI files. It supports the equivalent
of signcode.exe's "-j javasign.dll -jp low", i.e. add a valid signature
of signtool.exe's "-j javasign.dll -jp low", i.e. add a valid signature
for a CAB file containing Java files. It supports getting the timestamp
through a proxy as well.
@ -43,54 +43,45 @@ Before you can sign a file you need a Software Publishing
Certificate (spc) and a corresponding private key.
This article provides a good starting point as to how
to do the signing with the Microsoft signcode.exe:
to do the signing with the Microsoft signtool.exe:
http://www.matthew-jones.com/articles/codesigning.html
To sign with osslsigncode you need the spc file mentioned in the
article above, and you will also need the private key, it must
be a key file in DER or PEM format, or if osslsigncode was
To sign with osslsigncode you need the certificate file mentioned in the
article above, in SPC or PEM format, and you will also need the private
key which must be a key file in DER or PEM format, or if osslsigncode was
compiled against OpenSSL 1.0.0 or later, in PVK format.
. You can create a DER file from the PEM file by doing:
openssl rsa -passin pass:XXXXX -outform der \
-in <pem-key-file> -out <der-key-file>
To sign an EXE or MSI file you can now do:
osslsigncode -spc <spc-file> -key <der-key-file> \
osslsigncode sign -certs <cert-file> -key <der-key-file> \
-n "Your Application" -i http://www.yourwebsite.com/ \
-in yourapp.exe -out yourapp-signed.exe
or if you are using a PVK key file:
or if you are using a PEM or PVK key file with a password together
with a PEM certificate:
osslsigncode -spc <spc-file> -pvk <der-key-file> \
-n "Your Application" -i http://www.yourwebsite.com/ \
-in yourapp.exe -out yourapp-signed.exe
or if you are using a PEM key file:
osslsigncode -spc <spc-file> -key <der-key-file> -pass <pem-password> \
osslsigncode sign -certs <cert-file> \
-key <key-file> -pass <key-password> \
-n "Your Application" -i http://www.yourwebsite.com/ \
-in yourapp.exe -out yourapp-signed.exe
or if you want to add a timestamp as well:
osslsigncode -spc <spc-file> -key <der-key-file> \
osslsigncode sign -certs <cert-file> -key <key-file> \
-n "Your Application" -i http://www.yourwebsite.com/ \
-t http://timestamp.verisign.com/scripts/timstamp.dll \
-in yourapp.exe -out yourapp-signed.exe
You can use an spc and key stored in a PKCS#12 container:
You can use a certificate and key stored in a PKCS#12 container:
osslsigncode -pkcs12 <pkcs12-file> -pass <pkcs12-password> \
osslsigncode sign -pkcs12 <pkcs12-file> -pass <pkcs12-password> \
-n "Your Application" -i http://www.yourwebsite.com/ \
-in yourapp.exe -out yourapp-signed.exe
To sign a CAB file containing java class files:
osslsigncode -spc <spc-file> -key <der-key-file> \
osslsigncode sign -certs <cert-file> -key <key-file> \
-n "Your Application" -i http://www.yourwebsite.com/ \
-jp low \
-in yourapp.cab -out yourapp-signed.cab

View File

@ -103,6 +103,8 @@ static const char *rcsid = "$Id: osslsigncode.c,v 1.4 2011/08/12 11:08:12 mfive
#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_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... */
@ -427,8 +429,8 @@ static int add_timestamp(PKCS7 *sig, char *url, char *proxy, int rfc3161, const
struct curl_slist *slist = NULL;
CURLcode c;
BIO *bout, *bin, *b64;
u_char *p;
int len;
u_char *p = NULL;
int len = 0;
PKCS7_SIGNER_INFO *si =
sk_PKCS7_SIGNER_INFO_value
(sig->d.sign->signer_info, 0);
@ -472,13 +474,14 @@ static int add_timestamp(PKCS7 *sig, char *url, char *proxy, int rfc3161, const
M_ASN1_OCTET_STRING_set(req->messageImprint->digest, mdbuf, EVP_MD_size(md));
int yes = 1;
req->certReq = &yes;
len = i2d_TimeStampReq(req, NULL);
p = OPENSSL_malloc(len);
len = i2d_TimeStampReq(req, &p);
p -= len;
req->certReq = NULL;
TimeStampReq_free(req);
/* req->certReq = NULL; */
/* TimeStampReq_free(req); */
} else {
TimeStampRequest *req = TimeStampRequest_new();
req->type = OBJ_txt2obj(SPC_TIME_STAMP_REQUEST_OBJID, 1);
@ -491,6 +494,7 @@ static int add_timestamp(PKCS7 *sig, char *url, char *proxy, int rfc3161, const
len = i2d_TimeStampRequest(req, &p);
p -= len;
req->blob->signature = NULL;
TimeStampRequest_free(req);
}
@ -502,6 +506,7 @@ static int add_timestamp(PKCS7 *sig, char *url, char *proxy, int rfc3161, const
BIO_write(bout, p, len);
(void)BIO_flush(bout);
OPENSSL_free(p);
p = NULL;
len = BIO_get_mem_data(bout, &p);
@ -523,11 +528,6 @@ static int add_timestamp(PKCS7 *sig, char *url, char *proxy, int rfc3161, const
BIO_free_all(bin);
fprintf(stderr, "CURL failure: %s\n", curl_easy_strerror(c));
} else {
PKCS7 *p7;
int i;
PKCS7_SIGNER_INFO *info;
ASN1_STRING *astr;
(void)BIO_flush(bin);
if (rfc3161) {
@ -545,9 +545,28 @@ static int add_timestamp(PKCS7 *sig, char *url, char *proxy, int rfc3161, const
TimeStampResp_free(reply);
return -1;
}
p7 = PKCS7_dup(reply->token);
if (((len = i2d_PKCS7(reply->token, NULL)) <= 0) ||
(p = OPENSSL_malloc(len)) == NULL) {
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;
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);
PKCS7_set_attributes(si, attrs);
TimeStampResp_free(reply);
} 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)
@ -561,7 +580,6 @@ static int add_timestamp(PKCS7 *sig, char *url, char *proxy, int rfc3161, const
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));
@ -584,6 +602,7 @@ static int add_timestamp(PKCS7 *sig, char *url, char *proxy, int rfc3161, const
PKCS7_free(p7);
}
}
curl_easy_cleanup(curl);
@ -608,12 +627,8 @@ static void usage(const char *argv0)
fprintf(stderr,
"Usage: %s\n\n\t[ --version | -v ]\n\n"
"\t[ sign ]\n"
"\t\t( -spc <spcfile> -key <keyfile> | -pkcs12 <pkcs12file> "
#if OPENSSL_VERSION_NUMBER > 0x10000000
"| -spc <spcfile> -pvk <pvkfile> "
#endif
")\n"
"\t\t[ -pass <keypass> ]\n"
"\t\t( -certs <certfile> -key <keyfile> | -pkcs12 <pkcs12file> )\n"
"\t\t[ -pass <password> ]\n"
"\t\t[ -h {md5,sha1,sha2} ]\n"
"\t\t[ -n <desc> ] [ -i <url> ] [ -jp <level> ] [ -comm ]\n"
#ifdef ENABLE_CURL
@ -868,7 +883,7 @@ static void tohex(const unsigned char *v, unsigned char *b, int len)
{
int i;
for(i=0; i<len; i++)
sprintf(b+i*2, "%02X", v[i]);
sprintf((char*)b+i*2, "%02X", v[i]);
b[i*2] = 0x00;
}
@ -884,7 +899,7 @@ static void calc_pe_digest(BIO *bio, const EVP_MD *md, unsigned char *mdbuf,
memset(mdbuf, 0, EVP_MAX_MD_SIZE);
BIO_seek(bio, 0);
(void)BIO_seek(bio, 0);
BIO_read(bio, bfb, peheader + 88);
EVP_DigestUpdate(&mdctx, bfb, peheader + 88);
BIO_read(bio, bfb, 4);
@ -911,7 +926,7 @@ static void calc_pe_digest(BIO *bio, const EVP_MD *md, unsigned char *mdbuf,
static unsigned int asn1_simple_hdr_len(const unsigned char *p, unsigned int len) {
if (len <= 2 || p[0] > 0x31)
return 0;
return (p[1]&0x80) ? (2 + p[1]&0x7f) : 2;
return (p[1]&0x80) ? (2 + (p[1]&0x7f)) : 2;
}
static const unsigned char classid_page_hash[] = {
@ -1005,7 +1020,7 @@ static int verify_pe_file(char *indata, unsigned int peheader, int pe32plus,
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 = indata + sigpos + pos + 8;
const unsigned char *blob = (unsigned char*)indata + sigpos + pos + 8;
p7 = d2i_PKCS7(NULL, &blob, l - 8);
if (p7 && PKCS7_type_is_signed(p7) &&
!OBJ_cmp(p7->d.sign->contents->type, OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, 1)) &&
@ -1035,7 +1050,7 @@ static int verify_pe_file(char *indata, unsigned int peheader, int pe32plus,
if (mdtype == -1) {
printf("Failed to extract current message digest\n\n");
return;
return -1;
}
printf("Message digest algorithm : %s\n", OBJ_nid2sn(mdtype));
@ -1104,6 +1119,28 @@ static int verify_pe_file(char *indata, unsigned int peheader, int pe32plus,
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;
}
int main(int argc, char **argv)
{
@ -1120,17 +1157,14 @@ int main(int argc, char **argv)
const char *argv0 = argv[0];
static char buf[64*1024];
char *spcfile, *keyfile, *pkcs12file, *infile, *outfile, *desc, *url, *indata;
#if OPENSSL_VERSION_NUMBER > 0x10000000
char *pvkfile = NULL;
#endif
char *certfile, *keyfile, *pvkfile, *pkcs12file, *infile, *outfile, *desc, *url, *indata;
char *pass = "";
#ifdef ENABLE_CURL
char *turl = NULL, *proxy = NULL, *tsurl = NULL;
#endif
u_char *p;
int ret = 0, i, len = 0, jp = -1, fd = -1, pe32plus = 0, comm = 0;
unsigned int tmp, peheader = 0, padlen;
unsigned int tmp, peheader = 0, padlen = 0;
off_t fileend;
file_type_t type;
cmd_type_t cmd = CMD_SIGN;
@ -1162,7 +1196,7 @@ int main(int argc, char **argv)
OPENSSL_add_all_algorithms_conf();
md = EVP_sha1();
spcfile = keyfile = pkcs12file = infile = outfile = desc = url = NULL;
certfile = keyfile = pvkfile = pkcs12file = infile = outfile = desc = url = NULL;
hash = outdata = NULL;
if (argc > 1) {
@ -1192,20 +1226,15 @@ int main(int argc, char **argv)
} else if (!strcmp(*argv, "-out")) {
if (--argc < 1) usage(argv0);
outfile = *(++argv);
} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-spc")) {
} else if ((cmd == CMD_SIGN) && (!strcmp(*argv, "-spc") || !strcmp(*argv, "-certs"))) {
if (--argc < 1) usage(argv0);
spcfile = *(++argv);
certfile = *(++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);
#if OPENSSL_VERSION_NUMBER > 0x10000000
} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-pvk")) {
if (--argc < 1) usage(argv0);
pvkfile = *(++argv);
#endif
} else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-pass")) {
if (--argc < 1) usage(argv0);
pass = *(++argv);
@ -1300,11 +1329,7 @@ int main(int argc, char **argv)
if (argc > 0 || (turl && tsurl) || !infile ||
(cmd != CMD_VERIFY && !outfile) ||
(cmd == CMD_SIGN && !((spcfile && keyfile) || pkcs12file
#if OPENSSL_VERSION_NUMBER > 0x10000000
|| (spcfile && pvkfile)
#endif
))) {
(cmd == CMD_SIGN && !((certfile && keyfile) || pkcs12file))) {
if (failarg)
fprintf(stderr, "Unknown option: %s\n", failarg);
usage(argv0);
@ -1312,6 +1337,18 @@ int main(int argc, char **argv)
if (cmd == CMD_SIGN) {
/* Read certificate and key */
if (keyfile && (btmp = BIO_new_file(keyfile, "rb")) != NULL) {
unsigned char magic[4];
unsigned char pvkhdr[4] = { 0x1e, 0xf1, 0xb5, 0xb0 };
magic[0] = 0x00;
BIO_read(btmp, magic, 4);
if (!memcmp(magic, pvkhdr, 4)) {
pvkfile = keyfile;
keyfile = NULL;
}
BIO_free(btmp);
}
if (pkcs12file != NULL) {
if ((btmp = BIO_new_file(pkcs12file, "rb")) == NULL ||
(p12 = d2i_PKCS12_bio(btmp, NULL)) == NULL)
@ -1320,30 +1357,39 @@ int main(int argc, char **argv)
if (!PKCS12_parse(p12, pass, &pkey, &cert, &certs))
DO_EXIT_1("Failed to parse PKCS#12 file: %s (Wrong password?)\n", pkcs12file);
PKCS12_free(p12);
#if OPENSSL_VERSION_NUMBER > 0x10000000
} else if (pvkfile != NULL) {
if ((btmp = BIO_new_file(spcfile, "rb")) == NULL ||
(p7 = d2i_PKCS7_bio(btmp, NULL)) == NULL)
DO_EXIT_1("Failed to read DER-encoded spc file: %s\n", spcfile);
#if OPENSSL_VERSION_NUMBER > 0x10000000
if ((btmp = BIO_new_file(certfile, "rb")) == NULL ||
((p7 = d2i_PKCS7_bio(btmp, NULL)) == NULL &&
(certs = PEM_read_certs(btmp, "")) == NULL))
DO_EXIT_1("Failed to read certificate file: %s\n", certfile);
BIO_free(btmp);
if ((btmp = BIO_new_file(pvkfile, "rb")) == NULL ||
( (pkey = b2i_PVK_bio(btmp, NULL, NULL)) == NULL &&
(pkey = b2i_PVK_bio(btmp, NULL, pass)) == NULL))
( (pkey = b2i_PVK_bio(btmp, NULL, 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);
if (p7)
certs = p7->d.sign->cert;
#else
DO_EXIT_1("Can not read keys from PVK files, must compile against a newer version of OpenSSL: %s\n", pvkfile);
#endif
} else {
if ((btmp = BIO_new_file(spcfile, "rb")) == NULL ||
(p7 = d2i_PKCS7_bio(btmp, NULL)) == NULL)
DO_EXIT_1("Failed to read DER-encoded spc file: %s\n", spcfile);
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 certiticate 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)) == 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);
DO_EXIT_2("Failed to read private key file: %s (Wrong password? %s)\n", keyfile, pass);
BIO_free(btmp);
if (p7)
certs = p7->d.sign->cert;
}
}
@ -1540,7 +1586,7 @@ int main(int argc, char **argv)
if (cmd == CMD_EXTRACT) {
/* A lil' bit of ugliness. Reset stream, write signature and skip forward */
BIO_reset(outdata);
(void)BIO_reset(outdata);
BIO_write(outdata, indata + sigpos, siglen);
goto skip_signing;
}
@ -1657,6 +1703,8 @@ int main(int argc, char **argv)
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);
@ -1678,9 +1726,9 @@ int main(int argc, char **argv)
len -= EVP_MD_size(md);
memcpy(buf, p, len);
unsigned char mdbuf[EVP_MAX_MD_SIZE];
int mdlen = BIO_gets(hash, mdbuf, EVP_MAX_MD_SIZE);
int mdlen = BIO_gets(hash, (char*)mdbuf, EVP_MAX_MD_SIZE);
memcpy(buf+len, mdbuf, mdlen);
int seqhdrlen = asn1_simple_hdr_len(buf, len);
int seqhdrlen = asn1_simple_hdr_len((unsigned char*)buf, len);
BIO_write(sigdata, buf+seqhdrlen, len-seqhdrlen+mdlen);
if (!PKCS7_dataFinal(sig, sigdata))

61
tests/testsign.sh Executable file
View File

@ -0,0 +1,61 @@
#!/bin/sh
rm -f key.* cert.*
keytool -genkey \
-alias selfsigned -keysize 2048 -keyalg RSA -keypass passme -storepass passme -keystore key.ks << EOF
John Doe
ACME In
ACME
Springfield
LaLaLand
SE
yes
EOF
echo "Converting key/cert to PKCS12 container"
keytool -importkeystore \
-srckeystore key.ks -srcstoretype JKS -srckeypass passme -srcstorepass passme -srcalias selfsigned \
-destkeystore key.p12 -deststoretype PKCS12 -destkeypass passme -deststorepass passme
rm -f key.ks
echo "Converting key to PEM format"
openssl pkcs12 -in key.p12 -passin pass:passme -nocerts -nodes -out key.pem
echo "Converting key to PEM format (with password)"
openssl rsa -in key.pem -out keyp.pem -passout pass:passme
echo "Converting key to DER format"
openssl rsa -in key.pem -outform DER -out key.der -passout pass:passme
echo "Converting key to PVK format"
openssl rsa -in key.pem -outform PVK -pvk-strong -out key.pvk -passout pass:passme
echo "Converting cert to PEM format"
openssl pkcs12 -in key.p12 -passin pass:passme -nokeys -out cert.pem
echo "Converting cert to SPC format"
openssl crl2pkcs7 -nocrl -certfile cert.pem -outform DER -out cert.spc
wget -q -O putty.exe http://the.earth.li/~sgtatham/putty/latest/x86/putty.exe
../osslsigncode sign -spc cert.spc -key key.pem putty.exe putty1.exe
../osslsigncode sign -certs cert.spc -key keyp.pem -pass passme putty.exe putty2.exe
../osslsigncode sign -certs cert.pem -key keyp.pem -pass passme putty.exe putty3.exe
../osslsigncode sign -certs cert.spc -key key.der putty.exe putty4.exe
../osslsigncode sign -pkcs12 key.p12 -pass passme putty.exe putty5.exe
../osslsigncode sign -certs cert.spc -key key.pvk -pass passme putty.exe putty6.exe
echo ""
echo ""
check=`sha1sum putty[1-9]*.exe | cut -d' ' -f1 | uniq | wc -l`
cmp putty1.exe putty2.exe && \
cmp putty2.exe putty3.exe && \
cmp putty3.exe putty4.exe && \
cmp putty4.exe putty5.exe && \
cmp putty5.exe putty6.exe
if [ $? -ne 0 ]; then
echo "Failure is not an option."
else
echo "Yes, it works."
fi