summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/jar/jarver.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/nss/lib/jar/jarver.c
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/nss/lib/jar/jarver.c')
-rw-r--r--security/nss/lib/jar/jarver.c1167
1 files changed, 1167 insertions, 0 deletions
diff --git a/security/nss/lib/jar/jarver.c b/security/nss/lib/jar/jarver.c
new file mode 100644
index 0000000000..38d73c21c0
--- /dev/null
+++ b/security/nss/lib/jar/jarver.c
@@ -0,0 +1,1167 @@
+/* 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/. */
+
+/*
+ * JARVER
+ *
+ * Jarnature Parsing & Verification
+ */
+
+#include "nssrenam.h"
+#include "jar.h"
+#include "jarint.h"
+#include "certdb.h"
+#include "certt.h"
+#include "secpkcs7.h"
+#include "secder.h"
+
+#define SZ 512
+
+static int
+jar_validate_pkcs7(JAR *jar, JAR_Signer *signer, char *data, long length);
+
+static void
+jar_catch_bytes(void *arg, const char *buf, unsigned long len);
+
+static int
+jar_gather_signers(JAR *jar, JAR_Signer *signer, SEC_PKCS7ContentInfo *cinfo);
+
+static char *
+jar_eat_line(int lines, int eating, char *data, long *len);
+
+static JAR_Digest *
+jar_digest_section(char *manifest, long length);
+
+static JAR_Digest *jar_get_mf_digest(JAR *jar, char *path);
+
+static int
+jar_parse_digital_signature(char *raw_manifest, JAR_Signer *signer,
+ long length, JAR *jar);
+
+static int
+jar_add_cert(JAR *jar, JAR_Signer *signer, int type, CERTCertificate *cert);
+
+static char *jar_basename(const char *path);
+
+static int
+jar_signal(int status, JAR *jar, const char *metafile, char *pathname);
+
+#ifdef DEBUG
+static int jar_insanity_check(char *data, long length);
+#endif
+
+int
+jar_parse_mf(JAR *jar, char *raw_manifest, long length,
+ const char *path, const char *url);
+
+int
+jar_parse_sf(JAR *jar, char *raw_manifest, long length,
+ const char *path, const char *url);
+
+int
+jar_parse_sig(JAR *jar, const char *path, char *raw_manifest,
+ long length);
+
+int
+jar_parse_any(JAR *jar, int type, JAR_Signer *signer,
+ char *raw_manifest, long length, const char *path,
+ const char *url);
+
+static int
+jar_internal_digest(JAR *jar, const char *path, char *x_name, JAR_Digest *dig);
+
+/*
+ * J A R _ p a r s e _ m a n i f e s t
+ *
+ * Pass manifest files to this function. They are
+ * decoded and placed into internal representations.
+ *
+ * Accepts both signature and manifest files. Use
+ * the same "jar" for both.
+ *
+ */
+int
+JAR_parse_manifest(JAR *jar, char *raw_manifest, long length,
+ const char *path, const char *url)
+{
+ int filename_free = 0;
+
+ /* fill in the path, if supplied. This is the location
+ of the jar file on disk, if known */
+
+ if (jar->filename == NULL && path) {
+ jar->filename = PORT_Strdup(path);
+ if (jar->filename == NULL)
+ return JAR_ERR_MEMORY;
+ filename_free = 1;
+ }
+
+ /* fill in the URL, if supplied. This is the place
+ from which the jar file was retrieved. */
+
+ if (jar->url == NULL && url) {
+ jar->url = PORT_Strdup(url);
+ if (jar->url == NULL) {
+ if (filename_free) {
+ PORT_Free(jar->filename);
+ }
+ return JAR_ERR_MEMORY;
+ }
+ }
+
+ /* Determine what kind of file this is from the META-INF
+ directory. It could be MF, SF, or a binary RSA/DSA file */
+
+ if (!PORT_Strncasecmp(raw_manifest, "Manifest-Version:", 17)) {
+ return jar_parse_mf(jar, raw_manifest, length, path, url);
+ } else if (!PORT_Strncasecmp(raw_manifest, "Signature-Version:", 18)) {
+ return jar_parse_sf(jar, raw_manifest, length, path, url);
+ } else {
+ /* This is probably a binary signature */
+ return jar_parse_sig(jar, path, raw_manifest, length);
+ }
+}
+
+/*
+ * j a r _ p a r s e _ s i g
+ *
+ * Pass some manner of RSA or DSA digital signature
+ * on, after checking to see if it comes at an appropriate state.
+ *
+ */
+int
+jar_parse_sig(JAR *jar, const char *path, char *raw_manifest,
+ long length)
+{
+ JAR_Signer *signer;
+ int status = JAR_ERR_ORDER;
+
+ if (length <= 128) {
+ /* signature is way too small */
+ return JAR_ERR_SIG;
+ }
+
+ /* make sure that MF and SF have already been processed */
+
+ if (jar->globalmeta == NULL)
+ return JAR_ERR_ORDER;
+
+ /* Determine whether or not this RSA file has
+ has an associated SF file */
+
+ if (path) {
+ char *owner;
+ owner = jar_basename(path);
+
+ if (owner == NULL)
+ return JAR_ERR_MEMORY;
+
+ signer = jar_get_signer(jar, owner);
+ PORT_Free(owner);
+ } else
+ signer = jar_get_signer(jar, "*");
+
+ if (signer == NULL)
+ return JAR_ERR_ORDER;
+
+ /* Do not pass a huge pointer to this function,
+ since the underlying security code is unaware. We will
+ never pass >64k through here. */
+
+ if (length > 64000) {
+ /* this digital signature is way too big */
+ return JAR_ERR_SIG;
+ }
+
+ /* don't expense unneeded calloc overhead on non-win16 */
+ status = jar_parse_digital_signature(raw_manifest, signer, length, jar);
+
+ return status;
+}
+
+/*
+ * j a r _ p a r s e _ m f
+ *
+ * Parse the META-INF/manifest.mf file, whose
+ * information applies to all signers.
+ *
+ */
+int
+jar_parse_mf(JAR *jar, char *raw_manifest, long length,
+ const char *path, const char *url)
+{
+ if (jar->globalmeta) {
+ /* refuse a second manifest file, if passed for some reason */
+ return JAR_ERR_ORDER;
+ }
+
+ /* remember a digest for the global section */
+ jar->globalmeta = jar_digest_section(raw_manifest, length);
+ if (jar->globalmeta == NULL)
+ return JAR_ERR_MEMORY;
+ return jar_parse_any(jar, jarTypeMF, NULL, raw_manifest, length,
+ path, url);
+}
+
+/*
+ * j a r _ p a r s e _ s f
+ *
+ * Parse META-INF/xxx.sf, a digitally signed file
+ * pointing to a subset of MF sections.
+ *
+ */
+int
+jar_parse_sf(JAR *jar, char *raw_manifest, long length,
+ const char *path, const char *url)
+{
+ JAR_Signer *signer = NULL;
+ int status = JAR_ERR_MEMORY;
+
+ if (jar->globalmeta == NULL) {
+ /* It is a requirement that the MF file be passed before the SF file */
+ return JAR_ERR_ORDER;
+ }
+
+ signer = JAR_new_signer();
+ if (signer == NULL)
+ goto loser;
+
+ if (path) {
+ signer->owner = jar_basename(path);
+ if (signer->owner == NULL)
+ goto loser;
+ }
+
+ /* check for priors. When someone doctors a jar file
+ to contain identical path entries, prevent the second
+ one from affecting JAR functions */
+ if (jar_get_signer(jar, signer->owner)) {
+ /* someone is trying to spoof us */
+ status = JAR_ERR_ORDER;
+ goto loser;
+ }
+
+ /* remember its digest */
+ signer->digest = JAR_calculate_digest(raw_manifest, length);
+ if (signer->digest == NULL)
+ goto loser;
+
+ /* Add this signer to the jar */
+ ADDITEM(jar->signers, jarTypeOwner, signer->owner, signer,
+ sizeof(JAR_Signer));
+
+ return jar_parse_any(jar, jarTypeSF, signer, raw_manifest, length,
+ path, url);
+
+loser:
+ if (signer)
+ JAR_destroy_signer(signer);
+ return status;
+}
+
+/*
+ * j a r _ p a r s e _ a n y
+ *
+ * Parse a MF or SF manifest file.
+ *
+ */
+int
+jar_parse_any(JAR *jar, int type, JAR_Signer *signer,
+ char *raw_manifest, long length, const char *path,
+ const char *url)
+{
+ int status;
+ long raw_len;
+ JAR_Digest *dig, *mfdig = NULL;
+ char line[SZ];
+ char x_name[SZ], x_md5[SZ], x_sha[SZ];
+ char *x_info;
+ char *sf_md5 = NULL, *sf_sha1 = NULL;
+
+ *x_name = 0;
+ *x_md5 = 0;
+ *x_sha = 0;
+
+ PORT_Assert(length > 0);
+ raw_len = length;
+
+#ifdef DEBUG
+ if ((status = jar_insanity_check(raw_manifest, raw_len)) < 0)
+ return status;
+#endif
+
+ /* null terminate the first line */
+ raw_manifest = jar_eat_line(0, PR_TRUE, raw_manifest, &raw_len);
+
+ /* skip over the preliminary section */
+ /* This is one section at the top of the file with global metainfo */
+ while (raw_len > 0) {
+ JAR_Metainfo *met;
+
+ raw_manifest = jar_eat_line(1, PR_TRUE, raw_manifest, &raw_len);
+ if (raw_len <= 0 || !*raw_manifest)
+ break;
+
+ met = PORT_ZNew(JAR_Metainfo);
+ if (met == NULL)
+ return JAR_ERR_MEMORY;
+
+ /* Parse out the header & info */
+ if (PORT_Strlen(raw_manifest) >= SZ) {
+ /* almost certainly nonsense */
+ PORT_Free(met);
+ continue;
+ }
+
+ PORT_Strcpy(line, raw_manifest);
+ x_info = line;
+
+ while (*x_info && *x_info != ' ' && *x_info != '\t' && *x_info != ':')
+ x_info++;
+
+ if (*x_info)
+ *x_info++ = 0;
+
+ while (*x_info == ' ' || *x_info == '\t')
+ x_info++;
+
+ /* metainfo (name, value) pair is now (line, x_info) */
+ met->header = PORT_Strdup(line);
+ met->info = PORT_Strdup(x_info);
+
+ if (type == jarTypeMF) {
+ ADDITEM(jar->metainfo, jarTypeMeta,
+ /* pathname */ NULL, met, sizeof(JAR_Metainfo));
+ }
+
+ /* For SF files, this metadata may be the digests
+ of the MF file, still in the "met" structure. */
+
+ if (type == jarTypeSF) {
+ if (!PORT_Strcasecmp(line, "MD5-Digest")) {
+ sf_md5 = (char *)met->info;
+ } else if (!PORT_Strcasecmp(line, "SHA1-Digest") ||
+ !PORT_Strcasecmp(line, "SHA-Digest")) {
+ sf_sha1 = (char *)met->info;
+ } else {
+ PORT_Free(met->info);
+ met->info = NULL;
+ }
+ }
+
+ if (type != jarTypeMF) {
+ PORT_Free(met->header);
+ if ((type != jarTypeSF || !jar->globalmeta) && met->info) {
+ PORT_Free(met->info);
+ }
+ PORT_Free(met);
+ }
+ }
+
+ if (type == jarTypeSF && jar->globalmeta) {
+ /* this is a SF file which may contain a digest of the manifest.mf's
+ global metainfo. */
+
+ int match = 0;
+ JAR_Digest *glob = jar->globalmeta;
+
+ if (sf_md5) {
+ unsigned int md5_length;
+ unsigned char *md5_digest;
+
+ md5_digest = ATOB_AsciiToData(sf_md5, &md5_length);
+ PORT_Assert(md5_length == MD5_LENGTH);
+ PORT_Free(sf_md5);
+
+ if (md5_length != MD5_LENGTH)
+ return JAR_ERR_CORRUPT;
+
+ match = PORT_Memcmp(md5_digest, glob->md5, MD5_LENGTH);
+ PORT_Free(md5_digest);
+ }
+
+ if (sf_sha1 && match == 0) {
+ unsigned int sha1_length;
+ unsigned char *sha1_digest;
+
+ sha1_digest = ATOB_AsciiToData(sf_sha1, &sha1_length);
+ PORT_Assert(sha1_length == SHA1_LENGTH);
+ PORT_Free(sf_sha1);
+
+ if (sha1_length != SHA1_LENGTH)
+ return JAR_ERR_CORRUPT;
+
+ match = PORT_Memcmp(sha1_digest, glob->sha1, SHA1_LENGTH);
+ PORT_Free(sha1_digest);
+ }
+
+ if (match != 0) {
+ /* global digest doesn't match, SF file therefore invalid */
+ jar->valid = JAR_ERR_METADATA;
+ return JAR_ERR_METADATA;
+ }
+ }
+
+ /* done with top section of global data */
+ while (raw_len > 0) {
+ *x_md5 = 0;
+ *x_sha = 0;
+ *x_name = 0;
+
+ /* If this is a manifest file, attempt to get a digest of the following
+ section, without damaging it. This digest will be saved later. */
+
+ if (type == jarTypeMF) {
+ char *sec;
+ long sec_len = raw_len;
+
+ if (!*raw_manifest || *raw_manifest == '\n') {
+ /* skip the blank line */
+ sec = jar_eat_line(1, PR_FALSE, raw_manifest, &sec_len);
+ } else
+ sec = raw_manifest;
+
+ if (sec_len > 0 && !PORT_Strncasecmp(sec, "Name:", 5)) {
+ if (type == jarTypeMF)
+ mfdig = jar_digest_section(sec, sec_len);
+ else
+ mfdig = NULL;
+ }
+ }
+
+ while (raw_len > 0) {
+ raw_manifest = jar_eat_line(1, PR_TRUE, raw_manifest, &raw_len);
+ if (raw_len <= 0 || !*raw_manifest)
+ break; /* blank line, done with this entry */
+
+ if (PORT_Strlen(raw_manifest) >= SZ) {
+ /* almost certainly nonsense */
+ continue;
+ }
+
+ /* Parse out the name/value pair */
+ PORT_Strcpy(line, raw_manifest);
+ x_info = line;
+
+ while (*x_info && *x_info != ' ' && *x_info != '\t' &&
+ *x_info != ':')
+ x_info++;
+
+ if (*x_info)
+ *x_info++ = 0;
+
+ while (*x_info == ' ' || *x_info == '\t')
+ x_info++;
+
+ if (!PORT_Strcasecmp(line, "Name"))
+ PORT_Strcpy(x_name, x_info);
+ else if (!PORT_Strcasecmp(line, "MD5-Digest"))
+ PORT_Strcpy(x_md5, x_info);
+ else if (!PORT_Strcasecmp(line, "SHA1-Digest") ||
+ !PORT_Strcasecmp(line, "SHA-Digest"))
+ PORT_Strcpy(x_sha, x_info);
+
+ /* Algorithm list is meta info we don't care about; keeping it out
+ of metadata saves significant space for large jar files */
+ else if (!PORT_Strcasecmp(line, "Digest-Algorithms") ||
+ !PORT_Strcasecmp(line, "Hash-Algorithms"))
+ continue;
+
+ /* Meta info is only collected for the manifest.mf file,
+ since the JAR_get_metainfo call does not support identity */
+ else if (type == jarTypeMF) {
+ JAR_Metainfo *met;
+
+ /* this is meta-data */
+ met = PORT_ZNew(JAR_Metainfo);
+ if (met == NULL)
+ return JAR_ERR_MEMORY;
+
+ /* metainfo (name, value) pair is now (line, x_info) */
+ if ((met->header = PORT_Strdup(line)) == NULL) {
+ PORT_Free(met);
+ return JAR_ERR_MEMORY;
+ }
+
+ if ((met->info = PORT_Strdup(x_info)) == NULL) {
+ PORT_Free(met->header);
+ PORT_Free(met);
+ return JAR_ERR_MEMORY;
+ }
+
+ ADDITEM(jar->metainfo, jarTypeMeta,
+ x_name, met, sizeof(JAR_Metainfo));
+ }
+ }
+
+ if (!*x_name) {
+ /* Whatever that was, it wasn't an entry, because we didn't get a
+ name. We don't really have anything, so don't record this. */
+ continue;
+ }
+
+ dig = PORT_ZNew(JAR_Digest);
+ if (dig == NULL)
+ return JAR_ERR_MEMORY;
+
+ if (*x_md5) {
+ unsigned int binary_length;
+ unsigned char *binary_digest;
+
+ binary_digest = ATOB_AsciiToData(x_md5, &binary_length);
+ PORT_Assert(binary_length == MD5_LENGTH);
+ if (binary_length != MD5_LENGTH) {
+ PORT_Free(dig);
+ return JAR_ERR_CORRUPT;
+ }
+ memcpy(dig->md5, binary_digest, MD5_LENGTH);
+ dig->md5_status = jarHashPresent;
+ PORT_Free(binary_digest);
+ }
+
+ if (*x_sha) {
+ unsigned int binary_length;
+ unsigned char *binary_digest;
+
+ binary_digest = ATOB_AsciiToData(x_sha, &binary_length);
+ PORT_Assert(binary_length == SHA1_LENGTH);
+ if (binary_length != SHA1_LENGTH) {
+ PORT_Free(dig);
+ return JAR_ERR_CORRUPT;
+ }
+ memcpy(dig->sha1, binary_digest, SHA1_LENGTH);
+ dig->sha1_status = jarHashPresent;
+ PORT_Free(binary_digest);
+ }
+
+ PORT_Assert(type == jarTypeMF || type == jarTypeSF);
+ if (type == jarTypeMF) {
+ ADDITEM(jar->hashes, jarTypeMF, x_name, dig, sizeof(JAR_Digest));
+ } else if (type == jarTypeSF) {
+ ADDITEM(signer->sf, jarTypeSF, x_name, dig, sizeof(JAR_Digest));
+ } else {
+ PORT_Free(dig);
+ return JAR_ERR_ORDER;
+ }
+
+ /* we're placing these calculated digests of manifest.mf
+ sections in a list where they can subsequently be forgotten */
+ if (type == jarTypeMF && mfdig) {
+ ADDITEM(jar->manifest, jarTypeSect,
+ x_name, mfdig, sizeof(JAR_Digest));
+ mfdig = NULL;
+ }
+
+ /* Retrieve our saved SHA1 digest from saved copy and check digests.
+ This is just comparing the digest of the MF section as indicated in
+ the SF file with the one we remembered from parsing the MF file */
+
+ if (type == jarTypeSF) {
+ if ((status = jar_internal_digest(jar, path, x_name, dig)) < 0)
+ return status;
+ }
+ }
+
+ return 0;
+}
+
+static int
+jar_internal_digest(JAR *jar, const char *path, char *x_name, JAR_Digest *dig)
+{
+ int cv;
+ int status;
+
+ JAR_Digest *savdig;
+
+ savdig = jar_get_mf_digest(jar, x_name);
+ if (savdig == NULL) {
+ /* no .mf digest for this pathname */
+ status = jar_signal(JAR_ERR_ENTRY, jar, path, x_name);
+ if (status < 0)
+ return 0; /* was continue; */
+ return status;
+ }
+
+ /* check for md5 consistency */
+ if (dig->md5_status) {
+ cv = PORT_Memcmp(savdig->md5, dig->md5, MD5_LENGTH);
+ /* md5 hash of .mf file is not what expected */
+ if (cv) {
+ status = jar_signal(JAR_ERR_HASH, jar, path, x_name);
+
+ /* bad hash, man */
+ dig->md5_status = jarHashBad;
+ savdig->md5_status = jarHashBad;
+
+ if (status < 0)
+ return 0; /* was continue; */
+ return status;
+ }
+ }
+
+ /* check for sha1 consistency */
+ if (dig->sha1_status) {
+ cv = PORT_Memcmp(savdig->sha1, dig->sha1, SHA1_LENGTH);
+ /* sha1 hash of .mf file is not what expected */
+ if (cv) {
+ status = jar_signal(JAR_ERR_HASH, jar, path, x_name);
+
+ /* bad hash, man */
+ dig->sha1_status = jarHashBad;
+ savdig->sha1_status = jarHashBad;
+
+ if (status < 0)
+ return 0; /* was continue; */
+ return status;
+ }
+ }
+ return 0;
+}
+
+#ifdef DEBUG
+/*
+ * j a r _ i n s a n i t y _ c h e c k
+ *
+ * Check for illegal characters (or possibly so)
+ * in the manifest files, to detect potential memory
+ * corruption by our neighbors. Debug only, since
+ * not I18N safe.
+ *
+ */
+static int
+jar_insanity_check(char *data, long length)
+{
+ int c;
+ long off;
+
+ for (off = 0; off < length; off++) {
+ c = data[off];
+ if (c == '\n' || c == '\r' || (c >= ' ' && c <= 128))
+ continue;
+ return JAR_ERR_CORRUPT;
+ }
+ return 0;
+}
+#endif
+
+/*
+ * j a r _ p a r s e _ d i g i t a l _ s i g n a t u r e
+ *
+ * Parse an RSA or DSA (or perhaps other) digital signature.
+ * Right now everything is PKCS7.
+ *
+ */
+static int
+jar_parse_digital_signature(char *raw_manifest, JAR_Signer *signer,
+ long length, JAR *jar)
+{
+ return jar_validate_pkcs7(jar, signer, raw_manifest, length);
+}
+
+/*
+ * j a r _ a d d _ c e r t
+ *
+ * Add information for the given certificate
+ * (or whatever) to the JAR linked list. A pointer
+ * is passed for some relevant reference, say
+ * for example the original certificate.
+ *
+ */
+static int
+jar_add_cert(JAR *jar, JAR_Signer *signer, int type, CERTCertificate *cert)
+{
+ JAR_Cert *fing;
+ unsigned char *keyData;
+
+ if (cert == NULL)
+ return JAR_ERR_ORDER;
+
+ fing = PORT_ZNew(JAR_Cert);
+ if (fing == NULL)
+ goto loser;
+
+ fing->cert = CERT_DupCertificate(cert);
+
+ /* get the certkey */
+ fing->length = cert->derIssuer.len + 2 + cert->serialNumber.len;
+ fing->key = keyData = (unsigned char *)PORT_ZAlloc(fing->length);
+ if (fing->key == NULL)
+ goto loser;
+ keyData[0] = ((cert->derIssuer.len) >> 8) & 0xff;
+ keyData[1] = ((cert->derIssuer.len) & 0xff);
+ PORT_Memcpy(&keyData[2], cert->derIssuer.data, cert->derIssuer.len);
+ PORT_Memcpy(&keyData[2 + cert->derIssuer.len], cert->serialNumber.data,
+ cert->serialNumber.len);
+
+ ADDITEM(signer->certs, type, NULL, fing, sizeof(JAR_Cert));
+ return 0;
+
+loser:
+ if (fing) {
+ if (fing->cert)
+ CERT_DestroyCertificate(fing->cert);
+ PORT_Free(fing);
+ }
+ return JAR_ERR_MEMORY;
+}
+
+/*
+ * e a t _ l i n e
+ *
+ * Reads and/or modifies input buffer "data" of length "*len".
+ * This function does zero, one or two of the following tasks:
+ * 1) if "lines" is non-zero, it reads and discards that many lines from
+ * the input. NUL characters are treated as end-of-line characters,
+ * not as end-of-input characters. The input is NOT NUL terminated.
+ * Note: presently, all callers pass either 0 or 1 for lines.
+ * 2) After skipping the specified number of input lines, if "eating" is
+ * non-zero, it finds the end of the next line of input and replaces
+ * the end of line character(s) with a NUL character.
+ * This function modifies the input buffer, containing the file, in place.
+ * This function handles PC, Mac, and Unix style text files.
+ * On entry, *len contains the maximum number of characters that this
+ * function should ever examine, starting with the character in *data.
+ * On return, *len is reduced by the number of characters skipped by the
+ * first task, if any;
+ * If lines is zero and eating is false, this function returns
+ * the value in the data argument, but otherwise does nothing.
+ */
+static char *
+jar_eat_line(int lines, int eating, char *data, long *len)
+{
+ char *start = data;
+ long maxLen = *len;
+
+ if (maxLen <= 0)
+ return start;
+
+#define GO_ON ((data - start) < maxLen)
+
+ /* Eat the requisite number of lines, if any;
+ prior to terminating the current line with a 0. */
+ for (/* yip */; lines > 0; lines--) {
+ while (GO_ON && *data && *data != '\r' && *data != '\n')
+ data++;
+
+ /* Eat any leading CR */
+ if (GO_ON && *data == '\r')
+ data++;
+
+ /* After the CR, ok to eat one LF */
+ if (GO_ON && *data == '\n')
+ data++;
+
+ /* If there are NULs, this function probably put them there */
+ while (GO_ON && !*data)
+ data++;
+ }
+ maxLen -= data - start; /* we have this many characters left. */
+ *len = maxLen;
+ start = data; /* now start again here. */
+ if (maxLen > 0 && eating) {
+ /* Terminate this line with a 0 */
+ while (GO_ON && *data && *data != '\n' && *data != '\r')
+ data++;
+
+ /* If not past the end, we are allowed to eat one CR */
+ if (GO_ON && *data == '\r')
+ *data++ = 0;
+
+ /* After the CR (if any), if not past the end, ok to eat one LF */
+ if (GO_ON && *data == '\n')
+ *data++ = 0;
+ }
+ return start;
+}
+#undef GO_ON
+
+/*
+ * j a r _ d i g e s t _ s e c t i o n
+ *
+ * Return the digests of the next section of the manifest file.
+ * Does not damage the manifest file, unlike parse_manifest.
+ *
+ */
+static JAR_Digest *
+jar_digest_section(char *manifest, long length)
+{
+ long global_len;
+ char *global_end;
+
+ global_end = manifest;
+ global_len = length;
+
+ while (global_len > 0) {
+ global_end = jar_eat_line(1, PR_FALSE, global_end, &global_len);
+ if (global_len > 0 && (*global_end == 0 || *global_end == '\n'))
+ break;
+ }
+ return JAR_calculate_digest(manifest, global_end - manifest);
+}
+
+/*
+ * J A R _ v e r i f y _ d i g e s t
+ *
+ * Verifies that a precalculated digest matches the
+ * expected value in the manifest.
+ *
+ */
+int PR_CALLBACK
+JAR_verify_digest(JAR *jar, const char *name, JAR_Digest *dig)
+{
+ JAR_Item *it;
+ JAR_Digest *shindig;
+ ZZLink *link;
+ ZZList *list = jar->hashes;
+ int result1 = 0;
+ int result2 = 0;
+
+ if (jar->valid < 0) {
+ /* signature not valid */
+ return JAR_ERR_SIG;
+ }
+ if (ZZ_ListEmpty(list)) {
+ /* empty list */
+ return JAR_ERR_PNF;
+ }
+
+ for (link = ZZ_ListHead(list);
+ !ZZ_ListIterDone(list, link);
+ link = link->next) {
+ it = link->thing;
+ if (it->type == jarTypeMF &&
+ it->pathname && !PORT_Strcmp(it->pathname, name)) {
+ shindig = (JAR_Digest *)it->data;
+ if (shindig->md5_status) {
+ if (shindig->md5_status == jarHashBad)
+ return JAR_ERR_HASH;
+ result1 = memcmp(dig->md5, shindig->md5, MD5_LENGTH);
+ }
+ if (shindig->sha1_status) {
+ if (shindig->sha1_status == jarHashBad)
+ return JAR_ERR_HASH;
+ result2 = memcmp(dig->sha1, shindig->sha1, SHA1_LENGTH);
+ }
+ return (result1 == 0 && result2 == 0) ? 0 : JAR_ERR_HASH;
+ }
+ }
+ return JAR_ERR_PNF;
+}
+
+/*
+ * J A R _ f e t c h _ c e r t
+ *
+ * Given an opaque identifier of a certificate,
+ * return the full certificate.
+ *
+ * The new function, which retrieves by key.
+ *
+ */
+CERTCertificate *
+JAR_fetch_cert(long length, void *key)
+{
+ CERTIssuerAndSN issuerSN;
+ CERTCertificate *cert = NULL;
+ CERTCertDBHandle *certdb;
+
+ certdb = JAR_open_database();
+ if (certdb) {
+ unsigned char *keyData = (unsigned char *)key;
+ issuerSN.derIssuer.len = (keyData[0] << 8) + keyData[0];
+ issuerSN.derIssuer.data = &keyData[2];
+ issuerSN.serialNumber.len = length - (2 + issuerSN.derIssuer.len);
+ issuerSN.serialNumber.data = &keyData[2 + issuerSN.derIssuer.len];
+ cert = CERT_FindCertByIssuerAndSN(certdb, &issuerSN);
+ JAR_close_database(certdb);
+ }
+ return cert;
+}
+
+/*
+ * j a r _ g e t _ m f _ d i g e s t
+ *
+ * Retrieve a corresponding saved digest over a section
+ * of the main manifest file.
+ *
+ */
+static JAR_Digest *
+jar_get_mf_digest(JAR *jar, char *pathname)
+{
+ JAR_Item *it;
+ JAR_Digest *dig;
+ ZZLink *link;
+ ZZList *list = jar->manifest;
+
+ if (ZZ_ListEmpty(list))
+ return NULL;
+
+ for (link = ZZ_ListHead(list);
+ !ZZ_ListIterDone(list, link);
+ link = link->next) {
+ it = link->thing;
+ if (it->type == jarTypeSect &&
+ it->pathname && !PORT_Strcmp(it->pathname, pathname)) {
+ dig = (JAR_Digest *)it->data;
+ return dig;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * j a r _ b a s e n a m e
+ *
+ * Return the basename -- leading components of path stripped off,
+ * extension ripped off -- of a path.
+ *
+ */
+static char *
+jar_basename(const char *path)
+{
+ char *pith, *e, *basename, *ext;
+
+ if (path == NULL)
+ return PORT_Strdup("");
+
+ pith = PORT_Strdup(path);
+ basename = pith;
+ while (1) {
+ for (e = basename; *e && *e != '/' && *e != '\\'; e++)
+ /* yip */;
+ if (*e)
+ basename = ++e;
+ else
+ break;
+ }
+
+ if ((ext = PORT_Strrchr(basename, '.')) != NULL)
+ *ext = 0;
+
+ /* We already have the space allocated */
+ PORT_Strcpy(pith, basename);
+ return pith;
+}
+
+/*
+ * + + + + + + + + + + + + + + +
+ *
+ * CRYPTO ROUTINES FOR JAR
+ *
+ * The following functions are the cryptographic
+ * interface to PKCS7 for Jarnatures.
+ *
+ * + + + + + + + + + + + + + + +
+ *
+ */
+
+/*
+ * j a r _ c a t c h _ b y t e s
+ *
+ * In the event signatures contain enveloped data, it will show up here.
+ * But note that the lib/pkcs7 routines aren't ready for it.
+ *
+ */
+static void
+jar_catch_bytes(void *arg, const char *buf, unsigned long len)
+{
+ /* Actually this should never be called, since there is
+ presumably no data in the signature itself. */
+}
+
+/*
+ * j a r _ v a l i d a t e _ p k c s 7
+ *
+ * Validate (and decode, if necessary) a binary pkcs7
+ * signature in DER format.
+ *
+ */
+static int
+jar_validate_pkcs7(JAR *jar, JAR_Signer *signer, char *data, long length)
+{
+
+ SEC_PKCS7ContentInfo *cinfo = NULL;
+ SEC_PKCS7DecoderContext *dcx;
+ PRBool goodSig;
+ int status = 0;
+ SECItem detdig;
+
+ PORT_Assert(jar != NULL && signer != NULL);
+
+ if (jar == NULL || signer == NULL)
+ return JAR_ERR_ORDER;
+
+ signer->valid = JAR_ERR_SIG;
+
+ /* We need a context if we can get one */
+ dcx = SEC_PKCS7DecoderStart(jar_catch_bytes, NULL /*cb_arg*/,
+ NULL /*getpassword*/, jar->mw,
+ NULL, NULL, NULL);
+ if (dcx == NULL) {
+ /* strange pkcs7 failure */
+ return JAR_ERR_PK7;
+ }
+
+ SEC_PKCS7DecoderUpdate(dcx, data, length);
+ cinfo = SEC_PKCS7DecoderFinish(dcx);
+ if (cinfo == NULL) {
+ /* strange pkcs7 failure */
+ return JAR_ERR_PK7;
+ }
+ if (SEC_PKCS7ContentIsEncrypted(cinfo)) {
+ /* content was encrypted, fail */
+ return JAR_ERR_PK7;
+ }
+ if (SEC_PKCS7ContentIsSigned(cinfo) == PR_FALSE) {
+ /* content was not signed, fail */
+ return JAR_ERR_PK7;
+ }
+
+ PORT_SetError(0);
+
+ /* use SHA1 only */
+ detdig.len = SHA1_LENGTH;
+ detdig.data = signer->digest->sha1;
+ goodSig = SEC_PKCS7VerifyDetachedSignature(cinfo,
+ certUsageObjectSigner,
+ &detdig, HASH_AlgSHA1,
+ PR_FALSE);
+ jar_gather_signers(jar, signer, cinfo);
+ if (goodSig == PR_TRUE) {
+ /* signature is valid */
+ signer->valid = 0;
+ } else {
+ status = PORT_GetError();
+ PORT_Assert(status < 0);
+ if (status >= 0)
+ status = JAR_ERR_SIG;
+ jar->valid = status;
+ signer->valid = status;
+ }
+ jar->pkcs7 = PR_TRUE;
+ signer->pkcs7 = PR_TRUE;
+ SEC_PKCS7DestroyContentInfo(cinfo);
+ return status;
+}
+
+/*
+ * j a r _ g a t h e r _ s i g n e r s
+ *
+ * Add the single signer of this signature to the
+ * certificate linked list.
+ *
+ */
+static int
+jar_gather_signers(JAR *jar, JAR_Signer *signer, SEC_PKCS7ContentInfo *cinfo)
+{
+ int result;
+ CERTCertificate *cert;
+ CERTCertDBHandle *certdb;
+ SEC_PKCS7SignedData *sdp = cinfo->content.signedData;
+ SEC_PKCS7SignerInfo **pksigners, *pksigner;
+
+ if (sdp == NULL)
+ return JAR_ERR_PK7;
+
+ pksigners = sdp->signerInfos;
+ /* permit exactly one signer */
+ if (pksigners == NULL || pksigners[0] == NULL || pksigners[1] != NULL)
+ return JAR_ERR_PK7;
+
+ pksigner = *pksigners;
+ cert = pksigner->cert;
+
+ if (cert == NULL)
+ return JAR_ERR_PK7;
+
+ certdb = JAR_open_database();
+ if (certdb == NULL)
+ return JAR_ERR_GENERAL;
+
+ result = jar_add_cert(jar, signer, jarTypeSign, cert);
+ JAR_close_database(certdb);
+ return result;
+}
+
+/*
+ * j a r _ o p e n _ d a t a b a s e
+ *
+ * Open the certificate database,
+ * for use by JAR functions.
+ *
+ */
+CERTCertDBHandle *
+JAR_open_database(void)
+{
+ return CERT_GetDefaultCertDB();
+}
+
+/*
+ * j a r _ c l o s e _ d a t a b a s e
+ *
+ * Close the certificate database.
+ * For use by JAR functions.
+ *
+ */
+int
+JAR_close_database(CERTCertDBHandle *certdb)
+{
+ return 0;
+}
+
+/*
+ * j a r _ s i g n a l
+ *
+ * Nonfatal errors come here to callback Java.
+ *
+ */
+static int
+jar_signal(int status, JAR *jar, const char *metafile, char *pathname)
+{
+ char *errstring = JAR_get_error(status);
+ if (jar->signal) {
+ (*jar->signal)(status, jar, metafile, pathname, errstring);
+ return 0;
+ }
+ return status;
+}
+
+/*
+ * j a r _ a p p e n d
+ *
+ * Tack on an element to one of a JAR's linked
+ * lists, with rudimentary error handling.
+ *
+ */
+int
+jar_append(ZZList *list, int type, char *pathname, void *data, size_t size)
+{
+ JAR_Item *it = PORT_ZNew(JAR_Item);
+ ZZLink *entity;
+
+ if (it == NULL)
+ goto loser;
+
+ if (pathname) {
+ it->pathname = PORT_Strdup(pathname);
+ if (it->pathname == NULL)
+ goto loser;
+ }
+
+ it->type = (jarType)type;
+ it->data = (unsigned char *)data;
+ it->size = size;
+ entity = ZZ_NewLink(it);
+ if (entity) {
+ ZZ_AppendLink(list, entity);
+ return 0;
+ }
+
+loser:
+ if (it) {
+ if (it->pathname)
+ PORT_Free(it->pathname);
+ PORT_Free(it);
+ }
+ return JAR_ERR_MEMORY;
+}