summaryrefslogtreecommitdiffstats
path: root/modules/libmar
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /modules/libmar
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/libmar')
-rw-r--r--modules/libmar/README6
-rw-r--r--modules/libmar/moz.build17
-rw-r--r--modules/libmar/sign/mar_sign.c1130
-rw-r--r--modules/libmar/sign/moz.build30
-rw-r--r--modules/libmar/sign/nss_secutil.c231
-rw-r--r--modules/libmar/sign/nss_secutil.h40
-rw-r--r--modules/libmar/src/mar.h260
-rw-r--r--modules/libmar/src/mar_cmdline.h102
-rw-r--r--modules/libmar/src/mar_create.c391
-rw-r--r--modules/libmar/src/mar_extract.c87
-rw-r--r--modules/libmar/src/mar_private.h78
-rw-r--r--modules/libmar/src/mar_read.c787
-rw-r--r--modules/libmar/src/moz.build39
-rw-r--r--modules/libmar/tests/moz.build12
-rw-r--r--modules/libmar/tests/unit/data/0_sized.marbin0 -> 157 bytes
-rw-r--r--modules/libmar/tests/unit/data/0_sized_file0
-rw-r--r--modules/libmar/tests/unit/data/1_byte.marbin0 -> 157 bytes
-rw-r--r--modules/libmar/tests/unit/data/1_byte_file1
-rw-r--r--modules/libmar/tests/unit/data/binary_data.marbin0 -> 673 bytes
-rw-r--r--modules/libmar/tests/unit/data/binary_data_filebin0 -> 512 bytes
-rw-r--r--modules/libmar/tests/unit/data/cert9.dbbin0 -> 36864 bytes
-rw-r--r--modules/libmar/tests/unit/data/key4.dbbin0 -> 61440 bytes
-rw-r--r--modules/libmar/tests/unit/data/manipulated_backend_collision.marbin0 -> 210 bytes
-rw-r--r--modules/libmar/tests/unit/data/manipulated_frontend_collision.marbin0 -> 210 bytes
-rw-r--r--modules/libmar/tests/unit/data/manipulated_is_contained.marbin0 -> 210 bytes
-rw-r--r--modules/libmar/tests/unit/data/manipulated_is_container.marbin0 -> 210 bytes
-rw-r--r--modules/libmar/tests/unit/data/manipulated_multiple_collision.marbin0 -> 249 bytes
-rw-r--r--modules/libmar/tests/unit/data/manipulated_multiple_collision_first.marbin0 -> 249 bytes
-rw-r--r--modules/libmar/tests/unit/data/manipulated_multiple_collision_last.marbin0 -> 249 bytes
-rw-r--r--modules/libmar/tests/unit/data/manipulated_same_offset.marbin0 -> 210 bytes
-rw-r--r--modules/libmar/tests/unit/data/manipulated_signed.marbin0 -> 1194 bytes
-rw-r--r--modules/libmar/tests/unit/data/multiple_file.marbin0 -> 723 bytes
-rw-r--r--modules/libmar/tests/unit/data/multiple_signed_no_pib.marbin0 -> 2125 bytes
-rw-r--r--modules/libmar/tests/unit/data/multiple_signed_pib.marbin0 -> 2233 bytes
-rw-r--r--modules/libmar/tests/unit/data/multiple_signed_pib_2.marbin0 -> 2233 bytes
-rw-r--r--modules/libmar/tests/unit/data/multiple_signed_pib_mar.sig.011
-rw-r--r--modules/libmar/tests/unit/data/multiple_signed_pib_mar.sig.111
-rw-r--r--modules/libmar/tests/unit/data/multiple_signed_pib_mar.sig.211
-rw-r--r--modules/libmar/tests/unit/data/mycert.derbin0 -> 1189 bytes
-rw-r--r--modules/libmar/tests/unit/data/mycert2.derbin0 -> 1191 bytes
-rw-r--r--modules/libmar/tests/unit/data/mycert3.derbin0 -> 1191 bytes
-rw-r--r--modules/libmar/tests/unit/data/no_pib.marbin0 -> 553 bytes
-rw-r--r--modules/libmar/tests/unit/data/signed_no_pib.marbin0 -> 1085 bytes
-rw-r--r--modules/libmar/tests/unit/data/signed_pib.marbin0 -> 1193 bytes
-rw-r--r--modules/libmar/tests/unit/data/signed_pib_mar.signature.011
-rw-r--r--modules/libmar/tests/unit/data/signed_pib_mar.signature.mycert211
-rw-r--r--modules/libmar/tests/unit/data/signed_pib_with_mycert2.marbin0 -> 1193 bytes
-rw-r--r--modules/libmar/tests/unit/head_libmar.js162
-rw-r--r--modules/libmar/tests/unit/test_create.js112
-rw-r--r--modules/libmar/tests/unit/test_extract.js147
-rw-r--r--modules/libmar/tests/unit/test_sign_verify.js591
-rw-r--r--modules/libmar/tests/unit/xpcshell.toml10
-rw-r--r--modules/libmar/tool/mar.c446
-rw-r--r--modules/libmar/tool/moz.build72
-rw-r--r--modules/libmar/verify/MacVerifyCrypto.cpp218
-rw-r--r--modules/libmar/verify/cryptox.c239
-rw-r--r--modules/libmar/verify/cryptox.h165
-rw-r--r--modules/libmar/verify/mar_verify.c416
-rw-r--r--modules/libmar/verify/moz.build52
59 files changed, 5896 insertions, 0 deletions
diff --git a/modules/libmar/README b/modules/libmar/README
new file mode 100644
index 0000000000..422a289590
--- /dev/null
+++ b/modules/libmar/README
@@ -0,0 +1,6 @@
+This directory contains code for a simple archive file format, which
+is documented at http://wiki.mozilla.org/Software_Update:MAR
+
+The src directory builds a small static library used to create, read, and
+extract an archive file. The tool directory builds a command line utility
+around the library.
diff --git a/modules/libmar/moz.build b/modules/libmar/moz.build
new file mode 100644
index 0000000000..d74a03de16
--- /dev/null
+++ b/modules/libmar/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Application Update")
+
+DIRS += [
+ "src",
+ "sign",
+ "tool",
+ "verify",
+]
+
+TEST_DIRS += ["tests"]
diff --git a/modules/libmar/sign/mar_sign.c b/modules/libmar/sign/mar_sign.c
new file mode 100644
index 0000000000..87f67ca80c
--- /dev/null
+++ b/modules/libmar/sign/mar_sign.c
@@ -0,0 +1,1130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef XP_WIN
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include "mar_private.h"
+#include "mar_cmdline.h"
+#include "mar.h"
+#include "cryptox.h"
+#ifndef XP_WIN
+# include <unistd.h>
+#endif
+
+#include "nss_secutil.h"
+#include "base64.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,
+ uint32_t* 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_PKCS1_SHA384_WITH_RSA_ENCRYPTION, *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 contexts.
+ *
+ * @param fpDest The file pointer to write to.
+ * @param buffer The buffer to write.
+ * @param size The size of the buffer to write.
+ * @param ctxs Pointer to the first element in an array of signature
+ * contexts to update.
+ * @param ctxCount The number of signature contexts pointed to by ctxs
+ * @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 WriteAndUpdateSignatures(FILE* fpDest, void* buffer, uint32_t size,
+ SGNContext** ctxs, uint32_t ctxCount,
+ const char* err) {
+ uint32_t k;
+ if (!size) {
+ return 0;
+ }
+
+ if (fwrite(buffer, size, 1, fpDest) != 1) {
+ fprintf(stderr, "ERROR: Could not write %s\n", err);
+ return -2;
+ }
+
+ for (k = 0; k < ctxCount; ++k) {
+ if (SGN_Update(ctxs[k], buffer, size) != SECSuccess) {
+ fprintf(stderr, "ERROR: Could not update signature context for %s\n",
+ err);
+ return -3;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Adjusts each entry's content offset in the the passed in index by the
+ * specified amount.
+ *
+ * @param indexBuf A buffer containing the MAR index
+ * @param indexLength The length of the MAR index
+ * @param offsetAmount The amount to adjust each index entry by
+ */
+void AdjustIndexContentOffsets(char* indexBuf, uint32_t indexLength,
+ uint32_t offsetAmount) {
+ uint32_t* offsetToContent;
+ char* indexBufLoc = indexBuf;
+
+ /* Consume the index and adjust each index by the specified amount */
+ while (indexBufLoc != (indexBuf + indexLength)) {
+ /* Adjust the offset */
+ offsetToContent = (uint32_t*)indexBufLoc;
+ *offsetToContent = ntohl(*offsetToContent);
+ *offsetToContent += offsetAmount;
+ *offsetToContent = htonl(*offsetToContent);
+ /* Skip past the offset, length, and flags */
+ indexBufLoc += 3 * sizeof(uint32_t);
+ indexBufLoc += strlen(indexBufLoc) + 1;
+ }
+}
+
+/**
+ * Reads from fpSrc, writes it to fpDest, and updates the signature contexts.
+ *
+ * @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 ctxs Pointer to the first element in an array of signature
+ * contexts to update.
+ * @param ctxCount The number of signature contexts pointed to by ctxs
+ * @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 ReadWriteAndUpdateSignatures(FILE* fpSrc, FILE* fpDest, void* buffer,
+ uint32_t size, SGNContext** ctxs,
+ uint32_t ctxCount, 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 WriteAndUpdateSignatures(fpDest, buffer, size, ctxs, ctxCount, err);
+}
+
+/**
+ * Reads from fpSrc, writes it to fpDest.
+ *
+ * @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 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
+ */
+int ReadAndWrite(FILE* fpSrc, FILE* fpDest, void* buffer, uint32_t size,
+ 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;
+ }
+
+ if (fwrite(buffer, size, 1, fpDest) != 1) {
+ fprintf(stderr, "ERROR: Could not write %s\n", err);
+ return -2;
+ }
+
+ return 0;
+}
+
+/**
+ * Writes out a copy of the MAR at src but with the signature block stripped.
+ *
+ * @param src The path of the source MAR file
+ * @param dest The path of the MAR file to write out that
+ has no signature block
+ * @return 0 on success
+ * -1 on error
+*/
+int strip_signature_block(const char* src, const char* dest) {
+ uint32_t offsetToIndex, dstOffsetToIndex, indexLength, numSignatures = 0,
+ leftOver;
+ int32_t stripAmount = 0;
+ int64_t oldPos, numChunks, i, realSizeOfSrcMAR, numBytesToCopy,
+ sizeOfEntireMAR = 0;
+ FILE *fpSrc = NULL, *fpDest = NULL;
+ int rv = -1, hasSignatureBlock;
+ char buf[BLOCKSIZE];
+ char* indexBuf = NULL;
+
+ if (!src || !dest) {
+ fprintf(stderr, "ERROR: Invalid parameter passed in.\n");
+ return -1;
+ }
+
+ fpSrc = fopen(src, "rb");
+ if (!fpSrc) {
+ fprintf(stderr, "ERROR: could not open source file: %s\n", src);
+ 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 (get_mar_file_info(src, &hasSignatureBlock, NULL, NULL, NULL, NULL)) {
+ fprintf(stderr, "ERROR: could not determine if MAR is old or new.\n");
+ goto failure;
+ }
+
+ /* MAR ID */
+ if (ReadAndWrite(fpSrc, fpDest, buf, MAR_ID_SIZE, "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 (hasSignatureBlock) {
+ /* 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 strip */
+ if (fread(&numSignatures, sizeof(numSignatures), 1, fpSrc) != 1) {
+ fprintf(stderr, "ERROR: Could read num signatures\n");
+ goto failure;
+ }
+ numSignatures = ntohl(numSignatures);
+
+ for (i = 0; i < numSignatures; i++) {
+ uint32_t signatureLen;
+
+ /* Skip past the signature algorithm ID */
+ if (fseeko(fpSrc, sizeof(uint32_t), SEEK_CUR)) {
+ fprintf(stderr, "ERROR: Could not skip past signature algorithm ID\n");
+ }
+
+ /* Read in the length of the signature so we know how far to skip */
+ if (fread(&signatureLen, sizeof(uint32_t), 1, fpSrc) != 1) {
+ fprintf(stderr, "ERROR: Could not read signatures length.\n");
+ return CryptoX_Error;
+ }
+ signatureLen = ntohl(signatureLen);
+
+ /* Skip past the signature */
+ if (fseeko(fpSrc, signatureLen, SEEK_CUR)) {
+ fprintf(stderr, "ERROR: Could not skip past signature algorithm ID\n");
+ }
+
+ stripAmount += sizeof(uint32_t) + sizeof(uint32_t) + signatureLen;
+ }
+
+ } else {
+ sizeOfEntireMAR = realSizeOfSrcMAR;
+ numSignatures = 0;
+ }
+
+ if (((int64_t)offsetToIndex) > sizeOfEntireMAR) {
+ fprintf(stderr, "ERROR: Offset to index is larger than the file size.\n");
+ goto failure;
+ }
+
+ dstOffsetToIndex = offsetToIndex;
+ if (!hasSignatureBlock) {
+ dstOffsetToIndex += sizeof(sizeOfEntireMAR) + sizeof(numSignatures);
+ }
+ dstOffsetToIndex -= stripAmount;
+
+ /* Write out the index offset */
+ dstOffsetToIndex = htonl(dstOffsetToIndex);
+ if (fwrite(&dstOffsetToIndex, sizeof(dstOffsetToIndex), 1, fpDest) != 1) {
+ fprintf(stderr, "ERROR: Could not write offset to index\n");
+ goto failure;
+ }
+ dstOffsetToIndex = ntohl(dstOffsetToIndex);
+
+ /* Write out the new MAR file size */
+ if (!hasSignatureBlock) {
+ sizeOfEntireMAR += sizeof(sizeOfEntireMAR) + sizeof(numSignatures);
+ }
+ sizeOfEntireMAR -= stripAmount;
+
+ /* Write out the MAR size */
+ sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR);
+ if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fpDest) != 1) {
+ fprintf(stderr, "ERROR: Could not write size of MAR\n");
+ goto failure;
+ }
+ sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR);
+
+ /* Write out the number of signatures, which is 0 */
+ numSignatures = 0;
+ if (fwrite(&numSignatures, sizeof(numSignatures), 1, fpDest) != 1) {
+ fprintf(stderr, "ERROR: Could not write out num signatures\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) > ((int64_t)offsetToIndex)) {
+ fprintf(stderr, "ERROR: Index offset is too small.\n");
+ goto failure;
+ }
+ numBytesToCopy = ((int64_t)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 (ReadAndWrite(fpSrc, fpDest, buf, BLOCKSIZE, "content block")) {
+ goto failure;
+ }
+ }
+
+ /* Write out the left over */
+ if (ReadAndWrite(fpSrc, fpDest, buf, leftOver, "left over content block")) {
+ goto failure;
+ }
+
+ /* Length of the index */
+ if (ReadAndWrite(fpSrc, fpDest, &indexLength, sizeof(indexLength),
+ "index length")) {
+ goto failure;
+ }
+ indexLength = ntohl(indexLength);
+
+ /* Consume the index and adjust each index by the difference */
+ indexBuf = malloc(indexLength);
+ if (fread(indexBuf, indexLength, 1, fpSrc) != 1) {
+ fprintf(stderr, "ERROR: Could not read index\n");
+ goto failure;
+ }
+
+ /* Adjust each entry in the index */
+ if (hasSignatureBlock) {
+ AdjustIndexContentOffsets(indexBuf, indexLength, -stripAmount);
+ } else {
+ AdjustIndexContentOffsets(
+ indexBuf, indexLength,
+ sizeof(sizeOfEntireMAR) + sizeof(numSignatures) - stripAmount);
+ }
+
+ if (fwrite(indexBuf, indexLength, 1, fpDest) != 1) {
+ fprintf(stderr, "ERROR: Could not write index\n");
+ goto failure;
+ }
+
+ rv = 0;
+failure:
+ if (fpSrc) {
+ fclose(fpSrc);
+ }
+
+ if (fpDest) {
+ fclose(fpDest);
+ }
+
+ if (rv) {
+ remove(dest);
+ }
+
+ if (indexBuf) {
+ free(indexBuf);
+ }
+
+ if (rv) {
+ remove(dest);
+ }
+ return rv;
+}
+
+/**
+ * Extracts a signature from a MAR file, base64 encodes it, and writes it out
+ *
+ * @param src The path of the source MAR file
+ * @param sigIndex The index of the signature to extract
+ * @param dest The path of file to write the signature to
+ * @return 0 on success
+ * -1 on error
+ */
+int extract_signature(const char* src, uint32_t sigIndex, const char* dest) {
+ FILE *fpSrc = NULL, *fpDest = NULL;
+ uint32_t i;
+ uint32_t signatureCount;
+ uint32_t signatureLen;
+ uint8_t* extractedSignature = NULL;
+ char* base64Encoded = NULL;
+ int rv = -1;
+ if (!src || !dest) {
+ fprintf(stderr, "ERROR: Invalid parameter passed in.\n");
+ goto failure;
+ }
+
+ fpSrc = fopen(src, "rb");
+ if (!fpSrc) {
+ fprintf(stderr, "ERROR: could not open source file: %s\n", src);
+ goto failure;
+ }
+
+ fpDest = fopen(dest, "wb");
+ if (!fpDest) {
+ fprintf(stderr, "ERROR: could not create target file: %s\n", dest);
+ goto failure;
+ }
+
+ /* Skip to the start of the signature block */
+ if (fseeko(fpSrc, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) {
+ fprintf(stderr, "ERROR: could not seek to signature block\n");
+ goto failure;
+ }
+
+ /* Get the number of signatures */
+ if (fread(&signatureCount, sizeof(signatureCount), 1, fpSrc) != 1) {
+ fprintf(stderr, "ERROR: could not read signature count\n");
+ goto failure;
+ }
+ signatureCount = ntohl(signatureCount);
+ if (sigIndex >= signatureCount) {
+ fprintf(stderr, "ERROR: Signature index was out of range\n");
+ goto failure;
+ }
+
+ /* Skip to the correct signature */
+ for (i = 0; i <= sigIndex; i++) {
+ /* Avoid leaking while skipping signatures */
+ free(extractedSignature);
+ extractedSignature = NULL;
+
+ /* skip past the signature algorithm ID */
+ if (fseeko(fpSrc, sizeof(uint32_t), SEEK_CUR)) {
+ fprintf(stderr, "ERROR: Could not seek past sig algorithm ID.\n");
+ goto failure;
+ }
+
+ /* Get the signature length */
+ if (fread(&signatureLen, sizeof(signatureLen), 1, fpSrc) != 1) {
+ fprintf(stderr, "ERROR: could not read signature length\n");
+ goto failure;
+ }
+ signatureLen = ntohl(signatureLen);
+
+ /* Get the signature */
+ extractedSignature = malloc(signatureLen);
+ if (fread(extractedSignature, signatureLen, 1, fpSrc) != 1) {
+ fprintf(stderr, "ERROR: could not read signature\n");
+ goto failure;
+ }
+ }
+
+ base64Encoded = BTOA_DataToAscii(extractedSignature, signatureLen);
+ if (!base64Encoded) {
+ fprintf(stderr, "ERROR: could not obtain base64 encoded data\n");
+ goto failure;
+ }
+
+ if (fwrite(base64Encoded, strlen(base64Encoded), 1, fpDest) != 1) {
+ fprintf(stderr, "ERROR: Could not write base64 encoded string\n");
+ goto failure;
+ }
+
+ rv = 0;
+failure:
+ if (base64Encoded) {
+ PORT_Free(base64Encoded);
+ }
+
+ if (extractedSignature) {
+ free(extractedSignature);
+ }
+
+ if (fpSrc) {
+ fclose(fpSrc);
+ }
+
+ if (fpDest) {
+ fclose(fpDest);
+ }
+
+ if (rv) {
+ remove(dest);
+ }
+
+ return rv;
+}
+
+/**
+ * Imports a base64 encoded signature into a MAR file
+ *
+ * @param src The path of the source MAR file
+ * @param sigIndex The index of the signature to import
+ * @param base64SigFile A file which contains the signature to import
+ * @param dest The path of the destination MAR file with replaced
+ * signature
+ * @return 0 on success
+ * -1 on error
+ */
+int import_signature(const char* src, uint32_t sigIndex,
+ const char* base64SigFile, const char* dest) {
+ int rv = -1;
+ FILE* fpSrc = NULL;
+ FILE* fpDest = NULL;
+ FILE* fpSigFile = NULL;
+ uint32_t i;
+ uint32_t signatureCount, signatureLen, signatureAlgorithmID, numChunks,
+ leftOver;
+ char buf[BLOCKSIZE];
+ uint64_t sizeOfSrcMAR, sizeOfBase64EncodedFile;
+ char* passedInSignatureB64 = NULL;
+ uint8_t* passedInSignatureRaw = NULL;
+ uint8_t* extractedMARSignature = NULL;
+ unsigned int passedInSignatureLenRaw;
+
+ if (!src || !dest) {
+ fprintf(stderr, "ERROR: Invalid parameter passed in.\n");
+ goto failure;
+ }
+
+ fpSrc = fopen(src, "rb");
+ if (!fpSrc) {
+ fprintf(stderr, "ERROR: could not open source file: %s\n", src);
+ goto failure;
+ }
+
+ fpDest = fopen(dest, "wb");
+ if (!fpDest) {
+ fprintf(stderr, "ERROR: could not open dest file: %s\n", dest);
+ goto failure;
+ }
+
+ fpSigFile = fopen(base64SigFile, "rb");
+ if (!fpSigFile) {
+ fprintf(stderr, "ERROR: could not open sig file: %s\n", base64SigFile);
+ goto failure;
+ }
+
+ /* Get the src file size */
+ if (fseeko(fpSrc, 0, SEEK_END)) {
+ fprintf(stderr, "ERROR: Could not seek to end of src file.\n");
+ goto failure;
+ }
+ sizeOfSrcMAR = ftello(fpSrc);
+ if (fseeko(fpSrc, 0, SEEK_SET)) {
+ fprintf(stderr, "ERROR: Could not seek to start of src file.\n");
+ goto failure;
+ }
+
+ /* Get the sig file size */
+ if (fseeko(fpSigFile, 0, SEEK_END)) {
+ fprintf(stderr, "ERROR: Could not seek to end of sig file.\n");
+ goto failure;
+ }
+ sizeOfBase64EncodedFile = ftello(fpSigFile);
+ if (fseeko(fpSigFile, 0, SEEK_SET)) {
+ fprintf(stderr, "ERROR: Could not seek to start of sig file.\n");
+ goto failure;
+ }
+
+ /* Read in the base64 encoded signature to import */
+ passedInSignatureB64 = malloc(sizeOfBase64EncodedFile + 1);
+ passedInSignatureB64[sizeOfBase64EncodedFile] = '\0';
+ if (fread(passedInSignatureB64, sizeOfBase64EncodedFile, 1, fpSigFile) != 1) {
+ fprintf(stderr, "ERROR: Could read b64 sig file.\n");
+ goto failure;
+ }
+
+ /* Decode the base64 encoded data */
+ passedInSignatureRaw =
+ ATOB_AsciiToData(passedInSignatureB64, &passedInSignatureLenRaw);
+ if (!passedInSignatureRaw) {
+ fprintf(stderr, "ERROR: could not obtain base64 decoded data\n");
+ goto failure;
+ }
+
+ /* Read everything up until the signature block offset and write it out */
+ if (ReadAndWrite(fpSrc, fpDest, buf, SIGNATURE_BLOCK_OFFSET,
+ "signature block offset")) {
+ goto failure;
+ }
+
+ /* Get the number of signatures */
+ if (ReadAndWrite(fpSrc, fpDest, &signatureCount, sizeof(signatureCount),
+ "signature count")) {
+ goto failure;
+ }
+ signatureCount = ntohl(signatureCount);
+ if (signatureCount > MAX_SIGNATURES) {
+ fprintf(stderr, "ERROR: Signature count was out of range\n");
+ goto failure;
+ }
+
+ if (sigIndex >= signatureCount) {
+ fprintf(stderr, "ERROR: Signature index was out of range\n");
+ goto failure;
+ }
+
+ /* Read and write the whole signature block, but if we reach the
+ signature offset, then we should replace it with the specified
+ base64 decoded signature */
+ for (i = 0; i < signatureCount; i++) {
+ /* Read/Write the signature algorithm ID */
+ if (ReadAndWrite(fpSrc, fpDest, &signatureAlgorithmID,
+ sizeof(signatureAlgorithmID), "sig algorithm ID")) {
+ goto failure;
+ }
+
+ /* Read/Write the signature length */
+ if (ReadAndWrite(fpSrc, fpDest, &signatureLen, sizeof(signatureLen),
+ "sig length")) {
+ goto failure;
+ }
+ signatureLen = ntohl(signatureLen);
+
+ /* Get the signature */
+ if (extractedMARSignature) {
+ free(extractedMARSignature);
+ }
+ extractedMARSignature = malloc(signatureLen);
+
+ if (sigIndex == i) {
+ if (passedInSignatureLenRaw != signatureLen) {
+ fprintf(stderr, "ERROR: Signature length must be the same\n");
+ goto failure;
+ }
+
+ if (fread(extractedMARSignature, signatureLen, 1, fpSrc) != 1) {
+ fprintf(stderr, "ERROR: Could not read signature\n");
+ goto failure;
+ }
+
+ if (fwrite(passedInSignatureRaw, passedInSignatureLenRaw, 1, fpDest) !=
+ 1) {
+ fprintf(stderr, "ERROR: Could not write signature\n");
+ goto failure;
+ }
+ } else {
+ if (ReadAndWrite(fpSrc, fpDest, extractedMARSignature, signatureLen,
+ "signature")) {
+ goto failure;
+ }
+ }
+ }
+
+ /* We replaced the signature so let's just skip past the rest o the
+ file. */
+ numChunks = (sizeOfSrcMAR - ftello(fpSrc)) / BLOCKSIZE;
+ leftOver = (sizeOfSrcMAR - ftello(fpSrc)) % BLOCKSIZE;
+
+ /* Read each file and write it to the MAR file */
+ for (i = 0; i < numChunks; ++i) {
+ if (ReadAndWrite(fpSrc, fpDest, buf, BLOCKSIZE, "content block")) {
+ goto failure;
+ }
+ }
+
+ if (ReadAndWrite(fpSrc, fpDest, buf, leftOver, "left over content block")) {
+ goto failure;
+ }
+
+ rv = 0;
+
+failure:
+
+ if (fpSrc) {
+ fclose(fpSrc);
+ }
+
+ if (fpDest) {
+ fclose(fpDest);
+ }
+
+ if (fpSigFile) {
+ fclose(fpSigFile);
+ }
+
+ if (rv) {
+ remove(dest);
+ }
+
+ if (extractedMARSignature) {
+ free(extractedMARSignature);
+ }
+
+ if (passedInSignatureB64) {
+ free(passedInSignatureB64);
+ }
+
+ if (passedInSignatureRaw) {
+ PORT_Free(passedInSignatureRaw);
+ }
+
+ return rv;
+}
+
+/**
+ * Writes out a copy of the MAR at src but with embedded signatures.
+ * 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 certNames The nicknames of the certificate to use for signing
+ * @param certCount The number of certificate names contained in certNames.
+ * One signature will be produced for each certificate.
+ * @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* const* certNames, uint32_t certCount,
+ const char* src, const char* dest) {
+ uint32_t offsetToIndex, dstOffsetToIndex, indexLength, leftOver,
+ signatureAlgorithmID, numSignatures = 0, signatureSectionLength = 0;
+ uint32_t signatureLengths[MAX_SIGNATURES];
+ int64_t oldPos, numChunks, i, realSizeOfSrcMAR, signaturePlaceholderOffset,
+ numBytesToCopy, sizeOfEntireMAR = 0;
+ FILE *fpSrc = NULL, *fpDest = NULL;
+ int rv = -1, hasSignatureBlock;
+ SGNContext* ctxs[MAX_SIGNATURES];
+ SECItem secItems[MAX_SIGNATURES];
+ char buf[BLOCKSIZE];
+ SECKEYPrivateKey* privKeys[MAX_SIGNATURES];
+ CERTCertificate* certs[MAX_SIGNATURES];
+ char* indexBuf = NULL;
+ uint32_t k;
+
+ memset(signatureLengths, 0, sizeof(signatureLengths));
+ memset(ctxs, 0, sizeof(ctxs));
+ memset(secItems, 0, sizeof(secItems));
+ memset(privKeys, 0, sizeof(privKeys));
+ memset(certs, 0, sizeof(certs));
+
+ if (!NSSConfigDir || !certNames || certCount == 0 || !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);
+
+ fpSrc = fopen(src, "rb");
+ if (!fpSrc) {
+ fprintf(stderr, "ERROR: could not open source file: %s\n", src);
+ 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 (get_mar_file_info(src, &hasSignatureBlock, NULL, NULL, NULL, NULL)) {
+ fprintf(stderr, "ERROR: could not determine if MAR is old or new.\n");
+ goto failure;
+ }
+
+ for (k = 0; k < certCount; k++) {
+ if (NSSSignBegin(certNames[k], &ctxs[k], &privKeys[k], &certs[k],
+ &signatureLengths[k])) {
+ fprintf(stderr, "ERROR: NSSSignBegin failed\n");
+ goto failure;
+ }
+ }
+
+ /* MAR ID */
+ if (ReadWriteAndUpdateSignatures(fpSrc, fpDest, buf, MAR_ID_SIZE, ctxs,
+ certCount, "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 (hasSignatureBlock) {
+ /* 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 */
+ 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 you have multiple signatures,
+ you must add them all at the same time. */
+ if (numSignatures) {
+ fprintf(stderr, "ERROR: MAR is already signed\n");
+ goto failure;
+ }
+ } else {
+ sizeOfEntireMAR = realSizeOfSrcMAR;
+ }
+
+ if (((int64_t)offsetToIndex) > sizeOfEntireMAR) {
+ fprintf(stderr, "ERROR: Offset to index is larger than the file size.\n");
+ goto failure;
+ }
+
+ /* Calculate the total signature block length */
+ for (k = 0; k < certCount; k++) {
+ signatureSectionLength += sizeof(signatureAlgorithmID) +
+ sizeof(signatureLengths[k]) + signatureLengths[k];
+ }
+ dstOffsetToIndex = offsetToIndex;
+ if (!hasSignatureBlock) {
+ dstOffsetToIndex += sizeof(sizeOfEntireMAR) + sizeof(numSignatures);
+ }
+ dstOffsetToIndex += signatureSectionLength;
+
+ /* Write out the index offset */
+ dstOffsetToIndex = htonl(dstOffsetToIndex);
+ if (WriteAndUpdateSignatures(fpDest, &dstOffsetToIndex,
+ sizeof(dstOffsetToIndex), ctxs, certCount,
+ "index offset")) {
+ goto failure;
+ }
+ dstOffsetToIndex = ntohl(dstOffsetToIndex);
+
+ /* Write out the new MAR file size */
+ sizeOfEntireMAR += signatureSectionLength;
+ if (!hasSignatureBlock) {
+ sizeOfEntireMAR += sizeof(sizeOfEntireMAR) + sizeof(numSignatures);
+ }
+
+ /* Write out the MAR size */
+ sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR);
+ if (WriteAndUpdateSignatures(fpDest, &sizeOfEntireMAR,
+ sizeof(sizeOfEntireMAR), ctxs, certCount,
+ "size of MAR")) {
+ goto failure;
+ }
+ sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR);
+
+ /* Write out the number of signatures */
+ numSignatures = certCount;
+ numSignatures = htonl(numSignatures);
+ if (WriteAndUpdateSignatures(fpDest, &numSignatures, sizeof(numSignatures),
+ ctxs, certCount, "num signatures")) {
+ goto failure;
+ }
+ numSignatures = ntohl(numSignatures);
+
+ signaturePlaceholderOffset = ftello(fpDest);
+
+ for (k = 0; k < certCount; k++) {
+ /* Write out the signature algorithm ID, Only an ID of 2 is supported */
+ signatureAlgorithmID = htonl(2);
+ if (WriteAndUpdateSignatures(fpDest, &signatureAlgorithmID,
+ sizeof(signatureAlgorithmID), ctxs, certCount,
+ "num signatures")) {
+ goto failure;
+ }
+ signatureAlgorithmID = ntohl(signatureAlgorithmID);
+
+ /* Write out the signature length */
+ signatureLengths[k] = htonl(signatureLengths[k]);
+ if (WriteAndUpdateSignatures(fpDest, &signatureLengths[k],
+ sizeof(signatureLengths[k]), ctxs, certCount,
+ "signature length")) {
+ goto failure;
+ }
+ signatureLengths[k] = ntohl(signatureLengths[k]);
+
+ /* 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));
+ if (fwrite(buf, signatureLengths[k], 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) > ((int64_t)offsetToIndex)) {
+ fprintf(stderr, "ERROR: Index offset is too small.\n");
+ goto failure;
+ }
+ numBytesToCopy = ((int64_t)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 (ReadWriteAndUpdateSignatures(fpSrc, fpDest, buf, BLOCKSIZE, ctxs,
+ certCount, "content block")) {
+ goto failure;
+ }
+ }
+
+ /* Write out the left over */
+ if (ReadWriteAndUpdateSignatures(fpSrc, fpDest, buf, leftOver, ctxs,
+ certCount, "left over content block")) {
+ goto failure;
+ }
+
+ /* Length of the index */
+ if (ReadWriteAndUpdateSignatures(fpSrc, fpDest, &indexLength,
+ sizeof(indexLength), ctxs, certCount,
+ "index length")) {
+ goto failure;
+ }
+ indexLength = ntohl(indexLength);
+
+ /* Consume the index and adjust each index by signatureSectionLength */
+ indexBuf = malloc(indexLength);
+ if (fread(indexBuf, indexLength, 1, fpSrc) != 1) {
+ fprintf(stderr, "ERROR: Could not read index\n");
+ goto failure;
+ }
+
+ /* Adjust each entry in the index */
+ if (hasSignatureBlock) {
+ AdjustIndexContentOffsets(indexBuf, indexLength, signatureSectionLength);
+ } else {
+ AdjustIndexContentOffsets(indexBuf, indexLength,
+ sizeof(sizeOfEntireMAR) + sizeof(numSignatures) +
+ signatureSectionLength);
+ }
+
+ if (WriteAndUpdateSignatures(fpDest, indexBuf, indexLength, ctxs, certCount,
+ "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;
+ }
+
+ for (k = 0; k < certCount; k++) {
+ /* Get the signature */
+ if (SGN_End(ctxs[k], &secItems[k]) != SECSuccess) {
+ fprintf(stderr, "ERROR: Could not end signature context\n");
+ goto failure;
+ }
+ if (signatureLengths[k] != secItems[k].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;
+ }
+
+ for (k = 0; k < certCount; k++) {
+ /* Skip to the position of the next signature */
+ if (fseeko(fpDest,
+ sizeof(signatureAlgorithmID) + sizeof(signatureLengths[k]),
+ SEEK_CUR)) {
+ 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(secItems[k].data, secItems[k].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);
+ }
+
+ /* Cleanup */
+ for (k = 0; k < certCount; k++) {
+ if (ctxs[k]) {
+ SGN_DestroyContext(ctxs[k], PR_TRUE);
+ }
+
+ if (certs[k]) {
+ CERT_DestroyCertificate(certs[k]);
+ }
+
+ if (privKeys[k]) {
+ SECKEY_DestroyPrivateKey(privKeys[k]);
+ }
+
+ SECITEM_FreeItem(&secItems[k], PR_FALSE);
+ }
+
+ (void)NSS_Shutdown();
+
+ if (rv) {
+ remove(dest);
+ }
+
+ return rv;
+}
diff --git a/modules/libmar/sign/moz.build b/modules/libmar/sign/moz.build
new file mode 100644
index 0000000000..92f0d6cefd
--- /dev/null
+++ b/modules/libmar/sign/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library("signmar")
+
+UNIFIED_SOURCES += [
+ "mar_sign.c",
+ "nss_secutil.c",
+]
+
+FORCE_STATIC_LIB = True
+
+LOCAL_INCLUDES += [
+ "../src",
+ "../verify",
+]
+
+DEFINES["MAR_NSS"] = True
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ USE_STATIC_LIBS = True
+
+# C11 for static_assert
+c11_flags = ["-std=gnu11"]
+if CONFIG["CC_TYPE"] == "clang-cl":
+ c11_flags.insert(0, "-Xclang")
+CFLAGS += c11_flags
diff --git a/modules/libmar/sign/nss_secutil.c b/modules/libmar/sign/nss_secutil.c
new file mode 100644
index 0000000000..0425191510
--- /dev/null
+++ b/modules/libmar/sign/nss_secutil.c
@@ -0,0 +1,231 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* 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 <io.h>
+#else
+# include <unistd.h>
+#endif
+
+#if defined(_WINDOWS)
+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) {
+ static char consoleName[] = {
+# ifdef XP_UNIX
+ "/dev/tty"
+# else
+ "CON:"
+# endif
+ };
+
+ 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);
+ }
+
+ if (!QUIET_FGETS(phrase, sizeof(phrase), input)) {
+ fprintf(stderr, "QUIET_FGETS failed\n");
+#ifndef _WINDOWS
+ if (isInputTerminal) {
+ fclose(input);
+ }
+#endif
+ return NULL;
+ }
+
+ 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;
+ int32_t 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 */
+ if (i < nb) {
+ phrases[i++] = '\0';
+ }
+ /* clean up any EOL before the start of the next passphrase */
+ while ((i < nb) && (phrases[i] == '\r' || phrases[i] == '\n')) {
+ phrases[i++] = '\0';
+ }
+ /* now analyze the current passphrase */
+ phrase = &phrases[startphrase];
+ if (!tokenName) break;
+ if (PORT_Strncmp(phrase, tokenName, tokenLen)) continue;
+ phraseLen = PORT_Strlen(phrase);
+ if (phraseLen < (tokenLen + 1)) continue;
+ if (phrase[tokenLen] != ':') continue;
+ phrase = &phrase[tokenLen + 1];
+ break;
+
+ } while (i < nb);
+
+ phrase = PORT_Strdup((char*)phrase);
+ PORT_Free(phrases);
+ return phrase;
+}
+
+char* SECU_GetModulePassword(PK11SlotInfo* slot, PRBool retry, void* arg) {
+ char prompt[255];
+ secuPWData* pwdata = (secuPWData*)arg;
+ secuPWData pwnull = {PW_NONE, 0};
+ secuPWData pwxtrn = {PW_EXTERNAL, "external"};
+ char* pw;
+
+ if (pwdata == NULL) pwdata = &pwnull;
+
+ if (PK11_ProtectedAuthenticationPath(slot)) {
+ pwdata = &pwxtrn;
+ }
+ if (retry && pwdata->source != 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));
+ pw = GetPasswordString(NULL, prompt);
+ if (pw) {
+ memset(pw, 0, PORT_Strlen(pw));
+ PORT_Free(pw);
+ }
+ /* 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 0000000000..c06d830a7d
--- /dev/null
+++ b/modules/libmar/sign/nss_secutil.h
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* 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 "keyhi.h"
+#include <stdint.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 <conio.h>
+# include <io.h>
+# define QUIET_FGETS quiet_fgets
+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/mar.h b/modules/libmar/src/mar.h
new file mode 100644
index 0000000000..74ab7657f8
--- /dev/null
+++ b/modules/libmar/src/mar.h
@@ -0,0 +1,260 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MAR_H__
+#define MAR_H__
+
+#include <assert.h> // for C11 static_assert
+#include <stdint.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* We have a MAX_SIGNATURES limit so that an invalid MAR will never
+ * waste too much of either updater's or signmar's time.
+ * It is also used at various places internally and will affect memory usage.
+ * If you want to increase this value above 9 then you need to adjust parsing
+ * code in tool/mar.c.
+ */
+#define MAX_SIGNATURES 8
+static_assert(MAX_SIGNATURES <= 9, "too many signatures");
+
+struct ProductInformationBlock {
+ const char* MARChannelID;
+ const char* productVersion;
+};
+
+/**
+ * The MAR item data structure.
+ */
+typedef struct MarItem_ {
+ struct MarItem_* next; /* private field */
+ uint32_t offset; /* offset into archive */
+ uint32_t length; /* length of data in bytes */
+ uint32_t flags; /* contains file mode bits */
+ char name[1]; /* file path */
+} MarItem;
+
+/**
+ * File offset and length for tracking access of byte indexes
+ */
+typedef struct SeenIndex_ {
+ struct SeenIndex_* next; /* private field */
+ uint32_t offset; /* offset into archive */
+ uint32_t length; /* length of the data in bytes */
+} SeenIndex;
+
+#define TABLESIZE 256
+
+/**
+ * Mozilla ARchive (MAR) file data structure
+ */
+struct MarFile_ {
+ unsigned char* buffer; /* file buffer containing the entire MAR */
+ size_t data_len; /* byte count of the data in the buffer */
+ MarItem* item_table[TABLESIZE]; /* hash table of files in the archive */
+ SeenIndex* index_list; /* file indexes processed */
+ int item_table_is_valid; /* header and index validation flag */
+};
+
+typedef struct MarFile_ MarFile;
+
+/**
+ * Signature of callback function passed to mar_enum_items.
+ * @param mar The MAR file being visited.
+ * @param item The MAR item being visited.
+ * @param data The data parameter passed by the caller of mar_enum_items.
+ * @return A non-zero value to stop enumerating.
+ */
+typedef int (*MarItemCallback)(MarFile* mar, const MarItem* item, void* data);
+
+enum MarReadResult_ {
+ MAR_READ_SUCCESS,
+ MAR_IO_ERROR,
+ MAR_MEM_ERROR,
+ MAR_FILE_TOO_BIG_ERROR,
+};
+
+typedef enum MarReadResult_ MarReadResult;
+
+/**
+ * Open a MAR file for reading.
+ * @param path Specifies the path to the MAR file to open. This path must
+ * be compatible with fopen.
+ * @param out_mar Out-parameter through which the created MarFile structure is
+ * returned. Guaranteed to be a valid structure if
+ * MAR_READ_SUCCESS is returned. Otherwise NULL will be
+ * assigned.
+ * @return NULL if an error occurs.
+ */
+MarReadResult mar_open(const char* path, MarFile** out_mar);
+
+#ifdef XP_WIN
+MarReadResult mar_wopen(const wchar_t* path, MarFile** out_mar);
+#endif
+
+/**
+ * Close a MAR file that was opened using mar_open.
+ * @param mar The MarFile object to close.
+ */
+void mar_close(MarFile* mar);
+
+/**
+ * Reads the specified amount of data from the buffer in MarFile that contains
+ * the entirety of the MAR file data.
+ * @param mar The MAR file to read from.
+ * @param dest The buffer to read into.
+ * @param position The byte index to start reading from the MAR at.
+ * On success, position will be incremented by size.
+ * @param size The number of bytes to read.
+ * @return 0 If the specified amount of data was read.
+ * -1 If the buffer MAR is not large enough to read the
+ * specified amount of data at the specified position.
+ */
+int mar_read_buffer(MarFile* mar, void* dest, size_t* position, size_t size);
+
+/**
+ * Reads the specified amount of data from the buffer in MarFile that contains
+ * the entirety of the MAR file data. If there isn't that much data remaining,
+ * reads as much as possible.
+ * @param mar The MAR file to read from.
+ * @param dest The buffer to read into.
+ * @param position The byte index to start reading from the MAR at.
+ * This function will increment position by the number of bytes
+ * copied.
+ * @param size The maximum number of bytes to read.
+ * @return The number of bytes copied into dest.
+ */
+int mar_read_buffer_max(MarFile* mar, void* dest, size_t* position,
+ size_t size);
+
+/**
+ * Increments position by distance. Checks that the resulting position is still
+ * within the bounds of the buffer. Much like fseek, this will allow position to
+ * be successfully placed just after the end of the buffer.
+ * @param mar The MAR file to read from.
+ * @param position The byte index to start reading from the MAR at.
+ * On success, position will be incremented by size.
+ * @param distance The number of bytes to move forward by.
+ * @return 0 If position was successfully moved.
+ * -1 If moving position by distance would move it outside the
+ * bounds of the buffer.
+ */
+int mar_buffer_seek(MarFile* mar, size_t* position, size_t distance);
+
+/**
+ * Find an item in the MAR file by name.
+ * @param mar The MarFile object to query.
+ * @param item The name of the item to query.
+ * @return A const reference to a MAR item or NULL if not found.
+ */
+const MarItem* mar_find_item(MarFile* mar, const char* item);
+
+/**
+ * Enumerate all MAR items via callback function.
+ * @param mar The MAR file to enumerate.
+ * @param callback The function to call for each MAR item.
+ * @param data A caller specified value that is passed along to the
+ * callback function.
+ * @return 0 if the enumeration ran to completion. Otherwise, any
+ * non-zero return value from the callback is returned.
+ */
+int mar_enum_items(MarFile* mar, MarItemCallback callback, void* data);
+
+/**
+ * Read from MAR item at given offset up to bufsize bytes.
+ * @param mar The MAR file to read.
+ * @param item The MAR item to read.
+ * @param offset The byte offset relative to the start of the item.
+ * @param buf A pointer to a buffer to copy the data into.
+ * @param bufsize The length of the buffer to copy the data into.
+ * @return The number of bytes written or a negative value if an
+ * error occurs.
+ */
+int mar_read(MarFile* mar, const MarItem* item, int offset, uint8_t* buf,
+ int bufsize);
+
+/**
+ * Create a MAR file from a set of files.
+ * @param dest The path to the file to create. This path must be
+ * compatible with fopen.
+ * @param numfiles The number of files to store in the archive.
+ * @param files The list of null-terminated file paths. Each file
+ * path must be compatible with fopen.
+ * @param infoBlock The information to store in the product information block.
+ * @return A non-zero value if an error occurs.
+ */
+int mar_create(const char* dest, int numfiles, char** files,
+ struct ProductInformationBlock* infoBlock);
+
+/**
+ * Extract a MAR file to the current working directory.
+ * @param path The path to the MAR file to extract. This path must be
+ * compatible with fopen.
+ * @return A non-zero value if an error occurs.
+ */
+int mar_extract(const char* path);
+
+#define MAR_MAX_CERT_SIZE (16 * 1024) // Way larger than necessary
+
+/* Read the entire file (not a MAR file) into a newly-allocated buffer.
+ * This function does not write to stderr. Instead, the caller should
+ * write whatever error messages it sees fit. The caller must free the returned
+ * buffer using free().
+ *
+ * @param filePath The path to the file that should be read.
+ * @param maxSize The maximum valid file size.
+ * @param data On success, *data will point to a newly-allocated buffer
+ * with the file's contents in it.
+ * @param size On success, *size will be the size of the created buffer.
+ *
+ * @return 0 on success, -1 on error
+ */
+int mar_read_entire_file(const char* filePath, uint32_t maxSize,
+ /*out*/ const uint8_t** data,
+ /*out*/ uint32_t* size);
+
+/**
+ * Verifies a MAR file by verifying each signature with the corresponding
+ * certificate. That is, the first signature will be verified using the first
+ * certificate given, the second signature will be verified using the second
+ * certificate given, etc. The signature count must exactly match the number of
+ * certificates given, and all signature verifications must succeed.
+ * 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 Pointer to the first element in an array of certificate
+ * file data.
+ * @param certDataSizes Pointer to the first element in an array for size of
+ * the cert data.
+ * @param certCount The number of elements in certData and certDataSizes
+ * @return 0 on success
+ * a negative number if there was an error
+ * a positive number if the signature does not verify
+ */
+int mar_verify_signatures(MarFile* mar, const uint8_t* const* certData,
+ const uint32_t* certDataSizes, uint32_t certCount);
+
+/**
+ * Reads the product info block from the MAR file's additional block section.
+ * The caller is responsible for freeing the fields in infoBlock
+ * if the return is successful.
+ *
+ * @param infoBlock Out parameter for where to store the result to
+ * @return 0 on success, -1 on failure
+ */
+int mar_read_product_info_block(MarFile* mar,
+ struct ProductInformationBlock* infoBlock);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MAR_H__ */
diff --git a/modules/libmar/src/mar_cmdline.h b/modules/libmar/src/mar_cmdline.h
new file mode 100644
index 0000000000..4b9302f0b1
--- /dev/null
+++ b/modules/libmar/src/mar_cmdline.h
@@ -0,0 +1,102 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MAR_CMDLINE_H__
+#define MAR_CMDLINE_H__
+
+/* We use NSPR here just to import the definition of uint32_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ProductInformationBlock;
+
+/**
+ * Determines MAR file information.
+ *
+ * @param path The path of the MAR file to check.
+ * @param hasSignatureBlock Optional out parameter specifying if the MAR
+ * file has a signature block or not.
+ * @param numSignatures Optional out parameter for storing the number
+ * of signatures in the MAR file.
+ * @param hasAdditionalBlocks Optional out parameter specifying if the MAR
+ * file has additional blocks or not.
+ * @param offsetAdditionalBlocks Optional out parameter for the offset to the
+ * first additional block. Value is only valid if
+ * hasAdditionalBlocks is not equal to 0.
+ * @param numAdditionalBlocks Optional out parameter for the number of
+ * additional blocks. Value is only valid if
+ * has_additional_blocks is not equal to 0.
+ * @return 0 on success and non-zero on failure.
+ */
+int get_mar_file_info(const char* path, int* hasSignatureBlock,
+ uint32_t* numSignatures, int* hasAdditionalBlocks,
+ uint32_t* offsetAdditionalBlocks,
+ uint32_t* numAdditionalBlocks);
+
+/**
+ * Reads the product info block from the MAR file's additional block section.
+ * The caller is responsible for freeing the fields in infoBlock
+ * if the return is successful.
+ *
+ * @param infoBlock Out parameter for where to store the result to
+ * @return 0 on success, -1 on failure
+ */
+int read_product_info_block(char* path,
+ struct ProductInformationBlock* infoBlock);
+
+/**
+ * Refreshes the product information block with the new information.
+ * The input MAR must not be signed or the function call will fail.
+ *
+ * @param path The path to the MAR file whose product info block
+ * should be refreshed.
+ * @param infoBlock Out parameter for where to store the result to
+ * @return 0 on success, -1 on failure
+ */
+int refresh_product_info_block(const char* path,
+ struct ProductInformationBlock* infoBlock);
+
+/**
+ * Writes out a copy of the MAR at src but with the signature block stripped.
+ *
+ * @param src The path of the source MAR file
+ * @param dest The path of the MAR file to write out that
+ has no signature block
+ * @return 0 on success
+ * -1 on error
+*/
+int strip_signature_block(const char* src, const char* dest);
+
+/**
+ * Extracts a signature from a MAR file, base64 encodes it, and writes it out
+ *
+ * @param src The path of the source MAR file
+ * @param sigIndex The index of the signature to extract
+ * @param dest The path of file to write the signature to
+ * @return 0 on success
+ * -1 on error
+ */
+int extract_signature(const char* src, uint32_t sigIndex, const char* dest);
+
+/**
+ * Imports a base64 encoded signature into a MAR file
+ *
+ * @param src The path of the source MAR file
+ * @param sigIndex The index of the signature to import
+ * @param base64SigFile A file which contains the signature to import
+ * @param dest The path of the destination MAR file with replaced
+ * signature
+ * @return 0 on success
+ * -1 on error
+ */
+int import_signature(const char* src, uint32_t sigIndex,
+ const char* base64SigFile, const char* dest);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MAR_CMDLINE_H__ */
diff --git a/modules/libmar/src/mar_create.c b/modules/libmar/src/mar_create.c
new file mode 100644
index 0000000000..5c8e8a0a80
--- /dev/null
+++ b/modules/libmar/src/mar_create.c
@@ -0,0 +1,391 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include "mar_private.h"
+#include "mar_cmdline.h"
+#include "mar.h"
+
+#ifdef XP_WIN
+# include <winsock2.h>
+#else
+# include <netinet/in.h>
+# include <unistd.h>
+#endif
+
+struct MarItemStack {
+ void* head;
+ uint32_t size_used;
+ uint32_t size_allocated;
+ uint32_t last_offset;
+};
+
+/**
+ * Push a new item onto the stack of items. The stack is a single block
+ * of memory.
+ */
+static int mar_push(struct MarItemStack* stack, uint32_t length, uint32_t flags,
+ const char* name) {
+ int namelen;
+ uint32_t n_offset, n_length, n_flags;
+ uint32_t size;
+ char* data;
+
+ namelen = strlen(name);
+ size = MAR_ITEM_SIZE(namelen);
+
+ if (stack->size_allocated - stack->size_used < size) {
+ /* increase size of stack */
+ size_t size_needed = ROUND_UP(stack->size_used + size, BLOCKSIZE);
+ stack->head = realloc(stack->head, size_needed);
+ if (!stack->head) {
+ return -1;
+ }
+ stack->size_allocated = size_needed;
+ }
+
+ data = (((char*)stack->head) + stack->size_used);
+
+ n_offset = htonl(stack->last_offset);
+ n_length = htonl(length);
+ n_flags = htonl(flags);
+
+ memcpy(data, &n_offset, sizeof(n_offset));
+ data += sizeof(n_offset);
+
+ memcpy(data, &n_length, sizeof(n_length));
+ data += sizeof(n_length);
+
+ memcpy(data, &n_flags, sizeof(n_flags));
+ data += sizeof(n_flags);
+
+ memcpy(data, name, namelen + 1);
+
+ stack->size_used += size;
+ stack->last_offset += length;
+ return 0;
+}
+
+static int mar_concat_file(FILE* fp, const char* path) {
+ FILE* in;
+ char buf[BLOCKSIZE];
+ size_t len;
+ int rv = 0;
+
+ in = fopen(path, "rb");
+ if (!in) {
+ fprintf(stderr, "ERROR: could not open file in mar_concat_file()\n");
+ perror(path);
+ return -1;
+ }
+
+ while ((len = fread(buf, 1, BLOCKSIZE, in)) > 0) {
+ if (fwrite(buf, len, 1, fp) != 1) {
+ rv = -1;
+ break;
+ }
+ }
+
+ fclose(in);
+ return rv;
+}
+
+/**
+ * Writes out the product information block to the specified file.
+ *
+ * @param fp The opened MAR file being created.
+ * @param stack A pointer to the MAR item stack being used to create
+ * the MAR
+ * @param infoBlock The product info block to store in the file.
+ * @return 0 on success.
+ */
+static int mar_concat_product_info_block(
+ FILE* fp, struct MarItemStack* stack,
+ struct ProductInformationBlock* infoBlock) {
+ char buf[PIB_MAX_MAR_CHANNEL_ID_SIZE + PIB_MAX_PRODUCT_VERSION_SIZE];
+ uint32_t additionalBlockID = 1, infoBlockSize, unused;
+ if (!fp || !infoBlock || !infoBlock->MARChannelID ||
+ !infoBlock->productVersion) {
+ return -1;
+ }
+
+ /* The MAR channel name must be < 64 bytes per the spec */
+ if (strlen(infoBlock->MARChannelID) > PIB_MAX_MAR_CHANNEL_ID_SIZE) {
+ return -1;
+ }
+
+ /* The product version must be < 32 bytes per the spec */
+ if (strlen(infoBlock->productVersion) > PIB_MAX_PRODUCT_VERSION_SIZE) {
+ return -1;
+ }
+
+ /* Although we don't need the product information block size to include the
+ maximum MAR channel name and product version, we allocate the maximum
+ amount to make it easier to modify the MAR file for repurposing MAR files
+ to different MAR channels. + 2 is for the NULL terminators. */
+ infoBlockSize = sizeof(infoBlockSize) + sizeof(additionalBlockID) +
+ PIB_MAX_MAR_CHANNEL_ID_SIZE + PIB_MAX_PRODUCT_VERSION_SIZE +
+ 2;
+ if (stack) {
+ stack->last_offset += infoBlockSize;
+ }
+
+ /* Write out the product info block size */
+ infoBlockSize = htonl(infoBlockSize);
+ if (fwrite(&infoBlockSize, sizeof(infoBlockSize), 1, fp) != 1) {
+ return -1;
+ }
+ infoBlockSize = ntohl(infoBlockSize);
+
+ /* Write out the product info block ID */
+ additionalBlockID = htonl(additionalBlockID);
+ if (fwrite(&additionalBlockID, sizeof(additionalBlockID), 1, fp) != 1) {
+ return -1;
+ }
+ additionalBlockID = ntohl(additionalBlockID);
+
+ /* Write out the channel name and NULL terminator */
+ if (fwrite(infoBlock->MARChannelID, strlen(infoBlock->MARChannelID) + 1, 1,
+ fp) != 1) {
+ return -1;
+ }
+
+ /* Write out the product version string and NULL terminator */
+ if (fwrite(infoBlock->productVersion, strlen(infoBlock->productVersion) + 1,
+ 1, fp) != 1) {
+ return -1;
+ }
+
+ /* Write out the rest of the block that is unused */
+ unused = infoBlockSize - (sizeof(infoBlockSize) + sizeof(additionalBlockID) +
+ strlen(infoBlock->MARChannelID) +
+ strlen(infoBlock->productVersion) + 2);
+ memset(buf, 0, sizeof(buf));
+ if (fwrite(buf, unused, 1, fp) != 1) {
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * Refreshes the product information block with the new information.
+ * The input MAR must not be signed or the function call will fail.
+ *
+ * @param path The path to the MAR file whose product info block
+ * should be refreshed.
+ * @param infoBlock Out parameter for where to store the result to
+ * @return 0 on success, -1 on failure
+ */
+int refresh_product_info_block(const char* path,
+ struct ProductInformationBlock* infoBlock) {
+ FILE* fp;
+ int rv;
+ uint32_t numSignatures, additionalBlockSize, additionalBlockID,
+ offsetAdditionalBlocks, numAdditionalBlocks, i;
+ int additionalBlocks, hasSignatureBlock;
+ int64_t oldPos;
+
+ rv = get_mar_file_info(path, &hasSignatureBlock, &numSignatures,
+ &additionalBlocks, &offsetAdditionalBlocks,
+ &numAdditionalBlocks);
+ if (rv) {
+ fprintf(stderr, "ERROR: Could not obtain MAR information.\n");
+ return -1;
+ }
+
+ if (hasSignatureBlock && numSignatures) {
+ fprintf(stderr, "ERROR: Cannot refresh a signed MAR\n");
+ return -1;
+ }
+
+ fp = fopen(path, "r+b");
+ if (!fp) {
+ fprintf(stderr, "ERROR: could not open target file: %s\n", path);
+ return -1;
+ }
+
+ if (fseeko(fp, offsetAdditionalBlocks, SEEK_SET)) {
+ fprintf(stderr, "ERROR: could not seek to additional blocks\n");
+ fclose(fp);
+ return -1;
+ }
+
+ for (i = 0; i < numAdditionalBlocks; ++i) {
+ /* Get the position of the start of this block */
+ oldPos = ftello(fp);
+
+ /* Read the additional block size */
+ if (fread(&additionalBlockSize, sizeof(additionalBlockSize), 1, fp) != 1) {
+ fclose(fp);
+ return -1;
+ }
+ additionalBlockSize = ntohl(additionalBlockSize);
+
+ /* Read the additional block ID */
+ if (fread(&additionalBlockID, sizeof(additionalBlockID), 1, fp) != 1) {
+ fclose(fp);
+ return -1;
+ }
+ additionalBlockID = ntohl(additionalBlockID);
+
+ if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) {
+ if (fseeko(fp, oldPos, SEEK_SET)) {
+ fprintf(stderr, "Could not seek back to Product Information Block\n");
+ fclose(fp);
+ return -1;
+ }
+
+ if (mar_concat_product_info_block(fp, NULL, infoBlock)) {
+ fprintf(stderr, "Could not concat Product Information Block\n");
+ fclose(fp);
+ return -1;
+ }
+
+ fclose(fp);
+ return 0;
+ }
+
+ /* This is not the additional block you're looking for. Move along. */
+ if (fseek(fp, additionalBlockSize, SEEK_CUR)) {
+ fprintf(stderr, "ERROR: Could not seek past current block.\n");
+ fclose(fp);
+ return -1;
+ }
+ }
+
+ /* If we had a product info block we would have already returned */
+ fclose(fp);
+ fprintf(stderr, "ERROR: Could not refresh because block does not exist\n");
+ return -1;
+}
+
+/**
+ * Create a MAR file from a set of files.
+ * @param dest The path to the file to create. This path must be
+ * compatible with fopen.
+ * @param numfiles The number of files to store in the archive.
+ * @param files The list of null-terminated file paths. Each file
+ * path must be compatible with fopen.
+ * @param infoBlock The information to store in the product information block.
+ * @return A non-zero value if an error occurs.
+ */
+int mar_create(const char* dest, int num_files, char** files,
+ struct ProductInformationBlock* infoBlock) {
+ struct MarItemStack stack;
+ uint32_t offset_to_index = 0, size_of_index, numSignatures,
+ numAdditionalSections;
+ uint64_t sizeOfEntireMAR = 0;
+ struct stat st;
+ FILE* fp;
+ int i, rv = -1;
+
+ memset(&stack, 0, sizeof(stack));
+
+ fp = fopen(dest, "wb");
+ if (!fp) {
+ fprintf(stderr, "ERROR: could not create target file: %s\n", dest);
+ return -1;
+ }
+
+ if (fwrite(MAR_ID, MAR_ID_SIZE, 1, fp) != 1) {
+ goto failure;
+ }
+ if (fwrite(&offset_to_index, sizeof(uint32_t), 1, fp) != 1) {
+ goto failure;
+ }
+
+ stack.last_offset = MAR_ID_SIZE + sizeof(offset_to_index) +
+ sizeof(numSignatures) + sizeof(numAdditionalSections) +
+ sizeof(sizeOfEntireMAR);
+
+ /* We will circle back on this at the end of the MAR creation to fill it */
+ if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) {
+ goto failure;
+ }
+
+ /* Write out the number of signatures, for now only at most 1 is supported */
+ numSignatures = 0;
+ if (fwrite(&numSignatures, sizeof(numSignatures), 1, fp) != 1) {
+ goto failure;
+ }
+
+ /* Write out the number of additional sections, for now just 1
+ for the product info block */
+ numAdditionalSections = htonl(1);
+ if (fwrite(&numAdditionalSections, sizeof(numAdditionalSections), 1, fp) !=
+ 1) {
+ goto failure;
+ }
+ numAdditionalSections = ntohl(numAdditionalSections);
+
+ if (mar_concat_product_info_block(fp, &stack, infoBlock)) {
+ goto failure;
+ }
+
+ for (i = 0; i < num_files; ++i) {
+ if (stat(files[i], &st)) {
+ fprintf(stderr, "ERROR: file not found: %s\n", files[i]);
+ goto failure;
+ }
+
+ if (mar_push(&stack, st.st_size, st.st_mode & 0777, files[i])) {
+ goto failure;
+ }
+
+ /* concatenate input file to archive */
+ if (mar_concat_file(fp, files[i])) {
+ goto failure;
+ }
+ }
+
+ /* write out the index (prefixed with length of index) */
+ size_of_index = htonl(stack.size_used);
+ if (fwrite(&size_of_index, sizeof(size_of_index), 1, fp) != 1) {
+ goto failure;
+ }
+ 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);
+
+ sizeOfEntireMAR =
+ ((uint64_t)stack.last_offset) + stack.size_used + sizeof(size_of_index);
+ sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR);
+ if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) {
+ goto failure;
+ }
+ sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR);
+
+ rv = 0;
+failure:
+ if (stack.head) {
+ free(stack.head);
+ }
+ fclose(fp);
+ if (rv) {
+ remove(dest);
+ }
+ return rv;
+}
diff --git a/modules/libmar/src/mar_extract.c b/modules/libmar/src/mar_extract.c
new file mode 100644
index 0000000000..9b4a3fa26d
--- /dev/null
+++ b/modules/libmar/src/mar_extract.c
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include "mar_private.h"
+#include "mar.h"
+
+#ifdef XP_WIN
+# include <io.h>
+# include <direct.h>
+# define fdopen _fdopen
+#endif
+
+/* Ensure that the directory containing this file exists */
+static int mar_ensure_parent_dir(const char* path) {
+ char* slash = strrchr(path, '/');
+ if (slash) {
+ *slash = '\0';
+ mar_ensure_parent_dir(path);
+#ifdef XP_WIN
+ _mkdir(path);
+#else
+ mkdir(path, 0755);
+#endif
+ *slash = '/';
+ }
+ return 0;
+}
+
+static int mar_test_callback(MarFile* mar, const MarItem* item, void* unused) {
+ FILE* fp;
+ uint8_t buf[BLOCKSIZE];
+ int fd, len, offset = 0;
+
+ if (mar_ensure_parent_dir(item->name)) {
+ return -1;
+ }
+
+#ifdef XP_WIN
+ fd = _open(item->name, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY,
+ item->flags);
+#else
+ fd = creat(item->name, item->flags);
+#endif
+ if (fd == -1) {
+ fprintf(stderr, "ERROR: could not create file in mar_test_callback()\n");
+ perror(item->name);
+ return -1;
+ }
+
+ fp = fdopen(fd, "wb");
+ if (!fp) {
+ return -1;
+ }
+
+ while ((len = mar_read(mar, item, offset, buf, sizeof(buf))) > 0) {
+ if (fwrite(buf, len, 1, fp) != 1) {
+ break;
+ }
+ offset += len;
+ }
+
+ fclose(fp);
+ return len == 0 ? 0 : -1;
+}
+
+int mar_extract(const char* path) {
+ MarFile* mar;
+ int rv;
+
+ MarReadResult result = mar_open(path, &mar);
+ if (result != MAR_READ_SUCCESS) {
+ return -1;
+ }
+
+ rv = mar_enum_items(mar, mar_test_callback, NULL);
+
+ mar_close(mar);
+ return rv;
+}
diff --git a/modules/libmar/src/mar_private.h b/modules/libmar/src/mar_private.h
new file mode 100644
index 0000000000..bd9d4386fe
--- /dev/null
+++ b/modules/libmar/src/mar_private.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MAR_PRIVATE_H__
+#define MAR_PRIVATE_H__
+
+#include <assert.h> // for C11 static_assert
+#include "limits.h"
+#include <stdint.h>
+
+#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
+
+/* Make sure the file is less than 500MB. We do this to protect against
+ invalid MAR files. */
+#define MAX_SIZE_OF_MAR_FILE ((int64_t)524288000)
+
+/* Existing code makes assumptions that the file size is
+ smaller than LONG_MAX. */
+static_assert(MAX_SIZE_OF_MAR_FILE < ((int64_t)LONG_MAX),
+ "max mar file size is too big");
+
+/* We store at most the size up to the signature block + 4
+ bytes per BLOCKSIZE bytes */
+static_assert(sizeof(BLOCKSIZE) < (SIGNATURE_BLOCK_OFFSET + sizeof(uint32_t)),
+ "BLOCKSIZE is too big");
+
+/* The maximum size of any signature supported by current and future
+ implementations of the signmar program. */
+#define MAX_SIGNATURE_LENGTH 2048
+
+/* Each additional block has a unique ID.
+ The product information block has an ID of 1. */
+#define PRODUCT_INFO_BLOCK_ID 1
+
+#define MAR_ITEM_SIZE(namelen) (3 * sizeof(uint32_t) + (namelen) + 1)
+
+/* Product Information Block (PIB) constants */
+#define PIB_MAX_MAR_CHANNEL_ID_SIZE 63
+#define PIB_MAX_PRODUCT_VERSION_SIZE 31
+
+/* 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 <winsock2.h>
+/* Include stdio.h before redefining ftello and fseeko to avoid clobbering
+ * the ftello() and fseeko() function declarations in MinGW's stdio.h. */
+# include <stdio.h>
+# define ftello _ftelli64
+# define fseeko _fseeki64
+#else
+# define _FILE_OFFSET_BITS 64
+# include <netinet/in.h>
+# include <unistd.h>
+# include <stdio.h>
+#endif
+
+#define HOST_TO_NETWORK64(x) \
+ (((((uint64_t)x) & 0xFF) << 56) | ((((uint64_t)x) >> 8) & 0xFF) << 48) | \
+ (((((uint64_t)x) >> 16) & 0xFF) << 40) | \
+ (((((uint64_t)x) >> 24) & 0xFF) << 32) | \
+ (((((uint64_t)x) >> 32) & 0xFF) << 24) | \
+ (((((uint64_t)x) >> 40) & 0xFF) << 16) | \
+ (((((uint64_t)x) >> 48) & 0xFF) << 8) | (((uint64_t)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
new file mode 100644
index 0000000000..679dc258d6
--- /dev/null
+++ b/modules/libmar/src/mar_read.c
@@ -0,0 +1,787 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include "city.h"
+#include "mar_private.h"
+#include "mar.h"
+#ifdef XP_WIN
+# define strdup _strdup
+#endif
+
+/* This block must be at most 104 bytes.
+ MAR channel name < 64 bytes, and product version < 32 bytes + 3 NULL
+ terminator bytes. We only check for 96 though because we remove 8
+ bytes above from the additionalBlockSize: We subtract
+ sizeof(additionalBlockSize) and sizeof(additionalBlockID) */
+#define MAXADDITIONALBLOCKSIZE 96
+
+static uint32_t mar_hash_name(const char* name) {
+ return CityHash64(name, strlen(name)) % TABLESIZE;
+}
+
+static int mar_insert_item(MarFile* mar, const char* name, uint32_t namelen,
+ uint32_t offset, uint32_t length, uint32_t flags) {
+ MarItem *item, *root;
+ uint32_t hash;
+
+ item = (MarItem*)malloc(sizeof(MarItem) + namelen);
+ if (!item) {
+ return -1;
+ }
+ item->next = NULL;
+ item->offset = offset;
+ item->length = length;
+ item->flags = flags;
+ memcpy(item->name, name, namelen + 1);
+
+ hash = mar_hash_name(name);
+
+ root = mar->item_table[hash];
+ if (!root) {
+ mar->item_table[hash] = item;
+ } else {
+ /* append item */
+ while (root->next) root = root->next;
+ root->next = item;
+ }
+ return 0;
+}
+
+static int mar_consume_index(MarFile* mar, char** buf, const char* buf_end) {
+ /*
+ * Each item has the following structure:
+ * uint32_t offset (network byte order)
+ * uint32_t length (network byte order)
+ * uint32_t flags (network byte order)
+ * char name[N] (where N >= 1)
+ * char null_byte;
+ */
+ uint32_t offset;
+ uint32_t length;
+ uint32_t flags;
+ const char* name;
+ int namelen;
+
+ if ((buf_end - *buf) < (int)(3 * sizeof(uint32_t) + 2)) {
+ return -1;
+ }
+
+ memcpy(&offset, *buf, sizeof(offset));
+ *buf += sizeof(offset);
+
+ memcpy(&length, *buf, sizeof(length));
+ *buf += sizeof(length);
+
+ memcpy(&flags, *buf, sizeof(flags));
+ *buf += sizeof(flags);
+
+ offset = ntohl(offset);
+ length = ntohl(length);
+ flags = ntohl(flags);
+
+ name = *buf;
+ /* find namelen; must take care not to read beyond buf_end */
+ while (**buf) {
+ /* buf_end points one byte past the end of buf's allocation */
+ if (*buf == (buf_end - 1)) {
+ return -1;
+ }
+ ++(*buf);
+ }
+ namelen = (*buf - name);
+ /* must ensure that namelen is valid */
+ if (namelen < 0) {
+ return -1;
+ }
+ /* consume null byte */
+ if (*buf == buf_end) {
+ return -1;
+ }
+ ++(*buf);
+
+ return mar_insert_item(mar, name, namelen, offset, length, flags);
+}
+
+static int mar_read_index(MarFile* mar) {
+ char id[MAR_ID_SIZE], *buf, *bufptr, *bufend;
+ uint32_t offset_to_index, size_of_index;
+ size_t mar_position = 0;
+
+ /* verify MAR ID */
+ if (mar_read_buffer(mar, id, &mar_position, MAR_ID_SIZE) != 0) {
+ return -1;
+ }
+ if (memcmp(id, MAR_ID, MAR_ID_SIZE) != 0) {
+ return -1;
+ }
+
+ if (mar_read_buffer(mar, &offset_to_index, &mar_position, sizeof(uint32_t)) !=
+ 0) {
+ return -1;
+ }
+ offset_to_index = ntohl(offset_to_index);
+
+ mar_position = 0;
+ if (mar_buffer_seek(mar, &mar_position, offset_to_index) != 0) {
+ return -1;
+ }
+ if (mar_read_buffer(mar, &size_of_index, &mar_position, sizeof(uint32_t)) !=
+ 0) {
+ return -1;
+ }
+ size_of_index = ntohl(size_of_index);
+
+ buf = (char*)malloc(size_of_index);
+ if (!buf) {
+ return -1;
+ }
+ if (mar_read_buffer(mar, buf, &mar_position, size_of_index) != 0) {
+ free(buf);
+ return -1;
+ }
+
+ bufptr = buf;
+ bufend = buf + size_of_index;
+ while (bufptr < bufend && mar_consume_index(mar, &bufptr, bufend) == 0)
+ ;
+
+ free(buf);
+ return (bufptr == bufend) ? 0 : -1;
+}
+
+/**
+ * Adds an offset and length to the MarFile's index_list
+ * @param mar The MarFile that owns this offset length pair
+ * @param offset The byte offset in the archive to be marked as processed
+ * @param length The length corresponding to this byte offset
+ * @return int 1 on success, 0 if offset has been previously processed
+ * -1 if unable to allocate space for the SeenIndexes
+ */
+static int mar_insert_offset(MarFile* mar, uint32_t offset, uint32_t length) {
+ /* Ignore files with no length */
+ if (length == 0) {
+ return 1;
+ }
+
+ SeenIndex* index = (SeenIndex*)malloc(sizeof(SeenIndex));
+ if (!index) {
+ return -1;
+ }
+ index->next = NULL;
+ index->offset = offset;
+ index->length = length;
+ uint32_t index_end = index->offset + index->length - 1;
+
+ /* If this is our first index store it at the front */
+ if (mar->index_list == NULL) {
+ mar->index_list = index;
+ return 1;
+ }
+
+ /* Search for matching indexes in the list of those previously visited */
+ SeenIndex* previous;
+ SeenIndex* current = mar->index_list;
+ while (current != NULL) {
+ uint32_t current_end = current->offset + current->length - 1;
+
+ /* If index has collided with the front or end of current or if current has
+ collided with the front or end of index return false */
+ if ((index->offset >= current->offset && index->offset <= current_end) ||
+ (index_end >= current->offset && index_end <= current_end) ||
+ (current->offset >= index->offset && current->offset <= index_end) ||
+ (current_end >= index->offset && current_end <= index_end)) {
+ free(index);
+ return 0;
+ }
+
+ /* else move to the next in the list */
+ previous = current;
+ current = current->next;
+ }
+
+ /* These indexes are valid, track them */
+ previous->next = index;
+ return 1;
+}
+
+/**
+ * Internal shared code for mar_open and mar_wopen.
+ * Reads the entire MAR into memory. Fails if it is bigger than
+ * MAX_SIZE_OF_MAR_FILE bytes.
+ */
+static MarReadResult mar_fpopen(FILE* fp, MarFile** out_mar) {
+ *out_mar = NULL;
+ MarFile* mar;
+
+ mar = (MarFile*)malloc(sizeof(*mar));
+ if (!mar) {
+ return MAR_MEM_ERROR;
+ }
+
+ off_t buffer_size = -1;
+ if (fseeko(fp, 0, SEEK_END) == 0) {
+ buffer_size = ftello(fp);
+ }
+ rewind(fp);
+ if (buffer_size < 0) {
+ fprintf(stderr, "Warning: MAR size could not be determined\n");
+ buffer_size = MAX_SIZE_OF_MAR_FILE;
+ }
+ if (buffer_size > MAX_SIZE_OF_MAR_FILE) {
+ fprintf(stderr, "ERROR: MAR exceeds maximum size (%lli)\n",
+ (long long int)buffer_size);
+ free(mar);
+ return MAR_FILE_TOO_BIG_ERROR;
+ }
+
+ mar->buffer = malloc(buffer_size);
+ if (!mar->buffer) {
+ fprintf(stderr, "ERROR: MAR buffer could not be allocated\n");
+ free(mar);
+ return MAR_MEM_ERROR;
+ }
+ mar->data_len = fread(mar->buffer, 1, buffer_size, fp);
+ if (fgetc(fp) != EOF) {
+ fprintf(stderr, "ERROR: File is larger than buffer (%lli)\n",
+ (long long int)buffer_size);
+ free(mar->buffer);
+ free(mar);
+ return MAR_IO_ERROR;
+ }
+ if (ferror(fp)) {
+ fprintf(stderr, "ERROR: Failed to read MAR\n");
+ free(mar->buffer);
+ free(mar);
+ return MAR_IO_ERROR;
+ }
+
+ mar->item_table_is_valid = 0;
+ memset(mar->item_table, 0, sizeof(mar->item_table));
+ mar->index_list = NULL;
+
+ *out_mar = mar;
+ return MAR_READ_SUCCESS;
+}
+
+MarReadResult mar_open(const char* path, MarFile** out_mar) {
+ *out_mar = NULL;
+
+ FILE* fp;
+
+ fp = fopen(path, "rb");
+ if (!fp) {
+ fprintf(stderr, "ERROR: could not open file in mar_open()\n");
+ perror(path);
+ return MAR_IO_ERROR;
+ }
+
+ MarReadResult result = mar_fpopen(fp, out_mar);
+ fclose(fp);
+ return result;
+}
+
+#ifdef XP_WIN
+MarReadResult mar_wopen(const wchar_t* path, MarFile** out_mar) {
+ *out_mar = NULL;
+
+ FILE* fp;
+
+ _wfopen_s(&fp, path, L"rb");
+ if (!fp) {
+ fprintf(stderr, "ERROR: could not open file in mar_wopen()\n");
+ _wperror(path);
+ return MAR_IO_ERROR;
+ }
+
+ MarReadResult result = mar_fpopen(fp, out_mar);
+ fclose(fp);
+ return result;
+}
+#endif
+
+void mar_close(MarFile* mar) {
+ MarItem* item;
+ SeenIndex* index;
+ int i;
+
+ free(mar->buffer);
+
+ for (i = 0; i < TABLESIZE; ++i) {
+ item = mar->item_table[i];
+ while (item) {
+ MarItem* temp = item;
+ item = item->next;
+ free(temp);
+ }
+ }
+
+ while (mar->index_list != NULL) {
+ index = mar->index_list;
+ mar->index_list = index->next;
+ free(index);
+ }
+
+ free(mar);
+}
+
+int mar_read_buffer(MarFile* mar, void* dest, size_t* position, size_t size) {
+ // size may be provided by the MAR, which we may not have finished validating
+ // the signature on yet. Make sure not to trust it in a way that could
+ // cause an overflow.
+ if (size > mar->data_len) {
+ return -1;
+ }
+ if (*position > mar->data_len - size) {
+ return -1;
+ }
+ memcpy(dest, mar->buffer + *position, size);
+ *position += size;
+ return 0;
+}
+
+int mar_read_buffer_max(MarFile* mar, void* dest, size_t* position,
+ size_t size) {
+ // size may be provided by the MAR, which we may not have finished validating
+ // the signature on yet. Make sure not to trust it in a way that could
+ // cause an overflow.
+ if (mar->data_len <= *position) {
+ return 0;
+ }
+ size_t read_count = mar->data_len - *position;
+ if (read_count > size) {
+ read_count = size;
+ }
+ memcpy(dest, mar->buffer + *position, read_count);
+ *position += read_count;
+ return read_count;
+}
+
+int mar_buffer_seek(MarFile* mar, size_t* position, size_t distance) {
+ // distance may be provided by the MAR, which we may not have finished
+ // validating the signature on yet. Make sure not to trust it in a way that
+ // could cause an overflow.
+ if (distance > mar->data_len) {
+ return -1;
+ }
+ if (*position > mar->data_len - distance) {
+ return -1;
+ }
+ *position += distance;
+ return 0;
+}
+
+/**
+ * Determines the MAR file information.
+ *
+ * @param mar An open MAR file.
+ * @param mar_position The current position in the MAR.
+ * Its value will be updated to the current
+ * position in the MAR after the function exits.
+ * Since its initial value will never actually be
+ * used, this is effectively an outparam.
+ * @param hasSignatureBlock Optional out parameter specifying if the MAR
+ * file has a signature block or not.
+ * @param numSignatures Optional out parameter for storing the number
+ * of signatures in the MAR file.
+ * @param hasAdditionalBlocks Optional out parameter specifying if the MAR
+ * file has additional blocks or not.
+ * @param offsetAdditionalBlocks Optional out parameter for the offset to the
+ * first additional block. Value is only valid if
+ * hasAdditionalBlocks is not equal to 0.
+ * @param numAdditionalBlocks Optional out parameter for the number of
+ * additional blocks. Value is only valid if
+ * hasAdditionalBlocks is not equal to 0.
+ * @return 0 on success and non-zero on failure.
+ */
+int get_open_mar_file_info(MarFile* mar, size_t* mar_position,
+ int* hasSignatureBlock, uint32_t* numSignatures,
+ int* hasAdditionalBlocks,
+ uint32_t* offsetAdditionalBlocks,
+ uint32_t* numAdditionalBlocks) {
+ uint32_t offsetToIndex, offsetToContent, signatureCount, signatureLen, i;
+
+ /* One of hasSignatureBlock or hasAdditionalBlocks must be non NULL */
+ if (!hasSignatureBlock && !hasAdditionalBlocks) {
+ return -1;
+ }
+
+ /* Skip to the start of the offset index */
+ *mar_position = 0;
+ if (mar_buffer_seek(mar, mar_position, MAR_ID_SIZE) != 0) {
+ return -1;
+ }
+
+ /* Read the offset to the index. */
+ if (mar_read_buffer(mar, &offsetToIndex, mar_position,
+ sizeof(offsetToIndex)) != 0) {
+ return -1;
+ }
+ offsetToIndex = ntohl(offsetToIndex);
+
+ if (numSignatures) {
+ /* Skip past the MAR file size field */
+ if (mar_buffer_seek(mar, mar_position, sizeof(uint64_t)) != 0) {
+ return -1;
+ }
+
+ /* Read the number of signatures field */
+ if (mar_read_buffer(mar, numSignatures, mar_position,
+ sizeof(*numSignatures)) != 0) {
+ return -1;
+ }
+ *numSignatures = ntohl(*numSignatures);
+ }
+
+ /* Skip to the first index entry past the index size field
+ We do it in 2 calls because offsetToIndex + sizeof(uint32_t)
+ could overflow in theory. */
+ *mar_position = 0;
+ if (mar_buffer_seek(mar, mar_position, offsetToIndex) != 0) {
+ return -1;
+ }
+
+ if (mar_buffer_seek(mar, mar_position, sizeof(uint32_t)) != 0) {
+ return -1;
+ }
+
+ /* Read the first offset to content field. */
+ if (mar_read_buffer(mar, &offsetToContent, mar_position,
+ sizeof(offsetToContent)) != 0) {
+ return -1;
+ }
+ offsetToContent = ntohl(offsetToContent);
+
+ /* Check if we have a new or old MAR file */
+ if (hasSignatureBlock) {
+ if (offsetToContent == MAR_ID_SIZE + sizeof(uint32_t)) {
+ *hasSignatureBlock = 0;
+ } else {
+ *hasSignatureBlock = 1;
+ }
+ }
+
+ /* If the caller doesn't care about the product info block
+ value, then just return */
+ if (!hasAdditionalBlocks) {
+ return 0;
+ }
+
+ /* Skip to the start of the signature block */
+ *mar_position = 0;
+ if (mar_buffer_seek(mar, mar_position, SIGNATURE_BLOCK_OFFSET) != 0) {
+ return -1;
+ }
+
+ /* Get the number of signatures */
+ if (mar_read_buffer(mar, &signatureCount, mar_position,
+ sizeof(signatureCount)) != 0) {
+ return -1;
+ }
+ 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) {
+ return -1;
+ }
+
+ /* Skip past the whole signature block */
+ for (i = 0; i < signatureCount; i++) {
+ /* Skip past the signature algorithm ID */
+ if (mar_buffer_seek(mar, mar_position, sizeof(uint32_t)) != 0) {
+ return -1;
+ }
+
+ /* Read the signature length and skip past the signature */
+ if (mar_read_buffer(mar, &signatureLen, mar_position, sizeof(uint32_t)) !=
+ 0) {
+ return -1;
+ }
+ signatureLen = ntohl(signatureLen);
+ if (mar_buffer_seek(mar, mar_position, signatureLen) != 0) {
+ return -1;
+ }
+ }
+
+ if (*mar_position <= (size_t)INT64_MAX &&
+ (int64_t)mar_position == (int64_t)offsetToContent) {
+ *hasAdditionalBlocks = 0;
+ } else {
+ if (numAdditionalBlocks) {
+ /* We have an additional block, so read in the number of additional blocks
+ and set the offset. */
+ *hasAdditionalBlocks = 1;
+ if (mar_read_buffer(mar, numAdditionalBlocks, mar_position,
+ sizeof(uint32_t)) != 0) {
+ return -1;
+ }
+ *numAdditionalBlocks = ntohl(*numAdditionalBlocks);
+ if (offsetAdditionalBlocks) {
+ if (*mar_position > (size_t)UINT32_MAX) {
+ return -1;
+ }
+ *offsetAdditionalBlocks = (uint32_t)*mar_position;
+ }
+ } else if (offsetAdditionalBlocks) {
+ /* numAdditionalBlocks is not specified but offsetAdditionalBlocks
+ is, so fill it! */
+ if (mar_buffer_seek(mar, mar_position, sizeof(uint32_t)) != 0) {
+ return -1;
+ }
+ if (*mar_position > (size_t)UINT32_MAX) {
+ return -1;
+ }
+ *offsetAdditionalBlocks = (uint32_t)*mar_position;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Reads the product info block from the MAR file's additional block section.
+ * The caller is responsible for freeing the fields in infoBlock
+ * if the return is successful.
+ *
+ * @param infoBlock Out parameter for where to store the result to
+ * @return 0 on success, -1 on failure
+ */
+int read_product_info_block(char* path,
+ struct ProductInformationBlock* infoBlock) {
+ int rv;
+ MarFile* mar;
+ MarReadResult result = mar_open(path, &mar);
+ if (result != MAR_READ_SUCCESS) {
+ fprintf(stderr,
+ "ERROR: could not open file in read_product_info_block()\n");
+ return -1;
+ }
+ rv = mar_read_product_info_block(mar, infoBlock);
+ mar_close(mar);
+ return rv;
+}
+
+/**
+ * Reads the product info block from the MAR file's additional block section.
+ * The caller is responsible for freeing the fields in infoBlock
+ * if the return is successful.
+ *
+ * @param infoBlock Out parameter for where to store the result to
+ * @return 0 on success, -1 on failure
+ */
+int mar_read_product_info_block(MarFile* mar,
+ struct ProductInformationBlock* infoBlock) {
+ uint32_t offsetAdditionalBlocks, numAdditionalBlocks, additionalBlockSize,
+ additionalBlockID;
+ int hasAdditionalBlocks;
+ size_t mar_position = 0;
+
+ /* The buffer size is 97 bytes because the MAR channel name < 64 bytes, and
+ product version < 32 bytes + 3 NULL terminator bytes. */
+ char buf[MAXADDITIONALBLOCKSIZE + 1] = {'\0'};
+ if (get_open_mar_file_info(mar, &mar_position, NULL, NULL,
+ &hasAdditionalBlocks, &offsetAdditionalBlocks,
+ &numAdditionalBlocks) != 0) {
+ return -1;
+ }
+
+ /* We only have the one additional block type and only one is expected to be
+ in a MAR file so check if any exist and process the first found */
+ if (numAdditionalBlocks > 0) {
+ /* Read the additional block size */
+ if (mar_read_buffer(mar, &additionalBlockSize, &mar_position,
+ sizeof(additionalBlockSize)) != 0) {
+ return -1;
+ }
+ additionalBlockSize = ntohl(additionalBlockSize) -
+ sizeof(additionalBlockSize) -
+ sizeof(additionalBlockID);
+
+ /* Additional Block sizes should only be 96 bytes long */
+ if (additionalBlockSize > MAXADDITIONALBLOCKSIZE) {
+ return -1;
+ }
+
+ /* Read the additional block ID */
+ if (mar_read_buffer(mar, &additionalBlockID, &mar_position,
+ sizeof(additionalBlockID)) != 0) {
+ return -1;
+ }
+ additionalBlockID = ntohl(additionalBlockID);
+
+ if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) {
+ const char* location;
+ int len;
+
+ if (mar_read_buffer(mar, buf, &mar_position, additionalBlockSize) != 0) {
+ return -1;
+ }
+
+ /* Extract the MAR channel name from the buffer. For now we
+ point to the stack allocated buffer but we strdup this
+ if we are within bounds of each field's max length. */
+ location = buf;
+ len = strlen(location);
+ infoBlock->MARChannelID = location;
+ location += len + 1;
+ if (len >= 64) {
+ infoBlock->MARChannelID = NULL;
+ return -1;
+ }
+
+ /* Extract the version from the buffer */
+ len = strlen(location);
+ infoBlock->productVersion = location;
+ if (len >= 32) {
+ infoBlock->MARChannelID = NULL;
+ infoBlock->productVersion = NULL;
+ return -1;
+ }
+ infoBlock->MARChannelID = strdup(infoBlock->MARChannelID);
+ infoBlock->productVersion = strdup(infoBlock->productVersion);
+ return 0;
+ } else {
+ /* This is not the additional block you're looking for. Move along. */
+ if (mar_buffer_seek(mar, &mar_position, additionalBlockSize) != 0) {
+ return -1;
+ }
+ }
+ }
+
+ /* If we had a product info block we would have already returned */
+ return -1;
+}
+
+const MarItem* mar_find_item(MarFile* mar, const char* name) {
+ uint32_t hash;
+ const MarItem* item;
+
+ if (!mar->item_table_is_valid) {
+ if (mar_read_index(mar)) {
+ return NULL;
+ } else {
+ mar->item_table_is_valid = 1;
+ }
+ }
+
+ hash = mar_hash_name(name);
+
+ item = mar->item_table[hash];
+ while (item && strcmp(item->name, name) != 0) {
+ item = item->next;
+ }
+
+ /* If this is the first time seeing this item's indexes, return it */
+ if (mar_insert_offset(mar, item->offset, item->length) == 1) {
+ return item;
+ } else {
+ fprintf(stderr, "ERROR: file content collision in mar_find_item()\n");
+ return NULL;
+ }
+}
+
+int mar_enum_items(MarFile* mar, MarItemCallback callback, void* closure) {
+ MarItem* item;
+ int i, rv;
+
+ if (!mar->item_table_is_valid) {
+ if (mar_read_index(mar)) {
+ return -1;
+ } else {
+ mar->item_table_is_valid = 1;
+ }
+ }
+
+ for (i = 0; i < TABLESIZE; ++i) {
+ item = mar->item_table[i];
+ while (item) {
+ /* if this is the first time seeing this item's indexes, process it */
+ if (mar_insert_offset(mar, item->offset, item->length) == 1) {
+ rv = callback(mar, item, closure);
+ if (rv) {
+ return rv;
+ }
+ } else {
+ fprintf(stderr, "ERROR: file content collision in mar_enum_items()\n");
+ return 1;
+ }
+ item = item->next;
+ }
+ }
+
+ return 0;
+}
+
+int mar_read(MarFile* mar, const MarItem* item, int offset, uint8_t* buf,
+ int bufsize) {
+ int nr;
+ size_t mar_position = 0;
+
+ if (offset == (int)item->length) {
+ return 0;
+ }
+ if (offset > (int)item->length) {
+ return -1;
+ }
+
+ nr = item->length - offset;
+ if (nr > bufsize) {
+ nr = bufsize;
+ }
+
+ // Avoid adding item->offset and offset directly, just in case of overflow.
+ if (mar_buffer_seek(mar, &mar_position, item->offset)) {
+ return -1;
+ }
+ if (mar_buffer_seek(mar, &mar_position, offset)) {
+ return -1;
+ }
+
+ return mar_read_buffer_max(mar, buf, &mar_position, nr);
+}
+
+/**
+ * Determines the MAR file information.
+ *
+ * @param path The path of the MAR file to check.
+ * @param hasSignatureBlock Optional out parameter specifying if the MAR
+ * file has a signature block or not.
+ * @param numSignatures Optional out parameter for storing the number
+ * of signatures in the MAR file.
+ * @param hasAdditionalBlocks Optional out parameter specifying if the MAR
+ * file has additional blocks or not.
+ * @param offsetAdditionalBlocks Optional out parameter for the offset to the
+ * first additional block. Value is only valid if
+ * hasAdditionalBlocks is not equal to 0.
+ * @param numAdditionalBlocks Optional out parameter for the number of
+ * additional blocks. Value is only valid if
+ * has_additional_blocks is not equal to 0.
+ * @return 0 on success and non-zero on failure.
+ */
+int get_mar_file_info(const char* path, int* hasSignatureBlock,
+ uint32_t* numSignatures, int* hasAdditionalBlocks,
+ uint32_t* offsetAdditionalBlocks,
+ uint32_t* numAdditionalBlocks) {
+ int rv;
+ MarFile* mar;
+ size_t mar_position = 0;
+ MarReadResult result = mar_open(path, &mar);
+ if (result != MAR_READ_SUCCESS) {
+ fprintf(stderr, "ERROR: could not read file in get_mar_file_info()\n");
+ return -1;
+ }
+
+ rv = get_open_mar_file_info(mar, &mar_position, hasSignatureBlock,
+ numSignatures, hasAdditionalBlocks,
+ offsetAdditionalBlocks, numAdditionalBlocks);
+
+ mar_close(mar);
+ return rv;
+}
diff --git a/modules/libmar/src/moz.build b/modules/libmar/src/moz.build
new file mode 100644
index 0000000000..5c40291e92
--- /dev/null
+++ b/modules/libmar/src/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ "mar.h",
+ "mar_cmdline.h",
+]
+
+HOST_SOURCES += [
+ "mar_create.c",
+ "mar_extract.c",
+ "mar_read.c",
+]
+HostLibrary("hostmar")
+
+# C11 for static_assert
+c11_flags = ["-std=gnu11"]
+if CONFIG["CC_TYPE"] == "clang-cl":
+ c11_flags.insert(0, "-Xclang")
+HOST_CFLAGS += c11_flags
+
+LOCAL_INCLUDES += [
+ "../../../other-licenses/nsis/Contrib/CityHash/cityhash",
+]
+
+if CONFIG["MOZ_BUILD_APP"] != "tools/update-packaging":
+ Library("mar")
+
+ UNIFIED_SOURCES += HOST_SOURCES
+
+ CFLAGS += c11_flags
+
+ FORCE_STATIC_LIB = True
+
+ if CONFIG["OS_ARCH"] == "WINNT":
+ USE_STATIC_LIBS = True
diff --git a/modules/libmar/tests/moz.build b/modules/libmar/tests/moz.build
new file mode 100644
index 0000000000..34e7677609
--- /dev/null
+++ b/modules/libmar/tests/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG["OS_TARGET"] != "Android" and CONFIG["COMPILE_ENVIRONMENT"]:
+ XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.toml"]
+
+ TEST_HARNESS_FILES.xpcshell.modules.libmar.tests.unit += [
+ "!/dist/bin/signmar%s" % CONFIG["BIN_SUFFIX"],
+ ]
diff --git a/modules/libmar/tests/unit/data/0_sized.mar b/modules/libmar/tests/unit/data/0_sized.mar
new file mode 100644
index 0000000000..357eeb9a87
--- /dev/null
+++ b/modules/libmar/tests/unit/data/0_sized.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/0_sized_file b/modules/libmar/tests/unit/data/0_sized_file
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/modules/libmar/tests/unit/data/0_sized_file
diff --git a/modules/libmar/tests/unit/data/1_byte.mar b/modules/libmar/tests/unit/data/1_byte.mar
new file mode 100644
index 0000000000..a137f11adc
--- /dev/null
+++ b/modules/libmar/tests/unit/data/1_byte.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/1_byte_file b/modules/libmar/tests/unit/data/1_byte_file
new file mode 100644
index 0000000000..56a6051ca2
--- /dev/null
+++ b/modules/libmar/tests/unit/data/1_byte_file
@@ -0,0 +1 @@
+1 \ No newline at end of file
diff --git a/modules/libmar/tests/unit/data/binary_data.mar b/modules/libmar/tests/unit/data/binary_data.mar
new file mode 100644
index 0000000000..7fef469898
--- /dev/null
+++ b/modules/libmar/tests/unit/data/binary_data.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/binary_data_file b/modules/libmar/tests/unit/data/binary_data_file
new file mode 100644
index 0000000000..a0d7369e45
--- /dev/null
+++ b/modules/libmar/tests/unit/data/binary_data_file
Binary files differ
diff --git a/modules/libmar/tests/unit/data/cert9.db b/modules/libmar/tests/unit/data/cert9.db
new file mode 100644
index 0000000000..e0d6191e64
--- /dev/null
+++ b/modules/libmar/tests/unit/data/cert9.db
Binary files differ
diff --git a/modules/libmar/tests/unit/data/key4.db b/modules/libmar/tests/unit/data/key4.db
new file mode 100644
index 0000000000..85c9c5a215
--- /dev/null
+++ b/modules/libmar/tests/unit/data/key4.db
Binary files differ
diff --git a/modules/libmar/tests/unit/data/manipulated_backend_collision.mar b/modules/libmar/tests/unit/data/manipulated_backend_collision.mar
new file mode 100644
index 0000000000..41d4f78482
--- /dev/null
+++ b/modules/libmar/tests/unit/data/manipulated_backend_collision.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/manipulated_frontend_collision.mar b/modules/libmar/tests/unit/data/manipulated_frontend_collision.mar
new file mode 100644
index 0000000000..582af58b59
--- /dev/null
+++ b/modules/libmar/tests/unit/data/manipulated_frontend_collision.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/manipulated_is_contained.mar b/modules/libmar/tests/unit/data/manipulated_is_contained.mar
new file mode 100644
index 0000000000..d51b23587d
--- /dev/null
+++ b/modules/libmar/tests/unit/data/manipulated_is_contained.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/manipulated_is_container.mar b/modules/libmar/tests/unit/data/manipulated_is_container.mar
new file mode 100644
index 0000000000..98b33ce9e5
--- /dev/null
+++ b/modules/libmar/tests/unit/data/manipulated_is_container.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/manipulated_multiple_collision.mar b/modules/libmar/tests/unit/data/manipulated_multiple_collision.mar
new file mode 100644
index 0000000000..7e0a3dd724
--- /dev/null
+++ b/modules/libmar/tests/unit/data/manipulated_multiple_collision.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/manipulated_multiple_collision_first.mar b/modules/libmar/tests/unit/data/manipulated_multiple_collision_first.mar
new file mode 100644
index 0000000000..a10d3eb53b
--- /dev/null
+++ b/modules/libmar/tests/unit/data/manipulated_multiple_collision_first.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/manipulated_multiple_collision_last.mar b/modules/libmar/tests/unit/data/manipulated_multiple_collision_last.mar
new file mode 100644
index 0000000000..bfbb9ba853
--- /dev/null
+++ b/modules/libmar/tests/unit/data/manipulated_multiple_collision_last.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/manipulated_same_offset.mar b/modules/libmar/tests/unit/data/manipulated_same_offset.mar
new file mode 100644
index 0000000000..1326d1afd8
--- /dev/null
+++ b/modules/libmar/tests/unit/data/manipulated_same_offset.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/manipulated_signed.mar b/modules/libmar/tests/unit/data/manipulated_signed.mar
new file mode 100644
index 0000000000..df8b3b5dbb
--- /dev/null
+++ b/modules/libmar/tests/unit/data/manipulated_signed.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/multiple_file.mar b/modules/libmar/tests/unit/data/multiple_file.mar
new file mode 100644
index 0000000000..183493a368
--- /dev/null
+++ b/modules/libmar/tests/unit/data/multiple_file.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/multiple_signed_no_pib.mar b/modules/libmar/tests/unit/data/multiple_signed_no_pib.mar
new file mode 100644
index 0000000000..fb56eef98e
--- /dev/null
+++ b/modules/libmar/tests/unit/data/multiple_signed_no_pib.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/multiple_signed_pib.mar b/modules/libmar/tests/unit/data/multiple_signed_pib.mar
new file mode 100644
index 0000000000..3624436cf5
--- /dev/null
+++ b/modules/libmar/tests/unit/data/multiple_signed_pib.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/multiple_signed_pib_2.mar b/modules/libmar/tests/unit/data/multiple_signed_pib_2.mar
new file mode 100644
index 0000000000..edce42b854
--- /dev/null
+++ b/modules/libmar/tests/unit/data/multiple_signed_pib_2.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/multiple_signed_pib_mar.sig.0 b/modules/libmar/tests/unit/data/multiple_signed_pib_mar.sig.0
new file mode 100644
index 0000000000..fa75b9f231
--- /dev/null
+++ b/modules/libmar/tests/unit/data/multiple_signed_pib_mar.sig.0
@@ -0,0 +1,11 @@
+biW+t1VP/UUp/B+xnQNKRDib3r4ZYP/HX/O5ZVPaTalCIZJfeGjoGK8TAlNUQUPZ
+rHK/SZqr7rzsWlqb6rAGBWphnaZ3202luiaCBtb1Vi/MS7tPIssC6m2gvy5rwhMA
+xaSJr9Qyoj6AdMbBryG7ZEuV/HPw7CAs1djbHSa0KUsL8Xj1c5kk/zFgh3EEM7ap
+WBlfNRD7wggiWA4o/58gSWgjjqsFuiI5DH7cL3t3AisdBVAEI4A7h0tvX/9P5ipu
+0kthhZyg0lR6denEjQ8RMxTuLoa3KCuhrUC10oUb+ZhpdDEPCL6Dt2eb8FeB3rGd
+dzVzu2WVkBYU4JmDjOanGkOv8hSb8Efi7iZe4+9zEqqgymtCy0w4zzAYToTuaHK8
+1ryrtIzCt5AhGpcX7IJfC1Lc1TOzLa6/gigxj60wdxdWuP4d6tvraJMvSX47oIVM
+gB6Gu+G3LEPGS4Vcqun+G9sn2ee+4L2E/ZK4nh9kzAkpNR5COrzG6FxhuocIfpWa
+8F/sh3hZPJuuCopqd0lUfrkvwPe1hMd7RjMgPVTT0Mkol3YliMAW+kvUqrXZ/o6A
+6rLt2+t7V/FbYY1EJNhjISTlUYyWERPhTO6qMRbqUzNBd5/Dh6mRI7hxIf5KEQdu
+ZvJ8ORomD++TlXQsqcYCuYNZ5EM0npe/UfHU7JYuHo0= \ No newline at end of file
diff --git a/modules/libmar/tests/unit/data/multiple_signed_pib_mar.sig.1 b/modules/libmar/tests/unit/data/multiple_signed_pib_mar.sig.1
new file mode 100644
index 0000000000..3ab4cef5f4
--- /dev/null
+++ b/modules/libmar/tests/unit/data/multiple_signed_pib_mar.sig.1
@@ -0,0 +1,11 @@
+UtzJoTa2NS5uBfNTCeVVEC6R8Iaad8vq/KNJwlYsn6V+20mp/vBJIXh+hmHSGNEA
+ynpbqD1KLgzPMSsBbF2Azc6eT0frVDdPlKc5FyhpkB9YniOSpt+oWiU8M7UMsVhG
+/+9ClLLXImVeq3oySxGodpMh04sYrzBUR136cdk6mb+dAT3523xKRXZM72+WlQFQ
+G1rfzGTEWWFJCIwDJisauYbQlipvl6mfZttdQ2a7hgVbAJmFbm7nUsYr7t1ezP0A
+Jy1WIrZY4l8OzoL0TZ3aM1bC2vFYxv+SuH6E51MdVt/mLc7JSGzVmqdP0C58xNSz
+zmkfYwj5fWh6jRa6XQAl7Au3jujdVPV22bSdZV05RlypgLQHZmlvi+yRd8OCPJZY
+NLU8K3xZQP4sGr5vePtUqoVslsMtkUh/LUSTAAmFF8qPotxEzMb1LFYokPH37e1R
+8EwZbyp4wXOy7KYx2rB90J+4PoGPYIUe8xERHGDmrCt9G+siFB6OOSQEQEj5XfLw
+MkJSI9K3ldMtzIiDFKikmSpkCyeBFmEQrb6/zgl1qUBcE52yPkrybZdtvwseGrhU
+43ZsKX1/1FSj8MOkXuCFTMLMFRDpXuGvLTNPy1DPA3nsa0XoYFzp8Sg4pjJd4KUL
+mhYiy+v/27LfonFX0ak9+HlANsV96ixf3mrkVz7Tfc4= \ No newline at end of file
diff --git a/modules/libmar/tests/unit/data/multiple_signed_pib_mar.sig.2 b/modules/libmar/tests/unit/data/multiple_signed_pib_mar.sig.2
new file mode 100644
index 0000000000..974a425137
--- /dev/null
+++ b/modules/libmar/tests/unit/data/multiple_signed_pib_mar.sig.2
@@ -0,0 +1,11 @@
+F6F1LmabkmheGolLdYlWkaSnM782EoJnZAiDYOszsetXWltFLCd/SrfKaAABBUJZ
+jPS5EeEAFtNK6INCs/ULujohnQwhiMWwcLm7f5CGEaC7UzKB4tP5ZvnBASTDhAMj
+BipU2798XYyA5+LmWL81S+LZKEhGXdPsIC4GIqlY7hA0DKgsS9hWxC7VfuZb3r8T
+Mp//aOM79+DnPBHJYKBzoDLO/F/GlH0Hr7Gih/nOkiHTvQgZ3PDAxwhGgwLAsRA0
++LmrQ3MlHrLfT9SrPZq3kUtX/JIvYfAaUE8cV941a7cvfVY1RPjznJBk2lYowNys
+GhjoS9OpRBqdMVfY4dHZoCEgc07siDXhTCw4J9tPAjqOdsqwgsuoLhTEcIYeWnR7
+otYnFITcDvRRRkP98SRPjKquemvcYgeyX7vHomN8V+GhTlCn8NF4ModE1Ny7whiu
+V/+dwFH3S3Phg2wo2THfU1igUzZUJ5LhiO486NVa7n3wLZqpAMnsvDujqoL/8tWz
+ZQ5o/RF6XJHA0UKXxt3UiGZHDGuS3I6clZJa1PXgSfEHZyQmQ24bl2fmDURdLUbt
++t//Y5PZTktwjxIELbGYvVXoJYYHW7Jr5PlxvUxqq7WRHwOd9nL4Bs8gqr64I9sB
+8yMnjffnl5ZL53pS6SjIzPgymYNOYN1KpDuNoSBuXN8= \ No newline at end of file
diff --git a/modules/libmar/tests/unit/data/mycert.der b/modules/libmar/tests/unit/data/mycert.der
new file mode 100644
index 0000000000..ea1fd47faa
--- /dev/null
+++ b/modules/libmar/tests/unit/data/mycert.der
Binary files differ
diff --git a/modules/libmar/tests/unit/data/mycert2.der b/modules/libmar/tests/unit/data/mycert2.der
new file mode 100644
index 0000000000..d8cdfea972
--- /dev/null
+++ b/modules/libmar/tests/unit/data/mycert2.der
Binary files differ
diff --git a/modules/libmar/tests/unit/data/mycert3.der b/modules/libmar/tests/unit/data/mycert3.der
new file mode 100644
index 0000000000..b942d4d795
--- /dev/null
+++ b/modules/libmar/tests/unit/data/mycert3.der
Binary files differ
diff --git a/modules/libmar/tests/unit/data/no_pib.mar b/modules/libmar/tests/unit/data/no_pib.mar
new file mode 100644
index 0000000000..8976e7d737
--- /dev/null
+++ b/modules/libmar/tests/unit/data/no_pib.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/signed_no_pib.mar b/modules/libmar/tests/unit/data/signed_no_pib.mar
new file mode 100644
index 0000000000..92d97fec51
--- /dev/null
+++ b/modules/libmar/tests/unit/data/signed_no_pib.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/signed_pib.mar b/modules/libmar/tests/unit/data/signed_pib.mar
new file mode 100644
index 0000000000..1b8baa7969
--- /dev/null
+++ b/modules/libmar/tests/unit/data/signed_pib.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/data/signed_pib_mar.signature.0 b/modules/libmar/tests/unit/data/signed_pib_mar.signature.0
new file mode 100644
index 0000000000..d597fa5491
--- /dev/null
+++ b/modules/libmar/tests/unit/data/signed_pib_mar.signature.0
@@ -0,0 +1,11 @@
+Qv7nfMB5+ri3errM8NqkCl7LwWFHu3DXBFAHaw3Rl27hGyZw4xR+oKbQMkwvdrY7
+GxWZ0vBNSI4nte+Ii6XjDQcnzQgqINEZkL5EVMs1re1zA8OzuItxtXWeCoAPGgMg
+uhvPVWxMCUMia1yCWpVKwA9CKfsX+0+5jbSLeMg43q6Zoj+AVanCZZlQiH2nuBPL
+YB9hib9GlK4kjS1EqFmYuA8oDQiWDmWK1ULAmFUy9Ezho4il21rG4FQJYywLTQSr
+8PiSTKVH+8LZAvPNEzhG3UGOVsj85w2TzmLbvSiYFJCMx8NQLHbB+cmt+1ytenaK
+Qt+b2PC5Hs8LczOxTxpzgtAZPQbx2jqCxCeFOZGzY7Gfz4oi4YdqY+d4n854wwIb
+rxX7asiYwhOHoJ5nxKoqN1gsRyshSqYDH9+TwXJPrk1S8i5sIBMQpycr+f+1MPGx
+RsoorG3sKUfC+dXi3QqZEnBc+ULqIOrmW9J3TTUsxNpOkm03NvPQZyeF1wpU4jfz
+W1Hnu7nltdqDX1bdmCChZ3rP+7JJYwfvVtmBseIFGZHVMhqvzvvdlxLn+MeTDiAA
+Zrhi/BNkQstX7Bdqe58adzRAv/O80DH8ErKBqvqh17HXwMmGx6i+EGyNt8DfFLSm
+xEzxnsmFnOaBytzpth4pltEEpioQPS7/CSdRydi/29w= \ No newline at end of file
diff --git a/modules/libmar/tests/unit/data/signed_pib_mar.signature.mycert2 b/modules/libmar/tests/unit/data/signed_pib_mar.signature.mycert2
new file mode 100644
index 0000000000..045fc80be1
--- /dev/null
+++ b/modules/libmar/tests/unit/data/signed_pib_mar.signature.mycert2
@@ -0,0 +1,11 @@
+i6oFIDMnyZ5CUaYUCg8MEL48puCQdZMH9s2ZoGKzxK4YO6a/2Yhur4jNRfoxgQm3
+2o4qO4gUCjcwZmQHoSmseELJWP+6I929SZ4KUc/bXsIOMlZLcq+YSQSCbmkM/AeV
+NMW4SR8eVtU0BjstZafaWtvCp0nzYXwyDLUUKl006CzylCjGDO2yNC3GGTc6N0cC
+I1nzDOTNYknuHLJLhjJaJEd+c/J5g3BsDXi3Bh7ZtO1OkU/x/jhxbPfK2YsyHpUm
+8/4BKDy6ocV2zrDXuE4ZBPJXsOGshr3kZLAkrhUbGK14EEFx+PtCRLigfWlGIWd1
+ZdYv+0r+JaOdskArdcHCtfBF6IOnQLB1UjD2NsyhMnKPPm7KO26A+ig8DxlTyt4N
+sop0UryhQxHhh/iTkIJlMN1JONr39EG66pI/jo2HwArNL/sfqZ1m9GR+tDKKtMYm
+gFn0nxQiIgquYA2Q3sKdgtcHvQGxxvyKOa3lelykjny/4RCwsfM1S1KwG9TpPHW4
+5VUoztOuIsSpAwdc+gzNfzvqCi0ac0bUF66ksZ2qlKpG0VRq0O9Rdtv9FClbVUWM
+PRlmUuRdXkn6ouCx58dwHoABXr910GdqV5EoNXNyDG/Mnqu0eYSGDRQEjVvh5v+u
+FSbVjBI+ie7oTFeInJ7eWgJ7/XTMtHsCAw+RHN1drFo= \ No newline at end of file
diff --git a/modules/libmar/tests/unit/data/signed_pib_with_mycert2.mar b/modules/libmar/tests/unit/data/signed_pib_with_mycert2.mar
new file mode 100644
index 0000000000..22a998e227
--- /dev/null
+++ b/modules/libmar/tests/unit/data/signed_pib_with_mycert2.mar
Binary files differ
diff --git a/modules/libmar/tests/unit/head_libmar.js b/modules/libmar/tests/unit/head_libmar.js
new file mode 100644
index 0000000000..49ce2e8ba6
--- /dev/null
+++ b/modules/libmar/tests/unit/head_libmar.js
@@ -0,0 +1,162 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BIN_SUFFIX = mozinfo.bin_suffix;
+const tempDir = do_get_tempdir();
+
+/**
+ * Compares binary data of 2 arrays and throws if they aren't the same.
+ * Throws on mismatch, does nothing on match.
+ *
+ * @param arr1 The first array to compare
+ * @param arr2 The second array to compare
+ */
+function compareBinaryData(arr1, arr2) {
+ Assert.equal(arr1.length, arr2.length);
+ for (let i = 0; i < arr1.length; i++) {
+ if (arr1[i] != arr2[i]) {
+ throw new Error(
+ `Data differs at index ${i}, arr1: ${arr1[i]}, arr2: ${arr2[i]}`
+ );
+ }
+ }
+}
+
+/**
+ * Reads a file's data and returns it
+ *
+ * @param file The file to read the data from
+ * @return a byte array for the data in the file.
+ */
+function getBinaryFileData(file) {
+ let fileStream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ // Open as RD_ONLY with default permissions.
+ fileStream.init(file, -1, -1, null);
+
+ // Check the returned size versus the expected size.
+ let stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIBinaryInputStream
+ );
+ stream.setInputStream(fileStream);
+ let bytes = stream.readByteArray(stream.available());
+ fileStream.close();
+ return bytes;
+}
+
+/**
+ * Runs each method in the passed in object
+ * Every method of the passed in object that starts with test_ will be ran
+ * The cleanup_per_test method of the object will be run right away, it will be
+ * registered to be the cleanup function, and it will be run between each test.
+ *
+ * @return The number of tests ran
+ */
+function run_tests(obj) {
+ let cleanup_per_test = obj.cleanup_per_test;
+ if (cleanup_per_test === undefined) {
+ cleanup_per_test = function __cleanup_per_test() {};
+ }
+
+ registerCleanupFunction(cleanup_per_test);
+
+ // Make sure there's nothing left over from a preious failed test
+ cleanup_per_test();
+
+ let ranCount = 0;
+ // hasOwnProperty ensures we only see direct properties and not all
+ for (let f in obj) {
+ if (
+ typeof obj[f] === "function" &&
+ obj.hasOwnProperty(f) &&
+ f.toString().indexOf("test_") === 0
+ ) {
+ obj[f]();
+ cleanup_per_test();
+ ranCount++;
+ }
+ }
+ return ranCount;
+}
+
+/**
+ * Creates a MAR file with the content of files.
+ *
+ * @param outMAR The file where the MAR should be created to
+ * @param dataDir The directory where the relative file paths exist
+ * @param files The relative file paths of the files to include in the MAR
+ */
+function createMAR(outMAR, dataDir, files) {
+ // You cannot create an empy MAR.
+ Assert.ok(!!files.length);
+
+ // Get an nsIProcess to the signmar binary.
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ let signmarBin = do_get_file("signmar" + BIN_SUFFIX);
+
+ // Make sure the signmar binary exists and is an executable.
+ Assert.ok(signmarBin.exists());
+ Assert.ok(signmarBin.isExecutable());
+
+ // Ensure on non Windows platforms we encode the same permissions
+ // as the refernence MARs contain. On Windows this is also safe.
+ // The reference MAR files have permissions of 0o664, so in case
+ // someone is running these tests locally with another permission
+ // (perhaps 0o777), make sure that we encode them as 0o664.
+ for (let filePath of files) {
+ let f = dataDir.clone();
+ f.append(filePath);
+ f.permissions = 0o664;
+ }
+
+ // Setup the command line arguments to create the MAR.
+ let args = [
+ "-C",
+ dataDir.path,
+ "-H",
+ "@MAR_CHANNEL_ID@",
+ "-V",
+ "13.0a1",
+ "-c",
+ outMAR.path,
+ ];
+ args = args.concat(files);
+
+ info("Running: " + signmarBin.path + " " + args.join(" "));
+ process.init(signmarBin);
+ process.run(true, args, args.length);
+
+ // Verify signmar returned 0 for success.
+ Assert.equal(process.exitValue, 0);
+
+ // Verify the out MAR file actually exists.
+ Assert.ok(outMAR.exists());
+}
+
+/**
+ * Extracts a MAR file to the specified output directory.
+ *
+ * @param mar The MAR file that should be matched
+ * @param dataDir The directory to extract to
+ */
+function extractMAR(mar, dataDir) {
+ // Get an nsIProcess to the signmar binary.
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ let signmarBin = do_get_file("signmar" + BIN_SUFFIX);
+
+ // Make sure the signmar binary exists and is an executable.
+ Assert.ok(signmarBin.exists());
+ Assert.ok(signmarBin.isExecutable());
+
+ // Setup the command line arguments to extract the MAR.
+ let args = ["-C", dataDir.path, "-x", mar.path];
+
+ info("Running: " + signmarBin.path + " " + args.join(" "));
+ process.init(signmarBin);
+ process.run(true, args, args.length);
+
+ return process.exitValue;
+}
diff --git a/modules/libmar/tests/unit/test_create.js b/modules/libmar/tests/unit/test_create.js
new file mode 100644
index 0000000000..224364b419
--- /dev/null
+++ b/modules/libmar/tests/unit/test_create.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ /**
+ * Creates MAR from the passed files, compares it to the reference MAR.
+ *
+ * @param refMARFileName The name of the MAR file that should match
+ * @param files The files that should go in the created MAR
+ * @param checkNoMAR If true return an error if a file already exists
+ */
+ function run_one_test(refMARFileName, files, checkNoMAR) {
+ if (checkNoMAR === undefined) {
+ checkNoMAR = true;
+ }
+
+ // Ensure the MAR we will create doesn't already exist.
+ let outMAR = tempDir.clone();
+ outMAR.append("out.mar");
+ if (checkNoMAR) {
+ Assert.ok(!outMAR.exists());
+ }
+
+ // Create the actual MAR file.
+ createMAR(outMAR, do_get_file("data"), files);
+
+ // Get the reference MAR data.
+ let refMAR = do_get_file("data/" + refMARFileName);
+ let refMARData = getBinaryFileData(refMAR);
+
+ // Verify the data of the MAR is what it should be.
+ let outMARData = getBinaryFileData(outMAR);
+ if (mozinfo.os != "win") {
+ // Modify the array index that contains the file permission in this mar so
+ // the comparison succeeds. This value is only changed when the value is
+ // the expected value on non-Windows platforms since the MAR files are
+ // created on Windows. This makes it possible to use the same MAR files for
+ // all platforms.
+ switch (refMARFileName) {
+ case "0_sized.mar":
+ if (outMARData[143] == 180) {
+ outMARData[143] = 182;
+ }
+ break;
+ case "1_byte.mar":
+ if (outMARData[144] == 180) {
+ outMARData[144] = 182;
+ }
+ break;
+ case "binary_data.mar":
+ if (outMARData[655] == 180) {
+ outMARData[655] = 182;
+ }
+ break;
+ case "multiple_file.mar":
+ if (outMARData[656] == 180) {
+ outMARData[656] = 182;
+ }
+ if (outMARData[681] == 180) {
+ outMARData[681] = 182;
+ }
+ if (outMARData[705] == 180) {
+ outMARData[705] = 182;
+ }
+ }
+ }
+ compareBinaryData(outMARData, refMARData);
+ }
+
+ // Define the unit tests to run.
+ let tests = {
+ // Test creating a MAR file with a 0 byte file.
+ test_zero_sized: function _test_zero_sized() {
+ return run_one_test("0_sized.mar", ["0_sized_file"]);
+ },
+ // Test creating a MAR file with a 1 byte file.
+ test_one_byte: function _test_one_byte() {
+ return run_one_test("1_byte.mar", ["1_byte_file"]);
+ },
+ // Test creating a MAR file with binary data.
+ test_binary_data: function _test_binary_data() {
+ return run_one_test("binary_data.mar", ["binary_data_file"]);
+ },
+ // Test creating a MAR file with multiple files inside of it.
+ test_multiple_file: function _test_multiple_file() {
+ return run_one_test("multiple_file.mar", [
+ "0_sized_file",
+ "1_byte_file",
+ "binary_data_file",
+ ]);
+ },
+ // Test creating a MAR file on top of a different one that already exists
+ // at the location the new one will be created at.
+ test_overwrite_already_exists: function _test_overwrite_already_exists() {
+ let differentFile = do_get_file("data/1_byte.mar");
+ let outMARDir = tempDir.clone();
+ differentFile.copyTo(outMARDir, "out.mar");
+ return run_one_test("binary_data.mar", ["binary_data_file"], false);
+ },
+ // Between each test make sure the out MAR does not exist.
+ cleanup_per_test: function _cleanup_per_test() {
+ let outMAR = tempDir.clone();
+ outMAR.append("out.mar");
+ if (outMAR.exists()) {
+ outMAR.remove(false);
+ }
+ },
+ };
+
+ // Run all the tests
+ Assert.equal(run_tests(tests), Object.keys(tests).length - 1);
+}
diff --git a/modules/libmar/tests/unit/test_extract.js b/modules/libmar/tests/unit/test_extract.js
new file mode 100644
index 0000000000..46cbbcbbee
--- /dev/null
+++ b/modules/libmar/tests/unit/test_extract.js
@@ -0,0 +1,147 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ /**
+ * Extracts a MAR and makes sure each file matches the reference files.
+ *
+ * @param marFileName The name of the MAR file to extract
+ * @param files The files that the extracted MAR should contain
+ */
+ function extract_and_compare(marFileName, files) {
+ // Get the MAR file that we will be extracting
+ let mar = do_get_file("data/" + marFileName);
+
+ // Get the path that we will extract to
+ let outDir = tempDir.clone();
+ outDir.append("out");
+ Assert.ok(!outDir.exists());
+ outDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o777);
+
+ // Get the ref files and the files that will be extracted.
+ let outFiles = [];
+ let refFiles = [];
+ for (let i = 0; i < files.length; i++) {
+ let outFile = outDir.clone();
+ outFile.append(files[i]);
+ Assert.ok(!outFile.exists());
+
+ outFiles.push(outFile);
+ refFiles.push(do_get_file("data/" + files[i]));
+ }
+
+ // Extract the MAR contents to ./out dir and verify 0 for success.
+ Assert.equal(extractMAR(mar, outDir), 0);
+
+ // Compare to make sure the extracted files are the same.
+ for (let i = 0; i < files.length; i++) {
+ Assert.ok(outFiles[i].exists());
+ let refFileData = getBinaryFileData(refFiles[i]);
+ let outFileData = getBinaryFileData(outFiles[i]);
+ compareBinaryData(refFileData, outFileData);
+ }
+ }
+
+ /**
+ * Attempts to extract a MAR and expects a failure
+ *
+ * @param marFileName The name of the MAR file to extract
+ */
+ function extract_and_fail(marFileName) {
+ // Get the MAR file that we will be extracting
+ let mar = do_get_file("data/" + marFileName);
+
+ // Get the path that we will extract to
+ let outDir = tempDir.clone();
+ outDir.append("out");
+ Assert.ok(!outDir.exists());
+ outDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o777);
+
+ // Extract the MAR contents to ./out dir and verify -1 (255 from the
+ // nsIprocess) for failure
+ Assert.equal(extractMAR(mar, outDir), 1);
+ }
+
+ // Define the unit tests to run.
+ let tests = {
+ // Test extracting a MAR file with a 0 byte file.
+ test_zero_sized: function _test_zero_sized() {
+ return extract_and_compare("0_sized.mar", ["0_sized_file"]);
+ },
+ // Test extracting a MAR file with a 1 byte file.
+ test_one_byte: function _test_one_byte() {
+ return extract_and_compare("1_byte.mar", ["1_byte_file"]);
+ },
+ // Test extracting a MAR file with binary data.
+ test_binary_data: function _test_binary_data() {
+ return extract_and_compare("binary_data.mar", ["binary_data_file"]);
+ },
+ // Test extracting a MAR without a product information block (PIB) which
+ // contains binary data.
+ test_no_pib: function _test_no_pib() {
+ return extract_and_compare("no_pib.mar", ["binary_data_file"]);
+ },
+ // Test extracting a MAR without a product information block (PIB) that is
+ // signed and which contains binary data.
+ test_no_pib_signed: function _test_no_pib_signed() {
+ return extract_and_compare("signed_no_pib.mar", ["binary_data_file"]);
+ },
+ // Test extracting a MAR with a product information block (PIB) that is
+ // signed and which contains binary data.
+ test_pib_signed: function _test_pib_signed() {
+ return extract_and_compare("signed_pib.mar", ["binary_data_file"]);
+ },
+ // Test extracting a MAR file with multiple files inside of it.
+ test_multiple_file: function _test_multiple_file() {
+ return extract_and_compare("multiple_file.mar", [
+ "0_sized_file",
+ "1_byte_file",
+ "binary_data_file",
+ ]);
+ },
+ // Test collision detection where file A + B are the same offset
+ test_collision_same_offset: function test_collision_same_offset() {
+ return extract_and_fail("manipulated_same_offset.mar");
+ },
+ // Test collision detection where file A's indexes are a subset of file B's
+ test_collision_is_contained: function test_collision_is_contained() {
+ return extract_and_fail("manipulated_is_container.mar");
+ },
+ // Test collision detection where file B's indexes are a subset of file A's
+ test_collision_contained_by: function test_collision_contained_by() {
+ return extract_and_fail("manipulated_is_contained.mar");
+ },
+ // Test collision detection where file A ends in file B's indexes
+ test_collision_a_onto_b: function test_collision_a_onto_b() {
+ return extract_and_fail("manipulated_frontend_collision.mar");
+ },
+ // Test collision detection where file B ends in file A's indexes
+ test_collsion_b_onto_a: function test_collsion_b_onto_a() {
+ return extract_and_fail("manipulated_backend_collision.mar");
+ },
+ // Test collision detection where file C shares indexes with both file A & B
+ test_collision_multiple: function test_collision_multiple() {
+ return extract_and_fail("manipulated_multiple_collision.mar");
+ },
+ // Test collision detection where A is the last file in the list
+ test_collision_last: function test_collision_multiple_last() {
+ return extract_and_fail("manipulated_multiple_collision_last.mar");
+ },
+ // Test collision detection where A is the first file in the list
+ test_collision_first: function test_collision_multiple_first() {
+ return extract_and_fail("manipulated_multiple_collision_first.mar");
+ },
+ // Between each test make sure the out directory and its subfiles do
+ // not exist.
+ cleanup_per_test: function _cleanup_per_test() {
+ let outDir = tempDir.clone();
+ outDir.append("out");
+ if (outDir.exists()) {
+ outDir.remove(true);
+ }
+ },
+ };
+
+ // Run all the tests
+ Assert.equal(run_tests(tests), Object.keys(tests).length - 1);
+}
diff --git a/modules/libmar/tests/unit/test_sign_verify.js b/modules/libmar/tests/unit/test_sign_verify.js
new file mode 100644
index 0000000000..20f0d691ac
--- /dev/null
+++ b/modules/libmar/tests/unit/test_sign_verify.js
@@ -0,0 +1,591 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ /**
+ * Signs a MAR file.
+ *
+ * @param inMAR The MAR file that should be signed
+ * @param outMAR The MAR file to create
+ */
+ function signMAR(inMAR, outMAR, certs, wantSuccess, useShortHandCmdLine) {
+ // Get a process to the signmar binary from the dist/bin directory.
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(
+ Ci.nsIProcess
+ );
+ let signmarBin = do_get_file("signmar" + BIN_SUFFIX);
+
+ // Make sure the signmar binary exists and is an executable.
+ Assert.ok(signmarBin.exists());
+ Assert.ok(signmarBin.isExecutable());
+
+ // Setup the command line arguments to sign the MAR.
+ let NSSConfigDir = do_get_file("data");
+ let args = ["-d", NSSConfigDir.path];
+ if (certs.length == 1 && useShortHandCmdLine) {
+ args.push("-n", certs[0]);
+ } else {
+ for (let i = 0; i < certs.length; i++) {
+ args.push("-n" + i, certs[i]);
+ }
+ }
+ args.push("-s", inMAR.path, outMAR.path);
+
+ let exitValue;
+ process.init(signmarBin);
+ try {
+ process.run(true, args, args.length);
+ exitValue = process.exitValue;
+ } catch (e) {
+ // On Windows negative return value throws an exception
+ exitValue = -1;
+ }
+
+ // Verify signmar returned 0 for success.
+ if (wantSuccess) {
+ Assert.equal(exitValue, 0);
+ } else {
+ Assert.notEqual(exitValue, 0);
+ }
+ }
+
+ /**
+ * Extract a MAR signature.
+ *
+ * @param inMAR The MAR file who's signature should be extracted
+ * @param sigIndex The index of the signature to extract
+ * @param extractedSig The file where the extracted signature will be stored
+ * @param wantSuccess True if a successful signmar return code is desired
+ */
+ function extractMARSignature(inMAR, sigIndex, extractedSig, wantSuccess) {
+ // Get a process to the signmar binary from the dist/bin directory.
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(
+ Ci.nsIProcess
+ );
+ let signmarBin = do_get_file("signmar" + BIN_SUFFIX);
+
+ // Make sure the signmar binary exists and is an executable.
+ Assert.ok(signmarBin.exists());
+ Assert.ok(signmarBin.isExecutable());
+
+ // Setup the command line arguments to extract the signature in the MAR.
+ let args = ["-n" + sigIndex, "-X", inMAR.path, extractedSig.path];
+
+ let exitValue;
+ process.init(signmarBin);
+ try {
+ process.run(true, args, args.length);
+ exitValue = process.exitValue;
+ } catch (e) {
+ // On Windows negative return value throws an exception
+ exitValue = -1;
+ }
+
+ // Verify signmar returned 0 for success.
+ if (wantSuccess) {
+ Assert.equal(exitValue, 0);
+ } else {
+ Assert.notEqual(exitValue, 0);
+ }
+ }
+
+ /**
+ * Import a MAR signature.
+ *
+ * @param inMAR The MAR file who's signature should be imported to
+ * @param sigIndex The index of the signature to import to
+ * @param sigFile The file where the base64 signature exists
+ * @param outMAR The same as inMAR but with the specified signature
+ * swapped at the specified index.
+ * @param wantSuccess True if a successful signmar return code is desired
+ */
+ function importMARSignature(inMAR, sigIndex, sigFile, outMAR, wantSuccess) {
+ // Get a process to the signmar binary from the dist/bin directory.
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(
+ Ci.nsIProcess
+ );
+ let signmarBin = do_get_file("signmar" + BIN_SUFFIX);
+
+ // Make sure the signmar binary exists and is an executable.
+ Assert.ok(signmarBin.exists());
+ Assert.ok(signmarBin.isExecutable());
+
+ // Setup the command line arguments to import the signature in the MAR.
+ let args = ["-n" + sigIndex, "-I", inMAR.path, sigFile.path, outMAR.path];
+
+ let exitValue;
+ process.init(signmarBin);
+ try {
+ process.run(true, args, args.length);
+ exitValue = process.exitValue;
+ } catch (e) {
+ // On Windows negative return value throws an exception
+ exitValue = -1;
+ }
+
+ // Verify signmar returned 0 for success.
+ if (wantSuccess) {
+ Assert.equal(exitValue, 0);
+ } else {
+ Assert.notEqual(exitValue, 0);
+ }
+ }
+
+ /**
+ * Verifies a MAR file.
+ *
+ * @param signedMAR Verifies a MAR file
+ */
+ function verifyMAR(signedMAR, wantSuccess, certs, useShortHandCmdLine) {
+ // Get a process to the signmar binary from the dist/bin directory.
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(
+ Ci.nsIProcess
+ );
+ let signmarBin = do_get_file("signmar" + BIN_SUFFIX);
+
+ // Make sure the signmar binary exists and is an executable.
+ Assert.ok(signmarBin.exists());
+ Assert.ok(signmarBin.isExecutable());
+
+ // Will reference the arguments to use for verification in signmar
+ let args = [];
+
+ // Setup the command line arguments to create the MAR.
+ // Windows & Mac vs. Linux/... have different command line for verification
+ // since on Windows we verify with CryptoAPI, on Mac with Security
+ // Transforms or CDSA/CSSM and on all other platforms we verify with NSS. So
+ // on Windows and Mac we use an exported DER file and on other platforms we
+ // use the NSS config db.
+ if (mozinfo.os == "win" || mozinfo.os == "mac") {
+ if (certs.length == 1 && useShortHandCmdLine) {
+ args.push("-D", "data/" + certs[0] + ".der");
+ } else {
+ for (let i = 0; i < certs.length; i++) {
+ args.push("-D" + i, "data/" + certs[i] + ".der");
+ }
+ }
+ } else {
+ let NSSConfigDir = do_get_file("data");
+ args = ["-d", NSSConfigDir.path];
+ if (certs.length == 1 && useShortHandCmdLine) {
+ args.push("-n", certs[0]);
+ } else {
+ for (let i = 0; i < certs.length; i++) {
+ args.push("-n" + i, certs[i]);
+ }
+ }
+ }
+ args.push("-v", signedMAR.path);
+
+ let exitValue;
+ process.init(signmarBin);
+ try {
+ // We put this in a try block because nsIProcess doesn't like -1 returns
+ process.run(true, args, args.length);
+ exitValue = process.exitValue;
+ } catch (e) {
+ // On Windows negative return value throws an exception
+ exitValue = -1;
+ }
+
+ // Verify signmar returned 0 for success.
+ if (wantSuccess) {
+ Assert.equal(exitValue, 0);
+ } else {
+ Assert.notEqual(exitValue, 0);
+ }
+ }
+
+ /**
+ * Strips a MAR signature.
+ *
+ * @param signedMAR The MAR file that should be signed
+ * @param outMAR The MAR file to write to with signature stripped
+ */
+ function stripMARSignature(signedMAR, outMAR, wantSuccess) {
+ // Get a process to the signmar binary from the dist/bin directory.
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(
+ Ci.nsIProcess
+ );
+ let signmarBin = do_get_file("signmar" + BIN_SUFFIX);
+
+ // Make sure the signmar binary exists and is an executable.
+ Assert.ok(signmarBin.exists());
+ Assert.ok(signmarBin.isExecutable());
+
+ // Setup the command line arguments to create the MAR.
+ let args = ["-r", signedMAR.path, outMAR.path];
+
+ let exitValue;
+ process.init(signmarBin);
+ try {
+ process.run(true, args, args.length);
+ exitValue = process.exitValue;
+ } catch (e) {
+ // On Windows negative return value throws an exception
+ exitValue = -1;
+ }
+
+ // Verify signmar returned 0 for success.
+ if (wantSuccess) {
+ Assert.equal(exitValue, 0);
+ } else {
+ Assert.notEqual(exitValue, 0);
+ }
+ }
+
+ function cleanup() {
+ let outMAR = tempDir.clone();
+ outMAR.append("signed_out.mar");
+ if (outMAR.exists()) {
+ outMAR.remove(false);
+ }
+ outMAR = tempDir.clone();
+ outMAR.append("multiple_signed_out.mar");
+ if (outMAR.exists()) {
+ outMAR.remove(false);
+ }
+ outMAR = tempDir.clone();
+ outMAR.append("out.mar");
+ if (outMAR.exists()) {
+ outMAR.remove(false);
+ }
+
+ let outDir = tempDir.clone();
+ outDir.append("out");
+ if (outDir.exists()) {
+ outDir.remove(true);
+ }
+ }
+
+ const wantFailure = false;
+ const wantSuccess = true;
+ // Define the unit tests to run.
+ let tests = {
+ // Test signing a MAR file with a single signature
+ test_sign_single: function _test_sign_single() {
+ let inMAR = do_get_file("data/binary_data.mar");
+ let outMAR = tempDir.clone();
+ outMAR.append("signed_out.mar");
+ if (outMAR.exists()) {
+ outMAR.remove(false);
+ }
+ signMAR(inMAR, outMAR, ["mycert"], wantSuccess, true);
+ Assert.ok(outMAR.exists());
+ let outMARData = getBinaryFileData(outMAR);
+ let refMAR = do_get_file("data/signed_pib.mar");
+ let refMARData = getBinaryFileData(refMAR);
+ compareBinaryData(outMARData, refMARData);
+ },
+ // Test signing a MAR file with multiple signatures
+ test_sign_multiple: function _test_sign_multiple() {
+ let inMAR = do_get_file("data/binary_data.mar");
+ let outMAR = tempDir.clone();
+ outMAR.append("multiple_signed_out.mar");
+ if (outMAR.exists()) {
+ outMAR.remove(false);
+ }
+ Assert.ok(!outMAR.exists());
+ signMAR(
+ inMAR,
+ outMAR,
+ ["mycert", "mycert2", "mycert3"],
+ wantSuccess,
+ true
+ );
+ Assert.ok(outMAR.exists());
+ let outMARData = getBinaryFileData(outMAR);
+ let refMAR = do_get_file("data/multiple_signed_pib.mar");
+ let refMARData = getBinaryFileData(refMAR);
+ compareBinaryData(outMARData, refMARData);
+ },
+ // Test verifying a signed MAR file
+ test_verify_single: function _test_verify_single() {
+ let signedMAR = do_get_file("data/signed_pib.mar");
+ verifyMAR(signedMAR, wantSuccess, ["mycert"], true);
+ verifyMAR(signedMAR, wantSuccess, ["mycert"], false);
+ },
+ // Test verifying a signed MAR file with too many certs fails.
+ // Or if you want to look at it another way, One mycert signature
+ // is missing.
+ test_verify_single_too_many_certs:
+ function _test_verify_single_too_many_certs() {
+ let signedMAR = do_get_file("data/signed_pib.mar");
+ verifyMAR(signedMAR, wantFailure, ["mycert", "mycert"], true);
+ verifyMAR(signedMAR, wantFailure, ["mycert", "mycert"], false);
+ },
+ // Test verifying a signed MAR file fails when using a wrong cert
+ test_verify_single_wrong_cert: function _test_verify_single_wrong_cert() {
+ let signedMAR = do_get_file("data/signed_pib.mar");
+ verifyMAR(signedMAR, wantFailure, ["mycert2"], true);
+ verifyMAR(signedMAR, wantFailure, ["mycert2"], false);
+ },
+ // Test verifying a signed MAR file with multiple signatures
+ test_verify_multiple: function _test_verify_multiple() {
+ let signedMAR = do_get_file("data/multiple_signed_pib.mar");
+ verifyMAR(signedMAR, wantSuccess, ["mycert", "mycert2", "mycert3"]);
+ },
+ // Test verifying an unsigned MAR file fails
+ test_verify_unsigned_mar_file_fails:
+ function _test_verify_unsigned_mar_file_fails() {
+ let unsignedMAR = do_get_file("data/binary_data.mar");
+ verifyMAR(unsignedMAR, wantFailure, ["mycert", "mycert2", "mycert3"]);
+ },
+ // Test verifying a signed MAR file with the same signature multiple
+ // times fails. The input MAR has: mycert, mycert2, mycert3.
+ // we're checking to make sure the number of verified signatures
+ // is only 1 and not 3. Each signature should be verified once.
+ test_verify_multiple_same_cert: function _test_verify_multiple_same_cert() {
+ let signedMAR = do_get_file("data/multiple_signed_pib.mar");
+ verifyMAR(signedMAR, wantFailure, ["mycert", "mycert", "mycert"]);
+ },
+ // Test verifying a signed MAR file with the correct signatures but in
+ // a different order fails
+ test_verify_multiple_wrong_order:
+ function _test_verify_multiple_wrong_order() {
+ let signedMAR = do_get_file("data/multiple_signed_pib.mar");
+ verifyMAR(signedMAR, wantSuccess, ["mycert", "mycert2", "mycert3"]);
+ verifyMAR(signedMAR, wantFailure, ["mycert", "mycert3", "mycert2"]);
+ verifyMAR(signedMAR, wantFailure, ["mycert2", "mycert", "mycert3"]);
+ verifyMAR(signedMAR, wantFailure, ["mycert2", "mycert3", "mycert"]);
+ verifyMAR(signedMAR, wantFailure, ["mycert3", "mycert", "mycert2"]);
+ verifyMAR(signedMAR, wantFailure, ["mycert3", "mycert2", "mycert"]);
+ },
+ // Test verifying a signed MAR file without a PIB
+ test_verify_no_pib: function _test_verify_no_pib() {
+ let signedMAR = do_get_file("data/signed_no_pib.mar");
+ verifyMAR(signedMAR, wantSuccess, ["mycert"], true);
+ verifyMAR(signedMAR, wantSuccess, ["mycert"], false);
+ },
+ // Test verifying a signed MAR file with multiple signatures without a PIB
+ test_verify_no_pib_multiple: function _test_verify_no_pib_multiple() {
+ let signedMAR = do_get_file("data/multiple_signed_no_pib.mar");
+ verifyMAR(signedMAR, wantSuccess, ["mycert", "mycert2", "mycert3"]);
+ },
+ // Test verifying a crafted MAR file where the attacker tried to adjust
+ // the version number manually.
+ test_crafted_mar: function _test_crafted_mar() {
+ let signedBadMAR = do_get_file("data/manipulated_signed.mar");
+ verifyMAR(signedBadMAR, wantFailure, ["mycert"], true);
+ verifyMAR(signedBadMAR, wantFailure, ["mycert"], false);
+ },
+ // Test verifying a file that doesn't exist fails
+ test_bad_path_verify_fails: function _test_bad_path_verify_fails() {
+ let noMAR = do_get_file("data/does_not_exist.mar", true);
+ Assert.ok(!noMAR.exists());
+ verifyMAR(noMAR, wantFailure, ["mycert"], true);
+ },
+ // Test to make sure a stripped MAR is the same as the original MAR
+ test_strip_signature: function _test_strip_signature() {
+ let originalMAR = do_get_file("data/binary_data.mar");
+ let signedMAR = tempDir.clone();
+ signedMAR.append("signed_out.mar");
+ let outMAR = tempDir.clone();
+ outMAR.append("out.mar", true);
+ stripMARSignature(signedMAR, outMAR, wantSuccess);
+
+ // Verify that the stripped MAR matches the original data MAR exactly
+ let outMARData = getBinaryFileData(outMAR);
+ let originalMARData = getBinaryFileData(originalMAR);
+ compareBinaryData(outMARData, originalMARData);
+ },
+ // Test to make sure a stripped multi-signature-MAR is the same as the original MAR
+ test_strip_multiple_signatures: function _test_strip_multiple_signatures() {
+ let originalMAR = do_get_file("data/binary_data.mar");
+ let signedMAR = tempDir.clone();
+ signedMAR.append("multiple_signed_out.mar");
+ let outMAR = tempDir.clone();
+ outMAR.append("out.mar");
+ stripMARSignature(signedMAR, outMAR, wantSuccess);
+
+ // Verify that the stripped MAR matches the original data MAR exactly
+ let outMARData = getBinaryFileData(outMAR);
+ let originalMARData = getBinaryFileData(originalMAR);
+ compareBinaryData(outMARData, originalMARData);
+ },
+ // Test extracting the first signature in a MAR that has only a single signature
+ test_extract_sig_single: function _test_extract_sig_single() {
+ let inMAR = do_get_file("data/signed_pib.mar");
+ let extractedSig = do_get_file("extracted_signature", true);
+ if (extractedSig.exists()) {
+ extractedSig.remove(false);
+ }
+ extractMARSignature(inMAR, 0, extractedSig, wantSuccess);
+ Assert.ok(extractedSig.exists());
+
+ let referenceSig = do_get_file("data/signed_pib_mar.signature.0");
+ compareBinaryData(extractedSig, referenceSig);
+ },
+ // Test extracting the all signatures in a multi signature MAR
+ // The input MAR has 3 signatures.
+ test_extract_sig_multi: function _test_extract_sig_multi() {
+ for (let i = 0; i < 3; i++) {
+ let inMAR = do_get_file("data/multiple_signed_pib.mar");
+ let extractedSig = do_get_file("extracted_signature", true);
+ if (extractedSig.exists()) {
+ extractedSig.remove(false);
+ }
+ extractMARSignature(inMAR, i, extractedSig, wantSuccess);
+ Assert.ok(extractedSig.exists());
+
+ let referenceSig = do_get_file("data/multiple_signed_pib_mar.sig." + i);
+ compareBinaryData(extractedSig, referenceSig);
+ }
+ },
+ // Test extracting a signature that is out of range fails
+ test_extract_sig_out_of_range: function _test_extract_sig_out_of_range() {
+ let inMAR = do_get_file("data/signed_pib.mar");
+ let extractedSig = do_get_file("extracted_signature", true);
+ if (extractedSig.exists()) {
+ extractedSig.remove(false);
+ }
+ const outOfBoundsIndex = 5;
+ extractMARSignature(inMAR, outOfBoundsIndex, extractedSig, wantFailure);
+ Assert.ok(!extractedSig.exists());
+ },
+ // Test signing a file that doesn't exist fails
+ test_bad_path_sign_fails: function _test_bad_path_sign_fails() {
+ let inMAR = do_get_file("data/does_not_exist.mar", true);
+ let outMAR = tempDir.clone();
+ outMAR.append("signed_out.mar");
+ Assert.ok(!inMAR.exists());
+ signMAR(inMAR, outMAR, ["mycert"], wantFailure, true);
+ Assert.ok(!outMAR.exists());
+ },
+ // Test verifying only a subset of the signatures fails.
+ // The input MAR has: mycert, mycert2, mycert3.
+ // We're only verifying 2 of the 3 signatures and that should fail.
+ test_verify_multiple_subset: function _test_verify_multiple_subset() {
+ let signedMAR = do_get_file("data/multiple_signed_pib.mar");
+ verifyMAR(signedMAR, wantFailure, ["mycert", "mycert2"]);
+ },
+ // Test importing the first signature in a MAR that has only
+ // a single signature
+ test_import_sig_single: function _test_import_sig_single() {
+ // Make sure the input MAR was signed with mycert only
+ let inMAR = do_get_file("data/signed_pib.mar");
+ verifyMAR(inMAR, wantSuccess, ["mycert"], false);
+ verifyMAR(inMAR, wantFailure, ["mycert2"], false);
+ verifyMAR(inMAR, wantFailure, ["mycert3"], false);
+
+ // Get the signature file for this MAR signed with the key from mycert2
+ let sigFile = do_get_file("data/signed_pib_mar.signature.mycert2");
+ Assert.ok(sigFile.exists());
+ let outMAR = tempDir.clone();
+ outMAR.append("sigchanged_signed_pib.mar");
+ if (outMAR.exists()) {
+ outMAR.remove(false);
+ }
+
+ // Run the import operation
+ importMARSignature(inMAR, 0, sigFile, outMAR, wantSuccess);
+
+ // Verify we have a new MAR file, that mycert no longer verifies and that,
+ // mycert2 does verify
+ Assert.ok(outMAR.exists());
+ verifyMAR(outMAR, wantFailure, ["mycert"], false);
+ verifyMAR(outMAR, wantSuccess, ["mycert2"], false);
+ verifyMAR(outMAR, wantFailure, ["mycert3"], false);
+
+ // Compare the binary data to something that was signed originally
+ // with the private key from mycert2
+ let refMAR = do_get_file("data/signed_pib_with_mycert2.mar");
+ Assert.ok(refMAR.exists());
+ let refMARData = getBinaryFileData(refMAR);
+ let outMARData = getBinaryFileData(outMAR);
+ compareBinaryData(outMARData, refMARData);
+ },
+ // Test importing a signature that doesn't belong to the file
+ // fails to verify.
+ test_import_wrong_sig: function _test_import_wrong_sig() {
+ // Make sure the input MAR was signed with mycert only
+ let inMAR = do_get_file("data/signed_pib.mar");
+ verifyMAR(inMAR, wantSuccess, ["mycert"], false);
+ verifyMAR(inMAR, wantFailure, ["mycert2"], false);
+ verifyMAR(inMAR, wantFailure, ["mycert3"], false);
+
+ // Get the signature file for multiple_signed_pib.mar signed with the
+ // key from mycert
+ let sigFile = do_get_file("data/multiple_signed_pib_mar.sig.0");
+ Assert.ok(sigFile.exists());
+ let outMAR = tempDir.clone();
+ outMAR.append("sigchanged_signed_pib.mar");
+ if (outMAR.exists()) {
+ outMAR.remove(false);
+ }
+
+ // Run the import operation
+ importMARSignature(inMAR, 0, sigFile, outMAR, wantSuccess);
+
+ // Verify we have a new MAR file and that the mar file fails to verify
+ // when using a signature for another mar file.
+ Assert.ok(outMAR.exists());
+ verifyMAR(outMAR, wantFailure, ["mycert"], false);
+ verifyMAR(outMAR, wantFailure, ["mycert2"], false);
+ verifyMAR(outMAR, wantFailure, ["mycert3"], false);
+ },
+ // Test importing to the second signature in a MAR that has multiple
+ // signature
+ test_import_sig_multiple: function _test_import_sig_multiple() {
+ // Make sure the input MAR was signed with mycert only
+ let inMAR = do_get_file("data/multiple_signed_pib.mar");
+ verifyMAR(inMAR, wantSuccess, ["mycert", "mycert2", "mycert3"], false);
+ verifyMAR(inMAR, wantFailure, ["mycert", "mycert", "mycert3"], false);
+
+ // Get the signature file for this MAR signed with the key from mycert
+ let sigFile = do_get_file("data/multiple_signed_pib_mar.sig.0");
+ Assert.ok(sigFile.exists());
+ let outMAR = tempDir.clone();
+ outMAR.append("sigchanged_signed_pib.mar");
+ if (outMAR.exists()) {
+ outMAR.remove(false);
+ }
+
+ // Run the import operation
+ const secondSigPos = 1;
+ importMARSignature(inMAR, secondSigPos, sigFile, outMAR, wantSuccess);
+
+ // Verify we have a new MAR file and that mycert no longer verifies
+ // and that mycert2 does verify
+ Assert.ok(outMAR.exists());
+ verifyMAR(outMAR, wantSuccess, ["mycert", "mycert", "mycert3"], false);
+ verifyMAR(outMAR, wantFailure, ["mycert", "mycert2", "mycert3"], false);
+
+ // Compare the binary data to something that was signed originally
+ // with the private keys from mycert, mycert, mycert3
+ let refMAR = do_get_file("data/multiple_signed_pib_2.mar");
+ Assert.ok(refMAR.exists());
+ let refMARData = getBinaryFileData(refMAR);
+ let outMARData = getBinaryFileData(outMAR);
+ compareBinaryData(outMARData, refMARData);
+ },
+ // Test stripping a MAR that doesn't exist fails
+ test_bad_path_strip_fails: function _test_bad_path_strip_fails() {
+ let noMAR = do_get_file("data/does_not_exist.mar", true);
+ Assert.ok(!noMAR.exists());
+ let outMAR = tempDir.clone();
+ outMAR.append("out.mar");
+ stripMARSignature(noMAR, outMAR, wantFailure);
+ },
+ // Test extracting from a bad path fails
+ test_extract_bad_path: function _test_extract_bad_path() {
+ let noMAR = do_get_file("data/does_not_exist.mar", true);
+ let extractedSig = do_get_file("extracted_signature", true);
+ Assert.ok(!noMAR.exists());
+ if (extractedSig.exists()) {
+ extractedSig.remove(false);
+ }
+ extractMARSignature(noMAR, 0, extractedSig, wantFailure);
+ Assert.ok(!extractedSig.exists());
+ },
+ // Between each test make sure the out MAR does not exist.
+ cleanup_per_test: function _cleanup_per_test() {},
+ };
+
+ cleanup();
+
+ // Run all the tests
+ Assert.equal(run_tests(tests), Object.keys(tests).length - 1);
+
+ registerCleanupFunction(cleanup);
+}
diff --git a/modules/libmar/tests/unit/xpcshell.toml b/modules/libmar/tests/unit/xpcshell.toml
new file mode 100644
index 0000000000..5ffea5544f
--- /dev/null
+++ b/modules/libmar/tests/unit/xpcshell.toml
@@ -0,0 +1,10 @@
+[DEFAULT]
+head = "head_libmar.js"
+support-files = ["data/**"]
+skip-if = ["os == 'win' && msix"] # Updates are disabled for MSIX builds
+
+["test_create.js"]
+
+["test_extract.js"]
+
+["test_sign_verify.js"]
diff --git a/modules/libmar/tool/mar.c b/modules/libmar/tool/mar.c
new file mode 100644
index 0000000000..a5cf7ba6eb
--- /dev/null
+++ b/modules/libmar/tool/mar.c
@@ -0,0 +1,446 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "mar.h"
+#include "mar_cmdline.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+# include <direct.h>
+# define chdir _chdir
+#else
+# include <unistd.h>
+#endif
+
+#if !defined(NO_SIGN_VERIFY) && defined(MAR_NSS)
+# include "cert.h"
+# include "nss.h"
+# include "pk11pub.h"
+int NSSInitCryptoContext(const char* NSSConfigDir);
+#endif
+
+int mar_repackage_and_sign(const char* NSSConfigDir,
+ const char* const* certNames, uint32_t certCount,
+ const char* src, const char* dest);
+
+static void print_version() {
+ printf("Version: %s\n", MOZ_APP_VERSION);
+ printf("Default Channel ID: %s\n", MAR_CHANNEL_ID);
+}
+
+static void print_usage() {
+ printf("usage:\n");
+ printf("Create a MAR file:\n");
+ printf(
+ " mar -H MARChannelID -V ProductVersion [-C workingDir] "
+ "-c archive.mar [files...]\n");
+
+ printf("Extract a MAR file:\n");
+ printf(" mar [-C workingDir] -x archive.mar\n");
+#ifndef NO_SIGN_VERIFY
+ printf("Sign a MAR file:\n");
+ printf(
+ " mar [-C workingDir] -d NSSConfigDir -n certname -s "
+ "archive.mar out_signed_archive.mar\n");
+
+ printf("Strip a MAR signature:\n");
+ printf(
+ " mar [-C workingDir] -r "
+ "signed_input_archive.mar output_archive.mar\n");
+
+ printf("Extract a MAR signature:\n");
+ printf(
+ " mar [-C workingDir] -n(i) -X "
+ "signed_input_archive.mar base_64_encoded_signature_file\n");
+
+ printf("Import a MAR signature:\n");
+ printf(
+ " mar [-C workingDir] -n(i) -I "
+ "signed_input_archive.mar base_64_encoded_signature_file "
+ "changed_signed_output.mar\n");
+ printf("(i) is the index of the certificate to extract\n");
+# if !defined(MAR_NSS)
+ printf("Verify a MAR file:\n");
+ printf(" mar [-C workingDir] -D DERFilePath -v signed_archive.mar\n");
+ printf(
+ "At most %d signature certificate DER files are specified by "
+ "-D0 DERFilePath1 -D1 DERFilePath2, ...\n",
+ MAX_SIGNATURES);
+# else
+ printf("Verify a MAR file:\n");
+ printf(
+ " mar [-C workingDir] -d NSSConfigDir -n certname "
+ "-v signed_archive.mar\n");
+ printf(
+ "At most %d signature certificate names are specified by "
+ "-n0 certName -n1 certName2, ...\n",
+ MAX_SIGNATURES);
+# endif
+ printf(
+ "At most %d verification certificate names are specified by "
+ "-n0 certName -n1 certName2, ...\n",
+ MAX_SIGNATURES);
+#endif
+ printf("Print information on a MAR file:\n");
+ printf(" mar -t archive.mar\n");
+
+ printf("Print detailed information on a MAR file including signatures:\n");
+ printf(" mar -T archive.mar\n");
+
+ printf("Refresh the product information block of a MAR file:\n");
+ printf(
+ " mar -H MARChannelID -V ProductVersion [-C workingDir] "
+ "-i unsigned_archive_to_refresh.mar\n");
+
+ printf("Print executable version:\n");
+ printf(" mar --version\n");
+ printf("This program does not handle unicode file paths properly\n");
+}
+
+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;
+}
+
+static int mar_test(const char* path) {
+ MarFile* mar;
+
+ MarReadResult result = mar_open(path, &mar);
+ if (result != MAR_READ_SUCCESS) {
+ return -1;
+ }
+
+ printf("SIZE\tMODE\tNAME\n");
+ mar_enum_items(mar, mar_test_callback, NULL);
+
+ mar_close(mar);
+ return 0;
+}
+
+int main(int argc, char** argv) {
+ const char* certNames[MAX_SIGNATURES];
+ char* MARChannelID = NULL;
+ char* productVersion = NULL;
+ int rv = -1;
+#if !defined(NO_SIGN_VERIFY)
+ char* NSSConfigDir = NULL;
+ uint32_t k;
+ uint32_t certCount = 0;
+ int32_t sigIndex = -1;
+ uint32_t fileSizes[MAX_SIGNATURES];
+ const uint8_t* certBuffers[MAX_SIGNATURES];
+# if !defined(MAR_NSS)
+ char* DERFilePaths[MAX_SIGNATURES];
+# else
+ CERTCertificate* certs[MAX_SIGNATURES];
+# endif
+#endif
+
+ memset((void*)certNames, 0, sizeof(certNames));
+#if !defined(NO_SIGN_VERIFY)
+ memset(fileSizes, 0, sizeof(fileSizes));
+ memset((void*)certBuffers, 0, sizeof(certBuffers));
+# if !defined(MAR_NSS)
+ memset(DERFilePaths, 0, sizeof(DERFilePaths));
+# else
+ memset(certs, 0, sizeof(certs));
+# endif
+#endif
+
+ if (argc > 1 && 0 == strcmp(argv[1], "--version")) {
+ print_version();
+ return 0;
+ }
+
+ if (argc < 3) {
+ print_usage();
+ return -1;
+ }
+
+ 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' || argv[1][1] == 'i' ||
+ argv[1][1] == 'T' || argv[1][1] == 'r' || argv[1][1] == 'X' ||
+ argv[1][1] == 'I')) {
+ break;
+ /* -C workingdirectory */
+ }
+ if (argv[1][0] == '-' && argv[1][1] == 'C') {
+ if (chdir(argv[2]) != 0) {
+ return -1;
+ }
+ argv += 2;
+ argc -= 2;
+ }
+#if !defined(NO_SIGN_VERIFY)
+# if !defined(MAR_NSS)
+ /* -D DERFilePath, also matches -D[index] DERFilePath
+ We allow an index for verifying to be symmetric
+ with the import and export command line arguments. */
+ else if (argv[1][0] == '-' && argv[1][1] == 'D' &&
+ (argv[1][2] == (char)('0' + certCount) || argv[1][2] == '\0')) {
+ if (certCount >= MAX_SIGNATURES) {
+ print_usage();
+ return -1;
+ }
+ DERFilePaths[certCount++] = 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, also matches -n[index] certName
+ We allow an index for verifying to be symmetric
+ with the import and export command line arguments. */
+ } else if (argv[1][0] == '-' && argv[1][1] == 'n' &&
+ (argv[1][2] == (char)('0' + certCount) || argv[1][2] == '\0' ||
+ !strcmp(argv[2], "-X") || !strcmp(argv[2], "-I"))) {
+ if (certCount >= MAX_SIGNATURES) {
+ print_usage();
+ return -1;
+ }
+ certNames[certCount++] = argv[2];
+ if (strlen(argv[1]) > 2 &&
+ (!strcmp(argv[2], "-X") || !strcmp(argv[2], "-I")) &&
+ argv[1][2] >= '0' && argv[1][2] <= '9') {
+ sigIndex = argv[1][2] - '0';
+ argv++;
+ argc--;
+ } else {
+ argv += 2;
+ argc -= 2;
+ }
+ }
+#endif
+ else if (argv[1][0] == '-' && argv[1][1] == 'H') { // MAR channel ID
+ MARChannelID = argv[2];
+ argv += 2;
+ argc -= 2;
+ } else if (argv[1][0] == '-' && argv[1][1] == 'V') { // Product Version
+ productVersion = argv[2];
+ argv += 2;
+ argc -= 2;
+ } else {
+ print_usage();
+ return -1;
+ }
+ }
+
+ if (argv[1][0] != '-') {
+ print_usage();
+ return -1;
+ }
+
+ switch (argv[1][1]) {
+ case 'c': {
+ struct ProductInformationBlock infoBlock;
+ if (!productVersion) {
+ fprintf(stderr,
+ "ERROR: Version not specified (pass `-V <version>`).\n");
+ return -1;
+ }
+ if (!MARChannelID) {
+ fprintf(stderr,
+ "ERROR: MAR channel ID not specified (pass `-H "
+ "<mar-channel-id>`).\n");
+ return -1;
+ }
+ infoBlock.MARChannelID = MARChannelID;
+ infoBlock.productVersion = productVersion;
+ return mar_create(argv[2], argc - 3, argv + 3, &infoBlock);
+ }
+ case 'i': {
+ if (!productVersion) {
+ fprintf(stderr,
+ "ERROR: Version not specified (pass `-V <version>`).\n");
+ return -1;
+ }
+ if (!MARChannelID) {
+ fprintf(stderr,
+ "ERROR: MAR channel ID not specified (pass `-H "
+ "<mar-channel-id>`).\n");
+ return -1;
+ }
+ struct ProductInformationBlock infoBlock;
+ infoBlock.MARChannelID = MARChannelID;
+ infoBlock.productVersion = productVersion;
+ return refresh_product_info_block(argv[2], &infoBlock);
+ }
+ case 'T': {
+ struct ProductInformationBlock infoBlock;
+ uint32_t numSignatures, numAdditionalBlocks;
+ int hasSignatureBlock, hasAdditionalBlock;
+ if (!get_mar_file_info(argv[2], &hasSignatureBlock, &numSignatures,
+ &hasAdditionalBlock, NULL, &numAdditionalBlocks)) {
+ if (hasSignatureBlock) {
+ printf("Signature block found with %d signature%s\n", numSignatures,
+ numSignatures != 1 ? "s" : "");
+ }
+ if (hasAdditionalBlock) {
+ printf("%d additional block%s found:\n", numAdditionalBlocks,
+ numAdditionalBlocks != 1 ? "s" : "");
+ }
+
+ rv = read_product_info_block(argv[2], &infoBlock);
+ if (!rv) {
+ printf(" - Product Information Block:\n");
+ printf(
+ " - MAR channel name: %s\n"
+ " - Product version: %s\n",
+ infoBlock.MARChannelID, infoBlock.productVersion);
+ free((void*)infoBlock.MARChannelID);
+ free((void*)infoBlock.productVersion);
+ }
+ }
+ printf("\n");
+ /* The fall through from 'T' to 't' is intentional */
+ }
+ case 't':
+ return mar_test(argv[2]);
+
+ case 'x': // Extract a MAR file
+ return mar_extract(argv[2]);
+
+#ifndef NO_SIGN_VERIFY
+ case 'X': // Extract a MAR signature
+ if (sigIndex == -1) {
+ fprintf(stderr, "ERROR: Signature index was not passed.\n");
+ return -1;
+ }
+ if (sigIndex >= MAX_SIGNATURES || sigIndex < -1) {
+ fprintf(stderr, "ERROR: Signature index is out of range: %d.\n",
+ sigIndex);
+ return -1;
+ }
+ return extract_signature(argv[2], sigIndex, argv[3]);
+
+ case 'I': // Import a MAR signature
+ if (sigIndex == -1) {
+ fprintf(stderr, "ERROR: signature index was not passed.\n");
+ return -1;
+ }
+ if (sigIndex >= MAX_SIGNATURES || sigIndex < -1) {
+ fprintf(stderr, "ERROR: Signature index is out of range: %d.\n",
+ sigIndex);
+ return -1;
+ }
+ if (argc < 5) {
+ print_usage();
+ return -1;
+ }
+ return import_signature(argv[2], sigIndex, argv[3], argv[4]);
+
+ case 'v':
+ if (certCount == 0) {
+ print_usage();
+ return -1;
+ }
+
+# if defined(MAR_NSS)
+ if (!NSSConfigDir || certCount == 0) {
+ print_usage();
+ return -1;
+ }
+
+ if (NSSInitCryptoContext(NSSConfigDir)) {
+ fprintf(stderr, "ERROR: Could not initialize crypto library.\n");
+ return -1;
+ }
+# endif
+
+ rv = 0;
+ for (k = 0; k < certCount; ++k) {
+# if !defined(MAR_NSS)
+ rv = mar_read_entire_file(DERFilePaths[k], MAR_MAX_CERT_SIZE,
+ &certBuffers[k], &fileSizes[k]);
+
+ if (rv) {
+ fprintf(stderr, "ERROR: could not read file %s", DERFilePaths[k]);
+ break;
+ }
+# else
+ /* It is somewhat circuitous to look up a CERTCertificate and then pass
+ * in its DER encoding just so we can later re-create that
+ * CERTCertificate to extract the public key out of it. However, by
+ * doing things this way, we maximize the reuse of the
+ * mar_verify_signatures function and also we keep the control flow as
+ * similar as possible between programs and operating systems, at least
+ * for the functions that are critically important to security.
+ */
+ certs[k] = PK11_FindCertFromNickname(certNames[k], NULL);
+ if (certs[k]) {
+ certBuffers[k] = certs[k]->derCert.data;
+ fileSizes[k] = certs[k]->derCert.len;
+ } else {
+ rv = -1;
+ fprintf(stderr, "ERROR: could not find cert from nickname %s",
+ certNames[k]);
+ break;
+ }
+# endif
+ }
+
+ if (!rv) {
+ MarFile* mar;
+ MarReadResult result = mar_open(argv[2], &mar);
+ if (result == MAR_READ_SUCCESS) {
+ rv = mar_verify_signatures(mar, certBuffers, fileSizes, certCount);
+ mar_close(mar);
+ } else {
+ fprintf(stderr, "ERROR: Could not open MAR file.\n");
+ rv = -1;
+ }
+ }
+ for (k = 0; k < certCount; ++k) {
+# if !defined(MAR_NSS)
+ free((void*)certBuffers[k]);
+# else
+ /* certBuffers[k] is owned by certs[k] so don't free it */
+ CERT_DestroyCertificate(certs[k]);
+# endif
+ }
+ if (rv) {
+ /* Determine if the source MAR file has the new fields for signing */
+ int hasSignatureBlock;
+ if (get_mar_file_info(argv[2], &hasSignatureBlock, NULL, NULL, NULL,
+ NULL)) {
+ fprintf(stderr, "ERROR: could not determine if MAR is old or new.\n");
+ } else if (!hasSignatureBlock) {
+ fprintf(stderr,
+ "ERROR: The MAR file is in the old format so has"
+ " no signature to verify.\n");
+ }
+ }
+# if defined(MAR_NSS)
+ (void)NSS_Shutdown();
+# endif
+ return rv ? -1 : 0;
+
+ case 's':
+ if (!NSSConfigDir || certCount == 0 || argc < 4) {
+ print_usage();
+ return -1;
+ }
+ return mar_repackage_and_sign(NSSConfigDir, certNames, certCount, argv[2],
+ argv[3]);
+
+ case 'r':
+ return strip_signature_block(argv[2], argv[3]);
+#endif /* endif NO_SIGN_VERIFY disabled */
+
+ default:
+ print_usage();
+ return -1;
+ }
+}
diff --git a/modules/libmar/tool/moz.build b/modules/libmar/tool/moz.build
new file mode 100644
index 0000000000..b2772544d5
--- /dev/null
+++ b/modules/libmar/tool/moz.build
@@ -0,0 +1,72 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+HOST_SOURCES += [
+ "/other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp",
+ "mar.c",
+]
+
+HostProgram("mar")
+
+HOST_USE_LIBS += [
+ "hostmar",
+]
+
+if CONFIG["HOST_OS_ARCH"] == "WINNT":
+ HOST_OS_LIBS += [
+ "ws2_32",
+ ]
+
+# C11 for static_assert
+c11_flags = ["-std=gnu11"]
+if CONFIG["CC_TYPE"] == "clang-cl":
+ c11_flags.insert(0, "-Xclang")
+HOST_CFLAGS += c11_flags
+
+HOST_DEFINES["NO_SIGN_VERIFY"] = True
+
+if CONFIG["MOZ_BUILD_APP"] != "tools/update-packaging":
+ Program("signmar")
+
+ SOURCES += HOST_SOURCES
+
+ CFLAGS += c11_flags
+
+ USE_LIBS += [
+ "mar",
+ "nspr",
+ "nss",
+ "signmar",
+ "verifymar",
+ ]
+
+ if CONFIG["MOZ_USE_NSS_FOR_MAR"]:
+ DEFINES["MAR_NSS"] = True
+
+ if CONFIG["OS_ARCH"] == "WINNT":
+ USE_STATIC_LIBS = True
+
+ OS_LIBS += [
+ "ws2_32",
+ ]
+ if not CONFIG["MOZ_USE_NSS_FOR_MAR"]:
+ OS_LIBS += [
+ "crypt32",
+ "advapi32",
+ ]
+ elif CONFIG["OS_ARCH"] == "Darwin" and not CONFIG["MOZ_USE_NSS_FOR_MAR"]:
+ OS_LIBS += [
+ "-framework CoreFoundation",
+ "-framework Security",
+ ]
+
+ DisableStlWrapping()
+
+
+for var in ("MAR_CHANNEL_ID", "MOZ_APP_VERSION"):
+ HOST_DEFINES[var] = '"%s"' % CONFIG[var]
+ if SOURCES:
+ DEFINES[var] = HOST_DEFINES[var]
diff --git a/modules/libmar/verify/MacVerifyCrypto.cpp b/modules/libmar/verify/MacVerifyCrypto.cpp
new file mode 100644
index 0000000000..d1d1200fef
--- /dev/null
+++ b/modules/libmar/verify/MacVerifyCrypto.cpp
@@ -0,0 +1,218 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+#include <dlfcn.h>
+
+#include "cryptox.h"
+
+// We declare the necessary parts of the Security Transforms API here since
+// we're building with the 10.6 SDK, which doesn't know about Security
+// Transforms.
+#if defined(__cplusplus)
+extern "C" {
+#endif
+const CFStringRef kSecTransformInputAttributeName = CFSTR("INPUT");
+typedef CFTypeRef SecTransformRef;
+typedef OpaqueSecKeyRef* SecKeyRef;
+
+typedef SecTransformRef (*SecTransformCreateReadTransformWithReadStreamFunc)(
+ CFReadStreamRef inputStream);
+SecTransformCreateReadTransformWithReadStreamFunc
+ SecTransformCreateReadTransformWithReadStreamPtr = NULL;
+typedef CFTypeRef (*SecTransformExecuteFunc)(SecTransformRef transform,
+ CFErrorRef* error);
+SecTransformExecuteFunc SecTransformExecutePtr = NULL;
+typedef SecTransformRef (*SecVerifyTransformCreateFunc)(SecKeyRef key,
+ CFDataRef signature,
+ CFErrorRef* error);
+SecVerifyTransformCreateFunc SecVerifyTransformCreatePtr = NULL;
+typedef Boolean (*SecTransformSetAttributeFunc)(SecTransformRef transform,
+ CFStringRef key,
+ CFTypeRef value,
+ CFErrorRef* error);
+SecTransformSetAttributeFunc SecTransformSetAttributePtr = NULL;
+#if defined(__cplusplus)
+}
+#endif
+
+CryptoX_Result CryptoMac_InitCryptoProvider() {
+ if (!SecTransformCreateReadTransformWithReadStreamPtr) {
+ SecTransformCreateReadTransformWithReadStreamPtr =
+ (SecTransformCreateReadTransformWithReadStreamFunc)dlsym(
+ RTLD_DEFAULT, "SecTransformCreateReadTransformWithReadStream");
+ }
+ if (!SecTransformExecutePtr) {
+ SecTransformExecutePtr =
+ (SecTransformExecuteFunc)dlsym(RTLD_DEFAULT, "SecTransformExecute");
+ }
+ if (!SecVerifyTransformCreatePtr) {
+ SecVerifyTransformCreatePtr = (SecVerifyTransformCreateFunc)dlsym(
+ RTLD_DEFAULT, "SecVerifyTransformCreate");
+ }
+ if (!SecTransformSetAttributePtr) {
+ SecTransformSetAttributePtr = (SecTransformSetAttributeFunc)dlsym(
+ RTLD_DEFAULT, "SecTransformSetAttribute");
+ }
+ if (!SecTransformCreateReadTransformWithReadStreamPtr ||
+ !SecTransformExecutePtr || !SecVerifyTransformCreatePtr ||
+ !SecTransformSetAttributePtr) {
+ return CryptoX_Error;
+ }
+ return CryptoX_Success;
+}
+
+CryptoX_Result CryptoMac_VerifyBegin(CryptoX_SignatureHandle* aInputData) {
+ if (!aInputData) {
+ return CryptoX_Error;
+ }
+
+ void* inputData = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ if (!inputData) {
+ return CryptoX_Error;
+ }
+
+ *aInputData = inputData;
+ return CryptoX_Success;
+}
+
+CryptoX_Result CryptoMac_VerifyUpdate(CryptoX_SignatureHandle* aInputData,
+ void* aBuf, unsigned int aLen) {
+ if (aLen == 0) {
+ return CryptoX_Success;
+ }
+ if (!aInputData || !*aInputData) {
+ return CryptoX_Error;
+ }
+
+ CFMutableDataRef inputData = (CFMutableDataRef)*aInputData;
+
+ CFDataAppendBytes(inputData, (const uint8*)aBuf, aLen);
+ return CryptoX_Success;
+}
+
+CryptoX_Result CryptoMac_LoadPublicKey(const unsigned char* aCertData,
+ unsigned int aDataSize,
+ CryptoX_PublicKey* aPublicKey) {
+ if (!aCertData || aDataSize == 0 || !aPublicKey) {
+ return CryptoX_Error;
+ }
+ *aPublicKey = NULL;
+ CFDataRef certData = CFDataCreate(kCFAllocatorDefault, aCertData, aDataSize);
+ if (!certData) {
+ return CryptoX_Error;
+ }
+
+ SecCertificateRef cert =
+ SecCertificateCreateWithData(kCFAllocatorDefault, certData);
+ CFRelease(certData);
+ if (!cert) {
+ return CryptoX_Error;
+ }
+
+ OSStatus status = SecCertificateCopyPublicKey(cert, (SecKeyRef*)aPublicKey);
+ CFRelease(cert);
+ if (status != 0) {
+ return CryptoX_Error;
+ }
+
+ return CryptoX_Success;
+}
+
+CryptoX_Result CryptoMac_VerifySignature(CryptoX_SignatureHandle* aInputData,
+ CryptoX_PublicKey* aPublicKey,
+ const unsigned char* aSignature,
+ unsigned int aSignatureLen) {
+ if (!aInputData || !*aInputData || !aPublicKey || !*aPublicKey ||
+ !aSignature || aSignatureLen == 0) {
+ return CryptoX_Error;
+ }
+
+ CFDataRef signatureData =
+ CFDataCreate(kCFAllocatorDefault, aSignature, aSignatureLen);
+ if (!signatureData) {
+ return CryptoX_Error;
+ }
+
+ CFErrorRef error;
+ SecTransformRef verifier = SecVerifyTransformCreatePtr((SecKeyRef)*aPublicKey,
+ signatureData, &error);
+ if (!verifier || error) {
+ if (error) {
+ CFRelease(error);
+ }
+ CFRelease(signatureData);
+ return CryptoX_Error;
+ }
+
+ SecTransformSetAttributePtr(verifier, kSecDigestTypeAttribute, kSecDigestSHA2,
+ &error);
+ if (error) {
+ CFRelease(error);
+ CFRelease(signatureData);
+ CFRelease(verifier);
+ return CryptoX_Error;
+ }
+
+ int digestLength = 384;
+ CFNumberRef dLen =
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &digestLength);
+ SecTransformSetAttributePtr(verifier, kSecDigestLengthAttribute, dLen,
+ &error);
+ CFRelease(dLen);
+ if (error) {
+ CFRelease(error);
+ CFRelease(signatureData);
+ CFRelease(verifier);
+ return CryptoX_Error;
+ }
+
+ SecTransformSetAttributePtr(verifier, kSecTransformInputAttributeName,
+ (CFDataRef)*aInputData, &error);
+ if (error) {
+ CFRelease(error);
+ CFRelease(signatureData);
+ CFRelease(verifier);
+ return CryptoX_Error;
+ }
+
+ CryptoX_Result result = CryptoX_Error;
+ CFTypeRef rv = SecTransformExecutePtr(verifier, &error);
+ if (error) {
+ CFRelease(error);
+ CFRelease(signatureData);
+ CFRelease(verifier);
+ return CryptoX_Error;
+ }
+
+ if (CFGetTypeID(rv) == CFBooleanGetTypeID() &&
+ CFBooleanGetValue((CFBooleanRef)rv) == true) {
+ result = CryptoX_Success;
+ }
+
+ CFRelease(signatureData);
+ CFRelease(verifier);
+
+ return result;
+}
+
+void CryptoMac_FreeSignatureHandle(CryptoX_SignatureHandle* aInputData) {
+ if (!aInputData || !*aInputData) {
+ return;
+ }
+
+ CFMutableDataRef inputData = NULL;
+ inputData = (CFMutableDataRef)*aInputData;
+
+ CFRelease(inputData);
+}
+
+void CryptoMac_FreePublicKey(CryptoX_PublicKey* aPublicKey) {
+ if (!aPublicKey || !*aPublicKey) {
+ return;
+ }
+
+ CFRelease((SecKeyRef)*aPublicKey);
+}
diff --git a/modules/libmar/verify/cryptox.c b/modules/libmar/verify/cryptox.c
new file mode 100644
index 0000000000..8afc13e5e9
--- /dev/null
+++ b/modules/libmar/verify/cryptox.c
@@ -0,0 +1,239 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef XP_WIN
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include "cryptox.h"
+
+#if defined(MAR_NSS)
+
+/**
+ * Loads the public key for the specified cert name from the NSS store.
+ *
+ * @param certData The DER-encoded X509 certificate to extract the key from.
+ * @param certDataSize The size of certData.
+ * @param publicKey Out parameter for the public key to use.
+ * @return CryptoX_Success on success, CryptoX_Error on error.
+ */
+CryptoX_Result NSS_LoadPublicKey(const unsigned char* certData,
+ unsigned int certDataSize,
+ SECKEYPublicKey** publicKey) {
+ CERTCertificate* cert;
+ SECItem certDataItem = {siBuffer, (unsigned char*)certData, certDataSize};
+
+ if (!certData || !publicKey) {
+ return CryptoX_Error;
+ }
+
+ cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &certDataItem, NULL,
+ PR_FALSE, PR_TRUE);
+ /* Get the cert and embedded public key out of the database */
+ if (!cert) {
+ return CryptoX_Error;
+ }
+ *publicKey = CERT_ExtractPublicKey(cert);
+ CERT_DestroyCertificate(cert);
+
+ if (!*publicKey) {
+ 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_PKCS1_SHA384_WITH_RSA_ENCRYPTION, 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 CryptoAPI_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) {
+ CRYPT_DATA_BLOB blob;
+ CERT_CONTEXT* context;
+ if (!provider || !certData || !publicKey) {
+ 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,
+ NULL, 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_ENH_RSA_AES_PROV, PROV_RSA_AES,
+ CRYPT_VERIFYCONTEXT)) {
+ if (!CryptAcquireContext(provider, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES,
+ CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT)) {
+ if (!CryptAcquireContext(provider, NULL, NULL, PROV_RSA_AES,
+ CRYPT_VERIFYCONTEXT)) {
+ if (!CryptAcquireContext(provider, NULL, NULL, PROV_RSA_AES,
+ 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_SHA_384, 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 0000000000..9d7b1f04bc
--- /dev/null
+++ b/modules/libmar/verify/cryptox.h
@@ -0,0 +1,165 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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 "cert.h"
+# include "keyhi.h"
+# include "cryptohi.h"
+
+# define CryptoX_InvalidHandleValue NULL
+# define CryptoX_ProviderHandle void*
+# define CryptoX_SignatureHandle VFYContext*
+# define CryptoX_PublicKey SECKEYPublicKey*
+# define CryptoX_Certificate CERTCertificate*
+
+# ifdef __cplusplus
+extern "C" {
+# endif
+CryptoX_Result NSS_LoadPublicKey(const unsigned char* certData,
+ unsigned int certDataSize,
+ SECKEYPublicKey** publicKey);
+CryptoX_Result NSS_VerifyBegin(VFYContext** ctx,
+ SECKEYPublicKey* const* publicKey);
+CryptoX_Result NSS_VerifySignature(VFYContext* const* ctx,
+ const unsigned char* signature,
+ unsigned int signatureLen);
+# ifdef __cplusplus
+} // extern "C"
+# endif
+
+# define CryptoX_InitCryptoProvider(CryptoHandle) CryptoX_Success
+# define CryptoX_VerifyBegin(CryptoHandle, SignatureHandle, PublicKey) \
+ NSS_VerifyBegin(SignatureHandle, PublicKey)
+# define CryptoX_FreeSignatureHandle(SignatureHandle) \
+ VFY_DestroyContext(*SignatureHandle, PR_TRUE)
+# define CryptoX_VerifyUpdate(SignatureHandle, buf, len) \
+ VFY_Update(*SignatureHandle, (const unsigned char*)(buf), len)
+# define CryptoX_LoadPublicKey(CryptoHandle, certData, dataSize, publicKey) \
+ NSS_LoadPublicKey(certData, dataSize, publicKey)
+# 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 XP_MACOSX
+
+# define CryptoX_InvalidHandleValue NULL
+# define CryptoX_ProviderHandle void*
+# define CryptoX_SignatureHandle void*
+# define CryptoX_PublicKey void*
+# define CryptoX_Certificate void*
+
+// Forward-declare Objective-C functions implemented in MacVerifyCrypto.mm.
+# ifdef __cplusplus
+extern "C" {
+# endif
+CryptoX_Result CryptoMac_InitCryptoProvider();
+CryptoX_Result CryptoMac_VerifyBegin(CryptoX_SignatureHandle* aInputData);
+CryptoX_Result CryptoMac_VerifyUpdate(CryptoX_SignatureHandle* aInputData,
+ void* aBuf, unsigned int aLen);
+CryptoX_Result CryptoMac_LoadPublicKey(const unsigned char* aCertData,
+ unsigned int aDataSize,
+ CryptoX_PublicKey* aPublicKey);
+CryptoX_Result CryptoMac_VerifySignature(CryptoX_SignatureHandle* aInputData,
+ CryptoX_PublicKey* aPublicKey,
+ const unsigned char* aSignature,
+ unsigned int aSignatureLen);
+void CryptoMac_FreeSignatureHandle(CryptoX_SignatureHandle* aInputData);
+void CryptoMac_FreePublicKey(CryptoX_PublicKey* aPublicKey);
+# ifdef __cplusplus
+} // extern "C"
+# endif
+
+# define CryptoX_InitCryptoProvider(aProviderHandle) \
+ CryptoMac_InitCryptoProvider()
+# define CryptoX_VerifyBegin(aCryptoHandle, aInputData, aPublicKey) \
+ CryptoMac_VerifyBegin(aInputData)
+# define CryptoX_VerifyUpdate(aInputData, aBuf, aLen) \
+ CryptoMac_VerifyUpdate(aInputData, aBuf, aLen)
+# define CryptoX_LoadPublicKey(aProviderHandle, aCertData, aDataSize, \
+ aPublicKey) \
+ CryptoMac_LoadPublicKey(aCertData, aDataSize, aPublicKey)
+# define CryptoX_VerifySignature(aInputData, aPublicKey, aSignature, \
+ aSignatureLen) \
+ CryptoMac_VerifySignature(aInputData, aPublicKey, aSignature, aSignatureLen)
+# define CryptoX_FreeSignatureHandle(aInputData) \
+ CryptoMac_FreeSignatureHandle(aInputData)
+# define CryptoX_FreePublicKey(aPublicKey) CryptoMac_FreePublicKey(aPublicKey)
+# define CryptoX_FreeCertificate(aCertificate)
+
+#elif defined(XP_WIN)
+
+# include <windows.h>
+# include <wincrypt.h>
+
+CryptoX_Result CryptoAPI_InitCryptoContext(HCRYPTPROV* provider);
+CryptoX_Result CryptoAPI_LoadPublicKey(HCRYPTPROV hProv, BYTE* certData,
+ DWORD sizeOfCertData,
+ HCRYPTKEY* publicKey);
+CryptoX_Result CryptoAPI_VerifyBegin(HCRYPTPROV provider, HCRYPTHASH* hash);
+CryptoX_Result CryptoAPI_VerifyUpdate(HCRYPTHASH* hash, BYTE* buf, DWORD len);
+CryptoX_Result CryptoAPI_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_FreeSignatureHandle(SignatureHandle)
+# define CryptoX_VerifyUpdate(SignatureHandle, buf, len) \
+ CryptoAPI_VerifyUpdate(SignatureHandle, (BYTE*)(buf), len)
+# define CryptoX_LoadPublicKey(CryptoHandle, certData, dataSize, publicKey) \
+ CryptoAPI_LoadPublicKey(CryptoHandle, (BYTE*)(certData), dataSize, \
+ publicKey)
+# define CryptoX_VerifySignature(hash, publicKey, signedData, len) \
+ CryptoAPI_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_FreeSignatureHandle(SignatureHandle)
+# define CryptoX_VerifyUpdate(SignatureHandle, buf, len) CryptoX_Error
+# define CryptoX_LoadPublicKey(CryptoHandle, certData, dataSize, publicKey) \
+ 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 0000000000..5272bb585b
--- /dev/null
+++ b/modules/libmar/verify/mar_verify.c
@@ -0,0 +1,416 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef XP_WIN
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include "mar_private.h"
+#include "mar.h"
+#include "cryptox.h"
+
+int mar_read_entire_file(const char* filePath, uint32_t maxSize,
+ /*out*/ const uint8_t** data,
+ /*out*/ uint32_t* size) {
+ int result;
+ FILE* f;
+
+ if (!filePath || !data || !size) {
+ return -1;
+ }
+
+ f = fopen(filePath, "rb");
+ if (!f) {
+ return -1;
+ }
+
+ result = -1;
+ if (!fseeko(f, 0, SEEK_END)) {
+ int64_t fileSize = ftello(f);
+ if (fileSize > 0 && fileSize <= maxSize && !fseeko(f, 0, SEEK_SET)) {
+ unsigned char* fileData;
+
+ *size = (unsigned int)fileSize;
+ fileData = malloc(*size);
+ if (fileData) {
+ if (fread(fileData, *size, 1, f) == 1) {
+ *data = fileData;
+ result = 0;
+ } else {
+ free(fileData);
+ }
+ }
+ }
+ }
+
+ fclose(f);
+
+ return result;
+}
+
+int mar_extract_and_verify_signatures(MarFile* mar,
+ CryptoX_ProviderHandle provider,
+ CryptoX_PublicKey* keys,
+ uint32_t keyCount);
+int mar_verify_extracted_signatures(MarFile* mar,
+ CryptoX_ProviderHandle provider,
+ CryptoX_PublicKey* keys,
+ const uint8_t* const* extractedSignatures,
+ uint32_t keyCount, uint32_t* numVerified);
+
+/**
+ * Reads the specified number of bytes from the MAR buffer and
+ * stores them in the passed buffer.
+ *
+ * @param mar An opened MAR
+ * @param mar_position
+ * Our current position within the MAR file buffer.
+ * @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 ctxs Pointer to the first element in an array of verify context.
+ * @param count The number of elements in ctxs
+ * @param err The name of what is being written to in case of error.
+ * @return CryptoX_Success on success
+ * CryptoX_Error on error
+ */
+CryptoX_Result ReadAndUpdateVerifyContext(MarFile* mar, size_t* mar_position,
+ void* buffer, uint32_t size,
+ CryptoX_SignatureHandle* ctxs,
+ uint32_t count, const char* err) {
+ uint32_t k;
+ if (!mar || !mar_position || !buffer || !ctxs || count == 0 || !err) {
+ fprintf(stderr, "ERROR: Invalid parameter specified.\n");
+ return CryptoX_Error;
+ }
+
+ if (!size) {
+ return CryptoX_Success;
+ }
+
+ if (mar_read_buffer(mar, buffer, mar_position, size) != 0) {
+ fprintf(stderr, "ERROR: Could not read %s\n", err);
+ return CryptoX_Error;
+ }
+
+ for (k = 0; k < count; k++) {
+ if (CryptoX_Failed(CryptoX_VerifyUpdate(&ctxs[k], buffer, size))) {
+ fprintf(stderr, "ERROR: Could not update verify context for %s\n", err);
+ return CryptoX_Error;
+ }
+ }
+ return CryptoX_Success;
+}
+
+/**
+ * Verifies a MAR file by verifying each signature with the corresponding
+ * certificate. That is, the first signature will be verified using the first
+ * certificate given, the second signature will be verified using the second
+ * certificate given, etc. The signature count must exactly match the number of
+ * certificates given, and all signature verifications must succeed.
+ *
+ * @param mar The file who's signature should be calculated
+ * @param certData Pointer to the first element in an array of
+ * certificate data
+ * @param certDataSizes Pointer to the first element in an array for size of
+ * the data stored
+ * @param certCount The number of elements in certData and certDataSizes
+ * @return 0 on success
+ */
+int mar_verify_signatures(MarFile* mar, const uint8_t* const* certData,
+ const uint32_t* certDataSizes, uint32_t certCount) {
+ int rv = -1;
+ CryptoX_ProviderHandle provider = CryptoX_InvalidHandleValue;
+ CryptoX_PublicKey keys[MAX_SIGNATURES];
+ uint32_t k;
+
+ memset(keys, 0, sizeof(keys));
+
+ if (!mar || !certData || !certDataSizes || certCount == 0) {
+ fprintf(stderr, "ERROR: Invalid parameter specified.\n");
+ goto failure;
+ }
+
+ if (CryptoX_Failed(CryptoX_InitCryptoProvider(&provider))) {
+ fprintf(stderr, "ERROR: Could not init crytpo library.\n");
+ goto failure;
+ }
+
+ for (k = 0; k < certCount; ++k) {
+ if (CryptoX_Failed(CryptoX_LoadPublicKey(provider, certData[k],
+ certDataSizes[k], &keys[k]))) {
+ fprintf(stderr, "ERROR: Could not load public key.\n");
+ goto failure;
+ }
+ }
+
+ rv = mar_extract_and_verify_signatures(mar, provider, keys, certCount);
+
+failure:
+
+ for (k = 0; k < certCount; ++k) {
+ if (keys[k]) {
+ CryptoX_FreePublicKey(&keys[k]);
+ }
+ }
+
+ return rv;
+}
+
+/**
+ * Extracts each signature from the specified MAR file,
+ * then calls mar_verify_extracted_signatures to verify each signature.
+ *
+ * @param mar An opened MAR
+ * @param provider A library provider
+ * @param keys The public keys to use to verify the MAR
+ * @param keyCount The number of keys pointed to by keys
+ * @return 0 on success
+ */
+int mar_extract_and_verify_signatures(MarFile* mar,
+ CryptoX_ProviderHandle provider,
+ CryptoX_PublicKey* keys,
+ uint32_t keyCount) {
+ uint32_t signatureCount, signatureLen, numVerified = 0;
+ uint32_t signatureAlgorithmIDs[MAX_SIGNATURES];
+ uint8_t* extractedSignatures[MAX_SIGNATURES];
+ uint32_t i;
+ size_t mar_position = 0;
+
+ memset(signatureAlgorithmIDs, 0, sizeof(signatureAlgorithmIDs));
+ memset(extractedSignatures, 0, sizeof(extractedSignatures));
+
+ if (!mar) {
+ fprintf(stderr, "ERROR: Invalid file pointer passed.\n");
+ return CryptoX_Error;
+ }
+
+ /* Skip to the start of the signature block */
+ if (mar_buffer_seek(mar, &mar_position, SIGNATURE_BLOCK_OFFSET) != 0) {
+ fprintf(stderr, "ERROR: Could not seek to the signature block.\n");
+ return CryptoX_Error;
+ }
+
+ /* Get the number of signatures */
+ if (mar_read_buffer(mar, &signatureCount, &mar_position,
+ sizeof(signatureCount)) != 0) {
+ 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; i++) {
+ /* Get the signature algorithm ID */
+ if (mar_read_buffer(mar, &signatureAlgorithmIDs[i], &mar_position,
+ sizeof(uint32_t)) != 0) {
+ fprintf(stderr, "ERROR: Could not read signatures algorithm ID.\n");
+ return CryptoX_Error;
+ }
+ signatureAlgorithmIDs[i] = ntohl(signatureAlgorithmIDs[i]);
+
+ if (mar_read_buffer(mar, &signatureLen, &mar_position, sizeof(uint32_t)) !=
+ 0) {
+ fprintf(stderr, "ERROR: Could not read signatures length.\n");
+ return CryptoX_Error;
+ }
+ signatureLen = ntohl(signatureLen);
+
+ /* To protect 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;
+ }
+
+ extractedSignatures[i] = malloc(signatureLen);
+ if (!extractedSignatures[i]) {
+ fprintf(stderr, "ERROR: Could not allocate buffer for signature.\n");
+ return CryptoX_Error;
+ }
+ if (mar_read_buffer(mar, extractedSignatures[i], &mar_position,
+ signatureLen) != 0) {
+ fprintf(stderr, "ERROR: Could not read extracted signature.\n");
+ for (i = 0; i < signatureCount; ++i) {
+ free(extractedSignatures[i]);
+ }
+ return CryptoX_Error;
+ }
+
+ /* We don't try to verify signatures we don't know about */
+ if (signatureAlgorithmIDs[i] != 2) {
+ fprintf(stderr, "ERROR: Unknown signature algorithm ID.\n");
+ for (i = 0; i < signatureCount; ++i) {
+ free(extractedSignatures[i]);
+ }
+ return CryptoX_Error;
+ }
+ }
+
+ if (mar_verify_extracted_signatures(
+ mar, provider, keys, (const uint8_t* const*)extractedSignatures,
+ signatureCount, &numVerified) == CryptoX_Error) {
+ return CryptoX_Error;
+ }
+ for (i = 0; i < signatureCount; ++i) {
+ free(extractedSignatures[i]);
+ }
+
+ /* If we reached here and we verified every
+ signature, return success. */
+ if (numVerified == signatureCount && keyCount == numVerified) {
+ return CryptoX_Success;
+ }
+
+ if (numVerified == 0) {
+ fprintf(stderr, "ERROR: Not all signatures were verified.\n");
+ } else {
+ fprintf(stderr, "ERROR: Only %d of %d signatures were verified.\n",
+ numVerified, signatureCount);
+ }
+ return CryptoX_Error;
+}
+
+/**
+ * Verifies a MAR file by verifying each signature with the corresponding
+ * certificate. That is, the first signature will be verified using the first
+ * certificate given, the second signature will be verified using the second
+ * certificate given, etc. The signature count must exactly match the number of
+ * certificates given, and all signature verifications must succeed.
+ *
+ * @param mar An opened MAR
+ * @param provider A library provider
+ * @param keys A pointer to the first element in an
+ * array of keys.
+ * @param extractedSignatures Pointer to the first element in an array
+ * of extracted signatures.
+ * @param signatureCount The number of signatures in the MAR file
+ * @param numVerified Out parameter which will be filled with
+ * the number of verified signatures.
+ * This information can be useful for printing
+ * error messages.
+ * @return CryptoX_Success on success, *numVerified == signatureCount.
+ */
+CryptoX_Result mar_verify_extracted_signatures(
+ MarFile* mar, CryptoX_ProviderHandle provider, CryptoX_PublicKey* keys,
+ const uint8_t* const* extractedSignatures, uint32_t signatureCount,
+ uint32_t* numVerified) {
+ CryptoX_SignatureHandle signatureHandles[MAX_SIGNATURES];
+ char buf[BLOCKSIZE];
+ uint32_t signatureLengths[MAX_SIGNATURES];
+ uint32_t i;
+ int rv = CryptoX_Error;
+ size_t mar_position = 0;
+
+ memset(signatureHandles, 0, sizeof(signatureHandles));
+ memset(signatureLengths, 0, sizeof(signatureLengths));
+
+ if (!extractedSignatures || !numVerified) {
+ fprintf(stderr, "ERROR: Invalid parameter specified.\n");
+ goto failure;
+ }
+
+ *numVerified = 0;
+
+ /* 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");
+ goto failure;
+ }
+
+ for (i = 0; i < signatureCount; i++) {
+ if (CryptoX_Failed(
+ CryptoX_VerifyBegin(provider, &signatureHandles[i], &keys[i]))) {
+ fprintf(stderr, "ERROR: Could not initialize signature handle.\n");
+ goto failure;
+ }
+ }
+
+ /* Bytes 0-3: MAR1
+ Bytes 4-7: index offset
+ Bytes 8-15: size of entire MAR
+ */
+ if (CryptoX_Failed(ReadAndUpdateVerifyContext(
+ mar, &mar_position, buf, SIGNATURE_BLOCK_OFFSET + sizeof(uint32_t),
+ signatureHandles, signatureCount, "signature block"))) {
+ goto failure;
+ }
+
+ /* Read the signature block */
+ for (i = 0; i < signatureCount; i++) {
+ /* Get the signature algorithm ID */
+ if (CryptoX_Failed(ReadAndUpdateVerifyContext(
+ mar, &mar_position, &buf, sizeof(uint32_t), signatureHandles,
+ signatureCount, "signature algorithm ID"))) {
+ goto failure;
+ }
+
+ if (CryptoX_Failed(ReadAndUpdateVerifyContext(
+ mar, &mar_position, &signatureLengths[i], sizeof(uint32_t),
+ signatureHandles, signatureCount, "signature length"))) {
+ goto failure;
+ }
+ signatureLengths[i] = ntohl(signatureLengths[i]);
+ if (signatureLengths[i] > MAX_SIGNATURE_LENGTH) {
+ fprintf(stderr, "ERROR: Embedded signature length is too large.\n");
+ goto failure;
+ }
+
+ /* Skip past the signature itself as those are not included */
+ if (mar_buffer_seek(mar, &mar_position, signatureLengths[i]) != 0) {
+ fprintf(stderr, "ERROR: Could not seek past signature.\n");
+ goto failure;
+ }
+ }
+
+ /* Read the rest of the file after the signature block */
+ while (mar_position < mar->data_len) {
+ int numRead = mar_read_buffer_max(mar, buf, &mar_position, BLOCKSIZE);
+ for (i = 0; i < signatureCount; i++) {
+ if (CryptoX_Failed(
+ CryptoX_VerifyUpdate(&signatureHandles[i], buf, numRead))) {
+ fprintf(stderr,
+ "ERROR: Error updating verify context with"
+ " data block.\n");
+ goto failure;
+ }
+ }
+ }
+
+ /* Verify the signatures */
+ for (i = 0; i < signatureCount; i++) {
+ if (CryptoX_Failed(CryptoX_VerifySignature(&signatureHandles[i], &keys[i],
+ extractedSignatures[i],
+ signatureLengths[i]))) {
+ fprintf(stderr, "ERROR: Error verifying signature.\n");
+ goto failure;
+ }
+ ++*numVerified;
+ }
+
+ rv = CryptoX_Success;
+failure:
+ for (i = 0; i < signatureCount; i++) {
+ CryptoX_FreeSignatureHandle(&signatureHandles[i]);
+ }
+
+ return rv;
+}
diff --git a/modules/libmar/verify/moz.build b/modules/libmar/verify/moz.build
new file mode 100644
index 0000000000..5ea079a666
--- /dev/null
+++ b/modules/libmar/verify/moz.build
@@ -0,0 +1,52 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library("verifymar")
+
+UNIFIED_SOURCES += [
+ "cryptox.c",
+ "mar_verify.c",
+]
+
+FORCE_STATIC_LIB = True
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ USE_STATIC_LIBS = True
+elif CONFIG["OS_ARCH"] == "Darwin" and not CONFIG["MOZ_USE_NSS_FOR_MAR"]:
+ UNIFIED_SOURCES += [
+ "MacVerifyCrypto.cpp",
+ ]
+ OS_LIBS += [
+ "-framework Security",
+ ]
+else:
+ USE_LIBS += [
+ "nspr",
+ "nss",
+ "signmar",
+ ]
+ if CONFIG["OS_ARCH"] != "Darwin":
+ # Ideally, this would be '-Wl,-rpath=$ORIGIN', but the build system
+ # doesn't do the right escaping yet. Even more ideally, this would
+ # be LDFLAGS, but the build system doesn't propagate those like
+ # USE_LIBS and OS_LIBS. Bug #1041943.
+ OS_LIBS += [
+ "-Wl,-rpath=\\$$ORIGIN",
+ ]
+
+LOCAL_INCLUDES += [
+ "../src",
+]
+
+if CONFIG["MOZ_USE_NSS_FOR_MAR"]:
+ LOCAL_INCLUDES += ["../sign"]
+ DEFINES["MAR_NSS"] = True
+
+# C11 for static_assert
+c11_flags = ["-std=gnu11"]
+if CONFIG["CC_TYPE"] == "clang-cl":
+ c11_flags.insert(0, "-Xclang")
+CFLAGS += c11_flags