KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > osgi > internal > verifier > SignedBundleFile


1 /*******************************************************************************
2  * Copyright (c) 2006, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11
12 package org.eclipse.osgi.internal.verifier;
13
14 import java.io.*;
15 import java.net.URL JavaDoc;
16 import java.security.*;
17 import java.security.cert.*;
18 import java.util.*;
19 import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
20 import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
21 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
22 import org.eclipse.osgi.internal.provisional.verifier.*;
23 import org.eclipse.osgi.util.NLS;
24
25 /**
26  * This class wraps a Repository of classes and resources to check and enforce
27  * signatures. It requires full signing of the manifest by all signers. If no
28  * signatures are found, the classes and resources are retrieved without checks.
29  */

30 public class SignedBundleFile extends BundleFile implements CertificateVerifier, JarVerifierConstant {
31     private static DefaultTrustAuthority trustAllAuthority = new DefaultTrustAuthority(0);
32     private BundleFile bundleFile;
33     CertificateChain[] chains;
34
35     /**
36      * The key of the hashtable will be the name of the entry (type String). The
37      * value will be MessageDigest algorithm to use.
38      */

39     Hashtable digests4entries;
40     /**
41      * The key of the hashtable will be the name of the entry (type String). The
42      * value will be byte[] which is an array of one MessageDigest result.
43      */

44     Hashtable results4entries;
45     String JavaDoc manifestSHAResult = null;
46     String JavaDoc manifestMD5Result = null;
47     boolean certsInitialized = false;
48
49     SignedBundleFile() {
50         // default constructor
51
}
52
53     SignedBundleFile(CertificateChain[] chains, Hashtable digests4entries, Hashtable results4entries, String JavaDoc manifestMD5Result, String JavaDoc manifestSHAResult) {
54         this.chains = chains;
55         this.digests4entries = digests4entries;
56         this.results4entries = results4entries;
57         this.manifestMD5Result = manifestMD5Result;
58         this.manifestSHAResult = manifestSHAResult;
59         certsInitialized = true;
60         // isSigned = true;
61
}
62
63     /**
64      * Verify the digest listed in each entry in the .SF file with corresponding section in the manifest
65      */

66     private void verifyManifestAndSingatureFile(byte[] manifestBytes, byte[] sfBytes) {
67
68         String JavaDoc sf = new String JavaDoc(sfBytes);
69         sf = stripContinuations(sf);
70
71         // check if there -Digest-Manfiest: header in the file
72
int off = sf.indexOf(digestManifestSearch);
73         if (off != -1) {
74             int start = sf.lastIndexOf('\n', off);
75             String JavaDoc manfiestDigest = null;
76             if (start != -1) {
77                 // Signature-Version has to start the file, so there
78
// should always be a newline at the start of
79
// Digest-Manifest
80
String JavaDoc digestName = sf.substring(start + 1, off);
81                 if (digestName.equalsIgnoreCase(MD5_STR)) {
82                     if (manifestMD5Result == null)
83                         manifestMD5Result = calculateDigest(getMessageDigest(MD5_STR), manifestBytes);
84                     manfiestDigest = manifestMD5Result;
85                 } else if (digestName.equalsIgnoreCase(SHA1_STR)) {
86                     if (manifestSHAResult == null)
87                         manifestSHAResult = calculateDigest(getMessageDigest(SHA1_STR), manifestBytes);
88                     manfiestDigest = manifestSHAResult;
89                 }
90                 off += digestManifestSearchLen;
91
92                 // find out the index of first '\n' after the -Digest-Manifest:
93
int nIndex = sf.indexOf('\n', off);
94                 String JavaDoc digestValue = sf.substring(off, nIndex - 1);
95
96                 // check if the the computed digest value of manifest file equals to the digest value in the .sf file
97
if (!manfiestDigest.equals(digestValue)) {
98                     Exception JavaDoc e = new SecurityException JavaDoc(NLS.bind(JarVerifierMessages.Security_File_Is_Tampered, new String JavaDoc[] {bundleFile.getBaseFile().toString()}));
99                     SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
100                     throw (SecurityException JavaDoc) e;
101                 }
102             }
103         }
104
105     }
106
107     /**
108      * Read the .SF file abd assuming that same digest algorithm will be used through out the whole
109      * .SF file. That digest algorithm name in the last entry will be returned.
110      *
111      * @param SFBuf a .SF file in bytes
112      * @return the digest algorithm name used in the .SF file
113      */

114     private String JavaDoc getDigAlgFromSF(byte SFBuf[]) {
115         String JavaDoc rtvValue = null;
116
117         // need to make a string from the MF file data bytes
118
String JavaDoc mfStr = new String JavaDoc(SFBuf);
119         String JavaDoc entryStr = null;
120
121         // start parsing each entry in the MF String
122
int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME);
123         int length = mfStr.length();
124
125         while ((entryStartOffset != -1) && (entryStartOffset < length)) {
126
127             // get the start of the next 'entry', i.e. the end of this entry
128
int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1);
129             if (entryEndOffset == -1) {
130                 // if there is no next entry, then the end of the string
131
// is the end of this entry
132
entryEndOffset = mfStr.length();
133             }
134
135             // get the string for this entry only, since the entryStartOffset
136
// points to the '\n' befor the 'Name: ' we increase it by 1
137
// this is guaranteed to not go past end-of-string and be less
138
// then entryEndOffset.
139
entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset);
140             entryStr = stripContinuations(entryStr);
141             break;
142         }
143
144         if (entryStr != null) {
145             // process the entry to retrieve the digest algorith name
146
String JavaDoc digestLine = getDigestLine(entryStr, null);
147
148             // throw parsing
149
rtvValue = getMessageDigestName(digestLine);
150         }
151
152         return rtvValue;
153     }
154
155     /**
156      * @param mfBuf the data from an MF file of a JAR archive
157      *
158      * This method will populate the "digest type & result" hashtables
159      * with whatever entries it can correctly parse from the MF file and with the same digest algorithm.
160      * it will 'skip' incorrect entries (TODO: should the correct behavior
161      * be to throw an exception, or return an error code?)...
162      *
163      *
164      */

