From 3458244776ecd199775f3d89132aa5f6170f8be8 Mon Sep 17 00:00:00 2001 From: "Brian R. Bondy" Date: Fri, 24 Feb 2012 16:29:41 -0500 Subject: [PATCH] Bug 699700 - Add support for signing and verifying MAR files in libmar and the mar program. r=bsmith --- modules/libmar/Makefile.in | 2 +- modules/libmar/sign/Makefile.in | 69 +++ modules/libmar/sign/mar_sign.c | 525 +++++++++++++++++++++ modules/libmar/sign/nss_secutil.c | 269 +++++++++++ modules/libmar/sign/nss_secutil.h | 73 +++ modules/libmar/src/Makefile.in | 9 +- modules/libmar/src/mar.h | 26 + modules/libmar/src/mar_cmdline.h | 82 ++++ modules/libmar/src/mar_create.c | 37 +- modules/libmar/src/mar_extract.c | 3 +- modules/libmar/src/mar_private.h | 59 +++ modules/libmar/src/mar_read.c | 75 ++- modules/libmar/tool/Makefile.in | 26 +- modules/libmar/tool/mar.c | 152 +++++- modules/libmar/verify/Makefile.in | 69 +++ modules/libmar/verify/cryptox.c | 304 ++++++++++++ modules/libmar/verify/cryptox.h | 150 ++++++ modules/libmar/verify/mar_verify.c | 445 +++++++++++++++++ toolkit/mozapps/update/updater/Makefile.in | 6 + toolkit/toolkit-tiers.mk | 9 +- 20 files changed, 2356 insertions(+), 34 deletions(-) create mode 100644 modules/libmar/sign/Makefile.in create mode 100644 modules/libmar/sign/mar_sign.c create mode 100644 modules/libmar/sign/nss_secutil.c create mode 100644 modules/libmar/sign/nss_secutil.h create mode 100644 modules/libmar/src/mar_cmdline.h create mode 100644 modules/libmar/verify/Makefile.in create mode 100644 modules/libmar/verify/cryptox.c create mode 100644 modules/libmar/verify/cryptox.h create mode 100644 modules/libmar/verify/mar_verify.c diff --git a/modules/libmar/Makefile.in b/modules/libmar/Makefile.in index 0b95b3a2618d..3d13e4c9d100 100644 --- a/modules/libmar/Makefile.in +++ b/modules/libmar/Makefile.in @@ -43,6 +43,6 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk -DIRS = src tool +DIRS = sign verify src tool include $(topsrcdir)/config/rules.mk diff --git a/modules/libmar/sign/Makefile.in b/modules/libmar/sign/Makefile.in new file mode 100644 index 000000000000..415291e0f107 --- /dev/null +++ b/modules/libmar/sign/Makefile.in @@ -0,0 +1,69 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mar signing build config. +# +# The Initial Developer of the Original Code is +# Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2011 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Brian R. Bondy +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = signmar +LIBRARY_NAME = signmar +FORCE_STATIC_LIB = 1 +ifeq ($(OS_ARCH),WINNT) +USE_STATIC_LIBS = 1 +endif + +# This makefile just builds support for reading archives. +CSRCS = \ + mar_sign.c \ + nss_secutil.c \ + $(NULL) + +LOCAL_INCLUDES += -I$(srcdir)/../src \ + -I$(srcdir)/../verify \ + -I$(topsrcdir)/dist/include \ + $(NULL) + +CFLAGS += -DMAR_NSS + +include $(topsrcdir)/config/rules.mk + +# The intermediate (.ii/.s) files for host and target can have the same name... +# disable parallel builds +.NOTPARALLEL: diff --git a/modules/libmar/sign/mar_sign.c b/modules/libmar/sign/mar_sign.c new file mode 100644 index 000000000000..3d80c94beb89 --- /dev/null +++ b/modules/libmar/sign/mar_sign.c @@ -0,0 +1,525 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Archive signing code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifdef XP_WIN +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#endif + +#include +#include +#include +#include +#include +#include "mar_private.h" +#include "mar_cmdline.h" +#include "mar.h" +#include "cryptox.h" +#ifndef XP_WIN +#include +#endif + +#include "nss_secutil.h" + +/** + * Initializes the NSS context. + * + * @param NSSConfigDir The config dir containing the private key to use + * @return 0 on success + * -1 on error +*/ +int +NSSInitCryptoContext(const char *NSSConfigDir) +{ + SECStatus status = NSS_Initialize(NSSConfigDir, + "", "", SECMOD_DB, NSS_INIT_READONLY); + if (SECSuccess != status) { + fprintf(stderr, "ERROR: Could not initialize NSS\n"); + return -1; + } + + return 0; +} + +/** + * Obtains a signing context. + * + * @param ctx A pointer to the signing context to fill + * @return 0 on success + * -1 on error +*/ +int +NSSSignBegin(const char *certName, + SGNContext **ctx, + SECKEYPrivateKey **privKey, + CERTCertificate **cert, + PRUint32 *signatureLength) +{ + secuPWData pwdata = { PW_NONE, 0 }; + if (!certName || !ctx || !privKey || !cert || !signatureLength) { + fprintf(stderr, "ERROR: Invalid parameter passed to NSSSignBegin\n"); + return -1; + } + + /* Get the cert and embedded public key out of the database */ + *cert = PK11_FindCertFromNickname(certName, &pwdata); + if (!*cert) { + fprintf(stderr, "ERROR: Could not find cert from nickname\n"); + return -1; + } + + /* Get the private key out of the database */ + *privKey = PK11_FindKeyByAnyCert(*cert, &pwdata); + if (!*privKey) { + fprintf(stderr, "ERROR: Could not find private key\n"); + return -1; + } + + *signatureLength = PK11_SignatureLen(*privKey); + + if (*signatureLength > BLOCKSIZE) { + fprintf(stderr, + "ERROR: Program must be compiled with a larger block size" + " to support signing with signatures this large: %u.\n", + *signatureLength); + return -1; + } + + /* Check that the key length is large enough for our requirements */ + if (*signatureLength < XP_MIN_SIGNATURE_LEN_IN_BYTES) { + fprintf(stderr, "ERROR: Key length must be >= %d bytes\n", + XP_MIN_SIGNATURE_LEN_IN_BYTES); + return -1; + } + + *ctx = SGN_NewContext (SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, *privKey); + if (!*ctx) { + fprintf(stderr, "ERROR: Could not create signature context\n"); + return -1; + } + + if (SGN_Begin(*ctx) != SECSuccess) { + fprintf(stderr, "ERROR: Could not begin signature\n"); + return -1; + } + + return 0; +} + +/** + * Writes the passed buffer to the file fp and updates the signature context. + * + * @param fpDest The file pointer to write to. + * @param buffer The buffer to write. + * @param size The size of the buffer to write. + * @param ctx The signature context. + * @param err The name of what is being written to in case of error. + * @return 0 on success + * -2 on write error + * -3 on signature update error +*/ +int +WriteAndUpdateSignature(FILE *fpDest, void *buffer, + PRUint32 size, SGNContext *ctx, + const char *err) +{ + if (!size) { + return 0; + } + + if (fwrite(buffer, size, 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write %s\n", err); + return -2; + } + if (SGN_Update(ctx, (const unsigned char *)buffer, size) != SECSuccess) { + fprintf(stderr, "ERROR: Could not update signature context for %s\n", err); + return -3; + } + return 0; +} + +/** + * Reads from fpSrc, writes it to fpDest, and updates the signature context. + * + * @param fpSrc The file pointer to read from. + * @param fpDest The file pointer to write to. + * @param buffer The buffer to write. + * @param size The size of the buffer to write. + * @param ctx The signature context. + * @param err The name of what is being written to in case of error. + * @return 0 on success + * -1 on read error + * -2 on write error + * -3 on signature update error +*/ +int +ReadWriteAndUpdateSignature(FILE *fpSrc, FILE *fpDest, void *buffer, + PRUint32 size, SGNContext *ctx, + const char *err) +{ + if (!size) { + return 0; + } + + if (fread(buffer, size, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read %s\n", err); + return -1; + } + + return WriteAndUpdateSignature(fpDest, buffer, size, ctx, err); +} + + +/** + * Writes out a copy of the MAR at src but with an embedded signature. + * The passed in MAR file must not already be signed or an error will + * be returned. + * + * @param NSSConfigDir The NSS directory containing the private key for signing + * @param certName The nickname of the certificate to use for signing + * @param src The path of the source MAR file to sign + * @param dest The path of the MAR file to write out that is signed + * @return 0 on success + * -1 on error +*/ +int +mar_repackage_and_sign(const char *NSSConfigDir, + const char *certName, + const char *src, + const char *dest) +{ + PRUint32 offsetToIndex, dstOffsetToIndex, indexLength, + numSignatures = 0, signatureLength, leftOver, + signatureAlgorithmID, *offsetToContent, signatureSectionLength; + PRInt64 oldPos, sizeOfEntireMAR = 0, realSizeOfSrcMAR, + signaturePlaceholderOffset, numBytesToCopy, + numChunks, i; + FILE *fpSrc = NULL, *fpDest = NULL; + int rv = -1, oldMar; + SGNContext *ctx = NULL; + SECItem secItem; + char buf[BLOCKSIZE]; + SECKEYPrivateKey *privKey = NULL; + CERTCertificate *cert = NULL; + char *indexBuf = NULL, *indexBufLoc; + + if (!NSSConfigDir || !certName || !src || !dest) { + fprintf(stderr, "ERROR: Invalid parameter passed in.\n"); + return -1; + } + + if (NSSInitCryptoContext(NSSConfigDir)) { + fprintf(stderr, "ERROR: Could not init config dir: %s\n", NSSConfigDir); + goto failure; + } + + PK11_SetPasswordFunc(SECU_GetModulePassword); + + if (NSSSignBegin(certName, &ctx, &privKey, &cert, &signatureLength)) { + fprintf(stderr, "ERROR: NSSSignBegin failed\n"); + goto failure; + } + + fpSrc = fopen(src, "rb"); + if (!fpSrc) { + fprintf(stderr, "ERROR: could not open source file: %s\n", dest); + goto failure; + } + + fpDest = fopen(dest, "wb"); + if (!fpDest) { + fprintf(stderr, "ERROR: could not create target file: %s\n", dest); + goto failure; + } + + /* Determine if the source MAR file has the new fields for signing or not */ + if (is_old_mar(src, &oldMar)) { + fprintf(stderr, "ERROR: could not determine if MAR is old or new.\n"); + goto failure; + } + + /* MAR ID */ + if (ReadWriteAndUpdateSignature(fpSrc, fpDest, + buf, MAR_ID_SIZE, + ctx, "MAR ID")) { + goto failure; + } + + /* Offset to index */ + if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read offset\n"); + goto failure; + } + offsetToIndex = ntohl(offsetToIndex); + + /* Get the real size of the MAR */ + oldPos = ftello(fpSrc); + if (fseeko(fpSrc, 0, SEEK_END)) { + fprintf(stderr, "ERROR: Could not seek to end of file.\n"); + goto failure; + } + realSizeOfSrcMAR = ftello(fpSrc); + if (fseeko(fpSrc, oldPos, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek back to current location.\n"); + goto failure; + } + + if (!oldMar) { + /* Get the MAR length and adjust its size */ + if (fread(&sizeOfEntireMAR, + sizeof(sizeOfEntireMAR), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could read mar size\n"); + goto failure; + } + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + if (sizeOfEntireMAR != realSizeOfSrcMAR) { + fprintf(stderr, "ERROR: Source MAR is not of the right size\n"); + goto failure; + } + + /* Get the num signatures in the source file so we know what to skip over */ + if (fread(&numSignatures, sizeof(numSignatures), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could read num signatures\n"); + goto failure; + } + numSignatures = ntohl(numSignatures); + + /* We do not support resigning */ + if (numSignatures) { + fprintf(stderr, "ERROR: MAR is already signed\n"); + goto failure; + } + } else { + sizeOfEntireMAR = realSizeOfSrcMAR; + } + + if (((PRInt64)offsetToIndex) > sizeOfEntireMAR) { + fprintf(stderr, "ERROR: Offset to index is larger than the file size.\n"); + goto failure; + } + + /* Write out the new offset to the index */ + signatureSectionLength = sizeof(signatureAlgorithmID) + + sizeof(signatureLength) + + signatureLength; + dstOffsetToIndex = offsetToIndex; + if (oldMar) { + dstOffsetToIndex += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); + } + dstOffsetToIndex += signatureSectionLength; + + /* Write out the index offset */ + dstOffsetToIndex = htonl(dstOffsetToIndex); + if (WriteAndUpdateSignature(fpDest, &dstOffsetToIndex, + sizeof(dstOffsetToIndex), ctx, "index offset")) { + goto failure; + } + dstOffsetToIndex = ntohl(dstOffsetToIndex); + + /* Write out the new MAR file size */ + sizeOfEntireMAR += signatureSectionLength; + if (oldMar) { + sizeOfEntireMAR += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); + } + + /* Write out the MAR size */ + sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR); + if (WriteAndUpdateSignature(fpDest, &sizeOfEntireMAR, + sizeof(sizeOfEntireMAR), ctx, "size of MAR")) { + goto failure; + } + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + + /* Write out the number of signatures, for now only 1 is supported */ + numSignatures = 1; + numSignatures = htonl(numSignatures); + if (WriteAndUpdateSignature(fpDest, &numSignatures, + sizeof(numSignatures), ctx, "num signatures")) { + goto failure; + } + numSignatures = ntohl(numSignatures); + + /* Write out the signature ID, for now only an ID of 1 is supported */ + signatureAlgorithmID = htonl(1); + if (WriteAndUpdateSignature(fpDest, &signatureAlgorithmID, + sizeof(signatureAlgorithmID), + ctx, "num signatures")) { + goto failure; + } + signatureAlgorithmID = ntohl(signatureAlgorithmID); + + /* Write out the signature length */ + signatureLength = htonl(signatureLength); + if (WriteAndUpdateSignature(fpDest, &signatureLength, + sizeof(signatureLength), + ctx, "signature length")) { + goto failure; + } + signatureLength = ntohl(signatureLength); + + /* Write out a placeholder for the signature, we'll come back to this later + *** THIS IS NOT SIGNED because it is a placeholder that will be replaced + below, plus it is going to be the signature itself. *** */ + memset(buf, 0, sizeof(buf)); + signaturePlaceholderOffset = ftello(fpDest); + if (fwrite(buf, signatureLength, 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write signature length\n"); + goto failure; + } + + /* Write out the rest of the MAR excluding the index header and index + offsetToIndex unfortunately has to remain 32-bit because for backwards + compatibility with the old MAR file format. */ + if (ftello(fpSrc) > ((PRInt64)offsetToIndex)) { + fprintf(stderr, "ERROR: Index offset is too small.\n"); + goto failure; + } + numBytesToCopy = ((PRInt64)offsetToIndex) - ftello(fpSrc); + numChunks = numBytesToCopy / BLOCKSIZE; + leftOver = numBytesToCopy % BLOCKSIZE; + + /* Read each file and write it to the MAR file */ + for (i = 0; i < numChunks; ++i) { + if (ReadWriteAndUpdateSignature(fpSrc, fpDest, buf, + BLOCKSIZE, ctx, "content block")) { + goto failure; + } + } + + /* Write out the left over */ + if (ReadWriteAndUpdateSignature(fpSrc, fpDest, buf, + leftOver, ctx, "left over content block")) { + goto failure; + } + + /* Length of the index */ + if (ReadWriteAndUpdateSignature(fpSrc, fpDest, &indexLength, + sizeof(indexLength), ctx, "index length")) { + goto failure; + } + indexLength = ntohl(indexLength); + + /* Consume the index and adjust each index by signatureSectionLength */ + indexBuf = malloc(indexLength); + indexBufLoc = indexBuf; + if (fread(indexBuf, indexLength, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read index\n"); + goto failure; + } + while (indexBufLoc != (indexBuf + indexLength)) { + /* Adjust the offset */ + offsetToContent = (PRUint32 *)indexBufLoc; + *offsetToContent = ntohl(*offsetToContent); + if (oldMar) { + *offsetToContent += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); + } + *offsetToContent += signatureSectionLength; + *offsetToContent = htonl(*offsetToContent); + /* Skip past the offset, length, and flags */ + indexBufLoc += 3 * sizeof(PRUint32); + indexBufLoc += strlen(indexBufLoc) + 1; + } + if (WriteAndUpdateSignature(fpDest, indexBuf, + indexLength, ctx, "index")) { + goto failure; + } + + /* Ensure that we don't sign a file that is too large to be accepted by + the verification function. */ + if (ftello(fpDest) > MAX_SIZE_OF_MAR_FILE) { + goto failure; + } + + /* Get the signature */ + if (SGN_End(ctx, &secItem) != SECSuccess) { + fprintf(stderr, "ERROR: Could not end signature context\n"); + goto failure; + } + if (signatureLength != secItem.len) { + fprintf(stderr, "ERROR: Signature is not the expected length\n"); + goto failure; + } + + /* Get back to the location of the signature placeholder */ + if (fseeko(fpDest, signaturePlaceholderOffset, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to signature offset\n"); + goto failure; + } + + /* Write out the calculated signature. + *** THIS IS NOT SIGNED because it is the signature itself. *** */ + if (fwrite(secItem.data, secItem.len, 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write signature\n"); + goto failure; + } + + rv = 0; +failure: + if (fpSrc) { + fclose(fpSrc); + } + + if (fpDest) { + fclose(fpDest); + } + + if (rv) { + remove(dest); + } + + if (indexBuf) { + free(indexBuf); + } + + if (ctx) { + SGN_DestroyContext(ctx, PR_TRUE); + } + + if (cert) { + CERT_DestroyCertificate(cert); + } + + if (privKey) { + SECKEY_DestroyPrivateKey(privKey); + } + + if (rv) { + remove(dest); + } + return rv; +} diff --git a/modules/libmar/sign/nss_secutil.c b/modules/libmar/sign/nss_secutil.c new file mode 100644 index 000000000000..ae640ac45f70 --- /dev/null +++ b/modules/libmar/sign/nss_secutil.c @@ -0,0 +1,269 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is code copied from secutil and secpwd. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* With the exception of GetPasswordString, this file was + copied from NSS's cmd/lib/secutil.c hg revision 8f011395145e */ + +#include "nss_secutil.h" + +#include "prprf.h" +#ifdef XP_WIN +#include +#else +#include +#endif + +static char consoleName[] = { +#ifdef XP_UNIX + "/dev/tty" +#else +#ifdef XP_OS2 + "\\DEV\\CON" +#else + "CON:" +#endif +#endif +}; + +#if defined(_WINDOWS) +static char * quiet_fgets (char *buf, int length, FILE *input) +{ + int c; + char *end = buf; + + /* fflush (input); */ + memset (buf, 0, length); + + if (!isatty(fileno(input))) { + return fgets(buf,length,input); + } + + while (1) + { +#if defined (_WIN32_WCE) + c = getchar(); /* gets a character from stdin */ +#else + c = getch(); /* getch gets a character from the console */ +#endif + if (c == '\b') + { + if (end > buf) + end--; + } + + else if (--length > 0) + *end++ = c; + + if (!c || c == '\n' || c == '\r') + break; + } + + return buf; +} +#endif + +char * +GetPasswordString(void *arg, char *prompt) +{ + FILE *input = stdin; + char phrase[200] = {'\0'}; + int isInputTerminal = isatty(fileno(stdin)); + +#ifndef _WINDOWS + if (isInputTerminal) { + input = fopen(consoleName, "r"); + if (input == NULL) { + fprintf(stderr, "Error opening input terminal for read\n"); + return NULL; + } + } +#endif + + if (isInputTerminal) { + fprintf(stdout, "Please enter your password:\n"); + fflush(stdout); + } + + QUIET_FGETS (phrase, sizeof(phrase), input); + + if (isInputTerminal) { + fprintf(stdout, "\n"); + } + +#ifndef _WINDOWS + if (isInputTerminal) { + fclose(input); + } +#endif + + /* Strip off the newlines if present */ + if (phrase[PORT_Strlen(phrase)-1] == '\n' || + phrase[PORT_Strlen(phrase)-1] == '\r') { + phrase[PORT_Strlen(phrase)-1] = 0; + } + return (char*) PORT_Strdup(phrase); +} + +char * +SECU_FilePasswd(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + char* phrases, *phrase; + PRFileDesc *fd; + PRInt32 nb; + char *pwFile = arg; + int i; + const long maxPwdFileSize = 4096; + char* tokenName = NULL; + int tokenLen = 0; + + if (!pwFile) + return 0; + + if (retry) { + return 0; /* no good retrying - the files contents will be the same */ + } + + phrases = PORT_ZAlloc(maxPwdFileSize); + + if (!phrases) { + return 0; /* out of memory */ + } + + fd = PR_Open(pwFile, PR_RDONLY, 0); + if (!fd) { + fprintf(stderr, "No password file \"%s\" exists.\n", pwFile); + PORT_Free(phrases); + return NULL; + } + + nb = PR_Read(fd, phrases, maxPwdFileSize); + + PR_Close(fd); + + if (nb == 0) { + fprintf(stderr,"password file contains no data\n"); + PORT_Free(phrases); + return NULL; + } + + if (slot) { + tokenName = PK11_GetTokenName(slot); + if (tokenName) { + tokenLen = PORT_Strlen(tokenName); + } + } + i = 0; + do + { + int startphrase = i; + int phraseLen; + + /* handle the Windows EOL case */ + while (phrases[i] != '\r' && phrases[i] != '\n' && i < nb) i++; + /* terminate passphrase */ + phrases[i++] = '\0'; + /* clean up any EOL before the start of the next passphrase */ + while ( (isource != PW_NONE) { + PR_fprintf(PR_STDERR, "Incorrect password/PIN entered.\n"); + return NULL; + } + + switch (pwdata->source) { + case PW_NONE: + sprintf(prompt, "Enter Password or Pin for \"%s\":", + PK11_GetTokenName(slot)); + return GetPasswordString(NULL, prompt); + case PW_FROMFILE: + /* Instead of opening and closing the file every time, get the pw + * once, then keep it in memory (duh). + */ + pw = SECU_FilePasswd(slot, retry, pwdata->data); + pwdata->source = PW_PLAINTEXT; + pwdata->data = PL_strdup(pw); + /* it's already been dup'ed */ + return pw; + case PW_EXTERNAL: + sprintf(prompt, + "Press Enter, then enter PIN for \"%s\" on external device.\n", + PK11_GetTokenName(slot)); + (void) GetPasswordString(NULL, prompt); + /* Fall Through */ + case PW_PLAINTEXT: + return PL_strdup(pwdata->data); + default: + break; + } + + PR_fprintf(PR_STDERR, "Password check failed: No password found.\n"); + return NULL; +} diff --git a/modules/libmar/sign/nss_secutil.h b/modules/libmar/sign/nss_secutil.h new file mode 100644 index 000000000000..eb1611fc3fa0 --- /dev/null +++ b/modules/libmar/sign/nss_secutil.h @@ -0,0 +1,73 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is code copied from secutil and secpwd. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* With the exception of GetPasswordString, this file was + copied from NSS's cmd/lib/secutil.h hg revision 8f011395145e */ + +#ifndef NSS_SECUTIL_H_ +#define NSS_SECUTIL_H_ + +#include "nss.h" +#include "pk11pub.h" +#include "cryptohi.h" +#include "hasht.h" +#include "cert.h" +#include "key.h" + +typedef struct { + enum { + PW_NONE = 0, + PW_FROMFILE = 1, + PW_PLAINTEXT = 2, + PW_EXTERNAL = 3 + } source; + char *data; +} secuPWData; + +#if( defined(_WINDOWS) && !defined(_WIN32_WCE)) +#include +#include +#define QUIET_FGETS quiet_fgets +static char * quiet_fgets (char *buf, int length, FILE *input); +#else +#define QUIET_FGETS fgets +#endif + +char * +SECU_GetModulePassword(PK11SlotInfo *slot, PRBool retry, void *arg); + +#endif diff --git a/modules/libmar/src/Makefile.in b/modules/libmar/src/Makefile.in index fa8e255231ad..c651f4627344 100644 --- a/modules/libmar/src/Makefile.in +++ b/modules/libmar/src/Makefile.in @@ -53,15 +53,20 @@ endif # This makefile just builds support for reading archives. -CSRCS = \ + +HOST_CSRCS = \ mar_create.c \ mar_extract.c \ mar_read.c \ $(NULL) -HOST_CSRCS = $(CSRCS) + +CSRCS = \ + $(HOST_CSRCS) \ + $(NULL) EXPORTS = \ mar.h \ + mar_cmdline.h \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/modules/libmar/src/mar.h b/modules/libmar/src/mar.h index 45f46e835a75..bd6a859b7e3d 100644 --- a/modules/libmar/src/mar.h +++ b/modules/libmar/src/mar.h @@ -57,6 +57,13 @@ typedef struct MarItem_ { char name[1]; /* file path */ } MarItem; +#define TABLESIZE 256 + +struct MarFile_ { + FILE *fp; + MarItem *item_table[TABLESIZE]; +}; + typedef struct MarFile_ MarFile; /** @@ -137,6 +144,25 @@ int mar_create(const char *dest, int numfiles, char **files); */ int mar_extract(const char *path); +/** + * Verifies the embedded signature for the specified mar file. + * We do not check that the certificate was issued by any trusted authority. + * We assume it to be self-signed. We do not check whether the certificate + * is valid for this usage. + * + * @param mar The already opened MAR file. + * @param certData The certificate file data. + * @param sizeOfCertData The size of the cert data. + * @return 0 on success + * a negative number if there was an error + * a positive number if the signature does not verify + */ +#ifdef XP_WIN +int mar_verify_signatureW(MarFile *mar, + const char *certData, + PRUint32 sizeOfCertData); +#endif + #ifdef __cplusplus } #endif diff --git a/modules/libmar/src/mar_cmdline.h b/modules/libmar/src/mar_cmdline.h new file mode 100644 index 000000000000..5a6d4e7b4005 --- /dev/null +++ b/modules/libmar/src/mar_cmdline.h @@ -0,0 +1,82 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is command line utility function declarations. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef MAR_CMDLINE_H__ +#define MAR_CMDLINE_H__ + +/* We use NSPR here just to import the definition of PRUint32 */ +#include "prtypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Determines if the MAR file is new or old. + * + * @param path The path of the MAR file to check. + * @param oldMar An out parameter specifying if the MAR file is new or old. + * @return A non-zero value if an error occurred and the information + cannot be determined. + */ +int is_old_mar(const char *path, int *oldMar); + +/** + * Verifies the embedded signature of the specified file path. + * This is only used by the signmar program when used with arguments to verify + * a MAR. This should not be used to verify a MAR that will be extracted in the + * same operation by updater code. This function prints the error message if + * verification fails. + * + * @param pathToMAR The path of the MAR file who's signature should be checked + * @param certData The certificate file data. + * @param sizeOfCertData The size of the cert data. + * @param certName Used only if compiled as NSS, specifies the certName + * @return 0 on success + * a negative number if there was an error + * a positive number if the signature does not verify + */ +int mar_verify_signature(const char *pathToMAR, + const char *certData, + PRUint32 sizeOfCertData, + const char *certName); + +#ifdef __cplusplus +} +#endif + +#endif /* MAR_CMDLINE_H__ */ diff --git a/modules/libmar/src/mar_create.c b/modules/libmar/src/mar_create.c index daf45d9ce7b7..a15c6a1db08a 100644 --- a/modules/libmar/src/mar_create.c +++ b/modules/libmar/src/mar_create.c @@ -40,10 +40,9 @@ #include #include #include -#include #include -#include "mar.h" #include "mar_private.h" +#include "mar.h" #ifdef XP_WIN #include @@ -127,7 +126,8 @@ static int mar_concat_file(FILE *fp, const char *path) { int mar_create(const char *dest, int num_files, char **files) { struct MarItemStack stack; - PRUint32 offset_to_index = 0, size_of_index; + PRUint32 offset_to_index = 0, size_of_index, num_signatures; + PRUint64 size_of_entire_MAR = 0; struct stat st; FILE *fp; int i, rv = -1; @@ -145,7 +145,21 @@ int mar_create(const char *dest, int num_files, char **files) { if (fwrite(&offset_to_index, sizeof(PRUint32), 1, fp) != 1) goto failure; - stack.last_offset = MAR_ID_SIZE + sizeof(PRUint32); + stack.last_offset = MAR_ID_SIZE + + sizeof(num_signatures) + + sizeof(offset_to_index) + + sizeof(size_of_entire_MAR); + + /* We will circle back on this at the end of the MAR creation to fill it */ + if (fwrite(&size_of_entire_MAR, sizeof(size_of_entire_MAR), 1, fp) != 1) { + goto failure; + } + + /* Write out the number of signatures, for now only at most 1 is supported */ + num_signatures = 0; + if (fwrite(&num_signatures, sizeof(num_signatures), 1, fp) != 1) { + goto failure; + } for (i = 0; i < num_files; ++i) { if (stat(files[i], &st)) { @@ -168,12 +182,27 @@ int mar_create(const char *dest, int num_files, char **files) { if (fwrite(stack.head, stack.size_used, 1, fp) != 1) goto failure; + /* To protect against invalid MAR files, we assumes that the MAR file + size is less than or equal to MAX_SIZE_OF_MAR_FILE. */ + if (ftell(fp) > MAX_SIZE_OF_MAR_FILE) { + goto failure; + } + /* write out offset to index file in network byte order */ offset_to_index = htonl(stack.last_offset); if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) goto failure; if (fwrite(&offset_to_index, sizeof(offset_to_index), 1, fp) != 1) goto failure; + offset_to_index = ntohl(stack.last_offset); + + size_of_entire_MAR = ((PRUint64)stack.last_offset) + + stack.size_used + + sizeof(size_of_index); + size_of_entire_MAR = HOST_TO_NETWORK64(size_of_entire_MAR); + if (fwrite(&size_of_entire_MAR, sizeof(size_of_entire_MAR), 1, fp) != 1) + goto failure; + size_of_entire_MAR = NETWORK_TO_HOST64(size_of_entire_MAR); rv = 0; failure: diff --git a/modules/libmar/src/mar_extract.c b/modules/libmar/src/mar_extract.c index 563838daf770..3dbd78eb9b05 100644 --- a/modules/libmar/src/mar_extract.c +++ b/modules/libmar/src/mar_extract.c @@ -41,9 +41,8 @@ #include #include #include -#include -#include "mar.h" #include "mar_private.h" +#include "mar.h" #ifdef XP_WIN #include diff --git a/modules/libmar/src/mar_private.h b/modules/libmar/src/mar_private.h index 768505b23386..ac8db0b47e16 100644 --- a/modules/libmar/src/mar_private.h +++ b/modules/libmar/src/mar_private.h @@ -39,12 +39,71 @@ #ifndef MAR_PRIVATE_H__ #define MAR_PRIVATE_H__ +#include "prtypes.h" +#include "limits.h" + +/* Code in this module requires a guarantee that the size + of PRUint32 and PRUint64 are 4 and 8 bytes respectively. */ +PR_STATIC_ASSERT(sizeof(PRUint32) == 4); +PR_STATIC_ASSERT(sizeof(PRUint64) == 8); + #define BLOCKSIZE 4096 #define ROUND_UP(n, incr) (((n) / (incr) + 1) * (incr)) #define MAR_ID "MAR1" #define MAR_ID_SIZE 4 +/* The signature block comes directly after the header block + which is 16 bytes */ +#define SIGNATURE_BLOCK_OFFSET 16 + +/* We have a MAX_SIGNATURES limit so that an invalid MAR will never + waste too much of either updater's or signmar's time. */ +#define MAX_SIGNATURES 8 + +/* Make sure the file is less than 500MB. We do this to protect against + invalid MAR files. */ +#define MAX_SIZE_OF_MAR_FILE ((PRInt64)524288000) + +/* Existing code makes assumptions that the file size is + smaller than LONG_MAX. */ +PR_STATIC_ASSERT(MAX_SIZE_OF_MAR_FILE < ((PRInt64)LONG_MAX)); + +/* We store at most the size up to the signature block + 4 + bytes per BLOCKSIZE bytes */ +PR_STATIC_ASSERT(sizeof(BLOCKSIZE) < \ + (SIGNATURE_BLOCK_OFFSET + sizeof(PRUint32))); + +/* The maximum size of any signature supported by current and future + implementations of the signmar program. */ +#define MAX_SIGNATURE_LENGTH 2048 + #define MAR_ITEM_SIZE(namelen) (3*sizeof(PRUint32) + (namelen) + 1) +/* The mar program is compiled as a host bin so we don't have access to NSPR at + runtime. For that reason we use ntohl, htonl, and define HOST_TO_NETWORK64 + instead of the NSPR equivalents. */ +#ifdef XP_WIN +#include +#define ftello _ftelli64 +#define fseeko _fseeki64 +#else +#define _FILE_OFFSET_BITS 64 +#include +#include +#endif + +#include + +#define HOST_TO_NETWORK64(x) ( \ + ((((PRUint64) x) & 0xFF) << 56) | \ + ((((PRUint64) x) >> 8) & 0xFF) << 48) | \ + (((((PRUint64) x) >> 16) & 0xFF) << 40) | \ + (((((PRUint64) x) >> 24) & 0xFF) << 32) | \ + (((((PRUint64) x) >> 32) & 0xFF) << 24) | \ + (((((PRUint64) x) >> 40) & 0xFF) << 16) | \ + (((((PRUint64) x) >> 48) & 0xFF) << 8) | \ + (((PRUint64) x) >> 56) +#define NETWORK_TO_HOST64 HOST_TO_NETWORK64 + #endif /* MAR_PRIVATE_H__ */ diff --git a/modules/libmar/src/mar_read.c b/modules/libmar/src/mar_read.c index b8468c2d13cc..9dfedb7499cb 100644 --- a/modules/libmar/src/mar_read.c +++ b/modules/libmar/src/mar_read.c @@ -39,10 +39,9 @@ #include #include #include -#include #include -#include "mar.h" #include "mar_private.h" +#include "mar.h" #ifdef XP_WIN #include @@ -50,13 +49,6 @@ #include #endif -#define TABLESIZE 256 - -struct MarFile_ { - FILE *fp; - MarItem *item_table[TABLESIZE]; -}; - /* this is the same hash algorithm used by nsZipArchive.cpp */ static PRUint32 mar_hash_name(const char *name) { PRUint32 val = 0; @@ -291,3 +283,68 @@ int mar_read(MarFile *mar, const MarItem *item, int offset, char *buf, return fread(buf, 1, nr, mar->fp); } + +/** + * Determines if the MAR file is new or old. + * + * @param path The path of the MAR file to check. + * @param oldMar An out parameter specifying if the MAR file is new or old. + * @return A non-zero value if an error occurred and the information + cannot be determined. + */ +int is_old_mar(const char *path, int *oldMar) +{ + PRUint32 offsetToIndex, offsetToContent, oldPos; + FILE *fp; + + if (!oldMar) { + return -1; + } + + fp = fopen(path, "rb"); + if (!fp) { + return -1; + } + + oldPos = ftell(fp); + + /* Skip to the start of the offset index */ + if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) { + return -1; + } + + /* Read the offset to the index. */ + if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fp) != 1) + return -1; + offsetToIndex = ntohl(offsetToIndex); + + /* Skip to the first index entry past the index size field + We do it in 2 calls because offsetToIndex + sizeof(PRUint32) + could oerflow in theory. */ + if (fseek(fp, offsetToIndex, SEEK_SET)) { + return -1; + } + + if (fseek(fp, sizeof(PRUint32), SEEK_CUR)) { + return -1; + } + + /* Read the offset to content field. */ + if (fread(&offsetToContent, sizeof(offsetToContent), 1, fp) != 1) + return -1; + offsetToContent = ntohl(offsetToContent); + + /* Check if we have a new or old MAR file */ + if (offsetToContent == MAR_ID_SIZE + sizeof(PRUint32)) { + *oldMar = 1; + } else { + *oldMar = 0; + } + + /* Restore back our old position */ + if (fseek(fp, oldPos, SEEK_SET)) { + return -1; + } + + return 0; +} diff --git a/modules/libmar/tool/Makefile.in b/modules/libmar/tool/Makefile.in index 7584c50b9976..9a221dd9cd35 100644 --- a/modules/libmar/tool/Makefile.in +++ b/modules/libmar/tool/Makefile.in @@ -51,23 +51,37 @@ endif # The mar executable is output into dist/host/bin since it is something that # would only be used by our build system and should not itself be included in a # Mozilla distribution. -HOST_PROGRAM = mar$(HOST_BIN_SUFFIX) +HOST_PROGRAM = mar$(HOST_BIN_SUFFIX) +PROGRAM = signmar$(BIN_SUFFIX) +HOST_CFLAGS += -DNO_SIGN_VERIFY -HOST_CSRCS = \ - mar.c \ - $(NULL) +HOST_CSRCS = \ + mar.c \ + $(NULL) +CSRCS = $(HOST_CSRCS) -HOST_LIBS = $(DIST)/host/lib/$(LIB_PREFIX)hostmar.$(LIB_SUFFIX) +HOST_LIBS = $(DIST)/host/lib/$(LIB_PREFIX)hostmar.$(LIB_SUFFIX) +LIBS = $(DEPTH)/modules/libmar/src/$(LIB_PREFIX)mar.$(LIB_SUFFIX) \ + $(DEPTH)/modules/libmar/sign/$(LIB_PREFIX)signmar.$(LIB_SUFFIX) \ + $(DEPTH)/modules/libmar/verify/$(LIB_PREFIX)verifymar.$(LIB_SUFFIX) \ + $(DIST)/lib/$(LIB_PREFIX)nss3.$(LIB_SUFFIX) \ + $(DIST)/lib/$(LIB_PREFIX)nssutil3.$(LIB_SUFFIX) \ + $(NSPR_LIBS) \ + $(NULL) ifeq ($(HOST_OS_ARCH),WINNT) HOST_EXTRA_LIBS += $(call EXPAND_LIBNAME,ws2_32) +EXTRA_LIBS += $(call EXPAND_LIBNAME,ws2_32) +EXTRA_LIBS += $(call EXPAND_LIBNAME,crypt32) +EXTRA_LIBS += $(call EXPAND_LIBNAME,advapi32) endif include $(topsrcdir)/config/rules.mk ifdef CROSS_COMPILE ifdef HOST_NSPR_MDCPUCFG -HOST_CFLAGS += -DMDCPUCFG=$(HOST_NSPR_MDCPUCFG) +HOST_CFLAGS += -DMDCPUCFG=$(HOST_NSPR_MDCPUCFG) +CFLAGS += -DMDCPUCFG=$(HOST_NSPR_MDCPUCFG) endif endif diff --git a/modules/libmar/tool/mar.c b/modules/libmar/tool/mar.c index 127d622af397..90ba500134f2 100644 --- a/modules/libmar/tool/mar.c +++ b/modules/libmar/tool/mar.c @@ -37,20 +37,46 @@ * ***** END LICENSE BLOCK ***** */ #include +#include #include "mar.h" +#include "mar_cmdline.h" #ifdef XP_WIN +#include #include #define chdir _chdir #else #include #endif +#if !defined(NO_SIGN_VERIFY) && (!defined(XP_WIN) || defined(MAR_NSS)) +int NSSInitCryptoContext(const char *NSSConfigDir); +#endif + +int mar_repackage_and_sign(const char *NSSConfigDir, + const char *certName, + const char *src, + const char * dest); + static void print_usage() { - printf("usage: mar [-C dir] {-c|-x|-t} archive.mar [files...]\n"); + printf("usage:\n"); + printf(" mar [-C workingDir] {-c|-x|-t} archive.mar [files...]\n"); +#ifndef NO_SIGN_VERIFY + printf(" mar [-C workingDir] -d NSSConfigDir -n certname -s " + "archive.mar out_signed_archive.mar\n"); + +#if defined(XP_WIN) && !defined(MAR_NSS) + printf(" mar [-C workingDir] -D DERFilePath -v signed_archive.mar\n"); +#else + printf(" mar [-C workingDir] -d NSSConfigDir -n certname " + "-v signed_archive.mar\n"); +#endif +#endif } -static int mar_test_callback(MarFile *mar, const MarItem *item, void *unused) { +static int mar_test_callback(MarFile *mar, + const MarItem *item, + void *unused) { printf("%u\t0%o\t%s\n", item->length, item->flags, item->name); return 0; } @@ -70,17 +96,59 @@ static int mar_test(const char *path) { } int main(int argc, char **argv) { - int command; + char *NSSConfigDir = NULL; + char *certName = NULL; +#if defined(XP_WIN) && !defined(MAR_NSS) && !defined(NO_SIGN_VERIFY) + HANDLE certFile; + DWORD fileSize; + DWORD read; + char *certBuffer; + char *DERFilePath = NULL; +#endif if (argc < 3) { print_usage(); return -1; } - if (argv[1][1] == 'C') { - chdir(argv[2]); - argv += 2; - argc -= 2; + while (argc > 0) { + if (argv[1][0] == '-' && (argv[1][1] == 'c' || + argv[1][1] == 't' || argv[1][1] == 'x' || + argv[1][1] == 'v' || argv[1][1] == 's')) { + break; + /* -C workingdirectory */ + } else if (argv[1][0] == '-' && argv[1][1] == 'C') { + chdir(argv[2]); + argv += 2; + argc -= 2; + } +#if defined(XP_WIN) && !defined(MAR_NSS) && !defined(NO_SIGN_VERIFY) + /* -D DERFilePath */ + else if (argv[1][0] == '-' && argv[1][1] == 'D') { + DERFilePath = argv[2]; + argv += 2; + argc -= 2; + } +#endif + /* -d NSSConfigdir */ + else if (argv[1][0] == '-' && argv[1][1] == 'd') { + NSSConfigDir = argv[2]; + argv += 2; + argc -= 2; + /* -n certName */ + } else if (argv[1][0] == '-' && argv[1][1] == 'n') { + certName = argv[2]; + argv += 2; + argc -= 2; + } else { + print_usage(); + return -1; + } + } + + if (argv[1][0] != '-') { + print_usage(); + return -1; } switch (argv[1][1]) { @@ -90,6 +158,76 @@ int main(int argc, char **argv) { return mar_test(argv[2]); case 'x': return mar_extract(argv[2]); + +#ifndef NO_SIGN_VERIFY + case 'v': + +#if defined(XP_WIN) && !defined(MAR_NSS) + if (!DERFilePath) { + print_usage(); + return -1; + } + /* If the mar program was built using CryptoAPI, then read in the buffer + containing the cert from disk. */ + certFile = CreateFileA(DERFilePath, GENERIC_READ, + FILE_SHARE_READ | + FILE_SHARE_WRITE | + FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + 0, NULL); + if (INVALID_HANDLE_VALUE == certFile) { + return -1; + } + fileSize = GetFileSize(certFile, NULL); + certBuffer = malloc(fileSize); + if (!ReadFile(certFile, certBuffer, fileSize, &read, NULL) || + fileSize != read) { + CloseHandle(certFile); + free(certBuffer); + return -1; + } + CloseHandle(certFile); + + if (mar_verify_signature(argv[2], certBuffer, fileSize, NULL)) { + int oldMar = 0; + free(certBuffer); + + /* Determine if the source MAR file has the new fields for signing or not */ + if (is_old_mar(argv[2], &oldMar)) { + fprintf(stderr, "ERROR: could not determine if MAR is old or new.\n"); + } else if (oldMar) { + fprintf(stderr, "ERROR: The MAR file is in the old format so has" + " no signature to verify.\n"); + } + return -1; + } + + free(certBuffer); + return 0; +#else + if (!NSSConfigDir || !certName) { + print_usage(); + return -1; + } + + if (NSSInitCryptoContext(NSSConfigDir)) { + fprintf(stderr, "ERROR: Could not initialize crypto library.\n"); + return -1; + } + + return mar_verify_signature(argv[2], NULL, 0, + certName); + +#endif /* defined(XP_WIN) && !defined(MAR_NSS) */ + case 's': + if (!NSSConfigDir || !certName || argc < 4) { + print_usage(); + return -1; + } + return mar_repackage_and_sign(NSSConfigDir, certName, argv[2], argv[3]); +#endif /* endif NO_SIGN_VERIFY disabled */ + default: print_usage(); return -1; diff --git a/modules/libmar/verify/Makefile.in b/modules/libmar/verify/Makefile.in new file mode 100644 index 000000000000..f9375fc11597 --- /dev/null +++ b/modules/libmar/verify/Makefile.in @@ -0,0 +1,69 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mar verify build config. +# +# The Initial Developer of the Original Code is +# Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2011 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Brian R. Bondy +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = verifymar +LIBRARY_NAME = verifymar +FORCE_STATIC_LIB = 1 +ifeq ($(OS_ARCH),WINNT) +USE_STATIC_LIBS = 1 +endif + +# This makefile just builds support for reading archives. +CSRCS = \ + mar_verify.c \ + cryptox.c \ + $(NULL) + +LOCAL_INCLUDES += -I$(srcdir)/../src + +ifneq ($(OS_ARCH),WINNT) +DEFINES += -DMAR_NSS +LOCAL_INCLUDES += -I$(srcdir)/../sign +endif + +include $(topsrcdir)/config/rules.mk + +# The intermediate (.ii/.s) files for host and target can have the same name... +# disable parallel builds +.NOTPARALLEL: diff --git a/modules/libmar/verify/cryptox.c b/modules/libmar/verify/cryptox.c new file mode 100644 index 000000000000..a76b84f199b9 --- /dev/null +++ b/modules/libmar/verify/cryptox.c @@ -0,0 +1,304 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is cryptographic wrappers for Mozilla archive code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifdef XP_WIN +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#endif + +#include +#include "cryptox.h" + +#if defined(MAR_NSS) + +/** + * Loads the public key for the specified cert name from the NSS store. + * + * @param certName The cert name to find. + * @param publicKey Out parameter for the public key to use. + * @param cert Out parameter for the certificate to use. + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +NSS_LoadPublicKey(const char *certNickname, + SECKEYPublicKey **publicKey, + CERTCertificate **cert) +{ + secuPWData pwdata = { PW_NONE, 0 }; + if (!cert || !publicKey || !cert) { + return CryptoX_Error; + } + + /* Get the cert and embedded public key out of the database */ + *cert = PK11_FindCertFromNickname(certNickname, &pwdata); + if (!*cert) { + return CryptoX_Error; + } + *publicKey = CERT_ExtractPublicKey(*cert); + if (!*publicKey) { + CERT_DestroyCertificate(*cert); + return CryptoX_Error; + } + return CryptoX_Success; +} + +CryptoX_Result +NSS_VerifyBegin(VFYContext **ctx, + SECKEYPublicKey * const *publicKey) +{ + SECStatus status; + if (!ctx || !publicKey || !*publicKey) { + return CryptoX_Error; + } + + /* Check that the key length is large enough for our requirements */ + if ((SECKEY_PublicKeyStrength(*publicKey) * 8) < + XP_MIN_SIGNATURE_LEN_IN_BYTES) { + fprintf(stderr, "ERROR: Key length must be >= %d bytes\n", + XP_MIN_SIGNATURE_LEN_IN_BYTES); + return CryptoX_Error; + } + + *ctx = VFY_CreateContext(*publicKey, NULL, + SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, NULL); + if (*ctx == NULL) { + return CryptoX_Error; + } + + status = VFY_Begin(*ctx); + return SECSuccess == status ? CryptoX_Success : CryptoX_Error; +} + +/** + * Verifies if a verify context matches the passed in signature. + * + * @param ctx The verify context that the signature should match. + * @param signature The signature to match. + * @param signatureLen The length of the signature. + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +NSS_VerifySignature(VFYContext * const *ctx, + const unsigned char *signature, + unsigned int signatureLen) +{ + SECItem signedItem; + SECStatus status; + if (!ctx || !signature || !*ctx) { + return CryptoX_Error; + } + + signedItem.len = signatureLen; + signedItem.data = (unsigned char*)signature; + status = VFY_EndWithSignature(*ctx, &signedItem); + return SECSuccess == status ? CryptoX_Success : CryptoX_Error; +} + +#elif defined(XP_WIN) +/** + * Verifies if a signature + public key matches a hash context. + * + * @param hash The hash context that the signature should match. + * @param pubKey The public key to use on the signature. + * @param signature The signature to check. + * @param signatureLen The length of the signature. + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +CyprtoAPI_VerifySignature(HCRYPTHASH *hash, + HCRYPTKEY *pubKey, + const BYTE *signature, + DWORD signatureLen) +{ + DWORD i; + BOOL result; +/* Windows APIs expect the bytes in the signature to be in little-endian + * order, but we write the signature in big-endian order. Other APIs like + * NSS and OpenSSL expect big-endian order. + */ + BYTE *signatureReversed; + if (!hash || !pubKey || !signature || signatureLen < 1) { + return CryptoX_Error; + } + + signatureReversed = malloc(signatureLen); + if (!signatureReversed) { + return CryptoX_Error; + } + + for (i = 0; i < signatureLen; i++) { + signatureReversed[i] = signature[signatureLen - 1 - i]; + } + result = CryptVerifySignature(*hash, signatureReversed, + signatureLen, *pubKey, NULL, 0); + free(signatureReversed); + return result ? CryptoX_Success : CryptoX_Error; +} + +/** + * Obtains the public key for the passed in cert data + * + * @param provider The cyrto provider + * @param certData Data of the certificate to extract the public key from + * @param sizeOfCertData The size of the certData buffer + * @param certStore Pointer to the handle of the certificate store to use + * @param CryptoX_Success on success +*/ +CryptoX_Result +CryptoAPI_LoadPublicKey(HCRYPTPROV provider, + BYTE *certData, + DWORD sizeOfCertData, + HCRYPTKEY *publicKey, + HCERTSTORE *certStore) +{ + CRYPT_DATA_BLOB blob; + CERT_CONTEXT *context; + if (!provider || !certData || !publicKey || !certStore) { + return CryptoX_Error; + } + + blob.cbData = sizeOfCertData; + blob.pbData = certData; + if (!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &blob, + CERT_QUERY_CONTENT_FLAG_CERT, + CERT_QUERY_FORMAT_FLAG_BINARY, + 0, NULL, NULL, NULL, + certStore, NULL, (const void **)&context)) { + return CryptoX_Error; + } + + if (!CryptImportPublicKeyInfo(provider, + PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + &context->pCertInfo->SubjectPublicKeyInfo, + publicKey)) { + CertFreeCertificateContext(context); + return CryptoX_Error; + } + + CertFreeCertificateContext(context); + return CryptoX_Success; +} + +/* Try to acquire context in this way: + * 1. Enhanced provider without creating a new key set + * 2. Enhanced provider with creating a new key set + * 3. Default provider without creating a new key set + * 4. Default provider without creating a new key set + * #2 and #4 should not be needed because of the CRYPT_VERIFYCONTEXT, + * but we add it just in case. + * + * @param provider Out parameter containing the provider handle. + * @return CryptoX_Success on success, CryptoX_Error on error. + */ +CryptoX_Result +CryptoAPI_InitCryptoContext(HCRYPTPROV *provider) +{ + if (!CryptAcquireContext(provider, + NULL, + MS_ENHANCED_PROV, + PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + if (!CryptAcquireContext(provider, + NULL, + MS_ENHANCED_PROV, + PROV_RSA_FULL, + CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT)) { + if (!CryptAcquireContext(provider, + NULL, + NULL, + PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + if (!CryptAcquireContext(provider, + NULL, + NULL, + PROV_RSA_FULL, + CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT)) { + *provider = CryptoX_InvalidHandleValue; + return CryptoX_Error; + } + } + } + } + return CryptoX_Success; +} + +/** + * Begins a signature verification hash context + * + * @param provider The crypt provider to use + * @param hash Out parameter for a handle to the hash context + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +CryptoAPI_VerifyBegin(HCRYPTPROV provider, HCRYPTHASH* hash) +{ + BOOL result; + if (!provider || !hash) { + return CryptoX_Error; + } + + *hash = (HCRYPTHASH)NULL; + result = CryptCreateHash(provider, CALG_SHA1, + 0, 0, hash); + return result ? CryptoX_Success : CryptoX_Error; +} + +/** + * Updates a signature verification hash context + * + * @param hash The hash context to udpate + * @param buf The buffer to update the hash context with + * @param len The size of the passed in buffer + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +CryptoAPI_VerifyUpdate(HCRYPTHASH* hash, BYTE *buf, DWORD len) +{ + BOOL result; + if (!hash || !buf) { + return CryptoX_Error; + } + + result = CryptHashData(*hash, buf, len, 0); + return result ? CryptoX_Success : CryptoX_Error; +} + +#endif + + + diff --git a/modules/libmar/verify/cryptox.h b/modules/libmar/verify/cryptox.h new file mode 100644 index 000000000000..76336471eaa6 --- /dev/null +++ b/modules/libmar/verify/cryptox.h @@ -0,0 +1,150 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is cryptographic wrappers for Mozilla archive code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef CRYPTOX_H +#define CRYPTOX_H + +#define XP_MIN_SIGNATURE_LEN_IN_BYTES 256 + +#define CryptoX_Result int +#define CryptoX_Success 0 +#define CryptoX_Error (-1) +#define CryptoX_Succeeded(X) ((X) == CryptoX_Success) +#define CryptoX_Failed(X) ((X) != CryptoX_Success) + +#if defined(MAR_NSS) + +#include "nss_secutil.h" + +CryptoX_Result NSS_LoadPublicKey(const char *certNickname, + SECKEYPublicKey **publicKey, + CERTCertificate **cert); +CryptoX_Result NSS_VerifyBegin(VFYContext **ctx, + SECKEYPublicKey * const *publicKey); +CryptoX_Result NSS_VerifySignature(VFYContext * const *ctx , + const unsigned char *signature, + unsigned int signatureLen); + +#define CryptoX_InvalidHandleValue NULL +#define CryptoX_ProviderHandle void* +#define CryptoX_SignatureHandle VFYContext * +#define CryptoX_PublicKey SECKEYPublicKey * +#define CryptoX_Certificate CERTCertificate * +#define CryptoX_InitCryptoProvider(CryptoHandle) \ + CryptoX_Success +#define CryptoX_VerifyBegin(CryptoHandle, SignatureHandle, PublicKey) \ + NSS_VerifyBegin(SignatureHandle, PublicKey) +#define CryptoX_VerifyUpdate(SignatureHandle, buf, len) \ + VFY_Update(*SignatureHandle, (const unsigned char*)(buf), len) +#define CryptoX_LoadPublicKey(CryptoHandle, certData, dataSize, \ + publicKey, certName, cert) \ + NSS_LoadPublicKey(certName, publicKey, cert) +#define CryptoX_VerifySignature(hash, publicKey, signedData, len) \ + NSS_VerifySignature(hash, (const unsigned char *)(signedData), len) +#define CryptoX_FreePublicKey(key) \ + SECKEY_DestroyPublicKey(*key) +#define CryptoX_FreeCertificate(cert) \ + CERT_DestroyCertificate(*cert) + +#elif defined(XP_WIN) + +#include +#include + +CryptoX_Result CryptoAPI_InitCryptoContext(HCRYPTPROV *provider); +CryptoX_Result CryptoAPI_LoadPublicKey(HCRYPTPROV hProv, + BYTE *certData, + DWORD sizeOfCertData, + HCRYPTKEY *publicKey, + HCERTSTORE *cert); +CryptoX_Result CryptoAPI_VerifyBegin(HCRYPTPROV provider, HCRYPTHASH* hash); +CryptoX_Result CryptoAPI_VerifyUpdate(HCRYPTHASH* hash, + BYTE *buf, DWORD len); +CryptoX_Result CyprtoAPI_VerifySignature(HCRYPTHASH *hash, + HCRYPTKEY *pubKey, + const BYTE *signature, + DWORD signatureLen); + +#define CryptoX_InvalidHandleValue ((ULONG_PTR)NULL) +#define CryptoX_ProviderHandle HCRYPTPROV +#define CryptoX_SignatureHandle HCRYPTHASH +#define CryptoX_PublicKey HCRYPTKEY +#define CryptoX_Certificate HCERTSTORE +#define CryptoX_InitCryptoProvider(CryptoHandle) \ + CryptoAPI_InitCryptoContext(CryptoHandle) +#define CryptoX_VerifyBegin(CryptoHandle, SignatureHandle, PublicKey) \ + CryptoAPI_VerifyBegin(CryptoHandle, SignatureHandle) +#define CryptoX_VerifyUpdate(SignatureHandle, buf, len) \ + CryptoAPI_VerifyUpdate(SignatureHandle, (BYTE *)(buf), len) +#define CryptoX_LoadPublicKey(CryptoHandle, certData, dataSize, \ + publicKey, certName, cert) \ + CryptoAPI_LoadPublicKey(CryptoHandle, (BYTE*)(certData), \ + dataSize, publicKey, cert) +#define CryptoX_VerifySignature(hash, publicKey, signedData, len) \ + CyprtoAPI_VerifySignature(hash, publicKey, signedData, len) +#define CryptoX_FreePublicKey(key) \ + CryptDestroyKey(*(key)) +#define CryptoX_FreeCertificate(cert) \ + CertCloseStore(*(cert), CERT_CLOSE_STORE_FORCE_FLAG); + +#else + +/* This default implementation is necessary because we don't want to + * link to NSS from updater code on non Windows platforms. On Windows + * we use CyrptoAPI instead of NSS. We don't call any function as they + * would just fail, but this simplifies linking. + */ + +#define CryptoX_InvalidHandleValue NULL +#define CryptoX_ProviderHandle void* +#define CryptoX_SignatureHandle void* +#define CryptoX_PublicKey void* +#define CryptoX_Certificate void* +#define CryptoX_InitCryptoProvider(CryptoHandle) \ + CryptoX_Error +#define CryptoX_VerifyBegin(CryptoHandle, SignatureHandle, PublicKey) \ + CryptoX_Error +#define CryptoX_VerifyUpdate(SignatureHandle, buf, len) CryptoX_Error +#define CryptoX_LoadPublicKey(CryptoHandle, certData, dataSize, \ + publicKey, certName, cert) \ + CryptoX_Error +#define CryptoX_VerifySignature(hash, publicKey, signedData, len) CryptoX_Error +#define CryptoX_FreePublicKey(key) CryptoX_Error + +#endif + +#endif diff --git a/modules/libmar/verify/mar_verify.c b/modules/libmar/verify/mar_verify.c new file mode 100644 index 000000000000..ac1895475acf --- /dev/null +++ b/modules/libmar/verify/mar_verify.c @@ -0,0 +1,445 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Archive verify code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifdef XP_WIN +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#endif + +#include +#include +#include +#include +#include +#include "mar_private.h" +#include "mar.h" +#include "cryptox.h" + +int mar_verify_signature_fp(FILE *fp, + CryptoX_ProviderHandle provider, + CryptoX_PublicKey key); +int mar_verify_signature_for_fp(FILE *fp, + CryptoX_ProviderHandle provider, + CryptoX_PublicKey key, + PRUint32 signatureCount, + char *extractedSignature); + +/** + * Reads the specified number of bytes from the file pointer and + * stores them in the passed buffer. + * + * @param fp The file pointer to read from. + * @param buffer The buffer to store the read results. + * @param size The number of bytes to read, buffer must be + * at least of this size. + * @param ctx The verify context. + * @param err The name of what is being written to in case of error. + * @return 0 on success + * -1 on read error + * -2 on verify update error +*/ +int +ReadAndUpdateVerifyContext(FILE *fp, + void *buffer, + PRUint32 size, + CryptoX_SignatureHandle *ctx, + const char *err) +{ + if (!fp || !buffer || !ctx || !err) { + fprintf(stderr, "ERROR: Invalid parameter specified.\n"); + return CryptoX_Error; + } + + if (!size) { + return CryptoX_Success; + } + + if (fread(buffer, size, 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read %s\n", err); + return CryptoX_Error; + } + + if (CryptoX_Failed(CryptoX_VerifyUpdate(ctx, buffer, size))) { + fprintf(stderr, "ERROR: Could not update verify context for %s\n", err); + return -2; + } + return CryptoX_Success; +} + +/** + * Verifies the embedded signature of the specified file path. + * This is only used by the signmar program when used with arguments to verify + * a MAR. This should not be used to verify a MAR that will be extracted in the + * same operation by updater code. This function prints the error message if + * verification fails. + * + * @param pathToMAR The path of the MAR file who's signature should be checked + * @param certData The certificate file data. + * @param sizeOfCertData The size of the cert data. + * @param certName Used only if compiled as NSS, specifies the certName + * @return 0 on success + * a negative number if there was an error + * a positive number if the signature does not verify + */ +int +mar_verify_signature(const char *pathToMARFile, + const char *certData, + PRUint32 sizeOfCertData, + const char *certName) { + int rv; + CryptoX_ProviderHandle provider = CryptoX_InvalidHandleValue; + CryptoX_Certificate cert; + CryptoX_PublicKey key; + FILE *fp; + + if (!pathToMARFile || (!certData && !certName)) { + fprintf(stderr, "ERROR: Invalid parameter specified.\n"); + return CryptoX_Error; + } + + fp = fopen(pathToMARFile, "rb"); + if (!fp) { + fclose(fp); + fprintf(stderr, "ERROR: Could not open MAR file.\n"); + return CryptoX_Error; + } + + if (CryptoX_Failed(CryptoX_InitCryptoProvider(&provider))) { + fclose(fp); + fprintf(stderr, "ERROR: Could not init crytpo library.\n"); + return CryptoX_Error; + } + + if (CryptoX_Failed(CryptoX_LoadPublicKey(provider, certData, sizeOfCertData, + &key, certName, &cert))) { + fclose(fp); + fprintf(stderr, "ERROR: Could not load public key.\n"); + return CryptoX_Error; + } + + rv = mar_verify_signature_fp(fp, provider, key); + fclose(fp); + if (key) { + CryptoX_FreePublicKey(&key); + } + + if (cert) { + CryptoX_FreeCertificate(&cert); + } + return rv; +} + +#ifdef XP_WIN +/** + * Verifies a MAR file's signature by making sure at least one + * signature verifies. + * + * @param pathToMARFile The path of the MAR file who's signature + * should be calculated + * @param certData The certificate data + * @param sizeOfCertData The size of the data stored in certData + * @return 0 on success +*/ +int +mar_verify_signatureW(MarFile *mar, + const char *certData, + PRUint32 sizeOfCertData) { + int rv; + CryptoX_ProviderHandle provider = CryptoX_InvalidHandleValue; + CryptoX_Certificate cert; + CryptoX_PublicKey key; + + if (!mar || !certData) { + fprintf(stderr, "ERROR: Invalid parameter specified.\n"); + return CryptoX_Error; + } + + if (!mar->fp) { + fprintf(stderr, "ERROR: MAR file is not open.\n"); + return CryptoX_Error; + } + + if (CryptoX_Failed(CryptoX_InitCryptoProvider(&provider))) { + fprintf(stderr, "ERROR: Could not init crytpo library.\n"); + return CryptoX_Error; + } + + if (CryptoX_Failed(CryptoX_LoadPublicKey(provider, certData, sizeOfCertData, + &key, "", &cert))) { + fprintf(stderr, "ERROR: Could not load public key.\n"); + return CryptoX_Error; + } + + rv = mar_verify_signature_fp(mar->fp, provider, key); + if (key) { + CryptoX_FreePublicKey(&key); + } + + if (cert) { + CryptoX_FreeCertificate(&cert); + } + return rv; +} +#endif + +/** + * Verifies a MAR file's signature by making sure at least one + * signature verifies. + * + * @param fp An opened MAR file handle + * @param provider A library provider + * @param key The public key to use to verify the MAR + * @return 0 on success +*/ +int +mar_verify_signature_fp(FILE *fp, + CryptoX_ProviderHandle provider, + CryptoX_PublicKey key) { + char buf[5] = {0}; + PRUint32 signatureAlgorithmID, signatureCount, signatureLen, numVerified = 0; + int rv = -1; + PRInt64 curPos; + char *extractedSignature; + PRUint32 i; + + if (!fp) { + fprintf(stderr, "ERROR: Invalid file pointer passed.\n"); + return CryptoX_Error; + } + + /* To protect against invalid MAR files, we assumes that the MAR file + size is less than or equal to MAX_SIZE_OF_MAR_FILE. */ + if (fseeko(fp, 0, SEEK_END)) { + fprintf(stderr, "ERROR: Could not seek to the end of the MAR file.\n"); + return CryptoX_Error; + } + if (ftello(fp) > MAX_SIZE_OF_MAR_FILE) { + fprintf(stderr, "ERROR: MAR file is too large to be verified.\n"); + return CryptoX_Error; + } + + /* Skip to the start of the signature block */ + if (fseeko(fp, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to the signature block.\n"); + return CryptoX_Error; + } + + /* Get the number of signatures */ + if (fread(&signatureCount, sizeof(signatureCount), 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read number of signatures.\n"); + return CryptoX_Error; + } + signatureCount = ntohl(signatureCount); + + /* Check that we have less than the max amount of signatures so we don't + waste too much of either updater's or signmar's time. */ + if (signatureCount > MAX_SIGNATURES) { + fprintf(stderr, "ERROR: At most %d signatures can be specified.\n", + MAX_SIGNATURES); + return CryptoX_Error; + } + + for (i = 0; i < signatureCount && numVerified == 0; i++) { + /* Get the signature algorithm ID */ + if (fread(&signatureAlgorithmID, sizeof(PRUint32), 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read signatures algorithm ID.\n"); + return CryptoX_Error; + } + signatureAlgorithmID = ntohl(signatureAlgorithmID); + + if (fread(&signatureLen, sizeof(PRUint32), 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read signatures length.\n"); + return CryptoX_Error; + } + signatureLen = ntohl(signatureLen); + + /* To protected against invalid input make sure the signature length + isn't too big. */ + if (signatureLen > MAX_SIGNATURE_LENGTH) { + fprintf(stderr, "ERROR: Signature length is too large to verify.\n"); + return CryptoX_Error; + } + + extractedSignature = malloc(signatureLen); + if (!extractedSignature) { + fprintf(stderr, "ERROR: Could allocate buffer for signature.\n"); + return CryptoX_Error; + } + if (fread(extractedSignature, signatureLen, 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read extracted signature.\n"); + free(extractedSignature); + return CryptoX_Error; + } + + /* We don't try to verify signatures we don't know about */ + if (1 == signatureAlgorithmID) { + curPos = ftello(fp); + rv = mar_verify_signature_for_fp(fp, + provider, + key, + signatureCount, + extractedSignature); + if (CryptoX_Succeeded(rv)) { + numVerified++; + } + free(extractedSignature); + if (fseeko(fp, curPos, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek back to last signature.\n"); + return CryptoX_Error; + } + } else { + free(extractedSignature); + } + } + + /* If we reached here and we verified at least one + signature, return success. */ + if (numVerified > 0) { + return CryptoX_Success; + } else { + fprintf(stderr, "ERROR: No signatures were verified.\n"); + return CryptoX_Error; + } +} + +/** + * Verifies if a specific signature ID matches the extracted signature. + * + * @param fp An opened MAR file handle + * @param provider A library provider + * @param key The public key to use to verify the MAR + * @param signatureCount The number of signatures in the MAR file + * @param extractedSignature The signature that should be verified + * @return 0 on success +*/ +int +mar_verify_signature_for_fp(FILE *fp, + CryptoX_ProviderHandle provider, + CryptoX_PublicKey key, + PRUint32 signatureCount, + char *extractedSignature) { + CryptoX_SignatureHandle signatureHandle; + char buf[BLOCKSIZE]; + PRUint32 signatureLen; + PRUint32 i; + + if (!extractedSignature) { + fprintf(stderr, "ERROR: Invalid parameter specified.\n"); + return CryptoX_Error; + } + + /* This function is only called when we have at least one signature, + but to protected against future people who call this function we + make sure a non zero value is passed in. + */ + if (!signatureCount) { + fprintf(stderr, "ERROR: There must be at least one signature.\n"); + return CryptoX_Error; + } + + CryptoX_VerifyBegin(provider, &signatureHandle, &key); + + /* Skip to the start of the file */ + if (fseeko(fp, 0, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to start of the file\n"); + return CryptoX_Error; + } + + /* Bytes 0-3: MAR1 + Bytes 4-7: index offset + Bytes 8-15: size of entire MAR + */ + if (CryptoX_Failed(ReadAndUpdateVerifyContext(fp, buf, + SIGNATURE_BLOCK_OFFSET + + sizeof(PRUint32), + &signatureHandle, + "signature block"))) { + return CryptoX_Error; + } + + for (i = 0; i < signatureCount; i++) { + /* Get the signature algorithm ID */ + if (CryptoX_Failed(ReadAndUpdateVerifyContext(fp, + &buf, + sizeof(PRUint32), + &signatureHandle, + "signature algorithm ID"))) { + return CryptoX_Error; + } + + if (CryptoX_Failed(ReadAndUpdateVerifyContext(fp, + &signatureLen, + sizeof(PRUint32), + &signatureHandle, + "signature length"))) { + return CryptoX_Error; + } + signatureLen = ntohl(signatureLen); + + /* Skip past the signature itself as those are not included */ + if (fseeko(fp, signatureLen, SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not seek past signature.\n"); + return CryptoX_Error; + } + } + + while (!feof(fp)) { + int numRead = fread(buf, 1, BLOCKSIZE , fp); + if (ferror(fp)) { + fprintf(stderr, "ERROR: Error reading data block.\n"); + return CryptoX_Error; + } + + if (CryptoX_Failed(CryptoX_VerifyUpdate(&signatureHandle, + buf, numRead))) { + fprintf(stderr, "ERROR: Error updating verify context with" + " data block.\n"); + return CryptoX_Error; + } + } + + if (CryptoX_Failed(CryptoX_VerifySignature(&signatureHandle, + &key, + extractedSignature, + signatureLen))) { + fprintf(stderr, "ERROR: Error verifying signature.\n"); + return CryptoX_Error; + } + + return CryptoX_Success; +} diff --git a/toolkit/mozapps/update/updater/Makefile.in b/toolkit/mozapps/update/updater/Makefile.in index 1cfdcd14837e..74c855c5071b 100644 --- a/toolkit/mozapps/update/updater/Makefile.in +++ b/toolkit/mozapps/update/updater/Makefile.in @@ -66,6 +66,7 @@ LIBS += \ $(NULL) ifeq ($(OS_ARCH),WINNT) +LIBS += $(DEPTH)/modules/libmar/verify/$(LIB_PREFIX)verifymar.$(LIB_SUFFIX) USE_STATIC_LIBS = 1 HAVE_PROGRESSUI = 1 RCINCLUDE = updater.rc @@ -139,4 +140,9 @@ ifeq (,$(filter-out WINNT,$(OS_ARCH))) LOCAL_INCLUDES += -I$(topsrcdir)/toolkit/xre endif +ifeq ($(OS_ARCH),WINNT) +EXTRA_LIBS += $(call EXPAND_LIBNAME,crypt32) +EXTRA_LIBS += $(call EXPAND_LIBNAME,advapi32) +endif + CXXFLAGS += $(BZ2_CFLAGS) diff --git a/toolkit/toolkit-tiers.mk b/toolkit/toolkit-tiers.mk index 89163d1d3152..b29502d99eb9 100644 --- a/toolkit/toolkit-tiers.mk +++ b/toolkit/toolkit-tiers.mk @@ -80,7 +80,6 @@ ifdef MOZ_UPDATER ifndef MOZ_NATIVE_BZ2 tier_platform_dirs += modules/libbz2 endif -tier_platform_dirs += modules/libmar tier_platform_dirs += other-licenses/bsdiff endif @@ -212,14 +211,18 @@ ifdef MOZ_SPELLCHECK tier_platform_dirs += extensions/spellcheck endif -tier_platform_dirs += toolkit - ifdef MOZ_PSM tier_platform_dirs += security/manager else tier_platform_dirs += security/manager/boot/public security/manager/ssl/public endif +ifdef MOZ_UPDATER +tier_platform_dirs += modules/libmar +endif + +tier_platform_dirs += toolkit + ifdef MOZ_PREF_EXTENSIONS tier_platform_dirs += extensions/pref endif