Milán Major

netscape-revival

1 branch
Code

security/cmd/jar/zigbert.c

/*
 *  ZIGBERT
 *
 *  A command line tool to create manifest files
 *  from a directory hierarchy. It is assumed that
 *  the tree will be equivalent to what resides
 *  or will reside in an archive. 
 *
 *  Process to sign files: 
 *
 *     unpack archive into subdirectory SUB
 *     zigbert SUB
 *     add files from "META-INF/" to archive,
 *       or pack a new archive from hierarchy.
 *
 *  Options:
 *
 *     -eEXT :: sign only files ending in EXT
 *     -kKEY :: nickname of key to use for signature
 *     -mMET :: metainformation control file
 *     -dDIR :: point to a different ~/.netscape directory
 *     -jDIR :: sign these javascript files below here
 *     -pPW  :: password on command line (insecure)
 *
 *  Copyright 1996 Netscape Corporation
 * 
 */

#include <stdio.h>
#include <string.h>

#include <errno.h>


#include "zig.h"
#include "base64.h"

#include "secutil.h"
#include "secpkcs7.h"
#include "pk11func.h"

#ifdef _UNIX
#include <unistd.h> 
#endif

#ifdef XP_PC
/* Use the NSPR opendir routines */
#include "dirpoop.h"
#endif

/* SVR4 is to prevent Solaris from blowing up --Rob */
#if defined(__sun) && !defined(SVR4)
extern int getopt(int, char**, char*);
extern char *optarg;
#endif

#define zigprint printf



/* mf should not be global */
static FILE *mf;

#define MANIFEST "manifest.mf"

#define CREATOR  "Zignature Shell (zigbert 0.5)"
#define BREAKAGE "PLEASE DO NOT EDIT THIS FILE. YOU WILL BREAK IT."

static int manifesto (char *dirname);
static int manifesto_fn (char *filename, char *dirname, char *basedir, char *basename);
static int foreach 
  (char *dirname, char *prefix, int (*fn)(char *filename, char *dirname, char *basedir, char *basename));
extern int calculate_MD5_range (FILE *fp, long r1, long r2, DIGESTS *dig);
static int generate_SF_file (char *manifile, char *who);
static int is_dir (char *filename);
/* extern int sign_data (void); */
static int do_signing (char *dir);
static int create_pk7 (FILE *inFile, FILE *outFile);
static int ugly_trav (char *key);
static int add_meta (FILE *mf, char *name);
static int verify_global (ZIG *zig);
static int zig_list (char *filename);
static int zig_who (char *filename);
static int init_crypto (void);
static char *get_default_cert_dir (void);
int print_error (int i);

static CERTCertDBHandle *OpenCertDB(void);

static void usage (void);

/* Never use a global in this manner */
CERTCertificate *ugly;

static int ugly_find_nickname (CERTCertDBHandle *handle, char *nickname);



/* bad global bad bad */

/* key used to sign with pkcs7 */
char *keyName = NULL;

/* meta control file, contains name: value pairs */
char *metafile = NULL;

/* password on command line. Use for build testing only */
char *password = NULL;

/* directory containing cert?.db and key?.db */
char *cert_dir = NULL;

/* directories or files to exclude in descent */
char *exclude = NULL;

/* special javascript directory */
char *scriptdir = NULL;

/* extensions of files to sign */
char *ext = NULL;

/* -v to verify a jar file */
char *verify = NULL;

/* list our certificates with the -L option */
int list_certs = 0;

/* optimize, leave out optional headers */
int optimize = 0;

/* zatharus is the man who knows no time, dies tragic death */
int no_time = 0;

/* -w to tell us who signed this jar file */
char *tell_who = NULL;



XP_File XP_FileOpen
    (const char* name, XP_FileType type, const XP_FilePerm permissions)
  {
  return fopen (name, permissions);
  }

#ifndef XP_FileClose
XP_FileClose (XP_File fp)
  {
  return fclose (fp);
  }
#endif

#ifndef XP_FileWrite
int XP_FileWrite
    (const void* source, int32 count, XP_File file)
  {
  return fwrite (source, 1,
    count == -1 ? strlen((char*)source) : count, file);
  }
#endif

#ifndef XP_WIN
char *XP_GetString (int i)
  {
  return SECU_ErrorStringRaw ((int16) i);
  }
#endif

void FE_SetPasswordEnabled()
  {
  }

MWContext *FE_GetInitContext (void)
  {
  return 0;
  }

MWContext *XP_FindSomeContext()
  {
  /* No windows context in command tools */
  return NULL;
  }

