001/**
002 * Copyright 2019 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.security.KeyStore;
020import java.security.KeyStoreException;
021import java.security.NoSuchAlgorithmException;
022import java.security.PrivateKey;
023import java.security.Provider;
024import java.security.Security;
025import java.security.UnrecoverableKeyException;
026import java.security.cert.Certificate;
027import java.security.cert.CertificateEncodingException;
028import java.security.cert.X509Certificate;
029import java.util.ArrayList;
030import java.util.List;
031
032import org.bouncycastle.asn1.ASN1Encodable;
033import org.bouncycastle.asn1.ASN1EncodableVector;
034import org.bouncycastle.asn1.DERNull;
035import org.bouncycastle.asn1.DERSet;
036import org.bouncycastle.asn1.cms.Attribute;
037import org.bouncycastle.asn1.cms.AttributeTable;
038import org.bouncycastle.asn1.cms.CMSAttributes;
039import org.bouncycastle.asn1.cms.ContentInfo;
040import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
041import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
042import org.bouncycastle.cert.X509CertificateHolder;
043import org.bouncycastle.cert.jcajce.JcaCertStore;
044import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
045import org.bouncycastle.cms.CMSAttributeTableGenerator;
046import org.bouncycastle.cms.CMSException;
047import org.bouncycastle.cms.CMSSignedData;
048import org.bouncycastle.cms.DefaultCMSSignatureEncryptionAlgorithmFinder;
049import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
050import org.bouncycastle.cms.SignerInfoGenerator;
051import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
052import org.bouncycastle.cms.SignerInformation;
053import org.bouncycastle.cms.SignerInformationStore;
054import org.bouncycastle.cms.SignerInformationVerifier;
055import org.bouncycastle.cms.jcajce.JcaSignerInfoVerifierBuilder;
056import org.bouncycastle.operator.ContentSigner;
057import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
058import org.bouncycastle.operator.DigestCalculatorProvider;
059import org.bouncycastle.operator.OperatorCreationException;
060import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
061
062import net.jsign.asn1.authenticode.AuthenticodeDigestCalculatorProvider;
063import net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers;
064import net.jsign.asn1.authenticode.AuthenticodeSignedDataGenerator;
065import net.jsign.asn1.authenticode.FilteredAttributeTableGenerator;
066import net.jsign.asn1.authenticode.SpcSpOpusInfo;
067import net.jsign.asn1.authenticode.SpcStatementType;
068import net.jsign.msi.MSIFile;
069import net.jsign.pe.DataDirectory;
070import net.jsign.pe.DataDirectoryType;
071import net.jsign.pe.PEFile;
072import net.jsign.timestamp.Timestamper;
073import net.jsign.timestamp.TimestampingMode;
074
075/**
076 * Sign a file with Authenticode. Timestamping is enabled by default and relies
077 * on the Sectigo server (http://timestamp.sectigo.com).
078 * 
079 * @author Emmanuel Bourg
080 * @since 3.0
081 */
082public class AuthenticodeSigner {
083
084    protected Certificate[] chain;
085    protected PrivateKey privateKey;
086    protected DigestAlgorithm digestAlgorithm = DigestAlgorithm.getDefault();
087    protected String signatureAlgorithm;
088    protected Provider signatureProvider;
089    protected String programName;
090    protected String programURL;
091    protected boolean replace;
092    protected boolean timestamping = true;
093    protected TimestampingMode tsmode = TimestampingMode.AUTHENTICODE;
094    protected String[] tsaurlOverride;
095    protected Timestamper timestamper;
096    protected int timestampingRetries = -1;
097    protected int timestampingRetryWait = -1;
098
099    /**
100     * Create a signer with the specified certificate chain and private key.
101     *
102     * @param chain       the certificate chain. The first certificate is the signing certificate
103     * @param privateKey  the private key
104     * @throws IllegalArgumentException if the chain is empty
105     */
106    public AuthenticodeSigner(Certificate[] chain, PrivateKey privateKey) {
107        this.chain = chain;
108        this.privateKey = privateKey;
109        
110        if (chain == null || chain.length == 0) {
111            throw new IllegalArgumentException("The certificate chain is empty");
112        }
113    }
114
115    /**
116     * Create a signer with a certificate chain and private key from the specified keystore.
117     *
118     * @param keystore the keystore holding the certificate and the private key
119     * @param alias    the alias of the certificate in the keystore
120     * @param password the password to get the private key
121     * @throws KeyStoreException if the keystore has not been initialized (loaded).
122     * @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found
123     * @throws UnrecoverableKeyException if the key cannot be recovered (e.g., the given password is wrong).
124     */
125    public AuthenticodeSigner(KeyStore keystore, String alias, String password) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
126        Certificate[] chain = keystore.getCertificateChain(alias);
127        if (chain == null) {
128            throw new IllegalArgumentException("No certificate found in the keystore with the alias '" + alias + "'");
129        }
130        this.chain = chain;
131        this.privateKey = (PrivateKey) keystore.getKey(alias, password != null ? password.toCharArray() : null);
132    }
133
134    /**
135     * Set the program name embedded in the signature.
136     * 
137     * @param programName the program name
138     * @return the current signer
139     */
140    public AuthenticodeSigner withProgramName(String programName) {
141        this.programName = programName;
142        return this;
143    }
144
145    /**
146     * Set the program URL embedded in the signature.
147     * 
148     * @param programURL the program URL
149     * @return the current signer
150     */
151    public AuthenticodeSigner withProgramURL(String programURL) {
152        this.programURL = programURL;
153        return this;
154    }
155
156    /**
157     * Enable or disable the replacement of the previous signatures (disabled by default).
158     * 
159     * @param replace <code>true</code> if the new signature should replace the existing ones, <code>false</code> to append it
160     * @return the current signer
161     * @since 2.0
162     */
163    public AuthenticodeSigner withSignaturesReplaced(boolean replace) {
164        this.replace = replace;
165        return this;
166    }
167
168    /**
169     * Enable or disable the timestamping (enabled by default).
170     * 
171     * @param timestamping <code>true</code> to enable timestamping, <code>false</code> to disable it
172     * @return the current signer
173     */
174    public AuthenticodeSigner withTimestamping(boolean timestamping) {
175        this.timestamping = timestamping;
176        return this;
177    }
178
179    /**
180     * RFC3161 or Authenticode (Authenticode by default).
181     * 
182     * @param tsmode the timestamping mode
183     * @return the current signer
184     * @since 1.3
185     */
186    public AuthenticodeSigner withTimestampingMode(TimestampingMode tsmode) {
187        this.tsmode = tsmode;
188        return this;
189    }
190
191    /**
192     * Set the URL of the timestamping authority. Both RFC 3161 (as used for jar signing)
193     * and Authenticode timestamping services are supported.
194     * 
195     * @param url the URL of the timestamping authority
196     * @return the current signer
197     * @since 2.1
198     */
199    public AuthenticodeSigner withTimestampingAuthority(String url) {
200        return withTimestampingAuthority(new String[] { url });
201    }
202
203    /**
204     * Set the URLs of the timestamping authorities. Both RFC 3161 (as used for jar signing)
205     * and Authenticode timestamping services are supported.
206     * 
207     * @param urls the URLs of the timestamping authorities
208     * @return the current signer
209     * @since 2.1
210     */
211    public AuthenticodeSigner withTimestampingAuthority(String... urls) {
212        this.tsaurlOverride = urls;
213        return this;
214    }
215
216    /**
217     * Set the Timestamper implementation.
218     * 
219     * @param timestamper the timestamper implementation to use
220     * @return the current signer
221     */
222    public AuthenticodeSigner withTimestamper(Timestamper timestamper) {
223        this.timestamper = timestamper;
224        return this;
225    }
226
227    /**
228     * Set the number of retries for timestamping.
229     * 
230     * @param timestampingRetries the number of retries
231     * @return the current signer
232     */
233    public AuthenticodeSigner withTimestampingRetries(int timestampingRetries) {
234        this.timestampingRetries = timestampingRetries;
235        return this;
236    }
237
238    /**
239     * Set the number of seconds to wait between timestamping retries.
240     * 
241     * @param timestampingRetryWait the wait time between retries (in seconds)
242     * @return the current signer
243     */
244    public AuthenticodeSigner withTimestampingRetryWait(int timestampingRetryWait) {
245        this.timestampingRetryWait = timestampingRetryWait;
246        return this;
247    }
248
249    /**
250     * Set the digest algorithm to use (SHA-256 by default)
251     * 
252     * @param algorithm the digest algorithm
253     * @return the current signer
254     */
255    public AuthenticodeSigner withDigestAlgorithm(DigestAlgorithm algorithm) {
256        if (algorithm != null) {
257            this.digestAlgorithm = algorithm;
258        }
259        return this;
260    }
261
262    /**
263     * Explicitly sets the signature algorithm to use.
264     * 
265     * @param signatureAlgorithm the signature algorithm
266     * @return the current signer
267     * @since 2.0
268     */
269    public AuthenticodeSigner withSignatureAlgorithm(String signatureAlgorithm) {
270        this.signatureAlgorithm = signatureAlgorithm;
271        return this;
272    }
273
274    /**
275     * Explicitly sets the signature algorithm and provider to use.
276     * 
277     * @param signatureAlgorithm the signature algorithm
278     * @param signatureProvider the security provider for the specified algorithm
279     * @return the current signer
280     * @since 2.0
281     */
282    public AuthenticodeSigner withSignatureAlgorithm(String signatureAlgorithm, String signatureProvider) {
283        return withSignatureAlgorithm(signatureAlgorithm, Security.getProvider(signatureProvider));
284    }
285
286    /**
287     * Explicitly sets the signature algorithm and provider to use.
288     * 
289     * @param signatureAlgorithm the signature algorithm
290     * @param signatureProvider the security provider for the specified algorithm
291     * @return the current signer
292     * @since 2.0
293     */
294    public AuthenticodeSigner withSignatureAlgorithm(String signatureAlgorithm, Provider signatureProvider) {
295        this.signatureAlgorithm = signatureAlgorithm;
296        this.signatureProvider = signatureProvider;
297        return this;
298    }
299
300    /**
301     * Set the signature provider to use.
302     * 
303     * @param signatureProvider the security provider for the signature algorithm
304     * @return the current signer
305     * @since 2.0
306     */
307    public AuthenticodeSigner withSignatureProvider(Provider signatureProvider) {
308        this.signatureProvider = signatureProvider;
309        return this;
310    }
311
312    /**
313     * Sign the specified file.
314     *
315     * @param file the file to sign
316     * @throws Exception if signing fails
317     */
318    public void sign(Signable file) throws Exception {
319        if (file instanceof PEFile) {
320            PEFile pefile = (PEFile) file;
321
322            if (replace) {
323                DataDirectory certificateTable = pefile.getDataDirectory(DataDirectoryType.CERTIFICATE_TABLE);
324                if (certificateTable != null && !certificateTable.isTrailing()) {
325                    // erase the previous signature
326                    certificateTable.erase();
327                    certificateTable.write(0, 0);
328                }
329            }
330
331        } else if (file instanceof MSIFile) {
332            MSIFile msi = (MSIFile) file;
333            
334            if (!replace && msi.hasExtendedSignature()) {
335                throw new UnsupportedOperationException("The file has an extended signature which isn't supported by Jsign, it can't be signed without replacing the existing signature");
336            }
337        }
338        
339        CMSSignedData sigData = createSignedData(file);
340        
341        if (!replace) {
342            List<CMSSignedData> signatures = file.getSignatures();
343            if (!signatures.isEmpty()) {
344                // append the nested signature
345                sigData = addNestedSignature(signatures.get(0), sigData);
346            }
347        }
348        
349        file.setSignature(sigData);
350        file.save();
351    }
352
353    /**
354     * Create the PKCS7 message with the signature and the timestamp.
355     * 
356     * @param file the file to sign
357     * @return the PKCS7 message with the signature and the timestamp
358     * @throws Exception if an error occurs
359     */
360    protected CMSSignedData createSignedData(Signable file) throws Exception {
361        // compute the signature
362        ContentInfo contentInfo = file.createContentInfo(digestAlgorithm);
363        AuthenticodeSignedDataGenerator generator = createSignedDataGenerator();
364        CMSSignedData sigData = generator.generate(contentInfo.getContentType(), contentInfo.getContent());
365        
366        // verify the signature
367        DigestCalculatorProvider digestCalculatorProvider = new AuthenticodeDigestCalculatorProvider();
368        SignerInformationVerifier verifier = new JcaSignerInfoVerifierBuilder(digestCalculatorProvider).build(chain[0].getPublicKey());
369        sigData.getSignerInfos().iterator().next().verify(verifier);
370        
371        // timestamping
372        if (timestamping) {
373            Timestamper ts = timestamper;
374            if (ts == null) {
375                ts = Timestamper.create(tsmode);
376            }
377            if (tsaurlOverride != null) {
378                ts.setURLs(tsaurlOverride);
379            }
380            if (timestampingRetries != -1) {
381                ts.setRetries(timestampingRetries);
382            }
383            if (timestampingRetryWait != -1) {
384                ts.setRetryWait(timestampingRetryWait);
385            }
386            sigData = ts.timestamp(digestAlgorithm, sigData);
387        }
388        
389        return sigData;
390    }
391
392    private AuthenticodeSignedDataGenerator createSignedDataGenerator() throws CMSException, OperatorCreationException, CertificateEncodingException {
393        // create content signer
394        final String sigAlg;
395        if (signatureAlgorithm != null) {
396            sigAlg = signatureAlgorithm;
397        } else if ("EC".equals(privateKey.getAlgorithm())) {
398            sigAlg = digestAlgorithm + "withECDSA";
399        } else {
400            sigAlg = digestAlgorithm + "with" + privateKey.getAlgorithm();
401        }
402        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(sigAlg);
403        if (signatureProvider != null) {
404            contentSignerBuilder.setProvider(signatureProvider);
405        }
406        ContentSigner shaSigner = contentSignerBuilder.build(privateKey);
407
408        DigestCalculatorProvider digestCalculatorProvider = new AuthenticodeDigestCalculatorProvider();
409        
410        // prepare the authenticated attributes
411        CMSAttributeTableGenerator attributeTableGenerator = new DefaultSignedAttributeTableGenerator(createAuthenticatedAttributes());
412        attributeTableGenerator = new FilteredAttributeTableGenerator(attributeTableGenerator, CMSAttributes.signingTime, CMSAttributes.cmsAlgorithmProtect);
413        
414        // fetch the signing certificate
415        X509CertificateHolder certificate = new JcaX509CertificateHolder((X509Certificate) chain[0]);
416        
417        // prepare the signerInfo with the extra authenticated attributes
418        SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = new SignerInfoGeneratorBuilder(digestCalculatorProvider, new DefaultCMSSignatureEncryptionAlgorithmFinder(){
419            @Override
420            public AlgorithmIdentifier findEncryptionAlgorithm(final AlgorithmIdentifier signatureAlgorithm) {
421                //enforce "RSA" instead of "shaXXXRSA" for digest signature to be more like signtool
422                if (signatureAlgorithm.getAlgorithm().equals(PKCSObjectIdentifiers.sha256WithRSAEncryption) ||
423                    signatureAlgorithm.getAlgorithm().equals(PKCSObjectIdentifiers.sha384WithRSAEncryption) ||
424                    signatureAlgorithm.getAlgorithm().equals(PKCSObjectIdentifiers.sha512WithRSAEncryption)) {
425                    return new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);
426                } else {
427                    return super.findEncryptionAlgorithm(signatureAlgorithm);
428                }
429            }
430        });
431        signerInfoGeneratorBuilder.setSignedAttributeGenerator(attributeTableGenerator);
432        signerInfoGeneratorBuilder.setContentDigest(createContentDigestAlgorithmIdentifier(shaSigner.getAlgorithmIdentifier()));
433        SignerInfoGenerator signerInfoGenerator = signerInfoGeneratorBuilder.build(shaSigner, certificate);
434        
435        AuthenticodeSignedDataGenerator generator = new AuthenticodeSignedDataGenerator();
436        generator.addCertificates(new JcaCertStore(removeRoot(chain)));
437        generator.addSignerInfoGenerator(signerInfoGenerator);
438        
439        return generator;
440    }
441
442    /**
443     * Remove the root certificate from the chain, unless the chain consists in a single self signed certificate.
444     * 
445     * @param certificates the certificate chain to process
446     * @return the certificate chain without the root certificate
447     */
448    private List<Certificate> removeRoot(Certificate[] certificates) {
449        List<Certificate> list = new ArrayList<>();
450        
451        if (certificates.length == 1) {
452            list.add(certificates[0]);
453        } else {
454            for (Certificate certificate : certificates) {
455                if (!isSelfSigned((X509Certificate) certificate)) {
456                    list.add(certificate);
457                }
458            }
459        }
460        
461        return list;
462    }
463
464    private boolean isSelfSigned(X509Certificate certificate) {
465        return certificate.getSubjectDN().equals(certificate.getIssuerDN());
466    }
467
468    /**
469     * Creates the authenticated attributes for the SignerInfo section of the signature.
470     * 
471     * @return the authenticated attributes
472     */
473    private AttributeTable createAuthenticatedAttributes() {
474        List<Attribute> attributes = new ArrayList<>();
475        
476        SpcStatementType spcStatementType = new SpcStatementType(AuthenticodeObjectIdentifiers.SPC_INDIVIDUAL_SP_KEY_PURPOSE_OBJID);
477        attributes.add(new Attribute(AuthenticodeObjectIdentifiers.SPC_STATEMENT_TYPE_OBJID, new DERSet(spcStatementType)));
478        
479        SpcSpOpusInfo spcSpOpusInfo = new SpcSpOpusInfo(programName, programURL);
480        attributes.add(new Attribute(AuthenticodeObjectIdentifiers.SPC_SP_OPUS_INFO_OBJID, new DERSet(spcSpOpusInfo)));
481
482        return new AttributeTable(new DERSet(attributes.toArray(new ASN1Encodable[0])));
483    }
484
485    /**
486     * Embed a signature as an unsigned attribute of an existing signature.
487     * 
488     * @param primary   the root signature hosting the nested secondary signature
489     * @param secondary the additional signature to nest inside the primary one
490     * @return the signature combining the specified signatures
491     */
492    protected CMSSignedData addNestedSignature(CMSSignedData primary, CMSSignedData secondary) {
493        SignerInformation signerInformation = primary.getSignerInfos().getSigners().iterator().next();
494        
495        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
496        if (unsignedAttributes == null) {
497            unsignedAttributes = new AttributeTable(new DERSet());
498        }
499        Attribute nestedSignaturesAttribute = unsignedAttributes.get(AuthenticodeObjectIdentifiers.SPC_NESTED_SIGNATURE_OBJID);
500        if (nestedSignaturesAttribute == null) {
501            // first nested signature
502            unsignedAttributes = unsignedAttributes.add(AuthenticodeObjectIdentifiers.SPC_NESTED_SIGNATURE_OBJID, secondary.toASN1Structure());
503        } else {
504            // append the signature to the previous nested signatures
505            ASN1EncodableVector nestedSignatures = new ASN1EncodableVector();
506            for (ASN1Encodable nestedSignature : nestedSignaturesAttribute.getAttrValues()) {
507                nestedSignatures.add(nestedSignature);
508            }
509            nestedSignatures.add(secondary.toASN1Structure());
510            
511            ASN1EncodableVector attributes = unsignedAttributes.remove(AuthenticodeObjectIdentifiers.SPC_NESTED_SIGNATURE_OBJID).toASN1EncodableVector();
512            attributes.add(new Attribute(AuthenticodeObjectIdentifiers.SPC_NESTED_SIGNATURE_OBJID, new DERSet(nestedSignatures)));
513            
514            unsignedAttributes = new AttributeTable(attributes);
515        }
516        
517        signerInformation = SignerInformation.replaceUnsignedAttributes(signerInformation, unsignedAttributes);
518        return CMSSignedData.replaceSigners(primary, new SignerInformationStore(signerInformation));
519    }
520
521    /**
522     * Create the digest algorithm identifier to use as content digest.
523     * By default looks up the default identifier but also makes sure it includes
524     * the algorithm parameters and if not includes a DER NULL in order to align
525     * with what signtool currently does.
526     * @param signatureAlgorithm to get the corresponding digest algorithm identifier for
527     * @return an AlgorithmIdentifier for the digestAlgorithm and including parameters
528     */
529    protected AlgorithmIdentifier createContentDigestAlgorithmIdentifier(AlgorithmIdentifier signatureAlgorithm) {
530        AlgorithmIdentifier ai = new DefaultDigestAlgorithmIdentifierFinder().find(signatureAlgorithm);
531        if (ai.getParameters() == null) {
532            // Always include parameters to align with what signtool does
533            ai = new AlgorithmIdentifier(ai.getAlgorithm(), DERNull.INSTANCE);
534        }
535        return ai;
536    }
537}