diff options
Diffstat (limited to 'security/nss/cmd/signtool')
-rw-r--r-- | security/nss/cmd/signtool/Makefile | 44 | ||||
-rw-r--r-- | security/nss/cmd/signtool/README | 128 | ||||
-rw-r--r-- | security/nss/cmd/signtool/certgen.c | 713 | ||||
-rw-r--r-- | security/nss/cmd/signtool/javascript.c | 1817 | ||||
-rw-r--r-- | security/nss/cmd/signtool/list.c | 215 | ||||
-rw-r--r-- | security/nss/cmd/signtool/manifest.mn | 25 | ||||
-rw-r--r-- | security/nss/cmd/signtool/sign.c | 834 | ||||
-rw-r--r-- | security/nss/cmd/signtool/signtool.c | 1068 | ||||
-rw-r--r-- | security/nss/cmd/signtool/signtool.gyp | 33 | ||||
-rw-r--r-- | security/nss/cmd/signtool/signtool.h | 113 | ||||
-rw-r--r-- | security/nss/cmd/signtool/util.c | 1086 | ||||
-rw-r--r-- | security/nss/cmd/signtool/verify.c | 337 | ||||
-rw-r--r-- | security/nss/cmd/signtool/zip.c | 676 | ||||
-rw-r--r-- | security/nss/cmd/signtool/zip.h | 69 |
14 files changed, 7158 insertions, 0 deletions
diff --git a/security/nss/cmd/signtool/Makefile b/security/nss/cmd/signtool/Makefile new file mode 100644 index 000000000..408ee6da4 --- /dev/null +++ b/security/nss/cmd/signtool/Makefile @@ -0,0 +1,44 @@ +#! gmake +# +# 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/. + +####################################################################### +# (1) Include initial platform-independent assignments (MANDATORY). # +####################################################################### + +include manifest.mn + +####################################################################### +# (2) Include "global" configuration information. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/config.mk + +####################################################################### +# (3) Include "component" configuration information. (OPTIONAL) # +####################################################################### + +####################################################################### +# (4) Include "local" platform-dependent assignments (OPTIONAL). # +####################################################################### + +include ../platlibs.mk +include $(CORE_DEPTH)/coreconf/zlib.mk + +####################################################################### +# (5) Execute "global" rules. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/rules.mk + +####################################################################### +# (6) Execute "component" rules. (OPTIONAL) # +####################################################################### + +####################################################################### +# (7) Execute "local" rules. (OPTIONAL). # +####################################################################### + +include ../platrules.mk diff --git a/security/nss/cmd/signtool/README b/security/nss/cmd/signtool/README new file mode 100644 index 000000000..100fb2778 --- /dev/null +++ b/security/nss/cmd/signtool/README @@ -0,0 +1,128 @@ + Signing Tool (signtool) + 3.10 Release Notes + ======================================== + +Documentation is provided online at mozilla.org + +Problems or questions not covered by the online documentation can be +discussed in the DevEdge Security Newsgroup. + +=== New Features in 3.10 +======================= +One new option (-X) has been added to create a Mozilla aware signed XPI archive. +The option must be accompanied by the -Z option. This new option +creates a JAR file with the META-INF/zigbert.rsa/dsa file as the first file in +the archive instead of the default third to last. This will enable the archive +to be seen as signed by products incorporating XPInstall. i.e. .xpi extensions +for FireFox or Mozilla. + +=== New Features in 1.3 +======================= + +The security library components have been upgraded to utilize NSS_2_7_1_RTM. +This means that the maximum RSA keysize now supported should be 4096 bits. + +=== Zigbert 0.6 Support +======================= +This program was previously named Zigbert. The last version of zigbert +was Zigbert 0.6. Because all the functionality of Zigbert is maintained in +signtool 1.2, Zigbert is no longer supported. If you have problems +using Zigbert, please upgrade to signtool 1.2. + +=== New Features in 1.2 +======================= + +Certificate Generation Improvements +----------------------------------- +Two new options have been added to control generation of self-signed object +signing certificates with the -G option. The -s option takes the size (in bits) +of the generated RSA private key. The -t option takes the name of the PKCS #11 +token on which to generate the keypair and install the certificate. Both +options are optional. By default, the private key is 1024 bits and is generated +on the internal software token. + + +=== New Features in 1.1 +======================= + +File I/O +-------- +Signtool can now read its options from a command file specified with the -f +option on the command line. The format for the file is described in the +documentation. +Error messages and informational output can be redirected to an output file +by supplying the "--outfile" option on the command line or the "outfile=" +option in the command file. + +New Options +----------- +"--norecurse" tells Signtool not to recurse into subdirectories when signing +directories or parsing HTML with the -J option. +"--leavearc" tells Signtool not to delete the temporary .arc directories +produced by the -J option. This can aid debugging. +"--verbosity" tells Signtool how much information to display. 0 is the +default. -1 suppresses most messages, except for errors. + +=== Bug Fixes in 1.1 +==================== + +-J option revamped +------------------ +The -J option, which parses HTML files, extracts Java and Javascript code, +and stores them in signed JAR files, has been re-implemented. Several bugs +have been fixed: +- CODEBASE attribute is no longer ignored +- CLASS and SRC attributes can be be paths ("xxx/xxx/x.class") rather than + just filenames ("x.class"). +- LINK tags are handled correctly +- various HTML parsing bugs fixed +- error messages are more informative + +No Password on Key Database +--------------------------- +If you had not yet set a Communicator password (which locks key3.db, the +key database), signtool would fail with a cryptic error message whenever it +attempted to verify the password. Now this condition is detected at the +beginning of the program, and a more informative message is displayed. + +-x and -e Options +----------------- +Previously, only one of each of these options could be specified on the command +line. Now arbitrarily many can be specified. For example, to sign only files +with .class or .js extensions, the arguments "-eclass -ejs" could both be +specified. To exclude the directories "subdir1" and "subdir2" from signing, +the arguments "-x subdir1 -x subdir2" could both be specified. + +New Features in 1.0 +=================== + +Creation of JAR files +---------------------- +The -Z option causes signtool to output a JAR file formed by storing the +signed archive in ZIP format. This eliminates the need to use a separate ZIP +utility. The -c option specifies the compression level of the resulting +JAR file. + +Generation of Object-Signing Certificates and Keys +-------------------------------------------------- +The -G option will create a new, self-signed object-signing certificate +which can be used for testing purposes. The generated certificate and +associated public and private keys will be installed in the cert7.db and +key3.db files in the directory specified with the -d option (unless the key +is generated on an external token using the -t option). On Unix systems, +if no directory is specified, the user's Netscape directory (~/.netscape) +will be used. In addition, the certificate is output in X509 format to the +files x509.raw and x509.cacert in the current directory. x509.cacert can +be published on a web page and imported into browsers that visit that page. + +Extraction and Signing of JavaScript from HTML +---------------------------------------------- +The -J option activates the same functionality provided by the signpages +Perl script. It will parse a directory of html files, creating archives +of the JavaScript called from the HTML. These archives are then signed and +made into JAR files. + +Enhanced Smart Card Support +--------------------------- +Certificates that reside on smart cards are displayed when using the -L and +-l options. diff --git a/security/nss/cmd/signtool/certgen.c b/security/nss/cmd/signtool/certgen.c new file mode 100644 index 000000000..e095a01fb --- /dev/null +++ b/security/nss/cmd/signtool/certgen.c @@ -0,0 +1,713 @@ +/* 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 "signtool.h" + +#include "secoid.h" +#include "cryptohi.h" +#include "certdb.h" + +static char *GetSubjectFromUser(unsigned long serial); +static CERTCertificate *GenerateSelfSignedObjectSigningCert(char *nickname, + CERTCertDBHandle *db, char *subject, unsigned long serial, int keysize, + char *token); +static SECStatus ChangeTrustAttributes(CERTCertDBHandle *db, + CERTCertificate *cert, char *trusts); +static SECStatus set_cert_type(CERTCertificate *cert, unsigned int type); +static SECItem *sign_cert(CERTCertificate *cert, SECKEYPrivateKey *privk); +static CERTCertificate *install_cert(CERTCertDBHandle *db, SECItem *derCert, + char *nickname); +static SECStatus GenerateKeyPair(PK11SlotInfo *slot, SECKEYPublicKey **pubk, + SECKEYPrivateKey **privk, int keysize); +static CERTCertificateRequest *make_cert_request(char *subject, + SECKEYPublicKey *pubk); +static CERTCertificate *make_cert(CERTCertificateRequest *req, + unsigned long serial, CERTName *ca_subject); +static void output_ca_cert(CERTCertificate *cert, CERTCertDBHandle *db); + +/*********************************************************************** + * + * G e n e r a t e C e r t + * + * Runs the whole process of creating a new cert, getting info from the + * user, etc. + */ +int +GenerateCert(char *nickname, int keysize, char *token) +{ + CERTCertDBHandle *db; + CERTCertificate *cert; + char *subject; + unsigned long serial; + char stdinbuf[160]; + + /* Print warning about having the browser open */ + PR_fprintf(PR_STDOUT /*always go to console*/, + "\nWARNING: Performing this operation while the browser is running could cause" + "\ncorruption of your security databases. If the browser is currently running," + "\nyou should exit the browser before continuing this operation. Enter " + "\n\"y\" to continue, or anything else to abort: "); + pr_fgets(stdinbuf, 160, PR_STDIN); + PR_fprintf(PR_STDOUT, "\n"); + if (tolower(stdinbuf[0]) != 'y') { + PR_fprintf(errorFD, "Operation aborted at user's request.\n"); + errorCount++; + return -1; + } + + db = CERT_GetDefaultCertDB(); + if (!db) { + FatalError("Unable to open certificate database"); + } + + if (PK11_FindCertFromNickname(nickname, &pwdata)) { + PR_fprintf(errorFD, + "ERROR: Certificate with nickname \"%s\" already exists in database. You\n" + "must choose a different nickname.\n", + nickname); + errorCount++; + exit(ERRX); + } + + LL_L2UI(serial, PR_Now()); + + subject = GetSubjectFromUser(serial); + if (!subject) { + FatalError("Unable to get subject from user"); + } + + cert = GenerateSelfSignedObjectSigningCert(nickname, db, subject, + serial, keysize, token); + + if (cert) { + output_ca_cert(cert, db); + CERT_DestroyCertificate(cert); + } + + PORT_Free(subject); + return 0; +} + +#undef VERBOSE_PROMPTS + +/*********************************************************************8 + * G e t S u b j e c t F r o m U s e r + * + * Construct the subject information line for a certificate by querying + * the user on stdin. + */ +static char * +GetSubjectFromUser(unsigned long serial) +{ + char buf[STDIN_BUF_SIZE]; + char common_name_buf[STDIN_BUF_SIZE]; + char *common_name, *state, *orgunit, *country, *org, *locality; + char *email, *uid; + char *subject; + char *cp; + int subjectlen = 0; + + common_name = state = orgunit = country = org = locality = email = + uid = subject = NULL; + + /* Get subject information */ + PR_fprintf(PR_STDOUT, + "\nEnter certificate information. All fields are optional. Acceptable\n" + "characters are numbers, letters, spaces, and apostrophes.\n"); + +#ifdef VERBOSE_PROMPTS + PR_fprintf(PR_STDOUT, "\nCOMMON NAME\n" + "Enter the full name you want to give your certificate. (Example: Test-Only\n" + "Object Signing Certificate)\n" + "-->"); +#else + PR_fprintf(PR_STDOUT, "certificate common name: "); +#endif + if (!fgets(buf, STDIN_BUF_SIZE, stdin)) { + return NULL; + } + cp = chop(buf); + if (*cp == '\0') { + sprintf(common_name_buf, "%s (%lu)", DEFAULT_COMMON_NAME, + serial); + cp = common_name_buf; + } + common_name = PORT_ZAlloc(strlen(cp) + 6); + if (!common_name) { + out_of_memory(); + } + sprintf(common_name, "CN=%s, ", cp); + subjectlen += strlen(common_name); + +#ifdef VERBOSE_PROMPTS + PR_fprintf(PR_STDOUT, "\nORGANIZATION NAME\n" + "Enter the name of your organization. For example, this could be the name\n" + "of your company.\n" + "-->"); +#else + PR_fprintf(PR_STDOUT, "organization: "); +#endif + if (!fgets(buf, STDIN_BUF_SIZE, stdin)) { + return NULL; + } + cp = chop(buf); + if (*cp != '\0') { + org = PORT_ZAlloc(strlen(cp) + 5); + if (!org) { + out_of_memory(); + } + sprintf(org, "O=%s, ", cp); + subjectlen += strlen(org); + } + +#ifdef VERBOSE_PROMPTS + PR_fprintf(PR_STDOUT, "\nORGANIZATION UNIT\n" + "Enter the name of your organization unit. For example, this could be the\n" + "name of your department.\n" + "-->"); +#else + PR_fprintf(PR_STDOUT, "organization unit: "); +#endif + if (!fgets(buf, STDIN_BUF_SIZE, stdin)) { + return NULL; + } + cp = chop(buf); + if (*cp != '\0') { + orgunit = PORT_ZAlloc(strlen(cp) + 6); + if (!orgunit) { + out_of_memory(); + } + sprintf(orgunit, "OU=%s, ", cp); + subjectlen += strlen(orgunit); + } + +#ifdef VERBOSE_PROMPTS + PR_fprintf(PR_STDOUT, "\nSTATE\n" + "Enter the name of your state or province.\n" + "-->"); +#else + PR_fprintf(PR_STDOUT, "state or province: "); +#endif + if (!fgets(buf, STDIN_BUF_SIZE, stdin)) { + return NULL; + } + cp = chop(buf); + if (*cp != '\0') { + state = PORT_ZAlloc(strlen(cp) + 6); + if (!state) { + out_of_memory(); + } + sprintf(state, "ST=%s, ", cp); + subjectlen += strlen(state); + } + +#ifdef VERBOSE_PROMPTS + PR_fprintf(PR_STDOUT, "\nCOUNTRY\n" + "Enter the 2-character abbreviation for the name of your country.\n" + "-->"); +#else + PR_fprintf(PR_STDOUT, "country (must be exactly 2 characters): "); +#endif + if (!fgets(buf, STDIN_BUF_SIZE, stdin)) { + return NULL; + } + cp = chop(cp); + if (strlen(cp) != 2) { + *cp = '\0'; /* country code must be 2 chars */ + } + if (*cp != '\0') { + country = PORT_ZAlloc(strlen(cp) + 5); + if (!country) { + out_of_memory(); + } + sprintf(country, "C=%s, ", cp); + subjectlen += strlen(country); + } + +#ifdef VERBOSE_PROMPTS + PR_fprintf(PR_STDOUT, "\nUSERNAME\n" + "Enter your system username or UID\n" + "-->"); +#else + PR_fprintf(PR_STDOUT, "username: "); +#endif + if (!fgets(buf, STDIN_BUF_SIZE, stdin)) { + return NULL; + } + cp = chop(buf); + if (*cp != '\0') { + uid = PORT_ZAlloc(strlen(cp) + 7); + if (!uid) { + out_of_memory(); + } + sprintf(uid, "UID=%s, ", cp); + subjectlen += strlen(uid); + } + +#ifdef VERBOSE_PROMPTS + PR_fprintf(PR_STDOUT, "\nEMAIL ADDRESS\n" + "Enter your email address.\n" + "-->"); +#else + PR_fprintf(PR_STDOUT, "email address: "); +#endif + if (!fgets(buf, STDIN_BUF_SIZE, stdin)) { + return NULL; + } + cp = chop(buf); + if (*cp != '\0') { + email = PORT_ZAlloc(strlen(cp) + 5); + if (!email) { + out_of_memory(); + } + sprintf(email, "E=%s,", cp); + subjectlen += strlen(email); + } + + subjectlen++; + + subject = PORT_ZAlloc(subjectlen); + if (!subject) { + out_of_memory(); + } + + sprintf(subject, "%s%s%s%s%s%s%s", + common_name ? common_name : "", + org ? org : "", + orgunit ? orgunit : "", + state ? state : "", + country ? country : "", + uid ? uid : "", + email ? email : ""); + if ((strlen(subject) > 1) && (subject[strlen(subject) - 1] == ' ')) { + subject[strlen(subject) - 2] = '\0'; + } + + PORT_Free(common_name); + PORT_Free(org); + PORT_Free(orgunit); + PORT_Free(state); + PORT_Free(country); + PORT_Free(uid); + PORT_Free(email); + + return subject; +} + +/************************************************************************** + * + * G e n e r a t e S e l f S i g n e d O b j e c t S i g n i n g C e r t + * *phew*^ + * + */ +static CERTCertificate * +GenerateSelfSignedObjectSigningCert(char *nickname, CERTCertDBHandle *db, + char *subject, unsigned long serial, int keysize, char *token) +{ + CERTCertificate *cert, *temp_cert; + SECItem *derCert; + CERTCertificateRequest *req; + + PK11SlotInfo *slot = NULL; + SECKEYPrivateKey *privk = NULL; + SECKEYPublicKey *pubk = NULL; + + if (token) { + slot = PK11_FindSlotByName(token); + } else { + slot = PK11_GetInternalKeySlot(); + } + + if (slot == NULL) { + PR_fprintf(errorFD, "Can't find PKCS11 slot %s\n", + token ? token : ""); + errorCount++; + exit(ERRX); + } + + if (GenerateKeyPair(slot, &pubk, &privk, keysize) != SECSuccess) { + FatalError("Error generating keypair."); + } + req = make_cert_request(subject, pubk); + temp_cert = make_cert(req, serial, &req->subject); + if (set_cert_type(temp_cert, + NS_CERT_TYPE_OBJECT_SIGNING | + NS_CERT_TYPE_OBJECT_SIGNING_CA) != + SECSuccess) { + FatalError("Unable to set cert type"); + } + + derCert = sign_cert(temp_cert, privk); + cert = install_cert(db, derCert, nickname); + if (ChangeTrustAttributes(db, cert, ",,uC") != SECSuccess) { + FatalError("Unable to change trust on generated certificate"); + } + + /* !!! Free memory ? !!! */ + PK11_FreeSlot(slot); + SECKEY_DestroyPrivateKey(privk); + SECKEY_DestroyPublicKey(pubk); + CERT_DestroyCertificate(temp_cert); + CERT_DestroyCertificateRequest(req); + + return cert; +} + +/************************************************************************** + * + * C h a n g e T r u s t A t t r i b u t e s + */ +static SECStatus +ChangeTrustAttributes(CERTCertDBHandle *db, CERTCertificate *cert, char *trusts) +{ + + CERTCertTrust *trust; + + if (!db || !cert || !trusts) { + PR_fprintf(errorFD, "ChangeTrustAttributes got incomplete arguments.\n"); + errorCount++; + return SECFailure; + } + + trust = (CERTCertTrust *)PORT_ZAlloc(sizeof(CERTCertTrust)); + if (!trust) { + PR_fprintf(errorFD, "ChangeTrustAttributes unable to allocate " + "CERTCertTrust\n"); + errorCount++; + return SECFailure; + } + + if (CERT_DecodeTrustString(trust, trusts)) { + return SECFailure; + } + + if (CERT_ChangeCertTrust(db, cert, trust)) { + PR_fprintf(errorFD, "unable to modify trust attributes for cert %s\n", + cert->nickname ? cert->nickname : ""); + errorCount++; + return SECFailure; + } + + PORT_Free(trust); + return SECSuccess; +} + +/************************************************************************* + * + * s e t _ c e r t _ t y p e + */ +static SECStatus +set_cert_type(CERTCertificate *cert, unsigned int type) +{ + void *context; + SECStatus status = SECSuccess; + SECItem certType; + char ctype; + + context = CERT_StartCertExtensions(cert); + + certType.type = siBuffer; + certType.data = (unsigned char *)&ctype; + certType.len = 1; + ctype = (unsigned char)type; + if (CERT_EncodeAndAddBitStrExtension(context, SEC_OID_NS_CERT_EXT_CERT_TYPE, + &certType, PR_TRUE /*critical*/) != + SECSuccess) { + status = SECFailure; + } + + if (CERT_FinishExtensions(context) != SECSuccess) { + status = SECFailure; + } + + return status; +} + +/******************************************************************** + * + * s i g n _ c e r t + */ +static SECItem * +sign_cert(CERTCertificate *cert, SECKEYPrivateKey *privk) +{ + SECStatus rv; + + SECItem der2; + SECItem *result2; + + SECOidTag alg = SEC_OID_UNKNOWN; + + alg = SEC_GetSignatureAlgorithmOidTag(privk->keyType, SEC_OID_UNKNOWN); + if (alg == SEC_OID_UNKNOWN) { + FatalError("Unknown key type"); + } + + rv = SECOID_SetAlgorithmID(cert->arena, &cert->signature, alg, 0); + + if (rv != SECSuccess) { + PR_fprintf(errorFD, "%s: unable to set signature alg id\n", + PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + der2.len = 0; + der2.data = NULL; + + (void)SEC_ASN1EncodeItem(cert->arena, &der2, cert, SEC_ASN1_GET(CERT_CertificateTemplate)); + + if (rv != SECSuccess) { + PR_fprintf(errorFD, "%s: error encoding cert\n", PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + result2 = (SECItem *)PORT_ArenaZAlloc(cert->arena, sizeof(SECItem)); + if (result2 == NULL) + out_of_memory(); + + rv = SEC_DerSignData(cert->arena, result2, der2.data, der2.len, privk, alg); + + if (rv != SECSuccess) { + PR_fprintf(errorFD, "can't sign encoded certificate data\n"); + errorCount++; + exit(ERRX); + } else if (verbosity >= 0) { + PR_fprintf(outputFD, "certificate has been signed\n"); + } + + cert->derCert = *result2; + + return result2; +} + +/********************************************************************* + * + * i n s t a l l _ c e r t + * + * Installs the cert in the permanent database. + */ +static CERTCertificate * +install_cert(CERTCertDBHandle *db, SECItem *derCert, char *nickname) +{ + CERTCertificate *newcert; + PK11SlotInfo *newSlot; + + newSlot = PK11_ImportDERCertForKey(derCert, nickname, &pwdata); + if (newSlot == NULL) { + PR_fprintf(errorFD, "Unable to install certificate\n"); + errorCount++; + exit(ERRX); + } + + newcert = PK11_FindCertFromDERCertItem(newSlot, derCert, &pwdata); + PK11_FreeSlot(newSlot); + if (newcert == NULL) { + PR_fprintf(errorFD, "%s: can't find new certificate\n", + PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + if (verbosity >= 0) { + PR_fprintf(outputFD, "certificate \"%s\" added to database\n", + nickname); + } + + return newcert; +} + +/****************************************************************** + * + * G e n e r a t e K e y P a i r + */ +static SECStatus +GenerateKeyPair(PK11SlotInfo *slot, SECKEYPublicKey **pubk, + SECKEYPrivateKey **privk, int keysize) +{ + + PK11RSAGenParams rsaParams; + + if (keysize == -1) { + rsaParams.keySizeInBits = DEFAULT_RSA_KEY_SIZE; + } else { + rsaParams.keySizeInBits = keysize; + } + rsaParams.pe = 0x10001; + + if (PK11_Authenticate(slot, PR_FALSE /*loadCerts*/, &pwdata) != + SECSuccess) { + SECU_PrintError(progName, "failure authenticating to key database.\n"); + exit(ERRX); + } + + *privk = PK11_GenerateKeyPair(slot, CKM_RSA_PKCS_KEY_PAIR_GEN, &rsaParams, + + pubk, PR_TRUE /*isPerm*/, PR_TRUE /*isSensitive*/, &pwdata); + + if (*privk != NULL && *pubk != NULL) { + if (verbosity >= 0) { + PR_fprintf(outputFD, "generated public/private key pair\n"); + } + } else { + SECU_PrintError(progName, "failure generating key pair\n"); + exit(ERRX); + } + + return SECSuccess; +} + +/****************************************************************** + * + * m a k e _ c e r t _ r e q u e s t + */ +static CERTCertificateRequest * +make_cert_request(char *subject, SECKEYPublicKey *pubk) +{ + CERTName *subj; + CERTSubjectPublicKeyInfo *spki; + + CERTCertificateRequest *req; + + /* Create info about public key */ + spki = SECKEY_CreateSubjectPublicKeyInfo(pubk); + if (!spki) { + SECU_PrintError(progName, "unable to create subject public key"); + exit(ERRX); + } + + subj = CERT_AsciiToName(subject); + if (subj == NULL) { + FatalError("Invalid data in certificate description"); + } + + /* Generate certificate request */ + req = CERT_CreateCertificateRequest(subj, spki, 0); + if (!req) { + SECU_PrintError(progName, "unable to make certificate request"); + exit(ERRX); + } + + SECKEY_DestroySubjectPublicKeyInfo(spki); + CERT_DestroyName(subj); + + if (verbosity >= 0) { + PR_fprintf(outputFD, "certificate request generated\n"); + } + + return req; +} + +/****************************************************************** + * + * m a k e _ c e r t + */ +static CERTCertificate * +make_cert(CERTCertificateRequest *req, unsigned long serial, + CERTName *ca_subject) +{ + CERTCertificate *cert; + + CERTValidity *validity = NULL; + + PRTime now, after; + PRExplodedTime printableTime; + + now = PR_Now(); + PR_ExplodeTime(now, PR_GMTParameters, &printableTime); + + printableTime.tm_month += 3; + after = PR_ImplodeTime(&printableTime); + + validity = CERT_CreateValidity(now, after); + + if (validity == NULL) { + PR_fprintf(errorFD, "%s: error creating certificate validity\n", + PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + cert = CERT_CreateCertificate(serial, ca_subject, validity, req); + CERT_DestroyValidity(validity); + + if (cert == NULL) { + /* should probably be more precise here */ + PR_fprintf(errorFD, "%s: error while generating certificate\n", + PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + return cert; +} + +/************************************************************************* + * + * o u t p u t _ c a _ c e r t + */ +static void +output_ca_cert(CERTCertificate *cert, CERTCertDBHandle *db) +{ + FILE *out; + + SECItem *encodedCertChain; + SEC_PKCS7ContentInfo *certChain; + char *filename, *certData; + + /* the raw */ + + filename = PORT_ZAlloc(strlen(DEFAULT_X509_BASENAME) + 8); + if (!filename) + out_of_memory(); + + sprintf(filename, "%s.raw", DEFAULT_X509_BASENAME); + if ((out = fopen(filename, "wb")) == NULL) { + PR_fprintf(errorFD, "%s: Can't open %s output file\n", PROGRAM_NAME, + filename); + errorCount++; + exit(ERRX); + } + + certChain = SEC_PKCS7CreateCertsOnly(cert, PR_TRUE, db); + encodedCertChain = + SEC_PKCS7EncodeItem(NULL, NULL, certChain, NULL, NULL, NULL); + SEC_PKCS7DestroyContentInfo(certChain); + + if (encodedCertChain) { + fprintf(out, "Content-type: application/x-x509-ca-cert\n\n"); + fwrite(encodedCertChain->data, 1, encodedCertChain->len, + out); + SECITEM_FreeItem(encodedCertChain, PR_TRUE); + } else { + PR_fprintf(errorFD, "%s: Can't DER encode this certificate\n", + PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + fclose(out); + + /* and the cooked */ + + sprintf(filename, "%s.cacert", DEFAULT_X509_BASENAME); + if ((out = fopen(filename, "wb")) == NULL) { + PR_fprintf(errorFD, "%s: Can't open %s output file\n", PROGRAM_NAME, + filename); + errorCount++; + return; + } + + certData = BTOA_DataToAscii(cert->derCert.data, cert->derCert.len); + fprintf(out, "%s\n%s\n%s\n", NS_CERT_HEADER, certData, NS_CERT_TRAILER); + PORT_Free(certData); + + PORT_Free(filename); + fclose(out); + + if (verbosity >= 0) { + PR_fprintf(outputFD, "Exported certificate to %s.raw and %s.cacert.\n", + DEFAULT_X509_BASENAME, DEFAULT_X509_BASENAME); + } +} diff --git a/security/nss/cmd/signtool/javascript.c b/security/nss/cmd/signtool/javascript.c new file mode 100644 index 000000000..746f724f8 --- /dev/null +++ b/security/nss/cmd/signtool/javascript.c @@ -0,0 +1,1817 @@ +/* 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 "signtool.h" +#include <prmem.h> +#include <prio.h> +#include <prenv.h> + +static int javascript_fn(char *relpath, char *basedir, char *reldir, + char *filename, void *arg); +static int extract_js(char *filename); +static int copyinto(char *from, char *to); +static PRStatus ensureExists(char *base, char *path); +static int make_dirs(char *path, PRInt32 file_perms); + +static char *jartree = NULL; +static int idOrdinal; +static PRBool dumpParse = PR_FALSE; + +static char *event_handlers[] = { + "onAbort", + "onBlur", + "onChange", + "onClick", + "onDblClick", + "onDragDrop", + "onError", + "onFocus", + "onKeyDown", + "onKeyPress", + "onKeyUp", + "onLoad", + "onMouseDown", + "onMouseMove", + "onMouseOut", + "onMouseOver", + "onMouseUp", + "onMove", + "onReset", + "onResize", + "onSelect", + "onSubmit", + "onUnload" +}; + +static int num_handlers = 23; + +/* + * I n l i n e J a v a S c r i p t + * + * Javascript signing. Instead of passing an archive to signtool, + * a directory containing html files is given. Archives are created + * from the archive= and src= tag attributes inside the html, + * as appropriate. Then the archives are signed. + * + */ +int +InlineJavaScript(char *dir, PRBool recurse) +{ + jartree = dir; + if (verbosity >= 0) { + PR_fprintf(outputFD, "\nGenerating inline signatures from HTML files in: %s\n", + dir); + } + if (PR_GetEnvSecure("SIGNTOOL_DUMP_PARSE")) { + dumpParse = PR_TRUE; + } + + return foreach (dir, "", javascript_fn, recurse, PR_FALSE /*include dirs*/, + (void *)NULL); +} + +/************************************************************************ + * + * j a v a s c r i p t _ f n + */ +static int +javascript_fn(char *relpath, char *basedir, char *reldir, char *filename, void *arg) +{ + char fullname[FNSIZE]; + + /* only process inline scripts from .htm, .html, and .shtml*/ + + if (!(PL_strcaserstr(filename, ".htm") == filename + strlen(filename) - 4) && + !(PL_strcaserstr(filename, ".html") == filename + strlen(filename) - 5) && + !(PL_strcaserstr(filename, ".shtml") == filename + strlen(filename) - 6)) { + return 0; + } + + /* don't process scripts that signtool has already + extracted (those that are inside .arc directories) */ + + if (PL_strcaserstr(filename, ".arc") == filename + strlen(filename) - 4) + return 0; + + if (verbosity >= 0) { + PR_fprintf(outputFD, "Processing HTML file: %s\n", relpath); + } + + /* reset firstArchive at top of each HTML file */ + + /* skip directories that contain extracted scripts */ + + if (PL_strcaserstr(reldir, ".arc") == reldir + strlen(reldir) - 4) + return 0; + + sprintf(fullname, "%s/%s", basedir, relpath); + return extract_js(fullname); +} + +/*=========================================================================== + = + = D A T A S T R U C T U R E S + = +*/ +typedef enum { + TEXT_HTML_STATE = 0, + SCRIPT_HTML_STATE +} + +HTML_STATE; + +typedef enum { + /* we start in the start state */ + START_STATE, + + /* We are looking for or reading in an attribute */ + GET_ATT_STATE, + + /* We're burning ws before finding an attribute */ + PRE_ATT_WS_STATE, + + /* We're burning ws after an attribute. Looking for an '='. */ + POST_ATT_WS_STATE, + + /* We're burning ws after an '=', waiting for a value */ + PRE_VAL_WS_STATE, + + /* We're reading in a value */ + GET_VALUE_STATE, + + /* We're reading in a value that's inside quotes */ + GET_QUOTED_VAL_STATE, + + /* We've encountered the closing '>' */ + DONE_STATE, + + /* Error state */ + ERR_STATE +} + +TAG_STATE; + +typedef struct AVPair_Str { + char *attribute; + char *value; + unsigned int valueLine; /* the line that the value ends on */ + struct AVPair_Str *next; +} AVPair; + +typedef enum { + APPLET_TAG, + SCRIPT_TAG, + LINK_TAG, + STYLE_TAG, + COMMENT_TAG, + OTHER_TAG +} + +TAG_TYPE; + +typedef struct { + TAG_TYPE type; + AVPair *attList; + AVPair *attListTail; + char *text; +} TagItem; + +typedef enum { + TAG_ITEM, + TEXT_ITEM +} + +ITEM_TYPE; + +typedef struct HTMLItem_Str { + unsigned int startLine; + unsigned int endLine; + ITEM_TYPE type; + union { + TagItem *tag; + char *text; + } item; + struct HTMLItem_Str *next; +} HTMLItem; + +typedef struct { + PRFileDesc *fd; + PRInt32 curIndex; + PRBool IsEOF; +#define FILE_BUFFER_BUFSIZE 512 + char buf[FILE_BUFFER_BUFSIZE]; + PRInt32 startOffset; + PRInt32 maxIndex; + unsigned int lineNum; +} FileBuffer; + +/*=========================================================================== + = + = F U N C T I O N S + = +*/ +static HTMLItem *CreateTextItem(char *text, unsigned int startline, + unsigned int endline); +static HTMLItem *CreateTagItem(TagItem *ti, unsigned int startline, + unsigned int endline); +static TagItem *ProcessTag(FileBuffer *fb, char **errStr); +static void DestroyHTMLItem(HTMLItem *item); +static void DestroyTagItem(TagItem *ti); +static TAG_TYPE GetTagType(char *att); +static FileBuffer *FB_Create(PRFileDesc *fd); +static int FB_GetChar(FileBuffer *fb); +static PRInt32 FB_GetPointer(FileBuffer *fb); +static PRInt32 FB_GetRange(FileBuffer *fb, PRInt32 start, PRInt32 end, + char **buf); +static unsigned int FB_GetLineNum(FileBuffer *fb); +static void FB_Destroy(FileBuffer *fb); +static void PrintTagItem(PRFileDesc *fd, TagItem *ti); +static void PrintHTMLStream(PRFileDesc *fd, HTMLItem *head); + +/************************************************************************ + * + * C r e a t e T e x t I t e m + */ +static HTMLItem * +CreateTextItem(char *text, unsigned int startline, unsigned int endline) +{ + HTMLItem *item; + + item = PR_Malloc(sizeof(HTMLItem)); + if (!item) { + return NULL; + } + + item->type = TEXT_ITEM; + item->item.text = text; + item->next = NULL; + item->startLine = startline; + item->endLine = endline; + + return item; +} + +/************************************************************************ + * + * C r e a t e T a g I t e m + */ +static HTMLItem * +CreateTagItem(TagItem *ti, unsigned int startline, unsigned int endline) +{ + HTMLItem *item; + + item = PR_Malloc(sizeof(HTMLItem)); + if (!item) { + return NULL; + } + + item->type = TAG_ITEM; + item->item.tag = ti; + item->next = NULL; + item->startLine = startline; + item->endLine = endline; + + return item; +} + +static PRBool +isAttChar(int c) +{ + return (isalnum(c) || c == '/' || c == '-'); +} + +/************************************************************************ + * + * P r o c e s s T a g + */ +static TagItem * +ProcessTag(FileBuffer *fb, char **errStr) +{ + TAG_STATE state; + PRInt32 startText, startID, curPos; + PRBool firstAtt; + int curchar; + TagItem *ti = NULL; + AVPair *curPair = NULL; + char quotechar = '\0'; + unsigned int linenum; + unsigned int startline; + + state = START_STATE; + + startID = FB_GetPointer(fb); + startText = startID; + firstAtt = PR_TRUE; + + ti = (TagItem *)PR_Malloc(sizeof(TagItem)); + if (!ti) + out_of_memory(); + ti->type = OTHER_TAG; + ti->attList = NULL; + ti->attListTail = NULL; + ti->text = NULL; + + startline = FB_GetLineNum(fb); + + while (state != DONE_STATE && state != ERR_STATE) { + linenum = FB_GetLineNum(fb); + curchar = FB_GetChar(fb); + if (curchar == EOF) { + *errStr = PR_smprintf( + "line %d: Unexpected end-of-file while parsing tag starting at line %d.\n", + linenum, startline); + state = ERR_STATE; + continue; + } + + switch (state) { + case START_STATE: + if (curchar == '!') { + /* + * SGML tag or comment + * Here's the general rule for SGML tags. Everything from + * <! to > is the tag. Inside the tag, comments are + * delimited with --. So we are looking for the first '>' + * that is not commented out, that is, not inside a pair + * of --: <!DOCTYPE --this is a comment >(psyche!) --> + */ + + PRBool inComment = PR_FALSE; + short hyphenCount = 0; /* number of consecutive hyphens */ + + while (1) { + linenum = FB_GetLineNum(fb); + curchar = FB_GetChar(fb); + if (curchar == EOF) { + /* Uh oh, EOF inside comment */ + *errStr = PR_smprintf( + "line %d: Unexpected end-of-file inside comment starting at line %d.\n", + linenum, startline); + state = ERR_STATE; + break; + } + if (curchar == '-') { + if (hyphenCount == 1) { + /* This is a comment delimiter */ + inComment = !inComment; + hyphenCount = 0; + } else { + /* beginning of a comment delimiter? */ + hyphenCount = 1; + } + } else if (curchar == '>') { + if (!inComment) { + /* This is the end of the tag */ + state = DONE_STATE; + break; + } else { + /* The > is inside a comment, so it's not + * really the end of the tag */ + hyphenCount = 0; + } + } else { + hyphenCount = 0; + } + } + ti->type = COMMENT_TAG; + break; + } + /* fall through */ + case GET_ATT_STATE: + if (isspace(curchar) || curchar == '=' || curchar == '>') { + /* end of the current attribute */ + curPos = FB_GetPointer(fb) - 2; + if (curPos >= startID) { + /* We have an attribute */ + curPair = (AVPair *)PR_Malloc(sizeof(AVPair)); + if (!curPair) + out_of_memory(); + curPair->value = NULL; + curPair->next = NULL; + FB_GetRange(fb, startID, curPos, + &curPair->attribute); + + /* Stick this attribute on the list */ + if (ti->attListTail) { + ti->attListTail->next = curPair; + ti->attListTail = curPair; + } else { + ti->attList = ti->attListTail = + curPair; + } + + /* If this is the first attribute, find the type of tag + * based on it. Also, start saving the text of the tag. */ + if (firstAtt) { + ti->type = GetTagType(curPair->attribute); + startText = FB_GetPointer(fb) - + 1; + firstAtt = PR_FALSE; + } + } else { + if (curchar == '=') { + /* If we don't have any attribute but we do have an + * equal sign, that's an error */ + *errStr = PR_smprintf("line %d: Malformed tag starting at line %d.\n", + linenum, startline); + state = ERR_STATE; + break; + } + } + + /* Compute next state */ + if (curchar == '=') { + startID = FB_GetPointer(fb); + state = PRE_VAL_WS_STATE; + } else if (curchar == '>') { + state = DONE_STATE; + } else if (curPair) { + state = POST_ATT_WS_STATE; + } else { + state = PRE_ATT_WS_STATE; + } + } else if (isAttChar(curchar)) { + /* Just another char in the attribute. Do nothing */ + state = GET_ATT_STATE; + } else { + /* bogus char */ + *errStr = PR_smprintf("line %d: Bogus chararacter '%c' in tag.\n", + linenum, curchar); + state = ERR_STATE; + break; + } + break; + case PRE_ATT_WS_STATE: + if (curchar == '>') { + state = DONE_STATE; + } else if (isspace(curchar)) { + /* more whitespace, do nothing */ + } else if (isAttChar(curchar)) { + /* starting another attribute */ + startID = FB_GetPointer(fb) - 1; + state = GET_ATT_STATE; + } else { + /* bogus char */ + *errStr = PR_smprintf("line %d: Bogus character '%c' in tag.\n", + linenum, curchar); + state = ERR_STATE; + break; + } + break; + case POST_ATT_WS_STATE: + if (curchar == '>') { + state = DONE_STATE; + } else if (isspace(curchar)) { + /* more whitespace, do nothing */ + } else if (isAttChar(curchar)) { + /* starting another attribute */ + startID = FB_GetPointer(fb) - 1; + state = GET_ATT_STATE; + } else if (curchar == '=') { + /* there was whitespace between the attribute and its equal + * sign, which means there's a value coming up */ + state = PRE_VAL_WS_STATE; + } else { + /* bogus char */ + *errStr = PR_smprintf("line %d: Bogus character '%c' in tag.\n", + linenum, curchar); + state = ERR_STATE; + break; + } + break; + case PRE_VAL_WS_STATE: + if (curchar == '>') { + /* premature end-of-tag (sounds like a personal problem). */ + *errStr = PR_smprintf( + "line %d: End of tag while waiting for value.\n", + linenum); + state = ERR_STATE; + break; + } else if (isspace(curchar)) { + /* more whitespace, do nothing */ + break; + } else { + /* this must be some sort of value. Fall through + * to GET_VALUE_STATE */ + startID = FB_GetPointer(fb) - 1; + state = GET_VALUE_STATE; + } + /* Fall through if we didn't break on '>' or whitespace */ + case GET_VALUE_STATE: + if (isspace(curchar) || curchar == '>') { + /* end of value */ + curPos = FB_GetPointer(fb) - 2; + if (curPos >= startID) { + /* Grab the value */ + FB_GetRange(fb, startID, curPos, + &curPair->value); + curPair->valueLine = linenum; + } else { + /* empty value, leave as NULL */ + } + if (isspace(curchar)) { + state = PRE_ATT_WS_STATE; + } else { + state = DONE_STATE; + } + } else if (curchar == '\"' || curchar == '\'') { + /* quoted value. Start recording the value inside the quote*/ + startID = FB_GetPointer(fb); + state = GET_QUOTED_VAL_STATE; + PORT_Assert(quotechar == '\0'); + quotechar = curchar; /* look for matching quote type */ + } else { + /* just more value */ + } + break; + case GET_QUOTED_VAL_STATE: + PORT_Assert(quotechar != '\0'); + if (curchar == quotechar) { + /* end of quoted value */ + curPos = FB_GetPointer(fb) - 2; + if (curPos >= startID) { + /* Grab the value */ + FB_GetRange(fb, startID, curPos, + &curPair->value); + curPair->valueLine = linenum; + } else { + /* empty value, leave it as NULL */ + } + state = GET_ATT_STATE; + quotechar = '\0'; + startID = FB_GetPointer(fb); + } else { + /* more quoted value, continue */ + } + break; + case DONE_STATE: + case ERR_STATE: + default:; /* should never get here */ + } + } + + if (state == DONE_STATE) { + /* Get the text of the tag */ + curPos = FB_GetPointer(fb) - 1; + FB_GetRange(fb, startText, curPos, &ti->text); + + /* Return the tag */ + return ti; + } + + /* Uh oh, an error. Kill the tag item*/ + DestroyTagItem(ti); + return NULL; +} + +/************************************************************************ + * + * D e s t r o y H T M L I t e m + */ +static void +DestroyHTMLItem(HTMLItem *item) +{ + if (item->type == TAG_ITEM) { + DestroyTagItem(item->item.tag); + } else { + if (item->item.text) { + PR_Free(item->item.text); + } + } +} + +/************************************************************************ + * + * D e s t r o y T a g I t e m + */ +static void +DestroyTagItem(TagItem *ti) +{ + AVPair *temp; + + if (ti->text) { + PR_Free(ti->text); + ti->text = NULL; + } + + while (ti->attList) { + temp = ti->attList; + ti->attList = ti->attList->next; + + if (temp->attribute) { + PR_Free(temp->attribute); + temp->attribute = NULL; + } + if (temp->value) { + PR_Free(temp->value); + temp->value = NULL; + } + PR_Free(temp); + } + + PR_Free(ti); +} + +/************************************************************************ + * + * G e t T a g T y p e + */ +static TAG_TYPE +GetTagType(char *att) +{ + if (!PORT_Strcasecmp(att, "APPLET")) { + return APPLET_TAG; + } + if (!PORT_Strcasecmp(att, "SCRIPT")) { + return SCRIPT_TAG; + } + if (!PORT_Strcasecmp(att, "LINK")) { + return LINK_TAG; + } + if (!PORT_Strcasecmp(att, "STYLE")) { + return STYLE_TAG; + } + return OTHER_TAG; +} + +/************************************************************************ + * + * F B _ C r e a t e + */ +static FileBuffer * +FB_Create(PRFileDesc *fd) +{ + FileBuffer *fb; + PRInt32 amountRead; + PRInt32 storedOffset; + + fb = (FileBuffer *)PR_Malloc(sizeof(FileBuffer)); + fb->fd = fd; + storedOffset = PR_Seek(fd, 0, PR_SEEK_CUR); + PR_Seek(fd, 0, PR_SEEK_SET); + fb->startOffset = 0; + amountRead = PR_Read(fd, fb->buf, FILE_BUFFER_BUFSIZE); + if (amountRead == -1) + goto loser; + fb->maxIndex = amountRead - 1; + fb->curIndex = 0; + fb->IsEOF = (fb->curIndex > fb->maxIndex) ? PR_TRUE : PR_FALSE; + fb->lineNum = 1; + + PR_Seek(fd, storedOffset, PR_SEEK_SET); + return fb; +loser: + PR_Seek(fd, storedOffset, PR_SEEK_SET); + PR_Free(fb); + return NULL; +} + +/************************************************************************ + * + * F B _ G e t C h a r + */ +static int +FB_GetChar(FileBuffer *fb) +{ + PRInt32 storedOffset; + PRInt32 amountRead; + int retval = -1; + + if (fb->IsEOF) { + return EOF; + } + + storedOffset = PR_Seek(fb->fd, 0, PR_SEEK_CUR); + + retval = (unsigned char)fb->buf[fb->curIndex++]; + if (retval == '\n') + fb->lineNum++; + + if (fb->curIndex > fb->maxIndex) { + /* We're at the end of the buffer. Try to get some new data from the + * file */ + fb->startOffset += fb->maxIndex + 1; + PR_Seek(fb->fd, fb->startOffset, PR_SEEK_SET); + amountRead = PR_Read(fb->fd, fb->buf, FILE_BUFFER_BUFSIZE); + if (amountRead == -1) + goto loser; + fb->maxIndex = amountRead - 1; + fb->curIndex = 0; + } + + fb->IsEOF = (fb->curIndex > fb->maxIndex) ? PR_TRUE : PR_FALSE; + +loser: + PR_Seek(fb->fd, storedOffset, PR_SEEK_SET); + return retval; +} + +/************************************************************************ + * + * F B _ G e t L i n e N u m + * + */ +static unsigned int +FB_GetLineNum(FileBuffer *fb) +{ + return fb->lineNum; +} + +/************************************************************************ + * + * F B _ G e t P o i n t e r + * + */ +static PRInt32 +FB_GetPointer(FileBuffer *fb) +{ + return fb->startOffset + fb->curIndex; +} + +/************************************************************************ + * + * F B _ G e t R a n g e + * + */ +static PRInt32 +FB_GetRange(FileBuffer *fb, PRInt32 start, PRInt32 end, char **buf) +{ + PRInt32 amountRead; + PRInt32 storedOffset; + + *buf = PR_Malloc(end - start + 2); + if (*buf == NULL) { + return 0; + } + + storedOffset = PR_Seek(fb->fd, 0, PR_SEEK_CUR); + PR_Seek(fb->fd, start, PR_SEEK_SET); + amountRead = PR_Read(fb->fd, *buf, end - start + 1); + PR_Seek(fb->fd, storedOffset, PR_SEEK_SET); + if (amountRead == -1) { + PR_Free(*buf); + *buf = NULL; + return 0; + } + + (*buf)[end - start + 1] = '\0'; + return amountRead; +} + +/************************************************************************ + * + * F B _ D e s t r o y + * + */ +static void +FB_Destroy(FileBuffer *fb) +{ + if (fb) { + PR_Free(fb); + } +} + +/************************************************************************ + * + * P r i n t T a g I t e m + * + */ +static void +PrintTagItem(PRFileDesc *fd, TagItem *ti) +{ + AVPair *pair; + + PR_fprintf(fd, "TAG:\n----\nType: "); + switch (ti->type) { + case APPLET_TAG: + PR_fprintf(fd, "applet\n"); + break; + case SCRIPT_TAG: + PR_fprintf(fd, "script\n"); + break; + case LINK_TAG: + PR_fprintf(fd, "link\n"); + break; + case STYLE_TAG: + PR_fprintf(fd, "style\n"); + break; + case COMMENT_TAG: + PR_fprintf(fd, "comment\n"); + break; + case OTHER_TAG: + default: + PR_fprintf(fd, "other\n"); + break; + } + + PR_fprintf(fd, "Attributes:\n"); + for (pair = ti->attList; pair; pair = pair->next) { + PR_fprintf(fd, "\t%s=%s\n", pair->attribute, + pair->value ? pair->value : ""); + } + PR_fprintf(fd, "Text:%s\n", ti->text ? ti->text : ""); + + PR_fprintf(fd, "---End of tag---\n"); +} + +/************************************************************************ + * + * P r i n t H T M L S t r e a m + * + */ +static void +PrintHTMLStream(PRFileDesc *fd, HTMLItem *head) +{ + while (head) { + if (head->type == TAG_ITEM) { + PrintTagItem(fd, head->item.tag); + } else { + PR_fprintf(fd, "\nTEXT:\n-----\n%s\n-----\n\n", head->item.text); + } + head = head->next; + } +} + +/************************************************************************ + * + * S a v e I n l i n e S c r i p t + * + */ +static int +SaveInlineScript(char *text, char *id, char *basedir, char *archiveDir) +{ + char *filename = NULL; + PRFileDesc *fd = NULL; + int retval = -1; + PRInt32 writeLen; + char *ilDir = NULL; + + if (!text || !id || !archiveDir) { + return -1; + } + + if (dumpParse) { + PR_fprintf(outputFD, "SaveInlineScript: text=%s, id=%s, \n" + "basedir=%s, archiveDir=%s\n", + text, id, basedir, archiveDir); + } + + /* Make sure the archive directory is around */ + if (ensureExists(basedir, archiveDir) != PR_SUCCESS) { + PR_fprintf(errorFD, + "ERROR: Unable to create archive directory %s.\n", archiveDir); + errorCount++; + return -1; + } + + /* Make sure the inline script directory is around */ + ilDir = PR_smprintf("%s/inlineScripts", archiveDir); + scriptdir = "inlineScripts"; + if (ensureExists(basedir, ilDir) != PR_SUCCESS) { + PR_fprintf(errorFD, + "ERROR: Unable to create directory %s.\n", ilDir); + errorCount++; + return -1; + } + + filename = PR_smprintf("%s/%s/%s", basedir, ilDir, id); + + /* If the file already exists, give a warning, then blow it away */ + if (PR_Access(filename, PR_ACCESS_EXISTS) == PR_SUCCESS) { + PR_fprintf(errorFD, + "warning: file \"%s\" already exists--will overwrite.\n", + filename); + warningCount++; + if (rm_dash_r(filename)) { + PR_fprintf(errorFD, "ERROR: Unable to delete %s.\n", filename); + errorCount++; + goto finish; + } + } + + /* Write text into file with name id */ + fd = PR_Open(filename, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0777); + if (!fd) { + PR_fprintf(errorFD, "ERROR: Unable to create file \"%s\".\n", + filename); + errorCount++; + goto finish; + } + writeLen = strlen(text); + if (PR_Write(fd, text, writeLen) != writeLen) { + PR_fprintf(errorFD, "ERROR: Unable to write to file \"%s\".\n", + filename); + errorCount++; + goto finish; + } + + retval = 0; +finish: + if (filename) { + PR_smprintf_free(filename); + } + if (ilDir) { + PR_smprintf_free(ilDir); + } + if (fd) { + PR_Close(fd); + } + return retval; +} + +/************************************************************************ + * + * S a v e U n n a m a b l e S c r i p t + * + */ +static int +SaveUnnamableScript(char *text, char *basedir, char *archiveDir, + char *HTMLfilename) +{ + char *id = NULL; + char *ext = NULL; + char *start = NULL; + int retval = -1; + + if (!text || !archiveDir || !HTMLfilename) { + return -1; + } + + if (dumpParse) { + PR_fprintf(outputFD, "SaveUnnamableScript: text=%s, basedir=%s,\n" + "archiveDir=%s, filename=%s\n", + text, basedir, archiveDir, + HTMLfilename); + } + + /* Construct the filename */ + ext = PL_strrchr(HTMLfilename, '.'); + if (ext) { + *ext = '\0'; + } + for (start = HTMLfilename; strpbrk(start, "/\\"); + start = strpbrk(start, "/\\") + 1) + /* do nothing */; + if (*start == '\0') + start = HTMLfilename; + id = PR_smprintf("_%s%d", start, idOrdinal++); + if (ext) { + *ext = '.'; + } + + /* Now call SaveInlineScript to do the work */ + retval = SaveInlineScript(text, id, basedir, archiveDir); + + PR_Free(id); + + return retval; +} + +/************************************************************************ + * + * S a v e S o u r c e + * + */ +static int +SaveSource(char *src, char *codebase, char *basedir, char *archiveDir) +{ + char *from = NULL, *to = NULL; + int retval = -1; + char *arcDir = NULL; + + if (!src || !archiveDir) { + return -1; + } + + if (dumpParse) { + PR_fprintf(outputFD, "SaveSource: src=%s, codebase=%s, basedir=%s,\n" + "archiveDir=%s\n", + src, codebase, basedir, archiveDir); + } + + if (codebase) { + arcDir = PR_smprintf("%s/%s/%s/", basedir, codebase, archiveDir); + } else { + arcDir = PR_smprintf("%s/%s/", basedir, archiveDir); + } + + if (codebase) { + from = PR_smprintf("%s/%s/%s", basedir, codebase, src); + to = PR_smprintf("%s%s", arcDir, src); + } else { + from = PR_smprintf("%s/%s", basedir, src); + to = PR_smprintf("%s%s", arcDir, src); + } + + if (make_dirs(to, 0777)) { + PR_fprintf(errorFD, + "ERROR: Unable to create archive directory %s.\n", archiveDir); + errorCount++; + goto finish; + } + + retval = copyinto(from, to); +finish: + if (from) + PR_Free(from); + if (to) + PR_Free(to); + if (arcDir) + PR_Free(arcDir); + return retval; +} + +/************************************************************************ + * + * T a g T y p e T o S t r i n g + * + */ +char * +TagTypeToString(TAG_TYPE type) +{ + switch (type) { + case APPLET_TAG: + return "APPLET"; + case SCRIPT_TAG: + return "SCRIPT"; + case LINK_TAG: + return "LINK"; + case STYLE_TAG: + return "STYLE"; + default: + break; + } + return "unknown"; +} + +/************************************************************************ + * + * e x t r a c t _ j s + * + */ +static int +extract_js(char *filename) +{ + PRFileDesc *fd = NULL; + FileBuffer *fb = NULL; + HTMLItem *head = NULL; + HTMLItem *tail = NULL; + HTMLItem *curitem = NULL; + HTMLItem *styleList = NULL; + HTMLItem *styleListTail = NULL; + HTMLItem *entityList = NULL; + HTMLItem *entityListTail = NULL; + TagItem *tagp = NULL; + char *text = NULL; + char *tagerr = NULL; + char *archiveDir = NULL; + char *firstArchiveDir = NULL; + char *basedir = NULL; + PRInt32 textStart; + PRInt32 curOffset; + HTML_STATE state; + int curchar; + int retval = -1; + unsigned int linenum, startLine; + + /* Initialize the implicit ID counter for each file */ + idOrdinal = 0; + + /* + * First, parse the HTML into a stream of tags and text. + */ + + fd = PR_Open(filename, PR_RDONLY, 0); + if (!fd) { + PR_fprintf(errorFD, "Unable to open %s for reading.\n", filename); + errorCount++; + return -1; + } + + /* Construct base directory of filename. */ + { + char *cp; + + basedir = PL_strdup(filename); + + /* Remove trailing slashes */ + while ((cp = PL_strprbrk(basedir, "/\\")) == + (basedir + strlen(basedir) - 1)) { + *cp = '\0'; + } + + /* Now remove everything from the last slash (which will be followed + * by a filename) to the end */ + cp = PL_strprbrk(basedir, "/\\"); + if (cp) { + *cp = '\0'; + } + } + + state = TEXT_HTML_STATE; + + fb = FB_Create(fd); + + textStart = 0; + startLine = 0; + while (linenum = FB_GetLineNum(fb), (curchar = FB_GetChar(fb)) != + EOF) { + switch (state) { + case TEXT_HTML_STATE: + if (curchar == '<') { + /* + * Found a tag + */ + /* Save the text so far to a new text item */ + curOffset = FB_GetPointer(fb) - 2; + if (curOffset >= textStart) { + if (FB_GetRange(fb, textStart, curOffset, + &text) != + curOffset - textStart + 1) { + PR_fprintf(errorFD, + "Unable to read from %s.\n", + filename); + errorCount++; + goto loser; + } + /* little fudge here. If the first character on a line + * is '<', meaning a new tag, the preceding text item + * actually ends on the previous line. In this case + * we will be saying that the text segment ends on the + * next line. I don't think this matters for text items. */ + curitem = CreateTextItem(text, startLine, + linenum); + text = NULL; + if (tail == NULL) { + head = tail = curitem; + } else { + tail->next = curitem; + tail = curitem; + } + } + + /* Process the tag */ + tagp = ProcessTag(fb, &tagerr); + if (!tagp) { + if (tagerr) { + PR_fprintf(errorFD, "Error in file %s: %s\n", + filename, tagerr); + errorCount++; + } else { + PR_fprintf(errorFD, + "Error in file %s, in tag starting at line %d\n", + filename, linenum); + errorCount++; + } + goto loser; + } + /* Add the tag to the list */ + curitem = CreateTagItem(tagp, linenum, FB_GetLineNum(fb)); + if (tail == NULL) { + head = tail = curitem; + } else { + tail->next = curitem; + tail = curitem; + } + + /* What's the next state */ + if (tagp->type == SCRIPT_TAG) { + state = SCRIPT_HTML_STATE; + } + + /* Start recording text from the new offset */ + textStart = FB_GetPointer(fb); + startLine = FB_GetLineNum(fb); + } else { + /* regular character. Next! */ + } + break; + case SCRIPT_HTML_STATE: + if (curchar == '<') { + char *cp; + /* + * If this is a </script> tag, then we're at the end of the + * script. Otherwise, ignore + */ + curOffset = FB_GetPointer(fb) - 1; + cp = NULL; + if (FB_GetRange(fb, curOffset, curOffset + 8, &cp) != 9) { + if (cp) { + PR_Free(cp); + cp = NULL; + } + } else { + /* compare the strings */ + if (!PORT_Strncasecmp(cp, "</script>", 9)) { + /* This is the end of the script. Record the text. */ + curOffset--; + if (curOffset >= textStart) { + if (FB_GetRange(fb, textStart, curOffset, &text) != + curOffset - textStart + 1) { + PR_fprintf(errorFD, "Unable to read from %s.\n", + filename); + errorCount++; + goto loser; + } + curitem = CreateTextItem(text, startLine, linenum); + text = NULL; + if (tail == NULL) { + head = tail = curitem; + } else { + tail->next = curitem; + tail = curitem; + } + } + + /* Now parse the /script tag and put it on the list */ + tagp = ProcessTag(fb, &tagerr); + if (!tagp) { + if (tagerr) { + PR_fprintf(errorFD, "Error in file %s: %s\n", + filename, tagerr); + } else { + PR_fprintf(errorFD, + "Error in file %s, in tag starting at" + " line %d\n", + filename, linenum); + } + errorCount++; + goto loser; + } + curitem = CreateTagItem(tagp, linenum, + FB_GetLineNum(fb)); + if (tail == NULL) { + head = tail = curitem; + } else { + tail->next = curitem; + tail = curitem; + } + + /* go back to text state */ + state = TEXT_HTML_STATE; + + textStart = FB_GetPointer(fb); + startLine = FB_GetLineNum(fb); + } + } + } + break; + } + } + + /* End of the file. Wrap up any remaining text */ + if (state == SCRIPT_HTML_STATE) { + if (tail && tail->type == TAG_ITEM) { + PR_fprintf(errorFD, "ERROR: <SCRIPT> tag at %s:%d is not followed " + "by a </SCRIPT> tag.\n", + filename, tail->startLine); + } else { + PR_fprintf(errorFD, "ERROR: <SCRIPT> tag in file %s is not followed" + " by a </SCRIPT tag.\n", + filename); + } + errorCount++; + goto loser; + } + curOffset = FB_GetPointer(fb) - 1; + if (curOffset >= textStart) { + text = NULL; + if (FB_GetRange(fb, textStart, curOffset, &text) != + curOffset - textStart + 1) { + PR_fprintf(errorFD, "Unable to read from %s.\n", filename); + errorCount++; + goto loser; + } + curitem = CreateTextItem(text, startLine, linenum); + text = NULL; + if (tail == NULL) { + head = tail = curitem; + } else { + tail->next = curitem; + tail = curitem; + } + } + + if (dumpParse) { + PrintHTMLStream(outputFD, head); + } + + /* + * Now we have a stream of tags and text. Go through and deal with each. + */ + for (curitem = head; curitem; curitem = curitem->next) { + TagItem *tagp = NULL; + AVPair *pairp = NULL; + char *src = NULL, *id = NULL, *codebase = NULL; + PRBool hasEventHandler = PR_FALSE; + int i; + + /* Reset archive directory for each tag */ + if (archiveDir) { + PR_Free(archiveDir); + archiveDir = NULL; + } + + /* We only analyze tags */ + if (curitem->type != TAG_ITEM) { + continue; + } + + tagp = curitem->item.tag; + + /* go through the attributes to get information */ + for (pairp = tagp->attList; pairp; pairp = pairp->next) { + + /* ARCHIVE= */ + if (!PL_strcasecmp(pairp->attribute, "archive")) { + if (archiveDir) { + /* Duplicate attribute. Print warning */ + PR_fprintf(errorFD, + "warning: \"%s\" attribute overwrites previous attribute" + " in tag starting at %s:%d.\n", + pairp->attribute, filename, curitem->startLine); + warningCount++; + PR_Free(archiveDir); + } + archiveDir = PL_strdup(pairp->value); + + /* Substiture ".arc" for ".jar" */ + if ((PL_strlen(archiveDir) < 4) || + PL_strcasecmp((archiveDir + strlen(archiveDir) - 4), + ".jar")) { + PR_fprintf(errorFD, + "warning: ARCHIVE attribute should end in \".jar\" in tag" + " starting on %s:%d.\n", + filename, curitem->startLine); + warningCount++; + PR_Free(archiveDir); + archiveDir = PR_smprintf("%s.arc", archiveDir); + } else { + PL_strcpy(archiveDir + strlen(archiveDir) - 4, ".arc"); + } + + /* Record the first archive. This will be used later if + * the archive is not specified */ + if (firstArchiveDir == NULL) { + firstArchiveDir = PL_strdup(archiveDir); + } + } + /* CODEBASE= */ + else if (!PL_strcasecmp(pairp->attribute, "codebase")) { + if (codebase) { + /* Duplicate attribute. Print warning */ + PR_fprintf(errorFD, + "warning: \"%s\" attribute overwrites previous attribute" + " in tag staring at %s:%d.\n", + pairp->attribute, filename, curitem->startLine); + warningCount++; + } + codebase = pairp->value; + } + /* SRC= and HREF= */ + else if (!PORT_Strcasecmp(pairp->attribute, "src") || + !PORT_Strcasecmp(pairp->attribute, "href")) { + if (src) { + /* Duplicate attribute. Print warning */ + PR_fprintf(errorFD, + "warning: \"%s\" attribute overwrites previous attribute" + " in tag staring at %s:%d.\n", + pairp->attribute, filename, curitem->startLine); + warningCount++; + } + src = pairp->value; + } + /* CODE= */ + else if (!PORT_Strcasecmp(pairp->attribute, "code")) { + /*!!!XXX Change PORT to PL all over this code !!! */ + if (src) { + /* Duplicate attribute. Print warning */ + PR_fprintf(errorFD, + "warning: \"%s\" attribute overwrites previous attribute" + " ,in tag staring at %s:%d.\n", + pairp->attribute, filename, curitem->startLine); + warningCount++; + } + src = pairp->value; + + /* Append a .class if one is not already present */ + if ((PL_strlen(src) < 6) || + PL_strcasecmp((src + PL_strlen(src) - 6), ".class")) { + src = PR_smprintf("%s.class", src); + /* Put this string back into the data structure so it + * will be deallocated properly */ + PR_Free(pairp->value); + pairp->value = src; + } + } + /* ID= */ + else if (!PL_strcasecmp(pairp->attribute, "id")) { + if (id) { + /* Duplicate attribute. Print warning */ + PR_fprintf(errorFD, + "warning: \"%s\" attribute overwrites previous attribute" + " in tag staring at %s:%d.\n", + pairp->attribute, filename, curitem->startLine); + warningCount++; + } + id = pairp->value; + } + + /* STYLE= */ + /* style= attributes, along with JS entities, are stored into + * files with dynamically generated names. The filenames are + * based on the order in which the text is found in the file. + * All JS entities on all lines up to and including the line + * containing the end of the tag that has this style= attribute + * will be processed before this style=attribute. So we need + * to record the line that this _tag_ (not the attribute) ends on. + */ + else if (!PL_strcasecmp(pairp->attribute, "style") && pairp->value) { + HTMLItem *styleItem; + /* Put this item on the style list */ + styleItem = CreateTextItem(PL_strdup(pairp->value), + curitem->startLine, curitem->endLine); + if (styleListTail == NULL) { + styleList = styleListTail = styleItem; + } else { + styleListTail->next = styleItem; + styleListTail = styleItem; + } + } + /* Event handlers */ + else { + for (i = 0; i < num_handlers; i++) { + if (!PL_strcasecmp(event_handlers[i], pairp->attribute)) { + hasEventHandler = PR_TRUE; + break; + } + } + } + + /* JS Entity */ + { + char *entityStart, *entityEnd; + HTMLItem *entityItem; + + /* go through each JavaScript entity ( &{...}; ) and store it + * in the entityList. The important thing is to record what + * line number it's on, so we can get it in the right order + * in relation to style= attributes. + * Apparently, these can't flow across lines, so the start and + * end line will be the same. That helps matters. + */ + entityEnd = pairp->value; + while (entityEnd && + (entityStart = PL_strstr(entityEnd, "&{")) /*}*/ != NULL) { + entityStart += 2; /* point at beginning of actual entity */ + entityEnd = PL_strchr(entityStart, '}'); + if (entityEnd) { + /* Put this item on the entity list */ + *entityEnd = '\0'; + entityItem = CreateTextItem(PL_strdup(entityStart), + pairp->valueLine, pairp->valueLine); + *entityEnd = /* { */ '}'; + if (entityListTail) { + entityListTail->next = entityItem; + entityListTail = entityItem; + } else { + entityList = entityListTail = entityItem; + } + } + } + } + } + + /* If no archive was supplied, we use the first one of the file */ + if (!archiveDir && firstArchiveDir) { + archiveDir = PL_strdup(firstArchiveDir); + } + + /* If we have an event handler, we need to archive this tag */ + if (hasEventHandler) { + if (!id) { + PR_fprintf(errorFD, + "warning: tag starting at %s:%d has event handler but" + " no ID attribute. The tag will not be signed.\n", + filename, curitem->startLine); + warningCount++; + } else if (!archiveDir) { + PR_fprintf(errorFD, + "warning: tag starting at %s:%d has event handler but" + " no ARCHIVE attribute. The tag will not be signed.\n", + filename, curitem->startLine); + warningCount++; + } else { + if (SaveInlineScript(tagp->text, id, basedir, archiveDir)) { + goto loser; + } + } + } + + switch (tagp->type) { + case APPLET_TAG: + if (!src) { + PR_fprintf(errorFD, + "error: APPLET tag starting on %s:%d has no CODE " + "attribute.\n", + filename, curitem->startLine); + errorCount++; + goto loser; + } else if (!archiveDir) { + PR_fprintf(errorFD, + "error: APPLET tag starting on %s:%d has no ARCHIVE " + "attribute.\n", + filename, curitem->startLine); + errorCount++; + goto loser; + } else { + if (SaveSource(src, codebase, basedir, archiveDir)) { + goto loser; + } + } + break; + case SCRIPT_TAG: + case LINK_TAG: + case STYLE_TAG: + if (!archiveDir) { + PR_fprintf(errorFD, + "error: %s tag starting on %s:%d has no ARCHIVE " + "attribute.\n", + TagTypeToString(tagp->type), + filename, curitem->startLine); + errorCount++; + goto loser; + } else if (src) { + if (SaveSource(src, codebase, basedir, archiveDir)) { + goto loser; + } + } else if (id) { + /* Save the next text item */ + if (!curitem->next || (curitem->next->type != + TEXT_ITEM)) { + PR_fprintf(errorFD, + "warning: %s tag starting on %s:%d is not followed" + " by script text.\n", + TagTypeToString(tagp->type), + filename, curitem->startLine); + warningCount++; + /* just create empty file */ + if (SaveInlineScript("", id, basedir, archiveDir)) { + goto loser; + } + } else { + curitem = curitem->next; + if (SaveInlineScript(curitem->item.text, + id, basedir, + archiveDir)) { + goto loser; + } + } + } else { + /* No src or id tag--warning */ + PR_fprintf(errorFD, + "warning: %s tag starting on %s:%d has no SRC or" + " ID attributes. Will not sign.\n", + TagTypeToString(tagp->type), filename, curitem->startLine); + warningCount++; + } + break; + default: + /* do nothing for other tags */ + break; + } + } + + /* Now deal with all the unnamable scripts */ + if (firstArchiveDir) { + HTMLItem *style, *entity; + + /* Go through the lists of JS entities and style attributes. Do them + * in chronological order within a list. Pick the list with the lower + * endLine. In case of a tie, entities come first. + */ + style = styleList; + entity = entityList; + while (style || entity) { + if (!entity || (style && (style->endLine < entity->endLine))) { + /* Process style */ + SaveUnnamableScript(style->item.text, basedir, firstArchiveDir, + filename); + style = style->next; + } else { + /* Process entity */ + SaveUnnamableScript(entity->item.text, basedir, firstArchiveDir, + filename); + entity = entity->next; + } + } + } + + retval = 0; +loser: + /* Blow away the stream */ + while (head) { + curitem = head; + head = head->next; + DestroyHTMLItem(curitem); + } + while (styleList) { + curitem = styleList; + styleList = styleList->next; + DestroyHTMLItem(curitem); + } + while (entityList) { + curitem = entityList; + entityList = entityList->next; + DestroyHTMLItem(curitem); + } + if (text) { + PR_Free(text); + text = NULL; + } + if (fb) { + FB_Destroy(fb); + fb = NULL; + } + if (fd) { + PR_Close(fd); + } + if (tagerr) { + PR_smprintf_free(tagerr); + tagerr = NULL; + } + if (archiveDir) { + PR_Free(archiveDir); + archiveDir = NULL; + } + if (firstArchiveDir) { + PR_Free(firstArchiveDir); + firstArchiveDir = NULL; + } + if (entityListTail) { + PR_Free(entityListTail); + } + if (curitem) { + PR_Free(curitem); + } + if (basedir) { + PR_Free(basedir); + } + return retval; +} + +/********************************************************************** + * + * e n s u r e E x i s t s + * + * Check for existence of indicated directory. If it doesn't exist, + * it will be created. + * Returns PR_SUCCESS if the directory is present, PR_FAILURE otherwise. + */ +static PRStatus +ensureExists(char *base, char *path) +{ + char fn[FNSIZE]; + PRDir *dir; + sprintf(fn, "%s/%s", base, path); + + /*PR_fprintf(outputFD, "Trying to open directory %s.\n", fn);*/ + + if ((dir = PR_OpenDir(fn))) { + PR_CloseDir(dir); + return PR_SUCCESS; + } + return PR_MkDir(fn, 0777); +} + +/*************************************************************************** + * + * m a k e _ d i r s + * + * Ensure that the directory portion of the path exists. This may require + * making the directory, and its parent, and its parent's parent, etc. + */ +static int +make_dirs(char *path, int file_perms) +{ + char *Path; + char *start; + char *sep; + int ret = 0; + PRFileInfo info; + + if (!path) { + return 0; + } + + Path = PL_strdup(path); + if (!Path) { + return 0; + } + + start = strpbrk(Path, "/\\"); + if (!start) { + goto loser; + } + start++; /* start right after first slash */ + + /* Each time through the loop add one more directory. */ + while ((sep = strpbrk(start, "/\\"))) { + *sep = '\0'; + + if (PR_GetFileInfo(Path, &info) != PR_SUCCESS) { + /* No such dir, we have to create it */ + if (PR_MkDir(Path, file_perms) != PR_SUCCESS) { + PR_fprintf(errorFD, "ERROR: Unable to create directory %s.\n", + Path); + errorCount++; + ret = -1; + goto loser; + } + } else { + /* something exists by this name, make sure it's a directory */ + if (info.type != PR_FILE_DIRECTORY) { + PR_fprintf(errorFD, "ERROR: Unable to create directory %s.\n", + Path); + errorCount++; + ret = -1; + goto loser; + } + } + + start = sep + 1; /* start after the next slash */ + *sep = '/'; + } + +loser: + PR_Free(Path); + return ret; +} + +/* + * c o p y i n t o + * + * Function to copy file "from" to path "to". + * + */ +static int +copyinto(char *from, char *to) +{ + PRInt32 num; + char buf[BUFSIZ]; + PRFileDesc *infp = NULL, *outfp = NULL; + int retval = -1; + + if ((infp = PR_Open(from, PR_RDONLY, 0777)) == NULL) { + PR_fprintf(errorFD, "ERROR: Unable to open \"%s\" for reading.\n", + from); + errorCount++; + goto finish; + } + + /* If to already exists, print a warning before deleting it */ + if (PR_Access(to, PR_ACCESS_EXISTS) == PR_SUCCESS) { + PR_fprintf(errorFD, "warning: %s already exists--will overwrite\n", to); + warningCount++; + if (rm_dash_r(to)) { + PR_fprintf(errorFD, + "ERROR: Unable to remove %s.\n", to); + errorCount++; + goto finish; + } + } + + if ((outfp = PR_Open(to, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0777)) == + NULL) { + char *errBuf = NULL; + + errBuf = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_fprintf(errorFD, "ERROR: Unable to open \"%s\" for writing.\n", to); + if (PR_GetErrorText(errBuf)) { + PR_fprintf(errorFD, "Cause: %s\n", errBuf); + } + if (errBuf) { + PR_Free(errBuf); + } + errorCount++; + goto finish; + } + + while ((num = PR_Read(infp, buf, BUFSIZ)) > 0) { + if (PR_Write(outfp, buf, num) != num) { + PR_fprintf(errorFD, "ERROR: Error writing to %s.\n", to); + errorCount++; + goto finish; + } + } + + retval = 0; +finish: + if (infp) + PR_Close(infp); + if (outfp) + PR_Close(outfp); + + return retval; +} diff --git a/security/nss/cmd/signtool/list.c b/security/nss/cmd/signtool/list.c new file mode 100644 index 000000000..70f62d2b1 --- /dev/null +++ b/security/nss/cmd/signtool/list.c @@ -0,0 +1,215 @@ +/* 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 "signtool.h" +#include "pk11func.h" +#include "certdb.h" + +static int num_trav_certs = 0; +static SECStatus cert_trav_callback(CERTCertificate *cert, SECItem *k, + void *data); + +/********************************************************************* + * + * L i s t C e r t s + */ +int +ListCerts(char *key, int list_certs) +{ + int failed = 0; + SECStatus rv; + char *ugly_list; + CERTCertDBHandle *db; + + CERTCertificate *cert; + CERTVerifyLog errlog; + + errlog.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (errlog.arena == NULL) { + out_of_memory(); + } + errlog.head = NULL; + errlog.tail = NULL; + errlog.count = 0; + + ugly_list = PORT_ZAlloc(16); + + if (ugly_list == NULL) { + out_of_memory(); + } + + *ugly_list = 0; + + db = CERT_GetDefaultCertDB(); + + if (list_certs == 2) { + PR_fprintf(outputFD, "\nS Certificates\n"); + PR_fprintf(outputFD, "- ------------\n"); + } else { + PR_fprintf(outputFD, "\nObject signing certificates\n"); + PR_fprintf(outputFD, "---------------------------------------\n"); + } + + num_trav_certs = 0; + + /* Traverse ALL tokens in all slots, authenticating to them all */ + rv = PK11_TraverseSlotCerts(cert_trav_callback, (void *)&list_certs, + &pwdata); + + if (rv) { + PR_fprintf(outputFD, "**Traverse of ALL slots & tokens failed**\n"); + return -1; + } + + if (num_trav_certs == 0) { + PR_fprintf(outputFD, + "You don't appear to have any object signing certificates.\n"); + } + + if (list_certs == 2) { + PR_fprintf(outputFD, "- ------------\n"); + } else { + PR_fprintf(outputFD, "---------------------------------------\n"); + } + + if (list_certs == 1) { + PR_fprintf(outputFD, + "For a list including CA's, use \"%s -L\"\n", PROGRAM_NAME); + } + + if (list_certs == 2) { + PR_fprintf(outputFD, + "Certificates that can be used to sign objects have *'s to " + "their left.\n"); + } + + if (key) { + /* Do an analysis of the given cert */ + + cert = PK11_FindCertFromNickname(key, &pwdata); + + if (cert) { + PR_fprintf(outputFD, + "\nThe certificate with nickname \"%s\" was found:\n", + cert->nickname); + PR_fprintf(outputFD, "\tsubject name: %s\n", cert->subjectName); + PR_fprintf(outputFD, "\tissuer name: %s\n", cert->issuerName); + + PR_fprintf(outputFD, "\n"); + + rv = CERT_CertTimesValid(cert); + if (rv != SECSuccess) { + PR_fprintf(outputFD, "**This certificate is expired**\n"); + } else { + PR_fprintf(outputFD, "This certificate is not expired.\n"); + } + + rv = CERT_VerifyCert(db, cert, PR_TRUE, + certUsageObjectSigner, PR_Now(), &pwdata, &errlog); + + if (rv != SECSuccess) { + failed = 1; + if (errlog.count > 0) { + PR_fprintf(outputFD, + "**Certificate validation failed for the " + "following reason(s):**\n"); + } else { + PR_fprintf(outputFD, "**Certificate validation failed**"); + } + } else { + PR_fprintf(outputFD, "This certificate is valid.\n"); + } + displayVerifyLog(&errlog); + + } else { + failed = 1; + PR_fprintf(outputFD, + "The certificate with nickname \"%s\" was NOT FOUND\n", key); + } + } + + if (errlog.arena != NULL) { + PORT_FreeArena(errlog.arena, PR_FALSE); + } + + if (failed) { + return -1; + } + return 0; +} + +/******************************************************************** + * + * c e r t _ t r a v _ c a l l b a c k + */ +static SECStatus +cert_trav_callback(CERTCertificate *cert, SECItem *k, void *data) +{ + int list_certs = 1; + char *name; + + if (data) { + list_certs = *((int *)data); + } + +#define LISTING_USER_SIGNING_CERTS (list_certs == 1) +#define LISTING_ALL_CERTS (list_certs == 2) + + name = cert->nickname; + if (name) { + int isSigningCert; + + isSigningCert = cert->nsCertType & NS_CERT_TYPE_OBJECT_SIGNING; + if (!isSigningCert && LISTING_USER_SIGNING_CERTS) + return (SECSuccess); + + /* Display this name or email address */ + num_trav_certs++; + + if (LISTING_ALL_CERTS) { + PR_fprintf(outputFD, "%s ", isSigningCert ? "*" : " "); + } + PR_fprintf(outputFD, "%s\n", name); + + if (LISTING_USER_SIGNING_CERTS) { + int rv = SECFailure; + if (rv) { + CERTCertificate *issuerCert; + issuerCert = CERT_FindCertIssuer(cert, PR_Now(), + certUsageObjectSigner); + if (issuerCert) { + if (issuerCert->nickname && issuerCert->nickname[0]) { + PR_fprintf(outputFD, " Issued by: %s\n", + issuerCert->nickname); + rv = SECSuccess; + } + CERT_DestroyCertificate(issuerCert); + } + } + if (rv && cert->issuerName && cert->issuerName[0]) { + PR_fprintf(outputFD, " Issued by: %s \n", cert->issuerName); + } + { + char *expires; + expires = DER_TimeChoiceDayToAscii(&cert->validity.notAfter); + if (expires) { + PR_fprintf(outputFD, " Expires: %s\n", expires); + PORT_Free(expires); + } + } + + rv = CERT_VerifyCertNow(cert->dbhandle, cert, + PR_TRUE, certUsageObjectSigner, &pwdata); + + if (rv != SECSuccess) { + rv = PORT_GetError(); + PR_fprintf(outputFD, + " ++ Error ++ THIS CERTIFICATE IS NOT VALID (%s)\n", + secErrorString(rv)); + } + } + } + + return (SECSuccess); +} diff --git a/security/nss/cmd/signtool/manifest.mn b/security/nss/cmd/signtool/manifest.mn new file mode 100644 index 000000000..40be262db --- /dev/null +++ b/security/nss/cmd/signtool/manifest.mn @@ -0,0 +1,25 @@ +# 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/. + +CORE_DEPTH = ../.. + +MODULE = nss + +EXPORTS = + +CSRCS = signtool.c \ + certgen.c \ + javascript.c \ + list.c \ + sign.c \ + util.c \ + verify.c \ + zip.c \ + $(NULL) + +PROGRAM = signtool + +REQUIRES = seccmd + +EXTRA_LIBS = $(JAR_LIBS) diff --git a/security/nss/cmd/signtool/sign.c b/security/nss/cmd/signtool/sign.c new file mode 100644 index 000000000..6e776069a --- /dev/null +++ b/security/nss/cmd/signtool/sign.c @@ -0,0 +1,834 @@ +/* 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 "signtool.h" +#include "zip.h" +#include "prmem.h" +#include "blapi.h" +#include "sechash.h" /* for HASH_GetHashObject() */ + +static int create_pk7(char *dir, char *keyName, int *keyType); +static int jar_find_key_type(CERTCertificate *cert); +static int manifesto(char *dirname, char *install_script, PRBool recurse); +static int manifesto_fn(char *relpath, char *basedir, char *reldir, + char *filename, void *arg); +static int manifesto_xpi_fn(char *relpath, char *basedir, char *reldir, + char *filename, void *arg); +static int sign_all_arc_fn(char *relpath, char *basedir, char *reldir, + char *filename, void *arg); +static int add_meta(FILE *fp, char *name); +static int SignFile(FILE *outFile, FILE *inFile, CERTCertificate *cert); +static int generate_SF_file(char *manifile, char *who); +static int calculate_MD5_range(FILE *fp, long r1, long r2, + JAR_Digest *dig); +static void SignOut(void *arg, const char *buf, unsigned long len); + +static char *metafile = NULL; +static int optimize = 0; +static FILE *mf; +static ZIPfile *zipfile = NULL; + +/* + * S i g n A r c h i v e + * + * Sign an individual archive tree. A directory + * called META-INF is created underneath this. + * + */ +int +SignArchive(char *tree, char *keyName, char *zip_file, int javascript, + char *meta_file, char *install_script, int _optimize, PRBool recurse) +{ + int status; + char tempfn[FNSIZE], fullfn[FNSIZE]; + int keyType = rsaKey; + + metafile = meta_file; + optimize = _optimize; + + /* To create XPI compatible Archive manifesto() must be run before + * the zipfile is opened. This is so the signed files are not added + * the archive before the crucial rsa/dsa file*/ + if (xpi_arc) { + manifesto(tree, install_script, recurse); + } + + if (zip_file) { + zipfile = JzipOpen(zip_file, NULL /*no comment*/); + } + + /*Sign and add files to the archive normally with manifesto()*/ + if (!xpi_arc) { + manifesto(tree, install_script, recurse); + } + + if (keyName) { + status = create_pk7(tree, keyName, &keyType); + if (status < 0) { + PR_fprintf(errorFD, "the tree \"%s\" was NOT SUCCESSFULLY SIGNED\n", + tree); + errorCount++; + exit(ERRX); + } + } + + /* Add the rsa/dsa file as the first file in the archive. This is crucial + * for a XPInstall compatible archive */ + if (xpi_arc) { + if (verbosity >= 0) { + PR_fprintf(outputFD, "%s \n", XPI_TEXT); + } + + /* rsa/dsa to zip */ + sprintf(tempfn, "META-INF/%s.%s", base, (keyType == dsaKey ? "dsa" + : "rsa")); + sprintf(fullfn, "%s/%s", tree, tempfn); + JzipAdd(fullfn, tempfn, zipfile, compression_level); + + /* Loop through all files & subdirectories, add to archive */ + foreach (tree, "", manifesto_xpi_fn, recurse, PR_FALSE /*include dirs */, + (void *)NULL) + ; + } + /* mf to zip */ + strcpy(tempfn, "META-INF/manifest.mf"); + sprintf(fullfn, "%s/%s", tree, tempfn); + JzipAdd(fullfn, tempfn, zipfile, compression_level); + + /* sf to zip */ + sprintf(tempfn, "META-INF/%s.sf", base); + sprintf(fullfn, "%s/%s", tree, tempfn); + JzipAdd(fullfn, tempfn, zipfile, compression_level); + + /* Add the rsa/dsa file to the zip archive normally */ + if (!xpi_arc) { + /* rsa/dsa to zip */ + sprintf(tempfn, "META-INF/%s.%s", base, (keyType == dsaKey ? "dsa" + : "rsa")); + sprintf(fullfn, "%s/%s", tree, tempfn); + JzipAdd(fullfn, tempfn, zipfile, compression_level); + } + + JzipClose(zipfile); + + if (verbosity >= 0) { + if (javascript) { + PR_fprintf(outputFD, "jarfile \"%s\" signed successfully\n", + zip_file); + } else { + PR_fprintf(outputFD, "tree \"%s\" signed successfully\n", + tree); + } + } + + return 0; +} + +typedef struct { + char *keyName; + int javascript; + char *metafile; + char *install_script; + int optimize; +} SignArcInfo; + +/* + * S i g n A l l A r c + * + * Javascript may generate multiple .arc directories, one + * for each jar archive needed. Sign them all. + * + */ +int +SignAllArc(char *jartree, char *keyName, int javascript, char *metafile, + char *install_script, int optimize, PRBool recurse) +{ + SignArcInfo info; + + info.keyName = keyName; + info.javascript = javascript; + info.metafile = metafile; + info.install_script = install_script; + info.optimize = optimize; + + return foreach (jartree, "", sign_all_arc_fn, recurse, + PR_TRUE /*include dirs*/, (void *)&info); +} + +static int +sign_all_arc_fn(char *relpath, char *basedir, char *reldir, char *filename, + void *arg) +{ + char *zipfile = NULL; + char *arc = NULL, *archive = NULL; + int retval = 0; + SignArcInfo *infop = (SignArcInfo *)arg; + + /* Make sure there is one and only one ".arc" in the relative path, + * and that it is at the end of the path (don't sign .arcs within .arcs) */ + if ((PL_strcaserstr(relpath, ".arc") == relpath + strlen(relpath) - 4) && + (PL_strcasestr(relpath, ".arc") == relpath + strlen(relpath) - 4)) { + + if (!infop) { + PR_fprintf(errorFD, "%s: Internal failure\n", PROGRAM_NAME); + errorCount++; + retval = -1; + goto finish; + } + archive = PR_smprintf("%s/%s", basedir, relpath); + + zipfile = PL_strdup(archive); + arc = PORT_Strrchr(zipfile, '.'); + + if (arc == NULL) { + PR_fprintf(errorFD, "%s: Internal failure\n", PROGRAM_NAME); + errorCount++; + retval = -1; + goto finish; + } + + PL_strcpy(arc, ".jar"); + + if (verbosity >= 0) { + PR_fprintf(outputFD, "\nsigning: %s\n", zipfile); + } + retval = SignArchive(archive, infop->keyName, zipfile, + infop->javascript, infop->metafile, infop->install_script, + infop->optimize, PR_TRUE /* recurse */); + } +finish: + if (archive) + PR_Free(archive); + if (zipfile) + PR_Free(zipfile); + + return retval; +} + +/********************************************************************* + * + * c r e a t e _ p k 7 + */ +static int +create_pk7(char *dir, char *keyName, int *keyType) +{ + int status = 0; + char *file_ext; + + CERTCertificate *cert; + CERTCertDBHandle *db; + + FILE *in, *out; + + char sf_file[FNSIZE]; + char pk7_file[FNSIZE]; + + /* open cert database */ + db = CERT_GetDefaultCertDB(); + + if (db == NULL) + return -1; + + /* find cert */ + /*cert = CERT_FindCertByNicknameOrEmailAddr(db, keyName);*/ + cert = PK11_FindCertFromNickname(keyName, &pwdata); + + if (cert == NULL) { + SECU_PrintError(PROGRAM_NAME, + "Cannot find the cert \"%s\"", keyName); + return -1; + } + + /* determine the key type, which sets the extension for pkcs7 object */ + + *keyType = jar_find_key_type(cert); + file_ext = (*keyType == dsaKey) ? "dsa" : "rsa"; + + sprintf(sf_file, "%s/META-INF/%s.sf", dir, base); + sprintf(pk7_file, "%s/META-INF/%s.%s", dir, base, file_ext); + + if ((in = fopen(sf_file, "rb")) == NULL) { + PR_fprintf(errorFD, "%s: Can't open %s for reading\n", PROGRAM_NAME, + sf_file); + errorCount++; + exit(ERRX); + } + + if ((out = fopen(pk7_file, "wb")) == NULL) { + PR_fprintf(errorFD, "%s: Can't open %s for writing\n", PROGRAM_NAME, + sf_file); + errorCount++; + exit(ERRX); + } + + status = SignFile(out, in, cert); + + CERT_DestroyCertificate(cert); + fclose(in); + fclose(out); + + if (status) { + PR_fprintf(errorFD, "%s: PROBLEM signing data (%s)\n", + PROGRAM_NAME, SECU_Strerror(PORT_GetError())); + errorCount++; + return -1; + } + + return 0; +} + +/* + * j a r _ f i n d _ k e y _ t y p e + * + * Determine the key type for a given cert, which + * should be rsaKey or dsaKey. Any error return 0. + * + */ +static int +jar_find_key_type(CERTCertificate *cert) +{ + SECKEYPrivateKey *privk = NULL; + KeyType keyType; + + /* determine its type */ + privk = PK11_FindKeyByAnyCert(cert, &pwdata); + if (privk == NULL) { + PR_fprintf(errorFD, "warning - can't find private key for this cert\n"); + warningCount++; + return 0; + } + + keyType = privk->keyType; + SECKEY_DestroyPrivateKey(privk); + return keyType; +} + +/* + * m a n i f e s t o + * + * Run once for every subdirectory in which a + * manifest is to be created -- usually exactly once. + * + */ +static int +manifesto(char *dirname, char *install_script, PRBool recurse) +{ + char metadir[FNSIZE], sfname[FNSIZE]; + + /* Create the META-INF directory to hold signing info */ + + if (PR_Access(dirname, PR_ACCESS_READ_OK)) { + PR_fprintf(errorFD, "%s: unable to read your directory: %s\n", + PROGRAM_NAME, dirname); + errorCount++; + perror(dirname); + exit(ERRX); + } + + if (PR_Access(dirname, PR_ACCESS_WRITE_OK)) { + PR_fprintf(errorFD, "%s: unable to write to your directory: %s\n", + PROGRAM_NAME, dirname); + errorCount++; + perror(dirname); + exit(ERRX); + } + + sprintf(metadir, "%s/META-INF", dirname); + + strcpy(sfname, metadir); + + PR_MkDir(metadir, 0777); + + strcat(metadir, "/"); + strcat(metadir, MANIFEST); + + if ((mf = fopen(metadir, "wb")) == NULL) { + perror(MANIFEST); + PR_fprintf(errorFD, "%s: Probably, the directory you are trying to" + " sign has\n", + PROGRAM_NAME); + PR_fprintf(errorFD, "%s: permissions problems or may not exist.\n", + PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + if (verbosity >= 0) { + PR_fprintf(outputFD, "Generating %s file..\n", metadir); + } + + fprintf(mf, "Manifest-Version: 1.0\n"); + fprintf(mf, "Created-By: %s\n", CREATOR); + fprintf(mf, "Comments: %s\n", BREAKAGE); + + if (scriptdir) { + fprintf(mf, "Comments: --\n"); + fprintf(mf, "Comments: --\n"); + fprintf(mf, "Comments: -- This archive signs Javascripts which may not necessarily\n"); + fprintf(mf, "Comments: -- be included in the physical jar file.\n"); + fprintf(mf, "Comments: --\n"); + fprintf(mf, "Comments: --\n"); + } + + if (install_script) + fprintf(mf, "Install-Script: %s\n", install_script); + + if (metafile) + add_meta(mf, "+"); + + /* Loop through all files & subdirectories */ + foreach (dirname, "", manifesto_fn, recurse, PR_FALSE /*include dirs */, + (void *)NULL) + ; + + fclose(mf); + + strcat(sfname, "/"); + strcat(sfname, base); + strcat(sfname, ".sf"); + + if (verbosity >= 0) { + PR_fprintf(outputFD, "Generating %s.sf file..\n", base); + } + generate_SF_file(metadir, sfname); + + return 0; +} + +/* + * m a n i f e s t o _ x p i _ f n + * + * Called by pointer from SignArchive(), once for + * each file within the directory. This function + * is only used for adding to XPI compatible archive + * + */ +static int +manifesto_xpi_fn(char *relpath, char *basedir, char *reldir, char *filename, void *arg) +{ + char fullname[FNSIZE]; + + if (verbosity >= 0) { + PR_fprintf(outputFD, "--> %s\n", relpath); + } + + /* extension matching */ + if (extensionsGiven) { + char *ext = PL_strrchr(relpath, '.'); + if (!ext) + return 0; + if (!PL_HashTableLookup(extensions, ext)) + return 0; + } + sprintf(fullname, "%s/%s", basedir, relpath); + JzipAdd(fullname, relpath, zipfile, compression_level); + + return 0; +} + +/* + * m a n i f e s t o _ f n + * + * Called by pointer from manifesto(), once for + * each file within the directory. + * + */ +static int +manifesto_fn(char *relpath, char *basedir, char *reldir, char *filename, void *arg) +{ + int use_js; + char *md5, *sha1; + + JAR_Digest dig; + char fullname[FNSIZE]; + + if (verbosity >= 0) { + PR_fprintf(outputFD, "--> %s\n", relpath); + } + + /* extension matching */ + if (extensionsGiven) { + char *ext = PL_strrchr(relpath, '.'); + if (!ext) + return 0; + if (!PL_HashTableLookup(extensions, ext)) + return 0; + } + + sprintf(fullname, "%s/%s", basedir, relpath); + + fprintf(mf, "\n"); + + use_js = 0; + + if (scriptdir && !PORT_Strcmp(scriptdir, reldir)) + use_js++; + + /* sign non-.js files inside .arc directories using the javascript magic */ + + if ((PL_strcaserstr(filename, ".js") != filename + strlen(filename) - 3) && + (PL_strcaserstr(reldir, ".arc") == reldir + strlen(filename) - 4)) + use_js++; + + if (use_js) { + fprintf(mf, "Name: %s\n", filename); + fprintf(mf, "Magic: javascript\n"); + + if (optimize == 0) + fprintf(mf, "javascript.id: %s\n", filename); + + if (metafile) + add_meta(mf, filename); + } else { + fprintf(mf, "Name: %s\n", relpath); + if (metafile) + add_meta(mf, relpath); + } + + JAR_digest_file(fullname, &dig); + + if (optimize == 0) { + fprintf(mf, "Digest-Algorithms: MD5 SHA1\n"); + + md5 = BTOA_DataToAscii(dig.md5, MD5_LENGTH); + fprintf(mf, "MD5-Digest: %s\n", md5); + PORT_Free(md5); + } + + sha1 = BTOA_DataToAscii(dig.sha1, SHA1_LENGTH); + fprintf(mf, "SHA1-Digest: %s\n", sha1); + PORT_Free(sha1); + + if (!use_js) { + JzipAdd(fullname, relpath, zipfile, compression_level); + } + + return 0; +} + +/* + * a d d _ m e t a + * + * Parse the metainfo file, and add any details + * necessary to the manifest file. In most cases you + * should be using the -i option (ie, for SmartUpdate). + * + */ +static int +add_meta(FILE *fp, char *name) +{ + FILE *met; + char buf[BUFSIZ]; + + int place; + char *pattern, *meta; + + int num = 0; + + if ((met = fopen(metafile, "r")) != NULL) { + while (fgets(buf, BUFSIZ, met)) { + char *s; + + for (s = buf; *s && *s != '\n' && *s != '\r'; s++) + ; + *s = 0; + + if (*buf == 0) + continue; + + pattern = buf; + + /* skip to whitespace */ + for (s = buf; *s && *s != ' ' && *s != '\t'; s++) + ; + + /* terminate pattern */ + if (*s == ' ' || *s == '\t') + *s++ = 0; + + /* eat through whitespace */ + while (*s == ' ' || *s == '\t') + s++; + + meta = s; + + /* this will eventually be regexp matching */ + + place = 0; + if (!PORT_Strcmp(pattern, name)) + place = 1; + + if (place) { + num++; + if (verbosity >= 0) { + PR_fprintf(outputFD, "[%s] %s\n", name, meta); + } + fprintf(fp, "%s\n", meta); + } + } + fclose(met); + } else { + PR_fprintf(errorFD, "%s: can't open metafile: %s\n", PROGRAM_NAME, + metafile); + errorCount++; + exit(ERRX); + } + + return num; +} + +/********************************************************************** + * + * S i g n F i l e + */ +static int +SignFile(FILE *outFile, FILE *inFile, CERTCertificate *cert) +{ + int nb; + char ibuf[4096], digestdata[32]; + const SECHashObject *hashObj; + void *hashcx; + unsigned int len; + + SECItem digest; + SEC_PKCS7ContentInfo *cinfo; + SECStatus rv; + + if (outFile == NULL || inFile == NULL || cert == NULL) + return -1; + + /* XXX probably want to extend interface to allow other hash algorithms */ + hashObj = HASH_GetHashObject(HASH_AlgSHA1); + + hashcx = (*hashObj->create)(); + if (hashcx == NULL) + return -1; + + (*hashObj->begin)(hashcx); + + for (;;) { + if (feof(inFile)) + break; + nb = fread(ibuf, 1, sizeof(ibuf), inFile); + if (nb == 0) { + if (ferror(inFile)) { + PORT_SetError(SEC_ERROR_IO); + (*hashObj->destroy)(hashcx, PR_TRUE); + return -1; + } + /* eof */ + break; + } + (*hashObj->update)(hashcx, (unsigned char *)ibuf, nb); + } + + (*hashObj->end)(hashcx, (unsigned char *)digestdata, &len, 32); + (*hashObj->destroy)(hashcx, PR_TRUE); + + digest.data = (unsigned char *)digestdata; + digest.len = len; + + cinfo = SEC_PKCS7CreateSignedData(cert, certUsageObjectSigner, NULL, + SEC_OID_SHA1, &digest, NULL, NULL); + + if (cinfo == NULL) + return -1; + + rv = SEC_PKCS7IncludeCertChain(cinfo, NULL); + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return -1; + } + + if (no_time == 0) { + rv = SEC_PKCS7AddSigningTime(cinfo); + if (rv != SECSuccess) { + /* don't check error */ + } + } + + rv = SEC_PKCS7Encode(cinfo, SignOut, outFile, NULL, NULL, &pwdata); + + SEC_PKCS7DestroyContentInfo(cinfo); + + if (rv != SECSuccess) + return -1; + + return 0; +} + +/* + * g e n e r a t e _ S F _ f i l e + * + * From the supplied manifest file, calculates + * digests on the various sections, creating a .SF + * file in the process. + * + */ +static int +generate_SF_file(char *manifile, char *who) +{ + FILE *sf; + FILE *mf; + long r1, r2, r3; + char whofile[FNSIZE]; + char *buf, *name = NULL; + char *md5, *sha1; + JAR_Digest dig; + int line = 0; + + strcpy(whofile, who); + + if ((mf = fopen(manifile, "rb")) == NULL) { + perror(manifile); + exit(ERRX); + } + + if ((sf = fopen(whofile, "wb")) == NULL) { + perror(who); + exit(ERRX); + } + + buf = (char *)PORT_ZAlloc(BUFSIZ); + + if (buf) + name = (char *)PORT_ZAlloc(BUFSIZ); + + if (buf == NULL || name == NULL) + out_of_memory(); + + fprintf(sf, "Signature-Version: 1.0\n"); + fprintf(sf, "Created-By: %s\n", CREATOR); + fprintf(sf, "Comments: %s\n", BREAKAGE); + + if (fgets(buf, BUFSIZ, mf) == NULL) { + PR_fprintf(errorFD, "%s: empty manifest file!\n", PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + if (strncmp(buf, "Manifest-Version:", 17)) { + PR_fprintf(errorFD, "%s: not a manifest file!\n", PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + fseek(mf, 0L, SEEK_SET); + + /* Process blocks of headers, and calculate their hashen */ + + while (1) { + /* Beginning range */ + r1 = ftell(mf); + + if (fgets(name, BUFSIZ, mf) == NULL) + break; + + line++; + + if (r1 != 0 && strncmp(name, "Name:", 5)) { + PR_fprintf(errorFD, + "warning: unexpected input in manifest file \"%s\" at line %d:\n", + manifile, line); + PR_fprintf(errorFD, "%s\n", name); + warningCount++; + } + + r2 = r1; + while (fgets(buf, BUFSIZ, mf)) { + if (*buf == 0 || *buf == '\n' || *buf == '\r') + break; + + line++; + + /* Ending range for hashing */ + r2 = ftell(mf); + } + + r3 = ftell(mf); + + if (r1) { + fprintf(sf, "\n"); + fprintf(sf, "%s", name); + } + + calculate_MD5_range(mf, r1, r2, &dig); + + if (optimize == 0) { + fprintf(sf, "Digest-Algorithms: MD5 SHA1\n"); + + md5 = BTOA_DataToAscii(dig.md5, MD5_LENGTH); + fprintf(sf, "MD5-Digest: %s\n", md5); + PORT_Free(md5); + } + + sha1 = BTOA_DataToAscii(dig.sha1, SHA1_LENGTH); + fprintf(sf, "SHA1-Digest: %s\n", sha1); + PORT_Free(sha1); + + /* restore normalcy after changing offset position */ + fseek(mf, r3, SEEK_SET); + } + + PORT_Free(buf); + PORT_Free(name); + + fclose(sf); + fclose(mf); + + return 0; +} + +/* + * c a l c u l a t e _ M D 5 _ r a n g e + * + * Calculate the MD5 digest on a range of bytes in + * the specified fopen'd file. Returns base64. + * + */ +static int +calculate_MD5_range(FILE *fp, long r1, long r2, JAR_Digest *dig) +{ + int num; + int range; + unsigned char *buf; + SECStatus rv; + + range = r2 - r1; + + /* position to the beginning of range */ + fseek(fp, r1, SEEK_SET); + + buf = (unsigned char *)PORT_ZAlloc(range); + if (buf == NULL) + out_of_memory(); + + if ((num = fread(buf, 1, range, fp)) != range) { + PR_fprintf(errorFD, "%s: expected %d bytes, got %d\n", PROGRAM_NAME, + range, num); + errorCount++; + exit(ERRX); + } + + rv = PK11_HashBuf(SEC_OID_MD5, dig->md5, buf, range); + if (rv == SECSuccess) { + rv = PK11_HashBuf(SEC_OID_SHA1, dig->sha1, buf, range); + } + if (rv != SECSuccess) { + PR_fprintf(errorFD, "%s: can't generate digest context\n", + PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + PORT_Free(buf); + + return 0; +} + +static void +SignOut(void *arg, const char *buf, unsigned long len) +{ + fwrite(buf, len, 1, (FILE *)arg); +} diff --git a/security/nss/cmd/signtool/signtool.c b/security/nss/cmd/signtool/signtool.c new file mode 100644 index 000000000..51857d638 --- /dev/null +++ b/security/nss/cmd/signtool/signtool.c @@ -0,0 +1,1068 @@ +/* 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/. */ + +/* + * SIGNTOOL + * + * A command line tool to create manifest files + * from a directory hierarchy. It is assumed that + * the tree will be equivalent to what resides + * or will reside in an archive. + * + * + */ + +#include "nss.h" +#include "signtool.h" +#include "prmem.h" +#include "prio.h" + +/*********************************************************************** + * Global Variable Definitions + */ +char *progName; /* argv[0] */ + +/* password data */ +secuPWData pwdata = { PW_NONE, 0 }; + +/* directories or files to exclude in descent */ +PLHashTable *excludeDirs = NULL; +static PRBool exclusionsGiven = PR_FALSE; + +/* zatharus is the man who knows no time, dies tragic death */ +int no_time = 0; + +/* -b basename of .rsa, .sf files */ +char *base = DEFAULT_BASE_NAME; + +/* Only sign files with this extension */ +PLHashTable *extensions = NULL; +PRBool extensionsGiven = PR_FALSE; + +char *scriptdir = NULL; + +int verbosity = 0; + +PRFileDesc *outputFD = NULL, *errorFD = NULL; + +int errorCount = 0, warningCount = 0; + +int compression_level = DEFAULT_COMPRESSION_LEVEL; +PRBool compression_level_specified = PR_FALSE; + +int xpi_arc = 0; + +/* Command-line arguments */ +static char *genkey = NULL; +static char *verify = NULL; +static char *zipfile = NULL; +static char *cert_dir = NULL; +static int javascript = 0; +static char *jartree = NULL; +static char *keyName = NULL; +static char *metafile = NULL; +static char *install_script = NULL; +static int list_certs = 0; +static int list_modules = 0; +static int optimize = 0; +static int enableOCSP = 0; +static char *tell_who = NULL; +static char *outfile = NULL; +static char *cmdFile = NULL; +static PRBool noRecurse = PR_FALSE; +static PRBool leaveArc = PR_FALSE; +static int keySize = -1; +static char *token = NULL; + +typedef enum { + UNKNOWN_OPT, + HELP_OPT, + LONG_HELP_OPT, + BASE_OPT, + COMPRESSION_OPT, + CERT_DIR_OPT, + EXTENSION_OPT, + INSTALL_SCRIPT_OPT, + SCRIPTDIR_OPT, + CERTNAME_OPT, + LIST_OBJSIGN_CERTS_OPT, + LIST_ALL_CERTS_OPT, + METAFILE_OPT, + OPTIMIZE_OPT, + ENABLE_OCSP_OPT, + PASSWORD_OPT, + VERIFY_OPT, + WHO_OPT, + EXCLUDE_OPT, + NO_TIME_OPT, + JAVASCRIPT_OPT, + ZIPFILE_OPT, + GENKEY_OPT, + MODULES_OPT, + NORECURSE_OPT, + SIGNDIR_OPT, + OUTFILE_OPT, + COMMAND_FILE_OPT, + LEAVE_ARC_OPT, + VERBOSITY_OPT, + KEYSIZE_OPT, + TOKEN_OPT, + XPI_ARC_OPT +} + +OPT_TYPE; + +typedef enum { + DUPLICATE_OPTION_ERR = 0, + OPTION_NEEDS_ARG_ERR +} + +Error; + +static char *errStrings[] = { + "warning: %s option specified more than once.\n" + "Only last specification will be used.\n", + "ERROR: option \"%s\" requires an argument.\n" +}; + +static int ProcessOneOpt(OPT_TYPE type, char *arg); + +/********************************************************************* + * + * P r o c e s s C o m m a n d F i l e + */ +int +ProcessCommandFile() +{ + PRFileDesc *fd; +#define CMD_FILE_BUFSIZE 1024 + char buf[CMD_FILE_BUFSIZE]; + char *equals; + int linenum = 0; + int retval = -1; + OPT_TYPE type; + + fd = PR_Open(cmdFile, PR_RDONLY, 0777); + if (!fd) { + PR_fprintf(errorFD, "ERROR: Unable to open command file %s.\n"); + errorCount++; + return -1; + } + + while (pr_fgets(buf, CMD_FILE_BUFSIZE, fd)) { + char *eol; + linenum++; + + /* Chop off final newline */ + eol = PL_strchr(buf, '\r'); + if (!eol) { + eol = PL_strchr(buf, '\n'); + } + if (eol) + *eol = '\0'; + + equals = PL_strchr(buf, '='); + if (!equals) { + continue; + } + + *equals = '\0'; + equals++; + + /* Now buf points to the attribute, and equals points to the value. */ + + /* This is pretty straightforward, just deal with whatever attribute + * this is */ + if (!PL_strcasecmp(buf, "basename")) { + type = BASE_OPT; + } else if (!PL_strcasecmp(buf, "compression")) { + type = COMPRESSION_OPT; + } else if (!PL_strcasecmp(buf, "certdir")) { + type = CERT_DIR_OPT; + } else if (!PL_strcasecmp(buf, "extension")) { + type = EXTENSION_OPT; + } else if (!PL_strcasecmp(buf, "generate")) { + type = GENKEY_OPT; + } else if (!PL_strcasecmp(buf, "installScript")) { + type = INSTALL_SCRIPT_OPT; + } else if (!PL_strcasecmp(buf, "javascriptdir")) { + type = SCRIPTDIR_OPT; + } else if (!PL_strcasecmp(buf, "htmldir")) { + type = JAVASCRIPT_OPT; + if (jartree) { + PR_fprintf(errorFD, + "warning: directory to be signed specified more than once." + " Only last specification will be used.\n"); + warningCount++; + PR_Free(jartree); + jartree = NULL; + } + jartree = PL_strdup(equals); + } else if (!PL_strcasecmp(buf, "certname")) { + type = CERTNAME_OPT; + } else if (!PL_strcasecmp(buf, "signdir")) { + type = SIGNDIR_OPT; + } else if (!PL_strcasecmp(buf, "list")) { + type = LIST_OBJSIGN_CERTS_OPT; + } else if (!PL_strcasecmp(buf, "listall")) { + type = LIST_ALL_CERTS_OPT; + } else if (!PL_strcasecmp(buf, "metafile")) { + type = METAFILE_OPT; + } else if (!PL_strcasecmp(buf, "modules")) { + type = MODULES_OPT; + } else if (!PL_strcasecmp(buf, "optimize")) { + type = OPTIMIZE_OPT; + } else if (!PL_strcasecmp(buf, "ocsp")) { + type = ENABLE_OCSP_OPT; + } else if (!PL_strcasecmp(buf, "password")) { + type = PASSWORD_OPT; + } else if (!PL_strcasecmp(buf, "verify")) { + type = VERIFY_OPT; + } else if (!PL_strcasecmp(buf, "who")) { + type = WHO_OPT; + } else if (!PL_strcasecmp(buf, "exclude")) { + type = EXCLUDE_OPT; + } else if (!PL_strcasecmp(buf, "notime")) { + type = NO_TIME_OPT; + } else if (!PL_strcasecmp(buf, "jarfile")) { + type = ZIPFILE_OPT; + } else if (!PL_strcasecmp(buf, "outfile")) { + type = OUTFILE_OPT; + } else if (!PL_strcasecmp(buf, "leavearc")) { + type = LEAVE_ARC_OPT; + } else if (!PL_strcasecmp(buf, "verbosity")) { + type = VERBOSITY_OPT; + } else if (!PL_strcasecmp(buf, "keysize")) { + type = KEYSIZE_OPT; + } else if (!PL_strcasecmp(buf, "token")) { + type = TOKEN_OPT; + } else if (!PL_strcasecmp(buf, "xpi")) { + type = XPI_ARC_OPT; + } else { + PR_fprintf(errorFD, + "warning: unknown attribute \"%s\" in command file, line %d.\n", + buf, linenum); + warningCount++; + type = UNKNOWN_OPT; + } + + /* Process the option, whatever it is */ + if (type != UNKNOWN_OPT) { + if (ProcessOneOpt(type, equals) == -1) { + goto finish; + } + } + } + + retval = 0; + +finish: + PR_Close(fd); + return retval; +} + +/********************************************************************* + * + * p a r s e _ a r g s + */ +static int +parse_args(int argc, char *argv[]) +{ + char *opt; + char *arg; + int needsInc = 0; + int i; + OPT_TYPE type; + + /* Loop over all arguments */ + for (i = 1; i < argc; i++) { + opt = argv[i]; + arg = NULL; + + if (opt[0] == '-') { + if (opt[1] == '-') { + /* word option */ + if (i < argc - 1) { + needsInc = 1; + arg = argv[i + 1]; + } else { + arg = NULL; + } + + if (!PL_strcasecmp(opt + 2, "norecurse")) { + type = NORECURSE_OPT; + } else if (!PL_strcasecmp(opt + 2, "leavearc")) { + type = LEAVE_ARC_OPT; + } else if (!PL_strcasecmp(opt + 2, "verbosity")) { + type = VERBOSITY_OPT; + } else if (!PL_strcasecmp(opt + 2, "outfile")) { + type = OUTFILE_OPT; + } else if (!PL_strcasecmp(opt + 2, "keysize")) { + type = KEYSIZE_OPT; + } else if (!PL_strcasecmp(opt + 2, "token")) { + type = TOKEN_OPT; + } else { + PR_fprintf(errorFD, "warning: unknown option: %s\n", + opt); + warningCount++; + type = UNKNOWN_OPT; + } + } else { + /* char option */ + if (opt[2] != '\0') { + arg = opt + 2; + } else if (i < argc - 1) { + needsInc = 1; + arg = argv[i + 1]; + } else { + arg = NULL; + } + + switch (opt[1]) { + case 'b': + type = BASE_OPT; + break; + case 'c': + type = COMPRESSION_OPT; + break; + case 'd': + type = CERT_DIR_OPT; + break; + case 'e': + type = EXTENSION_OPT; + break; + case 'f': + type = COMMAND_FILE_OPT; + break; + case 'h': + type = HELP_OPT; + break; + case 'H': + type = LONG_HELP_OPT; + break; + case 'i': + type = INSTALL_SCRIPT_OPT; + break; + case 'j': + type = SCRIPTDIR_OPT; + break; + case 'k': + type = CERTNAME_OPT; + break; + case 'l': + type = LIST_OBJSIGN_CERTS_OPT; + break; + case 'L': + type = LIST_ALL_CERTS_OPT; + break; + case 'm': + type = METAFILE_OPT; + break; + case 'o': + type = OPTIMIZE_OPT; + break; + case 'O': + type = ENABLE_OCSP_OPT; + break; + case 'p': + type = PASSWORD_OPT; + break; + case 'v': + type = VERIFY_OPT; + break; + case 'w': + type = WHO_OPT; + break; + case 'x': + type = EXCLUDE_OPT; + break; + case 'X': + type = XPI_ARC_OPT; + break; + case 'z': + type = NO_TIME_OPT; + break; + case 'J': + type = JAVASCRIPT_OPT; + break; + case 'Z': + type = ZIPFILE_OPT; + break; + case 'G': + type = GENKEY_OPT; + break; + case 'M': + type = MODULES_OPT; + break; + case 's': + type = KEYSIZE_OPT; + break; + case 't': + type = TOKEN_OPT; + break; + default: + type = UNKNOWN_OPT; + PR_fprintf(errorFD, "warning: unrecognized option: -%c.\n", + opt[1]); + warningCount++; + break; + } + } + } else { + type = UNKNOWN_OPT; + if (i == argc - 1) { + if (jartree) { + PR_fprintf(errorFD, + "warning: directory to be signed specified more than once.\n" + " Only last specification will be used.\n"); + warningCount++; + PR_Free(jartree); + jartree = NULL; + } + jartree = PL_strdup(opt); + } else { + PR_fprintf(errorFD, "warning: unrecognized option: %s\n", opt); + warningCount++; + } + } + + if (type != UNKNOWN_OPT) { + short ateArg = ProcessOneOpt(type, arg); + if (ateArg == -1) { + /* error */ + return -1; + } + if (ateArg && needsInc) { + i++; + } + } + } + + return 0; +} + +/********************************************************************* + * + * P r o c e s s O n e O p t + * + * Since options can come from different places (command file, word options, + * char options), this is a central function that is called to deal with + * them no matter where they come from. + * + * type is the type of option. + * arg is the argument to the option, possibly NULL. + * Returns 1 if the argument was eaten, 0 if it wasn't, and -1 for error. + */ +static int +ProcessOneOpt(OPT_TYPE type, char *arg) +{ + int ate = 0; + + switch (type) { + case HELP_OPT: + Usage(); + break; + case LONG_HELP_OPT: + LongUsage(); + break; + case BASE_OPT: + if (base) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], "-b"); + warningCount++; + PR_Free(base); + base = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], "-b"); + errorCount++; + goto loser; + } + base = PL_strdup(arg); + ate = 1; + break; + case COMPRESSION_OPT: + if (compression_level_specified) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], "-c"); + warningCount++; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], "-c"); + errorCount++; + goto loser; + } + compression_level = atoi(arg); + compression_level_specified = PR_TRUE; + ate = 1; + break; + case CERT_DIR_OPT: + if (cert_dir) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], "-d"); + warningCount++; + PR_Free(cert_dir); + cert_dir = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], "-d"); + errorCount++; + goto loser; + } + cert_dir = PL_strdup(arg); + ate = 1; + break; + case EXTENSION_OPT: + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "extension (-e)"); + errorCount++; + goto loser; + } + PL_HashTableAdd(extensions, arg, arg); + extensionsGiven = PR_TRUE; + ate = 1; + break; + case INSTALL_SCRIPT_OPT: + if (install_script) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], + "installScript (-i)"); + warningCount++; + PR_Free(install_script); + install_script = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "installScript (-i)"); + errorCount++; + goto loser; + } + install_script = PL_strdup(arg); + ate = 1; + break; + case SCRIPTDIR_OPT: + if (scriptdir) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], + "javascriptdir (-j)"); + warningCount++; + PR_Free(scriptdir); + scriptdir = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "javascriptdir (-j)"); + errorCount++; + goto loser; + } + scriptdir = PL_strdup(arg); + ate = 1; + break; + case CERTNAME_OPT: + if (keyName) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], + "keyName (-k)"); + warningCount++; + PR_Free(keyName); + keyName = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "keyName (-k)"); + errorCount++; + goto loser; + } + keyName = PL_strdup(arg); + ate = 1; + break; + case LIST_OBJSIGN_CERTS_OPT: + case LIST_ALL_CERTS_OPT: + if (list_certs != 0) { + PR_fprintf(errorFD, + "warning: only one of -l and -L may be specified.\n"); + warningCount++; + } + list_certs = (type == LIST_OBJSIGN_CERTS_OPT ? 1 : 2); + break; + case METAFILE_OPT: + if (metafile) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], + "metafile (-m)"); + warningCount++; + PR_Free(metafile); + metafile = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "metafile (-m)"); + errorCount++; + goto loser; + } + metafile = PL_strdup(arg); + ate = 1; + break; + case OPTIMIZE_OPT: + optimize = 1; + break; + case ENABLE_OCSP_OPT: + enableOCSP = 1; + break; + case PASSWORD_OPT: + if (pwdata.data) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], + "password (-p)"); + warningCount++; + PR_Free(pwdata.data); + pwdata.data = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "password (-p)"); + errorCount++; + goto loser; + } + pwdata.source = PW_PLAINTEXT; + pwdata.data = PL_strdup(arg); + ate = 1; + break; + case VERIFY_OPT: + if (verify) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], + "verify (-v)"); + warningCount++; + PR_Free(verify); + verify = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "verify (-v)"); + errorCount++; + goto loser; + } + verify = PL_strdup(arg); + ate = 1; + break; + case WHO_OPT: + if (tell_who) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], + "who (-v)"); + warningCount++; + PR_Free(tell_who); + tell_who = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "who (-w)"); + errorCount++; + goto loser; + } + tell_who = PL_strdup(arg); + ate = 1; + break; + case EXCLUDE_OPT: + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "exclude (-x)"); + errorCount++; + goto loser; + } + PL_HashTableAdd(excludeDirs, arg, arg); + exclusionsGiven = PR_TRUE; + ate = 1; + break; + case NO_TIME_OPT: + no_time = 1; + break; + case JAVASCRIPT_OPT: + javascript++; + break; + case ZIPFILE_OPT: + if (zipfile) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], + "jarfile (-Z)"); + warningCount++; + PR_Free(zipfile); + zipfile = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "jarfile (-Z)"); + errorCount++; + goto loser; + } + zipfile = PL_strdup(arg); + ate = 1; + break; + case GENKEY_OPT: + if (genkey) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], + "generate (-G)"); + warningCount++; + PR_Free(genkey); + genkey = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "generate (-G)"); + errorCount++; + goto loser; + } + genkey = PL_strdup(arg); + ate = 1; + break; + case MODULES_OPT: + list_modules++; + break; + case SIGNDIR_OPT: + if (jartree) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], + "signdir"); + warningCount++; + PR_Free(jartree); + jartree = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "signdir"); + errorCount++; + goto loser; + } + jartree = PL_strdup(arg); + ate = 1; + break; + case OUTFILE_OPT: + if (outfile) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], + "outfile"); + warningCount++; + PR_Free(outfile); + outfile = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "outfile"); + errorCount++; + goto loser; + } + outfile = PL_strdup(arg); + ate = 1; + break; + case COMMAND_FILE_OPT: + if (cmdFile) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], + "-f"); + warningCount++; + PR_Free(cmdFile); + cmdFile = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "-f"); + errorCount++; + goto loser; + } + cmdFile = PL_strdup(arg); + ate = 1; + break; + case NORECURSE_OPT: + noRecurse = PR_TRUE; + break; + case LEAVE_ARC_OPT: + leaveArc = PR_TRUE; + break; + case VERBOSITY_OPT: + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], + "--verbosity"); + errorCount++; + goto loser; + } + verbosity = atoi(arg); + ate = 1; + break; + case KEYSIZE_OPT: + if (keySize != -1) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], "-s"); + warningCount++; + } + keySize = atoi(arg); + ate = 1; + if (keySize < 1 || keySize > MAX_RSA_KEY_SIZE) { + PR_fprintf(errorFD, "Invalid key size: %d.\n", keySize); + errorCount++; + goto loser; + } + break; + case TOKEN_OPT: + if (token) { + PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], "-t"); + PR_Free(token); + token = NULL; + } + if (!arg) { + PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], "-t"); + errorCount++; + goto loser; + } + token = PL_strdup(arg); + ate = 1; + break; + case XPI_ARC_OPT: + xpi_arc = 1; + break; + default: + PR_fprintf(errorFD, "warning: unknown option\n"); + warningCount++; + break; + } + + return ate; +loser: + return -1; +} + +/********************************************************************* + * + * m a i n + */ +int +main(int argc, char *argv[]) +{ + PRBool readOnly; + int retval = 0; + + outputFD = PR_STDOUT; + errorFD = PR_STDERR; + + progName = argv[0]; + + if (argc < 2) { + Usage(); + } + + excludeDirs = PL_NewHashTable(10, PL_HashString, PL_CompareStrings, + PL_CompareStrings, NULL, NULL); + extensions = PL_NewHashTable(10, PL_HashString, PL_CompareStrings, + PL_CompareStrings, NULL, NULL); + + if (parse_args(argc, argv)) { + retval = -1; + goto cleanup; + } + + /* Parse the command file if one was given */ + if (cmdFile) { + if (ProcessCommandFile()) { + retval = -1; + goto cleanup; + } + } + + /* Set up output redirection */ + if (outfile) { + if (PR_Access(outfile, PR_ACCESS_EXISTS) == PR_SUCCESS) { + /* delete the file if it is already present */ + PR_fprintf(errorFD, + "warning: %s already exists and will be overwritten.\n", + outfile); + warningCount++; + if (PR_Delete(outfile) != PR_SUCCESS) { + PR_fprintf(errorFD, "ERROR: unable to delete %s.\n", outfile); + errorCount++; + exit(ERRX); + } + } + outputFD = PR_Open(outfile, + PR_WRONLY | + PR_CREATE_FILE | PR_TRUNCATE, + 0777); + if (!outputFD) { + PR_fprintf(errorFD, "ERROR: Unable to create %s.\n", + outfile); + errorCount++; + exit(ERRX); + } + errorFD = outputFD; + } + + /* This seems to be a fairly common user error */ + + if (verify && list_certs > 0) { + PR_fprintf(errorFD, "%s: Can't use -l and -v at the same time\n", + PROGRAM_NAME); + errorCount++; + retval = -1; + goto cleanup; + } + + /* -J assumes -Z now */ + + if (javascript && zipfile) { + PR_fprintf(errorFD, "%s: Can't use -J and -Z at the same time\n", + PROGRAM_NAME); + PR_fprintf(errorFD, "%s: -J option will create the jar files for you\n", + PROGRAM_NAME); + errorCount++; + retval = -1; + goto cleanup; + } + + /* -X needs -Z */ + + if (xpi_arc && !zipfile) { + PR_fprintf(errorFD, "%s: option XPI (-X) requires option jarfile (-Z)\n", + PROGRAM_NAME); + errorCount++; + retval = -1; + goto cleanup; + } + + /* Less common mixing of -L with various options */ + + if (list_certs > 0 && + (tell_who || zipfile || javascript || + scriptdir || extensionsGiven || exclusionsGiven || install_script)) { + PR_fprintf(errorFD, "%s: Can't use -l or -L with that option\n", + PROGRAM_NAME); + errorCount++; + retval = -1; + goto cleanup; + } + + if (!cert_dir) + cert_dir = get_default_cert_dir(); + + VerifyCertDir(cert_dir, keyName); + + if (compression_level < MIN_COMPRESSION_LEVEL || + compression_level > MAX_COMPRESSION_LEVEL) { + PR_fprintf(errorFD, "Compression level must be between %d and %d.\n", + MIN_COMPRESSION_LEVEL, MAX_COMPRESSION_LEVEL); + errorCount++; + retval = -1; + goto cleanup; + } + + if (jartree && !keyName) { + PR_fprintf(errorFD, "You must specify a key with which to sign.\n"); + errorCount++; + retval = -1; + goto cleanup; + } + + readOnly = (genkey == NULL); /* only key generation requires write */ + if (InitCrypto(cert_dir, readOnly)) { + PR_fprintf(errorFD, "ERROR: Cryptographic initialization failed.\n"); + errorCount++; + retval = -1; + goto cleanup; + } + + if (enableOCSP) { + SECStatus rv = CERT_EnableOCSPChecking(CERT_GetDefaultCertDB()); + if (rv != SECSuccess) { + PR_fprintf(errorFD, "ERROR: Attempt to enable OCSP Checking failed.\n"); + errorCount++; + retval = -1; + } + } + + if (verify) { + if (VerifyJar(verify)) { + errorCount++; + retval = -1; + goto cleanup; + } + } else if (list_certs) { + if (ListCerts(keyName, list_certs)) { + errorCount++; + retval = -1; + goto cleanup; + } + } else if (list_modules) { + JarListModules(); + } else if (genkey) { + if (GenerateCert(genkey, keySize, token)) { + errorCount++; + retval = -1; + goto cleanup; + } + } else if (tell_who) { + if (JarWho(tell_who)) { + errorCount++; + retval = -1; + goto cleanup; + } + } else if (javascript && jartree) { + /* make sure directory exists */ + PRDir *dir; + dir = PR_OpenDir(jartree); + if (!dir) { + PR_fprintf(errorFD, "ERROR: unable to open directory %s.\n", + jartree); + errorCount++; + retval = -1; + goto cleanup; + } else { + PR_CloseDir(dir); + } + + /* undo junk from prior runs of signtool*/ + if (RemoveAllArc(jartree)) { + PR_fprintf(errorFD, "Error removing archive directories under %s\n", + jartree); + errorCount++; + retval = -1; + goto cleanup; + } + + /* traverse all the htm|html files in the directory */ + if (InlineJavaScript(jartree, !noRecurse)) { + retval = -1; + goto cleanup; + } + + /* sign any resultant .arc directories created in above step */ + if (SignAllArc(jartree, keyName, javascript, metafile, install_script, + optimize, !noRecurse)) { + retval = -1; + goto cleanup; + } + + if (!leaveArc) { + RemoveAllArc(jartree); + } + + if (errorCount > 0 || warningCount > 0) { + PR_fprintf(outputFD, "%d error%s, %d warning%s.\n", + errorCount, + errorCount == 1 ? "" : "s", warningCount, warningCount == 1 + ? "" + : "s"); + } else { + PR_fprintf(outputFD, "Directory %s signed successfully.\n", + jartree); + } + } else if (jartree) { + SignArchive(jartree, keyName, zipfile, javascript, metafile, + install_script, optimize, !noRecurse); + } else + Usage(); + +cleanup: + if (extensions) { + PL_HashTableDestroy(extensions); + extensions = NULL; + } + if (excludeDirs) { + PL_HashTableDestroy(excludeDirs); + excludeDirs = NULL; + } + if (outputFD != PR_STDOUT) { + PR_Close(outputFD); + } + rm_dash_r(TMP_OUTPUT); + if (retval == 0) { + if (NSS_Shutdown() != SECSuccess) { + exit(1); + } + } + return retval; +} diff --git a/security/nss/cmd/signtool/signtool.gyp b/security/nss/cmd/signtool/signtool.gyp new file mode 100644 index 000000000..53465ac63 --- /dev/null +++ b/security/nss/cmd/signtool/signtool.gyp @@ -0,0 +1,33 @@ +# 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/. +{ + 'includes': [ + '../../coreconf/config.gypi', + '../../cmd/platlibs.gypi' + ], + 'targets': [ + { + 'target_name': 'signtool', + 'type': 'executable', + 'sources': [ + 'certgen.c', + 'javascript.c', + 'list.c', + 'sign.c', + 'signtool.c', + 'util.c', + 'verify.c', + 'zip.c' + ], + 'dependencies': [ + '<(DEPTH)/exports.gyp:nss_exports', + '<(DEPTH)/lib/jar/jar.gyp:jar', + '<(DEPTH)/lib/zlib/zlib.gyp:nss_zlib' + ] + } + ], + 'variables': { + 'module': 'nss' + } +} diff --git a/security/nss/cmd/signtool/signtool.h b/security/nss/cmd/signtool/signtool.h new file mode 100644 index 000000000..bdb3b597c --- /dev/null +++ b/security/nss/cmd/signtool/signtool.h @@ -0,0 +1,113 @@ +/* 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 SIGNTOOL_H +#define SIGNTOOL_H + +#define DJN_TEST + +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "prprf.h" +#include "prio.h" +#include "secutil.h" +#include "ocsp.h" +#include "jar.h" +#include "jarfile.h" +#include "secpkcs7.h" +#include "pk11func.h" +#include "secmod.h" +#include "plhash.h" +#include "nss.h" + +#ifdef _UNIX +#include <unistd.h> +#endif + +/********************************************************************** + * General Defines + */ +#define JAR_BASE_END JAR_BASE + 100 +#define ERRX (-1) /* the exit code used on failure */ +#define FNSIZE 256 /* the maximum length for filenames */ +#define MAX_RSA_KEY_SIZE 4096 +#define DEFAULT_RSA_KEY_SIZE 1024 +#define MANIFEST "manifest.mf" +#define DEFAULT_X509_BASENAME "x509" +#define DEFAULT_COMMON_NAME "Signtool " NSS_VERSION " Testing Certificate" +#define CREATOR "Signtool (signtool " NSS_VERSION ")" +#define BREAKAGE "PLEASE DO NOT EDIT THIS FILE. YOU WILL BREAK IT." +#define MIN_COMPRESSION_LEVEL (-1) +#define MAX_COMPRESSION_LEVEL 9 +#define DEFAULT_COMPRESSION_LEVEL (-1) /* zlib understands this to be default*/ +#define STDIN_BUF_SIZE 160 +#define PROGRAM_NAME "signtool" +#define LONG_PROGRAM_NAME "Signing Tool" +#define DEFAULT_BASE_NAME "zigbert" +#define TMP_OUTPUT "signtool.tmp" +#define XPI_TEXT "Creating XPI Compatible Archive" + +/*************************************************************** + * Main Task Functions + */ +int GenerateCert(char *nickname, int keysize, char *token); +int ListCerts(char *key, int list_certs); +int VerifyJar(char *filename); +int SignArchive(char *tree, char *keyName, char *zip_file, int javascript, + char *meta_file, char *install_script, int _optimize, PRBool recurse); +int SignAllArc(char *jartree, char *keyName, int javascript, char *metafile, + char *install_script, int optimize, PRBool recurse); +int InlineJavaScript(char *dir, PRBool recurse); +int JarWho(char *filename); +void JarListModules(void); + +/************************************************************** + * Utility Functions + */ +CERTCertDBHandle *OpenCertDB(PRBool readOnly); + +int RemoveAllArc(char *tree); +void VerifyCertDir(char *dir, char *keyName); +int InitCrypto(char *cert_dir, PRBool readOnly); +int foreach (char *dirname, char *prefix, + int (*fn)(char *filename, char *dirname, char *basedir, char *base, void *arg), + PRBool recurse, PRBool includeDirs, void *arg); +void print_error(int i); +void give_help(int status); +const char *secErrorString(long code); +void displayVerifyLog(CERTVerifyLog *log); +void Usage(void); +void LongUsage(void); +char *chop(char *); +void out_of_memory(void); +void FatalError(char *msg); +char *get_default_cert_dir(void); +SECItem *password_hardcode(void *arg, void *handle); +char *pk11_password_hardcode(PK11SlotInfo *slot, PRBool retry, void *arg); +int rm_dash_r(char *path); +char *pr_fgets(char *buf, int size, PRFileDesc *file); + +/***************************************************************** + * Global Variables (*gag*) + */ +extern char *password; /* the password passed in on the command line */ +extern PLHashTable *excludeDirs; /* directory entry to skip while recursing */ +extern int no_time; +extern int xpi_arc; +extern char *base; /* basename of ".rsa" and ".sf" files */ +extern long *mozilla_event_queue; +extern char *progName; /* argv[0] */ +extern PLHashTable *extensions; /* only sign files with this extension */ +extern PRBool extensionsGiven; +extern char *scriptdir; +extern int compression_level; +extern PRFileDesc *outputFD, *errorFD; +extern int verbosity; +extern int errorCount; +extern int warningCount; +extern secuPWData pwdata; + +#endif /* SIGNTOOL_H */ diff --git a/security/nss/cmd/signtool/util.c b/security/nss/cmd/signtool/util.c new file mode 100644 index 000000000..49b7f3b05 --- /dev/null +++ b/security/nss/cmd/signtool/util.c @@ -0,0 +1,1086 @@ +/* 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 "signtool.h" +#include "prio.h" +#include "prmem.h" +#include "prenv.h" +#include "nss.h" + +static int is_dir(char *filename); + +/*********************************************************** + * Nasty hackish function definitions + */ + +long *mozilla_event_queue = 0; + +#ifndef XP_WIN +char * +XP_GetString(int i) +{ + /* nasty hackish cast to avoid changing the signature of + * JAR_init_callbacks() */ + return (char *)SECU_Strerror(i); +} +#endif + +void +FE_SetPasswordEnabled() +{ +} + +void /*MWContext*/ * +FE_GetInitContext(void) +{ + return 0; +} + +void /*MWContext*/ * +XP_FindSomeContext() +{ + /* No windows context in command tools */ + return NULL; +} + +void +ET_moz_CallFunction() +{ +} + +/* + * R e m o v e A l l A r c + * + * Remove .arc directories that are lingering + * from a previous run of signtool. + * + */ +int +RemoveAllArc(char *tree) +{ + PRDir *dir; + PRDirEntry *entry; + char *archive = NULL; + int retval = 0; + + dir = PR_OpenDir(tree); + if (!dir) + return -1; + + for (entry = PR_ReadDir(dir, 0); entry; entry = PR_ReadDir(dir, + 0)) { + + if (entry->name[0] == '.') { + continue; + } + + if (archive) + PR_Free(archive); + archive = PR_smprintf("%s/%s", tree, entry->name); + + if (PL_strcaserstr(entry->name, ".arc") == + (entry->name + strlen(entry->name) - 4)) { + + if (verbosity >= 0) { + PR_fprintf(outputFD, "removing: %s\n", archive); + } + + if (rm_dash_r(archive)) { + PR_fprintf(errorFD, "Error removing %s\n", archive); + errorCount++; + retval = -1; + goto finish; + } + } else if (is_dir(archive)) { + if (RemoveAllArc(archive)) { + retval = -1; + goto finish; + } + } + } + +finish: + PR_CloseDir(dir); + if (archive) + PR_Free(archive); + + return retval; +} + +/* + * r m _ d a s h _ r + * + * Remove a file, or a directory recursively. + * + */ +int +rm_dash_r(char *path) +{ + PRDir *dir; + PRDirEntry *entry; + PRFileInfo fileinfo; + char filename[FNSIZE]; + + if (PR_GetFileInfo(path, &fileinfo) != PR_SUCCESS) { + /*fprintf(stderr, "Error: Unable to access %s\n", filename);*/ + return -1; + } + if (fileinfo.type == PR_FILE_DIRECTORY) { + + dir = PR_OpenDir(path); + if (!dir) { + PR_fprintf(errorFD, "Error: Unable to open directory %s.\n", path); + errorCount++; + return -1; + } + + /* Recursively delete all entries in the directory */ + while ((entry = PR_ReadDir(dir, PR_SKIP_BOTH)) != NULL) { + sprintf(filename, "%s/%s", path, entry->name); + if (rm_dash_r(filename)) + return -1; + } + + if (PR_CloseDir(dir) != PR_SUCCESS) { + PR_fprintf(errorFD, "Error: Could not close %s.\n", path); + errorCount++; + return -1; + } + + /* Delete the directory itself */ + if (PR_RmDir(path) != PR_SUCCESS) { + PR_fprintf(errorFD, "Error: Unable to delete %s\n", path); + errorCount++; + return -1; + } + } else { + if (PR_Delete(path) != PR_SUCCESS) { + PR_fprintf(errorFD, "Error: Unable to delete %s\n", path); + errorCount++; + return -1; + } + } + return 0; +} + +/* + * u s a g e + * + * Print some useful help information + * + */ + +void +Usage(void) +{ +#define FPS PR_fprintf(outputFD, + FPS "%s %s -a signing tool for jar files\n", LONG_PROGRAM_NAME,NSS_VERSION); + FPS "\n\nType %s -H for more detailed descriptions\n", PROGRAM_NAME); + FPS "\nUsage: %s -k keyName [-b basename] [-c Compression Level]\n" + "\t\t [-d cert-dir] [-i installer script] [-m metafile] [-x name]\n" + "\t\t [-e extension] [-o] [-z] [-X] [--outfile] [--verbose value]\n" + "\t\t [--norecurse] [--leavearc] [-j directory] [-Z jarfile] [-O]\n" + "\t\t [-p password] directory-tree\n", PROGRAM_NAME); + FPS "\t%s -J -k keyName [-b basename] [-c Compression Level]\n" + "\t\t [-d cert-dir][-i installer script] [-m metafile] [-x name]\n" + "\t\t [-e extension] [-o] [-z] [-X] [--outfile] [--verbose value]\n" + "\t\t [--norecurse] [--leavearc] [-j directory] [-p password] [-O] \n" + "\t\t directory-tree\n", PROGRAM_NAME); + FPS "\t%s -h \n", PROGRAM_NAME); + FPS "\t%s -H \n", PROGRAM_NAME); + FPS "\t%s -l [-k keyName] [-d cert-dir] [--outfile] [-O] \n", PROGRAM_NAME); + FPS "\t%s -L [-k keyName] [-d cert-dir] [--outfile] [-O] \n", PROGRAM_NAME); + FPS "\t%s -M [--outfile] [-O] \n", PROGRAM_NAME); + FPS "\t%s -v [-d cert-dir] [--outfile] [-O] archive\n", PROGRAM_NAME); + FPS "\t%s -w [--outfile] [-O] archive\n" , PROGRAM_NAME); + FPS "\t%s -G nickname [--keysize|-s size] [-t |--token tokenname]\n" + "\t\t [--outfile] [-O] \n", PROGRAM_NAME); + FPS "\t%s -f filename\n" , PROGRAM_NAME); + exit(ERRX); +} + +void +LongUsage(void) +{ + FPS "%s %s -a signing tool for jar files\n", LONG_PROGRAM_NAME,NSS_VERSION); + FPS "\n%-20s Signs the directory-tree\n", + "signtool directory-tree"); + FPS "%-30s Nickname (key) of the certificate to sign with\n", + " -k keyname"); + FPS "%-30s Base filename for the .rsa and.sf files in the\n", + " -b basename"); + FPS "%-30s META-INF directory\n"," "); + FPS "%-30s Set the compression level. 0-9, 0=none\n", + " -c CompressionLevel"); + FPS "%-30s Certificate database directory containing cert*db\n", + " -d certificate directory"); + FPS "%-30s and key*db\n"," "); + FPS "%-30s Name of the installer script for SmartUpdate\n", + " -i installer script"); + FPS "%-30s Name of a metadata control file\n", + " -m metafile"); + FPS "%-30s For optimizing the archive for size.\n", + " -o"); + FPS "%-30s Omit Optional Headers\n"," "); + FPS "%-30s Excludes the specified directory or file from\n", + " -x directory or file name"); + FPS "%-30s signing\n"," "); + FPS "%-30s To not store the signing time in digital\n", + " -z directory or file name"); + FPS "%-30s signature\n"," "); + FPS "%-30s Create XPI Compatible Archive. It requires -Z\n", + " -X directory or file name"); + FPS "%-30s option\n"," "); + FPS "%-30s Sign only files with the given extension\n", + " -e"); + FPS "%-30s Causes the specified directory to be signed and\n", + " -j"); + FPS "%-30s tags its entries as inline JavaScript\n"," "); + FPS "%-30s Creates a JAR file with the specified name.\n", + " -Z"); + FPS "%-30s -Z option cannot be used with -J option\n"," "); + FPS "%-30s Specifies a password for the private-key database\n", + " -p"); + FPS "%-30s (insecure)\n"," "); + FPS "%-30s File to receive redirected output\n", + " --outfile filename"); + FPS "%-30s Sets the quantity of information generated in\n", + " --verbosity value"); + FPS "%-30s operation\n"," "); + FPS "%-30s Blocks recursion into subdirectories\n", + " --norecurse"); + FPS "%-30s Retains the temporary .arc (archive) directories\n", + " --leavearc"); + FPS "%-30s -J option creates\n"," "); + + FPS "\n%-20s Signs a directory of HTML files containing JavaScript and\n", + "-J" ); + FPS "%-20s creates as many archive files as are in the HTML tags.\n"," "); + + FPS "%-20s The options are same as without any command option given\n"," "); + FPS "%-20s above. -Z and -J options are not allowed together\n"," "); + + FPS "\n%-20s Generates a new private-public key pair and corresponding\n", + "-G nickname"); + FPS "%-20s object-signing certificates with the given nickname\n"," "); + FPS "%-30s Specifies the size of the key for generated \n", + " --keysize|-s keysize"); + FPS "%-30s certificate\n"," "); + FPS "%-30s Specifies which available token should generate\n", + " --token|-t token name "); + FPS "%-30s the key and receive the certificate\n"," "); + FPS "%-30s Specifies a file to receive redirected output\n", + " --outfile filename "); + + FPS "\n%-20s Display signtool help\n", + "-h "); + + FPS "\n%-20s Display signtool help(Detailed)\n", + "-H "); + + FPS "\n%-20s Lists signing certificates, including issuing CAs\n", + "-l "); + FPS "%-30s Certificate database directory containing cert*db\n", + " -d certificate directory"); + FPS "%-30s and key*db\n"," "); + + FPS "%-30s Specifies a file to receive redirected output\n", + " --outfile filename "); + FPS "%-30s Specifies the nickname (key) of the certificate\n", + " -k keyname"); + + FPS "\n%-20s Lists the certificates in your database\n", + "-L "); + FPS "%-30s Certificate database directory containing cert*db\n", + " -d certificate directory"); + FPS "%-30s and key*db\n"," "); + + FPS "%-30s Specifies a file to receive redirected output\n", + " --outfile filename "); + FPS "%-30s Specifies the nickname (key) of the certificate\n", + " -k keyname"); + + FPS "\n%-20s Lists the PKCS #11 modules available to signtool\n", + "-M "); + + FPS "\n%-20s Displays the contents of an archive and verifies\n", + "-v archive"); + FPS "%-20s cryptographic integrity\n"," "); + FPS "%-30s Certificate database directory containing cert*db\n", + " -d certificate directory"); + FPS "%-30s and key*db\n"," "); + FPS "%-30s Specifies a file to receive redirected output\n", + " --outfile filename "); + + FPS "\n%-20s Displays the names of signers in the archive\n", + "-w archive"); + FPS "%-30s Specifies a file to receive redirected output\n", + " --outfile filename "); + + FPS "\n%-30s Common option to all the above.\n", + " -O"); + FPS "%-30s Enable OCSP checking\n"," "); + + FPS "\n%-20s Specifies a text file containing options and arguments in\n", + "-f command-file"); + FPS "%-20s keyword=value format. Commands are taken from this file\n"," "); + + FPS "\n\n\n"); + FPS "Example:\n"); + FPS "%-10s -d \"certificate directory\" -k \"certnickname\" \\", + PROGRAM_NAME); + FPS "\n%-10s -p \"password\" -X -Z \"file.xpi\" directory-tree\n"," " ); + FPS "Common syntax to create an XPInstall compatible" + " signed archive\n\n"," "); + FPS "\nCommand File Keywords and Example:\n"); + FPS "\nKeyword\t\tValue\n"); + FPS "basename\tSame as -b option\n"); + FPS "compression\tSame as -c option\n"); + FPS "certdir\t\tSame as -d option\n"); + FPS "extension\tSame as -e option\n"); + FPS "generate\tSame as -G option\n"); + FPS "installscript\tSame as -i option\n"); + FPS "javascriptdir\tSame as -j option\n"); + FPS "htmldir\t\tSame as -J option\n"); + FPS "certname\tNickname of certificate, as with -k option\n"); + FPS "signdir\t\tThe directory to be signed, as with -k option\n"); + FPS "list\t\tSame as -l option. Value is ignored,\n" + " \t\tbut = sign must be present\n"); + FPS "listall\t\tSame as -L option. Value is ignored\n" + " \t\tbut = sign must be present\n"); + FPS "metafile\tSame as -m option\n"); + FPS "modules\t\tSame as -M option. Value is ignored,\n" + " \t\tbut = sign must be present\n"); + FPS "optimize\tSame as -o option. Value is ignored,\n" + " \tbut = sign must be present\n"); + FPS "ocsp\t\tSame as -O option\n"); + FPS "password\tSame as -p option\n"); + FPS "verify\t\tSame as -v option\n"); + FPS "who\t\tSame as -w option\n"); + FPS "exclude\t\tSame as -x option\n"); + FPS "notime\t\tSame as -z option. Value is ignored,\n" + " \t\tbut = sign must be present\n"); + FPS "jarfile\t\tSame as -Z option\n"); + FPS "outfile\t\tSame as --outfile option. The argument\n"); + FPS " \t\tis the name of a file to which output\n"); + FPS " \t\tof a file and error messages will be \n"); + FPS " \t\tredirected\n"); + FPS "leavearc\tSame as --leavearc option\n"); + FPS "verbosity\tSame as --verbosity option\n"); + FPS "keysize\t\tSame as -s option\n"); + FPS "token\t\tSame as -t option\n"); + FPS "xpi\t\tSame as -X option\n"); + FPS "\n\n"); + FPS "Here's an example of the use of the command file. The command\n\n"); + FPS " signtool -d c:\\netscape\\users\\james -k mycert -Z myjar.jar \\\n" + " signdir > output.txt\n\n"); + FPS "becomes\n\n"); + FPS " signtool -f somefile\n\n"); + FPS "where somefile contains the following lines:\n\n"); + FPS " certdir=c:\\netscape\\users\\james\n"," "); + FPS " certname=mycert\n"," "); + FPS " jarfile=myjar.jar\n"," "); + FPS " signdir=signdir\n"," "); + FPS " outfile=output.txt\n"," "); + exit(ERRX); +#undef FPS +} + +/* + * p r i n t _ e r r o r + * + * For the undocumented -E function. If an older version + * of communicator gives you a numeric error, we can see what + * really happened without doing hex math. + * + */ + +void +print_error(int err) +{ + PR_fprintf(errorFD, "Error %d: %s\n", err, JAR_get_error(err)); + errorCount++; + give_help(err); +} + +/* + * o u t _ o f _ m e m o r y + * + * Out of memory, exit Signtool. + * + */ +void +out_of_memory(void) +{ + PR_fprintf(errorFD, "%s: out of memory\n", PROGRAM_NAME); + errorCount++; + exit(ERRX); +} + +/* + * V e r i f y C e r t D i r + * + * Validate that the specified directory + * contains a certificate database + * + */ +void +VerifyCertDir(char *dir, char *keyName) +{ + char fn[FNSIZE]; + + /* don't try verifying if we don't have a local directory */ + if (strncmp(dir, "multiaccess:", sizeof("multiaccess:") - 1) == 0) { + return; + } + /* this function is truly evil. Tools and applications should not have + * any knowledge of actual cert databases! */ + return; + + /* This code is really broken because it makes underlying assumptions about + * how the NSS profile directory is laid out, but these names can change + * from release to release. */ + sprintf(fn, "%s/cert8.db", dir); + + if (PR_Access(fn, PR_ACCESS_EXISTS)) { + PR_fprintf(errorFD, "%s: No certificate database in \"%s\"\n", + PROGRAM_NAME, dir); + PR_fprintf(errorFD, "%s: Check the -d arguments that you gave\n", + PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + if (verbosity >= 0) { + PR_fprintf(outputFD, "using certificate directory: %s\n", dir); + } + + if (keyName == NULL) + return; + + /* if the user gave the -k key argument, verify that + a key database already exists */ + + sprintf(fn, "%s/key3.db", dir); + + if (PR_Access(fn, PR_ACCESS_EXISTS)) { + PR_fprintf(errorFD, "%s: No private key database in \"%s\"\n", + PROGRAM_NAME, + dir); + PR_fprintf(errorFD, "%s: Check the -d arguments that you gave\n", + PROGRAM_NAME); + errorCount++; + exit(ERRX); + } +} + +/* + * f o r e a c h + * + * A recursive function to loop through all names in + * the specified directory, as well as all subdirectories. + * + * FIX: Need to see if all platforms allow multiple + * opendir's to be called. + * + */ + +int foreach (char *dirname, char *prefix, + int (*fn)(char *relpath, char *basedir, char *reldir, char *filename, + void *arg), + PRBool recurse, PRBool includeDirs, void *arg) +{ + char newdir[FNSIZE]; + int retval = 0; + + PRDir *dir; + PRDirEntry *entry; + + strcpy(newdir, dirname); + if (*prefix) { + strcat(newdir, "/"); + strcat(newdir, prefix); + } + + dir = PR_OpenDir(newdir); + if (!dir) + return -1; + + for (entry = PR_ReadDir(dir, 0); entry; entry = PR_ReadDir(dir, 0)) { + if (strcmp(entry->name, ".") == 0 || + strcmp(entry->name, "..") == 0) { + /* no infinite recursion, please */ + continue; + } + + /* can't sign self */ + if (!strcmp(entry->name, "META-INF")) + continue; + + /* -x option */ + if (PL_HashTableLookup(excludeDirs, entry->name)) + continue; + + strcpy(newdir, dirname); + if (*dirname) + strcat(newdir, "/"); + + if (*prefix) { + strcat(newdir, prefix); + strcat(newdir, "/"); + } + strcat(newdir, entry->name); + + if (!is_dir(newdir) || includeDirs) { + char newpath[FNSIZE]; + + strcpy(newpath, prefix); + if (*newpath) + strcat(newpath, "/"); + strcat(newpath, entry->name); + + if ((*fn)(newpath, dirname, prefix, (char *)entry->name, + arg)) { + retval = -1; + break; + } + } + + if (is_dir(newdir)) { + if (recurse) { + char newprefix[FNSIZE]; + + strcpy(newprefix, prefix); + if (*newprefix) { + strcat(newprefix, "/"); + } + strcat(newprefix, entry->name); + + if (foreach (dirname, newprefix, fn, recurse, + includeDirs, arg)) { + retval = -1; + break; + } + } + } + } + + PR_CloseDir(dir); + + return retval; +} + +/* + * i s _ d i r + * + * Return 1 if file is a directory. + * Wonder if this runs on a mac, trust not. + * + */ +static int +is_dir(char *filename) +{ + PRFileInfo finfo; + + if (PR_GetFileInfo(filename, &finfo) != PR_SUCCESS) { + printf("Unable to get information about %s\n", filename); + return 0; + } + + return (finfo.type == PR_FILE_DIRECTORY); +} + +/*************************************************************** + * + * s e c E r r o r S t r i n g + * + * Returns an error string corresponding to the given error code. + * Doesn't cover all errors; returns a default for many. + * Returned string is only valid until the next call of this function. + */ +const char * +secErrorString(long code) +{ + static char errstring[80]; /* dynamically constructed error string */ + char *c; /* the returned string */ + + switch (code) { + case SEC_ERROR_IO: + c = "io error"; + break; + case SEC_ERROR_LIBRARY_FAILURE: + c = "security library failure"; + break; + case SEC_ERROR_BAD_DATA: + c = "bad data"; + break; + case SEC_ERROR_OUTPUT_LEN: + c = "output length"; + break; + case SEC_ERROR_INPUT_LEN: + c = "input length"; + break; + case SEC_ERROR_INVALID_ARGS: + c = "invalid args"; + break; + case SEC_ERROR_EXPIRED_CERTIFICATE: + c = "expired certificate"; + break; + case SEC_ERROR_REVOKED_CERTIFICATE: + c = "revoked certificate"; + break; + case SEC_ERROR_INADEQUATE_KEY_USAGE: + c = "inadequate key usage"; + break; + case SEC_ERROR_INADEQUATE_CERT_TYPE: + c = "inadequate certificate type"; + break; + case SEC_ERROR_UNTRUSTED_CERT: + c = "untrusted cert"; + break; + case SEC_ERROR_NO_KRL: + c = "no key revocation list"; + break; + case SEC_ERROR_KRL_BAD_SIGNATURE: + c = "key revocation list: bad signature"; + break; + case SEC_ERROR_KRL_EXPIRED: + c = "key revocation list expired"; + break; + case SEC_ERROR_REVOKED_KEY: + c = "revoked key"; + break; + case SEC_ERROR_CRL_BAD_SIGNATURE: + c = "certificate revocation list: bad signature"; + break; + case SEC_ERROR_CRL_EXPIRED: + c = "certificate revocation list expired"; + break; + case SEC_ERROR_CRL_NOT_YET_VALID: + c = "certificate revocation list not yet valid"; + break; + case SEC_ERROR_UNKNOWN_ISSUER: + c = "unknown issuer"; + break; + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + c = "expired issuer certificate"; + break; + case SEC_ERROR_BAD_SIGNATURE: + c = "bad signature"; + break; + case SEC_ERROR_BAD_KEY: + c = "bad key"; + break; + case SEC_ERROR_NOT_FORTEZZA_ISSUER: + c = "not fortezza issuer"; + break; + case SEC_ERROR_CA_CERT_INVALID: + c = "Certificate Authority certificate invalid"; + break; + case SEC_ERROR_EXTENSION_NOT_FOUND: + c = "extension not found"; + break; + case SEC_ERROR_CERT_NOT_IN_NAME_SPACE: + c = "certificate not in name space"; + break; + case SEC_ERROR_UNTRUSTED_ISSUER: + c = "untrusted issuer"; + break; + default: + sprintf(errstring, "security error %ld", code); + c = errstring; + break; + } + + return c; +} + +/*************************************************************** + * + * d i s p l a y V e r i f y L o g + * + * Prints the log of a cert verification. + */ +void +displayVerifyLog(CERTVerifyLog *log) +{ + CERTVerifyLogNode *node; + CERTCertificate *cert; + char *name; + + if (!log || (log->count <= 0)) { + return; + } + + for (node = log->head; node != NULL; node = node->next) { + + if (!(cert = node->cert)) { + continue; + } + + /* Get a name for this cert */ + if (cert->nickname != NULL) { + name = cert->nickname; + } else if (cert->emailAddr && cert->emailAddr[0]) { + name = cert->emailAddr; + } else { + name = cert->subjectName; + } + + printf("%s%s:\n", name, + (node->depth > 0) ? " [Certificate Authority]" : ""); + + printf("\t%s\n", secErrorString(node->error)); + } +} + +/* + * J a r L i s t M o d u l e s + * + * Print a list of the PKCS11 modules that are + * available. This is useful for smartcard people to + * make sure they have the drivers loaded. + * + */ +void +JarListModules(void) +{ + int i; + int count = 0; + + SECMODModuleList *modules = NULL; + static SECMODListLock *moduleLock = NULL; + + SECMODModuleList *mlp; + + if ((moduleLock = SECMOD_GetDefaultModuleListLock()) == NULL) { + /* this is the wrong text */ + PR_fprintf(errorFD, "%s: unable to acquire lock on module list\n", + PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + SECMOD_GetReadLock(moduleLock); + + modules = SECMOD_GetDefaultModuleList(); + + if (modules == NULL) { + SECMOD_ReleaseReadLock(moduleLock); + PR_fprintf(errorFD, "%s: Can't get module list\n", PROGRAM_NAME); + errorCount++; + exit(ERRX); + } + + PR_fprintf(outputFD, "\nListing of PKCS11 modules\n"); + PR_fprintf(outputFD, "-----------------------------------------------\n"); + + for (mlp = modules; mlp != NULL; mlp = mlp->next) { + count++; + PR_fprintf(outputFD, "%3d. %s\n", count, mlp->module->commonName); + + if (mlp->module->internal) + PR_fprintf(outputFD, " (this module is internally loaded)\n"); + else + PR_fprintf(outputFD, " (this is an external module)\n"); + + if (mlp->module->dllName) + PR_fprintf(outputFD, " DLL name: %s\n", + mlp->module->dllName); + + if (mlp->module->slotCount == 0) + PR_fprintf(outputFD, " slots: There are no slots attached to this module\n"); + else + PR_fprintf(outputFD, " slots: %d slots attached\n", + mlp->module->slotCount); + + if (mlp->module->loaded == 0) + PR_fprintf(outputFD, " status: Not loaded\n"); + else + PR_fprintf(outputFD, " status: loaded\n"); + + for (i = 0; i < mlp->module->slotCount; i++) { + PK11SlotInfo *slot = mlp->module->slots[i]; + + PR_fprintf(outputFD, "\n"); + PR_fprintf(outputFD, " slot: %s\n", PK11_GetSlotName(slot)); + PR_fprintf(outputFD, " token: %s\n", PK11_GetTokenName(slot)); + } + } + + PR_fprintf(outputFD, "-----------------------------------------------\n"); + + if (count == 0) + PR_fprintf(outputFD, + "Warning: no modules were found (should have at least one)\n"); + + SECMOD_ReleaseReadLock(moduleLock); +} + +/********************************************************************** + * c h o p + * + * Eliminates leading and trailing whitespace. Returns a pointer to the + * beginning of non-whitespace, or an empty string if it's all whitespace. + */ +char * +chop(char *str) +{ + char *start, *end; + + if (str) { + start = str; + + /* Nip leading whitespace */ + while (isspace(*start)) { + start++; + } + + /* Nip trailing whitespace */ + if (*start) { + end = start + strlen(start) - 1; + while (isspace(*end) && end > start) { + end--; + } + *(end + 1) = '\0'; + } + + return start; + } else { + return NULL; + } +} + +/*********************************************************************** + * + * F a t a l E r r o r + * + * Outputs an error message and bails out of the program. + */ +void +FatalError(char *msg) +{ + if (!msg) + msg = ""; + + PR_fprintf(errorFD, "FATAL ERROR: %s\n", msg); + errorCount++; + exit(ERRX); +} + +/************************************************************************* + * + * I n i t C r y p t o + */ +int +InitCrypto(char *cert_dir, PRBool readOnly) +{ + SECStatus rv; + static int prior = 0; + PK11SlotInfo *slotinfo; + + if (prior == 0) { + /* some functions such as OpenKeyDB expect this path to be + * implicitly set prior to calling */ + if (readOnly) { + rv = NSS_Init(cert_dir); + } else { + rv = NSS_InitReadWrite(cert_dir); + } + if (rv != SECSuccess) { + SECU_PrintPRandOSError(PROGRAM_NAME); + exit(-1); + } + + SECU_ConfigDirectory(cert_dir); + + /* Been there done that */ + prior++; + + PK11_SetPasswordFunc(SECU_GetModulePassword); + + /* Must login to FIPS before you do anything else */ + if (PK11_IsFIPS()) { + slotinfo = PK11_GetInternalSlot(); + if (!slotinfo) { + fprintf(stderr, "%s: Unable to get PKCS #11 Internal Slot." + "\n", + PROGRAM_NAME); + return -1; + } + if (PK11_Authenticate(slotinfo, PR_FALSE /*loadCerts*/, + &pwdata) != SECSuccess) { + fprintf(stderr, "%s: Unable to authenticate to %s.\n", + PROGRAM_NAME, PK11_GetSlotName(slotinfo)); + PK11_FreeSlot(slotinfo); + return -1; + } + PK11_FreeSlot(slotinfo); + } + + /* Make sure there is a password set on the internal key slot */ + slotinfo = PK11_GetInternalKeySlot(); + if (!slotinfo) { + fprintf(stderr, "%s: Unable to get PKCS #11 Internal Key Slot." + "\n", + PROGRAM_NAME); + return -1; + } + if (PK11_NeedUserInit(slotinfo)) { + PR_fprintf(errorFD, + "\nWARNING: No password set on internal key database. Most operations will fail." + "\nYou must create a password.\n"); + warningCount++; + } + + /* Make sure we can authenticate to the key slot in FIPS mode */ + if (PK11_IsFIPS()) { + if (PK11_Authenticate(slotinfo, PR_FALSE /*loadCerts*/, + &pwdata) != SECSuccess) { + fprintf(stderr, "%s: Unable to authenticate to %s.\n", + PROGRAM_NAME, PK11_GetSlotName(slotinfo)); + PK11_FreeSlot(slotinfo); + return -1; + } + } + PK11_FreeSlot(slotinfo); + } + + return 0; +} + +/* Windows foolishness is now in the secutil lib */ + +/***************************************************************** + * g e t _ d e f a u l t _ c e r t _ d i r + * + * Attempt to locate a certificate directory. + * Failing that, complain that the user needs to + * use the -d(irectory) parameter. + * + */ +char * +get_default_cert_dir(void) +{ + char *home; + + char *cd = NULL; + static char db[FNSIZE]; + +#ifdef XP_UNIX + home = PR_GetEnvSecure("HOME"); + + if (home && *home) { + sprintf(db, "%s/.netscape", home); + cd = db; + } +#endif + +#ifdef XP_PC + FILE *fp; + + /* first check the environment override */ + + home = PR_GetEnvSecure("JAR_HOME"); + + if (home && *home) { + sprintf(db, "%s/cert7.db", home); + + if ((fp = fopen(db, "r")) != NULL) { + fclose(fp); + cd = home; + } + } + + /* try the old navigator directory */ + + if (cd == NULL) { + home = "c:/Program Files/Netscape/Navigator"; + + sprintf(db, "%s/cert7.db", home); + + if ((fp = fopen(db, "r")) != NULL) { + fclose(fp); + cd = home; + } + } + + /* Try the current directory, I wonder if this + is really a good idea. Remember, Windows only.. */ + + if (cd == NULL) { + home = "."; + + sprintf(db, "%s/cert7.db", home); + + if ((fp = fopen(db, "r")) != NULL) { + fclose(fp); + cd = home; + } + } + +#endif + + if (!cd) { + PR_fprintf(errorFD, + "You must specify the location of your certificate directory\n"); + PR_fprintf(errorFD, + "with the -d option. Example: -d ~/.netscape in many cases with Unix.\n"); + errorCount++; + exit(ERRX); + } + + return cd; +} + +/************************************************************************ + * g i v e _ h e l p + */ +void +give_help(int status) +{ + if (status == SEC_ERROR_UNKNOWN_ISSUER) { + PR_fprintf(errorFD, + "The Certificate Authority (CA) for this certificate\n"); + PR_fprintf(errorFD, + "does not appear to be in your database. You should contact\n"); + PR_fprintf(errorFD, + "the organization which issued this certificate to obtain\n"); + PR_fprintf(errorFD, "a copy of its CA Certificate.\n"); + } +} + +/************************************************************************** + * + * p r _ f g e t s + * + * fgets implemented with NSPR. + */ +char * +pr_fgets(char *buf, int size, PRFileDesc *file) +{ + int i; + int status; + char c; + + i = 0; + while (i < size - 1) { + status = PR_Read(file, &c, 1); + if (status == -1) { + return NULL; + } else if (status == 0) { + if (i == 0) { + return NULL; + } + break; + } + buf[i++] = c; + if (c == '\n') { + break; + } + } + buf[i] = '\0'; + + return buf; +} diff --git a/security/nss/cmd/signtool/verify.c b/security/nss/cmd/signtool/verify.c new file mode 100644 index 000000000..41656a10f --- /dev/null +++ b/security/nss/cmd/signtool/verify.c @@ -0,0 +1,337 @@ +/* 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 "signtool.h" + +static int jar_cb(int status, JAR *jar, const char *metafile, + char *pathname, char *errortext); +static int verify_global(JAR *jar); + +/************************************************************************* + * + * V e r i f y J a r + */ +int +VerifyJar(char *filename) +{ + FILE *fp; + + int ret; + int status; + int failed = 0; + char *err; + + JAR *jar; + JAR_Context *ctx; + + JAR_Item *it; + + jar = JAR_new(); + + if ((fp = fopen(filename, "r")) == NULL) { + perror(filename); + exit(ERRX); + } else + fclose(fp); + + JAR_set_callback(JAR_CB_SIGNAL, jar, jar_cb); + + status = JAR_pass_archive(jar, jarArchGuess, filename, "some-url"); + + if (status < 0 || jar->valid < 0) { + failed = 1; + PR_fprintf(outputFD, + "\nNOTE -- \"%s\" archive DID NOT PASS crypto verification.\n", + filename); + if (status < 0) { + const char *errtext; + + if (status >= JAR_BASE && status <= JAR_BASE_END) { + errtext = JAR_get_error(status); + } else { + errtext = SECU_Strerror(PORT_GetError()); + } + + PR_fprintf(outputFD, " (reported reason: %s)\n\n", + errtext); + + /* corrupt files should not have their contents listed */ + + if (status == JAR_ERR_CORRUPT) + return -1; + } + PR_fprintf(outputFD, + "entries shown below will have their digests checked only.\n"); + jar->valid = 0; + } else + PR_fprintf(outputFD, + "archive \"%s\" has passed crypto verification.\n", filename); + + if (verify_global(jar)) + failed = 1; + + PR_fprintf(outputFD, "\n"); + PR_fprintf(outputFD, "%16s %s\n", "status", "path"); + PR_fprintf(outputFD, "%16s %s\n", "------------", "-------------------"); + + ctx = JAR_find(jar, NULL, jarTypeMF); + + while (JAR_find_next(ctx, &it) >= 0) { + if (it && it->pathname) { + rm_dash_r(TMP_OUTPUT); + ret = JAR_verified_extract(jar, it->pathname, TMP_OUTPUT); + /* if (ret < 0) printf ("error %d on %s\n", ret, it->pathname); */ + if (ret < 0) + failed = 1; + + if (ret == JAR_ERR_PNF) + err = "NOT PRESENT"; + else if (ret == JAR_ERR_HASH) + err = "HASH FAILED"; + else + err = "NOT VERIFIED"; + + PR_fprintf(outputFD, "%16s %s\n", + ret >= 0 ? "verified" : err, it->pathname); + + if (ret != 0 && ret != JAR_ERR_PNF && ret != JAR_ERR_HASH) + PR_fprintf(outputFD, " (reason: %s)\n", + JAR_get_error(ret)); + } + } + + JAR_find_end(ctx); + + if (status < 0 || jar->valid < 0) { + failed = 1; + PR_fprintf(outputFD, + "\nNOTE -- \"%s\" archive DID NOT PASS crypto verification.\n", + filename); + give_help(status); + } + + JAR_destroy(jar); + + if (failed) + return -1; + return 0; +} + +/*************************************************************************** + * + * v e r i f y _ g l o b a l + */ +static int +verify_global(JAR *jar) +{ + FILE *fp; + JAR_Context *ctx; + JAR_Item *it; + JAR_Digest *globaldig; + char *ext; + unsigned char *md5_digest, *sha1_digest; + unsigned int sha1_length, md5_length; + int retval = 0; + char buf[BUFSIZ]; + + ctx = JAR_find(jar, "*", jarTypePhy); + + while (JAR_find_next(ctx, &it) >= 0) { + if (!PORT_Strncmp(it->pathname, "META-INF", 8)) { + for (ext = it->pathname; *ext; ext++) + ; + while (ext > it->pathname && *ext != '.') + ext--; + + if (verbosity >= 0) { + if (!PORT_Strcasecmp(ext, ".rsa")) { + PR_fprintf(outputFD, "found a RSA signature file: %s\n", + it->pathname); + } + + if (!PORT_Strcasecmp(ext, ".dsa")) { + PR_fprintf(outputFD, "found a DSA signature file: %s\n", + it->pathname); + } + + if (!PORT_Strcasecmp(ext, ".mf")) { + PR_fprintf(outputFD, + "found a MF master manifest file: %s\n", + it->pathname); + } + } + + if (!PORT_Strcasecmp(ext, ".sf")) { + if (verbosity >= 0) { + PR_fprintf(outputFD, + "found a SF signature manifest file: %s\n", + it->pathname); + } + + rm_dash_r(TMP_OUTPUT); + if (JAR_extract(jar, it->pathname, TMP_OUTPUT) < 0) { + PR_fprintf(errorFD, "%s: error extracting %s\n", + PROGRAM_NAME, it->pathname); + errorCount++; + retval = -1; + continue; + } + + md5_digest = NULL; + sha1_digest = NULL; + + if ((fp = fopen(TMP_OUTPUT, "rb")) != NULL) { + while (fgets(buf, BUFSIZ, fp)) { + char *s; + + if (*buf == 0 || *buf == '\n' || *buf == '\r') + break; + + for (s = buf; *s && *s != '\n' && *s != '\r'; s++) + ; + *s = 0; + + if (!PORT_Strncmp(buf, "MD5-Digest: ", 12)) { + md5_digest = + ATOB_AsciiToData(buf + 12, &md5_length); + } + if (!PORT_Strncmp(buf, "SHA1-Digest: ", 13)) { + sha1_digest = + ATOB_AsciiToData(buf + 13, &sha1_length); + } + if (!PORT_Strncmp(buf, "SHA-Digest: ", 12)) { + sha1_digest = + ATOB_AsciiToData(buf + 12, &sha1_length); + } + } + + globaldig = jar->globalmeta; + + if (globaldig && md5_digest && verbosity >= 0) { + PR_fprintf(outputFD, + " md5 digest on global metainfo: %s\n", + PORT_Memcmp(md5_digest, globaldig->md5, MD5_LENGTH) + ? "no match" + : "match"); + } + + if (globaldig && sha1_digest && verbosity >= 0) { + PR_fprintf(outputFD, + " sha digest on global metainfo: %s\n", + PORT_Memcmp(sha1_digest, globaldig->sha1, SHA1_LENGTH) + ? "no match" + : "match"); + } + + if (globaldig == NULL && verbosity >= 0) { + PR_fprintf(outputFD, + "global metadigest is not available, strange.\n"); + } + + PORT_Free(md5_digest); + PORT_Free(sha1_digest); + fclose(fp); + } + } + } + } + + JAR_find_end(ctx); + + return retval; +} + +/************************************************************************ + * + * J a r W h o + */ +int +JarWho(char *filename) +{ + FILE *fp; + + JAR *jar; + JAR_Context *ctx; + + int status; + int retval = 0; + + JAR_Item *it; + JAR_Cert *fing; + + CERTCertificate *cert, *prev = NULL; + + jar = JAR_new(); + + if ((fp = fopen(filename, "r")) == NULL) { + perror(filename); + exit(ERRX); + } + fclose(fp); + + status = JAR_pass_archive(jar, jarArchGuess, filename, "some-url"); + + if (status < 0 || jar->valid < 0) { + PR_fprintf(outputFD, + "NOTE -- \"%s\" archive DID NOT PASS crypto verification.\n", + filename); + retval = -1; + if (jar->valid < 0 || status != -1) { + const char *errtext; + + if (status >= JAR_BASE && status <= JAR_BASE_END) { + errtext = JAR_get_error(status); + } else { + errtext = SECU_Strerror(PORT_GetError()); + } + + PR_fprintf(outputFD, " (reported reason: %s)\n\n", errtext); + } + } + + PR_fprintf(outputFD, "\nSigner information:\n\n"); + + ctx = JAR_find(jar, NULL, jarTypeSign); + + while (JAR_find_next(ctx, &it) >= 0) { + fing = (JAR_Cert *)it->data; + cert = fing->cert; + + if (cert) { + if (prev == cert) + break; + + if (cert->nickname) + PR_fprintf(outputFD, "nickname: %s\n", cert->nickname); + if (cert->subjectName) + PR_fprintf(outputFD, "subject name: %s\n", + cert->subjectName); + if (cert->issuerName) + PR_fprintf(outputFD, "issuer name: %s\n", cert->issuerName); + } else { + PR_fprintf(outputFD, "no certificate could be found\n"); + retval = -1; + } + + prev = cert; + } + + JAR_find_end(ctx); + + JAR_destroy(jar); + return retval; +} + +/************************************************************************ + * j a r _ c b + */ +static int +jar_cb(int status, JAR *jar, const char *metafile, + char *pathname, char *errortext) +{ + PR_fprintf(errorFD, "error %d: %s IN FILE %s\n", status, errortext, + pathname); + errorCount++; + return 0; +} diff --git a/security/nss/cmd/signtool/zip.c b/security/nss/cmd/signtool/zip.c new file mode 100644 index 000000000..35d5f5733 --- /dev/null +++ b/security/nss/cmd/signtool/zip.c @@ -0,0 +1,676 @@ +/* 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 "signtool.h" +#include "zip.h" +#include "zlib.h" +#include "prmem.h" + +static void inttox(int in, char *out); +static void longtox(long in, char *out); + +/**************************************************************** + * + * J z i p O p e n + * + * Opens a new ZIP file and creates a new ZIPfile structure to + * control the process of installing files into a zip. + */ +ZIPfile * +JzipOpen(char *filename, char *comment) +{ + ZIPfile *zipfile; + PRExplodedTime prtime; + + zipfile = PORT_ZAlloc(sizeof(ZIPfile)); + if (!zipfile) + out_of_memory(); + + /* Construct time and date */ + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &prtime); + zipfile->date = ((prtime.tm_year - 1980) << 9) | + ((prtime.tm_month + 1) << 5) | + prtime.tm_mday; + zipfile->time = (prtime.tm_hour << 11) | + (prtime.tm_min << 5) | + (prtime.tm_sec & 0x3f); + + zipfile->fp = NULL; + if (filename && + (zipfile->fp = PR_Open(filename, + PR_WRONLY | + PR_CREATE_FILE | + PR_TRUNCATE, + 0777)) == NULL) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "%s: can't open output jar, %s.%s\n", + PROGRAM_NAME, + filename, nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + + zipfile->list = NULL; + if (filename) { + zipfile->filename = PORT_ZAlloc(strlen(filename) + 1); + if (!zipfile->filename) + out_of_memory(); + PORT_Strcpy(zipfile->filename, filename); + } + if (comment) { + zipfile->comment = PORT_ZAlloc(strlen(comment) + 1); + if (!zipfile->comment) + out_of_memory(); + PORT_Strcpy(zipfile->comment, comment); + } + + return zipfile; +} + +static void * +my_alloc_func(void *opaque, uInt items, uInt size) +{ + return PORT_Alloc(items * size); +} + +static void +my_free_func(void *opaque, void *address) +{ + PORT_Free(address); +} + +static void +handle_zerror(int err, char *msg) +{ + if (!msg) { + msg = ""; + } + + errorCount++; /* unless Z_OK...see below */ + + switch (err) { + case Z_OK: + PR_fprintf(errorFD, "No error: %s\n", msg); + errorCount--; /* this was incremented above */ + break; + case Z_MEM_ERROR: + PR_fprintf(errorFD, "Deflation ran out of memory: %s\n", msg); + break; + case Z_STREAM_ERROR: + PR_fprintf(errorFD, "Invalid compression level: %s\n", msg); + break; + case Z_VERSION_ERROR: + PR_fprintf(errorFD, "Incompatible compression library version: %s\n", + msg); + break; + case Z_DATA_ERROR: + PR_fprintf(errorFD, "Compression data error: %s\n", msg); + break; + default: + PR_fprintf(errorFD, "Unknown error in compression library: %s\n", msg); + break; + } +} + +/**************************************************************** + * + * J z i p A d d + * + * Adds a new file into a ZIP file. The ZIP file must have already + * been opened with JzipOpen. + */ +int +JzipAdd(char *fullname, char *filename, ZIPfile *zipfile, int compression_level) +{ + ZIPentry *entry; + PRFileDesc *readfp; + PRFileDesc *zipfp; + unsigned long crc; + unsigned long local_size_pos; + int num; + int err; + int deflate_percent; + z_stream zstream; + Bytef inbuf[BUFSIZ]; + Bytef outbuf[BUFSIZ]; + + if (!fullname || !filename || !zipfile) { + return -1; + } + + zipfp = zipfile->fp; + if (!zipfp) + return -1; + + if ((readfp = PR_Open(fullname, PR_RDONLY, 0777)) == NULL) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "%s: %s\n", fullname, nsprErr ? nsprErr : ""); + errorCount++; + if (nsprErr) + PR_Free(nsprErr); + exit(ERRX); + } + + /* + * Make sure the input file is not the output file. + * Add a few bytes to the end of the JAR file and see if the input file + * twitches + */ + { + PRInt32 endOfJar; + PRInt32 inputSize; + PRBool isSame; + + inputSize = PR_Available(readfp); + + endOfJar = PR_Seek(zipfp, 0L, PR_SEEK_CUR); + + if (PR_Write(zipfp, "abcde", 5) < 5) { + char *nsprErr; + + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Writing to zip file: %s\n", + nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + + isSame = (PR_Available(readfp) != inputSize); + + PR_Seek(zipfp, endOfJar, PR_SEEK_SET); + + if (isSame) { + /* It's the same file! Forget it! */ + PR_Close(readfp); + return 0; + } + } + + if (verbosity >= 0) { + PR_fprintf(outputFD, "adding %s to %s...", fullname, zipfile->filename); + } + + entry = PORT_ZAlloc(sizeof(ZIPentry)); + if (!entry) + out_of_memory(); + + entry->filename = PORT_Strdup(filename); + entry->comment = NULL; + + /* Set up local file header */ + longtox(LSIG, entry->local.signature); + inttox(strlen(filename), entry->local.filename_len); + inttox(zipfile->time, entry->local.time); + inttox(zipfile->date, entry->local.date); + inttox(Z_DEFLATED, entry->local.method); + + /* Set up central directory entry */ + longtox(CSIG, entry->central.signature); + inttox(strlen(filename), entry->central.filename_len); + if (entry->comment) { + inttox(strlen(entry->comment), entry->central.commentfield_len); + } + longtox(PR_Seek(zipfile->fp, 0, PR_SEEK_CUR), + entry->central.localhdr_offset); + inttox(zipfile->time, entry->central.time); + inttox(zipfile->date, entry->central.date); + inttox(Z_DEFLATED, entry->central.method); + + /* Compute crc. Too bad we have to process the whole file to do this*/ + crc = crc32(0L, NULL, 0); + while ((num = PR_Read(readfp, inbuf, BUFSIZ)) > 0) { + crc = crc32(crc, inbuf, num); + } + PR_Seek(readfp, 0L, PR_SEEK_SET); + + /* Store CRC */ + longtox(crc, entry->local.crc32); + longtox(crc, entry->central.crc32); + + /* Stick this entry onto the end of the list */ + entry->next = NULL; + if (zipfile->list == NULL) { + /* First entry */ + zipfile->list = entry; + } else { + ZIPentry *pe; + + pe = zipfile->list; + while (pe->next != NULL) { + pe = pe->next; + } + pe->next = entry; + } + + /* + * Start writing stuff out + */ + + local_size_pos = PR_Seek(zipfp, 0, PR_SEEK_CUR) + 18; + /* File header */ + if (PR_Write(zipfp, &entry->local, sizeof(struct ZipLocal)) < + sizeof(struct ZipLocal)) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Writing zip data: %s\n", nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + + /* File Name */ + if (PR_Write(zipfp, filename, strlen(filename)) < strlen(filename)) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Writing zip data: %s\n", nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + + /* + * File data + */ + /* Initialize zstream */ + zstream.zalloc = my_alloc_func; + zstream.zfree = my_free_func; + zstream.opaque = NULL; + zstream.next_in = inbuf; + zstream.avail_in = BUFSIZ; + zstream.next_out = outbuf; + zstream.avail_out = BUFSIZ; + /* Setting the windowBits to -MAX_WBITS is an undocumented feature of + * zlib (see deflate.c in zlib). It is the same thing that Java does + * when you specify the nowrap option for deflation in java.util.zip. + * It causes zlib to leave out its headers and footers, which don't + * work in PKZIP files. + */ + err = deflateInit2(&zstream, compression_level, Z_DEFLATED, + -MAX_WBITS, 8 /*default*/, Z_DEFAULT_STRATEGY); + if (err != Z_OK) { + handle_zerror(err, zstream.msg); + exit(ERRX); + } + + while ((zstream.avail_in = PR_Read(readfp, inbuf, BUFSIZ)) > 0) { + zstream.next_in = inbuf; + /* Process this chunk of data */ + while (zstream.avail_in > 0) { + err = deflate(&zstream, Z_NO_FLUSH); + if (err != Z_OK) { + handle_zerror(err, zstream.msg); + exit(ERRX); + } + if (zstream.avail_out <= 0) { + if (PR_Write(zipfp, outbuf, BUFSIZ) < BUFSIZ) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Writing zip data: %s\n", + nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + zstream.next_out = outbuf; + zstream.avail_out = BUFSIZ; + } + } + } + + /* Now flush everything */ + while (1) { + err = deflate(&zstream, Z_FINISH); + if (err == Z_STREAM_END) { + break; + } else if (err == Z_OK) { + /* output buffer full, repeat */ + } else { + handle_zerror(err, zstream.msg); + exit(ERRX); + } + if (PR_Write(zipfp, outbuf, BUFSIZ) < BUFSIZ) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Writing zip data: %s\n", + nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + zstream.avail_out = BUFSIZ; + zstream.next_out = outbuf; + } + + /* If there's any output left, write it out. */ + if (zstream.next_out != outbuf) { + if (PR_Write(zipfp, outbuf, zstream.next_out - outbuf) < + zstream.next_out - outbuf) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Writing zip data: %s\n", + nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + zstream.avail_out = BUFSIZ; + zstream.next_out = outbuf; + } + + /* Now that we know the compressed size, write this to the headers */ + longtox(zstream.total_in, entry->local.orglen); + longtox(zstream.total_out, entry->local.size); + if (PR_Seek(zipfp, local_size_pos, PR_SEEK_SET) == -1) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Accessing zip file: %s\n", nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + if (PR_Write(zipfp, entry->local.size, 8) != 8) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Writing zip data: %s\n", nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + if (PR_Seek(zipfp, 0L, PR_SEEK_END) == -1) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Accessing zip file: %s\n", + nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + longtox(zstream.total_in, entry->central.orglen); + longtox(zstream.total_out, entry->central.size); + + /* Close out the deflation operation */ + err = deflateEnd(&zstream); + if (err != Z_OK) { + handle_zerror(err, zstream.msg); + exit(ERRX); + } + + PR_Close(readfp); + + if ((zstream.total_in > zstream.total_out) && (zstream.total_in > 0)) { + deflate_percent = (int)((zstream.total_in - + zstream.total_out) * + 100 / zstream.total_in); + } else { + deflate_percent = 0; + } + if (verbosity >= 0) { + PR_fprintf(outputFD, "(deflated %d%%)\n", deflate_percent); + } + + return 0; +} + +/******************************************************************** + * J z i p C l o s e + * + * Finishes the ZipFile. ALSO DELETES THE ZIPFILE STRUCTURE PASSED IN!! + */ +int +JzipClose(ZIPfile *zipfile) +{ + ZIPentry *pe, *dead; + PRFileDesc *zipfp; + struct ZipEnd zipend; + unsigned int entrycount = 0; + + if (!zipfile) { + return -1; + } + + if (!zipfile->filename) { + /* bogus */ + return 0; + } + + zipfp = zipfile->fp; + zipfile->central_start = PR_Seek(zipfp, 0L, PR_SEEK_CUR); + + /* Write out all the central directories */ + pe = zipfile->list; + while (pe) { + entrycount++; + + /* Write central directory info */ + if (PR_Write(zipfp, &pe->central, sizeof(struct ZipCentral)) < + sizeof(struct ZipCentral)) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Writing zip data: %s\n", + nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + + /* Write filename */ + if (PR_Write(zipfp, pe->filename, strlen(pe->filename)) < + strlen(pe->filename)) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Writing zip data: %s\n", + nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + + /* Write file comment */ + if (pe->comment) { + if (PR_Write(zipfp, pe->comment, strlen(pe->comment)) < + strlen(pe->comment)) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Writing zip data: %s\n", + nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + } + + /* Delete the structure */ + dead = pe; + pe = pe->next; + if (dead->filename) { + PORT_Free(dead->filename); + } + if (dead->comment) { + PORT_Free(dead->comment); + } + PORT_Free(dead); + } + zipfile->central_end = PR_Seek(zipfile->fp, 0L, PR_SEEK_CUR); + + /* Create the ZipEnd structure */ + PORT_Memset(&zipend, 0, sizeof(zipend)); + longtox(ESIG, zipend.signature); + inttox(entrycount, zipend.total_entries_disk); + inttox(entrycount, zipend.total_entries_archive); + longtox(zipfile->central_end - zipfile->central_start, + zipend.central_dir_size); + longtox(zipfile->central_start, zipend.offset_central_dir); + if (zipfile->comment) { + inttox(strlen(zipfile->comment), zipend.commentfield_len); + } + + /* Write out ZipEnd xtructure */ + if (PR_Write(zipfp, &zipend, sizeof(zipend)) < sizeof(zipend)) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Writing zip data: %s\n", + nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + + /* Write out Zipfile comment */ + if (zipfile->comment) { + if (PR_Write(zipfp, zipfile->comment, strlen(zipfile->comment)) < + strlen(zipfile->comment)) { + char *nsprErr; + if (PR_GetErrorTextLength()) { + nsprErr = PR_Malloc(PR_GetErrorTextLength() + 1); + PR_GetErrorText(nsprErr); + } else { + nsprErr = NULL; + } + PR_fprintf(errorFD, "Writing zip data: %s\n", + nsprErr ? nsprErr : ""); + if (nsprErr) + PR_Free(nsprErr); + errorCount++; + exit(ERRX); + } + } + + PR_Close(zipfp); + + /* Free the memory of the zipfile structure */ + if (zipfile->filename) { + PORT_Free(zipfile->filename); + } + if (zipfile->comment) { + PORT_Free(zipfile->comment); + } + PORT_Free(zipfile); + + return 0; +} + +/********************************************** + * i n t t o x + * + * Converts a two byte ugly endianed integer + * to our platform's integer. + * + */ + +static void +inttox(int in, char *out) +{ + out[0] = (in & 0xFF); + out[1] = (in & 0xFF00) >> 8; +} + +/********************************************* + * l o n g t o x + * + * Converts a four byte ugly endianed integer + * to our platform's integer. + * + */ + +static void +longtox(long in, char *out) +{ + out[0] = (in & 0xFF); + out[1] = (in & 0xFF00) >> 8; + out[2] = (in & 0xFF0000) >> 16; + out[3] = (in & 0xFF000000) >> 24; +} diff --git a/security/nss/cmd/signtool/zip.h b/security/nss/cmd/signtool/zip.h new file mode 100644 index 000000000..1c0076223 --- /dev/null +++ b/security/nss/cmd/signtool/zip.h @@ -0,0 +1,69 @@ +/* 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/. */ + +/* zip.h + * Structures and functions for creating ZIP archives. + */ +#ifndef ZIP_H +#define ZIP_H + +/* For general information on ZIP formats, you can look at jarfile.h + * in ns/security/lib/jar. Or look it up on the web... + */ + +/* One entry in a ZIPfile. This corresponds to one file in the archive. + * We have to save this information because first all the files go into + * the archive with local headers; then at the end of the file we have to + * put the central directory entries for each file. + */ +typedef struct ZIPentry_s { + struct ZipLocal local; /* local header info */ + struct ZipCentral central; /* central directory info */ + char *filename; /* name of file */ + char *comment; /* comment for this file -- optional */ + + struct ZIPentry_s *next; +} ZIPentry; + +/* This structure contains the necessary data for putting a ZIP file + * together. Has some overall information and a list of ZIPentrys. + */ +typedef struct ZIPfile_s { + char *filename; /* ZIP file name */ + char *comment; /* ZIP file comment -- may be NULL */ + PRFileDesc *fp; /* ZIP file pointer */ + ZIPentry *list; /* one entry for each file in the archive */ + unsigned int time; /* the GMT time of creation, in DOS format */ + unsigned int date; /* the GMT date of creation, in DOS format */ + unsigned long central_start; /* starting offset of central directory */ + unsigned long central_end; /*index right after the last byte of central*/ +} ZIPfile; + +/* Open a new ZIP file. Takes the name of the zip file and an optional + * comment to be included in the file. Returns a new ZIPfile structure + * which is used by JzipAdd and JzipClose + */ +ZIPfile *JzipOpen(char *filename, char *comment); + +/* Add a file to a ZIP archive. Fullname is the path relative to the + * current directory. Filename is what the name will be stored as in the + * archive, and thus what it will be restored as. zipfile is a structure + * returned from a previous call to JzipOpen. + * + * Non-zero return code means error (although usually the function will + * call exit() rather than return an error--gotta fix this). + */ +int JzipAdd(char *fullname, char *filename, ZIPfile *zipfile, + int compression_level); + +/* Finalize a ZIP archive. Adds all the footer information to the end of + * the file and closes it. Also DELETES THE ZIPFILE STRUCTURE that was + * passed in. So you never have to allocate or free a ZIPfile yourself. + * + * Non-zero return code means error (although usually the function will + * call exit() rather than return an error--gotta fix this). + */ +int JzipClose(ZIPfile *zipfile); + +#endif /* ZIP_H */ |