main (int argc, char *argv[])
  {
  int o;

  char *zigtree = NULL;

  int status;

  if (argc < 2)
    {
    usage();
    }

  while ((o = getopt (argc, argv, "e:m:n:k:d:p:j:Llx:v:ozw:E:")) != -1) 
    {
    switch (o) 
      {
      case '?': usage(); exit (0);
	        break;

      case 'e': ext = XP_STRDUP (optarg);
                zigprint ("signing only extension \"%s\"\n", ext);
                break;

      case 'm': metafile = XP_STRDUP (optarg);
                zigprint ("meta control file \"%s\"\n", metafile);
                break;

      case 'n':
      case 'k': keyName = XP_STRDUP (optarg);
                zigprint ("using key \"%s\"\n", keyName);
                break;

      case 'd': cert_dir = XP_STRDUP (optarg);
                break;

      case 'j': scriptdir = XP_STRDUP (optarg);
                zigprint ("javscript directory: %s\n", scriptdir);
                break;

      case 'p': password = XP_STRDUP (optarg);
                break;

      case 'L': 
      case 'l': list_certs = 1;
                break;

      case 'x': exclude = XP_STRDUP (optarg);
                break;

      case 'v': verify = XP_STRDUP (optarg);
                break;

      case 'o': optimize = 1;
                break;

      case 'z': no_time = 1;
                zigprint ("will not store signing time in objects\n");
                break;

      case 'w': tell_who = XP_STRDUP (optarg);
                break;

      case 'E': print_error (atoi (optarg));
		exit (0);
                break;
      }
    }


  if (!cert_dir)
    cert_dir = get_default_cert_dir();

  zigprint ("using certificate directory: %s\n", cert_dir);
  init_crypto();

  zigtree = argv [optind];

  if (verify)
    {
    zig_list (verify);
    }
  else if (list_certs)
    {
    ugly_trav (keyName);
    exit (0);
    }
  else if (tell_who)
    {
    zig_who (tell_who);
    }
  else if (zigtree)
    {
    manifesto (zigtree);

    if (keyName)
      {
      status = do_signing (zigtree);
      if (status < 0)
        {
        fprintf (stderr, "the tree \"%s\" was NOT SUCCESSFULLY SIGNED\n", zigtree);
        exit (1);
        }
      else
        zigprint ("tree \"%s\" signed successfully\n", zigtree);
      }
    }
  else
    usage();

  return 0;
  }

static void usage (void)
  {
  printf ("\n");
  printf ("zigbert 0.4 - a signing tool for jar files\n");
  printf ("\n");
  printf ("Usage:  zigbert [options] directory-tree \n");
  printf ("Options:\n");
  printf ("    -k\"cert nickname\"\n");
  printf ("    -d\"certificate directory\"\n");
  printf ("    -m\"metafile\"\n");
  printf ("\n");
  printf ("    -L     list the certificates in your database\n");
  printf ("           (this tells you what zigbert can see right now)\n");
  printf ("\n");
  printf ("Please note the following caveats:\n");
  printf ("  directory-tree must already exist.\n");
  printf ("  You will need to zip the directory-tree with a zip tool.\n\n");

  exit (0);
  }

int print_error (int err)
  {
  printf ("Error %d: %s\n", err, SOB_get_error (err));
  }

/*
 *  m a n i f e s t o
 *
 *  Run once for every subdirectory in which a 
 *  manifest is to be created -- usually exactly once.
 *
 */

static int manifesto (char *dirname)
  {
  char metadir [256], sfname [256];

  /* Create the META-INF directory to hold signing info */

  if (access (dirname, 004))
    {
    fprintf (stderr, "zigbert: unable to read your directory: %s\n", dirname);
    perror (dirname);
    exit (1);
    }

  strcpy (metadir, dirname);
  strcat (metadir, "/META-INF");

  strcpy (sfname, metadir);

#if defined(XP_UNIX)
  {
  char cmd [256];
  strcpy (cmd, "mkdir ");
  strcat (cmd, metadir);
  system (cmd);
  }
#elif defined(XP_WIN)
  mkdir(metadir);
#else
  XP_MakeDirectory (metadir, xpTemporary);
#endif

  strcat (metadir, "/");
  strcat (metadir, MANIFEST);

  if ((mf = fopen (metadir, "wb")) == NULL)
    {
    perror (MANIFEST);
    fprintf (stderr, "zigbert: Probably, the directory you are trying to sign has\n");
    fprintf (stderr, "zigbert: permissions problems or may not exist.\n");
    exit (1);
    }

  zigprint ("Generating %s file..\n", metadir);

  fprintf (mf, "Manifest-Version: 1.0\n");
  fprintf (mf, "Created-By: %s\n", CREATOR);
  fprintf (mf, "Comments: %s\n", BREAKAGE);

  if (scriptdir)
    {
    fprintf (mf, "Comments: --\n");
    fprintf (mf, "Comments: --\n");
    fprintf (mf, "Comments: -- This archive signs Javascripts which may not necessarily\n");
    fprintf (mf, "Comments: -- be included in the physical jar file.\n");
    fprintf (mf, "Comments: --\n");
    fprintf (mf, "Comments: --\n");
    }

  if (metafile)
    add_meta (mf, "+");

  /* Loop through all files & subdirectories */
  foreach (dirname, "", manifesto_fn);

  fclose (mf);

  strcat (sfname, "/zigbert.sf");

  zigprint ("Generating ZIGBERT.SF file..\n");
  generate_SF_file (metadir, sfname);

  return 0;
  }

