jdk/src/jdk.dev/share/classes/sun/tools/jar/SignatureFile.java
changeset 29455 e451c01a5747
parent 29454 e5e9478e2ddb
parent 29433 c97e2d1bad97
child 29478 6637277d28cc
equal deleted inserted replaced
29454:e5e9478e2ddb 29455:e451c01a5747
     1 /*
       
     2  * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package sun.tools.jar;
       
    27 
       
    28 import java.io.*;
       
    29 import java.util.*;
       
    30 import java.security.*;
       
    31 
       
    32 import sun.net.www.MessageHeader;
       
    33 import java.util.Base64;
       
    34 
       
    35 
       
    36 import sun.security.pkcs.*;
       
    37 import sun.security.x509.AlgorithmId;
       
    38 
       
    39 /**
       
    40  * <p>A signature file as defined in the <a
       
    41  * href="manifest.html">Manifest and Signature Format</a>. It has
       
    42  * essentially the same structure as a Manifest file in that it is a
       
    43  * set of RFC 822 headers (sections). The first section contains meta
       
    44  * data relevant to the entire file (i.e "Signature-Version:1.0") and
       
    45  * each subsequent section contains data relevant to specific entries:
       
    46  * entry sections.
       
    47  *
       
    48  * <p>Each entry section contains the name of an entry (which must
       
    49  * have a counterpart in the manifest). Like the manifest it contains
       
    50  * a hash, the hash of the manifest section corresponding to the
       
    51  * name. Since the manifest entry contains the hash of the data, this
       
    52  * is equivalent to a signature of the data, plus the attributes of
       
    53  * the manifest entry.
       
    54  *
       
    55  * <p>This signature file format deal with PKCS7 encoded DSA signature
       
    56  * block. It should be straightforward to extent to support other
       
    57  * algorithms.
       
    58  *
       
    59  * @author      David Brown
       
    60  * @author      Benjamin Renaud */
       
    61 
       
    62 public class SignatureFile {
       
    63 
       
    64     /* Are we debugging? */
       
    65     static final boolean debug = false;
       
    66 
       
    67     /* list of headers that all pertain to a particular file in the
       
    68      * archive */
       
    69     private Vector<MessageHeader> entries = new Vector<>();
       
    70 
       
    71     /* Right now we only support SHA hashes */
       
    72     static final String[] hashes = {"SHA"};
       
    73 
       
    74     static final void debug(String s) {
       
    75         if (debug)
       
    76             System.out.println("sig> " + s);
       
    77     }
       
    78 
       
    79     /*
       
    80      * The manifest we're working with.  */
       
    81     private Manifest manifest;
       
    82 
       
    83     /*
       
    84      * The file name for the file. This is the raw name, i.e. the
       
    85      * extention-less 8 character name (such as MYSIGN) which wil be
       
    86      * used to build the signature filename (MYSIGN.SF) and the block
       
    87      * filename (MYSIGN.DSA) */
       
    88     private String rawName;
       
    89 
       
    90     /* The digital signature block corresponding to this signature
       
    91      * file.  */
       
    92     private PKCS7 signatureBlock;
       
    93 
       
    94 
       
    95     /**
       
    96      * Private constructor which takes a name a given signature
       
    97      * file. The name must be extension-less and less or equal to 8
       
    98      * character in length.  */
       
    99     private SignatureFile(String name) throws JarException {
       
   100 
       
   101         entries = new Vector<>();
       
   102 
       
   103         if (name != null) {
       
   104             if (name.length() > 8 || name.indexOf('.') != -1) {
       
   105                 throw new JarException("invalid file name");
       
   106             }
       
   107             rawName = name.toUpperCase(Locale.ENGLISH);
       
   108         }
       
   109     }
       
   110 
       
   111     /**
       
   112      * Private constructor which takes a name a given signature file
       
   113      * and a new file predicate. If it is a new file, a main header
       
   114      * will be added. */
       
   115     private SignatureFile(String name, boolean newFile)
       
   116     throws JarException {
       
   117 
       
   118         this(name);
       
   119 
       
   120         if (newFile) {
       
   121             MessageHeader globals = new MessageHeader();
       
   122             globals.set("Signature-Version", "1.0");
       
   123             entries.addElement(globals);
       
   124         }
       
   125     }
       
   126 
       
   127     /**
       
   128      * Constructs a new Signature file corresponding to a given
       
   129      * Manifest. All entries in the manifest are signed.
       
   130      *
       
   131      * @param manifest the manifest to use.
       
   132      *
       
   133      * @param name for this signature file. This should
       
   134      * be less than 8 characters, and without a suffix (i.e.
       
   135      * without a period in it.
       
   136      *
       
   137      * @exception JarException if an invalid name is passed in.
       
   138      */
       
   139     public SignatureFile(Manifest manifest, String name)
       
   140     throws JarException {
       
   141 
       
   142         this(name, true);
       
   143 
       
   144         this.manifest = manifest;
       
   145         Enumeration<MessageHeader> enum_ = manifest.entries();
       
   146         while (enum_.hasMoreElements()) {
       
   147             MessageHeader mh = enum_.nextElement();
       
   148             String entryName = mh.findValue("Name");
       
   149             if (entryName != null) {
       
   150                 add(entryName);
       
   151             }
       
   152         }
       
   153     }
       
   154 
       
   155     /**
       
   156      * Constructs a new Signature file corresponding to a given
       
   157      * Manifest. Specific entries in the manifest are signed.
       
   158      *
       
   159      * @param manifest the manifest to use.
       
   160      *
       
   161      * @param entries the entries to sign.
       
   162      *
       
   163      * @param filename for this signature file. This should
       
   164      * be less than 8 characters, and without a suffix (i.e.
       
   165      * without a period in it.
       
   166      *
       
   167      * @exception JarException if an invalid name is passed in.
       
   168      */
       
   169     public SignatureFile(Manifest manifest, String[] entries,
       
   170                          String filename)
       
   171     throws JarException {
       
   172         this(filename, true);
       
   173         this.manifest = manifest;
       
   174         add(entries);
       
   175     }
       
   176 
       
   177     /**
       
   178      * Construct a Signature file from an input stream.
       
   179      *
       
   180      * @exception IOException if an invalid name is passed in or if a
       
   181      * stream exception occurs.
       
   182      */
       
   183     public SignatureFile(InputStream is, String filename)
       
   184     throws IOException {
       
   185         this(filename);
       
   186         while (is.available() > 0) {
       
   187             MessageHeader m = new MessageHeader(is);
       
   188             entries.addElement(m);
       
   189         }
       
   190     }
       
   191 
       
   192    /**
       
   193      * Construct a Signature file from an input stream.
       
   194      *
       
   195      * @exception IOException if an invalid name is passed in or if a
       
   196      * stream exception occurs.
       
   197      */
       
   198     public SignatureFile(InputStream is) throws IOException {
       
   199         this(is, null);
       
   200     }
       
   201 
       
   202     public SignatureFile(byte[] bytes) throws IOException {
       
   203         this(new ByteArrayInputStream(bytes));
       
   204     }
       
   205 
       
   206     /**
       
   207      * Returns the name of the signature file, ending with a ".SF"
       
   208      * suffix */
       
   209     public String getName() {
       
   210         return "META-INF/" + rawName + ".SF";
       
   211     }
       
   212 
       
   213     /**
       
   214      * Returns the name of the block file, ending with a block suffix
       
   215      * such as ".DSA". */
       
   216     public String getBlockName() {
       
   217         String suffix = "DSA";
       
   218         if (signatureBlock != null) {
       
   219             SignerInfo info = signatureBlock.getSignerInfos()[0];
       
   220             suffix = info.getDigestEncryptionAlgorithmId().getName();
       
   221             String temp = AlgorithmId.getEncAlgFromSigAlg(suffix);
       
   222             if (temp != null) suffix = temp;
       
   223         }
       
   224         return "META-INF/" + rawName + "." + suffix;
       
   225     }
       
   226 
       
   227     /**
       
   228      * Returns the signature block associated with this file.
       
   229      */
       
   230     public PKCS7 getBlock() {
       
   231         return signatureBlock;
       
   232     }
       
   233 
       
   234     /**
       
   235      * Sets the signature block associated with this file.
       
   236      */
       
   237     public void setBlock(PKCS7 block) {
       
   238         this.signatureBlock = block;
       
   239     }
       
   240 
       
   241     /**
       
   242      * Add a set of entries from the current manifest.
       
   243      */
       
   244     public void add(String[] entries) throws JarException {
       
   245         for (int i = 0; i < entries.length; i++) {
       
   246             add (entries[i]);
       
   247         }
       
   248     }
       
   249 
       
   250     /**
       
   251      * Add a specific entry from the current manifest.
       
   252      */
       
   253     public void add(String entry) throws JarException {
       
   254         MessageHeader mh = manifest.getEntry(entry);
       
   255         if (mh == null) {
       
   256             throw new JarException("entry " + entry + " not in manifest");
       
   257         }
       
   258         MessageHeader smh;
       
   259         try {
       
   260             smh = computeEntry(mh);
       
   261         } catch (IOException e) {
       
   262             throw new JarException(e.getMessage());
       
   263         }
       
   264         entries.addElement(smh);
       
   265     }
       
   266 
       
   267     /**
       
   268      * Get the entry corresponding to a given name. Returns null if
       
   269      *the entry does not exist.
       
   270      */
       
   271     public MessageHeader getEntry(String name) {
       
   272         Enumeration<MessageHeader> enum_ = entries();
       
   273         while(enum_.hasMoreElements()) {
       
   274             MessageHeader mh = enum_.nextElement();
       
   275             if (name.equals(mh.findValue("Name"))) {
       
   276                 return mh;
       
   277             }
       
   278         }
       
   279         return null;
       
   280     }
       
   281 
       
   282     /**
       
   283      * Returns the n-th entry. The global header is a entry 0.  */
       
   284     public MessageHeader entryAt(int n) {
       
   285         return entries.elementAt(n);
       
   286     }
       
   287 
       
   288     /**
       
   289      * Returns an enumeration of the entries.
       
   290      */
       
   291     public Enumeration<MessageHeader> entries() {
       
   292         return entries.elements();
       
   293     }
       
   294 
       
   295     /**
       
   296      * Given a manifest entry, computes the signature entry for this
       
   297      * manifest entry.
       
   298      */
       
   299     private MessageHeader computeEntry(MessageHeader mh) throws IOException {
       
   300         MessageHeader smh = new MessageHeader();
       
   301 
       
   302         String name = mh.findValue("Name");
       
   303         if (name == null) {
       
   304             return null;
       
   305         }
       
   306         smh.set("Name", name);
       
   307 
       
   308         try {
       
   309             for (int i = 0; i < hashes.length; ++i) {
       
   310                 MessageDigest dig = getDigest(hashes[i]);
       
   311                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
       
   312                 PrintStream ps = new PrintStream(baos);
       
   313                 mh.print(ps);
       
   314                 byte[] headerBytes = baos.toByteArray();
       
   315                 byte[] digest = dig.digest(headerBytes);
       
   316                 smh.set(hashes[i] + "-Digest", Base64.getMimeEncoder().encodeToString(digest));
       
   317             }
       
   318             return smh;
       
   319         } catch (NoSuchAlgorithmException e) {
       
   320             throw new JarException(e.getMessage());
       
   321         }
       
   322     }
       
   323 
       
   324     private Hashtable<String, MessageDigest> digests = new Hashtable<>();
       
   325 
       
   326     private MessageDigest getDigest(String algorithm)
       
   327     throws NoSuchAlgorithmException {
       
   328         MessageDigest dig = digests.get(algorithm);
       
   329         if (dig == null) {
       
   330             dig = MessageDigest.getInstance(algorithm);
       
   331             digests.put(algorithm, dig);
       
   332         }
       
   333         dig.reset();
       
   334         return dig;
       
   335     }
       
   336 
       
   337 
       
   338     /**
       
   339      * Add a signature file at current position in a stream
       
   340      */
       
   341     public void stream(OutputStream os) throws IOException {
       
   342 
       
   343         /* the first header in the file should be the global one.
       
   344          * It should say "SignatureFile-Version: x.x"; barf if not
       
   345          */
       
   346         MessageHeader globals = entries.elementAt(0);
       
   347         if (globals.findValue("Signature-Version") == null) {
       
   348             throw new JarException("Signature file requires " +
       
   349                             "Signature-Version: 1.0 in 1st header");
       
   350         }
       
   351 
       
   352         PrintStream ps = new PrintStream(os);
       
   353         globals.print(ps);
       
   354 
       
   355         for (int i = 1; i < entries.size(); ++i) {
       
   356             MessageHeader mh = entries.elementAt(i);
       
   357             mh.print(ps);
       
   358         }
       
   359     }
       
   360 }