1 11 12 package org.eclipse.osgi.internal.verifier; 13 14 import java.io.*; 15 import java.net.URL ; 16 import java.security.*; 17 import java.security.cert.*; 18 import java.util.*; 19 import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; 20 import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile; 21 import org.eclipse.osgi.framework.log.FrameworkLogEntry; 22 import org.eclipse.osgi.internal.provisional.verifier.*; 23 import org.eclipse.osgi.util.NLS; 24 25 30 public class SignedBundleFile extends BundleFile implements CertificateVerifier, JarVerifierConstant { 31 private static DefaultTrustAuthority trustAllAuthority = new DefaultTrustAuthority(0); 32 private BundleFile bundleFile; 33 CertificateChain[] chains; 34 35 39 Hashtable digests4entries; 40 44 Hashtable results4entries; 45 String manifestSHAResult = null; 46 String manifestMD5Result = null; 47 boolean certsInitialized = false; 48 49 SignedBundleFile() { 50 } 52 53 SignedBundleFile(CertificateChain[] chains, Hashtable digests4entries, Hashtable results4entries, String manifestMD5Result, String manifestSHAResult) { 54 this.chains = chains; 55 this.digests4entries = digests4entries; 56 this.results4entries = results4entries; 57 this.manifestMD5Result = manifestMD5Result; 58 this.manifestSHAResult = manifestSHAResult; 59 certsInitialized = true; 60 } 62 63 66 private void verifyManifestAndSingatureFile(byte[] manifestBytes, byte[] sfBytes) { 67 68 String sf = new String (sfBytes); 69 sf = stripContinuations(sf); 70 71 int off = sf.indexOf(digestManifestSearch); 73 if (off != -1) { 74 int start = sf.lastIndexOf('\n', off); 75 String manfiestDigest = null; 76 if (start != -1) { 77 String digestName = sf.substring(start + 1, off); 81 if (digestName.equalsIgnoreCase(MD5_STR)) { 82 if (manifestMD5Result == null) 83 manifestMD5Result = calculateDigest(getMessageDigest(MD5_STR), manifestBytes); 84 manfiestDigest = manifestMD5Result; 85 } else if (digestName.equalsIgnoreCase(SHA1_STR)) { 86 if (manifestSHAResult == null) 87 manifestSHAResult = calculateDigest(getMessageDigest(SHA1_STR), manifestBytes); 88 manfiestDigest = manifestSHAResult; 89 } 90 off += digestManifestSearchLen; 91 92 int nIndex = sf.indexOf('\n', off); 94 String digestValue = sf.substring(off, nIndex - 1); 95 96 if (!manfiestDigest.equals(digestValue)) { 98 Exception e = new SecurityException (NLS.bind(JarVerifierMessages.Security_File_Is_Tampered, new String [] {bundleFile.getBaseFile().toString()})); 99 SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); 100 throw (SecurityException ) e; 101 } 102 } 103 } 104 105 } 106 107 114 private String getDigAlgFromSF(byte SFBuf[]) { 115 String rtvValue = null; 116 117 String mfStr = new String (SFBuf); 119 String entryStr = null; 120 121 int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME); 123 int length = mfStr.length(); 124 125 while ((entryStartOffset != -1) && (entryStartOffset < length)) { 126 127 int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1); 129 if (entryEndOffset == -1) { 130 entryEndOffset = mfStr.length(); 133 } 134 135 entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset); 140 entryStr = stripContinuations(entryStr); 141 break; 142 } 143 144 if (entryStr != null) { 145 String digestLine = getDigestLine(entryStr, null); 147 148 rtvValue = getMessageDigestName(digestLine); 150 } 151 152 return rtvValue; 153 } 154 155 165 private void populateManifest(byte mfBuf[], String digAlg) { 166 String mfStr = new String (mfBuf); 168 169 int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME); 171 int length = mfStr.length(); 172 173 while ((entryStartOffset != -1) && (entryStartOffset < length)) { 174 175 int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1); 177 if (entryEndOffset == -1) { 178 entryEndOffset = mfStr.length(); 181 } 182 183 String entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset); 188 entryStr = stripContinuations(entryStr); 189 190 String entryName = getEntryFileName(entryStr); 192 193 if (entryName != null) { 196 197 String aDigestLine = getDigestLine(entryStr, digAlg); 198 199 if (aDigestLine != null) { 200 String msgDigestAlgorithm = getDigestAlgorithmFromString(aDigestLine); 201 byte digestResultsList[] = getDigestResultsList(aDigestLine); 202 203 218 if (digests4entries == null) { 219 digests4entries = new Hashtable(10); 220 results4entries = new Hashtable(10); 221 } 222 if (!digests4entries.contains(entryName)) { 227 digests4entries.put(entryName, msgDigestAlgorithm); 228 results4entries.put(entryName, digestResultsList); 229 } 230 231 } 233 } 235 entryStartOffset = entryEndOffset; 237 } 238 } 239 240 private String stripContinuations(String entry) { 241 if (entry.indexOf("\n ") < 0) return entry; 243 StringBuffer buffer = new StringBuffer (entry.length()); 244 int cont = entry.indexOf("\n "); int start = 0; 246 while (cont >= 0) { 247 buffer.append(entry.substring(start, cont - 1)); 248 start = cont + 2; 249 cont = cont + 2 < entry.length() ? entry.indexOf("\n ", cont + 2) : -1; } 251 if (start < entry.length()) 253 buffer.append(entry.substring(start)); 254 return buffer.toString(); 255 } 256 257 private String getEntryFileName(String manifestEntry) { 258 int nameStart = manifestEntry.indexOf(MF_ENTRY_NAME); 260 if (nameStart == -1) { 261 return null; 262 } 263 int nameEnd = manifestEntry.indexOf('\n', nameStart); 265 if (nameEnd == -1) { 266 return null; 267 } 268 if (manifestEntry.charAt(nameEnd - 1) == '\r') { 270 nameEnd--; 271 } 272 nameStart += MF_ENTRY_NAME.length(); 274 if (nameStart >= nameEnd) { 275 return null; 276 } 277 return manifestEntry.substring(nameStart, nameEnd); 278 } 279 280 297 private String getDigestLine(String manifestEntry, String desireDigestAlg) { 298 String rtvValue = null; 299 300 int indexDigest = manifestEntry.indexOf(MF_DIGEST_PART); 302 if (indexDigest == -1) 304 return null; 305 306 while (indexDigest != -1) { 310 int indexStart = manifestEntry.lastIndexOf('\n', indexDigest); 312 if (indexStart == -1) 313 return null; 314 int indexEnd = manifestEntry.indexOf('\n', indexDigest); 316 if (indexEnd == -1) 317 return null; 318 int indexEndToUse = indexEnd; 320 if (manifestEntry.charAt(indexEndToUse - 1) == '\r') 321 indexEndToUse--; 322 int indexStartToUse = indexStart + 1; 324 if (indexStartToUse >= indexEndToUse) 325 return null; 326 327 String digestLine = manifestEntry.substring(indexStartToUse, indexEndToUse); 330 String digAlg = getMessageDigestName(digestLine); 331 if (desireDigestAlg != null && desireDigestAlg.equalsIgnoreCase(digAlg)) { 332 rtvValue = digestLine; 333 break; 334 } 335 336 rtvValue = digestLine; 338 339 indexDigest = manifestEntry.indexOf(MF_DIGEST_PART, indexEnd); 341 } 342 343 return rtvValue; 345 } 346 347 private String getDigestAlgorithmFromString(String digestLines) { 348 if (digestLines != null) { 349 int indexDigest = digestLines.indexOf(MF_DIGEST_PART); 351 String sDigestAlgType = digestLines.substring(0, indexDigest); 352 if (sDigestAlgType.equalsIgnoreCase(MD5_STR)) { 353 return MD5_STR; 355 } else if (sDigestAlgType.equalsIgnoreCase(SHA1_STR)) { 356 return SHA1_STR; 358 } else { 359 throw new SecurityException (NLS.bind(JarVerifierMessages.Algorithm_Not_Supported, sDigestAlgType)); 362 } 363 } 364 return null; 365 } 366 367 375 private String getMessageDigestName(String digLine) { 376 String rtvValue = null; 377 if (digLine != null) { 378 int indexDigest = digLine.indexOf(MF_DIGEST_PART); 379 if (indexDigest != -1) { 380 rtvValue = digLine.substring(0, indexDigest); 381 } 382 } 383 return rtvValue; 384 } 385 386 private byte[] getDigestResultsList(String digestLines) { 387 byte resultsList[] = null; 388 if (digestLines != null) { 389 String sDigestLine = digestLines; 392 int indexDigest = sDigestLine.indexOf(MF_DIGEST_PART); 393 indexDigest += MF_DIGEST_PART.length(); 394 if (indexDigest >= sDigestLine.length()) { 397 resultsList = null; 398 } 400 String sResult = sDigestLine.substring(indexDigest); 402 try { 403 resultsList = Base64.decode(sResult.getBytes()); 404 } catch (Throwable t) { 405 resultsList = null; 407 } 408 } 409 return resultsList; 410 } 411 412 static private int readFully(InputStream is, byte b[]) throws IOException { 413 int count = b.length; 414 int offset = 0; 415 int rc; 416 while ((rc = is.read(b, offset, count)) > 0) { 417 count -= rc; 418 offset += rc; 419 } 420 return offset; 421 } 422 423 byte[] readIntoArray(BundleEntry be) throws IOException { 424 int size = (int) be.getSize(); 425 InputStream is = be.getInputStream(); 426 byte b[] = new byte[size]; 427 int rc = readFully(is, b); 428 if (rc != size) { 429 throw new IOException("Couldn't read all of " + be.getName() + ": " + rc + " != " + size); } 431 return b; 432 } 433 434 443 void setBundleFile(BundleFile bundleFile, int supportFlags) throws IOException { 444 this.bundleFile = bundleFile; 445 if (certsInitialized) 446 return; 447 BundleEntry be = bundleFile.getEntry(META_INF_MANIFEST_MF); 448 if (be == null) 449 return; 450 451 Enumeration en = bundleFile.getEntryPaths(META_INF); 453 List signers = new ArrayList(2); 454 while (en.hasMoreElements()) { 455 String name = (String ) en.nextElement(); 456 if ((name.endsWith(DOT_DSA) || name.endsWith(DOT_RSA)) && name.indexOf('/') == name.lastIndexOf('/')) 457 signers.add(name); 458 } 459 460 if (signers.size() == 0) 462 return; 463 464 byte manifestBytes[] = readIntoArray(be); 465 String latestSigner = findLatestSigner(bundleFile, signers); 467 try { 469 ArrayList processors = new ArrayList(signers.size()); 470 Iterator iSigners = signers.iterator(); 471 for (int i = 0; iSigners.hasNext(); i++) { 472 String signer = (String ) iSigners.next(); 473 PKCS7Processor processor = processSigner(bundleFile, manifestBytes, signer, latestSigner, supportFlags); 474 boolean error = false; 475 try { 476 processor.validateCerts(); 477 determineCertsTrust(processor, supportFlags); 478 } catch (CertificateExpiredException e) { 479 } catch (CertificateNotYetValidException e) { 481 } catch (InvalidKeyException e) { 483 error = true; 484 } 485 if (!error) { 486 if (signer == latestSigner) 488 processors.add(0, processor); 489 else 490 processors.add(processor); 491 } 492 } 493 chains = processors.size() == 0 ? null : (CertificateChain[]) processors.toArray(new CertificateChain[processors.size()]); 494 } catch (SignatureException e) { 495 throw new SecurityException (NLS.bind(JarVerifierMessages.Signature_Not_Verify_1, new String [] {latestSigner, bundleFile.toString()})); 496 } 497 } 498 499 private void determineCertsTrust(PKCS7Processor signerPKCS7, int supportFlags) { 500 CertificateTrustAuthority trustAuthority; 501 if ((supportFlags & SignedBundleHook.VERIFY_TRUST) != 0) 502 trustAuthority = SignedBundleHook.getTrustAuthority(); 503 else 504 trustAuthority = trustAllAuthority; 505 if (trustAuthority != null) 506 signerPKCS7.determineTrust(trustAuthority); 507 } 508 509 private PKCS7Processor processSigner(BundleFile bf, byte[] manifestBytes, String signer, String latestSigner, int supportFlags) throws IOException, SignatureException { 510 BundleEntry be = bf.getEntry(signer); 511 byte pkcs7Bytes[] = readIntoArray(be); 512 int dotIndex = signer.lastIndexOf('.'); 513 be = bf.getEntry(signer.substring(0, dotIndex) + DOT_SF); 514 byte sfBytes[] = readIntoArray(be); 515 516 PKCS7Processor chain = null; 519 try { 520 521 chain = new PKCS7Processor(pkcs7Bytes, 0, pkcs7Bytes.length); 522 523 chain.verifySFSignature(sfBytes, 0, sfBytes.length); 525 526 String digAlg = getDigAlgFromSF(sfBytes); 528 if (digAlg == null) { 529 throw new SecurityException (NLS.bind(JarVerifierMessages.SF_File_Parsing_Error, new String [] {bf.toString()})); 530 } 531 if (latestSigner == signer) { 534 verifyManifestAndSingatureFile(manifestBytes, sfBytes); 538 539 if ((supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0) 541 populateManifest(manifestBytes, digAlg); 542 } 543 544 } catch (InvalidKeyException e) { 549 SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); 550 throw new SecurityException (NLS.bind(JarVerifierMessages.Invalid_Key_Exception, new String [] {bf.getBaseFile().toString(), e.getMessage()})); 551 } catch (CertificateException e) { 552 SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); 553 throw new SecurityException (NLS.bind(JarVerifierMessages.PKCS7_Cert_Excep, new String [] {bf.getBaseFile().toString(), e.getMessage()})); 554 } catch (NoSuchAlgorithmException e) { 555 SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); 556 throw new SecurityException (NLS.bind(JarVerifierMessages.PKCS7_No_Such_Algorithm, new String [] {bf.getBaseFile().toString(), e.getMessage()})); 557 } 558 559 return chain; 560 } 561 562 private String findLatestSigner(BundleFile bf, List names) { 563 String result = null; 564 long latestTime = Long.MIN_VALUE; 565 for (Iterator iNames = names.iterator(); iNames.hasNext();) { 566 String name = (String ) iNames.next(); 567 BundleEntry entry = bf.getEntry(name); 568 if (entry.getTime() > latestTime) { 569 result = name; 570 latestTime = entry.getTime(); 571 } 572 } 573 return result; 574 } 575 576 579 private String calculateDigest(MessageDigest digest, byte[] bytes) { 580 return new String (Base64.encode(digest.digest(bytes))); 581 } 582 583 public File getFile(String path, boolean nativeCode) { 584 return bundleFile.getFile(path, nativeCode); 585 } 586 587 public BundleEntry getEntry(String path) { 588 if (path.length() > 0 && path.charAt(0) == '/') 590 path = path.substring(1); 591 BundleEntry be = bundleFile.getEntry(path); 592 if (digests4entries == null) 593 return be; 594 if (be == null) { 595 if (digests4entries.get(path) == null) 596 return null; 597 throw new SecurityException (NLS.bind(JarVerifierMessages.file_is_removed_from_jar, getBaseFile().toString(), path)); 598 } 599 if (be.getName().startsWith(META_INF)) 600 return be; 601 if (!isSigned()) 602 return be; 604 return new SignedBundleEntry(be); 605 } 606 607 public Enumeration getEntryPaths(String path) { 608 return bundleFile.getEntryPaths(path); 609 } 610 611 public void close() throws IOException { 612 bundleFile.close(); 613 } 614 615 public void open() throws IOException { 616 bundleFile.open(); 617 } 618 619 public boolean containsDir(String dir) { 620 return bundleFile.containsDir(dir); 621 } 622 623 boolean matchDNChain(String pattern) { 624 CertificateChain[] matchChains = getChains(); 625 for (int i = 0; i < matchChains.length; i++) 626 if (matchChains[i].isTrusted() && DNChainMatching.match(matchChains[i].getChain(), pattern)) 627 return true; 628 return false; 629 } 630 631 public File getBaseFile() { 632 return bundleFile.getBaseFile(); 633 } 634 635 class SignedBundleEntry extends BundleEntry { 636 BundleEntry nestedEntry; 637 638 SignedBundleEntry(BundleEntry nestedEntry) { 639 this.nestedEntry = nestedEntry; 640 } 641 642 public InputStream getInputStream() throws IOException { 643 String name = getName(); 644 String digest = digests4entries == null ? null : (String ) digests4entries.get(name); 645 if (digest == null) 646 throw new IOException("Corrupted file: the digest does not exist for the file " + name); byte results[] = (byte[]) results4entries.get(name); 649 return new DigestedInputStream(nestedEntry.getInputStream(), digest, results, nestedEntry.getSize()); 651 } 652 653 public long getSize() { 654 return nestedEntry.getSize(); 655 } 656 657 public String getName() { 658 return nestedEntry.getName(); 659 } 660 661 public long getTime() { 662 return nestedEntry.getTime(); 663 } 664 665 public URL getLocalURL() { 666 return nestedEntry.getLocalURL(); 667 } 668 669 public URL getFileURL() { 670 return nestedEntry.getFileURL(); 671 } 672 673 } 674 675 public void checkContent() throws CertificateException, CertificateExpiredException, SignatureException { 676 if (!isSigned() || digests4entries == null) 677 return; 678 679 for (Enumeration entries = digests4entries.keys(); entries.hasMoreElements();) { 680 String name = (String ) entries.nextElement(); 681 BundleEntry entry = getEntry(name); 682 if (entry == null) { 683 throw new SecurityException (NLS.bind(JarVerifierMessages.Jar_Is_Tampered, bundleFile.getBaseFile().getName())); 684 } 685 try { 686 entry.getBytes(); 687 } catch (IOException e) { 688 SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); 689 throw new SecurityException (NLS.bind(JarVerifierMessages.File_In_Jar_Is_Tampered, new String [] {name, bundleFile.getBaseFile().toString()})); 690 } 691 } 692 693 for (int i = 0; i < chains.length; i++) { 695 PKCS7Processor signerPKCS7 = (PKCS7Processor) chains[i]; 696 try { 697 signerPKCS7.validateCerts(); 698 } catch (InvalidKeyException e) { 699 throw new CertificateException(e.getMessage()); 700 } 701 determineCertsTrust(signerPKCS7, SignedBundleHook.VERIFY_ALL); 703 } 704 } 705 706 public String [] verifyContent() { 707 if (!isSigned() || digests4entries == null) 708 return EMPTY_STRING; 709 ArrayList corrupted = new ArrayList(0); for (Enumeration entries = digests4entries.keys(); entries.hasMoreElements();) { 711 String name = (String ) entries.nextElement(); 712 BundleEntry entry = getEntry(name); 713 if (entry == null) 714 corrupted.add(name); else 716 try { 717 entry.getBytes(); 718 } catch (IOException e) { 719 corrupted.add(name); 721 } 722 } 723 return corrupted.size() == 0 ? EMPTY_STRING : (String []) corrupted.toArray(new String [corrupted.size()]); 724 } 725 726 public CertificateChain[] getChains() { 727 if (!isSigned()) 728 return new CertificateChain[0]; 729 return chains; 730 } 731 732 public boolean isSigned() { 733 return chains != null; 734 } 735 736 static synchronized MessageDigest getMessageDigest(String algorithm) { 737 try { 738 return MessageDigest.getInstance(algorithm); 739 } catch (NoSuchAlgorithmException e) { 740 SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); 741 } 742 return null; 743 } 744 745 } 781 | Popular Tags |