diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /modules/libmar | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/libmar')
59 files changed, 5881 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..02a298a6f7 --- /dev/null +++ b/modules/libmar/sign/nss_secutil.c @@ -0,0 +1,226 @@ +/* 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"); + 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..0ac2bf7b2c --- /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; + } else { + /* 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..7b96d8dfd3 --- /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.ini"] + + 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 Binary files differnew file mode 100644 index 0000000000..357eeb9a87 --- /dev/null +++ b/modules/libmar/tests/unit/data/0_sized.mar 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 Binary files differnew file mode 100644 index 0000000000..a137f11adc --- /dev/null +++ b/modules/libmar/tests/unit/data/1_byte.mar 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 Binary files differnew file mode 100644 index 0000000000..7fef469898 --- /dev/null +++ b/modules/libmar/tests/unit/data/binary_data.mar diff --git a/modules/libmar/tests/unit/data/binary_data_file b/modules/libmar/tests/unit/data/binary_data_file Binary files differnew file mode 100644 index 0000000000..a0d7369e45 --- /dev/null +++ b/modules/libmar/tests/unit/data/binary_data_file diff --git a/modules/libmar/tests/unit/data/cert9.db b/modules/libmar/tests/unit/data/cert9.db Binary files differnew file mode 100644 index 0000000000..e0d6191e64 --- /dev/null +++ b/modules/libmar/tests/unit/data/cert9.db diff --git a/modules/libmar/tests/unit/data/key4.db b/modules/libmar/tests/unit/data/key4.db Binary files differnew file mode 100644 index 0000000000..85c9c5a215 --- /dev/null +++ b/modules/libmar/tests/unit/data/key4.db diff --git a/modules/libmar/tests/unit/data/manipulated_backend_collision.mar b/modules/libmar/tests/unit/data/manipulated_backend_collision.mar Binary files differnew file mode 100644 index 0000000000..41d4f78482 --- /dev/null +++ b/modules/libmar/tests/unit/data/manipulated_backend_collision.mar diff --git a/modules/libmar/tests/unit/data/manipulated_frontend_collision.mar b/modules/libmar/tests/unit/data/manipulated_frontend_collision.mar Binary files differnew file mode 100644 index 0000000000..582af58b59 --- /dev/null +++ b/modules/libmar/tests/unit/data/manipulated_frontend_collision.mar diff --git a/modules/libmar/tests/unit/data/manipulated_is_contained.mar b/modules/libmar/tests/unit/data/manipulated_is_contained.mar Binary files differnew file mode 100644 index 0000000000..d51b23587d --- /dev/null +++ b/modules/libmar/tests/unit/data/manipulated_is_contained.mar diff --git a/modules/libmar/tests/unit/data/manipulated_is_container.mar b/modules/libmar/tests/unit/data/manipulated_is_container.mar Binary files differnew file mode 100644 index 0000000000..98b33ce9e5 --- /dev/null +++ b/modules/libmar/tests/unit/data/manipulated_is_container.mar diff --git a/modules/libmar/tests/unit/data/manipulated_multiple_collision.mar b/modules/libmar/tests/unit/data/manipulated_multiple_collision.mar Binary files differnew file mode 100644 index 0000000000..7e0a3dd724 --- /dev/null +++ b/modules/libmar/tests/unit/data/manipulated_multiple_collision.mar diff --git a/modules/libmar/tests/unit/data/manipulated_multiple_collision_first.mar b/modules/libmar/tests/unit/data/manipulated_multiple_collision_first.mar Binary files differnew file mode 100644 index 0000000000..a10d3eb53b --- /dev/null +++ b/modules/libmar/tests/unit/data/manipulated_multiple_collision_first.mar diff --git a/modules/libmar/tests/unit/data/manipulated_multiple_collision_last.mar b/modules/libmar/tests/unit/data/manipulated_multiple_collision_last.mar Binary files differnew file mode 100644 index 0000000000..bfbb9ba853 --- /dev/null +++ b/modules/libmar/tests/unit/data/manipulated_multiple_collision_last.mar diff --git a/modules/libmar/tests/unit/data/manipulated_same_offset.mar b/modules/libmar/tests/unit/data/manipulated_same_offset.mar Binary files differnew file mode 100644 index 0000000000..1326d1afd8 --- /dev/null +++ b/modules/libmar/tests/unit/data/manipulated_same_offset.mar diff --git a/modules/libmar/tests/unit/data/manipulated_signed.mar b/modules/libmar/tests/unit/data/manipulated_signed.mar Binary files differnew file mode 100644 index 0000000000..df8b3b5dbb --- /dev/null +++ b/modules/libmar/tests/unit/data/manipulated_signed.mar diff --git a/modules/libmar/tests/unit/data/multiple_file.mar b/modules/libmar/tests/unit/data/multiple_file.mar Binary files differnew file mode 100644 index 0000000000..183493a368 --- /dev/null +++ b/modules/libmar/tests/unit/data/multiple_file.mar diff --git a/modules/libmar/tests/unit/data/multiple_signed_no_pib.mar b/modules/libmar/tests/unit/data/multiple_signed_no_pib.mar Binary files differnew file mode 100644 index 0000000000..fb56eef98e --- /dev/null +++ b/modules/libmar/tests/unit/data/multiple_signed_no_pib.mar diff --git a/modules/libmar/tests/unit/data/multiple_signed_pib.mar b/modules/libmar/tests/unit/data/multiple_signed_pib.mar Binary files differnew file mode 100644 index 0000000000..3624436cf5 --- /dev/null +++ b/modules/libmar/tests/unit/data/multiple_signed_pib.mar diff --git a/modules/libmar/tests/unit/data/multiple_signed_pib_2.mar b/modules/libmar/tests/unit/data/multiple_signed_pib_2.mar Binary files differnew file mode 100644 index 0000000000..edce42b854 --- /dev/null +++ b/modules/libmar/tests/unit/data/multiple_signed_pib_2.mar 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 Binary files differnew file mode 100644 index 0000000000..ea1fd47faa --- /dev/null +++ b/modules/libmar/tests/unit/data/mycert.der diff --git a/modules/libmar/tests/unit/data/mycert2.der b/modules/libmar/tests/unit/data/mycert2.der Binary files differnew file mode 100644 index 0000000000..d8cdfea972 --- /dev/null +++ b/modules/libmar/tests/unit/data/mycert2.der diff --git a/modules/libmar/tests/unit/data/mycert3.der b/modules/libmar/tests/unit/data/mycert3.der Binary files differnew file mode 100644 index 0000000000..b942d4d795 --- /dev/null +++ b/modules/libmar/tests/unit/data/mycert3.der diff --git a/modules/libmar/tests/unit/data/no_pib.mar b/modules/libmar/tests/unit/data/no_pib.mar Binary files differnew file mode 100644 index 0000000000..8976e7d737 --- /dev/null +++ b/modules/libmar/tests/unit/data/no_pib.mar diff --git a/modules/libmar/tests/unit/data/signed_no_pib.mar b/modules/libmar/tests/unit/data/signed_no_pib.mar Binary files differnew file mode 100644 index 0000000000..92d97fec51 --- /dev/null +++ b/modules/libmar/tests/unit/data/signed_no_pib.mar diff --git a/modules/libmar/tests/unit/data/signed_pib.mar b/modules/libmar/tests/unit/data/signed_pib.mar Binary files differnew file mode 100644 index 0000000000..1b8baa7969 --- /dev/null +++ b/modules/libmar/tests/unit/data/signed_pib.mar 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 Binary files differnew file mode 100644 index 0000000000..22a998e227 --- /dev/null +++ b/modules/libmar/tests/unit/data/signed_pib_with_mycert2.mar 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.ini b/modules/libmar/tests/unit/xpcshell.ini new file mode 100644 index 0000000000..c677d46438 --- /dev/null +++ b/modules/libmar/tests/unit/xpcshell.ini @@ -0,0 +1,8 @@ +[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..449c9f7efc --- /dev/null +++ b/modules/libmar/tool/mar.c @@ -0,0 +1,447 @@ +/* -*- 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(XP_WIN) || 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(XP_MACOSX) || (defined(XP_WIN) && !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) && defined(XP_WIN)) || defined(XP_MACOSX)) || \ + ((defined(XP_WIN) || defined(XP_MACOSX)) && !defined(MAR_NSS)) + char* DERFilePaths[MAX_SIGNATURES]; +# endif +# if (!defined(XP_WIN) && !defined(XP_MACOSX)) || defined(MAR_NSS) + CERTCertificate* certs[MAX_SIGNATURES]; +# endif +#endif + + memset((void*)certNames, 0, sizeof(certNames)); +#if defined(XP_WIN) && !defined(MAR_NSS) && !defined(NO_SIGN_VERIFY) + memset((void*)certBuffers, 0, sizeof(certBuffers)); +#endif +#if !defined(NO_SIGN_VERIFY) && \ + ((!defined(MAR_NSS) && defined(XP_WIN)) || defined(XP_MACOSX)) + memset(DERFilePaths, 0, sizeof(DERFilePaths)); + memset(fileSizes, 0, sizeof(fileSizes)); +#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) && defined(XP_WIN)) || defined(XP_MACOSX) + /* -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(XP_WIN) && !defined(XP_MACOSX)) || 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(XP_WIN) || defined(XP_MACOSX)) && !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(XP_WIN) || defined(XP_MACOSX)) && !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(XP_WIN) && !defined(XP_MACOSX)) || 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..0f25dcc10a --- /dev/null +++ b/modules/libmar/tool/moz.build @@ -0,0 +1,66 @@ +# -*- 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["OS_ARCH"] == "WINNT": + USE_STATIC_LIBS = True + + OS_LIBS += [ + "ws2_32", + "crypt32", + "advapi32", + ] + elif CONFIG["OS_ARCH"] == "Darwin": + 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..b07475655f --- /dev/null +++ b/modules/libmar/verify/moz.build @@ -0,0 +1,49 @@ +# -*- 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": + UNIFIED_SOURCES += [ + "MacVerifyCrypto.cpp", + ] + OS_LIBS += [ + "-framework Security", + ] +else: + DEFINES["MAR_NSS"] = True + LOCAL_INCLUDES += ["../sign"] + USE_LIBS += [ + "nspr", + "nss", + "signmar", + ] + # 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", +] + +# C11 for static_assert +c11_flags = ["-std=gnu11"] +if CONFIG["CC_TYPE"] == "clang-cl": + c11_flags.insert(0, "-Xclang") +CFLAGS += c11_flags |