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
26package sun.tools.jar;
27
28import java.io.*;
29import java.util.*;
30import java.security.*;
31
32import sun.net.www.MessageHeader;
33import java.util.Base64;
34
35
36import sun.security.pkcs.*;
37import 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
62public 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}
361