1 /* 2 * Copyright (c) 1997, 2011, 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.misc.BASE64Decoder; 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 /** get digest from cache */ 156 157 private MessageDigest getDigest(String algorithm) 158 { 159 if (createdDigests == null) 160 createdDigests = new HashMap<String, MessageDigest>(); 161 162 MessageDigest digest = createdDigests.get(algorithm); 163 164 if (digest == null) { 165 try { 166 digest = MessageDigest.getInstance(algorithm); 167 createdDigests.put(algorithm, digest); 168 } catch (NoSuchAlgorithmException nsae) { 169 // ignore 170 } 171 } 172 return digest; 173 } 174 175 /** 176 * process the signature block file. Goes through the .SF file 177 * and adds code signers for each section where the .SF section 178 * hash was verified against the Manifest section. 179 * 180 * 181 */ 182 public void process(Hashtable<String, CodeSigner[]> signers, 183 List<Object> manifestDigests) 184 throws IOException, SignatureException, NoSuchAlgorithmException, 185 JarException, CertificateException 186 { 187 // calls Signature.getInstance() and MessageDigest.getInstance() 188 // need to use local providers here, see Providers class 189 Object obj = null; 190 try { 191 obj = Providers.startJarVerification(); 192 processImpl(signers, manifestDigests); 193 } finally { 194 Providers.stopJarVerification(obj); 195 } 196 197 } 198 199 private void processImpl(Hashtable<String, CodeSigner[]> signers, 200 List<Object> manifestDigests) 201 throws IOException, SignatureException, NoSuchAlgorithmException, 202 JarException, CertificateException 203 { 204 Manifest sf = new Manifest(); 205 sf.read(new ByteArrayInputStream(sfBytes)); 206 207 String version = 208 sf.getMainAttributes().getValue(Attributes.Name.SIGNATURE_VERSION); 209 210 if ((version == null) || !(version.equalsIgnoreCase("1.0"))) { 211 // XXX: should this be an exception? 212 // for now we just ignore this signature file 213 return; 214 } 215 216 SignerInfo[] infos = block.verify(sfBytes); 217 218 if (infos == null) { 219 throw new SecurityException("cannot verify signature block file " + 220 name); 221 } 222 223 BASE64Decoder decoder = new BASE64Decoder(); 224 225 CodeSigner[] newSigners = getSigners(infos, block); 226 227 // make sure we have something to do all this work for... 228 if (newSigners == null) 229 return; 230 231 Iterator<Map.Entry<String,Attributes>> entries = 232 sf.getEntries().entrySet().iterator(); 233 234 // see if we can verify the whole manifest first 235 boolean manifestSigned = verifyManifestHash(sf, md, decoder, manifestDigests); 236 237 // verify manifest main attributes 238 if (!manifestSigned && !verifyManifestMainAttrs(sf, md, decoder)) { 239 throw new SecurityException 240 ("Invalid signature file digest for Manifest main attributes"); 241 } 242 243 // go through each section in the signature file 244 while(entries.hasNext()) { 245 246 Map.Entry<String,Attributes> e = entries.next(); 247 String name = e.getKey(); 248 249 if (manifestSigned || 250 (verifySection(e.getValue(), name, md, decoder))) { 251 252 if (name.startsWith("./")) 253 name = name.substring(2); 254 255 if (name.startsWith("/")) 256 name = name.substring(1); 257 258 updateSigners(newSigners, signers, name); 259 260 if (debug != null) { 261 debug.println("processSignature signed name = "+name); 262 } 263 264 } else if (debug != null) { 265 debug.println("processSignature unsigned name = "+name); 266 } 267 } 268 269 // MANIFEST.MF is always regarded as signed 270 updateSigners(newSigners, signers, JarFile.MANIFEST_NAME); 271 } 272 273 /** 274 * See if the whole manifest was signed. 275 */ 276 private boolean verifyManifestHash(Manifest sf, 277 ManifestDigester md, 278 BASE64Decoder decoder, 279 List<Object> manifestDigests) 280 throws IOException 281 { 282 Attributes mattr = sf.getMainAttributes(); 283 boolean manifestSigned = false; 284 285 // go through all the attributes and process *-Digest-Manifest entries 286 for (Map.Entry<Object,Object> se : mattr.entrySet()) { 287 288 String key = se.getKey().toString(); 289 290 if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST-MANIFEST")) { 291 // 16 is length of "-Digest-Manifest" 292 String algorithm = key.substring(0, key.length()-16); 293 294 manifestDigests.add(key); 295 manifestDigests.add(se.getValue()); 296 MessageDigest digest = getDigest(algorithm); 297 if (digest != null) { 298 byte[] computedHash = md.manifestDigest(digest); 299 byte[] expectedHash = 300 decoder.decodeBuffer((String)se.getValue()); 301 302 if (debug != null) { 303 debug.println("Signature File: Manifest digest " + 304 digest.getAlgorithm()); 305 debug.println( " sigfile " + toHex(expectedHash)); 306 debug.println( " computed " + toHex(computedHash)); 307 debug.println(); 308 } 309 310 if (MessageDigest.isEqual(computedHash, 311 expectedHash)) { 312 manifestSigned = true; 313 } else { 314 //XXX: we will continue and verify each section 315 } 316 } 317 } 318 } 319 return manifestSigned; 320 } 321 322 private boolean verifyManifestMainAttrs(Manifest sf, 323 ManifestDigester md, 324 BASE64Decoder decoder) 325 throws IOException 326 { 327 Attributes mattr = sf.getMainAttributes(); 328 boolean attrsVerified = true; 329 330 // go through all the attributes and process 331 // digest entries for the manifest main attributes 332 for (Map.Entry<Object,Object> se : mattr.entrySet()) { 333 String key = se.getKey().toString(); 334 335 if (key.toUpperCase(Locale.ENGLISH).endsWith(ATTR_DIGEST)) { 336 String algorithm = 337 key.substring(0, key.length() - ATTR_DIGEST.length()); 338 339 MessageDigest digest = getDigest(algorithm); 340 if (digest != null) { 341 ManifestDigester.Entry mde = 342 md.get(ManifestDigester.MF_MAIN_ATTRS, false); 343 byte[] computedHash = mde.digest(digest); 344 byte[] expectedHash = 345 decoder.decodeBuffer((String)se.getValue()); 346 347 if (debug != null) { 348 debug.println("Signature File: " + 349 "Manifest Main Attributes digest " + 350 digest.getAlgorithm()); 351 debug.println( " sigfile " + toHex(expectedHash)); 352 debug.println( " computed " + toHex(computedHash)); 353 debug.println(); 354 } 355 356 if (MessageDigest.isEqual(computedHash, 357 expectedHash)) { 358 // good 359 } else { 360 // we will *not* continue and verify each section 361 attrsVerified = false; 362 if (debug != null) { 363 debug.println("Verification of " + 364 "Manifest main attributes failed"); 365 debug.println(); 366 } 367 break; 368 } 369 } 370 } 371 } 372 373 // this method returns 'true' if either: 374 // . manifest main attributes were not signed, or 375 // . manifest main attributes were signed and verified 376 return attrsVerified; 377 } 378 379 /** 380 * given the .SF digest header, and the data from the 381 * section in the manifest, see if the hashes match. 382 * if not, throw a SecurityException. 383 * 384 * @return true if all the -Digest headers verified 385 * @exception SecurityException if the hash was not equal 386 */ 387 388 private boolean verifySection(Attributes sfAttr, 389 String name, 390 ManifestDigester md, 391 BASE64Decoder decoder) 392 throws IOException 393 { 394 boolean oneDigestVerified = false; 395 ManifestDigester.Entry mde = md.get(name,block.isOldStyle()); 396 397 if (mde == null) { 398 throw new SecurityException( 399 "no manifiest section for signature file entry "+name); 400 } 401 402 if (sfAttr != null) { 403 404 //sun.misc.HexDumpEncoder hex = new sun.misc.HexDumpEncoder(); 405 //hex.encodeBuffer(data, System.out); 406 407 // go through all the attributes and process *-Digest entries 408 for (Map.Entry<Object,Object> se : sfAttr.entrySet()) { 409 String key = se.getKey().toString(); 410 411 if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) { 412 // 7 is length of "-Digest" 413 String algorithm = key.substring(0, key.length()-7); 414 415 MessageDigest digest = getDigest(algorithm); 416 417 if (digest != null) { 418 boolean ok = false; 419 420 byte[] expected = 421 decoder.decodeBuffer((String)se.getValue()); 422 byte[] computed; 423 if (workaround) { 424 computed = mde.digestWorkaround(digest); 425 } else { 426 computed = mde.digest(digest); 427 } 428 429 if (debug != null) { 430 debug.println("Signature Block File: " + 431 name + " digest=" + digest.getAlgorithm()); 432 debug.println(" expected " + toHex(expected)); 433 debug.println(" computed " + toHex(computed)); 434 debug.println(); 435 } 436 437 if (MessageDigest.isEqual(computed, expected)) { 438 oneDigestVerified = true; 439 ok = true; 440 } else { 441 // attempt to fallback to the workaround 442 if (!workaround) { 443 computed = mde.digestWorkaround(digest); 444 if (MessageDigest.isEqual(computed, expected)) { 445 if (debug != null) { 446 debug.println(" re-computed " + toHex(computed)); 447 debug.println(); 448 } 449 workaround = true; 450 oneDigestVerified = true; 451 ok = true; 452 } 453 } 454 } 455 if (!ok){ 456 throw new SecurityException("invalid " + 457 digest.getAlgorithm() + 458 " signature file digest for " + name); 459 } 460 } 461 } 462 } 463 } 464 return oneDigestVerified; 465 } 466 467 /** 468 * Given the PKCS7 block and SignerInfo[], create an array of 469 * CodeSigner objects. We do this only *once* for a given 470 * signature block file. 471 */ 472 private CodeSigner[] getSigners(SignerInfo infos[], PKCS7 block) 473 throws IOException, NoSuchAlgorithmException, SignatureException, 474 CertificateException { 475 476 ArrayList<CodeSigner> signers = null; 477 478 for (int i = 0; i < infos.length; i++) { 479 480 SignerInfo info = infos[i]; 481 ArrayList<X509Certificate> chain = info.getCertificateChain(block); 482 CertPath certChain = certificateFactory.generateCertPath(chain); 483 if (signers == null) { 484 signers = new ArrayList<CodeSigner>(); 485 } 486 // Append the new code signer 487 signers.add(new CodeSigner(certChain, info.getTimestamp())); 488 489 if (debug != null) { 490 debug.println("Signature Block Certificate: " + 491 chain.get(0)); 492 } 493 } 494 495 if (signers != null) { 496 return signers.toArray(new CodeSigner[signers.size()]); 497 } else { 498 return null; 499 } 500 } 501 502 // for the toHex function 503 private static final char[] hexc = 504 {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; 505 /** 506 * convert a byte array to a hex string for debugging purposes 507 * @param data the binary data to be converted to a hex string 508 * @return an ASCII hex string 509 */ 510 511 static String toHex(byte[] data) { 512 513 StringBuffer sb = new StringBuffer(data.length*2); 514 515 for (int i=0; i<data.length; i++) { 516 sb.append(hexc[(data[i] >>4) & 0x0f]); 517 sb.append(hexc[data[i] & 0x0f]); 518 } 519 return sb.toString(); 520 } 521 522 // returns true if set contains signer 523 static boolean contains(CodeSigner[] set, CodeSigner signer) 524 { 525 for (int i = 0; i < set.length; i++) { 526 if (set[i].equals(signer)) 527 return true; 528 } 529 return false; 530 } 531 532 // returns true if subset is a subset of set 533 static boolean isSubSet(CodeSigner[] subset, CodeSigner[] set) 534 { 535 // check for the same object 536 if (set == subset) 537 return true; 538 539 boolean match; 540 for (int i = 0; i < subset.length; i++) { 541 if (!contains(set, subset[i])) 542 return false; 543 } 544 return true; 545 } 546 547 /** 548 * returns true if signer contains exactly the same code signers as 549 * oldSigner and newSigner, false otherwise. oldSigner 550 * is allowed to be null. 551 */ 552 static boolean matches(CodeSigner[] signers, CodeSigner[] oldSigners, 553 CodeSigner[] newSigners) { 554 555 // special case 556 if ((oldSigners == null) && (signers == newSigners)) 557 return true; 558 559 boolean match; 560 561 // make sure all oldSigners are in signers 562 if ((oldSigners != null) && !isSubSet(oldSigners, signers)) 563 return false; 564 565 // make sure all newSigners are in signers 566 if (!isSubSet(newSigners, signers)) { 567 return false; 568 } 569 570 // now make sure all the code signers in signers are 571 // also in oldSigners or newSigners 572 573 for (int i = 0; i < signers.length; i++) { 574 boolean found = 575 ((oldSigners != null) && contains(oldSigners, signers[i])) || 576 contains(newSigners, signers[i]); 577 if (!found) 578 return false; 579 } 580 return true; 581 } 582 583 void updateSigners(CodeSigner[] newSigners, 584 Hashtable<String, CodeSigner[]> signers, String name) { 585 586 CodeSigner[] oldSigners = signers.get(name); 587 588 // search through the cache for a match, go in reverse order 589 // as we are more likely to find a match with the last one 590 // added to the cache 591 592 CodeSigner[] cachedSigners; 593 for (int i = signerCache.size() - 1; i != -1; i--) { 594 cachedSigners = signerCache.get(i); 595 if (matches(cachedSigners, oldSigners, newSigners)) { 596 signers.put(name, cachedSigners); 597 return; 598 } 599 } 600 601 if (oldSigners == null) { 602 cachedSigners = newSigners; 603 } else { 604 cachedSigners = 605 new CodeSigner[oldSigners.length + newSigners.length]; 606 System.arraycopy(oldSigners, 0, cachedSigners, 0, 607 oldSigners.length); 608 System.arraycopy(newSigners, 0, cachedSigners, oldSigners.length, 609 newSigners.length); 610 } 611 signerCache.add(cachedSigners); 612 signers.put(name, cachedSigners); 613 } 614 }