165     private void populateManifest(byte mfBuf[], String JavaDoc digAlg) {
166         // need to make a string from the MF file data bytes
167
String JavaDoc mfStr = new String JavaDoc(mfBuf);
168
169         // start parsing each entry in the MF String
170
int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME);
171         int length = mfStr.length();
172
173         while ((entryStartOffset != -1) && (entryStartOffset < length)) {
174
175             // get the start of the next 'entry', i.e. the end of this entry
176
int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1);
177             if (entryEndOffset == -1) {
178                 // if there is no next entry, then the end of the string
179
// is the end of this entry
180
entryEndOffset = mfStr.length();
181             }
182
183             // get the string for this entry only, since the entryStartOffset
184
// points to the '\n' befor the 'Name: ' we increase it by 1
185
// this is guaranteed to not go past end-of-string and be less
186
// then entryEndOffset.
187
String JavaDoc entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset);
188             entryStr = stripContinuations(entryStr);
189
190             // entry points to the start of the next 'entry'
191
String JavaDoc entryName = getEntryFileName(entryStr);
192
193             // if we could retrieve an entry name, then we will extract
194
// digest type list, and the digest value list
195
if (entryName != null) {
196
197                 String JavaDoc aDigestLine = getDigestLine(entryStr, digAlg);
198
199                 if (aDigestLine != null) {
200                     String JavaDoc msgDigestAlgorithm = getDigestAlgorithmFromString(aDigestLine);
201                     byte digestResultsList[] = getDigestResultsList(aDigestLine);
202
203                     //
204
// only insert this entry into the table if its
205
// "well-formed",
206
// i.e. only if we could extract its name, digest types, and
207
// digest-results
208
//
209
// sanity check, if the 2 lists are non-null, then their
210
// counts must match
211
//
212
// if ((msgDigestObj != null) && (digestResultsList != null)
213
// && (1 != digestResultsList.length)) {
214
// throw new RuntimeException(
215
// "Errors occurs when parsing the manifest file stream!"); //$NON-NLS-1$
216
// }
217

218                     if (digests4entries == null) {
219                         digests4entries = new Hashtable(10);
220                         results4entries = new Hashtable(10);
221                     }
222                     // TODO throw exception if duplicate entry??
223
// no need, in theory, there is impossible to have two
224
// duplicate entries unless the manifest file
225
// is tampered
226
if (!digests4entries.contains(entryName)) {
227                         digests4entries.put(entryName, msgDigestAlgorithm);
228                         results4entries.put(entryName, digestResultsList);
229                     }
230
231                 } // could get lines of digest entries in this MF file entry
232

233             } // could retrieve entry name
234

