mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
1885 lines
41 KiB
C
1885 lines
41 KiB
C
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* 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 the Initial Developer are Copyright (C) 1994-2000
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
/*
|
|
* JARVER
|
|
*
|
|
* Jarnature Parsing & Verification
|
|
*/
|
|
|
|
#include "nssrenam.h"
|
|
#include "jar.h"
|
|
#include "jarint.h"
|
|
#include "certdb.h"
|
|
#include "certt.h"
|
|
#include "secpkcs7.h"
|
|
|
|
/*#include "cdbhdl.h" */
|
|
#include "secder.h"
|
|
|
|
/* to use huge pointers in 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
|
|
|
|
/* from certdb.h */
|
|
#define CERTDB_USER (1<<6)
|
|
|
|
#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)
|
|
{
|
|
int filename_free = 0;
|
|
|
|
/* 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;
|
|
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 (!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;
|
|
|
|
/* 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 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 */
|
|
PORT_Free (met);
|
|
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 != jarTypeMF)
|
|
{
|
|
PORT_Free (met->header);
|
|
if (type != jarTypeSF)
|
|
{
|
|
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 );
|
|
|
|
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)
|
|
{
|
|
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 || !*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)
|
|
{
|
|
PORT_Free (dig);
|
|
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)
|
|
{
|
|
PORT_Free (dig);
|
|
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
|
|
{
|
|
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; */
|
|
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)
|
|
{
|
|
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;
|
|
|
|
fing->cert = CERT_DupCertificate (cert);
|
|
|
|
/* 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)
|
|
{
|
|
cert = CERT_FindCertByNickname (certdb, key);
|
|
|
|
if (cert)
|
|
{
|
|
*length = cert->certKey.len;
|
|
|
|
*result = (void *) PORT_ZAlloc (*length);
|
|
|
|
if (*result)
|
|
PORT_Memcpy (*result, cert->certKey.data, *length);
|
|
else
|
|
{
|
|
JAR_close_database (certdb);
|
|
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 */
|
|
|
|
if (CERT_FindCertByNickname (CERT_GetDefaultCertDB(), cert_cn) == NULL)
|
|
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);
|
|
|
|
if (CERT_FindCertByNickname (CERT_GetDefaultCertDB(), cert_cn_o) == NULL)
|
|
{
|
|
PORT_Free (cert_cn_o);
|
|
return cert_cn;
|
|
}
|
|
PORT_Free (cert_cn_o);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
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 = -1;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
|
|
newcert = CERT_FindCertByNickname (certdb, nickname);
|
|
|
|
if (newcert && newcert->isperm)
|
|
{
|
|
/* already in permanant database */
|
|
JAR_close_database (certdb);
|
|
return 0;
|
|
}
|
|
|
|
if (newcert) cert = newcert;
|
|
|
|
/* FIX, since FindCert returns a bogus dbhandle
|
|
set it ourselves */
|
|
|
|
cert->dbhandle = certdb;
|
|
|
|
if (nickname != NULL)
|
|
{
|
|
PORT_Memset ((void *) &trust, 0, sizeof(trust));
|
|
|
|
if (CERT_AddTempCertToPerm (cert, nickname, &trust) != SECSuccess)
|
|
{
|
|
/* 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];
|
|
|
|
cert = CERT_FindCertByIssuerAndSN (certdb, &issuerSN);
|
|
|
|
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)
|
|
{
|
|
|
|
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 */
|
|
|
|
#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;
|
|
|
|
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;
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|