OpenCores
URL https://opencores.org/ocsvn/scarts/scarts/trunk

Subversion Repositories scarts

[/] [scarts/] [trunk/] [toolchain/] [scarts-gcc/] [gcc-4.1.1/] [libjava/] [classpath/] [java/] [util/] [jar/] [JarFile.java] - Blame information for rev 14

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 14 jlechner
/* JarFile.java - Representation of a jar file
2
   Copyright (C) 2000, 2003, 2004, 2005 Free Software Foundation, Inc.
3
 
4
This file is part of GNU Classpath.
5
 
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.
10
 
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.
15
 
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.
20
 
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.
25
 
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. */
37
 
38
 
39
package java.util.jar;
40
 
41
import gnu.java.io.Base64InputStream;
42
import gnu.java.security.OID;
43
import gnu.java.security.pkcs.PKCS7SignedData;
44
import gnu.java.security.pkcs.SignerInfo;
45
 
46
import java.io.ByteArrayOutputStream;
47
import java.io.File;
48
import java.io.FileNotFoundException;
49
import java.io.FilterInputStream;
50
import java.io.IOException;
51
import java.io.InputStream;
52
import java.security.InvalidKeyException;
53
import java.security.MessageDigest;
54
import java.security.NoSuchAlgorithmException;
55
import java.security.Signature;
56
import java.security.SignatureException;
57
import java.security.cert.CRLException;
58
import java.security.cert.Certificate;
59
import java.security.cert.CertificateException;
60
import java.security.cert.X509Certificate;
61
import java.util.Arrays;
62
import java.util.Enumeration;
63
import java.util.HashMap;
64
import java.util.HashSet;
65
import java.util.Iterator;
66
import java.util.LinkedList;
67
import java.util.List;
68
import java.util.Map;
69
import java.util.Set;
70
import java.util.zip.ZipEntry;
71
import java.util.zip.ZipException;
72
import java.util.zip.ZipFile;
73
 
74
/**
75
 * Representation of a jar file.
76
 * <p>
77
 * Note that this class is not a subclass of java.io.File but a subclass of
78
 * java.util.zip.ZipFile and you can only read JarFiles with it (although
79
 * there are constructors that take a File object).
80
 *
81
 * @since 1.2
82
 * @author Mark Wielaard (mark@klomp.org)
83
 * @author Casey Marshall (csm@gnu.org) wrote the certificate and entry
84
 *  verification code.
85
 */
