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