static int manifesto_fn (char *filename, char *dirname, char *basedir, char *basename)
  {
  DIGESTS dig;
  char fullname [256];

  /* crude extension matching */

  if (ext)
    {
    char *s;

    if (strlen (filename) < strlen (ext)) 
      return 0;

    /* last n bytes */
    s = strchr (filename, 0) - strlen (ext);

    if (strcmp (s, ext))
      return 0;
    }

  strcpy (fullname, dirname);
  strcat (fullname, "/");
  strcat (fullname, filename);

  fprintf (mf, "\n");

  if (scriptdir && !XP_STRCMP (scriptdir, basedir))
    {
    fprintf (mf, "Name: %s\n", basename);
    fprintf (mf, "Magic: javascript\n");

    if (optimize == 0)
      fprintf (mf, "javascript.id: %s\n", basename);

    if (metafile) 
      add_meta (mf, basename);
    }
  else
    {
    fprintf (mf, "Name: %s\n", filename);
    if (metafile)
      add_meta (mf, filename);
    }

  SOB_digest_file (fullname, &dig);


  if (optimize == 0)
    {
    fprintf (mf, "Digest-Algorithms: MD5 SHA1\n");
    fprintf (mf, "MD5-Digest: %s\n", BTOA_DataToAscii (dig.md5, dig.md5_length));
    }

  fprintf (mf, "SHA1-Digest: %s\n", BTOA_DataToAscii (dig.sha1, dig.sha1_length));

  return 0;
  }

static int add_meta (FILE *fp, char *name)
  {
  FILE *met;
  char buf [BUFSIZ];

  int place;
  char *pattern, *meta;

  int num = 0;

  if ((met = fopen (metafile, "r")) != NULL)
    {
    while (fgets (buf, BUFSIZ, met))
      {
      char *s;

      for (s = buf; *s && *s != '\n' && *s != '\r'; s++);
      *s = 0;

      if (*buf == 0)
        continue;

      pattern = buf;

      /* skip to whitespace */
      for (s = buf; *s && *s != ' ' && *s != '\t'; s++);

      /* terminate pattern */
      if (*s == ' ' || *s == '\t') *s++ = 0;

      /* eat through whitespace */
      while (*s == ' ' || *s == '\t') s++;

      meta = s;

      /* this will eventually be regexp matching */

      place = 0;
      if (!XP_STRCMP (pattern, name))
        place = 1;

      if (place)
        {
        num++;
        zigprint ("[%s] %s\n", name, meta);
        fprintf (fp, "%s\n", meta);
        }
      }
    fclose (met);
    }
  else
    zigprint ("can't open metafile: %s\n", metafile);

  return num;
  }

/*
 *  f o r e a c h 
 * 
 *  A recursive function to loop through all names in
 *  the specified directory, as well as all subdirectories.
 *
 *  FIX: Need to see if all platforms allow multiple
 *  opendir's to be called.
 *
 */

static int foreach 
    (char *dirname, char *prefix, 
       int (*fn)(char *filename, char *dirname, char *basedir, char *basename))
  {
  char newdir [512];

  XP_Dir dir;
  XP_DirEntryStruct *entry;

#if 0
/* Note, I will need to add this later re: BSDI bug */
#ifdef XP_UNIX
    {
    XP_StatStruct st;
    if (XP_Stat (dirname, &st, xpTemporary))
      return -1;

    if (!S_ISDIR (st.st_mode))
      return -1;
    }
#endif /* XP_UNIX */
#endif

  strcpy (newdir, dirname);
  if (*prefix)
    {
    strcat (newdir, "/");
    strcat (newdir, prefix);
    }

  dir = XP_OpenDir (newdir, xpTemporary);
  if (!dir) return -1;

  for (entry = XP_ReadDir (dir); entry; entry = XP_ReadDir (dir)) 
    {
    if (*entry->d_name == '.' || *entry->d_name == '#')
      continue;

    /* can't sign self */
    if (!strcmp (entry->d_name, "META-INF"))
      continue;

    /* -x option */
    if (exclude && !strcmp (entry->d_name, exclude))
      continue;

    strcpy (newdir, dirname);
    if (*dirname)
      strcat (newdir, "/");

    if (*prefix)
      {
      strcat (newdir, prefix);
      strcat (newdir, "/");
      }
    strcat (newdir, entry->d_name);

    if (is_dir (newdir))
      {
      char newprefix [512];

      strcpy (newprefix, prefix);
      if (*newprefix)
        strcat (newprefix, "/");
      strcat (newprefix, entry->d_name);

      foreach (dirname, newprefix, fn);
      }
    else
      {
      char newpath [512];

      strcpy (newpath, prefix);
      if (*newpath)
        strcat (newpath, "/");
      strcat (newpath, entry->d_name);

      zigprint ("--> %s\n", newpath);
      (*fn) (newpath, dirname, prefix, entry->d_name);
      }
    }

  XP_CloseDir (dir);

  return 0;
  }

/*
 *  c a l c u l a t e _ M D 5 _ r a n g e
 *
 *  Calculate the MD5 digest on a range of bytes in
 *  the specified fopen'd file. Returns base64.
 *
 */

int calculate_MD5_range (FILE *fp, long r1, long r2, DIGESTS *dig)
    {
    int num;
    int range;
    unsigned char *buf;

    MD5Context *md5 = 0;
    SHA1Context *sha1 = 0;

    range = r2 - r1;

    /* position to the beginning of range */
    fseek (fp, r1, SEEK_SET);

    buf = (unsigned char *) XP_CALLOC (1, range);
    if (buf == NULL)
      {
      zigprint ("out of memory\n");
      return -1;
      }
 
    if ((num = fread (buf, 1, range, fp)) != range)
      zigprint ("zigbert: short read, expected %d, got %d\n", range, num);

    md5 = MD5_NewContext();
    sha1 = SHA1_NewContext();

    if (md5 == NULL || sha1 == NULL) 
      {
      zigprint ("can't generate digest context\n");
      return -1;
      }

    MD5_Begin (md5);
    SHA1_Begin (sha1);

    MD5_Update (md5, buf, range);
    SHA1_Update (sha1, buf, range);

    MD5_End (md5, dig->md5, &dig->md5_length, MD5_LENGTH);
    SHA1_End (sha1, dig->sha1, &dig->sha1_length, SHA1_LENGTH);

    MD5_DestroyContext (md5, PR_TRUE);
    SHA1_DestroyContext (sha1, PR_TRUE);

    XP_FREE (buf);

    return 0;
    }