86
public class JarFile extends ZipFile
87
{
88
  // Fields
89
 
90
  /** The name of the manifest entry: META-INF/MANIFEST.MF */
91
  public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
92
 
93
  /** The META-INF directory entry. */
94
  private static final String META_INF = "META-INF/";
95
 
96
  /** The suffix for PKCS7 DSA signature entries. */
97
  private static final String PKCS7_DSA_SUFFIX = ".DSA";
98
 
99
  /** The suffix for PKCS7 RSA signature entries. */
100
  private static final String PKCS7_RSA_SUFFIX = ".RSA";
101
 
102
  /** The suffix for digest attributes. */
103
  private static final String DIGEST_KEY_SUFFIX = "-Digest";
104
 
105
  /** The suffix for signature files. */
106
  private static final String SF_SUFFIX = ".SF";
107
 
108
  // Signature OIDs.
109
  private static final OID MD2_OID = new OID("1.2.840.113549.2.2");
110
  private static final OID MD4_OID = new OID("1.2.840.113549.2.4");
111
  private static final OID MD5_OID = new OID("1.2.840.113549.2.5");
112
  private static final OID SHA1_OID = new OID("1.3.14.3.2.26");
113
  private static final OID DSA_ENCRYPTION_OID = new OID("1.2.840.10040.4.1");
114
  private static final OID RSA_ENCRYPTION_OID = new OID("1.2.840.113549.1.1.1");
115
 
116
  /**
117
   * The manifest of this file, if any, otherwise null.
118
   * Read when first needed.
119
   */
120
  private Manifest manifest;
121
 
122
  /** Whether to verify the manifest and all entries. */
123
  boolean verify;
124
 
125
  /** Whether the has already been loaded. */
126
  private boolean manifestRead = false;
127
 
128
  /** Whether the signature files have been loaded. */
129
  boolean signaturesRead = false;
130
 
131
  /**
132
   * A map between entry names and booleans, signaling whether or
133
   * not that entry has been verified.
134
   * Only be accessed with lock on this JarFile*/
135
  HashMap verified = new HashMap();
136
 
137
  /**
138
   * A mapping from entry name to certificates, if any.
139
   * Only accessed with lock on this JarFile.
140
   */
141
  HashMap entryCerts;
142
 
143
  static boolean DEBUG = false;
144
  static void debug(Object msg)
145
  {
146
    System.err.print(JarFile.class.getName());
147
    System.err.print(" >>> ");
148
    System.err.println(msg);
149
  }
150
 
151
  // Constructors
152
 
153
  /**
154
   * Creates a new JarFile. All jar entries are verified (when a Manifest file
155
   * for this JarFile exists). You need to actually open and read the complete
156
   * jar entry (with <code>getInputStream()</code>) to check its signature.
157
   *
158
   * @param fileName the name of the file to open
159
   * @exception FileNotFoundException if the fileName cannot be found
160
   * @exception IOException if another IO exception occurs while reading
161
   */
162
  public JarFile(String fileName) throws FileNotFoundException, IOException
163
  {
164
    this(fileName, true);
165
  }
166
 
167
  /**
168
   * Creates a new JarFile. If verify is true then all jar entries are
169
   * verified (when a Manifest file for this JarFile exists). You need to
170
   * actually open and read the complete jar entry
171
   * (with <code>getInputStream()</code>) to check its signature.
172
   *
173
   * @param fileName the name of the file to open
174
   * @param verify checks manifest and entries when true and a manifest
175
   * exists, when false no checks are made
176
   * @exception FileNotFoundException if the fileName cannot be found
177
   * @exception IOException if another IO exception occurs while reading
178
   */
179
  public JarFile(String fileName, boolean verify) throws
180
    FileNotFoundException, IOException
181
  {
182
    super(fileName);
183
    if (verify)
184
      {
185
        manifest = readManifest();
186
        verify();
187
      }
188
  }
189
 
190
  /**
191
   * Creates a new JarFile. All jar entries are verified (when a Manifest file
192
   * for this JarFile exists). You need to actually open and read the complete
193
   * jar entry (with <code>getInputStream()</code>) to check its signature.
194
   *
195
   * @param file the file to open as a jar file
196
   * @exception FileNotFoundException if the file does not exits
197
   * @exception IOException if another IO exception occurs while reading
198
   */
199
  public JarFile(File file) throws FileNotFoundException, IOException
200
  {
201
    this(file, true);
202
  }
203
 
204
  /**
205
   * Creates a new JarFile. If verify is true then all jar entries are
206
   * verified (when a Manifest file for this JarFile exists). You need to
207
   * actually open and read the complete jar entry
208
   * (with <code>getInputStream()</code>) to check its signature.
209
   *
210
   * @param file the file to open to open as a jar file
211
   * @param verify checks manifest and entries when true and a manifest
212
   * exists, when false no checks are made
213
   * @exception FileNotFoundException if file does not exist
214
   * @exception IOException if another IO exception occurs while reading
215
   */
216
  public JarFile(File file, boolean verify) throws FileNotFoundException,
217
    IOException
218
  {
219
    super(file);
220
    if (verify)
221
      {
222
        manifest = readManifest();
223
        verify();
224
      }
225
  }
226
 
227
  /**
228
   * Creates a new JarFile with the indicated mode. If verify is true then
229
   * all jar entries are verified (when a Manifest file for this JarFile
230
   * exists). You need to actually open and read the complete jar entry
231
   * (with <code>getInputStream()</code>) to check its signature.
232
   * manifest and if the manifest exists and verify is true verfies it.
233
   *
234
   * @param file the file to open to open as a jar file
235
   * @param verify checks manifest and entries when true and a manifest
236
   * exists, when false no checks are made
237
   * @param mode either ZipFile.OPEN_READ or
238
   *             (ZipFile.OPEN_READ | ZipFile.OPEN_DELETE)
239
   * @exception FileNotFoundException if the file does not exist
240
   * @exception IOException if another IO exception occurs while reading
241
   * @exception IllegalArgumentException when given an illegal mode
242
   *
243
   * @since 1.3
244
   */
245
  public JarFile(File file, boolean verify, int mode) throws
246
    FileNotFoundException, IOException, IllegalArgumentException
247
  {
248
    super(file, mode);
249
    if (verify)
250
      {
251
        manifest = readManifest();
252
        verify();
253
      }
254
  }
255
 
256
  // Methods
257
 
258
  /**
259
   * XXX - should verify the manifest file
260
   */
261
  private void verify()
262
  {
263
    // only check if manifest is not null
264
    if (manifest == null)
265
      {
266
        verify = false;
267
        return;
268
      }
269
 
270
    verify = true;
271
    // XXX - verify manifest
272
  }
273
 
274
  /**
275
   * Parses and returns the manifest if it exists, otherwise returns null.
276
   */
277
  private Manifest readManifest()
278
  {
279
    try
280
      {
281
        ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
282
        if (manEntry != null)
283
          {
284
            InputStream in = super.getInputStream(manEntry);
285
            manifestRead = true;
286
            return new Manifest(in);
287
          }
288
        else
289
          {
290
            manifestRead = true;
291
            return null;
292
          }
293
      }
294
    catch (IOException ioe)
295
      {
296
        manifestRead = true;
297
        return null;
298
      }
299
  }
300
 
301
  /**
302
   * Returns a enumeration of all the entries in the JarFile.
303
   * Note that also the Jar META-INF entries are returned.
304
   *
305
   * @exception IllegalStateException when the JarFile is already closed
306
   */
307
  public Enumeration entries() throws IllegalStateException
308
  {
309
    return new JarEnumeration(super.entries(), this);
310
  }
311
 
312
  /**
313
   * Wraps a given Zip Entries Enumeration. For every zip entry a
314
   * JarEntry is created and the corresponding Attributes are looked up.
315
   */
316
  private static class JarEnumeration implements Enumeration
317
  {
318
 
319
    private final Enumeration entries;
320
    private final JarFile jarfile;
321
 
322
    JarEnumeration(Enumeration e, JarFile f)
323
    {
324
      entries = e;
325
      jarfile = f;
326
    }
327
 
328
    public boolean hasMoreElements()
329
    {
330
      return entries.hasMoreElements();
331
    }
332
 
333
    public Object nextElement()
334
    {
335
      ZipEntry zip = (ZipEntry) entries.nextElement();
336
      JarEntry jar = new JarEntry(zip);
337
      Manifest manifest;
338
      try
339
        {
340
          manifest = jarfile.getManifest();
341
        }
342
      catch (IOException ioe)
343
        {
344
          manifest = null;
345
        }
346
 
347
      if (manifest != null)
348
        {
349
          jar.attr = manifest.getAttributes(jar.getName());
350
        }
351
 
352
      synchronized(jarfile)
353
        {
354
          if (jarfile.verify && !jarfile.signaturesRead)
355
            try
356
              {
357
                jarfile.readSignatures();
358
              }
359
            catch (IOException ioe)
360
              {
361
                if (JarFile.DEBUG)
362
                  {
363
                    JarFile.debug(ioe);
364
                    ioe.printStackTrace();
365
                  }
366
                jarfile.signaturesRead = true; // fudge it.
367
              }
368
 
369
          // Include the certificates only if we have asserted that the
370
          // signatures are valid. This means the certificates will not be
371
          // available if the entry hasn't been read yet.
372
          if (jarfile.entryCerts != null
373
              && jarfile.verified.get(zip.getName()) == Boolean.TRUE)
374
            {
375
              Set certs = (Set) jarfile.entryCerts.get(jar.getName());
376
              if (certs != null)
377
                jar.certs = (Certificate[])
378
                  certs.toArray(new Certificate[certs.size()]);
379
            }
380
        }
381
      return jar;
382
    }
383
  }
384
 
385
  /**
386
   * XXX
387
   * It actually returns a JarEntry not a zipEntry
388
   * @param name XXX
389
   */
390
  public synchronized ZipEntry getEntry(String name)
391
  {
392
    ZipEntry entry = super.getEntry(name);
393
    if (entry != null)
394
      {
395
        JarEntry jarEntry = new JarEntry(entry);
396
        Manifest manifest;
397
        try
398
          {
399
            manifest = getManifest();
400
          }
401
        catch (IOException ioe)
402
          {
403
            manifest = null;
404
          }
405
 
406
        if (manifest != null)
407
          {
408
            jarEntry.attr = manifest.getAttributes(name);
409
          }
410
 
411
        if (verify && !signaturesRead)
412
          try
413
            {
414
              readSignatures();
415
            }
416
          catch (IOException ioe)
417
            {
418
              if (DEBUG)
419
                {
420
                  debug(ioe);
421
                  ioe.printStackTrace();
422
                }
423
              signaturesRead = true;
424
            }
425
        // See the comments in the JarEnumeration for why we do this
426
        // check.
427
        if (DEBUG)
428
          debug("entryCerts=" + entryCerts + " verified " + name
429
                + " ? " + verified.get(name));
430
        if (entryCerts != null && verified.get(name) == Boolean.TRUE)
431
          {
432
            Set certs = (Set) entryCerts.get(name);
433
            if (certs != null)
434
              jarEntry.certs = (Certificate[])
435
                certs.toArray(new Certificate[certs.size()]);
436
          }
437
        return jarEntry;
438
      }
439
    return null;
440
  }
441
 
442
  /**
443
   * Returns an input stream for the given entry. If configured to
444
   * verify entries, the input stream returned will verify them while
445
   * the stream is read, but only on the first time.
446
   *
447
   * @param entry The entry to get the input stream for.
448
   * @exception ZipException XXX
449
   * @exception IOException XXX
450
   */
451
  public synchronized InputStream getInputStream(ZipEntry entry) throws
452
    ZipException, IOException
453
  {
454
    // If we haven't verified the hash, do it now.
455
    if (!verified.containsKey(entry.getName()) && verify)
456
      {
457
        if (DEBUG)
458
          debug("reading and verifying " + entry);
459
        return new EntryInputStream(entry, super.getInputStream(entry), this);
460
      }
461
    else
462
      {
463
        if (DEBUG)
464
          debug("reading already verified entry " + entry);
465
        if (verify && verified.get(entry.getName()) == Boolean.FALSE)
466
          throw new ZipException("digest for " + entry + " is invalid");
467
        return super.getInputStream(entry);
468
      }
469
  }
470
 
471
  /**
472
   * Returns the JarEntry that belongs to the name if such an entry
473
   * exists in the JarFile. Returns null otherwise
474
   * Convenience method that just casts the result from <code>getEntry</code>
475
   * to a JarEntry.
476
   *
477
   * @param name the jar entry name to look up
478
   * @return the JarEntry if it exists, null otherwise
479
   */
480
  public JarEntry getJarEntry(String name)
481
  {
482
    return (JarEntry) getEntry(name);
483
  }
484
 
485
  /**
486
   * Returns the manifest for this JarFile or null when the JarFile does not
487
   * contain a manifest file.
488
   */
489
  public synchronized Manifest getManifest() throws IOException
490
  {
491
    if (!manifestRead)
492
      manifest = readManifest();
493
 
494
    return manifest;
495
  }
496
 
497
  // Only called with lock on this JarFile.
498
  // Package private for use in inner classes.
499
  void readSignatures() throws IOException
500
  {
501
    Map pkcs7Dsa = new HashMap();
502
    Map pkcs7Rsa = new HashMap();
503
    Map sigFiles = new HashMap();
504
 
505
    // Phase 1: Read all signature files. These contain the user
506
    // certificates as well as the signatures themselves.
507
    for (Enumeration e = super.entries(); e.hasMoreElements(); )
508
      {
509
        ZipEntry ze = (ZipEntry) e.nextElement();
510
        String name = ze.getName();
511
        if (name.startsWith(META_INF))
512
          {
513
            String alias = name.substring(META_INF.length());
514
            if (alias.lastIndexOf('.') >= 0)
515
              alias = alias.substring(0, alias.lastIndexOf('.'));
516
 
517
            if (name.endsWith(PKCS7_DSA_SUFFIX) || name.endsWith(PKCS7_RSA_SUFFIX))
518
              {
519
                if (DEBUG)
520
                  debug("reading PKCS7 info from " + name + ", alias=" + alias);
521
                PKCS7SignedData sig = null;
522
                try
523
                  {
524
                    sig = new PKCS7SignedData(super.getInputStream(ze));
525
                  }
526
                catch (CertificateException ce)
527
                  {
528
                    IOException ioe = new IOException("certificate parsing error");
529
                    ioe.initCause(ce);
530
                    throw ioe;
531
                  }
532
                catch (CRLException crle)
533
                  {
534
                    IOException ioe = new IOException("CRL parsing error");
535
                    ioe.initCause(crle);
536
                    throw ioe;
537
                  }
538
                if (name.endsWith(PKCS7_DSA_SUFFIX))
539
                  pkcs7Dsa.put(alias, sig);
540
                else if (name.endsWith(PKCS7_RSA_SUFFIX))
541
                  pkcs7Rsa.put(alias, sig);
542
              }
543
            else if (name.endsWith(SF_SUFFIX))
544
              {
545
                if (DEBUG)
546
                  debug("reading signature file for " + alias + ": " + name);
547
                Manifest sf = new Manifest(super.getInputStream(ze));
548
                sigFiles.put(alias, sf);
549
                if (DEBUG)
550
                  debug("result: " + sf);
551
              }
552
          }
553
      }
554
 
555
    // Phase 2: verify the signatures on any signature files.
556
    Set validCerts = new HashSet();
557
    Map entryCerts = new HashMap();
558
    for (Iterator it = sigFiles.entrySet().iterator(); it.hasNext(); )
559
      {
560
        int valid = 0;
561
        Map.Entry e = (Map.Entry) it.next();
562
        String alias = (String) e.getKey();
563
 
564
        PKCS7SignedData sig = (PKCS7SignedData) pkcs7Dsa.get(alias);
565
        if (sig != null)
566
          {
567
            Certificate[] certs = sig.getCertificates();
568
            Set signerInfos = sig.getSignerInfos();
569
            for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); )
570
              verify(certs, (SignerInfo) it2.next(), alias, validCerts);
571
          }
572
 
573
        sig = (PKCS7SignedData) pkcs7Rsa.get(alias);
574
        if (sig != null)
575
          {
576
            Certificate[] certs = sig.getCertificates();
577
            Set signerInfos = sig.getSignerInfos();
578
            for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); )