235             // increment the offset to the ending entry...
236
entryStartOffset = entryEndOffset;
237         }
238     }
239
240     private String JavaDoc stripContinuations(String JavaDoc entry) {
241         if (entry.indexOf("\n ") < 0) //$NON-NLS-1$
242
return entry;
243         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(entry.length());
244         int cont = entry.indexOf("\n "); //$NON-NLS-1$
245
int start = 0;
246         while (cont >= 0) {
247             buffer.append(entry.substring(start, cont - 1));
248             start = cont + 2;
249             cont = cont + 2 < entry.length() ? entry.indexOf("\n ", cont + 2) : -1; //$NON-NLS-1$
250
}
251         // get the last one continuation
252
if (start < entry.length())
253             buffer.append(entry.substring(start));
254         return buffer.toString();
255     }
256
257     private String JavaDoc getEntryFileName(String JavaDoc manifestEntry) {
258         // get the beginning of the name
259
int nameStart = manifestEntry.indexOf(MF_ENTRY_NAME);
260         if (nameStart == -1) {
261             return null;
262         }
263         // check where the name ends
264
int nameEnd = manifestEntry.indexOf('\n', nameStart);
265         if (nameEnd == -1) {
266             return null;
267         }
268         // if there is a '\r' before the '\n', then we'll strip it
269
if (manifestEntry.charAt(nameEnd - 1) == '\r') {
270             nameEnd--;
271         }
272         // get to the beginning of the actual name...
273
nameStart += MF_ENTRY_NAME.length();
274         if (nameStart >= nameEnd) {
275             return null;
276         }
277         return manifestEntry.substring(nameStart, nameEnd);
278     }
279
280     /**
281      *
282      * @param manifestEntry contains a single MF file entry of the format
283      * "Name: foo"
284      * "MD5-Digest: [base64 encoded MD5 digest data]"
285      * "SHA1-Digest: [base64 encoded SHA1 digest dat]"
286      *
287      * @param manifestEntry a entry contains either one or multiple digest lines
288      * @param desireDigestAlg a string representing the desire digest value to be returned if there are
289      * multiple digest lines.
290      * If this value is null, return whatever digest value is in the entry.
291      *
292      * @return this function returns a digest line based on the desire digest algorithm value
293      * (since only MD5 and SHA1 are recognized here),
294      * or a 'null' will be returned if none of the digest algorithms
295      * were recognized.
296      */

297     private String JavaDoc getDigestLine(String JavaDoc manifestEntry, String JavaDoc desireDigestAlg) {
298         String JavaDoc rtvValue = null;
299
300         // find the first digest line
301
int indexDigest = manifestEntry.indexOf(MF_DIGEST_PART);
302         // if we didn't find any digests at all, then we are done
303
if (indexDigest == -1)
304             return null;
305
306         // while we continue to find digest entries
307
// note: in the following loop we bail if any of the lines
308
// look malformed...
309
while (indexDigest != -1) {
310             // see where this digest line begins (look to left)
311
int indexStart = manifestEntry.lastIndexOf('\n', indexDigest);
312             if (indexStart == -1)
313                 return null;
314             // see where it ends (look to right)
315
int indexEnd = manifestEntry.indexOf('\n', indexDigest);
316             if (indexEnd == -1)
317                 return null;
318             // strip off ending '\r', if any
319
int indexEndToUse = indexEnd;
320             if (manifestEntry.charAt(indexEndToUse - 1) == '\r')
321                 indexEndToUse--;
322             // indexStart points to the '\n' before this digest line
323
int indexStartToUse = indexStart + 1;
324             if (indexStartToUse >= indexEndToUse)
325                 return null;
326
327             // now this may be a valid digest line, parse it a bit more
328
// to see if this is a preferred digest algorithm
329
String JavaDoc digestLine = manifestEntry.substring(indexStartToUse, indexEndToUse);
330             String JavaDoc digAlg = getMessageDigestName(digestLine);
331             if (desireDigestAlg != null && desireDigestAlg.equalsIgnoreCase(digAlg)) {
332                 rtvValue = digestLine;
333                 break;
334             }
335
336             // desireDigestAlg is null, always return the
337
rtvValue = digestLine;
338
339             // iterate to next digest line in this entry
340
indexDigest = manifestEntry.indexOf(MF_DIGEST_PART, indexEnd);
341         }
342
343         // if we couldn't find any digest lines, then we are done
344
return rtvValue;
345     }
346
347     private String JavaDoc getDigestAlgorithmFromString(String JavaDoc digestLines) {
348         if (digestLines != null) {
349             // String sDigestLine = digestLines[i];
350
int indexDigest = digestLines.indexOf(MF_DIGEST_PART);
351             String JavaDoc sDigestAlgType = digestLines.substring(0, indexDigest);
352             if (sDigestAlgType.equalsIgnoreCase(MD5_STR)) {
353                 // remember the "algorithm type"
354
return MD5_STR;
355             } else if (sDigestAlgType.equalsIgnoreCase(SHA1_STR)) {
356                 // remember the "algorithm type" object
357
return SHA1_STR;
358             } else {
359                 // unknown algorithm type, we will stop processing this entry
360
// break;
361
throw new SecurityException JavaDoc(NLS.bind(JarVerifierMessages.Algorithm_Not_Supported, sDigestAlgType));
362             }
363         }
364         return null;
365     }
366
367     /**
368      * Return the Message Digest name
369      *
370      * @param digLine the message digest line is in the following format. That is in the
371      * following format:
372      * DIGEST_NAME-digest: digest value
373      * @return a string representing a message digest.
374      */