/*
 *  g e n e r a t e _ S F _ f i l e 
 *
 *  From the supplied manifest file, calculates
 *  digests on the various sections, creating a .SF
 *  file in the process.
 * 
 */

static int generate_SF_file (char *manifile, char *who)
  {
  FILE *sf;
  FILE *mf;

  long r1, r2, r3;

  char whofile [256];
  unsigned char *buf, *name;

  DIGESTS dig;

  int line = 0;

  strcpy (whofile, who);
  /* strcat (whofile, ".sf"); */

  if ((mf = fopen (manifile, "rb")) == NULL)
    {
    perror (manifile);
    return -1;
    }

  if ((sf = fopen (whofile, "wb")) == NULL)
    {
    perror (who);
    return -1;
    }

  buf = (unsigned char *) XP_CALLOC (1, BUFSIZ);
  name = (unsigned char *) XP_CALLOC (1, BUFSIZ);

  if (buf == NULL || name == NULL)
    {
    zigprint ("out of memory\n");
    return -1;
    }

  fprintf (sf, "Signature-Version: 1.0\n");
  fprintf (sf, "Created-By: %s\n", CREATOR);
  fprintf (sf, "Comments: %s\n", BREAKAGE);

  if (fgets (buf, BUFSIZ, mf) == NULL)
    {
    zigprint ("zigbert: empty manifest file!\n");
    return -1;
    }

  if (strncmp (buf, "Manifest-Version:", 17))
    {
    zigprint ("zigbert: not a manifest file!\n");
    return -1;
    }

  fseek (mf, 0L, SEEK_SET);

  /* Process blocks of headers, and calculate their hashen */

  while (1)
    {
    /* Beginning range */
    r1 = ftell (mf);

    if (fgets (name, BUFSIZ, mf) == NULL)
      break;

    line++;

    if (r1 != 0 && strncmp (name, "Name:", 5))
      {
      zigprint ("warning: unexpected input in manifest file \"%s\" at line %d:\n", manifile, line);
      zigprint ("%s\n", name);
      }

    while (fgets (buf, BUFSIZ, mf))
      {
      if (*buf == 0 || *buf == '\n' || *buf == '\r')
        break;

      line++;

      /* Ending range for hashing */
      r2 = ftell (mf);
      }

    r3 = ftell (mf);

    if (r1)
      {
      fprintf (sf, "\n");
      fprintf (sf, "%s", name);
      }

    calculate_MD5_range (mf, r1, r2, &dig);

    if (optimize == 0)
      {
      fprintf (sf, "Digest-Algorithms: MD5 SHA1\n");
      fprintf (sf, "MD5-Digest: %s\n", BTOA_DataToAscii (dig.md5, dig.md5_length));
      }

    fprintf (sf, "SHA1-Digest: %s\n", BTOA_DataToAscii (dig.sha1, dig.sha1_length));

    /* restore normalcy after changing offset position */
    fseek (mf, r3, SEEK_SET);
    }

  XP_FREE (buf);
  XP_FREE (name);

  fclose (sf);
  fclose (mf);

  return 0;
  }

/*
 *  i s _ d i r
 *
 *  Return 1 if file is a directory.
 *  Wonder if this runs on a mac, trust not.
 *
 */

#ifdef XP_MAC
static int is_dir (char *filename)
  {
  return 0;
  }
#else
static int is_dir (char *filename)
  {
  struct stat sb;

  if (stat (filename, &sb) < 0)
    {
    zigprint ("%s: %d\n", filename, errno);
    return 0;
    }

  if ((sb.st_mode & S_IFMT) == S_IFDIR)
    return 1;

  return 0;
  }
#endif

/*

extern SECStatus SEC_PKCS7Encode 
  (
  SEC_PKCS7ContentInfo *cinfo,
  SEC_PKCS7EncoderOutputCallback outputfn,
  void *outputarg,
  SECItem *bulkkey,
  SECKEYGetPasswordKey pwfn,
  void *pwfnarg
  );

*/

/*** SIGNING MODULE ***/

extern void SEC_Init(void); /* XXX */
extern SECStatus SECU_PKCS11Init(void); /* XXX */

static int do_signing (char *dir)
  {
  FILE *in, *out;

  char sf_file [256];
  char pk7_file [256];

  strcpy (sf_file, dir);
  strcat (sf_file, "/META-INF/zigbert.sf");

  strcpy (pk7_file, dir);
  strcat (pk7_file, "/META-INF/zigbert.rsa");

  in = fopen (sf_file, "rb");
  out = fopen (pk7_file, "wb");

  if (in == NULL || out == NULL)
    {
    zigprint ("error opening files for signing\n");
    return -1;
    }

  return create_pk7 (in, out);
  }