579
              verify(certs, (SignerInfo) it2.next(), alias, validCerts);
580
          }
581
 
582
        // It isn't a signature for anything. Punt it.
583
        if (validCerts.isEmpty())
584
          {
585
            it.remove();
586
            continue;
587
          }
588
 
589
        entryCerts.put(e.getValue(), new HashSet(validCerts));
590
        validCerts.clear();
591
      }
592
 
593
    // Phase 3: verify the signature file signatures against the manifest,
594
    // mapping the entry name to the target certificates.
595
    this.entryCerts = new HashMap();
596
    for (Iterator it = entryCerts.entrySet().iterator(); it.hasNext(); )
597
      {
598
        Map.Entry e = (Map.Entry) it.next();
599
        Manifest sigfile = (Manifest) e.getKey();
600
        Map entries = sigfile.getEntries();
601
        Set certificates = (Set) e.getValue();
602
 
603
        for (Iterator it2 = entries.entrySet().iterator(); it2.hasNext(); )
604
          {
605
            Map.Entry e2 = (Map.Entry) it2.next();
606
            String entryname = String.valueOf(e2.getKey());
607
            Attributes attr = (Attributes) e2.getValue();
608
            if (verifyHashes(entryname, attr))
609
              {
610
                if (DEBUG)
611
                  debug("entry " + entryname + " has certificates " + certificates);
612
                Set s = (Set) this.entryCerts.get(entryname);
613
                if (s != null)
614
                  s.addAll(certificates);
615
                else
616
                  this.entryCerts.put(entryname, new HashSet(certificates));
617
              }
618
          }
619
      }
