Imported GNU Classpath 0.90
[official-gcc.git] / libjava / classpath / tools / gnu / classpath / tools / jarsigner / JarVerifier.java
blobf80147dfa5e449a1d34c3033434f61986252b9b8
1 /* JarVerifier.java -- The verification handler of the gjarsigner tool
2 Copyright (C) 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package gnu.classpath.tools.jarsigner;
41 import gnu.java.security.OID;
42 import gnu.java.security.Registry;
43 import gnu.java.security.pkcs.PKCS7SignedData;
44 import gnu.java.security.pkcs.SignerInfo;
45 import gnu.java.security.sig.ISignature;
46 import gnu.java.security.sig.ISignatureCodec;
47 import gnu.java.security.sig.dss.DSSSignature;
48 import gnu.java.security.sig.dss.DSSSignatureX509Codec;
49 import gnu.java.security.sig.rsa.RSAPKCS1V1_5Signature;
50 import gnu.java.security.sig.rsa.RSAPKCS1V1_5SignatureX509Codec;
51 import gnu.java.security.util.Util;
52 import gnu.java.util.jar.JarUtils;
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.security.PublicKey;
57 import java.security.cert.Certificate;
58 import java.security.cert.CRLException;
59 import java.security.cert.CertificateException;
60 import java.util.ArrayList;
61 import java.util.Enumeration;
62 import java.util.HashMap;
63 import java.util.Iterator;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 import java.util.Map.Entry;
68 import java.util.jar.Attributes;
69 import java.util.jar.JarEntry;
70 import java.util.jar.JarFile;
71 import java.util.logging.Logger;
72 import java.util.zip.ZipException;
74 /**
75 * The JAR verification handler of the <code>gjarsigner</code> tool.
77 public class JarVerifier
79 private static final Logger log = Logger.getLogger(JarVerifier.class.getName());
80 /** The owner tool of this handler. */
81 private Main main;
82 private HashUtils util = new HashUtils();
83 /** The JAR file to verify. */
84 private JarFile jarFile;
85 /** Map of jar entry names to their hash. */
86 private Map entryHashes = new HashMap();
88 JarVerifier(Main main)
90 super();
92 this.main = main;
95 void start() throws Exception
97 log.entering(this.getClass().getName(), "start"); //$NON-NLS-1$
99 String jarFileName = main.getJarFileName();
100 jarFile = new JarFile(jarFileName);
102 // 1. find all signature (.SF) files
103 List sfFiles = new ArrayList();
104 for (Enumeration e = jarFile.entries(); e.hasMoreElements(); )
106 JarEntry je = (JarEntry) e.nextElement();
107 String jeName = je.getName();
108 if (! (jeName.startsWith(JarUtils.META_INF)
109 && jeName.endsWith(JarUtils.SF_SUFFIX)))
110 continue;
112 // only interested in .SF files in, and not deeper than, META-INF
113 String[] jeNameParts = jeName.split("/"); //$NON-NLS-1$
114 if (jeNameParts.length != 2)
115 continue;
117 String sfName = jeNameParts[1];
118 String sigFileName = sfName.substring(0, sfName.length() - 3);
119 sfFiles.add(sigFileName);
122 // 2. verify each one
123 if (sfFiles.isEmpty())
124 System.out.println(Messages.getString("JarVerifier.2")); //$NON-NLS-1$
125 else
127 int limit = sfFiles.size();
128 int count = 0;
129 for (Iterator it = sfFiles.iterator(); it.hasNext(); )
131 String alias = (String) it.next();
132 if (verifySF(alias))
133 if (verifySFEntries(alias))
134 count++;
137 if (count == 0)
138 System.out.println(Messages.getString("JarVerifier.3")); //$NON-NLS-1$
139 else if (count != limit)
140 System.out.println(Messages.getFormattedString("JarVerifier.4", //$NON-NLS-1$
141 new Integer[] {Integer.valueOf(count),
142 Integer.valueOf(limit)}));
143 else
144 System.out.println(Messages.getFormattedString("JarVerifier.7", //$NON-NLS-1$
145 Integer.valueOf(limit)));
148 log.exiting(this.getClass().getName(), "start"); //$NON-NLS-1$
152 * @param sigFileName the name of the signature file; i.e. the name to use for
153 * both the .SF and .DSA files.
154 * @return <code>true</code> if the designated file-name (usually a key-store
155 * <i>alias</i> name) has been successfully checked as the signer of the
156 * corresponding <code>.SF</code> file. Returns <code>false</code> otherwise.
157 * @throws IOException
158 * @throws ZipException
159 * @throws CertificateException
160 * @throws CRLException
162 private boolean verifySF(String sigFileName) throws CRLException,
163 CertificateException, ZipException, IOException
165 log.entering(this.getClass().getName(), "verifySF"); //$NON-NLS-1$
166 log.finest("About to verify signature of " + sigFileName + "..."); //$NON-NLS-1$ //$NON-NLS-2$
168 // 1. find the corresponding .DSA file for this .SF file
169 JarEntry dsaEntry = jarFile.getJarEntry(JarUtils.META_INF + sigFileName
170 + JarUtils.DSA_SUFFIX);
171 if (dsaEntry == null)
172 throw new SecurityException(Messages.getFormattedString("JarVerifier.13", //$NON-NLS-1$
173 sigFileName));
174 // 2. read the .DSA file contents as a PKCS7 SignedData
175 InputStream in = jarFile.getInputStream(dsaEntry);
176 PKCS7SignedData pkcs7SignedData = new PKCS7SignedData(in);
178 // 4. get the encrypted digest octet string from the first SignerInfo
179 // this octet string is the digital signature of the .SF file contents
180 Set signerInfos = pkcs7SignedData.getSignerInfos();
181 if (signerInfos == null || signerInfos.isEmpty())
182 throw new SecurityException(Messages.getString("JarVerifier.14")); //$NON-NLS-1$
184 SignerInfo signerInfo = (SignerInfo) signerInfos.iterator().next();
185 byte[] encryptedDigest = signerInfo.getEncryptedDigest();
186 if (encryptedDigest == null)
187 throw new SecurityException(Messages.getString("JarVerifier.16")); //$NON-NLS-1$
189 log.finest("\n" + Util.dumpString(encryptedDigest, "--- signedSFBytes ")); //$NON-NLS-1$ //$NON-NLS-2$
191 // 5. get the signer public key
192 Certificate cert = pkcs7SignedData.getCertificates()[0];
193 PublicKey verifierKey = cert.getPublicKey();
194 log.finest("--- verifier public key = " + verifierKey); //$NON-NLS-1$
196 // 6. verify the signature file signature
197 OID digestEncryptionAlgorithmOID = signerInfo.getDigestEncryptionAlgorithmId();
198 ISignature signatureAlgorithm;
199 ISignatureCodec signatureCodec;
200 if (digestEncryptionAlgorithmOID.equals(Main.DSA_SIGNATURE_OID))
202 signatureAlgorithm = new DSSSignature();
203 signatureCodec = new DSSSignatureX509Codec();
205 else
207 signatureAlgorithm = new RSAPKCS1V1_5Signature(Registry.MD5_HASH);
208 signatureCodec = new RSAPKCS1V1_5SignatureX509Codec();
211 Map signatureAttributes = new HashMap();
212 signatureAttributes.put(ISignature.VERIFIER_KEY, verifierKey);
213 signatureAlgorithm.setupVerify(signatureAttributes);
215 Object herSignature = signatureCodec.decodeSignature(encryptedDigest);
217 // 7. verify the signature file contents
218 JarEntry sfEntry = jarFile.getJarEntry(JarUtils.META_INF + sigFileName
219 + JarUtils.SF_SUFFIX);
220 in = jarFile.getInputStream(sfEntry);
221 byte[] buffer = new byte[2048];
222 int n;
223 while ((n = in.read(buffer)) != -1)
224 if (n > 0)
225 signatureAlgorithm.update(buffer, 0, n);
227 boolean result = signatureAlgorithm.verify(herSignature);
228 log.finer("Signature block [" + sigFileName + "] is " //$NON-NLS-1$ //$NON-NLS-2$
229 + (result ? "" : "NOT ") + "OK"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
231 log.exiting(this.getClass().getName(), "verifySF", Boolean.valueOf(result)); //$NON-NLS-1$
232 return result;
236 * This method is called after at least one signer (usually a key-store
237 * <code>alias</code> name) was found to be trusted; i.e. his/her signature
238 * block in the corresponding <code>.DSA</code> file was successfully
239 * verified using his/her public key.
240 * <p>
241 * This method, uses the contents of the corresponding <code>.SF</code> file
242 * to compute and verify the hashes of the manifest entries in the JAR file.
244 * @param alias the name of the signature file; i.e. the name to use for both
245 * the .SF and .DSA files.
246 * @return <code>true</code> if all the entries in the corresponding
247 * <code>.SF</code> file have the same hash values as their
248 * alter-ego in the <i>manifest</i> file of the JAR file inquestion.
249 * @throws IOException if an I/O related exception occurs during the process.
251 private boolean verifySFEntries(String alias) throws IOException
253 log.entering(this.getClass().getName(), "verifySFEntries"); //$NON-NLS-1$
255 // 1. read the signature file
256 JarEntry jarEntry = jarFile.getJarEntry(JarUtils.META_INF + alias
257 + JarUtils.SF_SUFFIX);
258 InputStream in = jarFile.getInputStream(jarEntry);
259 Attributes attr = new Attributes();
260 Map entries = new HashMap();
261 JarUtils.readSFManifest(attr, entries, in);
263 // 2. The .SF file by default includes a header containing a hash of the
264 // entire manifest file. When the header is present, then the verification
265 // can check to see whether or not the hash in the header indeed matches
266 // the hash of the manifest file.
267 boolean result = false;
268 String hash = attr.getValue(Main.DIGEST_MANIFEST_ATTR);
269 if (hash != null)
270 result = verifyManifest(hash);
272 // A verification is still considered successful if none of the files that
273 // were in the JAR file when the signature was generated have been changed
274 // since then, which is the case if the hashes in the non-header sections
275 // of the .SF file equal the hashes of the corresponding sections in the
276 // manifest file.
278 // 3. Read each file in the JAR file that has an entry in the .SF file.
279 // While reading, compute the file's digest, and then compare the result
280 // with the digest for this file in the manifest section. The digests
281 // should be the same, or verification fails.
282 if (! result)
283 for (Iterator it = entries.keySet().iterator(); it.hasNext();)
285 Entry me = (Entry) it.next();
286 String name = (String) me.getKey();
287 attr = (Attributes) me.getValue();
288 hash = attr.getValue(Main.DIGEST_ATTR);
289 result = verifySFEntry(name, hash);
290 if (! result)
291 break;
294 log.exiting(this.getClass().getName(), "verifySFEntries",
295 Boolean.valueOf(result)); //$NON-NLS-1$
296 return result;
300 * @param hash Base-64 encoded form of the manifest's digest.
301 * @return <code>true</code> if our computation of the manifest's hash
302 * matches the given value; <code>false</code> otherwise.
303 * @throws IOException if unable to acquire the JAR's manifest entry.
305 private boolean verifyManifest(String hash) throws IOException
307 return verifySFEntry(JarFile.MANIFEST_NAME, hash);
311 * @param name the name of a JAR entry to verify.
312 * @param hash Base-64 encoded form of the designated entry's digest.
313 * @return <code>true</code> if our computation of the JAR entry's hash
314 * matches the given value; <code>false</code> otherwise.
315 * @throws IOException if an exception occurs while returning the entry's
316 * input stream.
318 private boolean verifySFEntry(String name, String hash) throws IOException
320 String expectedValue = getEntryHash(JarFile.MANIFEST_NAME);
321 boolean result = expectedValue.equalsIgnoreCase(hash);
322 log.finest("Is " + name + " OK? " + result); //$NON-NLS-1$ //$NON-NLS-2$
323 return result;
326 private String getEntryHash(String entryName) throws IOException
328 String result = (String) entryHashes.get(entryName);
329 if (result == null)
331 JarEntry manifest = jarFile.getJarEntry(entryName);
332 InputStream in = jarFile.getInputStream(manifest);
333 result = util.hashStream(in);
334 entryHashes.put(entryName, result);
337 return result;