Manifest class : Manifest « Development Class « Java

Manifest class

 * Copyright (c) 2000 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 2nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book (recommended),
 * visit

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

 * 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.
public class Manifest {
  public static void main(String[] args) throws Exception {
    // 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 sig. by default
    String signatureAlgorithm = "DSA"; // Algorithm for digital sig.
    String password = null; // Private keys are protected
    File keystoreFile = null; // Where are keys stored
    String keystoreType = null; // What kind of keystore
    String keystorePassword = null; // How to access keystore
    List filelist = new ArrayList(); // The 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 (args[i].equals("-p"))
        password = args[++i];
      else if (args[i].equals("-keystore"))
        keystoreFile = new File(args[++i]);
      else if (args[i].equals("-keystoreType"))
        keystoreType = args[++i];
      else if (args[i].equals("-keystorePassword"))
        keystorePassword = args[++i];

      else if (!verify)
        throw new IllegalArgumentException(args[i]);

    // If certain arguments weren't supplied, get default values.
    if (keystoreFile == null) {
      File dir = new File(System.getProperty("user.home"));
      keystoreFile = new File(dir, ".keystore");
    if (keystoreType == null)
      keystoreType = KeyStore.getDefaultType();
    if (keystorePassword == null)
      keystorePassword = password;

    if (!verify && signername != null && password == null) {
      System.out.println("Use -p to specify a password.");

    // Get the keystore we'll use for signing or verifying signatures
    // If no password was provided, then assume we won't be dealing with
    // signatures, and skip the keystore.
    KeyStore keystore = null;
    if (keystorePassword != null) {
      keystore = KeyStore.getInstance(keystoreType);
      InputStream in = new BufferedInputStream(new FileInputStream(
      keystore.load(in, keystorePassword.toCharArray());

    // 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, keystore);
      create(manifestfile, digestAlgorithm, signername,
          signatureAlgorithm, keystore, password, 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.
  public static void create(String manifestfile, String digestAlgorithm,
      String signername, String signatureAlgorithm, KeyStore keystore,
      String password, List filelist) throws NoSuchAlgorithmException,
      InvalidKeyException, SignatureException, KeyStoreException,
      UnrecoverableKeyException, IOException {
    // For computing a signature, we have to process the files in a fixed,
    // repeatable order, so sort them alphabetically.
    int numfiles = filelist.size();

    Properties manifest = new Properties(), metadata = new Properties();
    MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
    Signature signature = null;
    byte[] digest;

    // If a signer name was specified, then prepare to sign the manifest
    if (signername != null) {
      // Get a Signature object
      signature = Signature.getInstance(signatureAlgorithm);

      // Look up the private key of the signer from the keystore
      PrivateKey key = (PrivateKey) keystore.getKey(signername, password

      // No prepare to create a signature for the specified signer

    // Now, loop through the files, in a well-known alphabetical order
    System.out.print("Computing message digests");
    for (int i = 0; i < numfiles; i++) {
      String filename = (String) filelist.get(i);
      // Compute the digest for each, and skip files that don't exist.
      try {
        digest = getFileDigest(filename, md);
      } catch (IOException e) {
        System.err.println("\nSkipping " + filename + ": " + e);
      // 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) {
      // Store the filename and the encoded digest bytes in the manifest
      manifest.put(filename, hexEncode(digest));

    // If a signer was specified, compute signature for the manifest
    byte[] signaturebytes = null;
    if (signature != null) {
      System.out.print("done\nComputing digital signature...");

      // 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...");

    // 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 message digests");, "Manifest metadata");

   * 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
  public static void verify(String manifestfile, KeyStore keystore)
      throws NoSuchAlgorithmException, SignatureException,
      InvalidKeyException, KeyStoreException, IOException {
    Properties manifest = new Properties();
    manifest.load(new FileInputStream(manifestfile));
    String digestAlgorithm = manifest.getProperty("__META.DIGESTALGORITHM");
    String signername = manifest.getProperty("__META.SIGNER");
    String signatureAlgorithm = manifest
    String hexsignature = manifest.getProperty("__META.SIGNATURE");

    // Get a list of filenames in the manifest.
    List files = new ArrayList();
    Enumeration names = manifest.propertyNames();
    while (names.hasMoreElements()) {
      String s = (String) names.nextElement();
      if (!s.startsWith("__META"))
    int numfiles = files.size();

    // If we've got a signature but no keystore, warn the user
    if (signername != null && keystore == null)
      System.out.println("Can't verify digital signature without "
          + "a keystore.");

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

      // 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.

      // Create a Signature object to do signature verification with.
      // Initialize it with the signer's public key from the keystore
      Signature signature = Signature.getInstance(signatureAlgorithm);
      PublicKey publickey = keystore.getCertificate(signername)

      // 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 < numfiles; i++) {
        String filename = (String) files.get(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 signature");

      // Tell the user we're done with this lengthy computation

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

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

      // 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 '" + filename
            + "' 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
    // And tell the user we're done with verification.

   * 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.
  public static byte[] getFileDigest(String filename, MessageDigest md)
      throws IOException {
    // Make sure there is nothing left behind in the MessageDigest

    // 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 ( != -1)
      /* do nothing */;

    // Finally, compute and return the digest value.
    return md.digest();

  /** This static buffer is used by getFileDigest() above */
  public static byte[] buffer = new byte[4096];

  /** This array is used to convert from bytes to hexadecimal numbers */
  static final char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
      '9', 'a', 'b', 'c', 'd', 'e', 'f' };

   * 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.
  public static String hexEncode(byte[] bytes) {
    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();

   * A convenience method to convert in the other direction, from a string of
   * hexadecimal digits to an array of bytes.
  public static byte[] hexDecode(String s) throws IllegalArgumentException {
    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");


Related examples in the same category