620
 
621
    signaturesRead = true;
622
  }
623
 
624
  /**
625
   * Tell if the given signer info is over the given alias's signature file,
626
   * given one of the certificates specified.
627
   */
628
  private void verify(Certificate[] certs, SignerInfo signerInfo,
629
                      String alias, Set validCerts)
630
  {
631
    Signature sig = null;
632
    try
633
      {
634
        OID alg = signerInfo.getDigestEncryptionAlgorithmId();
635
        if (alg.equals(DSA_ENCRYPTION_OID))
636
          {
637
            if (!signerInfo.getDigestAlgorithmId().equals(SHA1_OID))
638
              return;
639
            sig = Signature.getInstance("SHA1withDSA");
640
          }
641
        else if (alg.equals(RSA_ENCRYPTION_OID))
642
          {
643
            OID hash = signerInfo.getDigestAlgorithmId();
644
            if (hash.equals(MD2_OID))
645
              sig = Signature.getInstance("md2WithRsaEncryption");
646
            else if (hash.equals(MD4_OID))
647
              sig = Signature.getInstance("md4WithRsaEncryption");
648
            else if (hash.equals(MD5_OID))
649
              sig = Signature.getInstance("md5WithRsaEncryption");
650
            else if (hash.equals(SHA1_OID))
651
              sig = Signature.getInstance("sha1WithRsaEncryption");
652
            else
653
              return;
654
          }
655
        else
656
          {
657
            if (DEBUG)
658
              debug("unsupported signature algorithm: " + alg);
659
            return;
660
          }
661
      }
662
    catch (NoSuchAlgorithmException nsae)
663
      {
664
        if (DEBUG)
665
          {
666
            debug(nsae);
667
            nsae.printStackTrace();
668
          }
669
        return;
670
      }
671
    ZipEntry sigFileEntry = super.getEntry(META_INF + alias + SF_SUFFIX);
672
    if (sigFileEntry == null)
673
      return;
674
    for (int i = 0; i < certs.length; i++)
675
      {
676
        if (!(certs[i] instanceof X509Certificate))
677
          continue;
678
        X509Certificate cert = (X509Certificate) certs[i];
679
        if (!cert.getIssuerX500Principal().equals(signerInfo.getIssuer()) ||
680
            !cert.getSerialNumber().equals(signerInfo.getSerialNumber()))
681
          continue;
682
        try
683
          {
684
            sig.initVerify(cert.getPublicKey());
685
            InputStream in = super.getInputStream(sigFileEntry);
686
            if (in == null)
687
              continue;
688
            byte[] buf = new byte[1024];
689
            int len = 0;
690
            while ((len = in.read(buf)) != -1)
691
              sig.update(buf, 0, len);
692
            if (sig.verify(signerInfo.getEncryptedDigest()))
693
              {
694
                if (DEBUG)
695
                  debug("signature for " + cert.getSubjectDN() + " is good");
696
                validCerts.add(cert);
697
              }
698
          }
699
        catch (IOException ioe)
700
          {
701
            continue;
702
          }
703
        catch (InvalidKeyException ike)
704
          {
705
            continue;
706
          }
707
        catch (SignatureException se)
708
          {
709
            continue;
710
          }
711
      }
712
  }