static SECKEYKeyDBHandle *OpenKeyDB (char *progName)
  {
  int ret;
  char *fName;

  struct stat stat_buf;
  SECKEYKeyDBHandle *keyHandle;

  fName = SECU_DatabaseFileName(xpKeyDB);

  printf ("using key database: %s\n", fName);

  ret = stat (fName, &stat_buf);
  if (ret < 0) 
    {
    if (errno == ENOENT) 
      {
      /* no key.db */
      SECU_PrintError (progName, "unable to locate key database");
      return NULL;
      }
    else 
      {
      /* stat error */
      SECU_PrintError (progName, "stat: ", strerror(errno));
      return NULL;
      }
    }

   keyHandle = SECKEY_OpenKeyDBFilename (fName, PR_FALSE);
   if (keyHandle == NULL) 
     {
     SECU_PrintError (progName, "could not open key database %s", fName);
     return NULL;
     }

  return (keyHandle);
  }

static void SignOut (void *arg, const char *buf, unsigned long len)
  {
  fwrite (buf, len, 1, (FILE *) arg);
  }

/*
 *  p a s s w o r d _ h a r d c o d e 
 *
 *  A function to use the password passed in the -p(password) argument
 *  of the command line. This is only to be used for build & testing purposes,
 *  as it's extraordinarily insecure. 
 *
 *  After use once, null it out otherwise PKCS11 calls us forever.
 *
 */

char *password_hardcode (PK11SlotInfo *slot, void *arg)
  {
  char *pw;
  pw = password ? XP_STRDUP (password) : NULL;
  password = NULL;
  return pw;
  }

static int SignFile (FILE *outFile, FILE *inFile, CERTCertificate *cert)
  {
  int nb;
  char ibuf[4096], digestdata[32];
  SECHashObject *hashObj;
  void *hashcx;
  unsigned int len;

  SECItem digest;
  SEC_PKCS7ContentInfo *cinfo;
  SECStatus rv;

  SECCertUsage signwith;

  /* Right now use the SSL certificates (a hack until 
     object signing is available */

  signwith = certUsageObjectSigner;

  if (outFile == NULL || inFile == NULL || cert == NULL)
    return -1;

  /* XXX probably want to extend interface to allow other hash algorithms */
  hashObj = &SECHashObjects[HASH_AlgSHA1];

  hashcx = (* hashObj->create)();
  if (hashcx == NULL)
    return -1;

  (* hashObj->begin)(hashcx);

  for (;;) 
    {
    if (feof(inFile)) break;
    nb = fread(ibuf, 1, sizeof(ibuf), inFile);
    if (nb == 0) 
      {
      if (ferror(inFile)) 
        {
        PORT_SetError(SEC_ERROR_IO);
	(* hashObj->destroy)(hashcx, PR_TRUE);
	return -1;
        }
      /* eof */
      break;
      }
    (* hashObj->update)(hashcx, ibuf, nb);
    }

  (* hashObj->end)(hashcx, digestdata, &len, 32);
  (* hashObj->destroy)(hashcx, PR_TRUE);

  digest.data = digestdata;
  digest.len = len;

  /* XXX Need a better way to handle that usage stuff! */
  cinfo = SEC_PKCS7CreateSignedData 
              (cert, signwith, NULL, SEC_OID_SHA1, &digest, NULL, NULL);

  if (cinfo == NULL)
    return -1;

  rv = SEC_PKCS7IncludeCertChain (cinfo, NULL);
  if (rv != SECSuccess) 
    {
    SEC_PKCS7DestroyContentInfo (cinfo);
    return -1;
    }

  if (no_time == 0)
    {
    rv = SEC_PKCS7AddSigningTime (cinfo);
    if (rv != SECSuccess)
      {
      /* don't check error */
      }
    }

  if (password)
    {
    PK11_SetPasswordFunc (password_hardcode);

    rv = SEC_PKCS7Encode 
               (cinfo, SignOut, outFile, NULL, /*password_hardcode*/ NULL, NULL);
    }
  else
    {
    rv = SEC_PKCS7Encode 
               (cinfo, SignOut, outFile, NULL, SECU_GetPassword, NULL);
    }

  SEC_PKCS7DestroyContentInfo (cinfo);

  if (rv != SECSuccess)
    return -1;

  return 0;
  }

static int create_pk7 (FILE *inFile, FILE *outFile)
  {
  SECStatus rv;

  CERTCertificate *cert;

  SECKEYKeyDBHandle *keyHandle;
  CERTCertDBHandle *certHandle;

  /* Call the libsec initialization routines */
  PR_Init ("sign", 1, 1, 0);

  /* ugly */
  SECU_ConfigDirectory (cert_dir);

  rv = SECU_PKCS11Init();
  if (rv != SECSuccess)
      return -1;

  SEC_Init();

  /* open key database */
  keyHandle = OpenKeyDB ("zigbert");

  if (keyHandle == NULL) 
    return -1; 

  SECKEY_SetDefaultKeyDB (keyHandle);

  /* open cert database */
  certHandle = OpenCertDB(); 

  if (certHandle == NULL) 
    return -1;

  /* find cert */
  cert = CERT_FindCertByNickname (certHandle, keyName);

#if 0
  /* find cert */
  /* below call works when the nickname index is trashed */
  ugly_find_nickname (certHandle, keyName);
  cert = ugly;
#endif

  if (cert == NULL) 
    {
    SECU_PrintError
      (
      "zigbert",
      "the corresponding cert for key \"%s\" does not exist",
      keyName
      );
    return -1;
    }

  CERT_SetDefaultCertDB (certHandle);

  if (SignFile (outFile, inFile, cert)) 
    {
    fprintf (stderr, "%s: PROBLEM signing data (%s)\n",
		"zigbert", SECU_ErrorString ((int16) PORT_GetError()));
    return -1;
    }

  return 0;
  }

