PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/projects/eclipse_SDK-3.7.1/plugins/org.eclipse.osgi.source_3.7.1.R37x_v20110808-1106/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 496 lines | 307 code | 51 blank | 138 comment | 90 complexity | 23604b3d0611c35a062cf4731675dd48 MD5 | raw file
  1. /*******************************************************************************
  2. * Copyright (c) 2007, 2011 IBM Corporation and others. All rights reserved.
  3. * This program and the accompanying materials are made available under the
  4. * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  5. * and is available at http://www.eclipse.org/legal/epl-v10.html
  6. *
  7. * Contributors: IBM Corporation - initial API and implementation
  8. ******************************************************************************/
  9. package org.eclipse.osgi.internal.signedcontent;
  10. import java.io.IOException;
  11. import java.io.InputStream;
  12. import java.security.*;
  13. import java.security.cert.Certificate;
  14. import java.security.cert.CertificateException;
  15. import java.util.*;
  16. import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
  17. import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
  18. import org.eclipse.osgi.framework.log.FrameworkLogEntry;
  19. import org.eclipse.osgi.signedcontent.SignerInfo;
  20. import org.eclipse.osgi.util.NLS;
  21. public class SignatureBlockProcessor implements SignedContentConstants {
  22. private final SignedBundleFile signedBundle;
  23. private List<SignerInfo> signerInfos = new ArrayList<SignerInfo>();
  24. private Map<String, Object> contentMDResults = new HashMap<String, Object>();
  25. // map of tsa singers keyed by SignerInfo -> {tsa_SignerInfo, signingTime}
  26. private Map<SignerInfo, Object[]> tsaSignerInfos;
  27. private final int supportFlags;
  28. public SignatureBlockProcessor(SignedBundleFile signedContent, int supportFlags) {
  29. this.signedBundle = signedContent;
  30. this.supportFlags = supportFlags;
  31. }
  32. public SignedContentImpl process() throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
  33. BundleFile wrappedBundleFile = signedBundle.getWrappedBundleFile();
  34. BundleEntry be = wrappedBundleFile.getEntry(META_INF_MANIFEST_MF);
  35. if (be == null)
  36. return createUnsignedContent();
  37. // read all the signature block file names into a list
  38. Enumeration<String> en = wrappedBundleFile.getEntryPaths(META_INF);
  39. List<String> signers = new ArrayList<String>(2);
  40. while (en.hasMoreElements()) {
  41. String name = en.nextElement();
  42. if ((name.endsWith(DOT_DSA) || name.endsWith(DOT_RSA)) && name.indexOf('/') == name.lastIndexOf('/'))
  43. signers.add(name);
  44. }
  45. // this means the jar is not signed
  46. if (signers.size() == 0)
  47. return createUnsignedContent();
  48. byte manifestBytes[] = readIntoArray(be);
  49. // process the signers
  50. for (Iterator<String> iSigners = signers.iterator(); iSigners.hasNext();)
  51. processSigner(wrappedBundleFile, manifestBytes, iSigners.next());
  52. // done processing now create a SingedContent to return
  53. SignerInfo[] allSigners = signerInfos.toArray(new SignerInfo[signerInfos.size()]);
  54. for (Iterator<Map.Entry<String, Object>> iResults = contentMDResults.entrySet().iterator(); iResults.hasNext();) {
  55. Map.Entry<String, Object> entry = iResults.next();
  56. @SuppressWarnings("unchecked")
  57. List<Object>[] value = (List<Object>[]) entry.getValue();
  58. SignerInfo[] entrySigners = value[0].toArray(new SignerInfo[value[0].size()]);
  59. byte[][] entryResults = value[1].toArray(new byte[value[1].size()][]);
  60. entry.setValue(new Object[] {entrySigners, entryResults});
  61. }
  62. SignedContentImpl result = new SignedContentImpl(allSigners, (supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0 ? contentMDResults : null);
  63. result.setContent(signedBundle);
  64. result.setTSASignerInfos(tsaSignerInfos);
  65. return result;
  66. }
  67. private SignedContentImpl createUnsignedContent() {
  68. SignedContentImpl result = new SignedContentImpl(new SignerInfo[0], contentMDResults);
  69. result.setContent(signedBundle);
  70. return result;
  71. }
  72. private void processSigner(BundleFile bf, byte[] manifestBytes, String signer) throws IOException, SignatureException, InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
  73. BundleEntry be = bf.getEntry(signer);
  74. byte pkcs7Bytes[] = readIntoArray(be);
  75. int dotIndex = signer.lastIndexOf('.');
  76. be = bf.getEntry(signer.substring(0, dotIndex) + DOT_SF);
  77. byte sfBytes[] = readIntoArray(be);
  78. // Step 1, verify the .SF file is signed by the private key that corresponds to the public key
  79. // in the .RSA/.DSA file
  80. String baseFile = bf.getBaseFile() != null ? bf.getBaseFile().toString() : null;
  81. PKCS7Processor processor = new PKCS7Processor(pkcs7Bytes, 0, pkcs7Bytes.length, signer, baseFile);
  82. // call the Step 1 in the Jar File Verification algorithm
  83. processor.verifySFSignature(sfBytes, 0, sfBytes.length);
  84. // algorithm used
  85. String digAlg = getDigAlgFromSF(sfBytes);
  86. if (digAlg == null)
  87. throw new SignatureException(NLS.bind(SignedContentMessages.SF_File_Parsing_Error, new String[] {bf.toString()}));
  88. // get the digest results
  89. // Process the Step 2 in the Jar File Verification algorithm
  90. // Get the manifest out of the signature file and make sure
  91. // it matches MANIFEST.MF
  92. verifyManifestAndSignatureFile(manifestBytes, sfBytes);
  93. // create a SignerInfo with the processed information
  94. SignerInfoImpl signerInfo = new SignerInfoImpl(processor.getCertificates(), null, digAlg);
  95. if ((supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0)
  96. // only populate the manifests digest information for verifying content at runtime
  97. populateMDResults(manifestBytes, signerInfo);
  98. signerInfos.add(signerInfo);
  99. // check for tsa signers
  100. Certificate[] tsaCerts = processor.getTSACertificates();
  101. Date signingTime = processor.getSigningTime();
  102. if (tsaCerts != null && signingTime != null) {
  103. SignerInfoImpl tsaSignerInfo = new SignerInfoImpl(tsaCerts, null, digAlg);
  104. if (tsaSignerInfos == null)
  105. tsaSignerInfos = new HashMap<SignerInfo, Object[]>(2);
  106. tsaSignerInfos.put(signerInfo, new Object[] {tsaSignerInfo, signingTime});
  107. }
  108. }
  109. /**
  110. * Verify the digest listed in each entry in the .SF file with corresponding section in the manifest
  111. * @throws SignatureException
  112. */
  113. private void verifyManifestAndSignatureFile(byte[] manifestBytes, byte[] sfBytes) throws SignatureException {
  114. String sf = new String(sfBytes);
  115. sf = stripContinuations(sf);
  116. // check if there -Digest-Manfiest: header in the file
  117. int off = sf.indexOf(digestManifestSearch);
  118. if (off != -1) {
  119. int start = sf.lastIndexOf('\n', off);
  120. String manifestDigest = null;
  121. if (start != -1) {
  122. // Signature-Version has to start the file, so there
  123. // should always be a newline at the start of
  124. // Digest-Manifest
  125. String digestName = sf.substring(start + 1, off);
  126. if (digestName.equalsIgnoreCase(MD5_STR))
  127. manifestDigest = calculateDigest(getMessageDigest(MD5_STR), manifestBytes);
  128. else if (digestName.equalsIgnoreCase(SHA1_STR))
  129. manifestDigest = calculateDigest(getMessageDigest(SHA1_STR), manifestBytes);
  130. off += digestManifestSearchLen;
  131. // find out the index of first '\n' after the -Digest-Manifest:
  132. int nIndex = sf.indexOf('\n', off);
  133. String digestValue = sf.substring(off, nIndex - 1);
  134. // check if the the computed digest value of manifest file equals to the digest value in the .sf file
  135. if (!digestValue.equals(manifestDigest)) {
  136. SignatureException se = new SignatureException(NLS.bind(SignedContentMessages.Security_File_Is_Tampered, new String[] {signedBundle.getBaseFile().toString()}));
  137. SignedBundleHook.log(se.getMessage(), FrameworkLogEntry.ERROR, se);
  138. throw se;
  139. }
  140. }
  141. }
  142. }
  143. private void populateMDResults(byte mfBuf[], SignerInfo signerInfo) throws NoSuchAlgorithmException {
  144. // need to make a string from the MF file data bytes
  145. String mfStr = new String(mfBuf);
  146. // start parsing each entry in the MF String
  147. int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME);
  148. int length = mfStr.length();
  149. while ((entryStartOffset != -1) && (entryStartOffset < length)) {
  150. // get the start of the next 'entry', i.e. the end of this entry
  151. int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1);
  152. if (entryEndOffset == -1) {
  153. // if there is no next entry, then the end of the string
  154. // is the end of this entry
  155. entryEndOffset = mfStr.length();
  156. }
  157. // get the string for this entry only, since the entryStartOffset
  158. // points to the '\n' befor the 'Name: ' we increase it by 1
  159. // this is guaranteed to not go past end-of-string and be less
  160. // then entryEndOffset.
  161. String entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset);
  162. entryStr = stripContinuations(entryStr);
  163. // entry points to the start of the next 'entry'
  164. String entryName = getEntryFileName(entryStr);
  165. // if we could retrieve an entry name, then we will extract
  166. // digest type list, and the digest value list
  167. if (entryName != null) {
  168. String aDigestLine = getDigestLine(entryStr, signerInfo.getMessageDigestAlgorithm());
  169. if (aDigestLine != null) {
  170. String msgDigestAlgorithm = getDigestAlgorithmFromString(aDigestLine);
  171. if (!msgDigestAlgorithm.equalsIgnoreCase(signerInfo.getMessageDigestAlgorithm()))
  172. continue; // TODO log error?
  173. byte digestResult[] = getDigestResultsList(aDigestLine);
  174. //
  175. // only insert this entry into the table if its
  176. // "well-formed",
  177. // i.e. only if we could extract its name, digest types, and
  178. // digest-results
  179. //
  180. // sanity check, if the 2 lists are non-null, then their
  181. // counts must match
  182. //
  183. // if ((msgDigestObj != null) && (digestResultsList != null)
  184. // && (1 != digestResultsList.length)) {
  185. // throw new RuntimeException(
  186. // "Errors occurs when parsing the manifest file stream!"); //$NON-NLS-1$
  187. // }
  188. @SuppressWarnings("unchecked")
  189. List<Object>[] mdResult = (List<Object>[]) contentMDResults.get(entryName);
  190. if (mdResult == null) {
  191. @SuppressWarnings("unchecked")
  192. List<Object>[] arrayLists = new ArrayList[2];
  193. mdResult = arrayLists;
  194. mdResult[0] = new ArrayList<Object>();
  195. mdResult[1] = new ArrayList<Object>();
  196. contentMDResults.put(entryName, mdResult);
  197. }
  198. mdResult[0].add(signerInfo);
  199. mdResult[1].add(digestResult);
  200. } // could get lines of digest entries in this MF file entry
  201. } // could retrieve entry name
  202. // increment the offset to the ending entry...
  203. entryStartOffset = entryEndOffset;
  204. }
  205. }
  206. private static byte[] getDigestResultsList(String digestLines) {
  207. byte resultsList[] = null;
  208. if (digestLines != null) {
  209. // for each digest-line retrieve the digest result
  210. // for (int i = 0; i < digestLines.length; i++) {
  211. String sDigestLine = digestLines;
  212. int indexDigest = sDigestLine.indexOf(MF_DIGEST_PART);
  213. indexDigest += MF_DIGEST_PART.length();
  214. // if there is no data to extract for this digest value
  215. // then we will fail...
  216. if (indexDigest >= sDigestLine.length()) {
  217. resultsList = null;
  218. // break;
  219. }
  220. // now attempt to base64 decode the result
  221. String sResult = sDigestLine.substring(indexDigest);
  222. try {
  223. resultsList = Base64.decode(sResult.getBytes());
  224. } catch (Throwable t) {
  225. // malformed digest result, no longer processing this entry
  226. resultsList = null;
  227. }
  228. }
  229. return resultsList;
  230. }
  231. private static String getDigestAlgorithmFromString(String digestLines) throws NoSuchAlgorithmException {
  232. if (digestLines != null) {
  233. // String sDigestLine = digestLines[i];
  234. int indexDigest = digestLines.indexOf(MF_DIGEST_PART);
  235. String sDigestAlgType = digestLines.substring(0, indexDigest);
  236. if (sDigestAlgType.equalsIgnoreCase(MD5_STR)) {
  237. // remember the "algorithm type"
  238. return MD5_STR;
  239. } else if (sDigestAlgType.equalsIgnoreCase(SHA1_STR)) {
  240. // remember the "algorithm type" object
  241. return SHA1_STR;
  242. } else {
  243. // unknown algorithm type, we will stop processing this entry
  244. // break;
  245. throw new NoSuchAlgorithmException(NLS.bind(SignedContentMessages.Algorithm_Not_Supported, sDigestAlgType));
  246. }
  247. }
  248. return null;
  249. }
  250. private static String getEntryFileName(String manifestEntry) {
  251. // get the beginning of the name
  252. int nameStart = manifestEntry.indexOf(MF_ENTRY_NAME);
  253. if (nameStart == -1) {
  254. return null;
  255. }
  256. // check where the name ends
  257. int nameEnd = manifestEntry.indexOf('\n', nameStart);
  258. if (nameEnd == -1) {
  259. return null;
  260. }
  261. // if there is a '\r' before the '\n', then we'll strip it
  262. if (manifestEntry.charAt(nameEnd - 1) == '\r') {
  263. nameEnd--;
  264. }
  265. // get to the beginning of the actual name...
  266. nameStart += MF_ENTRY_NAME.length();
  267. if (nameStart >= nameEnd) {
  268. return null;
  269. }
  270. return manifestEntry.substring(nameStart, nameEnd);
  271. }
  272. /**
  273. * Returns the Base64 encoded digest of the passed set of bytes.
  274. */
  275. private static String calculateDigest(MessageDigest digest, byte[] bytes) {
  276. return new String(Base64.encode(digest.digest(bytes)));
  277. }
  278. static synchronized MessageDigest getMessageDigest(String algorithm) {
  279. try {
  280. return MessageDigest.getInstance(algorithm);
  281. } catch (NoSuchAlgorithmException e) {
  282. SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
  283. }
  284. return null;
  285. }
  286. /**
  287. * Read the .SF file abd assuming that same digest algorithm will be used through out the whole
  288. * .SF file. That digest algorithm name in the last entry will be returned.
  289. *
  290. * @param SFBuf a .SF file in bytes
  291. * @return the digest algorithm name used in the .SF file
  292. */
  293. private static String getDigAlgFromSF(byte SFBuf[]) {
  294. // need to make a string from the MF file data bytes
  295. String mfStr = new String(SFBuf);
  296. String entryStr = null;
  297. // start parsing each entry in the MF String
  298. int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME);
  299. int length = mfStr.length();
  300. while ((entryStartOffset != -1) && (entryStartOffset < length)) {
  301. // get the start of the next 'entry', i.e. the end of this entry
  302. int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1);
  303. if (entryEndOffset == -1) {
  304. // if there is no next entry, then the end of the string
  305. // is the end of this entry
  306. entryEndOffset = mfStr.length();
  307. }
  308. // get the string for this entry only, since the entryStartOffset
  309. // points to the '\n' befor the 'Name: ' we increase it by 1
  310. // this is guaranteed to not go past end-of-string and be less
  311. // then entryEndOffset.
  312. entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset);
  313. entryStr = stripContinuations(entryStr);
  314. break;
  315. }
  316. if (entryStr != null) {
  317. // process the entry to retrieve the digest algorith name
  318. String digestLine = getDigestLine(entryStr, null);
  319. // throw parsing
  320. return getMessageDigestName(digestLine);
  321. }
  322. return null;
  323. }
  324. /**
  325. *
  326. * @param manifestEntry contains a single MF file entry of the format
  327. * "Name: foo"
  328. * "MD5-Digest: [base64 encoded MD5 digest data]"
  329. * "SHA1-Digest: [base64 encoded SHA1 digest dat]"
  330. *
  331. * @param desireDigestAlg a string representing the desire digest value to be returned if there are
  332. * multiple digest lines.
  333. * If this value is null, return whatever digest value is in the entry.
  334. *
  335. * @return this function returns a digest line based on the desire digest algorithm value
  336. * (since only MD5 and SHA1 are recognized here),
  337. * or a 'null' will be returned if none of the digest algorithms
  338. * were recognized.
  339. */
  340. private static String getDigestLine(String manifestEntry, String desireDigestAlg) {
  341. String result = null;
  342. // find the first digest line
  343. int indexDigest = manifestEntry.indexOf(MF_DIGEST_PART);
  344. // if we didn't find any digests at all, then we are done
  345. if (indexDigest == -1)
  346. return null;
  347. // while we continue to find digest entries
  348. // note: in the following loop we bail if any of the lines
  349. // look malformed...
  350. while (indexDigest != -1) {
  351. // see where this digest line begins (look to left)
  352. int indexStart = manifestEntry.lastIndexOf('\n', indexDigest);
  353. if (indexStart == -1)
  354. return null;
  355. // see where it ends (look to right)
  356. int indexEnd = manifestEntry.indexOf('\n', indexDigest);
  357. if (indexEnd == -1)
  358. return null;
  359. // strip off ending '\r', if any
  360. int indexEndToUse = indexEnd;
  361. if (manifestEntry.charAt(indexEndToUse - 1) == '\r')
  362. indexEndToUse--;
  363. // indexStart points to the '\n' before this digest line
  364. int indexStartToUse = indexStart + 1;
  365. if (indexStartToUse >= indexEndToUse)
  366. return null;
  367. // now this may be a valid digest line, parse it a bit more
  368. // to see if this is a preferred digest algorithm
  369. String digestLine = manifestEntry.substring(indexStartToUse, indexEndToUse);
  370. String digAlg = getMessageDigestName(digestLine);
  371. if (desireDigestAlg != null) {
  372. if (desireDigestAlg.equalsIgnoreCase(digAlg))
  373. return digestLine;
  374. }
  375. // desireDigestAlg is null, always return the digestLine
  376. result = digestLine;
  377. // iterate to next digest line in this entry
  378. indexDigest = manifestEntry.indexOf(MF_DIGEST_PART, indexEnd);
  379. }
  380. // if we couldn't find any digest lines, then we are done
  381. return result;
  382. }
  383. /**
  384. * Return the Message Digest name
  385. *
  386. * @param digLine the message digest line is in the following format. That is in the
  387. * following format:
  388. * DIGEST_NAME-digest: digest value
  389. * @return a string representing a message digest.
  390. */
  391. private static String getMessageDigestName(String digLine) {
  392. String rtvValue = null;
  393. if (digLine != null) {
  394. int indexDigest = digLine.indexOf(MF_DIGEST_PART);
  395. if (indexDigest != -1) {
  396. rtvValue = digLine.substring(0, indexDigest);
  397. }
  398. }
  399. return rtvValue;
  400. }
  401. private static String stripContinuations(String entry) {
  402. if (entry.indexOf("\n ") < 0) //$NON-NLS-1$
  403. return entry;
  404. StringBuffer buffer = new StringBuffer(entry.length());
  405. int cont = entry.indexOf("\n "); //$NON-NLS-1$
  406. int start = 0;
  407. while (cont >= 0) {
  408. buffer.append(entry.substring(start, cont - 1));
  409. start = cont + 2;
  410. cont = cont + 2 < entry.length() ? entry.indexOf("\n ", cont + 2) : -1; //$NON-NLS-1$
  411. }
  412. // get the last one continuation
  413. if (start < entry.length())
  414. buffer.append(entry.substring(start));
  415. return buffer.toString();
  416. }
  417. private static byte[] readIntoArray(BundleEntry be) throws IOException {
  418. int size = (int) be.getSize();
  419. InputStream is = be.getInputStream();
  420. try {
  421. byte b[] = new byte[size];
  422. int rc = readFully(is, b);
  423. if (rc != size) {
  424. throw new IOException("Couldn't read all of " + be.getName() + ": " + rc + " != " + size); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
  425. }
  426. return b;
  427. } finally {
  428. try {
  429. is.close();
  430. } catch (IOException e) {
  431. // do nothing;
  432. }
  433. }
  434. }
  435. private static int readFully(InputStream is, byte b[]) throws IOException {
  436. int count = b.length;
  437. int offset = 0;
  438. int rc;
  439. while ((rc = is.read(b, offset, count)) > 0) {
  440. count -= rc;
  441. offset += rc;
  442. }
  443. return offset;
  444. }
  445. }