mar.c (13718B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <string.h> 10 #include "mar.h" 11 #include "mar_cmdline.h" 12 13 #ifdef XP_WIN 14 # include <windows.h> 15 # include <direct.h> 16 # define chdir _chdir 17 #else 18 # include <unistd.h> 19 #endif 20 21 #if !defined(NO_SIGN_VERIFY) && defined(MAR_NSS) 22 # include "cert.h" 23 # include "nss.h" 24 # include "pk11pub.h" 25 int NSSInitCryptoContext(const char* NSSConfigDir); 26 #endif 27 28 int mar_repackage_and_sign(const char* NSSConfigDir, 29 const char* const* certNames, uint32_t certCount, 30 const char* src, const char* dest); 31 32 static void print_version() { 33 printf("Version: %s\n", MOZ_APP_VERSION); 34 printf("Default Channel ID: %s\n", MAR_CHANNEL_ID); 35 } 36 37 static void print_usage() { 38 printf("usage:\n"); 39 printf("Create a MAR file:\n"); 40 printf( 41 " mar -H MARChannelID -V ProductVersion [-C workingDir] " 42 "-c archive.mar [files...]\n"); 43 44 printf("Extract a MAR file:\n"); 45 printf(" mar [-C workingDir] -x archive.mar\n"); 46 #ifndef NO_SIGN_VERIFY 47 printf("Sign a MAR file:\n"); 48 printf( 49 " mar [-C workingDir] -d NSSConfigDir -n certname -s " 50 "archive.mar out_signed_archive.mar\n"); 51 52 printf("Strip a MAR signature:\n"); 53 printf( 54 " mar [-C workingDir] -r " 55 "signed_input_archive.mar output_archive.mar\n"); 56 57 printf("Extract a MAR signature:\n"); 58 printf( 59 " mar [-C workingDir] -n(i) -X " 60 "signed_input_archive.mar base_64_encoded_signature_file\n"); 61 62 printf("Import a MAR signature:\n"); 63 printf( 64 " mar [-C workingDir] -n(i) -I " 65 "signed_input_archive.mar base_64_encoded_signature_file " 66 "changed_signed_output.mar\n"); 67 printf("(i) is the index of the certificate to extract\n"); 68 # if !defined(MAR_NSS) 69 printf("Verify a MAR file:\n"); 70 printf(" mar [-C workingDir] -D DERFilePath -v signed_archive.mar\n"); 71 printf( 72 "At most %d signature certificate DER files are specified by " 73 "-D0 DERFilePath1 -D1 DERFilePath2, ...\n", 74 MAX_SIGNATURES); 75 # else 76 printf("Verify a MAR file:\n"); 77 printf( 78 " mar [-C workingDir] -d NSSConfigDir -n certname " 79 "-v signed_archive.mar\n"); 80 printf( 81 "At most %d signature certificate names are specified by " 82 "-n0 certName -n1 certName2, ...\n", 83 MAX_SIGNATURES); 84 # endif 85 printf( 86 "At most %d verification certificate names are specified by " 87 "-n0 certName -n1 certName2, ...\n", 88 MAX_SIGNATURES); 89 #endif 90 printf("Print information on a MAR file:\n"); 91 printf(" mar -t archive.mar\n"); 92 93 printf("Print detailed information on a MAR file including signatures:\n"); 94 printf(" mar -T archive.mar\n"); 95 96 printf("Refresh the product information block of a MAR file:\n"); 97 printf( 98 " mar -H MARChannelID -V ProductVersion [-C workingDir] " 99 "-i unsigned_archive_to_refresh.mar\n"); 100 101 printf("Print executable version:\n"); 102 printf(" mar --version\n"); 103 printf("This program does not handle unicode file paths properly\n"); 104 } 105 106 static int mar_test_callback(MarFile* mar, const MarItem* item, void* unused) { 107 printf("%u\t0%o\t%s\n", item->length, item->flags, item->name); 108 return 0; 109 } 110 111 static int mar_test(const char* path) { 112 MarFile* mar; 113 114 MarReadResult result = mar_open(path, &mar); 115 if (result != MAR_READ_SUCCESS) { 116 return -1; 117 } 118 119 printf("SIZE\tMODE\tNAME\n"); 120 mar_enum_items(mar, mar_test_callback, NULL); 121 122 mar_close(mar); 123 return 0; 124 } 125 126 int main(int argc, char** argv) { 127 const char* certNames[MAX_SIGNATURES]; 128 char* MARChannelID = NULL; 129 char* productVersion = NULL; 130 int rv = -1; 131 #if !defined(NO_SIGN_VERIFY) 132 char* NSSConfigDir = NULL; 133 uint32_t k; 134 uint32_t certCount = 0; 135 int32_t sigIndex = -1; 136 uint32_t fileSizes[MAX_SIGNATURES]; 137 const uint8_t* certBuffers[MAX_SIGNATURES]; 138 # if !defined(MAR_NSS) 139 char* DERFilePaths[MAX_SIGNATURES]; 140 # else 141 CERTCertificate* certs[MAX_SIGNATURES]; 142 # endif 143 #endif 144 145 memset((void*)certNames, 0, sizeof(certNames)); 146 #if !defined(NO_SIGN_VERIFY) 147 memset(fileSizes, 0, sizeof(fileSizes)); 148 memset((void*)certBuffers, 0, sizeof(certBuffers)); 149 # if !defined(MAR_NSS) 150 memset(DERFilePaths, 0, sizeof(DERFilePaths)); 151 # else 152 memset(certs, 0, sizeof(certs)); 153 # endif 154 #endif 155 156 if (argc > 1 && 0 == strcmp(argv[1], "--version")) { 157 print_version(); 158 return 0; 159 } 160 161 if (argc < 3) { 162 print_usage(); 163 return -1; 164 } 165 166 while (argc > 0) { 167 if (argv[1][0] == '-' && 168 (argv[1][1] == 'c' || argv[1][1] == 't' || argv[1][1] == 'x' || 169 argv[1][1] == 'v' || argv[1][1] == 's' || argv[1][1] == 'i' || 170 argv[1][1] == 'T' || argv[1][1] == 'r' || argv[1][1] == 'X' || 171 argv[1][1] == 'I')) { 172 break; 173 /* -C workingdirectory */ 174 } 175 if (argv[1][0] == '-' && argv[1][1] == 'C') { 176 if (chdir(argv[2]) != 0) { 177 return -1; 178 } 179 argv += 2; 180 argc -= 2; 181 } 182 #if !defined(NO_SIGN_VERIFY) 183 # if !defined(MAR_NSS) 184 /* -D DERFilePath, also matches -D[index] DERFilePath 185 We allow an index for verifying to be symmetric 186 with the import and export command line arguments. */ 187 else if (argv[1][0] == '-' && argv[1][1] == 'D' && 188 (argv[1][2] == (char)('0' + certCount) || argv[1][2] == '\0')) { 189 if (certCount >= MAX_SIGNATURES) { 190 print_usage(); 191 return -1; 192 } 193 DERFilePaths[certCount++] = argv[2]; 194 argv += 2; 195 argc -= 2; 196 } 197 # endif 198 /* -d NSSConfigdir */ 199 else if (argv[1][0] == '-' && argv[1][1] == 'd') { 200 NSSConfigDir = argv[2]; 201 argv += 2; 202 argc -= 2; 203 /* -n certName, also matches -n[index] certName 204 We allow an index for verifying to be symmetric 205 with the import and export command line arguments. */ 206 } else if (argv[1][0] == '-' && argv[1][1] == 'n' && 207 (argv[1][2] == (char)('0' + certCount) || argv[1][2] == '\0' || 208 !strcmp(argv[2], "-X") || !strcmp(argv[2], "-I"))) { 209 if (certCount >= MAX_SIGNATURES) { 210 print_usage(); 211 return -1; 212 } 213 certNames[certCount++] = argv[2]; 214 if (strlen(argv[1]) > 2 && 215 (!strcmp(argv[2], "-X") || !strcmp(argv[2], "-I")) && 216 argv[1][2] >= '0' && argv[1][2] <= '9') { 217 sigIndex = argv[1][2] - '0'; 218 argv++; 219 argc--; 220 } else { 221 argv += 2; 222 argc -= 2; 223 } 224 } 225 #endif 226 else if (argv[1][0] == '-' && argv[1][1] == 'H') { // MAR channel ID 227 MARChannelID = argv[2]; 228 argv += 2; 229 argc -= 2; 230 } else if (argv[1][0] == '-' && argv[1][1] == 'V') { // Product Version 231 productVersion = argv[2]; 232 argv += 2; 233 argc -= 2; 234 } else { 235 print_usage(); 236 return -1; 237 } 238 } 239 240 if (argv[1][0] != '-') { 241 print_usage(); 242 return -1; 243 } 244 245 switch (argv[1][1]) { 246 case 'c': { 247 struct ProductInformationBlock infoBlock; 248 if (!productVersion) { 249 fprintf(stderr, 250 "ERROR: Version not specified (pass `-V <version>`).\n"); 251 return -1; 252 } 253 if (!MARChannelID) { 254 fprintf(stderr, 255 "ERROR: MAR channel ID not specified (pass `-H " 256 "<mar-channel-id>`).\n"); 257 return -1; 258 } 259 infoBlock.MARChannelID = MARChannelID; 260 infoBlock.productVersion = productVersion; 261 return mar_create(argv[2], argc - 3, argv + 3, &infoBlock); 262 } 263 case 'i': { 264 if (!productVersion) { 265 fprintf(stderr, 266 "ERROR: Version not specified (pass `-V <version>`).\n"); 267 return -1; 268 } 269 if (!MARChannelID) { 270 fprintf(stderr, 271 "ERROR: MAR channel ID not specified (pass `-H " 272 "<mar-channel-id>`).\n"); 273 return -1; 274 } 275 struct ProductInformationBlock infoBlock; 276 infoBlock.MARChannelID = MARChannelID; 277 infoBlock.productVersion = productVersion; 278 return refresh_product_info_block(argv[2], &infoBlock); 279 } 280 case 'T': { 281 struct ProductInformationBlock infoBlock; 282 uint32_t numSignatures, numAdditionalBlocks; 283 int hasSignatureBlock, hasAdditionalBlock; 284 if (!get_mar_file_info(argv[2], &hasSignatureBlock, &numSignatures, 285 &hasAdditionalBlock, NULL, &numAdditionalBlocks)) { 286 if (hasSignatureBlock) { 287 printf("Signature block found with %d signature%s\n", numSignatures, 288 numSignatures != 1 ? "s" : ""); 289 } 290 if (hasAdditionalBlock) { 291 printf("%d additional block%s found:\n", numAdditionalBlocks, 292 numAdditionalBlocks != 1 ? "s" : ""); 293 } 294 295 rv = read_product_info_block(argv[2], &infoBlock); 296 if (!rv) { 297 printf(" - Product Information Block:\n"); 298 printf( 299 " - MAR channel name: %s\n" 300 " - Product version: %s\n", 301 infoBlock.MARChannelID, infoBlock.productVersion); 302 free((void*)infoBlock.MARChannelID); 303 free((void*)infoBlock.productVersion); 304 } 305 } 306 printf("\n"); 307 /* The fall through from 'T' to 't' is intentional */ 308 } 309 case 't': 310 return mar_test(argv[2]); 311 312 case 'x': // Extract a MAR file 313 return mar_extract(argv[2]); 314 315 #ifndef NO_SIGN_VERIFY 316 case 'X': // Extract a MAR signature 317 if (sigIndex == -1) { 318 fprintf(stderr, "ERROR: Signature index was not passed.\n"); 319 return -1; 320 } 321 if (sigIndex >= MAX_SIGNATURES || sigIndex < -1) { 322 fprintf(stderr, "ERROR: Signature index is out of range: %d.\n", 323 sigIndex); 324 return -1; 325 } 326 return extract_signature(argv[2], sigIndex, argv[3]); 327 328 case 'I': // Import a MAR signature 329 if (sigIndex == -1) { 330 fprintf(stderr, "ERROR: signature index was not passed.\n"); 331 return -1; 332 } 333 if (sigIndex >= MAX_SIGNATURES || sigIndex < -1) { 334 fprintf(stderr, "ERROR: Signature index is out of range: %d.\n", 335 sigIndex); 336 return -1; 337 } 338 if (argc < 5) { 339 print_usage(); 340 return -1; 341 } 342 return import_signature(argv[2], sigIndex, argv[3], argv[4]); 343 344 case 'v': 345 if (certCount == 0) { 346 print_usage(); 347 return -1; 348 } 349 350 # if defined(MAR_NSS) 351 if (!NSSConfigDir || certCount == 0) { 352 print_usage(); 353 return -1; 354 } 355 356 if (NSSInitCryptoContext(NSSConfigDir)) { 357 fprintf(stderr, "ERROR: Could not initialize crypto library.\n"); 358 return -1; 359 } 360 # endif 361 362 rv = 0; 363 for (k = 0; k < certCount; ++k) { 364 # if !defined(MAR_NSS) 365 rv = mar_read_entire_file(DERFilePaths[k], MAR_MAX_CERT_SIZE, 366 &certBuffers[k], &fileSizes[k]); 367 368 if (rv) { 369 fprintf(stderr, "ERROR: could not read file %s", DERFilePaths[k]); 370 break; 371 } 372 # else 373 /* It is somewhat circuitous to look up a CERTCertificate and then pass 374 * in its DER encoding just so we can later re-create that 375 * CERTCertificate to extract the public key out of it. However, by 376 * doing things this way, we maximize the reuse of the 377 * mar_verify_signatures function and also we keep the control flow as 378 * similar as possible between programs and operating systems, at least 379 * for the functions that are critically important to security. 380 */ 381 certs[k] = PK11_FindCertFromNickname(certNames[k], NULL); 382 if (certs[k]) { 383 certBuffers[k] = certs[k]->derCert.data; 384 fileSizes[k] = certs[k]->derCert.len; 385 } else { 386 rv = -1; 387 fprintf(stderr, "ERROR: could not find cert from nickname %s", 388 certNames[k]); 389 break; 390 } 391 # endif 392 } 393 394 if (!rv) { 395 MarFile* mar; 396 MarReadResult result = mar_open(argv[2], &mar); 397 if (result == MAR_READ_SUCCESS) { 398 rv = mar_verify_signatures(mar, certBuffers, fileSizes, certCount); 399 mar_close(mar); 400 } else { 401 fprintf(stderr, "ERROR: Could not open MAR file.\n"); 402 rv = -1; 403 } 404 } 405 for (k = 0; k < certCount; ++k) { 406 # if !defined(MAR_NSS) 407 free((void*)certBuffers[k]); 408 # else 409 /* certBuffers[k] is owned by certs[k] so don't free it */ 410 CERT_DestroyCertificate(certs[k]); 411 # endif 412 } 413 if (rv) { 414 /* Determine if the source MAR file has the new fields for signing */ 415 int hasSignatureBlock; 416 if (get_mar_file_info(argv[2], &hasSignatureBlock, NULL, NULL, NULL, 417 NULL)) { 418 fprintf(stderr, "ERROR: could not determine if MAR is old or new.\n"); 419 } else if (!hasSignatureBlock) { 420 fprintf(stderr, 421 "ERROR: The MAR file is in the old format so has" 422 " no signature to verify.\n"); 423 } 424 } 425 # if defined(MAR_NSS) 426 (void)NSS_Shutdown(); 427 # endif 428 return rv ? -1 : 0; 429 430 case 's': 431 if (!NSSConfigDir || certCount == 0 || argc < 4) { 432 print_usage(); 433 return -1; 434 } 435 return mar_repackage_and_sign(NSSConfigDir, certNames, certCount, argv[2], 436 argv[3]); 437 438 case 'r': 439 return strip_signature_block(argv[2], argv[3]); 440 #endif /* endif NO_SIGN_VERIFY disabled */ 441 442 default: 443 print_usage(); 444 return -1; 445 } 446 }