713
 
714
  /**
715
   * Verifies that the digest(s) in a signature file were, in fact, made
716
   * over the manifest entry for ENTRY.
717
   *
718
   * @param entry The entry name.
719
   * @param attr The attributes from the signature file to verify.
720
   */
721
  private boolean verifyHashes(String entry, Attributes attr)
722
  {
723
    int verified = 0;
724
 
725
    // The bytes for ENTRY's manifest entry, which are signed in the
726
    // signature file.
727
    byte[] entryBytes = null;
728
    try
729
      {
730
        ZipEntry e = super.getEntry(entry);
731
        if (e == null)
732
          {
733
            if (DEBUG)
734
              debug("verifyHashes: no entry '" + entry + "'");
735
            return false;
736
          }
737
        entryBytes = readManifestEntry(e);
738
      }
739
    catch (IOException ioe)
740
      {
741
        if (DEBUG)
742
          {
743
            debug(ioe);
744
            ioe.printStackTrace();
745
          }
746
        return false;
747
      }
748
 
749
    for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
750
      {
751
        Map.Entry e = (Map.Entry) it.next();
752
        String key = String.valueOf(e.getKey());
753
        if (!key.endsWith(DIGEST_KEY_SUFFIX))
754
          continue;
755
        String alg = key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length());
756
        try
757
          {
758
            byte[] hash = Base64InputStream.decode((String) e.getValue());
759
            MessageDigest md = MessageDigest.getInstance(alg);
760
            md.update(entryBytes);
761
            byte[] hash2 = md.digest();
762
            if (DEBUG)
763
              debug("verifying SF entry " + entry + " alg: " + md.getAlgorithm()
764
                    + " expect=" + new java.math.BigInteger(hash).toString(16)
765
                    + " comp=" + new java.math.BigInteger(hash2).toString(16));
766
            if (!Arrays.equals(hash, hash2))
767
              return false;
768
            verified++;
769
          }
770
        catch (IOException ioe)
771
          {
772
            if (DEBUG)
773
              {
774
                debug(ioe);
775
                ioe.printStackTrace();
776
              }
777
            return false;
778
          }
779
        catch (NoSuchAlgorithmException nsae)
780
          {
781
            if (DEBUG)
782
              {
783
                debug(nsae);
784
                nsae.printStackTrace();
785
              }
786
            return false;
787
          }
788
      }