375     private String JavaDoc getMessageDigestName(String JavaDoc digLine) {
376         String JavaDoc rtvValue = null;
377         if (digLine != null) {
378             int indexDigest = digLine.indexOf(MF_DIGEST_PART);
379             if (indexDigest != -1) {
380                 rtvValue = digLine.substring(0, indexDigest);
381             }
382         }
383         return rtvValue;
384     }
385
386     private byte[] getDigestResultsList(String JavaDoc digestLines) {
387         byte resultsList[] = null;
388         if (digestLines != null) {
389             // for each digest-line retrieve the digest result
390
// for (int i = 0; i < digestLines.length; i++) {
391
String JavaDoc sDigestLine = digestLines;
392             int indexDigest = sDigestLine.indexOf(MF_DIGEST_PART);
393             indexDigest += MF_DIGEST_PART.length();
394             // if there is no data to extract for this digest value
395
// then we will fail...
396
if (indexDigest >= sDigestLine.length()) {
397                 resultsList = null;
398                 // break;
399
}
400             // now attempt to base64 decode the result
401
String JavaDoc sResult = sDigestLine.substring(indexDigest);
402             try {
403                 resultsList = Base64.decode(sResult.getBytes());
404             } catch (Throwable JavaDoc t) {
405                 // malformed digest result, no longer processing this entry
406
resultsList = null;
407             }
408         }
409         return resultsList;
410     }
411
412     static private int readFully(InputStream is, byte b[]) throws IOException {
413         int count = b.length;
414         int offset = 0;
415         int rc;
416         while ((rc = is.read(b, offset, count)) > 0) {
417             count -= rc;
418             offset += rc;
419         }
420         return offset;
421     }
422
423     byte[] readIntoArray(BundleEntry be) throws IOException {
424         int size = (int) be.getSize();
425         InputStream is = be.getInputStream();
426         byte b[] = new byte[size];
427         int rc = readFully(is, b);
428         if (rc != size) {
429             throw new IOException("Couldn't read all of " + be.getName() + ": " + rc + " != " + size); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
430
}
431         return b;
432     }
433
434     /**
435      * Sets the BundleFile for this singed bundle. It will extract
436      * signatures and digests from the bundle file and validate input streams
437      * before using them from the bundle file.
438      *
439      * @param bundleFile the BundleFile to extract elements from.
440      * @param the support flags for this signed bundle
441      * @throws IOException
442      */

