diff --git a/modules/libmar/sign/mar_sign.c b/modules/libmar/sign/mar_sign.c index 3d80c94beb8..a68fe9a5361 100644 --- a/modules/libmar/sign/mar_sign.c +++ b/modules/libmar/sign/mar_sign.c @@ -230,7 +230,7 @@ mar_repackage_and_sign(const char *NSSConfigDir, signaturePlaceholderOffset, numBytesToCopy, numChunks, i; FILE *fpSrc = NULL, *fpDest = NULL; - int rv = -1, oldMar; + int rv = -1, hasSignatureBlock; SGNContext *ctx = NULL; SECItem secItem; char buf[BLOCKSIZE]; @@ -268,7 +268,7 @@ mar_repackage_and_sign(const char *NSSConfigDir, } /* Determine if the source MAR file has the new fields for signing or not */ - if (is_old_mar(src, &oldMar)) { + 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; } @@ -299,7 +299,7 @@ mar_repackage_and_sign(const char *NSSConfigDir, goto failure; } - if (!oldMar) { + if (hasSignatureBlock) { /* Get the MAR length and adjust its size */ if (fread(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fpSrc) != 1) { @@ -338,7 +338,7 @@ mar_repackage_and_sign(const char *NSSConfigDir, sizeof(signatureLength) + signatureLength; dstOffsetToIndex = offsetToIndex; - if (oldMar) { + if (!hasSignatureBlock) { dstOffsetToIndex += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); } dstOffsetToIndex += signatureSectionLength; @@ -353,7 +353,7 @@ mar_repackage_and_sign(const char *NSSConfigDir, /* Write out the new MAR file size */ sizeOfEntireMAR += signatureSectionLength; - if (oldMar) { + if (!hasSignatureBlock) { sizeOfEntireMAR += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); } @@ -445,7 +445,7 @@ mar_repackage_and_sign(const char *NSSConfigDir, /* Adjust the offset */ offsetToContent = (PRUint32 *)indexBufLoc; *offsetToContent = ntohl(*offsetToContent); - if (oldMar) { + if (!hasSignatureBlock) { *offsetToContent += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); } *offsetToContent += signatureSectionLength; diff --git a/modules/libmar/src/mar.h b/modules/libmar/src/mar.h index bd6a859b7e3..419840dd159 100644 --- a/modules/libmar/src/mar.h +++ b/modules/libmar/src/mar.h @@ -46,6 +46,11 @@ extern "C" { #endif +struct ProductInformationBlock { + const char *MARChannelID; + const char *productVersion; +}; + /** * The MAR item data structure. */ @@ -71,7 +76,7 @@ typedef struct MarFile_ MarFile; * @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. - * @returns A non-zero value to stop enumerating. + * @return A non-zero value to stop enumerating. */ typedef int (* MarItemCallback)(MarFile *mar, const MarItem *item, void *data); @@ -79,7 +84,7 @@ typedef int (* MarItemCallback)(MarFile *mar, const MarItem *item, void *data); * Open a MAR file for reading. * @param path Specifies the path to the MAR file to open. This path must * be compatible with fopen. - * @returns NULL if an error occurs. + * @return NULL if an error occurs. */ MarFile *mar_open(const char *path); @@ -97,7 +102,7 @@ void mar_close(MarFile *mar); * 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. - * @returns A const reference to a MAR item or NULL if not found. + * @return A const reference to a MAR item or NULL if not found. */ const MarItem *mar_find_item(MarFile *mar, const char *item); @@ -107,7 +112,7 @@ const MarItem *mar_find_item(MarFile *mar, const char *item); * @param callback The function to call for each MAR item. * @param data A caller specified value that is passed along to the * callback function. - * @returns Zero if the enumeration ran to completion. Otherwise, any + * @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); @@ -119,7 +124,7 @@ int mar_enum_items(MarFile *mar, MarItemCallback callback, void *data); * @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. - * @returns The number of bytes written or a negative value if an + * @return The number of bytes written or a negative value if an * error occurs. */ int mar_read(MarFile *mar, const MarItem *item, int offset, char *buf, @@ -132,15 +137,19 @@ int mar_read(MarFile *mar, const MarItem *item, int offset, char *buf, * @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. - * @returns A non-zero value if an error occurs. + * @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); +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. - * @returns A non-zero value if an error occurs. + * @return A non-zero value if an error occurs. */ int mar_extract(const char *path); @@ -163,6 +172,18 @@ int mar_verify_signatureW(MarFile *mar, PRUint32 sizeOfCertData); #endif +/** + * 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 diff --git a/modules/libmar/src/mar_cmdline.h b/modules/libmar/src/mar_cmdline.h index 5a6d4e7b400..f3136a5efcc 100644 --- a/modules/libmar/src/mar_cmdline.h +++ b/modules/libmar/src/mar_cmdline.h @@ -46,14 +46,30 @@ extern "C" { #endif /** - * Determines if the MAR file is new or old. - * - * @param path The path of the MAR file to check. - * @param oldMar An out parameter specifying if the MAR file is new or old. - * @return A non-zero value if an error occurred and the information - cannot be determined. + * Determines MAR file information. + * + * @param path The path of the MAR file to check. + * @param hasSignatureBlock Optional out parameter specifying if the MAR + * file is 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 + * has_additional_blocks + * 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 euqal to 0. + * @return 0 on success and non-zero on failure. */ -int is_old_mar(const char *path, int *oldMar); +int get_mar_file_info(const char *path, + int *hasSignatureBlock, + int *numSignatures, + int *hasAdditionalBlocks, + int *offsetAdditionalBlocks, + int *numAdditionalBlocks); /** * Verifies the embedded signature of the specified file path. @@ -75,6 +91,18 @@ int mar_verify_signature(const char *pathToMAR, PRUint32 sizeOfCertData, const char *certName); +/** + * 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); + #ifdef __cplusplus } #endif diff --git a/modules/libmar/src/mar_create.c b/modules/libmar/src/mar_create.c index a15c6a1db08..68fde5b9e4c 100644 --- a/modules/libmar/src/mar_create.c +++ b/modules/libmar/src/mar_create.c @@ -21,6 +21,7 @@ * * Contributor(s): * Darin Fisher + * Brian R. Bondy * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -42,6 +43,7 @@ #include #include #include "mar_private.h" +#include "mar_cmdline.h" #include "mar.h" #ifdef XP_WIN @@ -124,10 +126,206 @@ static int mar_concat_file(FILE *fp, const char *path) { return rv; } -int mar_create(const char *dest, int num_files, char **files) { +/** + * 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 product_info 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]; + PRUint32 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 who's 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; + PRUint32 hasSignatureBlock, numSignatures, additionalBlocks, + additionalBlockSize, additionalBlockID, offsetAdditionalBlocks, + numAdditionalBlocks, i; + PRInt64 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) { + return -1; + } + additionalBlockSize = ntohl(additionalBlockSize); + + /* Read the additional block ID */ + if (fread(&additionalBlockID, + sizeof(additionalBlockID), + 1, fp) != 1) { + 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; - PRUint32 offset_to_index = 0, size_of_index, num_signatures; - PRUint64 size_of_entire_MAR = 0; + PRUint32 offset_to_index = 0, size_of_index, + numSignatures, numAdditionalSections; + PRUint64 sizeOfEntireMAR = 0; struct stat st; FILE *fp; int i, rv = -1; @@ -146,18 +344,32 @@ int mar_create(const char *dest, int num_files, char **files) { goto failure; stack.last_offset = MAR_ID_SIZE + - sizeof(num_signatures) + sizeof(offset_to_index) + - sizeof(size_of_entire_MAR); + sizeof(numSignatures) + + sizeof(numAdditionalSections) + + sizeof(sizeOfEntireMAR); /* We will circle back on this at the end of the MAR creation to fill it */ - if (fwrite(&size_of_entire_MAR, sizeof(size_of_entire_MAR), 1, fp) != 1) { + if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) { goto failure; } /* Write out the number of signatures, for now only at most 1 is supported */ - num_signatures = 0; - if (fwrite(&num_signatures, sizeof(num_signatures), 1, fp) != 1) { + 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; } @@ -196,13 +408,13 @@ int mar_create(const char *dest, int num_files, char **files) { goto failure; offset_to_index = ntohl(stack.last_offset); - size_of_entire_MAR = ((PRUint64)stack.last_offset) + - stack.size_used + - sizeof(size_of_index); - size_of_entire_MAR = HOST_TO_NETWORK64(size_of_entire_MAR); - if (fwrite(&size_of_entire_MAR, sizeof(size_of_entire_MAR), 1, fp) != 1) + sizeOfEntireMAR = ((PRUint64)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; - size_of_entire_MAR = NETWORK_TO_HOST64(size_of_entire_MAR); + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); rv = 0; failure: diff --git a/modules/libmar/src/mar_private.h b/modules/libmar/src/mar_private.h index ac8db0b47e1..b30b929c2c3 100644 --- a/modules/libmar/src/mar_private.h +++ b/modules/libmar/src/mar_private.h @@ -78,8 +78,16 @@ PR_STATIC_ASSERT(sizeof(BLOCKSIZE) < \ 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(PRUint32) + (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. */ diff --git a/modules/libmar/src/mar_read.c b/modules/libmar/src/mar_read.c index 9dfedb7499c..f2b6b791353 100644 --- a/modules/libmar/src/mar_read.c +++ b/modules/libmar/src/mar_read.c @@ -235,6 +235,125 @@ void mar_close(MarFile *mar) { free(mar); } +/** + * 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; + mar.fp = fopen(path, "rb"); + if (!mar.fp) { + return -1; + } + rv = mar_read_product_info_block(&mar, infoBlock); + fclose(mar.fp); + 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) +{ + int i, hasAdditionalBlocks, offset, + offsetAdditionalBlocks, numAdditionalBlocks, + additionalBlockSize, additionalBlockID; + /* The buffer size is 97 bytes because the MAR channel name < 64 bytes, and + product version < 32 bytes + 3 NULL terminator bytes. */ + char buf[97] = { '\0' }; + int ret = get_mar_file_info_fp(mar->fp, NULL, NULL, + &hasAdditionalBlocks, + &offsetAdditionalBlocks, + &numAdditionalBlocks); + for (i = 0; i < numAdditionalBlocks; ++i) { + /* Read the additional block size */ + if (fread(&additionalBlockSize, + sizeof(additionalBlockSize), + 1, mar->fp) != 1) { + return -1; + } + additionalBlockSize = ntohl(additionalBlockSize) - + sizeof(additionalBlockSize) - + sizeof(additionalBlockID); + + /* Read the additional block ID */ + if (fread(&additionalBlockID, + sizeof(additionalBlockID), + 1, mar->fp) != 1) { + return -1; + } + additionalBlockID = ntohl(additionalBlockID); + + if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) { + const char *location; + int len; + + /* 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) */ + if (additionalBlockSize > 96) { + return -1; + } + + if (fread(buf, additionalBlockSize, 1, mar->fp) != 1) { + 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; + location += len + 1; + 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 (fseek(mar->fp, additionalBlockSize, SEEK_CUR)) { + 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) { PRUint32 hash; const MarItem *item; @@ -284,29 +403,81 @@ int mar_read(MarFile *mar, const MarItem *item, int offset, char *buf, return fread(buf, 1, nr, mar->fp); } -/** - * Determines if the MAR file is new or old. - * - * @param path The path of the MAR file to check. - * @param oldMar An out parameter specifying if the MAR file is new or old. - * @return A non-zero value if an error occurred and the information - cannot be determined. - */ -int is_old_mar(const char *path, int *oldMar) -{ - PRUint32 offsetToIndex, offsetToContent, oldPos; - FILE *fp; - - if (!oldMar) { - return -1; - } - fp = fopen(path, "rb"); +/** + * 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 is 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 + * has_additional_blocks + * 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 euqal to 0. + * @return 0 on success and non-zero on failure. + */ +int get_mar_file_info(const char *path, + int *hasSignatureBlock, + int *numSignatures, + int *hasAdditionalBlocks, + int *offsetAdditionalBlocks, + int *numAdditionalBlocks) +{ + int rv; + FILE *fp = fopen(path, "rb"); if (!fp) { return -1; } - oldPos = ftell(fp); + rv = get_mar_file_info_fp(fp, hasSignatureBlock, + numSignatures, hasAdditionalBlocks, + offsetAdditionalBlocks, numAdditionalBlocks); + + fclose(fp); + return rv; +} + +/** + * Determines the MAR file information. + * + * @param fp An opened MAR file in read mode. + * @param hasSignatureBlock Optional out parameter specifying if the MAR + * file is 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 + * has_additional_blocks + * 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 euqal to 0. + * @return 0 on success and non-zero on failure. + */ +int get_mar_file_info_fp(FILE *fp, + int *hasSignatureBlock, + int *numSignatures, + int *hasAdditionalBlocks, + int *offsetAdditionalBlocks, + int *numAdditionalBlocks) +{ + PRUint32 offsetToIndex, offsetToContent, signatureCount, signatureLen; + int i; + + /* One of hasSignatureBlock or hasAdditionalBlocks must be non NULL */ + if (!hasSignatureBlock && !hasAdditionalBlocks) { + return -1; + } + /* Skip to the start of the offset index */ if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) { @@ -314,10 +485,24 @@ int is_old_mar(const char *path, int *oldMar) } /* Read the offset to the index. */ - if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fp) != 1) + if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fp) != 1) { return -1; + } offsetToIndex = ntohl(offsetToIndex); + if (numSignatures) { + /* Skip past the MAR file size field */ + if (fseek(fp, sizeof(PRUint64), SEEK_CUR)) { + return -1; + } + + /* Read the offset to the index. */ + if (fread(numSignatures, sizeof(*numSignatures), 1, fp) != 1) { + 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(PRUint32) could oerflow in theory. */ @@ -329,22 +514,81 @@ int is_old_mar(const char *path, int *oldMar) return -1; } - /* Read the offset to content field. */ - if (fread(&offsetToContent, sizeof(offsetToContent), 1, fp) != 1) + /* Read the first offset to content field. */ + if (fread(&offsetToContent, sizeof(offsetToContent), 1, fp) != 1) { return -1; + } offsetToContent = ntohl(offsetToContent); /* Check if we have a new or old MAR file */ - if (offsetToContent == MAR_ID_SIZE + sizeof(PRUint32)) { - *oldMar = 1; - } else { - *oldMar = 0; + if (hasSignatureBlock) { + if (offsetToContent == MAR_ID_SIZE + sizeof(PRUint32)) { + *hasSignatureBlock = 0; + } else { + *hasSignatureBlock = 1; + } } - /* Restore back our old position */ - if (fseek(fp, oldPos, SEEK_SET)) { + /* 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 */ + if (fseeko(fp, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) { return -1; } + /* Get the number of signatures */ + if (fread(&signatureCount, sizeof(signatureCount), 1, fp) != 1) { + 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 (fseek(fp, sizeof(PRUint32), SEEK_CUR)) { + return -1; + } + + /* Read the signature length and skip past the signature */ + if (fread(&signatureLen, sizeof(PRUint32), 1, fp) != 1) { + return -1; + } + signatureLen = ntohl(signatureLen); + if (fseek(fp, signatureLen, SEEK_CUR)) { + return -1; + } + } + + if (ftell(fp) == 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 (fread(numAdditionalBlocks, sizeof(PRUint32), 1, fp) != 1) { + return -1; + } + *numAdditionalBlocks = ntohl(*numAdditionalBlocks); + if (offsetAdditionalBlocks) { + *offsetAdditionalBlocks = ftell(fp); + } + } else if (offsetAdditionalBlocks) { + /* numAdditionalBlocks is not specified but offsetAdditionalBlocks + is, so fill it! */ + *offsetAdditionalBlocks = ftell(fp) + sizeof(PRUint32); + } + } + return 0; } diff --git a/modules/libmar/tool/Makefile.in b/modules/libmar/tool/Makefile.in index 9a221dd9cd3..c6026d8cd76 100644 --- a/modules/libmar/tool/Makefile.in +++ b/modules/libmar/tool/Makefile.in @@ -54,7 +54,15 @@ endif HOST_PROGRAM = mar$(HOST_BIN_SUFFIX) PROGRAM = signmar$(BIN_SUFFIX) -HOST_CFLAGS += -DNO_SIGN_VERIFY +DEFINES += \ + -DMAR_CHANNEL_ID='"$(MAR_CHANNEL_ID)"' \ + -DMOZ_APP_VERSION='"$(MOZ_APP_VERSION)"' \ + $(NULL) + +HOST_CFLAGS += \ + -DNO_SIGN_VERIFY \ + $(DEFINES) \ + $(NULL) HOST_CSRCS = \ mar.c \ diff --git a/modules/libmar/tool/mar.c b/modules/libmar/tool/mar.c index 90ba500134f..cdbf69c4c63 100644 --- a/modules/libmar/tool/mar.c +++ b/modules/libmar/tool/mar.c @@ -60,7 +60,8 @@ int mar_repackage_and_sign(const char *NSSConfigDir, static void print_usage() { printf("usage:\n"); - printf(" mar [-C workingDir] {-c|-x|-t} archive.mar [files...]\n"); + printf(" mar [-H MARChannelID] [-V ProductVersion] [-C workingDir] " + "{-c|-x|-t|-T} archive.mar [files...]\n"); #ifndef NO_SIGN_VERIFY printf(" mar [-C workingDir] -d NSSConfigDir -n certname -s " "archive.mar out_signed_archive.mar\n"); @@ -72,6 +73,8 @@ static void print_usage() { "-v signed_archive.mar\n"); #endif #endif + printf(" mar [-H MARChannelID] [-V ProductVersion] [-C workingDir] " + "-i unsigned_archive_to_refresh.mar\n"); } static int mar_test_callback(MarFile *mar, @@ -98,6 +101,8 @@ static int mar_test(const char *path) { int main(int argc, char **argv) { char *NSSConfigDir = NULL; char *certName = NULL; + char *MARChannelID = MAR_CHANNEL_ID; + char *productVersion = MOZ_APP_VERSION; #if defined(XP_WIN) && !defined(MAR_NSS) && !defined(NO_SIGN_VERIFY) HANDLE certFile; DWORD fileSize; @@ -114,7 +119,8 @@ int main(int argc, char **argv) { 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] == 'v' || argv[1][1] == 's' || + argv[1][1] == 'i' || argv[1][1] == 'T')) { break; /* -C workingdirectory */ } else if (argv[1][0] == '-' && argv[1][1] == 'C') { @@ -140,7 +146,18 @@ int main(int argc, char **argv) { certName = argv[2]; argv += 2; argc -= 2; - } else { + /* MAR channel ID */ + } else if (argv[1][0] == '-' && argv[1][1] == 'H') { + MARChannelID = argv[2]; + argv += 2; + argc -= 2; + /* Product Version */ + } else if (argv[1][0] == '-' && argv[1][1] == 'V') { + productVersion = argv[2]; + argv += 2; + argc -= 2; + } + else { print_usage(); return -1; } @@ -152,10 +169,56 @@ int main(int argc, char **argv) { } switch (argv[1][1]) { - case 'c': - return mar_create(argv[2], argc - 3, argv + 3); + case 'c': { + struct ProductInformationBlock infoBlock; + infoBlock.MARChannelID = MARChannelID; + infoBlock.productVersion = productVersion; + return mar_create(argv[2], argc - 3, argv + 3, &infoBlock); + } + case 'i': { + struct ProductInformationBlock infoBlock; + infoBlock.MARChannelID = MARChannelID; + infoBlock.productVersion = productVersion; + return refresh_product_info_block(argv[2], &infoBlock); + } + case 'T': { + int rv; + struct ProductInformationBlock infoBlock; + int hasSignatureBlock, numSignatures, + hasAdditionalBlock, numAdditionalBlocks; + 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': return mar_extract(argv[2]); @@ -190,13 +253,13 @@ int main(int argc, char **argv) { CloseHandle(certFile); if (mar_verify_signature(argv[2], certBuffer, fileSize, NULL)) { - int oldMar = 0; + /* Determine if the source MAR file has the new fields for signing */ + int hasSignatureBlock; free(certBuffer); - - /* Determine if the source MAR file has the new fields for signing or not */ - if (is_old_mar(argv[2], &oldMar)) { + 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 (oldMar) { + } else if (!hasSignatureBlock) { fprintf(stderr, "ERROR: The MAR file is in the old format so has" " no signature to verify.\n"); }