/* There is a separate copy here than in ns/security/cmd/lib/secutil.c 
   to allow a base name "cert_dir" to be used */

static char *
certDBNameCallback(void *arg, int dbVersion)
{
    char *fnarg;
    char *dir;
    char *filename;
    
    dir = SECU_ConfigDirectory(cert_dir);
 
    switch ( dbVersion ) {
      case 7:
        fnarg = "7";
        break;
      case 6:
	fnarg = "6";
	break;
      case 5:
	fnarg = "5";
	break;
      case 4:
      default:
	fnarg = "";
	break;
    }
    filename = PR_smprintf("%s/cert%s.db", dir, fnarg);
    return(filename);
}

static char *progName = "zigbert";

static CERTCertDBHandle
*OpenCertDB(void)
  /* NOTE: This routine has been modified to allow the libsec/pcertdb.c
   * routines to automatically find and convert the old cert database
   * into the new v3.0 format (cert db version 5).
   */
{
    CERTCertDBHandle *certHandle;
    SECStatus rv;

    /* Allocate a handle to fill with CERT_OpenCertDB below */
    certHandle = (CERTCertDBHandle *)PORT_ZAlloc(sizeof(CERTCertDBHandle));
    if (!certHandle) {
	SECU_PrintError(progName, "unable to get database handle");
	return NULL;
    }

    rv = CERT_OpenCertDB(certHandle, FALSE, certDBNameCallback, NULL);

    if (rv) {
	SECU_PrintError(progName, "could not open certificate database");
	if (certHandle) free (certHandle);  /* we don't want to leave anything behind... */
	return NULL;
    } else {
	CERT_SetDefaultCertDB(certHandle);
    }

    return certHandle;
}

/******************** HACKY ****************************/

static SECStatus
ugly_find_nick_trav (CERTCertificate *cert, SECItem *k, void *data)
{
    char *name;
    char *target;

    target = (char *) data;

    if ( cert->dbEntry ) 
	{
	name = cert->dbEntry->nickname;
	if ( name == NULL )
	    name = cert->emailAddr;
	if (!XP_STRCMP (target, name))
		{
		printf ("FOUND: %s\n", name);
		ugly = CERT_DupCertificate (cert);
		}
	}

    return (SECSuccess);
}

static int
ugly_find_nickname (CERTCertDBHandle *handle, char *nickname)
{
    int rv;
    ugly = NULL;
    rv = SEC_TraversePermCerts 
		(handle, ugly_find_nick_trav, (void *) nickname);
    if (rv)
	return -1;
    return 0;
}

/*************************/


static SECStatus cert_trav_callback (CERTCertificate *cert, SECItem *k, void *data)
  {
  int trusted;

  char *name;
  char **ugly_list;

  ugly_list = (char **) data;

  if (cert->dbEntry)
    {
    name = cert->dbEntry->nickname;

    if (name == NULL)
      name = cert->emailAddr;

    trusted = cert->trust->objectSigningFlags & CERTDB_USER;

    /* Add this name or email to list */

    if (name)
      {
      *ugly_list = XP_REALLOC (*ugly_list, XP_STRLEN (*ugly_list) + XP_STRLEN (name) + 4);

      if (**ugly_list)
        XP_STRCAT (*ugly_list, trusted ? "\n* " : "\n  ");
      else
        XP_STRCAT (*ugly_list, trusted ? "* " : "  ");

      XP_STRCAT (*ugly_list, name);
      }
    }

  return (SECSuccess);
  }

static int ugly_trav (char *key)
  {
  SECStatus rv;

  CERTCertificate *cert;

  CERTCertDBHandle *certHandle;

  char *ugly_list;


  /* Call the libsec initialization routines */
  PR_Init ("sign", 1, 1, 0);

  /* ugly */
  SECU_ConfigDirectory (cert_dir);

  rv = SECU_PKCS11Init();
  if (rv != SECSuccess)
      return -1;

  SEC_Init();


  /* open cert database */
  certHandle = OpenCertDB(); 

  if (certHandle == NULL) 
    return -1;



  ugly_list = XP_CALLOC (1, 16);

  if (ugly_list == NULL)
    return -1;

  *ugly_list = 0;

  rv = SEC_TraversePermCerts 
          (certHandle, cert_trav_callback, (void *) &ugly_list);

  if (rv)
    return -1;

  printf ("S Certificates\n");
  printf ("- ------------\n");
  printf ("%s\n", ugly_list);
  printf ("------------\n");
  printf ("Certificates that can be used to sign objects have *'s to their left.\n");

  if (key)
    {
    SECStatus rv;
    CERTCertificate *issuerCert;

    cert = CERT_FindCertByNickname (certHandle, key);

    if (cert)
      {
      printf ("The cert with nickname \"%s\" was found:\n", cert->nickname);
      printf ("  subject name: %s\n", cert->subjectName);
      printf ("   issuer name: %s\n", cert->issuerName);

      rv = CERT_CertTimesValid (cert);
      if (rv != SECSuccess)
        printf ("certificate %s is expired\n", cert->nickname);
      else
        printf ("(this cert is not expired)\n");


      rv = CERT_VerifyCertNow (certHandle, cert, PR_TRUE, certUsageObjectSigner, NULL);
      if (rv != SECSuccess)
        printf ("certificate %s is not valid\n", cert->nickname);
      else
        printf ("(this cert is valid)\n");


      issuerCert = CERT_FindCertIssuer (cert);

      rv = CERT_CertTimesValid (issuerCert);
      if (rv != SECSuccess)
        printf ("certificate's issuer cert, %s, is expired\n", issuerCert->nickname);
      else
        printf ("(the issuer cert is not expired)\n");


      rv = CERT_VerifyCertNow (certHandle, issuerCert, PR_TRUE, certUsageObjectSigner, NULL);
      if (rv != SECSuccess)
        printf ("issuer certificate %s is not valid\n", issuerCert->nickname);
      else
        printf ("(the issuer cert is valid)\n");
      }
    else
      printf ("The cert with nickname \"%s\" was NOT FOUND\n", issuerCert->nickname);
    }

  return 0;
  }