789
 
790
    // We have to find at least one valid digest.
791
    return verified > 0;
792
  }
793
 
794
  /**
795
   * Read the raw bytes that comprise a manifest entry. We can't use the
796
   * Manifest object itself, because that loses information (such as line
797
   * endings, and order of entries).
798
   */
799
  private byte[] readManifestEntry(ZipEntry entry) throws IOException
800
  {
801
    InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME));
802
    ByteArrayOutputStream out = new ByteArrayOutputStream();
803
    byte[] target = ("Name: " + entry.getName()).getBytes();
804
    int t = 0, c, prev = -1, state = 0, l = -1;
805
 
806
    while ((c = in.read()) != -1)
807
      {
808
//         if (DEBUG)
809
//           debug("read "
810
//                 + (c == '\n' ? "\\n" : (c == '\r' ? "\\r" : String.valueOf((char) c)))
811
//                 + " state=" + state + " prev="
812
//                 + (prev == '\n' ? "\\n" : (prev == '\r' ? "\\r" : String.valueOf((char) prev)))
813
//                 + " t=" + t + (t < target.length ? (" target[t]=" + (char) target[t]) : "")
814
//                 + " l=" + l);
815
        switch (state)
816
          {
817
 
818
          // Step 1: read until we find the "target" bytes: the start
819
          // of the entry we need to read.
820
          case 0:
821
            if (((byte) c) != target[t])
822
              t = 0;
823
            else
824
              {
825
                t++;
826
                if (t == target.length)
827
                  {
828
                    out.write(target);
829
                    state = 1;
830
                  }
831
              }
832
            break;
833
 
834
          // Step 2: assert that there is a newline character after
835
          // the "target" bytes.
836
          case 1:
837
            if (c != '\n' && c != '\r')
838
              {
839
                out.reset();
840
                t = 0;
841
                state = 0;
842
              }
843
            else
844
              {
845
                out.write(c);
846
                state = 2;
847
              }
848
            break;
849
 
850
          // Step 3: read this whole entry, until we reach an empty
851
          // line.
852
          case 2:
853
            if (c == '\n')
854
              {
855
                out.write(c);
856
                // NL always terminates a line.
857
                if (l == 0 || (l == 1 && prev == '\r'))
858
                  return out.toByteArray();
859
                l = 0;
860
              }
861
            else
862
              {
863
                // Here we see a blank line terminated by a CR,
864
                // followed by the next entry. Technically, `c' should
865
                // always be 'N' at this point.
866
                if (l == 1 && prev == '\r')
867
                  return out.toByteArray();
868
                out.write(c);
869
                l++;
870
              }
871
            prev = c;
872
            break;
873
 
874
          default:
875
            throw new RuntimeException("this statement should be unreachable");
876
          }
877
      }
