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: * JAR.C michael@0: * michael@0: * Jarnature. michael@0: * Routines common to signing and validating. michael@0: * michael@0: */ michael@0: michael@0: #include "jar.h" michael@0: #include "jarint.h" michael@0: #include "portreg.h" michael@0: michael@0: static void michael@0: jar_destroy_list (ZZList *list); michael@0: michael@0: static int michael@0: jar_find_first_cert(JAR_Signer *signer, int type, JAR_Item **it); michael@0: michael@0: /* michael@0: * J A R _ n e w michael@0: * michael@0: * Create a new instantiation of a manifest representation. michael@0: * Use this as a token to any calls to this API. michael@0: * michael@0: */ michael@0: JAR * michael@0: JAR_new(void) michael@0: { michael@0: JAR *jar; michael@0: michael@0: if ((jar = (JAR*)PORT_ZAlloc (sizeof (JAR))) == NULL) michael@0: goto loser; michael@0: if ((jar->manifest = ZZ_NewList()) == NULL) michael@0: goto loser; michael@0: if ((jar->hashes = ZZ_NewList()) == NULL) michael@0: goto loser; michael@0: if ((jar->phy = ZZ_NewList()) == NULL) michael@0: goto loser; michael@0: if ((jar->metainfo = ZZ_NewList()) == NULL) michael@0: goto loser; michael@0: if ((jar->signers = ZZ_NewList()) == NULL) michael@0: goto loser; michael@0: return jar; michael@0: michael@0: loser: michael@0: if (jar) { michael@0: if (jar->manifest) michael@0: ZZ_DestroyList (jar->manifest); michael@0: if (jar->hashes) michael@0: ZZ_DestroyList (jar->hashes); michael@0: if (jar->phy) michael@0: ZZ_DestroyList (jar->phy); michael@0: if (jar->metainfo) michael@0: ZZ_DestroyList (jar->metainfo); michael@0: if (jar->signers) michael@0: ZZ_DestroyList (jar->signers); michael@0: PORT_Free (jar); michael@0: } michael@0: return NULL; michael@0: } michael@0: michael@0: /* michael@0: * J A R _ d e s t r o y michael@0: */ michael@0: void PR_CALLBACK michael@0: JAR_destroy(JAR *jar) michael@0: { michael@0: PORT_Assert( jar != NULL ); michael@0: michael@0: if (jar == NULL) michael@0: return; michael@0: michael@0: if (jar->fp) michael@0: JAR_FCLOSE ((PRFileDesc*)jar->fp); michael@0: if (jar->url) michael@0: PORT_Free (jar->url); michael@0: if (jar->filename) michael@0: PORT_Free (jar->filename); michael@0: michael@0: /* Free the linked list elements */ michael@0: jar_destroy_list (jar->manifest); michael@0: ZZ_DestroyList (jar->manifest); michael@0: jar_destroy_list (jar->hashes); michael@0: ZZ_DestroyList (jar->hashes); michael@0: jar_destroy_list (jar->phy); michael@0: ZZ_DestroyList (jar->phy); michael@0: jar_destroy_list (jar->metainfo); michael@0: ZZ_DestroyList (jar->metainfo); michael@0: jar_destroy_list (jar->signers); michael@0: ZZ_DestroyList (jar->signers); michael@0: PORT_Free (jar); michael@0: } michael@0: michael@0: static void michael@0: jar_destroy_list(ZZList *list) michael@0: { michael@0: ZZLink *link, *oldlink; michael@0: JAR_Item *it; michael@0: JAR_Physical *phy; michael@0: JAR_Digest *dig; michael@0: JAR_Cert *fing; michael@0: JAR_Metainfo *met; michael@0: JAR_Signer *signer; michael@0: michael@0: if (list && !ZZ_ListEmpty (list)) { michael@0: link = ZZ_ListHead (list); michael@0: while (!ZZ_ListIterDone (list, link)) { michael@0: it = link->thing; michael@0: if (!it) michael@0: goto next; michael@0: if (it->pathname) michael@0: PORT_Free (it->pathname); michael@0: michael@0: switch (it->type) { michael@0: case jarTypeMeta: michael@0: met = (JAR_Metainfo *) it->data; michael@0: if (met) { michael@0: if (met->header) michael@0: PORT_Free (met->header); michael@0: if (met->info) michael@0: PORT_Free (met->info); michael@0: PORT_Free (met); michael@0: } michael@0: break; michael@0: michael@0: case jarTypePhy: michael@0: phy = (JAR_Physical *) it->data; michael@0: if (phy) michael@0: PORT_Free (phy); michael@0: break; michael@0: michael@0: case jarTypeSign: michael@0: fing = (JAR_Cert *) it->data; michael@0: if (fing) { michael@0: if (fing->cert) michael@0: CERT_DestroyCertificate (fing->cert); michael@0: if (fing->key) michael@0: PORT_Free (fing->key); michael@0: PORT_Free (fing); michael@0: } michael@0: break; michael@0: michael@0: case jarTypeSect: michael@0: case jarTypeMF: michael@0: case jarTypeSF: michael@0: dig = (JAR_Digest *) it->data; michael@0: if (dig) { michael@0: PORT_Free (dig); michael@0: } michael@0: break; michael@0: michael@0: case jarTypeOwner: michael@0: signer = (JAR_Signer *) it->data; michael@0: if (signer) michael@0: JAR_destroy_signer (signer); michael@0: break; michael@0: michael@0: default: michael@0: /* PORT_Assert( 1 != 2 ); */ michael@0: break; michael@0: } michael@0: PORT_Free (it); michael@0: michael@0: next: michael@0: oldlink = link; michael@0: link = link->next; michael@0: ZZ_DestroyLink (oldlink); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * J A R _ g e t _ m e t a i n f o michael@0: * michael@0: * Retrieve meta information from the manifest file. michael@0: * It doesn't matter whether it's from .MF or .SF, does it? michael@0: * michael@0: */ michael@0: michael@0: int michael@0: JAR_get_metainfo(JAR *jar, char *name, char *header, void **info, michael@0: unsigned long *length) michael@0: { michael@0: JAR_Item *it; michael@0: ZZLink *link; michael@0: ZZList *list; michael@0: michael@0: PORT_Assert( jar != NULL && header != NULL ); michael@0: michael@0: if (jar == NULL || header == NULL) michael@0: return JAR_ERR_PNF; michael@0: michael@0: list = jar->metainfo; michael@0: michael@0: if (ZZ_ListEmpty (list)) michael@0: return JAR_ERR_PNF; 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 == jarTypeMeta) { michael@0: JAR_Metainfo *met; michael@0: michael@0: if ((name && !it->pathname) || (!name && it->pathname)) michael@0: continue; michael@0: if (name && it->pathname && strcmp (it->pathname, name)) michael@0: continue; michael@0: met = (JAR_Metainfo *) it->data; michael@0: if (!PORT_Strcasecmp (met->header, header)) { michael@0: *info = PORT_Strdup (met->info); michael@0: *length = PORT_Strlen (met->info); michael@0: return 0; michael@0: } michael@0: } michael@0: } michael@0: return JAR_ERR_PNF; michael@0: } michael@0: michael@0: /* michael@0: * J A R _ f i n d michael@0: * michael@0: * Establish the search pattern for use michael@0: * by JAR_find_next, to traverse the filenames michael@0: * or certificates in the JAR structure. michael@0: * michael@0: * See jar.h for a description on how to use. michael@0: * michael@0: */ michael@0: JAR_Context * michael@0: JAR_find (JAR *jar, char *pattern, jarType type) michael@0: { michael@0: JAR_Context *ctx; michael@0: michael@0: PORT_Assert( jar != NULL ); michael@0: michael@0: if (!jar) michael@0: return NULL; michael@0: michael@0: ctx = (JAR_Context *) PORT_ZAlloc (sizeof (JAR_Context)); michael@0: if (ctx == NULL) michael@0: return NULL; michael@0: michael@0: ctx->jar = jar; michael@0: if (pattern) { michael@0: if ((ctx->pattern = PORT_Strdup (pattern)) == NULL) { michael@0: PORT_Free (ctx); michael@0: return NULL; michael@0: } michael@0: } michael@0: ctx->finding = type; michael@0: michael@0: switch (type) { michael@0: case jarTypeMF: michael@0: ctx->next = ZZ_ListHead (jar->hashes); michael@0: break; michael@0: michael@0: case jarTypeSF: michael@0: case jarTypeSign: michael@0: ctx->next = NULL; michael@0: ctx->nextsign = ZZ_ListHead (jar->signers); michael@0: break; michael@0: michael@0: case jarTypeSect: michael@0: ctx->next = ZZ_ListHead (jar->manifest); michael@0: break; michael@0: michael@0: case jarTypePhy: michael@0: ctx->next = ZZ_ListHead (jar->phy); michael@0: break; michael@0: michael@0: case jarTypeOwner: michael@0: if (jar->signers) michael@0: ctx->next = ZZ_ListHead (jar->signers); michael@0: else michael@0: ctx->next = NULL; michael@0: break; michael@0: michael@0: case jarTypeMeta: michael@0: ctx->next = ZZ_ListHead (jar->metainfo); michael@0: break; michael@0: michael@0: default: michael@0: PORT_Assert( 1 != 2); michael@0: break; michael@0: } michael@0: return ctx; michael@0: } michael@0: michael@0: /* michael@0: * J A R _ f i n d _ e n d michael@0: * michael@0: * Destroy the find iterator context. michael@0: * michael@0: */ michael@0: void michael@0: JAR_find_end (JAR_Context *ctx) michael@0: { michael@0: PORT_Assert( ctx != NULL ); michael@0: if (ctx) { michael@0: if (ctx->pattern) michael@0: PORT_Free (ctx->pattern); michael@0: PORT_Free (ctx); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * J A R _ f i n d _ n e x t michael@0: * michael@0: * Return the next item of the given type michael@0: * from one of the JAR linked lists. michael@0: * michael@0: */ michael@0: michael@0: int JAR_find_next (JAR_Context *ctx, JAR_Item **it) michael@0: { michael@0: JAR *jar; michael@0: ZZList *list = NULL; michael@0: int finding; michael@0: JAR_Signer *signer = NULL; michael@0: michael@0: PORT_Assert( ctx != NULL ); michael@0: PORT_Assert( ctx->jar != NULL ); michael@0: michael@0: jar = ctx->jar; michael@0: michael@0: /* Internally, convert jarTypeSign to jarTypeSF, and return michael@0: the actual attached certificate later */ michael@0: finding = (ctx->finding == jarTypeSign) ? jarTypeSF : ctx->finding; michael@0: if (ctx->nextsign) { michael@0: if (ZZ_ListIterDone (jar->signers, ctx->nextsign)) { michael@0: *it = NULL; michael@0: return -1; michael@0: } michael@0: PORT_Assert (ctx->nextsign->thing != NULL); michael@0: signer = (JAR_Signer*)ctx->nextsign->thing->data; michael@0: } michael@0: michael@0: /* Find out which linked list to traverse. Then if michael@0: necessary, advance to the next linked list. */ michael@0: while (1) { michael@0: switch (finding) { michael@0: case jarTypeSign: /* not any more */ michael@0: PORT_Assert( finding != jarTypeSign ); michael@0: list = signer->certs; michael@0: break; michael@0: michael@0: case jarTypeSect: michael@0: list = jar->manifest; michael@0: break; michael@0: michael@0: case jarTypePhy: michael@0: list = jar->phy; michael@0: break; michael@0: michael@0: case jarTypeSF: /* signer, not jar */ michael@0: PORT_Assert( signer != NULL ); michael@0: list = signer ? signer->sf : NULL; michael@0: break; michael@0: michael@0: case jarTypeMF: michael@0: list = jar->hashes; michael@0: break; michael@0: michael@0: case jarTypeOwner: michael@0: list = jar->signers; michael@0: break; michael@0: michael@0: case jarTypeMeta: michael@0: list = jar->metainfo; michael@0: break; michael@0: michael@0: default: michael@0: PORT_Assert( 1 != 2 ); michael@0: list = NULL; michael@0: break; michael@0: } michael@0: if (list == NULL) { michael@0: *it = NULL; michael@0: return -1; michael@0: } michael@0: /* When looping over lists of lists, advance to the next signer. michael@0: This is done when multiple signers are possible. */ michael@0: if (ZZ_ListIterDone (list, ctx->next)) { michael@0: if (ctx->nextsign && jar->signers) { michael@0: ctx->nextsign = ctx->nextsign->next; michael@0: if (!ZZ_ListIterDone (jar->signers, ctx->nextsign)) { michael@0: PORT_Assert (ctx->nextsign->thing != NULL); michael@0: signer = (JAR_Signer*)ctx->nextsign->thing->data; michael@0: PORT_Assert( signer != NULL ); michael@0: ctx->next = NULL; michael@0: continue; michael@0: } michael@0: } michael@0: *it = NULL; michael@0: return -1; michael@0: } michael@0: michael@0: /* if the signer changed, still need to fill in the "next" link */ michael@0: if (ctx->nextsign && ctx->next == NULL) { michael@0: switch (finding) { michael@0: case jarTypeSF: michael@0: ctx->next = ZZ_ListHead (signer->sf); michael@0: break; michael@0: michael@0: case jarTypeSign: michael@0: ctx->next = ZZ_ListHead (signer->certs); michael@0: break; michael@0: } michael@0: } michael@0: PORT_Assert( ctx->next != NULL ); michael@0: if (ctx->next == NULL) { michael@0: *it = NULL; michael@0: return -1; michael@0: } michael@0: while (!ZZ_ListIterDone (list, ctx->next)) { michael@0: *it = ctx->next->thing; michael@0: ctx->next = ctx->next->next; michael@0: if (!*it || (*it)->type != finding) michael@0: continue; michael@0: if (ctx->pattern && *ctx->pattern) { michael@0: if (PORT_RegExpSearch ((*it)->pathname, ctx->pattern)) michael@0: continue; michael@0: } michael@0: /* We have a valid match. If this is a jarTypeSign michael@0: return the certificate instead.. */ michael@0: if (ctx->finding == jarTypeSign) { michael@0: JAR_Item *itt; michael@0: michael@0: /* just the first one for now */ michael@0: if (jar_find_first_cert (signer, jarTypeSign, &itt) >= 0) { michael@0: *it = itt; michael@0: return 0; michael@0: } michael@0: continue; michael@0: } michael@0: return 0; michael@0: } michael@0: } /* end while */ michael@0: } michael@0: michael@0: static int michael@0: jar_find_first_cert (JAR_Signer *signer, int type, JAR_Item **it) michael@0: { michael@0: ZZLink *link; michael@0: ZZList *list = signer->certs; michael@0: int status = JAR_ERR_PNF; michael@0: michael@0: *it = NULL; 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: if (link->thing->type == type) { michael@0: *it = link->thing; michael@0: status = 0; michael@0: break; michael@0: } michael@0: } michael@0: return status; michael@0: } michael@0: michael@0: JAR_Signer * michael@0: JAR_new_signer (void) michael@0: { michael@0: JAR_Signer *signer = (JAR_Signer *) PORT_ZAlloc (sizeof (JAR_Signer)); michael@0: if (signer == NULL) michael@0: goto loser; michael@0: michael@0: /* certs */ michael@0: signer->certs = ZZ_NewList(); michael@0: if (signer->certs == NULL) michael@0: goto loser; michael@0: michael@0: /* sf */ michael@0: signer->sf = ZZ_NewList(); michael@0: if (signer->sf == NULL) michael@0: goto loser; michael@0: return signer; michael@0: michael@0: loser: michael@0: if (signer) { michael@0: if (signer->certs) michael@0: ZZ_DestroyList (signer->certs); michael@0: if (signer->sf) michael@0: ZZ_DestroyList (signer->sf); michael@0: PORT_Free (signer); michael@0: } michael@0: return NULL; michael@0: } michael@0: michael@0: void michael@0: JAR_destroy_signer(JAR_Signer *signer) michael@0: { michael@0: if (signer) { michael@0: if (signer->owner) michael@0: PORT_Free (signer->owner); michael@0: if (signer->digest) michael@0: PORT_Free (signer->digest); michael@0: jar_destroy_list (signer->sf); michael@0: ZZ_DestroyList (signer->sf); michael@0: jar_destroy_list (signer->certs); michael@0: ZZ_DestroyList (signer->certs); michael@0: PORT_Free (signer); michael@0: } michael@0: } michael@0: michael@0: JAR_Signer * michael@0: jar_get_signer(JAR *jar, char *basename) michael@0: { michael@0: JAR_Item *it; michael@0: JAR_Context *ctx = JAR_find (jar, NULL, jarTypeOwner); michael@0: JAR_Signer *candidate; michael@0: JAR_Signer *signer = NULL; michael@0: michael@0: if (ctx == NULL) michael@0: return NULL; michael@0: michael@0: while (JAR_find_next (ctx, &it) >= 0) { michael@0: candidate = (JAR_Signer *) it->data; michael@0: if (*basename == '*' || !PORT_Strcmp (candidate->owner, basename)) { michael@0: signer = candidate; michael@0: break; michael@0: } michael@0: } michael@0: JAR_find_end (ctx); michael@0: return signer; michael@0: } michael@0: michael@0: /* michael@0: * J A R _ g e t _ f i l e n a m e michael@0: * michael@0: * Returns the filename associated with michael@0: * a JAR structure. michael@0: * michael@0: */ michael@0: char * michael@0: JAR_get_filename(JAR *jar) michael@0: { michael@0: return jar->filename; michael@0: } michael@0: michael@0: /* michael@0: * J A R _ g e t _ u r l michael@0: * michael@0: * Returns the URL associated with michael@0: * a JAR structure. Nobody really uses this now. michael@0: * michael@0: */ michael@0: char * michael@0: JAR_get_url(JAR *jar) michael@0: { michael@0: return jar->url; michael@0: } michael@0: michael@0: /* michael@0: * J A R _ s e t _ c a l l b a c k michael@0: * michael@0: * Register some manner of callback function for this jar. michael@0: * michael@0: */ michael@0: int michael@0: JAR_set_callback(int type, JAR *jar, jar_settable_callback_fn *fn) michael@0: { michael@0: if (type == JAR_CB_SIGNAL) { michael@0: jar->signal = fn; michael@0: return 0; michael@0: } michael@0: return -1; michael@0: } michael@0: michael@0: /* michael@0: * Callbacks michael@0: * michael@0: */ michael@0: michael@0: /* To return an error string */ michael@0: char *(*jar_fn_GetString) (int) = NULL; michael@0: michael@0: /* To return an MWContext for Java */ michael@0: void *(*jar_fn_FindSomeContext) (void) = NULL; michael@0: michael@0: /* To fabricate an MWContext for FE_GetPassword */ michael@0: void *(*jar_fn_GetInitContext) (void) = NULL; michael@0: michael@0: void michael@0: JAR_init_callbacks(char *(*string_cb)(int), michael@0: void *(*find_cx)(void), michael@0: void *(*init_cx)(void)) michael@0: { michael@0: jar_fn_GetString = string_cb; michael@0: jar_fn_FindSomeContext = find_cx; michael@0: jar_fn_GetInitContext = init_cx; michael@0: } michael@0: michael@0: /* michael@0: * J A R _ g e t _ e r r o r michael@0: * michael@0: * This is provided to map internal JAR errors to strings for michael@0: * the Java console. Also, a DLL may call this function if it does michael@0: * not have access to the XP_GetString function. michael@0: * michael@0: * These strings aren't UI, since they are Java console only. michael@0: * michael@0: */ michael@0: char * michael@0: JAR_get_error(int status) michael@0: { michael@0: char *errstring = NULL; michael@0: michael@0: switch (status) { michael@0: case JAR_ERR_GENERAL: michael@0: errstring = "General JAR file error"; michael@0: break; michael@0: michael@0: case JAR_ERR_FNF: michael@0: errstring = "JAR file not found"; michael@0: break; michael@0: michael@0: case JAR_ERR_CORRUPT: michael@0: errstring = "Corrupt JAR file"; michael@0: break; michael@0: michael@0: case JAR_ERR_MEMORY: michael@0: errstring = "Out of memory"; michael@0: break; michael@0: michael@0: case JAR_ERR_DISK: michael@0: errstring = "Disk error (perhaps out of space)"; michael@0: break; michael@0: michael@0: case JAR_ERR_ORDER: michael@0: errstring = "Inconsistent files in META-INF directory"; michael@0: break; michael@0: michael@0: case JAR_ERR_SIG: michael@0: errstring = "Invalid digital signature file"; michael@0: break; michael@0: michael@0: case JAR_ERR_METADATA: michael@0: errstring = "JAR metadata failed verification"; michael@0: break; michael@0: michael@0: case JAR_ERR_ENTRY: michael@0: errstring = "No Manifest entry for this JAR entry"; michael@0: break; michael@0: michael@0: case JAR_ERR_HASH: michael@0: errstring = "Invalid Hash of this JAR entry"; michael@0: break; michael@0: michael@0: case JAR_ERR_PK7: michael@0: errstring = "Strange PKCS7 or RSA failure"; michael@0: break; michael@0: michael@0: case JAR_ERR_PNF: michael@0: errstring = "Path not found inside JAR file"; michael@0: break; michael@0: michael@0: default: michael@0: if (jar_fn_GetString) { michael@0: errstring = jar_fn_GetString (status); michael@0: } else { michael@0: /* this is not a normal situation, and would only be michael@0: called in cases of improper initialization */ michael@0: char *err = (char*)PORT_Alloc (40); michael@0: if (err) michael@0: PR_snprintf (err, 39, "Error %d\n", status); /* leak me! */ michael@0: else michael@0: err = "Error! Bad! Out of memory!"; michael@0: return err; michael@0: } michael@0: break; michael@0: } michael@0: return errstring; michael@0: }