static int zig_list (char *filename)
  {
  FILE *fp;

  int ret;
  int status;

  ZIG *zig;
  SOBITEM *it;

  init_crypto();

  zig = SOB_new();

  if ((fp = fopen (filename, "r")) == NULL)
    {
    perror (filename);
    exit (1);
    }
  else
    fclose (fp);

  status = SOB_pass_archive (ZIG_F_GUESS, filename, "some-url", zig);

  if (status < 0 || zig->valid < 0)
    {
    zigprint ("NOTE -- \"%s\" archive DID NOT PASS crypto verification.\n", filename);
    if (status < 0)
      zigprint ("  (purported reason: %s -- may be incorrect)\n", 
			SECU_ErrorString ((int16) PORT_GetError()));
    zigprint ("entries shown below will have their digests checked only.\n"); 
    zig->valid = 0;
    }
  else
    zigprint ("archive \"%s\" has passed crypto verification.\n", filename);

  if (SOB_set_nailed (zig, 1) < 0)
    return -1;

  verify_global (zig);

  zigprint ("\n");
  zigprint ("%16s   %s\n", "status", "path");
  zigprint ("%16s   %s\n", "------------", "-------------------");

  SOB_find (zig, "*", ZIG_MF);

  while (SOB_find_next (zig, &it) >= 0)
    {
    if (it && it->pathname)
      {
      ret = SOB_verified_extract (zig, it->pathname, "output");
      zigprint ("%16s   %s\n", 
        ret >= 0 ? "verified" : "NOT VERIFIED", it->pathname);
      }
    }

  if (status < 0 || zig->valid < 0)
    zigprint ("\nNOTE -- \"%s\" archive DID NOT PASS crypto verification.\n", filename);

  SOB_destroy (&zig);

  return 0;
  }

static int zig_who (char *filename)
  {
  FILE *fp;

  ZIG *zig;
  int status;

  SOBITEM *it;
  FINGERZIG *fing;

  CERTCertificate *cert;

  init_crypto();

  zig = SOB_new();

  if ((fp = fopen (filename, "r")) == NULL)
    {
    perror (filename);
    exit (1);
    }
  else
    fclose (fp);

  status = SOB_pass_archive (ZIG_F_GUESS, filename, "some-url", zig);

  if (status < 0 || zig->valid < 0)
    {
    zigprint ("NOTE -- \"%s\" archive DID NOT PASS crypto verification.\n", filename);
    if (zig->valid < 0 || status != -1)
      {
      zigprint ("  (purported reason: %s (%d) -- may be incorrect)\n", 
            SECU_ErrorString (PORT_GetError()), status);
      }
    }

  printf ("\nSigner information:\n\n");

  SOB_find (zig, "*", ZIG_FP2);

  while (SOB_find_next (zig, &it) >= 0)
    {
    fing = (FINGERZIG *) it->data;
    cert = fing->cert;
    if (cert)
      {
      if (cert->nickname) 
        printf ("nickname: %s\n", cert->nickname);
      if (cert->subjectName)
        printf ("subject name: %s\n", cert->subjectName);
      if (cert->issuerName)
        printf ("issuer name: %s\n", cert->issuerName);
      }
    else
      printf ("no cert could be found\n");
    }

  printf ("\nDumping the cert chain found in this signature:\n\n");

  SOB_find (zig, "*", ZIG_WALK);

  while (SOB_find_next (zig, &it) >= 0)
    {
    fing = (FINGERZIG *) it->data;
    cert = fing->cert;
    if (cert)
      {
      if (cert->nickname) 
        printf ("nickname: %s\n", cert->nickname);
      if (cert->subjectName)
        printf ("subject name: %s\n", cert->subjectName);
      if (cert->issuerName)
        printf ("issuer name: %s\n", cert->issuerName);
      }
    else
      printf ("no cert could be found\n");
    }

  SOB_destroy (&zig);
  }

