From d4392c21673143371a9e826219e6d3b11a3397ad Mon Sep 17 00:00:00 2001 From: Per Allansson Date: Mon, 11 Mar 2013 20:09:31 +0100 Subject: [PATCH] - 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) --- ChangeLog | 2 + README | 45 +++++------ osslsigncode.c | 192 +++++++++++++++++++++++++++++----------------- tests/testsign.sh | 61 +++++++++++++++ 4 files changed, 201 insertions(+), 99 deletions(-) create mode 100755 tests/testsign.sh diff --git a/ChangeLog b/ChangeLog index 6a66805..ac6cf9f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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) diff --git a/README b/README index 3bac87b..149ceb2 100644 --- a/README +++ b/README @@ -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 -out - To sign an EXE or MSI file you can now do: - osslsigncode -spc -key \ + osslsigncode sign -certs -key \ -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 -pvk \ - -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 -key -pass \ + osslsigncode sign -certs \ + -key -pass \ -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 -key \ + osslsigncode sign -certs -key \ -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 -pass \ + osslsigncode sign -pkcs12 -pass \ -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 -key \ + osslsigncode sign -certs -key \ -n "Your Application" -i http://www.yourwebsite.com/ \ -jp low \ -in yourapp.cab -out yourapp-signed.cab diff --git a/osslsigncode.c b/osslsigncode.c index b91a107..04f437d 100644 --- a/osslsigncode.c +++ b/osslsigncode.c @@ -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,28 +580,28 @@ 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)); + 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) { + 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); + PKCS7_add_attribute + (si, NID_pkcs9_countersignature, + V_ASN1_SEQUENCE, astr); - 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) { - 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); - PKCS7_add_attribute - (si, NID_pkcs9_countersignature, - V_ASN1_SEQUENCE, astr); - - 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 -key | -pkcs12 " -#if OPENSSL_VERSION_NUMBER > 0x10000000 - "| -spc -pvk " -#endif - ")\n" - "\t\t[ -pass ]\n" + "\t\t( -certs -key | -pkcs12 )\n" + "\t\t[ -pass ]\n" "\t\t[ -h {md5,sha1,sha2} ]\n" "\t\t[ -n ] [ -i ] [ -jp ] [ -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 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,31 +1357,40 @@ 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); - certs = p7->d.sign->cert; + 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)) diff --git a/tests/testsign.sh b/tests/testsign.sh new file mode 100755 index 0000000..a3076dc --- /dev/null +++ b/tests/testsign.sh @@ -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 + +