FileDocCategorySizeDatePackage
Manifest.javaAPI DocExample13749Mon Sep 22 13:30:32 BST 1997None

Manifest

public class Manifest extends Object

Fields Summary
public static byte[]
buffer
This static buffer is used by getFileDigest() above
static final char[]
digits
This array is used to convert from bytes to hexadecimal numbers
Constructors Summary
Methods Summary
public static voidcreate(java.lang.String manifestfile, java.lang.String digestAlgorithm, java.lang.String signername, java.lang.String signatureAlgorithm, java.util.Vector filelist)
This method creates a manifest file with the specified name, for the specified vector of files, using the named message digest algorithm. If signername is non-null, it adds a digital signature to the manifest, using the named signature algorithm. This method can throw a bunch of exceptions.

    // For computing a signature, we have to process the files in a fixed,
    // repeatable order, so copy the filenames into an array and sort it.
    // Use the Sorter class from Chapter 2.
    String[] files = new String[filelist.size()];
    filelist.copyInto(files);
    Sorter.sortAscii(files);
    
    Properties manifest = new Properties(), metadata = new Properties();
    MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
    Signature signature = null;
    byte[] digest;
    
    // If a signer name has been specified, then prepare to sign the manifest
    if (signername != null) {
      // Look up the signer object
      Signer signer = 
        (Signer)IdentityScope.getSystemScope().getIdentity(signername);
      // Get a Signature object
      signature = Signature.getInstance(signatureAlgorithm);
      // And prepare to create a signature for the specified signer
      signature.initSign(signer.getPrivateKey());
    }
    
    // Now, loop through the files, in a well-known alphabetical order
    System.out.print("Computing message digests");
    for(int i = 0; i < files.length; i++) {
      // Compute the digest for each one, and skip files that don't exist.
      try { digest = getFileDigest(files[i], md); } 
      catch (IOException e) {
        System.err.println("\nSkipping " + files[i] + ": " + e);
        continue;
      }
      // If we're computing a signature, use the bytes of the filename and
      // of the digest as part of the data to sign.
      if (signature != null) {
        signature.update(files[i].getBytes());
        signature.update(digest);
      }
      // Store the filename and the encoded digest bytes in the manifest
      manifest.put(files[i], hexEncode(digest));
      System.out.print('.");
      System.out.flush();
    }
    
    // If a signer was specified, compute digital signature for the manifest
    byte[] signaturebytes = null;
    if (signature != null) {
      System.out.print("done\nComputing digital signature...");
      System.out.flush();
      
      // Compute the digital signature by encrypting a message digest of all
      // the bytes passed to the update() method using the private key of the
      // signer.  This is a time consuming operation.
      signaturebytes = signature.sign();
    }

    // Tell the user what comes next
    System.out.print("done\nWriting manifest...");
    System.out.flush();

    // Store some metadata about this manifest, including the name of the
    // message digest algorithm it uses
    metadata.put("__META.DIGESTALGORITHM", digestAlgorithm);
    // If we're signing the manifest, store some more metadata
    if (signername != null) {
      // Store the name of the signer
      metadata.put("__META.SIGNER", signername);
      // Store the name of the algorithm
      metadata.put("__META.SIGNATUREALGORITHM", signatureAlgorithm);
      // And generate the signature, encode it, and store it
      metadata.put("__META.SIGNATURE", hexEncode(signaturebytes));
    }

    // Now, save the manifest data and the metadata to the manifest file
    FileOutputStream f = new FileOutputStream(manifestfile);
    manifest.save(f, "Manifest message digests");
    metadata.save(f, "Manifest metadata");
    System.out.println("done");
  
public static byte[]getFileDigest(java.lang.String filename, java.security.MessageDigest md)
This convenience method is used by both create() and verify(). It reads the contents of a named file and computes a message digest for it, using the specified MessageDigest object.

    // Make sure there is nothing left behind in the MessageDigest
    md.reset();

    // Create a stream to read from the file and compute the digest
    DigestInputStream in = 
      new DigestInputStream(new FileInputStream(filename),md);

    // Read to the end of the file, discarding everything we read.
    // The DigestInputStream automatically passes all the bytes read to
    // the update() method of the MessageDigest
    while(in.read(buffer) != -1) /* do nothing */ ;

    // Finally, compute and return the digest value.
    return md.digest();
  
public static byte[]hexDecode(java.lang.String s)
A convenience method to convert in the other direction, from a string of hexadecimal digits to an array of bytes.

    try {
      int len = s.length();
      byte[] r = new byte[len/2];
      for(int i = 0; i < r.length; i++) {
        int digit1 = s.charAt(i*2), digit2 = s.charAt(i*2 + 1);
        if ((digit1 >= '0") && (digit1 <= '9")) digit1 -= '0";
        else if ((digit1 >= 'a") && (digit1 <= 'f")) digit1 -= 'a" - 10;
        if ((digit2 >= '0") && (digit2 <= '9")) digit2 -= '0";
        else if ((digit2 >= 'a") && (digit2 <= 'f")) digit2 -= 'a" - 10;
        r[i] = (byte)((digit1 << 4) + digit2);
      }
      return r;
    }
    catch (Exception e) {
      throw new IllegalArgumentException("hexDecode(): invalid input");
    }
  
public static java.lang.StringhexEncode(byte[] bytes)
A convenience method to convert an array of bytes to a String. We do this simply by converting each byte to two hexadecimal digits. Something like Base 64 encoding is more compact, but harder to encode.


                                           
       
    StringBuffer s = new StringBuffer(bytes.length * 2);
    for(int i = 0; i < bytes.length; i++) {
      byte b = bytes[i];
      s.append(digits[(b & 0xf0) >> 4]);
      s.append(digits[b & 0x0f]);
    }
    return s.toString();
  
public static voidmain(java.lang.String[] args)
This program creates a manifest file for the specified files, or verifies an existing manifest file. By default the manifest file is named MANIFEST, but the -m option can be used to override this. The -v option specifies that the manifest should be verified. Verification is also the default option if no files are specified.

    try {
      // Set the default values of the command-line arguments
      boolean verify = false;              // Verify manifest or create one?
      String manifestfile = "MANIFEST";    // Manifest file name
      String digestAlgorithm = "MD5";      // Algorithm for message digests
      String signername = null;            // Signer.  No signature by default
      String signatureAlgorithm = "DSA";   // Algorithm for digital signatures
      Vector filelist = new Vector();      // The list of files to digest
      
      // Parse the command-line arguments, overriding the defaults above
      for(int i = 0; i < args.length; i++) {
        if (args[i].equals("-v")) verify = true;
        else if (args[i].equals("-m")) manifestfile = args[++i];
        else if (args[i].equals("-da") && !verify) digestAlgorithm = args[++i];
        else if (args[i].equals("-s") && !verify) signername = args[++i];
        else if (args[i].equals("-sa") && !verify) 
          signatureAlgorithm = args[++i];
        else if (!verify) filelist.addElement(args[i]);
        else throw new IllegalArgumentException(args[i]);
      }

      // If -v was specified or no file were given, verify a manifest
      // Otherwise, create a new manifest for the specified files
      if (verify || (filelist.size() == 0)) verify(manifestfile);
      else create(manifestfile, digestAlgorithm, 
                  signername, signatureAlgorithm, filelist);
    }
    // If anything goes wrong, display the exception, and print a usage message
    catch (Exception e) {
      System.err.println("\n" + e);
      System.err.println("Usage: java Manifest [-v] [-m <manifestfile>]\n" + 
                         "   or: java Manifest " +
                         "[-m <manifestfile>] [-da <digest algorithm>]\n" + 
                         "\t\t[-s <signer>] [-sa <signature algorithm>] " +
                         "files...");
    }
  
public static voidverify(java.lang.String manifestfile)
This method verifies the digital signature of the named manifest file, if it has one, and if that verification succeeds, it verifies the message digest of each file in filelist that is also named in the manifest. This method can throw a bunch of exceptions

    Properties manifest = new Properties();
    manifest.load(new FileInputStream(manifestfile));
    String digestAlgorithm = manifest.getProperty("__META.DIGESTALGORITHM");
    String signername = manifest.getProperty("__META.SIGNER");
    String signatureAlgorithm = 
      manifest.getProperty("__META.SIGNATUREALGORITHM");
    String hexsignature = manifest.getProperty("__META.SIGNATURE");

    // Get a list of filenames in the manifest.  Use an Enumeration to
    // get them into a Vector, then allocate an array and copy them into that.
    Vector filelist = new Vector();
    Enumeration names = manifest.propertyNames();
    while(names.hasMoreElements()) {
      String s = (String)names.nextElement();
      if (!s.startsWith("__META")) filelist.addElement(s);
    }
    String[] files = new String[filelist.size()];
    filelist.copyInto(files);

    // If the manifest contained metadata about a digital signature, then
    // verify that signature first
    if (signername != null) {
      System.out.print("Verifying digital signature...");
      System.out.flush();

      // To verify the signature, we must process the files in exactly the
      // same order we did when we created the signature.  We guarantee
      // this order by sorting the filenames.
      Sorter.sortAscii(files);

      // Get the Signer identity, create a Signature object, and initialize
      // it for signature verification, using the signer's public key
      Identity signer = 
        (Identity)IdentityScope.getSystemScope().getIdentity(signername);
      Signature signature = Signature.getInstance(signatureAlgorithm);
      signature.initVerify(signer.getPublicKey());

      // Now loop through these files in their known sorted order
      // For each one, send the bytes of the filename and of the digest
      // to the signature object for use in computing the signature.
      // It is important that this be done in exactly the same order when
      // verifying the signature as it was done when creating the signature.
      for(int i = 0; i < files.length; i++) {
        signature.update(files[i].getBytes());
        signature.update(hexDecode(manifest.getProperty(files[i])));
      }

      // Now decode the signature read from the manifest file and pass it
      // to the verify() method of the signature object.  If the signature
      // is not verified, print an error message and exit.
      if (!signature.verify(hexDecode(hexsignature))) {
        System.out.println("\nManifest has an invalid digital signature");
        System.exit(0);
      }
      
      // Tell the user we're done with this lengthy computation
      System.out.println("verified.");
    }

    // Tell the user we're starting the next phase of verification
    System.out.print("Verifying file message digests");
    System.out.flush();

    // Get a MessageDigest object to compute digests
    MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
    // Loop through all files
    for(int i = 0; i < files.length; i++) {
      // Look up the encoded digest from the manifest file
      String hexdigest = manifest.getProperty(files[i]);
      // Compute the digest for the file.
      byte[] digest;
      try { digest = getFileDigest(files[i], md); } 
      catch (IOException e) {
        System.out.println("\nSkipping " + files[i] + ": " + e);
        continue;
      }

      // Encode the computed digest and compare it to the encoded digest
      // from the manifest.  If they are not equal, print an error message.
      if (!hexdigest.equals(hexEncode(digest)))
        System.out.println("\nFile '" + files[i] + "' failed verification.");

      // Send one dot of output for each file we process.  Since computing
      // message digests takes some time, this lets the user know that the
      // program is functioning and making progress
      System.out.print("."); 
      System.out.flush();
    }
    // And tell the user we're done with verification.
    System.out.println("done.");