summaryrefslogtreecommitdiff
path: root/security/nss/lib/jar/jarver.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/jar/jarver.c')
-rw-r--r--security/nss/lib/jar/jarver.c2031
1 files changed, 2031 insertions, 0 deletions
diff --git a/security/nss/lib/jar/jarver.c b/security/nss/lib/jar/jarver.c
new file mode 100644
index 000000000..be974a1dd
--- /dev/null
+++ b/security/nss/lib/jar/jarver.c
@@ -0,0 +1,2031 @@
+/*
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Netscape security libraries.
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation. Portions created by Netscape are
+ * Copyright (C) 1994-2000 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU General Public License Version 2 or later (the
+ * "GPL"), in which case the provisions of the GPL are applicable
+ * instead of those above. If you wish to allow use of your
+ * version of this file only under the terms of the GPL and not to
+ * allow others to use your version of this file under the MPL,
+ * indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by
+ * the GPL. If you do not delete the provisions above, a recipient
+ * may use your version of this file under either the MPL or the
+ * GPL.
+ */
+
+/*
+ * JARVER
+ *
+ * Jarnature Parsing & Verification
+ */
+
+#define USE_MOZ_THREAD
+
+#include "jar.h"
+#include "jarint.h"
+
+#ifdef USE_MOZ_THREAD
+#include "jarevil.h"
+#endif
+/*#include "cdbhdl.h" */
+#include "secder.h"
+
+/* to use huge pointers in win16 */
+
+#if !defined(XP_WIN16)
+#define xp_HUGE_MEMCPY PORT_Memcpy
+#define xp_HUGE_STRCPY PORT_Strcpy
+#define xp_HUGE_STRLEN PORT_Strlen
+#define xp_HUGE_STRNCASECMP PORT_Strncasecmp
+#else
+#define xp_HUGE_MEMCPY hmemcpy
+int xp_HUGE_STRNCASECMP (char ZHUGEP *buf, char *key, int len);
+size_t xp_HUGE_STRLEN (char ZHUGEP *s);
+char *xp_HUGE_STRCPY (char *to, char ZHUGEP *from);
+#endif
+
+/* from certdb.h */
+#define CERTDB_USER (1<<6)
+
+#if 0
+/* from certdb.h */
+extern PRBool SEC_CertNicknameConflict
+ (char *nickname, CERTCertDBHandle *handle);
+/* from certdb.h */
+extern SECStatus SEC_AddTempNickname
+ (CERTCertDBHandle *handle, char *nickname, SECItem *certKey);
+/* from certdb.h */
+typedef SECStatus (* PermCertCallback)(CERTCertificate *cert, SECItem *k, void *pdata);
+
+/* from certdb.h */
+SECStatus SEC_TraversePermCerts
+ (CERTCertDBHandle *handle, PermCertCallback certfunc, void *udata);
+#endif
+
+
+
+#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 ZHUGEP *jar_eat_line
+ (int lines, int eating, char ZHUGEP *data, long *len);
+
+static JAR_Digest *jar_digest_section
+ (char ZHUGEP *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 CERTCertificate *jar_get_certificate
+ (JAR *jar, long keylen, void *key, int *result);
+
+static char *jar_cert_element (char *name, char *tag, int occ);
+
+static char *jar_choose_nickname (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 ZHUGEP *data, long length);
+#endif
+
+int jar_parse_mf
+ (JAR *jar, char ZHUGEP *raw_manifest,
+ long length, const char *path, const char *url);
+
+int jar_parse_sf
+ (JAR *jar, char ZHUGEP *raw_manifest,
+ long length, const char *path, const char *url);
+
+int jar_parse_sig
+ (JAR *jar, const char *path, char ZHUGEP *raw_manifest, long length);
+
+int jar_parse_any
+ (JAR *jar, int type, JAR_Signer *signer, char ZHUGEP *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 ZHUGEP *raw_manifest,
+ long length, const char *path, const char *url)
+ {
+
+#if defined(XP_WIN16)
+ PORT_Assert( !IsBadHugeReadPtr(raw_manifest, length) );
+#endif
+
+ /* fill in the path, if supplied. This is a 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;
+ }
+
+ /* 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)
+ 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 (!xp_HUGE_STRNCASECMP (raw_manifest, "Manifest-Version:", 17))
+ {
+ return jar_parse_mf (jar, raw_manifest, length, path, url);
+ }
+ else if (!xp_HUGE_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 ZHUGEP *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;
+
+#if 0
+ /* XXX Turn this on to disable multiple signers */
+ if (jar->digest == NULL)
+ return JAR_ERR_ORDER;
+#endif
+
+ /* 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;
+ }
+
+#ifdef XP_WIN16
+ /*
+ * For Win16, copy the portion of the raw_buffer containing the digital
+ * signature into another buffer... This insures that the data will
+ * NOT cross a segment boundary. Therefore,
+ * jar_parse_digital_signature(...) does NOT need to deal with HUGE
+ * pointers...
+ */
+
+ {
+ unsigned char *manifest_copy;
+
+ manifest_copy = (unsigned char *) PORT_ZAlloc (length);
+ if (manifest_copy)
+ {
+ xp_HUGE_MEMCPY (manifest_copy, raw_manifest, length);
+
+ status = jar_parse_digital_signature
+ (manifest_copy, signer, length, jar);
+
+ PORT_Free (manifest_copy);
+ }
+ else
+ {
+ /* out of memory */
+ return JAR_ERR_MEMORY;
+ }
+ }
+#else
+ /* don't expense unneeded calloc overhead on non-win16 */
+ status = jar_parse_digital_signature
+ (raw_manifest, signer, length, jar);
+#endif
+
+ 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 ZHUGEP *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 ZHUGEP *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 ZHUGEP *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)
+ {
+ JAR_Metainfo *met;
+
+ raw_manifest = jar_eat_line (1, PR_TRUE, raw_manifest, &raw_len);
+ if (!*raw_manifest) break;
+
+ met = (JAR_Metainfo*)PORT_ZAlloc (sizeof (JAR_Metainfo));
+ if (met == NULL)
+ return JAR_ERR_MEMORY;
+
+ /* Parse out the header & info */
+
+ if (xp_HUGE_STRLEN (raw_manifest) >= SZ)
+ {
+ /* almost certainly nonsense */
+ continue;
+ }
+
+ xp_HUGE_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;
+
+ if (!PORT_Strcasecmp (line, "SHA1-Digest") || !PORT_Strcasecmp (line, "SHA-Digest"))
+ sf_sha1 = (char *) met->info;
+ }
+ }
+
+ 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 );
+
+ if (md5_length != MD5_LENGTH)
+ return JAR_ERR_CORRUPT;
+
+ match = PORT_Memcmp (md5_digest, glob->md5, MD5_LENGTH);
+ }
+
+ 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 );
+
+ if (sha1_length != SHA1_LENGTH)
+ return JAR_ERR_CORRUPT;
+
+ match = PORT_Memcmp (sha1_digest, glob->sha1, SHA1_LENGTH);
+ }
+
+ 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)
+ {
+ *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 ZHUGEP *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 (!xp_HUGE_STRNCASECMP (sec, "Name:", 5))
+ {
+ if (type == jarTypeMF)
+ mfdig = jar_digest_section (sec, sec_len);
+ else
+ mfdig = NULL;
+ }
+ }
+
+
+ while (raw_len)
+ {
+ raw_manifest = jar_eat_line (1, PR_TRUE, raw_manifest, &raw_len);
+ if (!*raw_manifest) break; /* blank line, done with this entry */
+
+ if (xp_HUGE_STRLEN (raw_manifest) >= SZ)
+ {
+ /* almost certainly nonsense */
+ continue;
+ }
+
+
+ /* Parse out the name/value pair */
+
+ xp_HUGE_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 = (JAR_Metainfo*)PORT_ZAlloc (sizeof (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)
+ return JAR_ERR_MEMORY;
+
+ if ((met->info = PORT_Strdup (x_info)) == NULL)
+ return JAR_ERR_MEMORY;
+
+ ADDITEM (jar->metainfo, jarTypeMeta,
+ x_name, met, sizeof (JAR_Metainfo));
+ }
+ }
+
+ if(!x_name || !*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 = (JAR_Digest*)PORT_ZAlloc (sizeof (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)
+ return JAR_ERR_CORRUPT;
+
+ memcpy (dig->md5, binary_digest, MD5_LENGTH);
+ dig->md5_status = jarHashPresent;
+ }
+
+ 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)
+ return JAR_ERR_CORRUPT;
+
+ memcpy (dig->sha1, binary_digest, SHA1_LENGTH);
+ dig->sha1_status = jarHashPresent;
+ }
+
+ 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
+ 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; */
+ else
+ 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; */
+ else
+ 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; */
+ else
+ 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 ZHUGEP *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)
+ {
+#if defined(XP_WIN16)
+ PORT_Assert( LOWORD(raw_manifest) + length < 0xFFFF );
+#endif
+ 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 = (JAR_Cert*)PORT_ZAlloc (sizeof (JAR_Cert));
+
+ if (fing == NULL)
+ goto loser;
+
+#ifdef USE_MOZ_THREAD
+ fing->cert = jar_moz_dup (cert);
+#else
+ fing->cert = CERT_DupCertificate (cert);
+#endif
+
+ /* get the certkey */
+
+ fing->length = cert->derIssuer.len + 2 + cert->serialNumber.len;
+
+ keyData = (unsigned char *) PORT_ZAlloc (fing->length);
+ fing->key = keyData;
+
+ 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,
+ /* pathname */ 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
+ *
+ * Consume an ascii line from the top of a file kept
+ * in memory. This destroys the file in place. This function
+ * handles PC, Mac, and Unix style text files.
+ *
+ */
+
+static char ZHUGEP *jar_eat_line
+ (int lines, int eating, char ZHUGEP *data, long *len)
+ {
+ char ZHUGEP *ret;
+
+ ret = data;
+ if (!*len) return ret;
+
+ /* Eat the requisite number of lines, if any;
+ prior to terminating the current line with a 0. */
+
+ for (/* yip */ ; lines; lines--)
+ {
+ while (*data && *data != '\n')
+ data++;
+
+ /* After the CR, ok to eat one LF */
+
+ if (*data == '\n')
+ data++;
+
+ /* If there are zeros, we put them there */
+
+ while (*data == 0 && data - ret < *len)
+ data++;
+ }
+
+ *len -= data - ret;
+ ret = data;
+
+ if (eating)
+ {
+ /* Terminate this line with a 0 */
+
+ while (*data && *data != '\n' && *data != '\r')
+ data++;
+
+ /* In any case we are allowed to eat CR */
+
+ if (*data == '\r')
+ *data++ = 0;
+
+ /* After the CR, ok to eat one LF */
+
+ if (*data == '\n')
+ *data++ = 0;
+ }
+
+ return ret;
+ }
+
+/*
+ * 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 ZHUGEP *manifest, long length)
+ {
+ long global_len;
+ char ZHUGEP *global_end;
+
+ global_end = manifest;
+ global_len = length;
+
+ while (global_len)
+ {
+ global_end = jar_eat_line (1, PR_FALSE, global_end, &global_len);
+ if (*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;
+
+ int result1, result2;
+
+ list = jar->hashes;
+
+ result1 = 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;
+ else
+ result1 = memcmp (dig->md5, shindig->md5, MD5_LENGTH);
+ }
+
+ if (shindig->sha1_status)
+ {
+ if (shindig->sha1_status == jarHashBad)
+ return JAR_ERR_HASH;
+ else
+ result2 = memcmp (dig->sha1, shindig->sha1, SHA1_LENGTH);
+ }
+
+ return (result1 == 0 && result2 == 0) ? 0 : JAR_ERR_HASH;
+ }
+ }
+
+ return JAR_ERR_PNF;
+ }
+
+/*
+ * J A R _ c e r t _ a t t r i b u t e
+ *
+ * Return the named certificate attribute from the
+ * certificate specified by the given key.
+ *
+ */
+
+int PR_CALLBACK JAR_cert_attribute
+ (JAR *jar, jarCert attrib, long keylen, void *key,
+ void **result, unsigned long *length)
+ {
+ int status = 0;
+ char *ret = NULL;
+
+ CERTCertificate *cert;
+
+ CERTCertDBHandle *certdb;
+
+ JAR_Digest *dig;
+ SECItem hexme;
+
+ *length = 0;
+
+ if (attrib == 0 || key == 0)
+ return JAR_ERR_GENERAL;
+
+ if (attrib == jarCertJavaHack)
+ {
+ cert = (CERTCertificate *) NULL;
+ certdb = JAR_open_database();
+
+ if (certdb)
+ {
+#ifdef USE_MOZ_THREAD
+ cert = jar_moz_nickname (certdb, (char*)key);
+#else
+ cert = CERT_FindCertByNickname (certdb, key);
+#endif
+
+ if (cert)
+ {
+ *length = cert->certKey.len;
+
+ *result = (void *) PORT_ZAlloc (*length);
+
+ if (*result)
+ PORT_Memcpy (*result, cert->certKey.data, *length);
+ else
+ return JAR_ERR_MEMORY;
+ }
+ JAR_close_database (certdb);
+ }
+
+ return cert ? 0 : JAR_ERR_GENERAL;
+ }
+
+ if (jar && jar->pkcs7 == 0)
+ return JAR_ERR_GENERAL;
+
+ cert = jar_get_certificate (jar, keylen, key, &status);
+
+ if (cert == NULL || status < 0)
+ return JAR_ERR_GENERAL;
+
+#define SEP " <br> "
+#define SEPLEN (PORT_Strlen(SEP))
+
+ switch (attrib)
+ {
+ case jarCertCompany:
+
+ ret = cert->subjectName;
+
+ /* This is pretty ugly looking but only used
+ here for this one purpose. */
+
+ if (ret)
+ {
+ int retlen = 0;
+
+ char *cer_ou1, *cer_ou2, *cer_ou3;
+ char *cer_cn, *cer_e, *cer_o, *cer_l;
+
+ cer_cn = CERT_GetCommonName (&cert->subject);
+ cer_e = CERT_GetCertEmailAddress (&cert->subject);
+ cer_ou3 = jar_cert_element (ret, "OU=", 3);
+ cer_ou2 = jar_cert_element (ret, "OU=", 2);
+ cer_ou1 = jar_cert_element (ret, "OU=", 1);
+ cer_o = CERT_GetOrgName (&cert->subject);
+ cer_l = CERT_GetCountryName (&cert->subject);
+
+ if (cer_cn) retlen += SEPLEN + PORT_Strlen (cer_cn);
+ if (cer_e) retlen += SEPLEN + PORT_Strlen (cer_e);
+ if (cer_ou1) retlen += SEPLEN + PORT_Strlen (cer_ou1);
+ if (cer_ou2) retlen += SEPLEN + PORT_Strlen (cer_ou2);
+ if (cer_ou3) retlen += SEPLEN + PORT_Strlen (cer_ou3);
+ if (cer_o) retlen += SEPLEN + PORT_Strlen (cer_o);
+ if (cer_l) retlen += SEPLEN + PORT_Strlen (cer_l);
+
+ ret = (char *) PORT_ZAlloc (1 + retlen);
+
+ if (cer_cn) { PORT_Strcpy (ret, cer_cn); PORT_Strcat (ret, SEP); }
+ if (cer_e) { PORT_Strcat (ret, cer_e); PORT_Strcat (ret, SEP); }
+ if (cer_ou1) { PORT_Strcat (ret, cer_ou1); PORT_Strcat (ret, SEP); }
+ if (cer_ou2) { PORT_Strcat (ret, cer_ou2); PORT_Strcat (ret, SEP); }
+ if (cer_ou3) { PORT_Strcat (ret, cer_ou3); PORT_Strcat (ret, SEP); }
+ if (cer_o) { PORT_Strcat (ret, cer_o); PORT_Strcat (ret, SEP); }
+ if (cer_l) PORT_Strcat (ret, cer_l);
+
+ /* return here to avoid unsightly memory leak */
+
+ *result = ret;
+ *length = PORT_Strlen (ret);
+
+ return 0;
+ }
+ break;
+
+ case jarCertCA:
+
+ ret = cert->issuerName;
+
+ if (ret)
+ {
+ int retlen = 0;
+
+ char *cer_ou1, *cer_ou2, *cer_ou3;
+ char *cer_cn, *cer_e, *cer_o, *cer_l;
+
+ /* This is pretty ugly looking but only used
+ here for this one purpose. */
+
+ cer_cn = CERT_GetCommonName (&cert->issuer);
+ cer_e = CERT_GetCertEmailAddress (&cert->issuer);
+ cer_ou3 = jar_cert_element (ret, "OU=", 3);
+ cer_ou2 = jar_cert_element (ret, "OU=", 2);
+ cer_ou1 = jar_cert_element (ret, "OU=", 1);
+ cer_o = CERT_GetOrgName (&cert->issuer);
+ cer_l = CERT_GetCountryName (&cert->issuer);
+
+ if (cer_cn) retlen += SEPLEN + PORT_Strlen (cer_cn);
+ if (cer_e) retlen += SEPLEN + PORT_Strlen (cer_e);
+ if (cer_ou1) retlen += SEPLEN + PORT_Strlen (cer_ou1);
+ if (cer_ou2) retlen += SEPLEN + PORT_Strlen (cer_ou2);
+ if (cer_ou3) retlen += SEPLEN + PORT_Strlen (cer_ou3);
+ if (cer_o) retlen += SEPLEN + PORT_Strlen (cer_o);
+ if (cer_l) retlen += SEPLEN + PORT_Strlen (cer_l);
+
+ ret = (char *) PORT_ZAlloc (1 + retlen);
+
+ if (cer_cn) { PORT_Strcpy (ret, cer_cn); PORT_Strcat (ret, SEP); }
+ if (cer_e) { PORT_Strcat (ret, cer_e); PORT_Strcat (ret, SEP); }
+ if (cer_ou1) { PORT_Strcat (ret, cer_ou1); PORT_Strcat (ret, SEP); }
+ if (cer_ou2) { PORT_Strcat (ret, cer_ou2); PORT_Strcat (ret, SEP); }
+ if (cer_ou3) { PORT_Strcat (ret, cer_ou3); PORT_Strcat (ret, SEP); }
+ if (cer_o) { PORT_Strcat (ret, cer_o); PORT_Strcat (ret, SEP); }
+ if (cer_l) PORT_Strcat (ret, cer_l);
+
+ /* return here to avoid unsightly memory leak */
+
+ *result = ret;
+ *length = PORT_Strlen (ret);
+
+ return 0;
+ }
+
+ break;
+
+ case jarCertSerial:
+
+ ret = CERT_Hexify (&cert->serialNumber, 1);
+ break;
+
+ case jarCertExpires:
+
+ ret = DER_UTCDayToAscii (&cert->validity.notAfter);
+ break;
+
+ case jarCertNickname:
+
+ ret = jar_choose_nickname (cert);
+ break;
+
+ case jarCertFinger:
+
+ dig = JAR_calculate_digest
+ ((char *) cert->derCert.data, cert->derCert.len);
+
+ if (dig)
+ {
+ hexme.len = sizeof (dig->md5);
+ hexme.data = dig->md5;
+ ret = CERT_Hexify (&hexme, 1);
+ }
+ break;
+
+ default:
+
+ return JAR_ERR_GENERAL;
+ }
+
+ *result = ret ? PORT_Strdup (ret) : NULL;
+ *length = ret ? PORT_Strlen (ret) : 0;
+
+ return 0;
+ }
+
+/*
+ * j a r _ c e r t _ e l e m e n t
+ *
+ * Retrieve an element from an x400ish ascii
+ * designator, in a hackish sort of way. The right
+ * thing would probably be to sort AVATags.
+ *
+ */
+
+static char *jar_cert_element (char *name, char *tag, int occ)
+ {
+ if (name && tag)
+ {
+ char *s;
+ int found = 0;
+
+ while (occ--)
+ {
+ if (PORT_Strstr (name, tag))
+ {
+ name = PORT_Strstr (name, tag) + PORT_Strlen (tag);
+ found = 1;
+ }
+ else
+ {
+ name = PORT_Strstr (name, "=");
+ if (name == NULL) return NULL;
+ found = 0;
+ }
+ }
+
+ if (!found) return NULL;
+
+ /* must mangle only the copy */
+ name = PORT_Strdup (name);
+
+ /* advance to next equal */
+ for (s = name; *s && *s != '='; s++)
+ /* yip */ ;
+
+ /* back up to previous comma */
+ while (s > name && *s != ',') s--;
+
+ /* zap the whitespace and return */
+ *s = 0;
+ }
+
+ return name;
+ }
+
+/*
+ * j a r _ c h o o s e _ n i c k n a m e
+ *
+ * Attempt to determine a suitable nickname for
+ * a certificate with a computer-generated "tmpcertxxx"
+ * nickname. It needs to be something a user can
+ * understand, so try a few things.
+ *
+ */
+
+static char *jar_choose_nickname (CERTCertificate *cert)
+ {
+ char *cert_cn;
+ char *cert_o;
+ char *cert_cn_o;
+
+ int cn_o_length;
+
+ /* is the existing name ok */
+
+ if (cert->nickname && PORT_Strncmp (cert->nickname, "tmpcert", 7))
+ return PORT_Strdup (cert->nickname);
+
+ /* we have an ugly name here people */
+
+ /* Try the CN */
+ cert_cn = CERT_GetCommonName (&cert->subject);
+
+ if (cert_cn)
+ {
+ /* check for duplicate nickname */
+
+#ifdef USE_MOZ_THREAD
+ if (jar_moz_nickname (CERT_GetDefaultCertDB(), cert_cn) == NULL)
+#else
+ if (CERT_FindCertByNickname (CERT_GetDefaultCertDB(), cert_cn) == NULL)
+#endif
+ return cert_cn;
+
+ /* Try the CN plus O */
+ cert_o = CERT_GetOrgName (&cert->subject);
+
+ cn_o_length = PORT_Strlen (cert_cn) + 3 + PORT_Strlen (cert_o) + 20;
+ cert_cn_o = (char*)PORT_ZAlloc (cn_o_length);
+
+ PR_snprintf (cert_cn_o, cn_o_length,
+ "%s's %s Certificate", cert_cn, cert_o);
+
+#ifdef USE_MOZ_THREAD
+ if (jar_moz_nickname (CERT_GetDefaultCertDB(), cert_cn_o) == NULL)
+#else
+ if (CERT_FindCertByNickname (CERT_GetDefaultCertDB(), cert_cn_o) == NULL)
+#endif
+ return cert_cn;
+ }
+
+ /* If all that failed, use the ugly nickname */
+ return cert->nickname ? PORT_Strdup (cert->nickname) : NULL;
+ }
+
+/*
+ * J A R _ c e r t _ h t m l
+ *
+ * Return an HTML representation of the certificate
+ * designated by the given fingerprint, in specified style.
+ *
+ * JAR is optional, but supply it if you can in order
+ * to optimize.
+ *
+ */
+
+char *JAR_cert_html
+ (JAR *jar, int style, long keylen, void *key, int *result)
+ {
+ char *html;
+ CERTCertificate *cert;
+
+ *result = -1;
+
+ if (style != 0)
+ return NULL;
+
+ cert = jar_get_certificate (jar, keylen, key, result);
+
+ if (cert == NULL || *result < 0)
+ return NULL;
+
+ *result = 0;
+
+ html = CERT_HTMLCertInfo (cert, /* show images */ PR_TRUE,
+ /*show issuer*/PR_TRUE);
+
+ if (html == NULL)
+ *result = -1;
+
+ return html;
+ }
+
+/*
+ * J A R _ s t a s h _ c e r t
+ *
+ * Stash the certificate pointed to by this
+ * fingerprint, in persistent storage somewhere.
+ *
+ */
+
+extern int PR_CALLBACK JAR_stash_cert
+ (JAR *jar, long keylen, void *key)
+ {
+ int result = 0;
+
+ char *nickname;
+ CERTCertTrust trust;
+
+ CERTCertDBHandle *certdb;
+ CERTCertificate *cert, *newcert;
+
+ cert = jar_get_certificate (jar, keylen, key, &result);
+
+ if (result < 0)
+ return result;
+
+ if (cert == NULL)
+ return JAR_ERR_GENERAL;
+
+ if ((certdb = JAR_open_database()) == NULL)
+ return JAR_ERR_GENERAL;
+
+ /* Attempt to give a name to the newish certificate */
+ nickname = jar_choose_nickname (cert);
+
+#ifdef USE_MOZ_THREAD
+ newcert = jar_moz_nickname (certdb, nickname);
+#else
+ newcert = CERT_FindCertByNickname (certdb, nickname);
+#endif
+
+ if (newcert && newcert->isperm)
+ {
+ /* already in permanant database */
+ return 0;
+ }
+
+ if (newcert) cert = newcert;
+
+ /* FIX, since FindCert returns a bogus dbhandle
+ set it ourselves */
+
+ cert->dbhandle = certdb;
+
+#if 0
+ nickname = cert->subjectName;
+ if (nickname)
+ {
+ /* Not checking for a conflict here. But this should
+ be a new cert or it would have been found earlier. */
+
+ nickname = jar_cert_element (nickname, "CN=", 1);
+
+ if (SEC_CertNicknameConflict (nickname, cert->dbhandle))
+ {
+ /* conflict */
+ nickname = PORT_Realloc (&nickname, PORT_Strlen (nickname) + 3);
+
+ /* Beyond one copy, there are probably serious problems
+ so we will stop at two rather than counting.. */
+
+ PORT_Strcat (nickname, " #2");
+ }
+ }
+#endif
+
+ if (nickname != NULL)
+ {
+ PORT_Memset ((void *) &trust, 0, sizeof(trust));
+
+#ifdef USE_MOZ_THREAD
+ if (jar_moz_perm (cert, nickname, &trust) != SECSuccess)
+#else
+ if (CERT_AddTempCertToPerm (cert, nickname, &trust) != SECSuccess)
+#endif
+ {
+ /* XXX might want to call PORT_GetError here */
+ result = JAR_ERR_GENERAL;
+ }
+ }
+
+ JAR_close_database (certdb);
+
+ return result;
+ }
+
+/*
+ * 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.
+ *
+ */
+
+void *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];
+
+#ifdef USE_MOZ_THREAD
+ cert = jar_moz_certkey (certdb, &issuerSN);
+#else
+ cert = CERT_FindCertByIssuerAndSN (certdb, &issuerSN);
+#endif
+
+ JAR_close_database (certdb);
+ }
+
+ return (void *) 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;
+
+ 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)
+ {
+ SECItem detdig;
+
+ SEC_PKCS7ContentInfo *cinfo = NULL;
+ SEC_PKCS7DecoderContext *dcx;
+
+ int status = 0;
+ char *errstring = NULL;
+
+ 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 */
+
+#ifdef MOZILLA_CLIENT_OLD
+ if (jar->mw == NULL) {
+ JAR_set_context (jar, NULL);
+ }
+#endif
+
+
+ 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;
+
+#ifdef USE_MOZ_THREAD
+ if (jar_moz_verify
+ (cinfo, certUsageObjectSigner, &detdig, HASH_AlgSHA1, PR_FALSE)==
+ SECSuccess)
+#else
+ if (SEC_PKCS7VerifyDetachedSignature
+ (cinfo, certUsageObjectSigner, &detdig, HASH_AlgSHA1, PR_FALSE)==
+ PR_TRUE)
+#endif
+ {
+ /* signature is valid */
+ signer->valid = 0;
+ jar_gather_signers (jar, signer, cinfo);
+ }
+ else
+ {
+ status = PORT_GetError();
+
+ PORT_Assert( status < 0 );
+ if (status >= 0) status = JAR_ERR_SIG;
+
+ jar->valid = status;
+ signer->valid = status;
+
+ errstring = JAR_get_error (status);
+ /*XP_TRACE(("JAR signature invalid (reason %d = %s)", status, errstring));*/
+ }
+
+ 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;
+ SEC_PKCS7SignerInfo **pksigners, *pksigner;
+
+ sdp = cinfo->content.signedData;
+
+ 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)
+ {
+ CERTCertDBHandle *certdb;
+
+ certdb = CERT_GetDefaultCertDB();
+
+ return certdb;
+ }
+
+/*
+ * 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)
+ {
+#ifdef notdef
+ CERTCertDBHandle *defaultdb;
+
+ /* This really just retrieves the handle, nothing more */
+ defaultdb = CERT_GetDefaultCertDB();
+
+ /* If there is no default db, it means we opened
+ the permanent database for some reason */
+
+ if (defaultdb == NULL && certdb != NULL)
+ CERT_ClosePermCertDB (certdb);
+#endif
+
+ return 0;
+ }
+
+/*
+ * j a r _ g e t _ c e r t i f i c a t e
+ *
+ * Return the certificate referenced
+ * by a given fingerprint, or NULL if not found.
+ * Error code is returned in result.
+ *
+ */
+
+static CERTCertificate *jar_get_certificate
+ (JAR *jar, long keylen, void *key, int *result)
+ {
+ int found = 0;
+
+ JAR_Item *it;
+ JAR_Cert *fing = NULL;
+
+ JAR_Context *ctx;
+
+ if (jar == NULL)
+ {
+ void *cert;
+ cert = JAR_fetch_cert (keylen, key);
+ *result = (cert == NULL) ? JAR_ERR_GENERAL : 0;
+ return (CERTCertificate *) cert;
+ }
+
+ ctx = JAR_find (jar, NULL, jarTypeSign);
+
+ while (JAR_find_next (ctx, &it) >= 0)
+ {
+ fing = (JAR_Cert *) it->data;
+
+ if (keylen != fing->length)
+ continue;
+
+ PORT_Assert( keylen < 0xFFFF );
+ if (!PORT_Memcmp (fing->key, key, keylen))
+ {
+ found = 1;
+ break;
+ }
+ }
+
+ JAR_find_end (ctx);
+
+ if (found == 0)
+ {
+ *result = JAR_ERR_GENERAL;
+ return NULL;
+ }
+
+ PORT_Assert(fing != NULL);
+ *result = 0;
+ return fing->cert;
+ }
+
+/*
+ * 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;
+
+ 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;
+ ZZLink *entity;
+
+ it = (JAR_Item*)PORT_ZAlloc (sizeof (JAR_Item));
+
+ 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;
+ }
+
+/*
+ * W I N 1 6 s t u f f
+ *
+ * These functions possibly belong in xp_mem.c, they operate
+ * on huge string pointers for win16.
+ *
+ */
+
+#if defined(XP_WIN16)
+int xp_HUGE_STRNCASECMP (char ZHUGEP *buf, char *key, int len)
+ {
+ while (len--)
+ {
+ char c1, c2;
+
+ c1 = *buf++;
+ c2 = *key++;
+
+ if (c1 >= 'a' && c1 <= 'z') c1 -= ('a' - 'A');
+ if (c2 >= 'a' && c2 <= 'z') c2 -= ('a' - 'A');
+
+ if (c1 != c2)
+ return (c1 < c2) ? -1 : 1;
+ }
+ return 0;
+ }
+
+size_t xp_HUGE_STRLEN (char ZHUGEP *s)
+ {
+ size_t len = 0L;
+ while (*s++) len++;
+ return len;
+ }
+
+char *xp_HUGE_STRCPY (char *to, char ZHUGEP *from)
+ {
+ char *ret = to;
+
+ while (*from)
+ *to++ = *from++;
+ *to = 0;
+
+ return ret;
+ }
+#endif