ProductDigest.java

  1. /*
  2.  * ProductDigest
  3.  */
  4. package gov.usgs.earthquake.product;

  5. import gov.usgs.earthquake.product.io.ObjectProductSource;
  6. import gov.usgs.earthquake.product.io.ProductHandler;
  7. import gov.usgs.earthquake.util.NullOutputStream;
  8. import gov.usgs.util.CryptoUtils;
  9. import gov.usgs.util.StreamUtils;
  10. import gov.usgs.util.XmlUtils;
  11. import gov.usgs.util.CryptoUtils.Version;

  12. import java.io.File;
  13. import java.net.URI;
  14. import java.net.URL;
  15. import java.nio.charset.Charset;
  16. import java.nio.charset.StandardCharsets;
  17. import java.security.DigestOutputStream;
  18. import java.security.KeyPair;
  19. import java.security.MessageDigest;
  20. import java.security.NoSuchAlgorithmException;
  21. import java.util.Base64;
  22. import java.util.Date;
  23. import java.util.Iterator;
  24. import java.util.logging.Logger;


  25. /**
  26.  * Used to generate product digests.
  27.  *
  28.  * All product attributes and content are used when generating a digest, except
  29.  * any existing signature, since the digest is used to generate or verify
  30.  * signatures.
  31.  *
  32.  * Calls to ProductOutput methods on this class must occur in identical order to
  33.  * generate consistent signatures. Therefore it is almost required to use the
  34.  * ObjectProductInput, which fulfills this requirement.
  35.  */
  36. public class ProductDigest implements ProductHandler {

  37.     /** Logging object. */
  38.     private static final Logger LOGGER = Logger.getLogger(ProductDigest.class
  39.             .getName());

  40.     /** Character set used when computing digests. */
  41.     public static final Charset CHARSET = StandardCharsets.UTF_8;

  42.     /** Algorithm used when generating product digest. */
  43.     public static final String MESSAGE_DIGEST_ALGORITHM = "SHA1";

  44.     /** v2 digest algorithm */
  45.     public static final String MESSAGE_DIGEST_V2_ALGORITHM = "SHA-256";

  46.     /** The stream used to compute the product digest. */
  47.     private DigestOutputStream digestStream;

  48.     /** The computed digest. */
  49.     private byte[] digest = null;

  50.     /** The signature version. */
  51.     private Version version = null;

  52.     /**
  53.      * Construct a new ProductDigest.
  54.      * @param version signature version
  55.      * @throws NoSuchAlgorithmException if not SHA1 or SHA-256
  56.      */
  57.     protected ProductDigest(final Version version) throws NoSuchAlgorithmException {
  58.         final String algorithm = version == Version.SIGNATURE_V2
  59.                 ? MESSAGE_DIGEST_V2_ALGORITHM
  60.                 : MESSAGE_DIGEST_ALGORITHM;
  61.         LOGGER.fine("Using digest version " + version.toString()
  62.                 + ", algorithm=" + algorithm);
  63.         MessageDigest digest = MessageDigest.getInstance(algorithm);
  64.         this.digestStream = new DigestOutputStream(new NullOutputStream(), digest);
  65.         this.version = version;
  66.     }

  67.     /**
  68.      * A convenience method that generates a product digest.
  69.      *
  70.      * @param product
  71.      *            the product to digest
  72.      * @return the computed digest.
  73.      * @throws Exception
  74.      *             if errors occur while digesting product.
  75.      */
  76.     public static byte[] digestProduct(final Product product) throws Exception {
  77.         return digestProduct(product, Version.SIGNATURE_V1);
  78.     }

  79.     /**
  80.      *
  81.      * @param product A product
  82.      * @param version What version of product digest
  83.      * @return A byte array of the product digest
  84.      * @throws Exception if error occurs
  85.      */
  86.     public static byte[] digestProduct(final Product product, final Version version)
  87.             throws Exception {
  88.         Date start = new Date();
  89.         ProductDigest productDigest = new ProductDigest(version);
  90.         // ObjectProductInput generates ProductOutput calls in a reliable order.
  91.         new ObjectProductSource(product).streamTo(productDigest);
  92.         Date end = new Date();

  93.         byte[] digest = productDigest.getDigest();
  94.         LOGGER.fine("Digest='" + Base64.getEncoder().encodeToString(digest)
  95.                 + "' , " + (end.getTime() - start.getTime()) + "ms");
  96.         return digest;
  97.     }

  98.     /**
  99.      * @return the computed digest, or null if not finished yet.
  100.      */
  101.     public byte[] getDigest() {
  102.         return digest;
  103.     }

  104.     /**
  105.      * Digest the id, update time, status, and URL.
  106.      */
  107.     public void onBeginProduct(ProductId id, String status, URL trackerURL)
  108.             throws Exception {
  109.         digestStream.write(id.toString().getBytes(CHARSET));
  110.         digestStream.write(XmlUtils.formatDate(id.getUpdateTime()).getBytes(
  111.                 CHARSET));
  112.         digestStream.write(status.getBytes(CHARSET));
  113.         if (this.version != Version.SIGNATURE_V2 && trackerURL != null) {
  114.             digestStream.write(trackerURL.toString().getBytes(CHARSET));
  115.         }
  116.     }

  117.     /**
  118.      * Digest the path, content attributes, and content bytes.
  119.      */
  120.     public void onContent(ProductId id, String path, Content content)
  121.             throws Exception {
  122.         digestStream.write(path.getBytes(CHARSET));
  123.         digestStream.write(content.getContentType().getBytes(CHARSET));
  124.         digestStream.write(XmlUtils.formatDate(content.getLastModified())
  125.                 .getBytes(CHARSET));
  126.         digestStream.write(content.getLength().toString().getBytes(CHARSET));
  127.         if (this.version == Version.SIGNATURE_V2) {
  128.             digestStream.write(content.getSha256().getBytes(CHARSET));
  129.         } else {
  130.             StreamUtils.transferStream(content.getInputStream(),
  131.                     new StreamUtils.UnclosableOutputStream(digestStream));
  132.         }
  133.     }

  134.     /**
  135.      * Finish computing digest.
  136.      */
  137.     public void onEndProduct(ProductId id) throws Exception {
  138.         // finish computing message digest.
  139.         digestStream.flush();
  140.         digest = digestStream.getMessageDigest().digest();
  141.     }

  142.     /**
  143.      * Digest the link relation and href.
  144.      */
  145.     public void onLink(ProductId id, String relation, URI href)
  146.             throws Exception {
  147.         digestStream.write(relation.getBytes(CHARSET));
  148.         digestStream.write(href.toString().getBytes(CHARSET));
  149.     }

  150.     /**
  151.      * Digest the property name and value.
  152.      */
  153.     public void onProperty(ProductId id, String name, String value)
  154.             throws Exception {
  155.         digestStream.write(name.getBytes(CHARSET));
  156.         digestStream.write(value.getBytes(CHARSET));
  157.     }

  158.     /**
  159.      * Don't digest signature version.
  160.      */
  161.     @Override
  162.     public void onSignatureVersion(ProductId id, Version version) throws Exception {
  163.         // generating signature, ignore
  164.     }

  165.     /**
  166.      * Don't digest the signature.
  167.      */
  168.     @Override
  169.     public void onSignature(ProductId id, String signature) throws Exception {
  170.         // generating signature, ignore
  171.     }

  172.     /**
  173.      * Free any resources associated with this handler.
  174.      */
  175.     @Override
  176.     public void close() {
  177.         StreamUtils.closeStream(digestStream);
  178.     }


  179.     /**
  180.      * CLI access into ProductDigest
  181.      * @param args CLI Args
  182.      * @throws Exception if error occurs
  183.      */
  184.     public static void main(final String[] args) throws Exception {
  185.         if (args.length == 0) {
  186.             System.err.println("Usage: ProductDigest FILE [FILE ...]");
  187.             System.err
  188.                     .println("where FILE is a file or directory to include in digest");
  189.             System.exit(1);
  190.         }

  191.         Product product = new Product(new ProductId("test", "test", "test"));
  192.         try {
  193.             product.setTrackerURL(new URL("http://localhost/tracker"));
  194.         } catch (Exception e) {
  195.             // ignore
  196.         }

  197.         // treat all arguments as files or directories to be added as content
  198.         for (String arg : args) {
  199.             File file = new File(arg);
  200.             if (!file.exists()) {
  201.                 System.err.println(file.getCanonicalPath() + " does not exist");
  202.                 System.exit(1);
  203.             }

  204.             if (file.isDirectory()) {
  205.                 product.getContents().putAll(
  206.                         FileContent.getDirectoryContents(file));
  207.             } else {
  208.                 product.getContents()
  209.                         .put(file.getName(), new FileContent(file));
  210.             }
  211.         }

  212.         long totalBytes = 0L;
  213.         Iterator<String> iter = product.getContents().keySet().iterator();
  214.         while (iter.hasNext()) {
  215.             totalBytes += product.getContents().get(iter.next()).getLength();
  216.         }

  217.         KeyPair keyPair = CryptoUtils.generateDSAKeyPair(CryptoUtils.DSA_1024);
  218.         Date start = new Date();
  219.         product.sign(keyPair.getPrivate());
  220.         Date end = new Date();
  221.         long elapsed = (end.getTime() - start.getTime());

  222.         System.err.println("Digested " + totalBytes + " bytes of content in "
  223.                 + elapsed + "ms");
  224.         System.err.println("Average rate = " + (totalBytes / (elapsed / 1000.0))
  225.                 + " bytes/second");
  226.     }

  227. }