443     void setBundleFile(BundleFile bundleFile, int supportFlags) throws IOException {
444         this.bundleFile = bundleFile;
445         if (certsInitialized)
446             return;
447         BundleEntry be = bundleFile.getEntry(META_INF_MANIFEST_MF);
448         if (be == null)
449             return;
450
451         // read all the signature block file names into a list
452
Enumeration en = bundleFile.getEntryPaths(META_INF);
453         List signers = new ArrayList(2);
454         while (en.hasMoreElements()) {
455             String JavaDoc name = (String JavaDoc) en.nextElement();
456             if ((name.endsWith(DOT_DSA) || name.endsWith(DOT_RSA)) && name.indexOf('/') == name.lastIndexOf('/'))
457                 signers.add(name);
458         }
459
460         // this means the jar is not signed
461
if (signers.size() == 0)
462             return;
463
464         byte manifestBytes[] = readIntoArray(be);
465         // determine the signer to be used for later
466
String JavaDoc latestSigner = findLatestSigner(bundleFile, signers);
467         // process the signers
468
try {
469             ArrayList processors = new ArrayList(signers.size());
470             Iterator iSigners = signers.iterator();
471             for (int i = 0; iSigners.hasNext(); i++) {
472                 String JavaDoc signer = (String JavaDoc) iSigners.next();
473                 PKCS7Processor processor = processSigner(bundleFile, manifestBytes, signer, latestSigner, supportFlags);
474                 boolean error = false;
475                 try {
476                     processor.validateCerts();
477                     determineCertsTrust(processor, supportFlags);
478                 } catch (CertificateExpiredException e) {
479                     // ignore
480
} catch (CertificateNotYetValidException e) {
481                     // ignore
482
} catch (InvalidKeyException e) {
483                     error = true;
484                 }
485                 if (!error) {
486                     // make sure the latestSigner is the first in the list
487
if (signer == latestSigner)
488                         processors.add(0, processor);
489                     else
490                         processors.add(processor);
491                 }
492             }
493             chains = processors.size() == 0 ? null : (CertificateChain[]) processors.toArray(new CertificateChain[processors.size()]);
494         } catch (SignatureException e) {
495             throw new SecurityException JavaDoc(NLS.bind(JarVerifierMessages.Signature_Not_Verify_1, new String JavaDoc[] {latestSigner, bundleFile.toString()}));
496         }
497     }
498
499     private void determineCertsTrust(PKCS7Processor signerPKCS7, int supportFlags) {
500         CertificateTrustAuthority trustAuthority;
501         if ((supportFlags & SignedBundleHook.VERIFY_TRUST) != 0)
502             trustAuthority = SignedBundleHook.getTrustAuthority();
503         else
504             trustAuthority = trustAllAuthority;
505         if (trustAuthority != null)
506             signerPKCS7.determineTrust(trustAuthority);
507     }
508
509     private PKCS7Processor processSigner(BundleFile bf, byte[] manifestBytes, String JavaDoc signer, String JavaDoc latestSigner, int supportFlags) throws IOException, SignatureException {
510         BundleEntry be = bf.getEntry(signer);
511         byte pkcs7Bytes[] = readIntoArray(be);
512         int dotIndex = signer.lastIndexOf('.');
513         be = bf.getEntry(signer.substring(0, dotIndex) + DOT_SF);
514         byte sfBytes[] = readIntoArray(be);
515
516         // Step 1, verify the .SF file is signed by the private key that corresponds to the public key
517
// in the .RSA/.DSA file
518
PKCS7Processor chain = null;
519         try {
520
521             chain = new PKCS7Processor(pkcs7Bytes, 0, pkcs7Bytes.length);
522
523             // call the Step 1 in the Jar File Verification algorithm
524
chain.verifySFSignature(sfBytes, 0, sfBytes.length);
525
526             // algorithm used
527
String JavaDoc digAlg = getDigAlgFromSF(sfBytes);
528             if (digAlg == null) {
529                 throw new SecurityException JavaDoc(NLS.bind(JarVerifierMessages.SF_File_Parsing_Error, new String JavaDoc[] {bf.toString()}));
530             }
531             // populate the two digest hashtable instance variable based on the used Message Digest algorithm
532
if (latestSigner == signer) { // only do this if it is the latest signer
533

534                 // Process the Step 2 in the Jar File Verification algorithm
535
// Get the manifest out of the signature file and make sure
536
// it matches MANIFEST.MF
537
verifyManifestAndSingatureFile(manifestBytes, sfBytes);
538
539                 // only populate the manifests if we are verifying content at runtime
540
if ((supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0)
541                     populateManifest(manifestBytes, digAlg);
542             }
543
544             // process the Step 3
545
// read each file in the JAR file that has an entry in the .SF file.
546
// also determine the
547
// verifySFEntriesDigest(bf, sfBytes, manifestBytes);
548
} catch (InvalidKeyException e) {
549             SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
550             throw new SecurityException JavaDoc(NLS.bind(JarVerifierMessages.Invalid_Key_Exception, new String JavaDoc[] {bf.getBaseFile().toString(), e.getMessage()}));
551         } catch (CertificateException e) {
552             SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
553             throw new SecurityException JavaDoc(NLS.bind(JarVerifierMessages.PKCS7_Cert_Excep, new String JavaDoc[] {bf.getBaseFile().toString(), e.getMessage()}));
554         } catch (NoSuchAlgorithmException e) {
555             SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
556             throw new SecurityException JavaDoc(NLS.bind(JarVerifierMessages.PKCS7_No_Such_Algorithm, new String JavaDoc[] {bf.getBaseFile().toString(), e.getMessage()}));
557         }
558
559         return chain;
560     }
561
562     private String JavaDoc findLatestSigner(BundleFile bf, List names) {
563         String JavaDoc result = null;
564         long latestTime = Long.MIN_VALUE;
565         for (Iterator iNames = names.iterator(); iNames.hasNext();) {
566             String JavaDoc name = (String JavaDoc) iNames.next();
567             BundleEntry entry = bf.getEntry(name);
568             if (entry.getTime() > latestTime) {
569                 result = name;
570                 latestTime = entry.getTime();
571             }
572         }
573         return result;
574     }
575
576     /**
577      * Returns the Base64 encoded digest of the passed set of bytes.
578      */

579     private String JavaDoc calculateDigest(MessageDigest digest, byte[] bytes) {
580         return new String JavaDoc(Base64.encode(digest.digest(bytes)));
581     }
582
583     public File getFile(String JavaDoc path, boolean nativeCode) {
584         return bundleFile.getFile(path, nativeCode);
585     }
586
587     public BundleEntry getEntry(String JavaDoc path) {
588         // strip off leading slashes so we can ensure the path matches the one provided in the manifest.
589
if (path.length() > 0 && path.charAt(0) == '/')
590             path = path.substring(1);
591         BundleEntry be = bundleFile.getEntry(path);
592         if (digests4entries == null)
593             return be;
594         if (be == null) {
595             if (digests4entries.get(path) == null)
596                 return null;
597             throw new SecurityException JavaDoc(NLS.bind(JarVerifierMessages.file_is_removed_from_jar, getBaseFile().toString(), path));
598         }
599         if (be.getName().startsWith(META_INF))
600             return be;
601         if (!isSigned())
602             // If there is no signatures, we just return the regular bundle entry
603
return be;
604         return new SignedBundleEntry(be);
605     }
606
607     public Enumeration getEntryPaths(String JavaDoc path) {
608         return bundleFile.getEntryPaths(path);
609     }
610
611     public void close() throws IOException {
612         bundleFile.close();
613     }
614
615     public void open() throws IOException {
616         bundleFile.open();
617     }
618
619     public boolean containsDir(String JavaDoc dir) {
620         return bundleFile.containsDir(dir);
621     }
622
623     boolean matchDNChain(String JavaDoc pattern) {
624         CertificateChain[] matchChains = getChains();
625         for (int i = 0; i < matchChains.length; i++)
626             if (matchChains[i].isTrusted() && DNChainMatching.match(matchChains[i].getChain(), pattern))
627                 return true;
628         return false;
629     }
630
631     public File getBaseFile() {
632         return bundleFile.getBaseFile();
633     }
634
635     class SignedBundleEntry extends BundleEntry {
636         BundleEntry nestedEntry;
637
638         SignedBundleEntry(BundleEntry nestedEntry) {
639             this.nestedEntry = nestedEntry;
640         }
641
642         public InputStream getInputStream() throws IOException {
643             String JavaDoc name = getName();
644             String JavaDoc digest = digests4entries == null ? null : (String JavaDoc) digests4entries.get(name);
645             if (digest == null)
646                  // the digest does not exist; this must be a corrupted jar
647
throw new IOException("Corrupted file: the digest does not exist for the file " + name); //$NON-NLS-1$
648
byte results[] = (byte[]) results4entries.get(name);
649             // TODO ELI: it is probbaly best to decode the value of digest into Base64 base. This will only optimize the bad jar case.
650
return new DigestedInputStream(nestedEntry.getInputStream(), digest, results, nestedEntry.getSize());
651         }
652
653         public long getSize() {
654             return nestedEntry.getSize();
655         }
656
657         public String JavaDoc getName() {
658             return nestedEntry.getName();
659         }
660
661         public long getTime() {
662             return nestedEntry.getTime();
663         }
664
665         public URL JavaDoc getLocalURL() {
666             return nestedEntry.getLocalURL();
667         }
668
669         public URL JavaDoc getFileURL() {
670             return nestedEntry.getFileURL();
671         }
672
673     }
674
675     public void checkContent() throws CertificateException, CertificateExpiredException, SignatureException {
676         if (!isSigned() || digests4entries == null)
677             return;
678
679         for (Enumeration entries = digests4entries.keys(); entries.hasMoreElements();) {
680             String JavaDoc name = (String JavaDoc) entries.nextElement();
681             BundleEntry entry = getEntry(name);
682             if (entry == null) {
683                 throw new SecurityException JavaDoc(NLS.bind(JarVerifierMessages.Jar_Is_Tampered, bundleFile.getBaseFile().getName()));
684             }
685             try {
686                 entry.getBytes();
687             } catch (IOException e) {
688                 SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
689                 throw new SecurityException JavaDoc(NLS.bind(JarVerifierMessages.File_In_Jar_Is_Tampered, new String JavaDoc[] {name, bundleFile.getBaseFile().toString()}));
690             }
691         }
692
693         // validate the certs chain and determine the trust
694
for (int i = 0; i < chains.length; i++) {
695             PKCS7Processor signerPKCS7 = (PKCS7Processor) chains[i];
696             try {
697                 signerPKCS7.validateCerts();
698             } catch (InvalidKeyException e) {
699                 throw new CertificateException(e.getMessage());
700             }
701             // determine the trust of certificates
702
determineCertsTrust(signerPKCS7, SignedBundleHook.VERIFY_ALL);
703         }
704     }
705
706     public String JavaDoc[] verifyContent() {
707         if (!isSigned() || digests4entries == null)
708             return EMPTY_STRING;
709         ArrayList corrupted = new ArrayList(0); // be optimistic
710
for (Enumeration entries = digests4entries.keys(); entries.hasMoreElements();) {
711             String JavaDoc name = (String JavaDoc) entries.nextElement();
712             BundleEntry entry = getEntry(name);
713             if (entry == null)
714                 corrupted.add(name); // we expected the entry to be here
715
else
716                 try {
717                     entry.getBytes();
718                 } catch (IOException e) {
719                     // must be invalid
720
corrupted.add(name);
721                 }
722         }
723         return corrupted.size() == 0 ? EMPTY_STRING : (String JavaDoc[]) corrupted.toArray(new String JavaDoc[corrupted.size()]);
724     }
725
726     public CertificateChain[] getChains() {
727         if (!isSigned())
728             return new CertificateChain[0];
729         return chains;
730     }
731
732     public boolean isSigned() {
733         return chains != null;
734     }
735
736     static synchronized MessageDigest getMessageDigest(String JavaDoc algorithm) {
737         try {
738             return MessageDigest.getInstance(algorithm);
739         } catch (NoSuchAlgorithmException e) {
740             SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
741         }
742         return null;
743     }
744
745     // static public void main(String args[]) throws IOException {
746
//
747
// ZipBundleFile jf = new ZipBundleFile(new File(args[0]), null);
748
// SignedBundleFile sr = new SignedBundleFile();
749
//
750
// sr.setBundleFile(jf);
751
//
752
// // read the first level directory entries
753
// Enumeration en = sr.getEntryPaths("/"); //$NON-NLS-1$
754
// while (en.hasMoreElements()) {
755
// String filePath = (String) en.nextElement();
756
// System.out.println("main(): " + filePath); //$NON-NLS-1$
757
//
758
// // if this file is not a directory file
759
// // then we'll get its input stream for testing
760
// if (filePath.indexOf('/') == -1) {
761
// BundleEntry be = sr.getEntry(filePath);
762
// InputStream is = be.getInputStream();
763
// is.skip(be.getSize());
764
// is.read();
765
// is.close();
766
// }
767
// }
768
//
769
// if (!sr.isSigned()) {
770
// System.out.println("No signers present"); //$NON-NLS-1$
771
// } else {
772
// CertificateChain[] chains = sr.getChains();
773
// for (int i = 0; i < chains.length; i++) {
774
// System.out.println(chains[i].getChain());
775
// }
776
// }
777
//
778
// System.out.println("Done"); //$NON-NLS-1$
779
// }
780
}
781
Popular Tags