001/**
002 * Copyright 2019 Emmanuel Bourg and contributors
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;
018
019import java.io.File;
020import java.io.IOException;
021import java.nio.charset.Charset;
022import java.security.MessageDigest;
023import java.util.List;
024
025import org.bouncycastle.asn1.ASN1Object;
026import org.bouncycastle.cms.CMSSignedData;
027
028import net.jsign.msi.MSIFile;
029import net.jsign.pe.PEFile;
030import net.jsign.script.JScript;
031import net.jsign.script.PowerShellScript;
032import net.jsign.script.PowerShellXMLScript;
033import net.jsign.script.VBScript;
034import net.jsign.script.WindowsScript;
035
036/**
037 * A file that can be signed with Authenticode.
038 *
039 * @author Emmanuel Bourg
040 */
041public interface Signable {
042
043    /**
044     * Computes the digest of the file.
045     * 
046     * @param digest the message digest to update
047     * @return the digest of the file
048     * @throws IOException if an I/O error occurs
049     */
050    byte[] computeDigest(MessageDigest digest) throws IOException;
051
052    /**
053     * Creates the SpcIndirectDataContent structure containing the digest of the file.
054     * 
055     * @param digestAlgorithm the digest algorithm to use
056     * @return the SpcIndirectDataContent structure in ASN.1 format
057     * @throws IOException if an I/O error occurs
058     */
059    ASN1Object createIndirectData(DigestAlgorithm digestAlgorithm) throws IOException;
060
061    /**
062     * Returns the Authenticode signatures on the file.
063     * 
064     * @return the signatures
065     * @throws IOException if an I/O error occurs
066     */
067    List<CMSSignedData> getSignatures() throws IOException;
068
069    /**
070     * Sets the signature of the file, overwriting the previous one.
071     * 
072     * @param signature the signature to put
073     * @throws IOException if an I/O error occurs
074     */
075    void setSignature(CMSSignedData signature) throws IOException;
076
077    /**
078     * Saves the file.
079     * 
080     * @throws IOException if an I/O error occurs
081     */
082    void save() throws IOException;
083
084    /**
085     * Returns a signable object for the file specified.
086     *
087     * @param file the file that is intended to to be signed
088     * @return the signable object for the specified file
089     * @throws IOException if an I/O error occurs
090     * @throws UnsupportedOperationException if the file specified isn't supported
091     */
092    static Signable of(File file) throws IOException {
093        return of(file, null);
094    }
095
096    /**
097     * Returns a signable object for the file specified.
098     *
099     * @param file     the file that is intended to to be signed
100     * @param encoding the character encoding (for text files only).
101     *                 If the file has a byte order mark this parameter is ignored.
102     * @return the signable object for the specified file
103     * @throws IOException if an I/O error occurs
104     * @throws UnsupportedOperationException if the file specified isn't supported
105     */
106    static Signable of(File file, Charset encoding) throws IOException {
107        if (PEFile.isPEFile(file)) {
108            return new PEFile(file);
109
110        } else if (MSIFile.isMSIFile(file)) {
111            return new MSIFile(file);
112
113        } else if (file.getName().endsWith(".ps1")
114                || file.getName().endsWith(".psd1")
115                || file.getName().endsWith(".psm1")) {
116            return new PowerShellScript(file, encoding);
117
118        } else if (file.getName().endsWith(".ps1xml")) {
119            return new PowerShellXMLScript(file, encoding);
120
121        } else if (file.getName().endsWith(".vbs")
122                || file.getName().endsWith(".vbe")) {
123            return new VBScript(file, encoding);
124
125        } else if (file.getName().endsWith(".js")
126                || file.getName().endsWith(".jse")) {
127            return new JScript(file, encoding);
128
129        } else if (file.getName().endsWith(".wsf")) {
130            return new WindowsScript(file, encoding);
131
132        } else {
133            throw new UnsupportedOperationException("Unsupported file: " + file);
134        }
135    }
136}