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}