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