Mega Code Archive

 
Categories / Java / Development Class
 

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 http://www.davidflanagan.com/javaexamples2.  */ import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.security.DigestInputStream; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.UnrecoverableKeyException; 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)         filelist.add(args[i]);       else         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.");       return;     }     // 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(           keystoreFile));       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);     else       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.     Collections.sort(filelist);     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           .toCharArray());       // No prepare to create a signature for the specified signer       signature.initSign(key);     }     // 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);         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(filename.getBytes());         signature.update(digest);       }       // Store the filename and the encoded digest bytes in the manifest       manifest.put(filename, hexEncode(digest));       System.out.print('.');       System.out.flush();     }     // If a signer was specified, compute 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.store(f, "Manifest message digests");     metadata.store(f, "Manifest metadata");     System.out.println("done");   }   /**    * 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         .getProperty("__META.SIGNATUREALGORITHM");     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"))         files.add(s);     }     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...");       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.       Collections.sort(files);       // 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)           .getPublicKey();       signature.initVerify(publickey);       // 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);         signature.update(filename.getBytes());         signature.update(hexDecode(manifest.getProperty(filename)));       }       // 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");         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 < 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);         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 '" + 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       System.out.print(".");       System.out.flush();     }     // And tell the user we're done with verification.     System.out.println("done.");   }   /**    * 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     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();   }   /** 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");     }   } }