878
 
879
    // The last entry, with a single CR terminating the line.
880
    if (state == 2 && prev == '\r' && l == 0)
881
      return out.toByteArray();
882
 
883
    // We should not reach this point, we didn't find the entry (or, possibly,
884
    // it is the last entry and is malformed).
885
    throw new IOException("could not find " + entry + " in manifest");
886
  }
887
 
888
  /**
889
   * A utility class that verifies jar entries as they are read.
890
   */
891
  private static class EntryInputStream extends FilterInputStream
892
  {
893
    private final JarFile jarfile;
894
    private final long length;
895
    private long pos;
896
    private final ZipEntry entry;
897
    private final byte[][] hashes;
898
    private final MessageDigest[] md;
899
    private boolean checked;
900
 
901
    EntryInputStream(final ZipEntry entry,
902
                     final InputStream in,
903
                     final JarFile jar)
904
      throws IOException
905
    {
906
      super(in);
907
      this.entry = entry;
908
      this.jarfile = jar;
909
 
910
      length = entry.getSize();
911
      pos = 0;
912
      checked = false;
913
 
914
      Attributes attr;
915
      Manifest manifest = jarfile.getManifest();
916
      if (manifest != null)
917
        attr = manifest.getAttributes(entry.getName());
918
      else
919
        attr = null;
920
      if (DEBUG)
921
        debug("verifying entry " + entry + " attr=" + attr);
922
      if (attr == null)
923
        {
924
          hashes = new byte[0][];
925
          md = new MessageDigest[0];
926
        }
927
      else
928
        {
929
          List hashes = new LinkedList();
930
          List md = new LinkedList();
931
          for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
932
            {
933
              Map.Entry e = (Map.Entry) it.next();
934
              String key = String.valueOf(e.getKey());
935
              if (key == null)
936
                continue;
937
              if (!key.endsWith(DIGEST_KEY_SUFFIX))
938
                continue;
939
              hashes.add(Base64InputStream.decode((String) e.getValue()));
940
              try
941
                {
942
                  md.add(MessageDigest.getInstance
943
                         (key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length())));
944
                }
945
              catch (NoSuchAlgorithmException nsae)
946
                {
947
                  IOException ioe = new IOException("no such message digest: " + key);
948
                  ioe.initCause(nsae);
949
                  throw ioe;
950
                }
951
            }
952
          if (DEBUG)
953
            debug("digests=" + md);
954
          this.hashes = (byte[][]) hashes.toArray(new byte[hashes.size()][]);
955
          this.md = (MessageDigest[]) md.toArray(new MessageDigest[md.size()]);
956
        }
957
    }
