1
/* ***** BEGIN LICENSE BLOCK *****
2
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
4
* The contents of this file are subject to the Mozilla Public License Version
5
* 1.1 (the "License"); you may not use this file except in compliance with
6
* the License. You may obtain a copy of the License at
7
* http://www.mozilla.org/MPL/
9
* Software distributed under the License is distributed on an "AS IS" basis,
10
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11
* for the specific language governing rights and limitations under the
14
* The Original Code is the Netscape security libraries.
16
* The Initial Developer of the Original Code is
17
* Netscape Communications Corporation.
18
* Portions created by the Initial Developer are Copyright (C) 1994-2000
19
* the Initial Developer. All Rights Reserved.
23
* Alternatively, the contents of this file may be used under the terms of
24
* either the GNU General Public License Version 2 or later (the "GPL"), or
25
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26
* in which case the provisions of the GPL or the LGPL are applicable instead
27
* of those above. If you wish to allow use of your version of this file only
28
* under the terms of either the GPL or the LGPL, and not to allow others to
29
* use your version of this file under the terms of the MPL, indicate your
30
* decision by deleting the provisions above and replace them with the notice
31
* and other provisions required by the GPL or the LGPL. If you do not delete
32
* the provisions above, a recipient may use your version of this file under
33
* the terms of any one of the MPL, the GPL or the LGPL.
35
* ***** END LICENSE BLOCK ***** */
41
#include "sechash.h" /* for HASH_GetHashObject() */
43
static int create_pk7 (char *dir, char *keyName, int *keyType);
44
static int jar_find_key_type (CERTCertificate *cert);
45
static int manifesto (char *dirname, char *install_script, PRBool recurse);
46
static int manifesto_fn(char *relpath, char *basedir, char *reldir,
47
char *filename, void *arg);
48
static int manifesto_xpi_fn(char *relpath, char *basedir, char *reldir,
49
char *filename, void *arg);
50
static int sign_all_arc_fn(char *relpath, char *basedir, char *reldir,
51
char *filename, void *arg);
52
static int add_meta (FILE *fp, char *name);
53
static int SignFile (FILE *outFile, FILE *inFile, CERTCertificate *cert);
54
static int generate_SF_file (char *manifile, char *who);
55
static int calculate_MD5_range (FILE *fp, long r1, long r2,
57
static void SignOut (void *arg, const char *buf, unsigned long len);
59
static char *metafile = NULL;
60
static int optimize = 0;
62
static ZIPfile *zipfile = NULL;
65
* S i g n A r c h i v e
67
* Sign an individual archive tree. A directory
68
* called META-INF is created underneath this.
72
SignArchive(char *tree, char *keyName, char *zip_file, int javascript,
73
char *meta_file, char *install_script, int _optimize, PRBool recurse)
76
char tempfn [FNSIZE], fullfn [FNSIZE];
82
/* To create XPI compatible Archive manifesto() must be run before
83
* the zipfile is opened. This is so the signed files are not added
84
* the archive before the crucial rsa/dsa file*/
86
manifesto (tree, install_script, recurse);
90
zipfile = JzipOpen(zip_file, NULL /*no comment*/);
93
/*Sign and add files to the archive normally with manifesto()*/
95
manifesto (tree, install_script, recurse);
99
status = create_pk7 (tree, keyName, &keyType);
101
PR_fprintf(errorFD, "the tree \"%s\" was NOT SUCCESSFULLY SIGNED\n",
108
/* Add the rsa/dsa file as the first file in the archive. This is crucial
109
* for a XPInstall compatible archive */
111
if (verbosity >= 0) {
112
PR_fprintf(outputFD, "%s \n", XPI_TEXT);
116
sprintf (tempfn, "META-INF/%s.%s", base, (keyType == dsaKey ?
118
sprintf (fullfn, "%s/%s", tree, tempfn);
119
JzipAdd(fullfn, tempfn, zipfile, compression_level);
121
/* Loop through all files & subdirectories, add to archive */
122
foreach (tree, "", manifesto_xpi_fn, recurse, PR_FALSE /*include dirs */,
126
strcpy (tempfn, "META-INF/manifest.mf");
127
sprintf (fullfn, "%s/%s", tree, tempfn);
128
JzipAdd(fullfn, tempfn, zipfile, compression_level);
131
sprintf (tempfn, "META-INF/%s.sf", base);
132
sprintf (fullfn, "%s/%s", tree, tempfn);
133
JzipAdd(fullfn, tempfn, zipfile, compression_level);
135
/* Add the rsa/dsa file to the zip archive normally */
138
sprintf (tempfn, "META-INF/%s.%s", base, (keyType == dsaKey ?
140
sprintf (fullfn, "%s/%s", tree, tempfn);
141
JzipAdd(fullfn, tempfn, zipfile, compression_level);
146
if (verbosity >= 0) {
148
PR_fprintf(outputFD, "jarfile \"%s\" signed successfully\n",
151
PR_fprintf(outputFD, "tree \"%s\" signed successfully\n",
164
char *install_script;
169
* S i g n A l l A r c
171
* Javascript may generate multiple .arc directories, one
172
* for each jar archive needed. Sign them all.
176
SignAllArc(char *jartree, char *keyName, int javascript, char *metafile,
177
char *install_script, int optimize, PRBool recurse)
181
info.keyName = keyName;
182
info.javascript = javascript;
183
info.metafile = metafile;
184
info.install_script = install_script;
185
info.optimize = optimize;
187
return foreach(jartree, "", sign_all_arc_fn, recurse,
188
PR_TRUE /*include dirs*/, (void * )&info);
193
sign_all_arc_fn(char *relpath, char *basedir, char *reldir, char *filename,
196
char *zipfile = NULL;
197
char *arc = NULL, *archive = NULL;
199
SignArcInfo * infop = (SignArcInfo * )arg;
201
/* Make sure there is one and only one ".arc" in the relative path,
202
* and that it is at the end of the path (don't sign .arcs within .arcs) */
203
if ( (PL_strcaserstr(relpath, ".arc") == relpath + strlen(relpath) -
205
(PL_strcasestr(relpath, ".arc") == relpath + strlen(relpath) - 4) ) {
208
PR_fprintf(errorFD, "%s: Internal failure\n", PROGRAM_NAME);
213
archive = PR_smprintf("%s/%s", basedir, relpath);
215
zipfile = PL_strdup(archive);
216
arc = PORT_Strrchr (zipfile, '.');
219
PR_fprintf(errorFD, "%s: Internal failure\n", PROGRAM_NAME);
225
PL_strcpy (arc, ".jar");
227
if (verbosity >= 0) {
228
PR_fprintf(outputFD, "\nsigning: %s\n", zipfile);
230
retval = SignArchive(archive, infop->keyName, zipfile,
231
infop->javascript, infop->metafile, infop->install_script,
232
infop->optimize, PR_TRUE /* recurse */);
244
/*********************************************************************
246
* c r e a t e _ p k 7
249
create_pk7 (char *dir, char *keyName, int *keyType)
254
CERTCertificate * cert;
255
CERTCertDBHandle * db;
259
char sf_file [FNSIZE];
260
char pk7_file [FNSIZE];
262
/* open cert database */
263
db = CERT_GetDefaultCertDB();
269
/*cert = CERT_FindCertByNicknameOrEmailAddr(db, keyName);*/
270
cert = PK11_FindCertFromNickname(keyName, NULL /*wincx*/);
273
SECU_PrintError ( PROGRAM_NAME,
274
"the cert \"%s\" does not exist in the database", keyName);
279
/* determine the key type, which sets the extension for pkcs7 object */
281
*keyType = jar_find_key_type (cert);
282
file_ext = (*keyType == dsaKey) ? "dsa" : "rsa";
284
sprintf (sf_file, "%s/META-INF/%s.sf", dir, base);
285
sprintf (pk7_file, "%s/META-INF/%s.%s", dir, base, file_ext);
287
if ((in = fopen (sf_file, "rb")) == NULL) {
288
PR_fprintf(errorFD, "%s: Can't open %s for reading\n", PROGRAM_NAME,
294
if ((out = fopen (pk7_file, "wb")) == NULL) {
295
PR_fprintf(errorFD, "%s: Can't open %s for writing\n", PROGRAM_NAME,
301
status = SignFile (out, in, cert);
303
CERT_DestroyCertificate (cert);
308
PR_fprintf(errorFD, "%s: PROBLEM signing data (%s)\n",
309
PROGRAM_NAME, SECU_ErrorString ((int16) PORT_GetError()));
319
* j a r _ f i n d _ k e y _ t y p e
321
* Determine the key type for a given cert, which
322
* should be rsaKey or dsaKey. Any error return 0.
326
jar_find_key_type (CERTCertificate *cert)
328
PK11SlotInfo * slot = NULL;
329
SECKEYPrivateKey * privk = NULL;
332
/* determine its type */
333
PK11_FindObjectForCert (cert, /*wincx*/ NULL, &slot);
336
PR_fprintf(errorFD, "warning - can't find slot for this cert\n");
341
privk = PK11_FindPrivateKeyFromCert (slot, cert, /*wincx*/ NULL);
342
PK11_FreeSlot (slot);
345
PR_fprintf(errorFD, "warning - can't find private key for this cert\n");
350
keyType = privk->keyType;
351
SECKEY_DestroyPrivateKey (privk);
359
* Run once for every subdirectory in which a
360
* manifest is to be created -- usually exactly once.
364
manifesto (char *dirname, char *install_script, PRBool recurse)
366
char metadir [FNSIZE], sfname [FNSIZE];
368
/* Create the META-INF directory to hold signing info */
370
if (PR_Access (dirname, PR_ACCESS_READ_OK)) {
371
PR_fprintf(errorFD, "%s: unable to read your directory: %s\n",
372
PROGRAM_NAME, dirname);
378
if (PR_Access (dirname, PR_ACCESS_WRITE_OK)) {
379
PR_fprintf(errorFD, "%s: unable to write to your directory: %s\n",
380
PROGRAM_NAME, dirname);
386
sprintf (metadir, "%s/META-INF", dirname);
388
strcpy (sfname, metadir);
390
PR_MkDir (metadir, 0777);
392
strcat (metadir, "/");
393
strcat (metadir, MANIFEST);
395
if ((mf = fopen (metadir, "wb")) == NULL) {
397
PR_fprintf(errorFD, "%s: Probably, the directory you are trying to"
398
" sign has\n", PROGRAM_NAME);
399
PR_fprintf(errorFD, "%s: permissions problems or may not exist.\n",
405
if (verbosity >= 0) {
406
PR_fprintf(outputFD, "Generating %s file..\n", metadir);
409
fprintf(mf, "Manifest-Version: 1.0\n");
410
fprintf (mf, "Created-By: %s\n", CREATOR);
411
fprintf (mf, "Comments: %s\n", BREAKAGE);
414
fprintf (mf, "Comments: --\n");
415
fprintf (mf, "Comments: --\n");
416
fprintf (mf, "Comments: -- This archive signs Javascripts which may not necessarily\n");
417
fprintf (mf, "Comments: -- be included in the physical jar file.\n");
418
fprintf (mf, "Comments: --\n");
419
fprintf (mf, "Comments: --\n");
423
fprintf (mf, "Install-Script: %s\n", install_script);
428
/* Loop through all files & subdirectories */
429
foreach (dirname, "", manifesto_fn, recurse, PR_FALSE /*include dirs */,
434
strcat (sfname, "/");
435
strcat (sfname, base);
436
strcat (sfname, ".sf");
438
if (verbosity >= 0) {
439
PR_fprintf(outputFD, "Generating %s.sf file..\n", base);
441
generate_SF_file (metadir, sfname);
448
* m a n i f e s t o _ x p i _ f n
450
* Called by pointer from SignArchive(), once for
451
* each file within the directory. This function
452
* is only used for adding to XPI compatible archive
455
static int manifesto_xpi_fn
456
(char *relpath, char *basedir, char *reldir, char *filename, void *arg)
458
char fullname [FNSIZE];
460
if (verbosity >= 0) {
461
PR_fprintf(outputFD, "--> %s\n", relpath);
464
/* extension matching */
465
if (extensionsGiven) {
466
char *ext = PL_strrchr(relpath, '.');
469
if (!PL_HashTableLookup(extensions, ext))
472
sprintf (fullname, "%s/%s", basedir, relpath);
473
JzipAdd(fullname, relpath, zipfile, compression_level);
480
* m a n i f e s t o _ f n
482
* Called by pointer from manifesto(), once for
483
* each file within the directory.
486
static int manifesto_fn
487
(char *relpath, char *basedir, char *reldir, char *filename, void *arg)
492
char fullname [FNSIZE];
494
if (verbosity >= 0) {
495
PR_fprintf(outputFD, "--> %s\n", relpath);
498
/* extension matching */
499
if (extensionsGiven) {
500
char *ext = PL_strrchr(relpath, '.');
503
if (!PL_HashTableLookup(extensions, ext))
507
sprintf (fullname, "%s/%s", basedir, relpath);
513
if (scriptdir && !PORT_Strcmp (scriptdir, reldir))
516
/* sign non-.js files inside .arc directories using the javascript magic */
518
if ( (PL_strcaserstr(filename, ".js") != filename + strlen(filename) - 3)
519
&& (PL_strcaserstr(reldir, ".arc") == reldir + strlen(filename) - 4))
523
fprintf (mf, "Name: %s\n", filename);
524
fprintf (mf, "Magic: javascript\n");
527
fprintf (mf, "javascript.id: %s\n", filename);
530
add_meta (mf, filename);
532
fprintf (mf, "Name: %s\n", relpath);
534
add_meta (mf, relpath);
537
JAR_digest_file (fullname, &dig);
541
fprintf (mf, "Digest-Algorithms: MD5 SHA1\n");
542
fprintf (mf, "MD5-Digest: %s\n", BTOA_DataToAscii (dig.md5,
546
fprintf (mf, "SHA1-Digest: %s\n", BTOA_DataToAscii (dig.sha1, SHA1_LENGTH));
549
JzipAdd(fullname, relpath, zipfile, compression_level);
559
* Parse the metainfo file, and add any details
560
* necessary to the manifest file. In most cases you
561
* should be using the -i option (ie, for SmartUpdate).
564
static int add_meta (FILE *fp, char *name)
570
char *pattern, *meta;
574
if ((met = fopen (metafile, "r")) != NULL) {
575
while (fgets (buf, BUFSIZ, met)) {
578
for (s = buf; *s && *s != '\n' && *s != '\r'; s++)
587
/* skip to whitespace */
588
for (s = buf; *s && *s != ' ' && *s != '\t'; s++)
591
/* terminate pattern */
592
if (*s == ' ' || *s == '\t')
595
/* eat through whitespace */
596
while (*s == ' ' || *s == '\t')
601
/* this will eventually be regexp matching */
604
if (!PORT_Strcmp (pattern, name))
609
if (verbosity >= 0) {
610
PR_fprintf(outputFD, "[%s] %s\n", name, meta);
612
fprintf (fp, "%s\n", meta);
617
PR_fprintf(errorFD, "%s: can't open metafile: %s\n", PROGRAM_NAME,
627
/**********************************************************************
632
SignFile (FILE *outFile, FILE *inFile, CERTCertificate *cert)
635
char ibuf[4096], digestdata[32];
636
const SECHashObject *hashObj;
641
SEC_PKCS7ContentInfo * cinfo;
644
if (outFile == NULL || inFile == NULL || cert == NULL)
647
/* XXX probably want to extend interface to allow other hash algorithms */
648
hashObj = HASH_GetHashObject(HASH_AlgSHA1);
650
hashcx = (*hashObj->create)();
654
(*hashObj->begin)(hashcx);
659
nb = fread(ibuf, 1, sizeof(ibuf), inFile);
661
if (ferror(inFile)) {
662
PORT_SetError(SEC_ERROR_IO);
663
(*hashObj->destroy)(hashcx, PR_TRUE);
669
(*hashObj->update)(hashcx, (unsigned char *) ibuf, nb);
672
(*hashObj->end)(hashcx, (unsigned char *) digestdata, &len, 32);
673
(*hashObj->destroy)(hashcx, PR_TRUE);
675
digest.data = (unsigned char *) digestdata;
678
cinfo = SEC_PKCS7CreateSignedData
679
(cert, certUsageObjectSigner, NULL,
680
SEC_OID_SHA1, &digest, NULL, NULL);
685
rv = SEC_PKCS7IncludeCertChain (cinfo, NULL);
686
if (rv != SECSuccess) {
687
SEC_PKCS7DestroyContentInfo (cinfo);
692
rv = SEC_PKCS7AddSigningTime (cinfo);
693
if (rv != SECSuccess) {
694
/* don't check error */
699
rv = SEC_PKCS7Encode(cinfo, SignOut, outFile, NULL,
700
(SECKEYGetPasswordKey) password_hardcode, NULL);
702
rv = SEC_PKCS7Encode(cinfo, SignOut, outFile, NULL, NULL,
707
SEC_PKCS7DestroyContentInfo (cinfo);
709
if (rv != SECSuccess)
717
* g e n e r a t e _ S F _ f i l e
719
* From the supplied manifest file, calculates
720
* digests on the various sections, creating a .SF
721
* file in the process.
724
static int generate_SF_file (char *manifile, char *who)
729
char whofile [FNSIZE];
730
char *buf, *name = NULL;
734
strcpy (whofile, who);
736
if ((mf = fopen (manifile, "rb")) == NULL) {
741
if ((sf = fopen (whofile, "wb")) == NULL) {
746
buf = (char *) PORT_ZAlloc (BUFSIZ);
749
name = (char *) PORT_ZAlloc (BUFSIZ);
751
if (buf == NULL || name == NULL)
754
fprintf (sf, "Signature-Version: 1.0\n");
755
fprintf (sf, "Created-By: %s\n", CREATOR);
756
fprintf (sf, "Comments: %s\n", BREAKAGE);
758
if (fgets (buf, BUFSIZ, mf) == NULL) {
759
PR_fprintf(errorFD, "%s: empty manifest file!\n", PROGRAM_NAME);
764
if (strncmp (buf, "Manifest-Version:", 17)) {
765
PR_fprintf(errorFD, "%s: not a manifest file!\n", PROGRAM_NAME);
770
fseek (mf, 0L, SEEK_SET);
772
/* Process blocks of headers, and calculate their hashen */
775
/* Beginning range */
778
if (fgets (name, BUFSIZ, mf) == NULL)
783
if (r1 != 0 && strncmp (name, "Name:", 5)) {
785
"warning: unexpected input in manifest file \"%s\" at line %d:\n",
787
PR_fprintf(errorFD, "%s\n", name);
792
while (fgets (buf, BUFSIZ, mf)) {
793
if (*buf == 0 || *buf == '\n' || *buf == '\r')
798
/* Ending range for hashing */
806
fprintf (sf, "%s", name);
809
calculate_MD5_range (mf, r1, r2, &dig);
812
fprintf (sf, "Digest-Algorithms: MD5 SHA1\n");
813
fprintf (sf, "MD5-Digest: %s\n",
814
BTOA_DataToAscii (dig.md5, MD5_LENGTH));
817
fprintf (sf, "SHA1-Digest: %s\n",
818
BTOA_DataToAscii (dig.sha1, SHA1_LENGTH));
820
/* restore normalcy after changing offset position */
821
fseek (mf, r3, SEEK_SET);
835
* c a l c u l a t e _ M D 5 _ r a n g e
837
* Calculate the MD5 digest on a range of bytes in
838
* the specified fopen'd file. Returns base64.
842
calculate_MD5_range (FILE *fp, long r1, long r2, JAR_Digest *dig)
848
MD5Context * md5 = 0;
849
SHA1Context * sha1 = 0;
851
unsigned int sha1_length, md5_length;
855
/* position to the beginning of range */
856
fseek (fp, r1, SEEK_SET);
858
buf = (unsigned char *) PORT_ZAlloc (range);
862
if ((num = fread (buf, 1, range, fp)) != range) {
863
PR_fprintf(errorFD, "%s: expected %d bytes, got %d\n", PROGRAM_NAME,
869
md5 = MD5_NewContext();
870
sha1 = SHA1_NewContext();
872
if (md5 == NULL || sha1 == NULL) {
873
PR_fprintf(errorFD, "%s: can't generate digest context\n",
882
MD5_Update (md5, buf, range);
883
SHA1_Update (sha1, buf, range);
885
MD5_End (md5, dig->md5, &md5_length, MD5_LENGTH);
886
SHA1_End (sha1, dig->sha1, &sha1_length, SHA1_LENGTH);
888
MD5_DestroyContext (md5, PR_TRUE);
889
SHA1_DestroyContext (sha1, PR_TRUE);
897
static void SignOut (void *arg, const char *buf, unsigned long len)
899
fwrite (buf, len, 1, (FILE * ) arg);