001/**
002 * Copyright 2012 Emmanuel Bourg
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package net.jsign.pe;
018
019import java.io.Closeable;
020import java.io.File;
021import java.io.IOException;
022import java.io.OutputStream;
023import java.io.PrintWriter;
024import java.nio.ByteBuffer;
025import java.nio.ByteOrder;
026import java.nio.channels.SeekableByteChannel;
027import java.nio.file.Files;
028import java.nio.file.StandardOpenOption;
029import java.security.MessageDigest;
030import java.util.ArrayList;
031import java.util.Date;
032import java.util.List;
033
034import net.jsign.DigestAlgorithm;
035import net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers;
036
037import org.bouncycastle.asn1.ASN1Encodable;
038import org.bouncycastle.asn1.cms.Attribute;
039import org.bouncycastle.asn1.cms.AttributeTable;
040import org.bouncycastle.asn1.cms.ContentInfo;
041import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
042import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
043import org.bouncycastle.cert.X509CertificateHolder;
044import org.bouncycastle.cms.CMSProcessable;
045import org.bouncycastle.cms.CMSSignedData;
046import org.bouncycastle.cms.SignerInformation;
047
048/**
049 * Portable Executable File.
050 * 
051 * This class is thread safe.
052 * 
053 * @see <a href="http://msdn.microsoft.com/en-us/library/windows/hardware/gg463119.aspx">Microsoft PE and COFF Specification </a>
054 * 
055 * @author Emmanuel Bourg
056 * @since 1.0
057 */
058public class PEFile implements Closeable {
059
060    /** The position of the PE header in the file */
061    private final long peHeaderOffset;
062
063    private File file;
064    final SeekableByteChannel channel;
065
066    /** Reusable buffer for reading bytes, words, dwords and qwords from the file */
067    private final ByteBuffer valueBuffer = ByteBuffer.allocate(8);
068    {
069        valueBuffer.order(ByteOrder.LITTLE_ENDIAN);
070    }
071
072    /**
073     * Create a PEFile from the specified file.
074     *
075     * @param file
076     * @throws IOException
077     */
078    public PEFile(File file) throws IOException {
079        this(Files.newByteChannel(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE));
080        this.file = file;
081    }
082
083    /**
084     * Create a PEFile from the specified channel.
085     *
086     * @since 2.0
087     */
088    public PEFile(SeekableByteChannel channel) throws IOException {
089        this.channel = channel;
090        
091        try {
092            // DOS Header
093            read(0, 0, 2);
094            if (valueBuffer.get() != 'M' || valueBuffer.get() != 'Z') {
095                throw new IOException("DOS header signature not found");
096            }
097
098            // PE Header
099            read(0x3C, 0, 4);
100            peHeaderOffset = valueBuffer.getInt() & 0xFFFFFFFFL;
101            read(peHeaderOffset, 0, 4);
102            if (valueBuffer.get() != 'P' || valueBuffer.get() != 'E' || valueBuffer.get() != 0 || valueBuffer.get() != 0) {
103                throw new IOException("PE signature not found as expected at offset 0x" + Long.toHexString(peHeaderOffset));
104            }
105
106        } catch (IOException e) {
107            channel.close();
108            throw e;
109        }
110    }
111
112    public synchronized void close() throws IOException {
113        channel.close();
114    }
115
116    synchronized int read(byte[] buffer, long base, int offset) {
117        try {
118            channel.position(base + offset);
119            return channel.read(ByteBuffer.wrap(buffer));
120        } catch (IOException e) {
121            throw new RuntimeException(e);
122        }
123    }
124
125    private void read(long base, int offset, int length) {
126        try {
127            valueBuffer.limit(length);
128            valueBuffer.clear();
129            channel.position(base + offset);
130            channel.read(valueBuffer);
131            valueBuffer.rewind();
132        } catch (IOException e) {
133            throw new RuntimeException(e);
134        }
135    }
136
137    synchronized int read(long base, int offset) {
138        read(base, offset, 1);
139        return valueBuffer.get();
140    }
141
142    synchronized int readWord(long base, int offset) {
143        read(base, offset, 2);
144        return valueBuffer.getShort() & 0xFFFF;
145    }
146
147    synchronized long readDWord(long base, int offset) {
148        read(base, offset, 4);
149        return valueBuffer.getInt() & 0xFFFFFFFFL;
150    }
151
152    synchronized long readQWord(long base, int offset) {
153        read(base, offset, 8);
154        return valueBuffer.getLong();
155    }
156
157    synchronized void write(long base, byte[] data) {
158        try {
159            channel.position(base);
160            channel.write(ByteBuffer.wrap(data));
161        } catch (IOException e) {
162            throw new RuntimeException(e);
163        }
164    }
165
166    public MachineType getMachineType() {
167        return MachineType.valueOf(readWord(peHeaderOffset, 4));
168    }
169
170    /**
171     * The number of sections. This indicates the size of the section table,
172     * which immediately follows the headers.
173     */
174    public int getNumberOfSections() {
175        return readWord(peHeaderOffset, 6);
176    }
177
178    /**
179     * The low 32 bits of the number of seconds since 00:00 January 1, 1970
180     * (a C runtime time_t value), that indicates when the file was created.
181     */
182    public Date getTimeDateStamp() {
183        return new Date(1000 * readDWord(peHeaderOffset, 8));
184    }
185
186    /**
187     * The file offset of the COFF symbol table, or zero if no COFF symbol table
188     * is present. This value should be zero for an image because COFF debugging
189     * information is deprecated.
190     */
191    public long getPointerToSymbolTable() {
192        return readDWord(peHeaderOffset, 12);
193    }
194
195    /**
196     * The number of entries in the symbol table. This data can be used to
197     * locate the string table, which immediately follows the symbol table.
198     * This value should be zero for an image because COFF debugging
199     * information is deprecated.
200     */
201    public long getNumberOfSymbols() {
202        return readDWord(peHeaderOffset, 16);
203    }
204
205    /**
206     * The size of the optional header, which is required for executable files
207     * but not for object files. This value should be zero for an object file.
208     */
209    public int getSizeOfOptionalHeader() {
210        return readWord(peHeaderOffset, 20);
211    }
212
213    /**
214     * The flags that indicate the attributes of the file. 
215     */
216    public int getCharacteristics() {
217        return readWord(peHeaderOffset, 22);
218    }
219    
220    public PEFormat getFormat() {
221        return PEFormat.valueOf(readWord(peHeaderOffset, 24));
222    }
223
224    /**
225     * The linker major version number.
226     */
227    public int getMajorLinkerVersion() {
228        return read(peHeaderOffset, 26);
229    }
230
231    /**
232     * The linker minor version number.
233     */
234    public int getMinorLinkerVersion() {
235        return read(peHeaderOffset, 27);
236    }
237
238    /**
239     * The size of the code (text) section, or the sum of all code sections
240     * if there are multiple sections.
241     */
242    public long getSizeOfCode() {
243        return readDWord(peHeaderOffset, 28);
244    }
245
246    /**
247     * The size of the initialized data section, or the sum of all such
248     * sections if there are multiple data sections.
249     */
250    public long getSizeOfInitializedData() {
251        return readDWord(peHeaderOffset, 32);
252    }
253
254    /**
255     * The size of the uninitialized data section (BSS), or the sum of all such
256     * sections if there are multiple BSS sections.
257     */
258    public long getSizeOfUninitializedData() {
259        return readDWord(peHeaderOffset, 36);
260    }
261
262    /**
263     * The address of the entry point relative to the image base when the
264     * executable file is loaded into memory. For program images, this is the
265     * starting address. For device drivers, this is the address of the
266     * initialization function. An entry point is optional for DLLs. When no
267     * entry point is present, this field must be zero.
268     */
269    public long getAddressOfEntryPoint() {
270        return readDWord(peHeaderOffset, 40);
271    }
272
273    /**
274     * The address that is relative to the image base of the beginning-of-code 
275     * section when it is loaded into memory.
276     */
277    public long getBaseOfCode() {
278        return readDWord(peHeaderOffset, 44);
279    }
280
281    /**
282     * The address that is relative to the image base of the beginning-of-data 
283     * section when it is loaded into memory (PE32 only).
284     */
285    public long getBaseOfData() {
286        if (PEFormat.PE32.equals(getFormat())) {
287            return readDWord(peHeaderOffset, 48);
288        } else {
289            return 0;
290        }
291    }
292
293    /**
294     * The preferred address of the first byte of image when loaded into memory;
295     * must be a multiple of 64 K. The default for DLLs is 0x10000000. The default
296     * for Windows CE EXEs is 0x00010000. The default for Windows NT, Windows 2000,
297     * Windows XP, Windows 95, Windows 98, and Windows Me is 0x00400000.
298     */
299    public long getImageBase() {
300        if (PEFormat.PE32.equals(getFormat())) {
301            return readDWord(peHeaderOffset, 52);
302        } else {
303            return readQWord(peHeaderOffset, 48);
304        }
305    }
306
307    /**
308     * The alignment (in bytes) of sections when they are loaded into memory.
309     * It must be greater than or equal to FileAlignment. The default is the
310     * page size for the architecture.
311     */
312    public long getSectionAlignment() {
313        return readDWord(peHeaderOffset, 56);
314    }
315
316    /**
317     * The alignment factor (in bytes) that is used to align the raw data of
318     * sections in the image file. The value should be a power of 2 between
319     * 512 and 64 K, inclusive. The default is 512. If the SectionAlignment
320     * is less than the architecture?s page size, then FileAlignment must
321     * match SectionAlignment.
322     */
323    public long getFileAlignment() {
324        return readDWord(peHeaderOffset, 60);
325    }
326
327    /**
328     * The major version number of the required operating system.
329     */
330    public int getMajorOperatingSystemVersion() {
331        return readWord(peHeaderOffset, 64);
332    }
333
334    /**
335     * The minor version number of the required operating system.
336     */
337    public int getMinorOperatingSystemVersion() {
338        return readWord(peHeaderOffset, 66);
339    }
340
341    /**
342     * The major version number of the image.
343     */
344    public int getMajorImageVersion() {
345        return readWord(peHeaderOffset, 68);
346    }
347
348    /**
349     * The minor version number of the image.
350     */
351    public int getMinorImageVersion() {
352        return readWord(peHeaderOffset, 70);
353    }
354
355    /**
356     * The major version number of the subsystem.
357     */
358    public int getMajorSubsystemVersion() {
359        return readWord(peHeaderOffset, 72);
360    }
361
362    /**
363     * The minor version number of the subsystem.
364     */
365    public int getMinorSubsystemVersion() {
366        return readWord(peHeaderOffset, 74);
367    }
368
369    /**
370     * Reserved, must be zero.
371     */
372    public long getWin32VersionValue() {
373        return readDWord(peHeaderOffset, 76);
374    }
375
376    /**
377     * The size (in bytes) of the image, including all headers, as the image
378     * is loaded in memory. It must be a multiple of SectionAlignment.
379     */
380    public long getSizeOfImage() {
381        return readDWord(peHeaderOffset, 80);
382    }
383
384    /**
385     * The combined size of an MS DOS stub, PE header, and section headers
386     * rounded up to a multiple of FileAlignment.
387     */
388    public long getSizeOfHeaders() {
389        return readDWord(peHeaderOffset, 84);
390    }
391
392    /**
393     * The image file checksum.
394     */
395    public long getCheckSum() {
396        return readDWord(peHeaderOffset, 88);
397    }
398
399    /**
400     * Compute the checksum of the image file. The algorithm for computing
401     * the checksum is incorporated into IMAGHELP.DLL.
402     */
403    public synchronized long computeChecksum() {
404        PEImageChecksum checksum = new PEImageChecksum(peHeaderOffset + 88);
405        
406        ByteBuffer b = ByteBuffer.allocate(64 * 1024);
407        
408        try {
409            channel.position(0);
410            
411            int len;
412            while ((len = channel.read(b)) > 0) {
413                checksum.update(b.array(), 0, len);
414            }
415        } catch (IOException e) {
416            throw new RuntimeException(e);
417        }
418        
419        return checksum.getValue();
420    }
421
422    public synchronized void updateChecksum() {
423        ByteBuffer buffer = ByteBuffer.allocate(4);
424        buffer.order(ByteOrder.LITTLE_ENDIAN);
425        buffer.putInt((int) computeChecksum());
426        
427        try {
428            channel.position(peHeaderOffset + 88);
429            channel.write(buffer);
430        } catch (IOException e) {
431            throw new RuntimeException(e);
432        }
433    }
434
435    /**
436     * The subsystem that is required to run this image.
437     */
438    public Subsystem getSubsystem() {
439        return Subsystem.valueOf(readWord(peHeaderOffset, 92));
440    }
441
442    public int getDllCharacteristics() {
443        return readWord(peHeaderOffset, 94);
444    }
445
446    /**
447     * The size of the stack to reserve. Only SizeOfStackCommit is committed;
448     * the rest is made available one page at a time until the reserve size is reached.
449     */
450    public long getSizeOfStackReserve() {
451        if (PEFormat.PE32.equals(getFormat())) {
452            return readDWord(peHeaderOffset, 96);
453        } else {
454            return readQWord(peHeaderOffset, 96);
455        }
456    }
457
458    /**
459     * The size of the stack to commit.
460     */
461    public long getSizeOfStackCommit() {
462        if (PEFormat.PE32.equals(getFormat())) {
463            return readDWord(peHeaderOffset, 100);
464        } else {
465            return readQWord(peHeaderOffset, 104);
466        }
467    }
468
469    /**
470     * The size of the local heap space to reserve. Only SizeOfHeapCommit is
471     * committed; the rest is made available one page at a time until the
472     * reserve size is reached.
473     */
474    public long getSizeOfHeapReserve() {
475        if (PEFormat.PE32.equals(getFormat())) {
476            return readDWord(peHeaderOffset, 104);
477        } else {
478            return readQWord(peHeaderOffset, 112);
479        }
480    }
481
482    /**
483     * The size of the local heap space to commit.
484     */
485    public long getSizeOfHeapCommit() {
486        if (PEFormat.PE32.equals(getFormat())) {
487            return readDWord(peHeaderOffset, 108);
488        } else {
489            return readQWord(peHeaderOffset, 120);
490        }
491    }
492
493    /**
494     * Reserved, must be zero.
495     */
496    public long getLoaderFlags() {
497        return readDWord(peHeaderOffset, PEFormat.PE32.equals(getFormat()) ? 112 : 128);
498    }
499
500    /**
501     * The number of data-directory entries in the remainder of the optional
502     * header. Each describes a location and size.
503     */
504    public int getNumberOfRvaAndSizes() {
505        return (int) readDWord(peHeaderOffset, PEFormat.PE32.equals(getFormat()) ? 116 : 132);
506    }
507
508    int getDataDirectoryOffset() {
509        return (int) peHeaderOffset + (PEFormat.PE32.equals(getFormat()) ? 120 : 136);
510    }
511
512    /**
513     * Returns the data directory of the specified type.
514     */
515    public DataDirectory getDataDirectory(DataDirectoryType type) {
516        if (type.ordinal() >= getNumberOfRvaAndSizes()) {
517            return null;
518        } else {
519            return new DataDirectory(this, type.ordinal());
520        }
521    }
522
523    /**
524     * Writes the data directory of the specified type. The data is either appended
525     * at the end of the file or written over the previous data of the same type if
526     * there is enough space.
527     * 
528     * @param type the type of the data directory
529     * @param data the content of the data directory
530     */
531    public synchronized void writeDataDirectory(DataDirectoryType type, byte[] data) throws IOException {
532        DataDirectory directory = getDataDirectory(type);
533        
534        if (!directory.exists()) {
535            // append the data directory at the end of the file
536            long offset = channel.size();
537            
538            channel.position(offset);
539            channel.write(ByteBuffer.wrap(data));
540            
541            // update the entry in the data directory table
542            directory.write(offset, data.length);
543            
544        } else {
545            if (data.length == directory.getSize()) {
546                // same size as before, just overwrite
547                channel.position(directory.getVirtualAddress());
548                channel.write(ByteBuffer.wrap(data));
549
550            } else if (data.length < directory.getSize() && type != DataDirectoryType.CERTIFICATE_TABLE) {
551                // the new data is smaller, erase and rewrite in-place
552                // this doesn't work with the certificate table since it changes the file digest
553                directory.erase();
554                channel.position(directory.getVirtualAddress());
555                channel.write(ByteBuffer.wrap(data));
556                
557                // update the size in the data directory table
558                directory.write(directory.getVirtualAddress(), data.length);
559
560            } else if (directory.isTrailing()) {
561                // the data is at the end of the file, overwrite it
562                channel.position(directory.getVirtualAddress());
563                channel.write(ByteBuffer.wrap(data));
564                channel.truncate(directory.getVirtualAddress() + data.length); // trim the file if the data shrunk
565                
566                // update the size in the data directory table
567                directory.write(directory.getVirtualAddress(), data.length);
568
569            } else {
570                if (type == DataDirectoryType.CERTIFICATE_TABLE) {
571                    throw new IOException("The certificate table isn't at the end of the file and can't be moved without invalidating the signature");
572                }
573                
574                // the new data is larger, erase and relocate it at the end
575                directory.erase();
576                
577                long offset = channel.size();
578                
579                channel.position(offset);
580                channel.write(ByteBuffer.wrap(data));
581                
582                // update the entry in the data directory table
583                directory.write(offset, data.length);
584            }
585        }
586        
587        updateChecksum();
588    }
589
590    /**
591     * Returns the authenticode signatures on the file.
592     */
593    public synchronized List<CMSSignedData> getSignatures() {
594        List<CMSSignedData> signatures = new ArrayList<>();
595        
596        for (CertificateTableEntry entry : getCertificateTable()) {
597            try {
598                CMSSignedData signedData = entry.getSignature();
599                signatures.add(signedData);
600                
601                // look for nested signatures
602                SignerInformation signerInformation = signedData.getSignerInfos().getSigners().iterator().next();
603                AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
604                if (unsignedAttributes != null) {
605                    Attribute nestedSignatures = unsignedAttributes.get(AuthenticodeObjectIdentifiers.SPC_NESTED_SIGNATURE_OBJID);
606                    if (nestedSignatures != null) {
607                        for (ASN1Encodable nestedSignature : nestedSignatures.getAttrValues()) {
608                            signatures.add(new CMSSignedData((CMSProcessable) null, ContentInfo.getInstance(nestedSignature)));
609                        }
610                    }
611                }
612            } catch (UnsupportedOperationException e) {
613                // unsupported type, just skip
614            } catch (Exception e) {
615                e.printStackTrace();
616            }
617        }
618        
619        return signatures;
620    }
621
622    private synchronized List<CertificateTableEntry> getCertificateTable() {
623        List<CertificateTableEntry> entries = new ArrayList<>();
624        DataDirectory certificateTable = getDataDirectory(DataDirectoryType.CERTIFICATE_TABLE);
625        
626        if (certificateTable != null && certificateTable.exists()) {
627            long position = certificateTable.getVirtualAddress();
628            
629            try {
630                entries.add(new CertificateTableEntry(this, position));
631                
632                // todo read the remaining entries (but Authenticode use only one, extra signatures are appended as a SPC_NESTED_SIGNATURE unauthenticated attribute)
633            } catch (Exception e) {
634                e.printStackTrace();
635            }
636        }
637        
638        return entries;
639    }
640
641    public synchronized List<Section> getSections() {
642        List<Section> sections = new ArrayList<>();
643        int sectionTableOffset = getDataDirectoryOffset() + 8 * getNumberOfRvaAndSizes();
644        
645        for (int i = 0; i < getNumberOfSections(); i++) {
646            sections.add(new Section(this, sectionTableOffset + 40 * i));
647        }
648        
649        return sections;
650    }
651
652    /**
653     * Print detailed informations about the PE file.
654     */
655    public void printInfo(OutputStream out) {
656        printInfo(new PrintWriter(out, true));
657    }
658
659    /**
660     * Print detailed informations about the PE file.
661     */
662    public void printInfo(PrintWriter out) {
663        if (file != null) {
664            out.println("PE File");
665            out.println("  Name:          " + file.getName());
666            out.println("  Size:          " + file.length());
667            out.println("  Last Modified: " + new Date(file.lastModified()));
668            out.println();
669        }
670        
671        out.println("PE Header");
672        out.println("  Machine:                    " + getMachineType());
673        out.println("  Number of sections:         " + getNumberOfSections());
674        out.println("  Timestamp:                  " + getTimeDateStamp());
675        out.println("  Pointer to symbol table:    0x" + Long.toHexString(getPointerToSymbolTable()));
676        out.println("  Number of symbols:          " + getNumberOfSymbols());
677        out.println("  Size of optional header:    " + getSizeOfOptionalHeader());
678        out.println("  Characteristics:            0x" + Long.toBinaryString(getCharacteristics()));
679        out.println();
680        
681        out.println("Optional Header");
682        PEFormat format = getFormat();
683        out.println("  PE Format:                  0x" + Integer.toHexString(format.value) + " (" + format.label + ")");
684        out.println("  Linker version:             " + getMajorLinkerVersion() + "." + getMinorLinkerVersion());
685        out.println("  Size of code:               " + getSizeOfCode());
686        out.println("  Size of initialized data:   " + getSizeOfInitializedData());
687        out.println("  Size of uninitialized data: " + getSizeOfUninitializedData());
688        out.println("  Address of entry point:     0x" + Long.toHexString(getAddressOfEntryPoint()));
689        out.println("  Base of code:               0x" + Long.toHexString(getBaseOfCode()));
690        if (PEFormat.PE32.equals(getFormat())) {
691            out.println("  Base of data:               0x" + Long.toHexString(getBaseOfData()));
692        }
693        out.println("  Image base:                 0x" + Long.toHexString(getImageBase()));
694        out.println("  Section alignment:          " + getSectionAlignment());
695        out.println("  File alignment:             " + getFileAlignment());
696        out.println("  Operating system version:   " + getMajorOperatingSystemVersion() + "." + getMinorOperatingSystemVersion());
697        out.println("  Image version:              " + getMajorImageVersion() + "." + getMinorImageVersion());
698        out.println("  Subsystem version:          " + getMajorSubsystemVersion() + "." + getMinorSubsystemVersion());
699        out.println("  Size of image:              " + getSizeOfImage());
700        out.println("  Size of headers:            " + getSizeOfHeaders());
701        out.println("  Checksum:                   0x" + Long.toHexString(getCheckSum()));
702        out.println("  Checksum (computed):        0x" + Long.toHexString(computeChecksum()));
703        out.println("  Subsystem:                  " + getSubsystem());
704        out.println("  DLL characteristics:        0x" + Long.toBinaryString(getDllCharacteristics()));
705        out.println("  Size of stack reserve:      " + getSizeOfStackReserve());
706        out.println("  Size of stack commit:       " + getSizeOfStackCommit());
707        out.println("  Size of heap reserve:       " + getSizeOfHeapReserve());
708        out.println("  Size of heap commit:        " + getSizeOfHeapCommit());
709        out.println("  Number of RVA and sizes:    " + getNumberOfRvaAndSizes());
710        out.println();
711        
712        out.println("Data Directory");
713        for (DataDirectoryType type : DataDirectoryType.values()) {
714            DataDirectory entry = getDataDirectory(type);
715            if (entry != null && entry.exists()) {
716                out.printf("  %-30s 0x%08x %8d bytes\n", type, entry.getVirtualAddress(), entry.getSize());
717            }
718        }
719        out.println();
720        
721        out.println("Sections");
722        out.println("      Name     Virtual Size  Virtual Address  Raw Data Size  Raw Data Ptr  Characteristics");
723        List<Section> sections = getSections();
724        for (int i = 0; i < sections.size(); i++) {
725            Section section = sections.get(i);
726            out.printf("  #%d  %-8s     %8d       0x%08x       %8d    0x%08x  %s\n", i + 1, section.getName(), section.getVirtualSize(), section.getVirtualAddress(), section.getSizeOfRawData(), section.getPointerToRawData(), section.getCharacteristics());
727        }
728        out.println();
729        
730        List<CMSSignedData> signatures = getSignatures();
731        if (!signatures.isEmpty()) {
732            out.println("Signatures");
733            for (CMSSignedData signedData : signatures) {
734                SignerInformation signerInformation = signedData.getSignerInfos().getSigners().iterator().next();
735                X509CertificateHolder certificate = (X509CertificateHolder) signedData.getCertificates().getMatches(signerInformation.getSID()).iterator().next();
736                
737                String commonName = certificate.getSubject().getRDNs(X509ObjectIdentifiers.commonName)[0].getFirst().getValue().toString();
738                
739                AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
740                boolean timestamped = unsignedAttributes != null &&
741                           (unsignedAttributes.get(PKCSObjectIdentifiers.pkcs_9_at_counterSignature) != null
742                         || unsignedAttributes.get(AuthenticodeObjectIdentifiers.SPC_RFC3161_OBJID)  != null);
743                DigestAlgorithm algorithm = DigestAlgorithm.of(signerInformation.getDigestAlgorithmID().getAlgorithm());
744                out.println("  " + commonName + "  " + (algorithm != null ? "[" + algorithm.id + "]  " : "") + (timestamped ? "(timestamped)" : ""));
745            }
746        }
747    }
748
749    /**
750     * Compute the digest of the file. The checksum field, the certificate
751     * directory table entry and the certificate table are excluded from
752     * the digest.
753     */
754    private synchronized byte[] computeDigest(MessageDigest digest) throws IOException {
755        long checksumLocation = peHeaderOffset + 88;
756        
757        DataDirectory certificateTable = getDataDirectory(DataDirectoryType.CERTIFICATE_TABLE);
758        
759        // digest from the beginning to the checksum field (excluded)
760        updateDigest(digest, 0, checksumLocation);
761        
762        // skip the checksum field
763        long position = checksumLocation + 4;
764        
765        // digest from the end of the checksum field to the beginning of the certificate table entry
766        int certificateTableOffset = getDataDirectoryOffset() + 8 * DataDirectoryType.CERTIFICATE_TABLE.ordinal();
767        updateDigest(digest, position, certificateTableOffset);
768        
769        // skip the certificate entry
770        position = certificateTableOffset + 8;
771        
772        // todo digest the sections in ascending address order
773        
774        // digest from the end of the certificate table entry to the beginning of the certificate table
775        if (certificateTable != null && certificateTable.exists()) {
776            updateDigest(digest, position, certificateTable.getVirtualAddress());
777            position = certificateTable.getVirtualAddress() + certificateTable.getSize();
778        }
779        
780        // digest from the end of the certificate table to the end of the file
781        updateDigest(digest, position, channel.size());
782        
783        return digest.digest();
784    }
785
786    /**
787     * Update the specified digest by reading the underlying RandomAccessFile
788     * from the start offset included to the end offset excluded.
789     * 
790     * @param digest
791     * @param startOffset
792     * @param endOffset
793     */
794    private void updateDigest(MessageDigest digest, long startOffset, long endOffset) throws IOException {
795        channel.position(startOffset);
796        
797        ByteBuffer buffer = ByteBuffer.allocate(8192);
798        
799        long position = startOffset;
800        while (position < endOffset) {
801            buffer.clear();
802            buffer.limit((int) Math.min(buffer.capacity(), endOffset - position));
803            channel.read(buffer);
804            buffer.rewind();
805            
806            digest.update(buffer);
807            
808            position += buffer.limit();
809        }
810    }
811
812    /**
813     * Compute the checksum of the file using the specified digest algorithm.
814     * 
815     * @param algorithm the digest algorithm, typically SHA1
816     */
817    public byte[] computeDigest(DigestAlgorithm algorithm) throws IOException {
818        return computeDigest(algorithm.getMessageDigest());
819    }
820
821    /**
822     * Increase the size of the file up to a size that is a multiple of the specified value.
823     * 
824     * @param multiple
825     */
826    public synchronized void pad(int multiple) throws IOException {
827        long padding = (multiple - channel.size() % multiple) % multiple;
828        channel.position(channel.size());
829        channel.write(ByteBuffer.allocate((int) padding));
830    }
831}