1 /* 2 * Copyright (c) 1997, 2013, 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 sun.security.util; 27 28 import java.security.cert.CertPath; 29 import java.security.cert.X509Certificate; 30 import java.security.cert.CertificateException; 31 import java.security.cert.CertificateFactory; 32 import java.security.*; 33 import java.io.*; 34 import java.util.*; 35 import java.util.jar.*; 36 37 import sun.security.pkcs.*; 38 import sun.security.timestamp.TimestampToken; 39 import sun.misc.BASE64Decoder; 40 41 import sun.security.jca.Providers; 42 43 public class SignatureFileVerifier { 44 45 /* Are we debugging ? */ 46 private static final Debug debug = Debug.getInstance("jar"); 47 48 /* cache of CodeSigner objects */ 49 private ArrayList<CodeSigner[]> signerCache; 50 51 private static final String ATTR_DIGEST = 52 ("-DIGEST-" + ManifestDigester.MF_MAIN_ATTRS).toUpperCase 53 (Locale.ENGLISH); 54 55 /** the PKCS7 block for this .DSA/.RSA/.EC file */ 56 private PKCS7 block; 57 58 /** the raw bytes of the .SF file */ 59 private byte sfBytes[]; 60 61 /** the name of the signature block file, uppercased and without 62 * the extension (.DSA/.RSA/.EC) 63 */ 64 private String name; 65 66 /** the ManifestDigester */ 67 private ManifestDigester md; 68 69 /** cache of created MessageDigest objects */ 70 private HashMap<String, MessageDigest> createdDigests; 71 72 /* workaround for parsing Netscape jars */ 73 private boolean workaround = false; 74 75 /* for generating certpath objects */ 76 private CertificateFactory certificateFactory = null; 77 78 /** 79 * Create the named SignatureFileVerifier. 80 * 81 * @param name the name of the signature block file (.DSA/.RSA/.EC) 82 * 83 * @param rawBytes the raw bytes of the signature block file 84 */ 85 public SignatureFileVerifier(ArrayList<CodeSigner[]> signerCache, 86 ManifestDigester md, 87 String name, 88 byte rawBytes[]) 89 throws IOException, CertificateException 90 { 91 // new PKCS7() calls CertificateFactory.getInstance() 92 // need to use local providers here, see Providers class 93 Object obj = null; 94 try { 95 obj = Providers.startJarVerification(); 96 block = new PKCS7(rawBytes); 97 sfBytes = block.getContentInfo().getData(); 98 certificateFactory = CertificateFactory.getInstance("X509"); 99 } finally { 100 Providers.stopJarVerification(obj); 101 } 102 this.name = name.substring(0, name.lastIndexOf(".")) 103 .toUpperCase(Locale.ENGLISH); 104 this.md = md; 105 this.signerCache = signerCache; 106 } 107 108 /** 109 * returns true if we need the .SF file 110 */ 111 public boolean needSignatureFileBytes() 112 { 113 114 return sfBytes == null; 115 } 116 117 118 /** 119 * returns true if we need this .SF file. 120 * 121 * @param name the name of the .SF file without the extension 122 * 123 */ 124 public boolean needSignatureFile(String name) 125 { 126 return this.name.equalsIgnoreCase(name); 127 } 128 129 /** 130 * used to set the raw bytes of the .SF file when it 131 * is external to the signature block file. 132 */ 133 public void setSignatureFile(byte sfBytes[]) 134 { 135 this.sfBytes = sfBytes; 136 } 137 138 /** 139 * Utility method used by JarVerifier and JarSigner 140 * to determine the signature file names and PKCS7 block 141 * files names that are supported 142 * 143 * @param s file name 144 * @return true if the input file name is a supported 145 * Signature File or PKCS7 block file name 146 */ 147 public static boolean isBlockOrSF(String s) { 148 // we currently only support DSA and RSA PKCS7 blocks 149 if (s.endsWith(".SF") || s.endsWith(".DSA") || 150 s.endsWith(".RSA") || s.endsWith(".EC")) { 151 return true; 152 } 153 return false; 154 } 155 156 /** get digest from cache */ 157 158 private MessageDigest getDigest(String algorithm) 159 { 160 if (createdDigests == null) 161 createdDigests = new HashMap<String, MessageDigest>(); 162 163 MessageDigest digest = createdDigests.get(algorithm); 164 165 if (digest == null) { 166 try { 167 digest = MessageDigest.getInstance(algorithm); 168 createdDigests.put(algorithm, digest); 169 } catch (NoSuchAlgorithmException nsae) { 170 // ignore 171 } 172 } 173 return digest; 174 } 175 176 /** 177 * process the signature block file. Goes through the .SF file 178 * and adds code signers for each section where the .SF section 179 * hash was verified against the Manifest section. 180 * 181 * 182 */ 183 public void process(Hashtable<String, CodeSigner[]> signers, 184 List manifestDigests) 185 throws IOException, SignatureException, NoSuchAlgorithmException, 186 JarException, CertificateException 187 { 188 // calls Signature.getInstance() and MessageDigest.getInstance() 189 // need to use local providers here, see Providers class 190 Object obj = null; 191 try { 192 obj = Providers.startJarVerification(); 193 processImpl(signers, manifestDigests); 194 } finally { 195 Providers.stopJarVerification(obj); 196 } 197 198 } 199 200 private void processImpl(Hashtable<String, CodeSigner[]> signers, 201 List manifestDigests) 202 throws IOException, SignatureException, NoSuchAlgorithmException, 203 JarException, CertificateException 204 { 205 Manifest sf = new Manifest(); 206 sf.read(new ByteArrayInputStream(sfBytes)); 207 208 String version = 209 sf.getMainAttributes().getValue(Attributes.Name.SIGNATURE_VERSION); 210 211 if ((version == null) || !(version.equalsIgnoreCase("1.0"))) { 212 // XXX: should this be an exception? 213 // for now we just ignore this signature file 214 return; 215 } 216 217 SignerInfo[] infos = block.verify(sfBytes); 218 219 if (infos == null) { 220 throw new SecurityException("cannot verify signature block file " + 221 name); 222 } 223 224 BASE64Decoder decoder = new BASE64Decoder(); 225 226 CodeSigner[] newSigners = getSigners(infos, block); 227 228 // make sure we have something to do all this work for... 229 if (newSigners == null) 230 return; 231 232 Iterator<Map.Entry<String,Attributes>> entries = 233 sf.getEntries().entrySet().iterator(); 234 235 // see if we can verify the whole manifest first 236 boolean manifestSigned = verifyManifestHash(sf, md, decoder, manifestDigests); 237 238 // verify manifest main attributes 239 if (!manifestSigned && !verifyManifestMainAttrs(sf, md, decoder)) { 240 throw new SecurityException 241 ("Invalid signature file digest for Manifest main attributes"); 242 } 243 244 // go through each section in the signature file 245 while(entries.hasNext()) { 246 247 Map.Entry<String,Attributes> e = entries.next(); 248 String name = e.getKey(); 249 250 if (manifestSigned || 251 (verifySection(e.getValue(), name, md, decoder))) { 252 253 if (name.startsWith("./")) 254 name = name.substring(2); 255 256 if (name.startsWith("/")) 257 name = name.substring(1); 258 259 updateSigners(newSigners, signers, name); 260 261 if (debug != null) { 262 debug.println("processSignature signed name = "+name); 263 } 264 265 } else if (debug != null) { 266 debug.println("processSignature unsigned name = "+name); 267 } 268 } 269 270 // MANIFEST.MF is always regarded as signed 271 updateSigners(newSigners, signers, JarFile.MANIFEST_NAME); 272 } 273 274 /** 275 * See if the whole manifest was signed. 276 */ 277 private boolean verifyManifestHash(Manifest sf, 278 ManifestDigester md, 279 BASE64Decoder decoder, 280 List manifestDigests) 281 throws IOException 282 { 283 Attributes mattr = sf.getMainAttributes(); 284 boolean manifestSigned = false; 285 286 // go through all the attributes and process *-Digest-Manifest entries 287 for (Map.Entry<Object,Object> se : mattr.entrySet()) { 288 289 String key = se.getKey().toString(); 290 291 if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST-MANIFEST")) { 292 // 16 is length of "-Digest-Manifest" 293 String algorithm = key.substring(0, key.length()-16); 294 295 manifestDigests.add(key); 296 manifestDigests.add(se.getValue()); 297 MessageDigest digest = getDigest(algorithm); 298 if (digest != null) { 299 byte[] computedHash = md.manifestDigest(digest); 300 byte[] expectedHash = 301 decoder.decodeBuffer((String)se.getValue()); 302 303 if (debug != null) { 304 debug.println("Signature File: Manifest digest " + 305 digest.getAlgorithm()); 306 debug.println( " sigfile " + toHex(expectedHash)); 307 debug.println( " computed " + toHex(computedHash)); 308 debug.println(); 309 } 310 311 if (MessageDigest.isEqual(computedHash, 312 expectedHash)) { 313 manifestSigned = true; 314 } else { 315 //XXX: we will continue and verify each section 316 } 317 } 318 } 319 } 320 return manifestSigned; 321 } 322 323 private boolean verifyManifestMainAttrs(Manifest sf, 324 ManifestDigester md, 325 BASE64Decoder decoder) 326 throws IOException 327 { 328 Attributes mattr = sf.getMainAttributes(); 329 boolean attrsVerified = true; 330 331 // go through all the attributes and process 332 // digest entries for the manifest main attributes 333 for (Map.Entry<Object,Object> se : mattr.entrySet()) { 334 String key = se.getKey().toString(); 335 336 if (key.toUpperCase(Locale.ENGLISH).endsWith(ATTR_DIGEST)) { 337 String algorithm = 338 key.substring(0, key.length() - ATTR_DIGEST.length()); 339 340 MessageDigest digest = getDigest(algorithm); 341 if (digest != null) { 342 ManifestDigester.Entry mde = 343 md.get(ManifestDigester.MF_MAIN_ATTRS, false); 344 byte[] computedHash = mde.digest(digest); 345 byte[] expectedHash = 346 decoder.decodeBuffer((String)se.getValue()); 347 348 if (debug != null) { 349 debug.println("Signature File: " + 350 "Manifest Main Attributes digest " + 351 digest.getAlgorithm()); 352 debug.println( " sigfile " + toHex(expectedHash)); 353 debug.println( " computed " + toHex(computedHash)); 354 debug.println(); 355 } 356 357 if (MessageDigest.isEqual(computedHash, 358 expectedHash)) { 359 // good 360 } else { 361 // we will *not* continue and verify each section 362 attrsVerified = false; 363 if (debug != null) { 364 debug.println("Verification of " + 365 "Manifest main attributes failed"); 366 debug.println(); 367 } 368 break; 369 } 370 } 371 } 372 } 373 374 // this method returns 'true' if either: 375 // . manifest main attributes were not signed, or 376 // . manifest main attributes were signed and verified 377 return attrsVerified; 378 } 379 380 /** 381 * given the .SF digest header, and the data from the 382 * section in the manifest, see if the hashes match. 383 * if not, throw a SecurityException. 384 * 385 * @return true if all the -Digest headers verified 386 * @exception SecurityException if the hash was not equal 387 */ 388 389 private boolean verifySection(Attributes sfAttr, 390 String name, 391 ManifestDigester md, 392 BASE64Decoder decoder) 393 throws IOException 394 { 395 boolean oneDigestVerified = false; 396 ManifestDigester.Entry mde = md.get(name,block.isOldStyle()); 397 398 if (mde == null) { 399 throw new SecurityException( 400 "no manifiest section for signature file entry "+name); 401 } 402 403 if (sfAttr != null) { 404 405 //sun.misc.HexDumpEncoder hex = new sun.misc.HexDumpEncoder(); 406 //hex.encodeBuffer(data, System.out); 407 408 // go through all the attributes and process *-Digest entries 409 for (Map.Entry<Object,Object> se : sfAttr.entrySet()) { 410 String key = se.getKey().toString(); 411 412 if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) { 413 // 7 is length of "-Digest" 414 String algorithm = key.substring(0, key.length()-7); 415 416 MessageDigest digest = getDigest(algorithm); 417 418 if (digest != null) { 419 boolean ok = false; 420 421 byte[] expected = 422 decoder.decodeBuffer((String)se.getValue()); 423 byte[] computed; 424 if (workaround) { 425 computed = mde.digestWorkaround(digest); 426 } else { 427 computed = mde.digest(digest); 428 } 429 430 if (debug != null) { 431 debug.println("Signature Block File: " + 432 name + " digest=" + digest.getAlgorithm()); 433 debug.println(" expected " + toHex(expected)); 434 debug.println(" computed " + toHex(computed)); 435 debug.println(); 436 } 437 438 if (MessageDigest.isEqual(computed, expected)) { 439 oneDigestVerified = true; 440 ok = true; 441 } else { 442 // attempt to fallback to the workaround 443 if (!workaround) { 444 computed = mde.digestWorkaround(digest); 445 if (MessageDigest.isEqual(computed, expected)) { 446 if (debug != null) { 447 debug.println(" re-computed " + toHex(computed)); 448 debug.println(); 449 } 450 workaround = true; 451 oneDigestVerified = true; 452 ok = true; 453 } 454 } 455 } 456 if (!ok){ 457 throw new SecurityException("invalid " + 458 digest.getAlgorithm() + 459 " signature file digest for " + name); 460 } 461 } 462 } 463 } 464 } 465 return oneDigestVerified; 466 } 467 468 /** 469 * Given the PKCS7 block and SignerInfo[], create an array of 470 * CodeSigner objects. We do this only *once* for a given 471 * signature block file. 472 */ 473 private CodeSigner[] getSigners(SignerInfo infos[], PKCS7 block) 474 throws IOException, NoSuchAlgorithmException, SignatureException, 475 CertificateException { 476 477 ArrayList<CodeSigner> signers = null; 478 479 for (int i = 0; i < infos.length; i++) { 480 481 SignerInfo info = infos[i]; 482 ArrayList<X509Certificate> chain = info.getCertificateChain(block); 483 CertPath certChain = certificateFactory.generateCertPath(chain); 484 if (signers == null) { 485 signers = new ArrayList<CodeSigner>(); 486 } 487 // Append the new code signer 488 signers.add(new CodeSigner(certChain, getTimestamp(info))); 489 490 if (debug != null) { 491 debug.println("Signature Block Certificate: " + 492 chain.get(0)); 493 } 494 } 495 496 if (signers != null) { 497 return signers.toArray(new CodeSigner[signers.size()]); 498 } else { 499 return null; 500 } 501 } 502 503 /* 504 * Examines a signature timestamp token to generate a timestamp object. 505 * 506 * Examines the signer's unsigned attributes for a 507 * <tt>signatureTimestampToken</tt> attribute. If present, 508 * then it is parsed to extract the date and time at which the 509 * timestamp was generated. 510 * 511 * @param info A signer information element of a PKCS 7 block. 512 * 513 * @return A timestamp token or null if none is present. 514 * @throws IOException if an error is encountered while parsing the 515 * PKCS7 data. 516 * @throws NoSuchAlgorithmException if an error is encountered while 517 * verifying the PKCS7 object. 518 * @throws SignatureException if an error is encountered while 519 * verifying the PKCS7 object. 520 * @throws CertificateException if an error is encountered while generating 521 * the TSA's certpath. 522 */ 523 private Timestamp getTimestamp(SignerInfo info) 524 throws IOException, NoSuchAlgorithmException, SignatureException, 525 CertificateException { 526 527 Timestamp timestamp = null; 528 529 // Extract the signer's unsigned attributes 530 PKCS9Attributes unsignedAttrs = info.getUnauthenticatedAttributes(); 531 if (unsignedAttrs != null) { 532 PKCS9Attribute timestampTokenAttr = 533 unsignedAttrs.getAttribute("signatureTimestampToken"); 534 if (timestampTokenAttr != null) { 535 PKCS7 timestampToken = 536 new PKCS7((byte[])timestampTokenAttr.getValue()); 537 // Extract the content (an encoded timestamp token info) 538 byte[] encodedTimestampTokenInfo = 539 timestampToken.getContentInfo().getData(); 540 // Extract the signer (the Timestamping Authority) 541 // while verifying the content 542 SignerInfo[] tsa = 543 timestampToken.verify(encodedTimestampTokenInfo); 544 // Expect only one signer 545 ArrayList<X509Certificate> chain = 546 tsa[0].getCertificateChain(timestampToken); 547 CertPath tsaChain = certificateFactory.generateCertPath(chain); 548 // Create a timestamp token info object 549 TimestampToken timestampTokenInfo = 550 new TimestampToken(encodedTimestampTokenInfo); 551 // Check that the signature timestamp applies to this signature 552 verifyTimestamp(timestampTokenInfo, info.getEncryptedDigest()); 553 // Create a timestamp object 554 timestamp = 555 new Timestamp(timestampTokenInfo.getDate(), tsaChain); 556 } 557 } 558 return timestamp; 559 } 560 561 /* 562 * Check that the signature timestamp applies to this signature. 563 * Match the hash present in the signature timestamp token against the hash 564 * of this signature. 565 */ 566 private void verifyTimestamp(TimestampToken token, byte[] signature) 567 throws NoSuchAlgorithmException, SignatureException { 568 569 MessageDigest md = 570 MessageDigest.getInstance(token.getHashAlgorithm().getName()); 571 572 if (!Arrays.equals(token.getHashedMessage(), md.digest(signature))) { 573 throw new SignatureException("Signature timestamp (#" + 574 token.getSerialNumber() + ") generated on " + token.getDate() + 575 " is inapplicable"); 576 } 577 578 if (debug != null) { 579 debug.println(); 580 debug.println("Detected signature timestamp (#" + 581 token.getSerialNumber() + ") generated on " + token.getDate()); 582 debug.println(); 583 } 584 } 585 586 // for the toHex function 587 private static final char[] hexc = 588 {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; 589 /** 590 * convert a byte array to a hex string for debugging purposes 591 * @param data the binary data to be converted to a hex string 592 * @return an ASCII hex string 593 */ 594 595 static String toHex(byte[] data) { 596 597 StringBuffer sb = new StringBuffer(data.length*2); 598 599 for (int i=0; i<data.length; i++) { 600 sb.append(hexc[(data[i] >>4) & 0x0f]); 601 sb.append(hexc[data[i] & 0x0f]); 602 } 603 return sb.toString(); 604 } 605 606 // returns true if set contains signer 607 static boolean contains(CodeSigner[] set, CodeSigner signer) 608 { 609 for (int i = 0; i < set.length; i++) { 610 if (set[i].equals(signer)) 611 return true; 612 } 613 return false; 614 } 615 616 // returns true if subset is a subset of set 617 static boolean isSubSet(CodeSigner[] subset, CodeSigner[] set) 618 { 619 // check for the same object 620 if (set == subset) 621 return true; 622 623 boolean match; 624 for (int i = 0; i < subset.length; i++) { 625 if (!contains(set, subset[i])) 626 return false; 627 } 628 return true; 629 } 630 631 /** 632 * returns true if signer contains exactly the same code signers as 633 * oldSigner and newSigner, false otherwise. oldSigner 634 * is allowed to be null. 635 */ 636 static boolean matches(CodeSigner[] signers, CodeSigner[] oldSigners, 637 CodeSigner[] newSigners) { 638 639 // special case 640 if ((oldSigners == null) && (signers == newSigners)) 641 return true; 642 643 boolean match; 644 645 // make sure all oldSigners are in signers 646 if ((oldSigners != null) && !isSubSet(oldSigners, signers)) 647 return false; 648 649 // make sure all newSigners are in signers 650 if (!isSubSet(newSigners, signers)) { 651 return false; 652 } 653 654 // now make sure all the code signers in signers are 655 // also in oldSigners or newSigners 656 657 for (int i = 0; i < signers.length; i++) { 658 boolean found = 659 ((oldSigners != null) && contains(oldSigners, signers[i])) || 660 contains(newSigners, signers[i]); 661 if (!found) 662 return false; 663 } 664 return true; 665 } 666 667 void updateSigners(CodeSigner[] newSigners, 668 Hashtable<String, CodeSigner[]> signers, String name) { 669 670 CodeSigner[] oldSigners = signers.get(name); 671 672 // search through the cache for a match, go in reverse order 673 // as we are more likely to find a match with the last one 674 // added to the cache 675 676 CodeSigner[] cachedSigners; 677 for (int i = signerCache.size() - 1; i != -1; i--) { 678 cachedSigners = signerCache.get(i); 679 if (matches(cachedSigners, oldSigners, newSigners)) { 680 signers.put(name, cachedSigners); 681 return; 682 } 683 } 684 685 if (oldSigners == null) { 686 cachedSigners = newSigners; 687 } else { 688 cachedSigners = 689 new CodeSigner[oldSigners.length + newSigners.length]; 690 System.arraycopy(oldSigners, 0, cachedSigners, 0, 691 oldSigners.length); 692 System.arraycopy(newSigners, 0, cachedSigners, oldSigners.length, 693 newSigners.length); 694 } 695 signerCache.add(cachedSigners); 696 signers.put(name, cachedSigners); 697 } 698 } 699