958
 
959
    public boolean markSupported()
960
    {
961
      return false;
962
    }
963
 
964
    public void mark(int readLimit)
965
    {
966
    }
967
 
968
    public void reset()
969
    {
970
    }
971
 
972
    public int read() throws IOException
973
    {
974
      int b = super.read();
975
      if (b == -1)
976
        {
977
          eof();
978
          return -1;
979
        }
980
      for (int i = 0; i < md.length; i++)
981
        md[i].update((byte) b);
982
      pos++;
983
      if (length > 0 && pos >= length)
984
        eof();
985
      return b;
986
    }
987
 
988
    public int read(byte[] buf, int off, int len) throws IOException
989
    {
990
      int count = super.read(buf, off, (int) Math.min(len, (length != 0
991
                                                            ? length - pos
992
                                                            : Integer.MAX_VALUE)));
993
      if (count == -1 || (length > 0 && pos >= length))
994
        {
995
          eof();
996
          return -1;
997
        }
998
      for (int i = 0; i < md.length; i++)
999
        md[i].update(buf, off, count);
1000
      pos += count;
1001
      if (length != 0 && pos >= length)
1002
        eof();
1003
      return count;
1004
    }
1005
 
1006
    public int read(byte[] buf) throws IOException
1007
    {
1008
      return read(buf, 0, buf.length);
1009
    }
1010
 
1011
    public long skip(long bytes) throws IOException
1012
    {
1013
      byte[] b = new byte[1024];
1014
      long amount = 0;
1015
      while (amount < bytes)
1016
        {
1017
          int l = read(b, 0, (int) Math.min(b.length, bytes - amount));
1018
          if (l == -1)
1019
            break;
1020
          amount += l;
1021
        }
1022
      return amount;
1023
    }
1024
 
1025
    private void eof() throws IOException
1026
    {
1027
      if (checked)
1028
        return;
1029
      checked = true;
1030
      for (int i = 0; i < md.length; i++)
1031
        {
1032
          byte[] hash = md[i].digest();
1033
          if (DEBUG)
1034
            debug("verifying " + md[i].getAlgorithm() + " expect="
1035
                  + new java.math.BigInteger(hashes[i]).toString(16)
1036
                  + " comp=" + new java.math.BigInteger(hash).toString(16));
1037
          if (!Arrays.equals(hash, hashes[i]))
1038
            {
1039
              synchronized(jarfile)
1040
                {
1041
                  if (DEBUG)
1042
                    debug(entry + " could NOT be verified");
1043
                  jarfile.verified.put(entry.getName(), Boolean.FALSE);
1044
                }
1045
              return;
1046
              // XXX ??? what do we do here?
1047
              // throw new ZipException("message digest mismatch");
1048
            }
1049
        }
1050
 
1051
      synchronized(jarfile)
1052
        {
1053
          if (DEBUG)
1054
            debug(entry + " has been VERIFIED");
1055
          jarfile.verified.put(entry.getName(), Boolean.TRUE);
1056
        }
1057
    }
1058
  }
1059
}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.