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