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