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;
018
019import java.io.IOException;
020import java.security.KeyStore;
021import java.security.KeyStoreException;
022import java.security.NoSuchAlgorithmException;
023import java.security.PrivateKey;
024import java.security.Provider;
025import java.security.Security;
026import java.security.UnrecoverableKeyException;
027import java.security.cert.Certificate;
028import java.security.cert.CertificateEncodingException;
029import java.security.cert.X509Certificate;
030import java.util.ArrayList;
031import java.util.List;
032
033import net.jsign.asn1.authenticode.AuthenticodeDigestCalculatorProvider;
034import net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers;
035import net.jsign.asn1.authenticode.AuthenticodeSignedDataGenerator;
036import net.jsign.asn1.authenticode.SpcAttributeTypeAndOptionalValue;
037import net.jsign.asn1.authenticode.SpcIndirectDataContent;
038import net.jsign.asn1.authenticode.SpcPeImageData;
039import net.jsign.asn1.authenticode.SpcSpOpusInfo;
040import net.jsign.asn1.authenticode.SpcStatementType;
041import net.jsign.pe.CertificateTableEntry;
042import net.jsign.pe.DataDirectory;
043import net.jsign.pe.DataDirectoryType;
044import net.jsign.pe.PEFile;
045import net.jsign.timestamp.Timestamper;
046import net.jsign.timestamp.TimestampingMode;
047import org.bouncycastle.asn1.ASN1Encodable;
048import org.bouncycastle.asn1.ASN1EncodableVector;
049import org.bouncycastle.asn1.DERNull;
050import org.bouncycastle.asn1.DERSet;
051import org.bouncycastle.asn1.cms.Attribute;
052import org.bouncycastle.asn1.cms.AttributeTable;
053import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
054import org.bouncycastle.asn1.x509.DigestInfo;
055import org.bouncycastle.cert.X509CertificateHolder;
056import org.bouncycastle.cert.jcajce.JcaCertStore;
057import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
058import org.bouncycastle.cms.CMSAttributeTableGenerator;
059import org.bouncycastle.cms.CMSException;
060import org.bouncycastle.cms.CMSSignedData;
061import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
062import org.bouncycastle.cms.SignerInfoGenerator;
063import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
064import org.bouncycastle.cms.SignerInformation;
065import org.bouncycastle.cms.SignerInformationStore;
066import org.bouncycastle.cms.SignerInformationVerifier;
067import org.bouncycastle.cms.jcajce.JcaSignerInfoVerifierBuilder;
068import org.bouncycastle.operator.ContentSigner;
069import org.bouncycastle.operator.DigestCalculatorProvider;
070import org.bouncycastle.operator.OperatorCreationException;
071import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
072
073/**
074 * Sign a portable executable file. Timestamping is enabled by default
075 * and relies on the Comodo server (http://timestamp.comodoca.com/authenticode).
076 * 
077 * @see <a href="http://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/Authenticode_PE.docx">Windows Authenticode Portable Executable Signature Format</a>
078 * @see <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/bb931395%28v=vs.85%29.aspx?ppud=4">Time Stamping Authenticode Signatures</a>
079 * 
080 * @author Emmanuel Bourg
081 * @since 1.0
082 */
083public class PESigner {
084
085    private Certificate[] chain;
086    private PrivateKey privateKey;
087    private DigestAlgorithm digestAlgorithm = DigestAlgorithm.getDefault();
088    private String signatureAlgorithm;
089    private Provider signatureProvider;
090    private String programName;
091    private String programURL;
092    private boolean replace;
093
094    private boolean timestamping = true;
095    private TimestampingMode tsmode = TimestampingMode.AUTHENTICODE;
096    private String[] tsaurlOverride;
097    private Timestamper timestamper;
098    private int timestampingRetries = -1;
099    private int timestampingRetryWait = -1;
100
101    /**
102     * Create a PESigner with the specified certificate chain and private key.
103     *
104     * @param chain       the certificate chain. The first certificate is the signing certificate
105     * @param privateKey  the private key
106     * @throws IllegalArgumentException if the chain is empty
107     */
108    public PESigner(Certificate[] chain, PrivateKey privateKey) {
109        this.chain = chain;
110        this.privateKey = privateKey;
111        
112        if (chain == null || chain.length == 0) {
113            throw new IllegalArgumentException("The certificate chain is empty");
114        }
115    }
116
117    /**
118     * Create a PESigner with a certificate chain and private key from the specified keystore.
119     *
120     * @param keystore the keystore holding the certificate and the private key
121     * @param alias    the alias of the certificate in the keystore
122     * @param password the password to get the private key
123     */
124    public PESigner(KeyStore keystore, String alias, String password) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
125        Certificate[] chain = keystore.getCertificateChain(alias);
126        if (chain == null) {
127            throw new IllegalArgumentException("No certificate found in the keystore with the alias '" + alias + "'");
128        }
129        this.chain = chain;
130        this.privateKey = (PrivateKey) keystore.getKey(alias, password.toCharArray());
131    }
132
133    /**
134     * Set the program name embedded in the signature.
135     */
136    public PESigner withProgramName(String programName) {
137        this.programName = programName;
138        return this;
139    }
140
141    /**
142     * Set the program URL embedded in the signature.
143     */
144    public PESigner withProgramURL(String programURL) {
145        this.programURL = programURL;
146        return this;
147    }
148
149    /**
150     * Enable or disable the replacement of the previous signatures (disabled by default).
151     * 
152     * @since 2.0
153     */
154    public PESigner withSignaturesReplaced(boolean replace) {
155        this.replace = replace;
156        return this;
157    }
158
159    /**
160     * Enable or disable the timestamping (enabled by default).
161     */
162    public PESigner withTimestamping(boolean timestamping) {
163        this.timestamping = timestamping;
164        return this;
165    }
166
167    /**
168     * RFC3161 or Authenticode (Authenticode by default).
169     * 
170     * @since 1.3
171     */
172    public PESigner withTimestampingMode(TimestampingMode tsmode) {
173        this.tsmode = tsmode;
174        return this;
175    }
176
177    /**
178     * Set the URL of the timestamping authority. RFC 3161 servers as used
179     * for jar signing are not compatible with Authenticode signatures.
180     * 
181     * @since 2.1
182     */
183    public PESigner withTimestampingAuthority(String url) {
184        return withTimestampingAuthority(new String[] { url });
185    }
186
187    /**
188     * Set the URL of the timestamping authority. RFC 3161 servers as used
189     * for jar signing are not compatible with Authenticode signatures.
190     * 
191     * @deprecated
192     */
193    public PESigner withTimestampingAutority(String url) {
194        return withTimestampingAuthority(url);
195    }
196
197    /**
198     * Set the URL of the timestamping authority. RFC 3161 servers as used
199     * for jar signing are not compatible with Authenticode signatures.
200     * 
201     * @since 2.1
202     */
203    public PESigner withTimestampingAuthority(String... url) {
204        this.tsaurlOverride = url;
205        return this;
206    }
207
208    /**
209     * Set the URL of the timestamping authority. RFC 3161 servers as used
210     * for jar signing are not compatible with Authenticode signatures.
211     *
212     * @since 2.0
213     * @deprecated
214     */
215    public PESigner withTimestampingAutority(String... url) {
216        return withTimestampingAuthority(url);
217    }
218
219    /**
220     * Set the Timestamper implementation.
221     */
222    public PESigner withTimestamper(Timestamper timestamper) {
223        this.timestamper = timestamper;
224        return this;
225    }
226
227    /**
228     * Set the number of retries for timestamping.
229     */
230    public PESigner withTimestampingRetries(int timestampingRetries) {
231        this.timestampingRetries = timestampingRetries;
232        return this;
233    }
234
235    /**
236     * Set the number of seconds to wait between timestamping retries.
237     */
238    public PESigner withTimestampingRetryWait(int timestampingRetryWait) {
239        this.timestampingRetryWait = timestampingRetryWait;
240        return this;
241    }
242
243    /**
244     * Set the digest algorithm to use (SHA-256 by default)
245     */
246    public PESigner withDigestAlgorithm(DigestAlgorithm algorithm) {
247        if (algorithm != null) {
248            this.digestAlgorithm = algorithm;
249        }
250        return this;
251    }
252
253    /**
254     * Explicitly sets the signature algorithm to use.
255     * 
256     * @since 2.0
257     */
258    public PESigner withSignatureAlgorithm(String signatureAlgorithm) {
259        this.signatureAlgorithm = signatureAlgorithm;
260        return this;
261    }
262
263    /**
264     * Explicitly sets the signature algorithm and provider to use.
265     * 
266     * @since 2.0
267     */
268    public PESigner withSignatureAlgorithm(String signatureAlgorithm, String signatureProvider) {
269        return withSignatureAlgorithm(signatureAlgorithm, Security.getProvider(signatureProvider));
270    }
271
272    /**
273     * Explicitly sets the signature algorithm and provider to use.
274     * 
275     * @since 2.0
276     */
277    public PESigner withSignatureAlgorithm(String signatureAlgorithm, Provider signatureProvider) {
278        this.signatureAlgorithm = signatureAlgorithm;
279        this.signatureProvider = signatureProvider;
280        return this;
281    }
282
283    /**
284     * Set the signature provider to use.
285     * 
286     * @since 2.0
287     */
288    public PESigner withSignatureProvider(Provider signatureProvider) {
289        this.signatureProvider = signatureProvider;
290        return this;
291    }
292
293    /**
294     * Sign the specified executable file.
295     *
296     * @throws Exception
297     */
298    public void sign(PEFile file) throws Exception {
299        // pad the file on a 8 byte boundary
300        // todo only if there was no previous certificate table
301        file.pad(8);
302        
303        if (replace) {
304            DataDirectory certificateTable = file.getDataDirectory(DataDirectoryType.CERTIFICATE_TABLE);
305            if (certificateTable != null && !certificateTable.isTrailing()) {
306                // erase the previous signature
307                certificateTable.erase();
308                certificateTable.write(0, 0);
309            }
310        }
311        
312        // compute the signature
313        CMSSignedData sigData = createSignature(file);
314        
315        // verify the signature
316        DigestCalculatorProvider digestCalculatorProvider = new AuthenticodeDigestCalculatorProvider();
317        SignerInformationVerifier verifier = new JcaSignerInfoVerifierBuilder(digestCalculatorProvider).build(chain[0].getPublicKey());
318        sigData.getSignerInfos().iterator().next().verify(verifier);
319        
320        // timestamping
321        if (timestamping) {
322            Timestamper ts = timestamper;
323            if (ts == null) {
324                ts = Timestamper.create(tsmode);
325            }
326            if (tsaurlOverride != null) {
327                ts.setURLs(tsaurlOverride);
328            }
329            if (timestampingRetries != -1) {
330                ts.setRetries(timestampingRetries);
331            }
332            if (timestampingRetryWait != -1) {
333                ts.setRetryWait(timestampingRetryWait);
334            }
335            sigData = ts.timestamp(digestAlgorithm, sigData);
336        }
337        
338        List<CMSSignedData> signatures = file.getSignatures();
339        if (!signatures.isEmpty() && !replace) {
340            // append the nested signature
341            sigData = addNestedSignature(signatures.get(0), sigData);
342        }
343
344        CertificateTableEntry entry = new CertificateTableEntry(sigData);
345        
346        file.writeDataDirectory(DataDirectoryType.CERTIFICATE_TABLE, entry.toBytes());
347        file.close();
348    }
349
350    private CMSSignedData addNestedSignature(CMSSignedData primary, CMSSignedData secondary) throws CMSException {
351        SignerInformation signerInformation = primary.getSignerInfos().getSigners().iterator().next();
352        
353        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
354        if (unsignedAttributes == null) {
355            unsignedAttributes = new AttributeTable(new DERSet());
356        }
357        Attribute nestedSignaturesAttribute = unsignedAttributes.get(AuthenticodeObjectIdentifiers.SPC_NESTED_SIGNATURE_OBJID);
358        if (nestedSignaturesAttribute == null) {
359            // first nested signature
360            unsignedAttributes = unsignedAttributes.add(AuthenticodeObjectIdentifiers.SPC_NESTED_SIGNATURE_OBJID, secondary.toASN1Structure());
361        } else {
362            // append the signature to the previous nested signatures
363            ASN1EncodableVector nestedSignatures = new ASN1EncodableVector();
364            for (ASN1Encodable nestedSignature : nestedSignaturesAttribute.getAttrValues()) {
365                nestedSignatures.add(nestedSignature);
366            }
367            nestedSignatures.add(secondary.toASN1Structure());
368            
369            ASN1EncodableVector attributes = unsignedAttributes.remove(AuthenticodeObjectIdentifiers.SPC_NESTED_SIGNATURE_OBJID).toASN1EncodableVector();
370            attributes.add(new Attribute(AuthenticodeObjectIdentifiers.SPC_NESTED_SIGNATURE_OBJID, new DERSet(nestedSignatures)));
371            
372            unsignedAttributes = new AttributeTable(attributes);
373        }
374        
375        signerInformation = SignerInformation.replaceUnsignedAttributes(signerInformation, unsignedAttributes);
376        return CMSSignedData.replaceSigners(primary, new SignerInformationStore(signerInformation));
377    }
378
379    private CMSSignedData createSignature(PEFile file) throws IOException, CMSException, OperatorCreationException, CertificateEncodingException {
380        byte[] sha = file.computeDigest(digestAlgorithm);
381        
382        AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(digestAlgorithm.oid, DERNull.INSTANCE);
383        DigestInfo digestInfo = new DigestInfo(algorithmIdentifier, sha);
384        SpcAttributeTypeAndOptionalValue data = new SpcAttributeTypeAndOptionalValue(AuthenticodeObjectIdentifiers.SPC_PE_IMAGE_DATA_OBJID, new SpcPeImageData());
385        SpcIndirectDataContent spcIndirectDataContent = new SpcIndirectDataContent(data, digestInfo);
386
387        // create content signer
388        final String sigAlg;
389        if (signatureAlgorithm == null) {
390            sigAlg = digestAlgorithm + "with" + privateKey.getAlgorithm();
391        } else {
392            sigAlg = signatureAlgorithm;
393        }
394        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(sigAlg);
395        if (signatureProvider != null) {
396            contentSignerBuilder.setProvider(signatureProvider);
397        }
398        ContentSigner shaSigner = contentSignerBuilder.build(privateKey);
399
400        DigestCalculatorProvider digestCalculatorProvider = new AuthenticodeDigestCalculatorProvider();
401        
402        // prepare the authenticated attributes
403        CMSAttributeTableGenerator attributeTableGenerator = new DefaultSignedAttributeTableGenerator(createAuthenticatedAttributes());
404        
405        // fetch the signing certificate
406        X509CertificateHolder certificate = new JcaX509CertificateHolder((X509Certificate) chain[0]);
407        
408        // prepare the signerInfo with the extra authenticated attributes
409        SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = new SignerInfoGeneratorBuilder(digestCalculatorProvider);
410        signerInfoGeneratorBuilder.setSignedAttributeGenerator(attributeTableGenerator);
411        SignerInfoGenerator signerInfoGenerator = signerInfoGeneratorBuilder.build(shaSigner, certificate);
412        
413        AuthenticodeSignedDataGenerator generator = new AuthenticodeSignedDataGenerator();
414        generator.addCertificates(new JcaCertStore(removeRoot(chain)));
415        generator.addSignerInfoGenerator(signerInfoGenerator);
416        
417        return generator.generate(AuthenticodeObjectIdentifiers.SPC_INDIRECT_DATA_OBJID, spcIndirectDataContent);
418    }
419
420    /**
421     * Remove the root certificate from the chain, unless the chain consists in a single self signed certificate.
422     */
423    private List<Certificate> removeRoot(Certificate[] certificates) {
424        List<Certificate> list = new ArrayList<>();
425        
426        if (certificates.length == 1) {
427            list.add(certificates[0]);
428        } else {
429            for (Certificate certificate : certificates) {
430                if (!isSelfSigned((X509Certificate) certificate)) {
431                    list.add(certificate);
432                }
433            }
434        }
435        
436        return list;
437    }
438
439    private boolean isSelfSigned(X509Certificate certificate) {
440        return certificate.getSubjectDN().equals(certificate.getIssuerDN());
441    }
442
443    /**
444     * Creates the authenticated attributes for the SignerInfo section of the signature.
445     */
446    private AttributeTable createAuthenticatedAttributes() {
447        List<Attribute> attributes = new ArrayList<>();
448        
449        SpcStatementType spcStatementType = new SpcStatementType(AuthenticodeObjectIdentifiers.SPC_INDIVIDUAL_SP_KEY_PURPOSE_OBJID);
450        attributes.add(new Attribute(AuthenticodeObjectIdentifiers.SPC_STATEMENT_TYPE_OBJID, new DERSet(spcStatementType)));
451        
452        SpcSpOpusInfo spcSpOpusInfo = new SpcSpOpusInfo(programName, programURL);
453        attributes.add(new Attribute(AuthenticodeObjectIdentifiers.SPC_SP_OPUS_INFO_OBJID, new DERSet(spcSpOpusInfo)));
454
455        return new AttributeTable(new DERSet(attributes.toArray(new ASN1Encodable[attributes.size()])));
456    }
457}