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 "Cannot find the cert \"%s\"", 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
);