static int verify_global (ZIG *zig)
  {
  FILE *fp;

  char *ext;

  SOBITEM *it;
  DIGESTS dig, *globaldig;

  char buf [BUFSIZ];

  unsigned char *md5_digest, *sha1_digest;

  SOB_find (zig, "*", ZIG_PHY);

  while (SOB_find_next (zig, &it) >= 0)
    {
    if (!XP_STRNCMP (it->pathname, "META-INF", 8))
      {
      for (ext = it->pathname; *ext; ext++);
      while (ext > it->pathname && *ext != '.') ext--;

      if (!XP_STRCASECMP (ext, ".rsa"))
        zigprint ("found a RSA signature file: %s\n", it->pathname);

      if (!XP_STRCASECMP (ext, ".dsa"))
        zigprint ("found a DSA signature file: %s\n", it->pathname);

      if (!XP_STRCASECMP (ext, ".mf"))
        zigprint ("found a MF master manifest file: %s\n", it->pathname);

      if (!XP_STRCASECMP (ext, ".sf"))
        {
        zigprint ("found a SF signature manifest file: %s\n", it->pathname);

        if (SOB_extract (zig, it->pathname, "output") < 0)
          {
          zigprint ("error extracting %s\n", it->pathname);
          continue;
          }

        md5_digest = NULL;
        sha1_digest = NULL;

        if ((fp = fopen ("output", "rb")) != NULL)
          {
          while (fgets (buf, BUFSIZ, fp))
            {
            char *s;

            if (*buf == 0 || *buf == '\n' || *buf == '\r')
              break;

            for (s = buf; *s && *s != '\n' && *s != '\r'; s++);
            *s = 0;

            if (!XP_STRNCMP (buf, "MD5-Digest: ", 12))
              {
              md5_digest = ATOB_AsciiToData (buf + 12, &dig.md5_length);
              }

            if (!XP_STRNCMP (buf, "SHA1-Digest: ", 13))
              {
              sha1_digest = ATOB_AsciiToData (buf + 13, &dig.sha1_length);
              }

            if (!XP_STRNCMP (buf, "SHA-Digest: ", 12))
              {
              sha1_digest = ATOB_AsciiToData (buf + 12, &dig.sha1_length);
              }
            }

          globaldig = zig->globalmeta;

          if (globaldig && md5_digest)
             {
             zigprint ("  md5 digest on global metainfo: %s\n", 
                 XP_MEMCMP (md5_digest, globaldig->md5, MD5_LENGTH) ? "no match" : "match");
             }

          if (globaldig && sha1_digest)
             {
             zigprint ("  sha digest on global metainfo: %s\n", 
                 XP_MEMCMP (sha1_digest, globaldig->sha1, SHA1_LENGTH) ? "no match" : "match");
             }

          if (globaldig == NULL)
             zigprint ("global metadigest is not available, strange.\n");

          fclose (fp);
          }
        }
      }
    }

  return 0;
  }

/* this is only used for zigbert -v to view archives */

static int init_crypto (void)
  {
  SECStatus rv;
  static int prior = 0;

  CERTCertDBHandle *certHandle;

  if (prior == 0)
    {
    /* Call the libsec initialization routines */
    PR_Init ("verify", 1, 1, 0);

    /* ugly */
    SECU_ConfigDirectory (cert_dir);

    rv = SECU_PKCS11Init();
    if (rv != SECSuccess)
        return -1;

    SEC_Init();

    prior = 1;

    /* open cert database */
    certHandle = OpenCertDB(); 

    if (certHandle == NULL) 
      return -1;

    CERT_SetDefaultCertDB (certHandle);
    }

  return 0;
  }


/* Windows foolishness follows */

#ifdef _WINDOWS

char *NOT_NULL (const char *x)
  {
  return (char *) x;
  }

void XP_AssertAtLine (char *pFileName, int iLine)
  {
  abort();
  }

void SSL_InitHashLock (void)
  {
  }

void SSL3_Init (void)
  {
  }

void FE_Trace (const char *msg)
  {
  }

RPC_STATUS __stdcall UuidCreate (UUID * Uuid)
  {
  return 0;
  }

#endif

/*
 *  get_default_cert_dir
 *
 *  Attempt to locate a certificate directory.
 *  Failing that, complain that the user needs to
 *  use the -d(irectory) parameter.
 *
 */

static char *get_default_cert_dir (void)
  {
  char *home;

  char *cd = NULL;
  static char db [256];

#ifdef XP_UNIX
  home = getenv ("HOME");

  if (home && *home)
    {
    sprintf (db, "%s/.netscape", home);
    cd = db;
    }
#endif

#ifdef XP_PC
  FILE *fp;

  /* first check the environment override */

  home = getenv ("ZIG_HOME");

  if (home && *home)
    {
    sprintf (db, "%s/cert7.db", home);

    if ((fp = fopen (db, "r")) != NULL)
      {
      fclose (fp);
      cd = home;
      }
    }

  /* try the old navigator directory */

  if (cd == NULL)
    {
    home = "c:/Program Files/Netscape/Navigator";

    sprintf (db, "%s/cert7.db", home);

    if ((fp = fopen (db, "r")) != NULL)
      {
      fclose (fp);
      cd = home;
      }
    }

  /* maybe USER= is defined.. */

  if (cd == NULL)
    {
    home = getenv ("USER");

    if (home && *home)
      {
      sprintf (db, "c:/Program Files/Netscape/Users/%s/cert7.db", home);
      if ((fp = fopen (db, "r")) != NULL)
        {
        fclose (fp);
        cd = home;
        }
      }
    }
#endif

  if (!cd)
    {
    zigprint ("You must specify the location of your certificate directory\n");
    zigprint ("with the -d option. Example: -d ~/.netscape in many cases with Unix.\n");
    exit (0);
    }

  return cd;
  }