summaryrefslogtreecommitdiffstats
path: root/security/nss/cmd/signtool
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/cmd/signtool')
-rw-r--r--security/nss/cmd/signtool/Makefile44
-rw-r--r--security/nss/cmd/signtool/README128
-rw-r--r--security/nss/cmd/signtool/certgen.c713
-rw-r--r--security/nss/cmd/signtool/javascript.c1817
-rw-r--r--security/nss/cmd/signtool/list.c215
-rw-r--r--security/nss/cmd/signtool/manifest.mn25
-rw-r--r--security/nss/cmd/signtool/sign.c834
-rw-r--r--security/nss/cmd/signtool/signtool.c1068
-rw-r--r--security/nss/cmd/signtool/signtool.gyp33
-rw-r--r--security/nss/cmd/signtool/signtool.h113
-rw-r--r--security/nss/cmd/signtool/util.c1086
-rw-r--r--security/nss/cmd/signtool/verify.c337
-rw-r--r--security/nss/cmd/signtool/zip.c676
-rw-r--r--security/nss/cmd/signtool/zip.h69
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 */