1 /* 2 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.javafx.tools.packager; 27 28 import java.io.ByteArrayOutputStream; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.UnsupportedEncodingException; 32 import java.math.BigInteger; 33 import java.security.CodeSigner; 34 import java.security.InvalidKeyException; 35 import java.security.NoSuchAlgorithmException; 36 import java.security.Principal; 37 import java.security.PrivateKey; 38 import java.security.PublicKey; 39 import java.security.Signature; 40 import java.security.SignatureException; 41 import java.security.Timestamp; 42 import java.security.cert.CertPath; 43 import java.security.cert.CertificateException; 44 import java.security.cert.CertificateFactory; 45 import java.security.cert.X509Certificate; 46 import java.util.ArrayList; 47 import java.util.Locale; 48 import java.util.jar.JarFile; 49 import java.util.zip.ZipEntry; 50 import java.util.zip.ZipInputStream; 51 import java.util.zip.ZipOutputStream; 52 import sun.security.pkcs.ContentInfo; 53 import sun.security.pkcs.PKCS7; 54 import sun.security.pkcs.PKCS9Attribute; 55 import sun.security.pkcs.PKCS9Attributes; 56 import sun.security.pkcs.ParsingException; 57 import sun.security.pkcs.SignerInfo; 58 import sun.security.timestamp.TimestampToken; 59 import sun.security.x509.AlgorithmId; 60 import sun.security.x509.X500Name; 61 62 /** 63 * (Source copied from: 64 * com.sun.deploy.security.JarSignature in Deploy workspace) 65 * 66 * This class is an abstraction of signature that is currently used 67 * for implementation of jar signing as BLOBs. 68 * 69 * There are 2 modes of use for this class - signing and validation 70 * and same instance of JarSignature object can not be reused. 71 * 72 * Signing mode: 73 * - create new instance using JarSignature.create() 74 * - add entries you want to include into the signature using 75 * updateWithEntry(). (Note the order is important.) 76 * - use getEncoded() to get bytes for the result signature 77 * 78 * Validation mode: 79 * - create new instance using JarSignature.load() 80 * - add entries using updateWithEntry() 81 * - use isValid() to validate result 82 * - use getCodeSigners() to get list of code signers used 83 * 84 * @Deprecated 85 */ 86 @Deprecated 87 public class JarSignature { 88 //name of jar manifest attribute that contains signature 89 public static final String BLOB_SIGNATURE = "META-INF/SIGNATURE.BSF"; 90 91 private final Signature sig; 92 private final X509Certificate certChain[]; // for singing scenarios only 93 private final CodeSigner codeSigners[]; // for validation only 94 private final SignerInfo signerInfos[]; // for validation only 95 96 /** 97 * Loads jar signature from given byte array. 98 * If signature could not be reconstructed then exceptions are thrown. 99 */ 100 public static JarSignature load(byte[] rawSignature) throws ParsingException, 101 CertificateException, IOException, NoSuchAlgorithmException, 102 InvalidKeyException, SignatureException 103 { 104 PKCS7 pkcs7 = new PKCS7(rawSignature); 105 SignerInfo[] infos = pkcs7.getSignerInfos(); 106 if (infos == null || infos.length != 1) { 107 throw new IllegalArgumentException( 108 "BLOB signature currently only support single signer."); 109 } 110 X509Certificate cert = infos[0].getCertificate(pkcs7); 111 PublicKey publicKey = cert.getPublicKey(); 112 CodeSigner[] signers = extractCodeSigners(infos, pkcs7); 113 Signature sig = getSignature(infos[0]); 114 sig.initVerify(publicKey); 115 116 return new JarSignature(sig, infos, signers); 117 } 118 119 /** 120 * Creates new signature for signing. 121 * 122 * @param privateKey Key to be used for signing. 123 * @return 124 * @throws NoSuchAlgorithmException 125 * @throws InvalidKeyException 126 */ 127 public static JarSignature create( 128 PrivateKey privateKey, X509Certificate chain[]) 129 throws NoSuchAlgorithmException, InvalidKeyException 130 { 131 Signature signature = getSignature(privateKey.getAlgorithm()); 132 signature.initSign(privateKey); 133 return new JarSignature(signature, chain); 134 } 135 136 private JarSignature(Signature signature, X509Certificate chain[]) { 137 certChain = chain; 138 signerInfos = null; 139 codeSigners = null; 140 sig = signature; 141 } 142 143 private JarSignature(Signature signature, SignerInfo[] infos, CodeSigner[] signers) { 144 certChain = null; 145 signerInfos = infos; 146 codeSigners = signers; 147 sig = signature; 148 } 149 150 public boolean isValidationMode() { 151 return certChain == null; 152 } 153 154 /** 155 * Get default signature object base on default signature algorithm derived 156 * from given key algorithm for use in encoding usage. 157 */ 158 private static Signature getSignature(String keyAlgorithm) 159 throws NoSuchAlgorithmException 160 { 161 if (keyAlgorithm.equalsIgnoreCase("DSA")) { 162 return Signature.getInstance("SHA1withDSA"); 163 } else if (keyAlgorithm.equalsIgnoreCase("RSA")) { 164 return Signature.getInstance("SHA256withRSA"); 165 } else if (keyAlgorithm.equalsIgnoreCase("EC")) { 166 return Signature.getInstance("SHA256withECDSA"); 167 } 168 throw new IllegalArgumentException( 169 "Key algorithm should be either DSA, RSA or EC"); 170 } 171 172 /** 173 * Derive Signature from signer info for use in validation. 174 */ 175 private static Signature getSignature(SignerInfo info) 176 throws NoSuchAlgorithmException 177 { 178 String digestAlgorithm = info.getDigestAlgorithmId().getName(); 179 String keyAlgorithm = info.getDigestEncryptionAlgorithmId().getName(); 180 String signatureAlgorithm = makeSigAlg( 181 digestAlgorithm, keyAlgorithm); 182 return Signature.getInstance(signatureAlgorithm); 183 } 184 185 String getSignatureAlgorithm() throws NoSuchAlgorithmException { 186 return sig.getAlgorithm(); 187 } 188 189 AlgorithmId getDigestAlgorithm() throws NoSuchAlgorithmException { 190 String name = getDigAlgFromSigAlg(sig.getAlgorithm()); 191 return AlgorithmId.get(name); 192 } 193 194 AlgorithmId getKeyAlgorithm() throws NoSuchAlgorithmException { 195 String name = getEncAlgFromSigAlg(sig.getAlgorithm()); 196 return AlgorithmId.get(name); 197 } 198 199 private static String makeSigAlg(String digAlg, String encAlg) { 200 digAlg = digAlg.replace("-", "").toUpperCase(Locale.ENGLISH); 201 if (digAlg.equalsIgnoreCase("SHA")) digAlg = "SHA1"; 202 203 encAlg = encAlg.toUpperCase(Locale.ENGLISH); 204 if (encAlg.equals("EC")) encAlg = "ECDSA"; 205 206 return digAlg + "with" + encAlg; 207 } 208 209 private static String getDigAlgFromSigAlg(String signatureAlgorithm) { 210 signatureAlgorithm = signatureAlgorithm.toUpperCase(Locale.ENGLISH); 211 int with = signatureAlgorithm.indexOf("WITH"); 212 if (with > 0) { 213 return signatureAlgorithm.substring(0, with); 214 } 215 return null; 216 } 217 218 private static String getEncAlgFromSigAlg(String signatureAlgorithm) { 219 signatureAlgorithm = signatureAlgorithm.toUpperCase(Locale.ENGLISH); 220 int with = signatureAlgorithm.indexOf("WITH"); 221 String keyAlgorithm = null; 222 if (with > 0) { 223 int and = signatureAlgorithm.indexOf("AND", with + 4); 224 if (and > 0) { 225 keyAlgorithm = signatureAlgorithm.substring(with + 4, and); 226 } else { 227 keyAlgorithm = signatureAlgorithm.substring(with + 4); 228 } 229 if (keyAlgorithm.equalsIgnoreCase("ECDSA")) { 230 keyAlgorithm = "EC"; 231 } 232 } 233 return keyAlgorithm; 234 } 235 236 /** 237 * Returns encoded representation of signature. 238 * @throws UnsupportedOperationException if called in validation mode. 239 */ 240 public byte[] getEncoded() 241 throws NoSuchAlgorithmException, SignatureException, IOException { 242 if (isValidationMode()) { 243 throw new UnsupportedOperationException( 244 "Method is not for validation mode."); 245 } 246 247 AlgorithmId digestAlgId = getDigestAlgorithm(); 248 AlgorithmId[] digestAlgIds = {digestAlgId}; 249 ContentInfo contentInfo = new ContentInfo(ContentInfo.DATA_OID, null); 250 Principal issuerName = certChain[0].getIssuerDN(); 251 BigInteger serialNumber = certChain[0].getSerialNumber(); 252 byte[] signature = sig.sign(); 253 SignerInfo signerInfo = 254 new SignerInfo((X500Name) issuerName, serialNumber, digestAlgId, 255 getKeyAlgorithm(), signature); 256 257 SignerInfo[] signerInfos = {signerInfo}; 258 PKCS7 pkcs7 = new PKCS7(digestAlgIds, contentInfo, certChain, 259 signerInfos); 260 ByteArrayOutputStream bos = new ByteArrayOutputStream(8192); 261 pkcs7.encodeSignedData(bos); 262 return bos.toByteArray(); 263 } 264 265 private class ValidationStream extends InputStream { 266 InputStream dataStream = null; 267 268 public ValidationStream( 269 InputStream is) { 270 dataStream = is; 271 } 272 273 public int read() throws IOException { 274 int v = dataStream.read(); 275 if (v > -1) { 276 try { 277 JarSignature.this.sig.update((byte) v); 278 } catch (SignatureException ex) { 279 throw new RuntimeException(ex); 280 } 281 } 282 return v; 283 } 284 285 public int read(byte[] b, int off, int len) throws IOException { 286 len = dataStream.read(b, off, len); 287 if (len > 0) { 288 try { 289 JarSignature.this.sig.update(b, off, len); 290 } catch (SignatureException ex) { 291 throw new RuntimeException(ex); 292 } 293 } 294 return len; 295 } 296 297 public void close() throws IOException { 298 dataStream.close(); 299 } 300 301 } 302 303 /** 304 * Performs partial validation of zip/jar entry with given name and 305 * data stream. Return wrapped version of input stream to complete validation. 306 * Stream need to be read fully for validation to be complete. 307 * 308 * @throws SignatureException 309 */ 310 public InputStream updateWithZipEntry(String name, InputStream is) 311 throws SignatureException { 312 try { 313 sig.update(name.getBytes("UTF-8")); 314 } catch(UnsupportedEncodingException e) { 315 throw new SignatureException(e); 316 } 317 return new ValidationStream(is); 318 } 319 320 public void update(byte[] v) throws SignatureException { 321 sig.update(v); 322 } 323 324 public boolean isValid() { 325 try { 326 return sig.verify(signerInfos[0].getEncryptedDigest()); 327 } catch(Exception e) { 328 return false; 329 } 330 } 331 332 public CodeSigner[] getCodeSigners() { 333 CodeSigner[] result = new CodeSigner[codeSigners.length]; 334 System.arraycopy(codeSigners, 0, result, 0, result.length); 335 return result; 336 } 337 338 /* 339 * Implementation is mostly borrowed from 340 * sun.security.util.SignatureFileVerifier 341 */ 342 private static CodeSigner[] extractCodeSigners(SignerInfo infos[], PKCS7 block) 343 throws IOException, NoSuchAlgorithmException, SignatureException, 344 CertificateException { 345 ArrayList s = new ArrayList(); 346 347 CertificateFactory certificateFactory = 348 CertificateFactory.getInstance("X509"); 349 for (int i = 0; i < infos.length; i++) { 350 SignerInfo info = infos[i]; 351 ArrayList chain = info.getCertificateChain(block); 352 CertPath certPath = certificateFactory.generateCertPath(chain); 353 // Append the new code signer 354 CodeSigner signer = 355 new CodeSigner(certPath, getTimestamp(info, certificateFactory)); 356 // if (block.getCRLs() != null) { 357 // SharedSecrets.getJavaSecurityCodeSignerAccess().setCRLs( 358 // signer, block.getCRLs()); 359 // } 360 s.add(signer); 361 } 362 363 return (CodeSigner[]) s.toArray(new CodeSigner[s.size()]); 364 } 365 366 /* 367 * Examines a signature timestamp token to generate a timestamp object. 368 * 369 * Examines the signer's unsigned attributes for a 370 * <tt>signatureTimestampToken</tt> attribute. If present, 371 * then it is parsed to extract the date and time at which the 372 * timestamp was generated. 373 * 374 * @param info A signer information element of a PKCS 7 block. 375 * 376 * @return A timestamp token or null if none is present. 377 * @throws IOException if an error is encountered while parsing the 378 * PKCS7 data. 379 * @throws NoSuchAlgorithmException if an error is encountered while 380 * verifying the PKCS7 object. 381 * @throws SignatureException if an error is encountered while 382 * verifying the PKCS7 object. 383 * @throws CertificateException if an error is encountered while generating 384 * the TSA's certpath. 385 */ 386 private static Timestamp getTimestamp(SignerInfo info, 387 CertificateFactory certificateFactory) throws IOException, 388 NoSuchAlgorithmException, SignatureException, CertificateException 389 { 390 Timestamp timestamp = null; 391 392 // Extract the signer's unsigned attributes 393 PKCS9Attributes unsignedAttrs = info.getUnauthenticatedAttributes(); 394 if (unsignedAttrs != null) { 395 PKCS9Attribute timestampTokenAttr = 396 unsignedAttrs.getAttribute("signatureTimestampToken"); 397 if (timestampTokenAttr != null) { 398 PKCS7 timestampToken = 399 new PKCS7((byte[]) timestampTokenAttr.getValue()); 400 // Extract the content (an encoded timestamp token info) 401 byte[] encodedTimestampTokenInfo = 402 timestampToken.getContentInfo().getData(); 403 // Extract the signer (the Timestamping Authority) 404 // while verifying the content 405 SignerInfo[] tsa = 406 timestampToken.verify(encodedTimestampTokenInfo); 407 // Expect only one signer 408 ArrayList chain = tsa[0].getCertificateChain(timestampToken); 409 CertPath tsaChain = certificateFactory.generateCertPath( 410 chain); 411 // Create a timestamp token info object 412 TimestampToken timestampTokenInfo = 413 new TimestampToken(encodedTimestampTokenInfo); 414 // Create a timestamp object 415 timestamp = 416 new Timestamp(timestampTokenInfo.getDate(), tsaChain); 417 } 418 } 419 return timestamp; 420 } 421 422 public interface InputStreamSource { 423 InputStream getInputStream() throws IOException; 424 } 425 426 public void signJarAsBLOB(InputStreamSource input, ZipOutputStream jos) 427 throws IOException, SignatureException, NoSuchAlgorithmException 428 { 429 byte copyBuf[] = new byte[8000]; 430 int n; 431 ZipEntry e; 432 ZipInputStream jis = new ZipInputStream(input.getInputStream()); 433 434 try { 435 //calculate signature in the first pass 436 //consider all entries except directories and old signature file (if any) 437 boolean hasManifest = false; 438 boolean hasMetaInf = false; 439 while ((e = jis.getNextEntry()) != null) { 440 if (JarFile.MANIFEST_NAME.equals( 441 e.getName().toUpperCase(Locale.ENGLISH))) { 442 hasManifest = true; 443 } 444 if ("META-INF/".equals( 445 e.getName().toUpperCase(Locale.ENGLISH))) { 446 hasMetaInf = true; 447 } 448 if (!BLOB_SIGNATURE.equals(e.getName()) && 449 !e.getName().endsWith("/")) { 450 readFully(updateWithZipEntry(e.getName(), jis)); 451 } 452 } 453 454 byte[] signature = getEncoded(); 455 456 //2 pass: save manifest and other entries 457 458 //reopen input file 459 jis.close(); 460 jis = new ZipInputStream(input.getInputStream()); 461 while ((e = jis.getNextEntry()) != null) { 462 String name = e.getName(); 463 464 //special case - jar has no manifest and possibly no META-INF 465 // => add META-INF entry once 466 //Then output manifest 467 if (!hasMetaInf) { 468 ZipEntry ze = new ZipEntry("META-INF/"); 469 ze.setTime(System.currentTimeMillis()); 470 //NOTE: Do we need CRC32? for this entry? 471 jos.putNextEntry(ze); 472 jos.closeEntry(); 473 474 hasMetaInf = true; 475 } 476 if (!hasManifest) { 477 addSignatureEntry(signature, jos); 478 hasManifest = true; 479 } 480 481 482 //copy entry unless it is old signature file 483 if (!BLOB_SIGNATURE.equals(name)) { 484 // do our own compression 485 ZipEntry e2 = new ZipEntry(name); 486 e2.setMethod(e.getMethod()); 487 e2.setTime(e.getTime()); 488 e2.setComment(e.getComment()); 489 e2.setExtra(e.getExtra()); 490 if (e.getMethod() == ZipEntry.STORED) { 491 e2.setSize(e.getSize()); 492 e2.setCrc(e.getCrc()); 493 } 494 jos.putNextEntry(e2); 495 496 while ((n = jis.read(copyBuf)) != -1) { 497 jos.write(copyBuf, 0, n); 498 } 499 jos.closeEntry(); 500 } 501 502 String upperName = name.toUpperCase(Locale.ENGLISH); 503 boolean isManifestEntry = JarFile.MANIFEST_NAME.equals(upperName); 504 505 //output signature after manifest 506 if (isManifestEntry) { 507 addSignatureEntry(signature, jos); 508 } 509 } 510 } finally { 511 jis.close(); 512 jos.close(); 513 } 514 } 515 516 private void addSignatureEntry(byte[] signature, ZipOutputStream jos) throws IOException { 517 ZipEntry ze = new ZipEntry(BLOB_SIGNATURE); 518 ze.setSize(signature.length); 519 ze.setTime(System.currentTimeMillis()); 520 //NOTE: Do we need a CRC32? 521 jos.putNextEntry(ze); 522 jos.write(signature); 523 jos.closeEntry(); 524 525 } 526 527 private static void readFully(InputStream is) throws IOException { 528 byte buf[] = new byte[10000]; 529 while (is.read(buf) != -1) {} 530 } 531 532 }