Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java')
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java514
1 files changed, 0 insertions, 514 deletions
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java
deleted file mode 100644
index bef43d0db..000000000
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java
+++ /dev/null
@@ -1,514 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2007, 2019 IBM Corporation and others.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which accompanies this distribution,
- * and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors: IBM Corporation - initial API and implementation
- ******************************************************************************/
-package org.eclipse.osgi.internal.signedcontent;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SignatureException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import org.eclipse.osgi.framework.log.FrameworkLogEntry;
-import org.eclipse.osgi.signedcontent.SignerInfo;
-import org.eclipse.osgi.storage.bundlefile.BundleEntry;
-import org.eclipse.osgi.storage.bundlefile.BundleFile;
-import org.eclipse.osgi.util.NLS;
-
-public class SignatureBlockProcessor implements SignedContentConstants {
- private final SignedBundleFile signedBundle;
- private List<SignerInfo> signerInfos = new ArrayList<>();
- private Map<String, Object> contentMDResults = new HashMap<>();
- // map of tsa singers keyed by SignerInfo -> {tsa_SignerInfo, signingTime}
- private Map<SignerInfo, Object[]> tsaSignerInfos;
- private final int supportFlags;
- private final SignedBundleHook signedBundleHook;
-
- public SignatureBlockProcessor(SignedBundleFile signedContent, int supportFlags, SignedBundleHook signedBundleHook) {
- this.signedBundle = signedContent;
- this.supportFlags = supportFlags;
- this.signedBundleHook = signedBundleHook;
- }
-
- public SignedContentImpl process() throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
- BundleFile wrappedBundleFile = signedBundle.getBundleFile();
- BundleEntry be = wrappedBundleFile.getEntry(META_INF_MANIFEST_MF);
- if (be == null)
- return createUnsignedContent();
-
- // read all the signature block file names into a list
- Enumeration<String> en = wrappedBundleFile.getEntryPaths(META_INF);
- List<String> signers = new ArrayList<>(2);
- while (en.hasMoreElements()) {
- String name = en.nextElement();
- if ((name.endsWith(DOT_DSA) || name.endsWith(DOT_RSA)) && name.indexOf('/') == name.lastIndexOf('/'))
- signers.add(name);
- }
-
- // this means the jar is not signed
- if (signers.size() == 0)
- return createUnsignedContent();
-
- byte manifestBytes[] = readIntoArray(be);
- // process the signers
-
- for (Iterator<String> iSigners = signers.iterator(); iSigners.hasNext();)
- processSigner(wrappedBundleFile, manifestBytes, iSigners.next());
-
- // done processing now create a SingedContent to return
- SignerInfo[] allSigners = signerInfos.toArray(new SignerInfo[signerInfos.size()]);
- for (Map.Entry<String, Object> entry : contentMDResults.entrySet()) {
- @SuppressWarnings("unchecked")
- List<Object>[] value = (List<Object>[]) entry.getValue();
- SignerInfo[] entrySigners = value[0].toArray(new SignerInfo[value[0].size()]);
- byte[][] entryResults = value[1].toArray(new byte[value[1].size()][]);
- entry.setValue(new Object[] {entrySigners, entryResults});
- }
- SignedContentImpl result = new SignedContentImpl(allSigners, (supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0 ? contentMDResults : null);
- result.setContent(signedBundle);
- result.setTSASignerInfos(tsaSignerInfos);
- return result;
- }
-
- private SignedContentImpl createUnsignedContent() {
- SignedContentImpl result = new SignedContentImpl(new SignerInfo[0], contentMDResults);
- result.setContent(signedBundle);
- return result;
- }
-
- private void processSigner(BundleFile bf, byte[] manifestBytes, String signer) throws IOException, SignatureException, InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
- BundleEntry be = bf.getEntry(signer);
- byte pkcs7Bytes[] = readIntoArray(be);
- int dotIndex = signer.lastIndexOf('.');
- be = bf.getEntry(signer.substring(0, dotIndex) + DOT_SF);
- byte sfBytes[] = readIntoArray(be);
-
- // Step 1, verify the .SF file is signed by the private key that corresponds to the public key
- // in the .RSA/.DSA file
- String baseFile = String.valueOf(bf.getBaseFile());
- PKCS7Processor processor = new PKCS7Processor(pkcs7Bytes, 0, pkcs7Bytes.length, signer, baseFile);
- // call the Step 1 in the Jar File Verification algorithm
- processor.verifySFSignature(sfBytes, 0, sfBytes.length);
- // algorithm used
- String digAlg = getDigAlgFromSF(sfBytes);
- if (digAlg == null)
- throw new SignatureException(NLS.bind(SignedContentMessages.SF_File_Parsing_Error, new String[] {bf.toString()}));
- // get the digest results
- // Process the Step 2 in the Jar File Verification algorithm
- // Get the manifest out of the signature file and make sure
- // it matches MANIFEST.MF
- verifyManifestAndSignatureFile(manifestBytes, sfBytes);
-
- // create a SignerInfo with the processed information
- SignerInfoImpl signerInfo = new SignerInfoImpl(processor.getCertificates(), null, digAlg);
- if ((supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0)
- // only populate the manifests digest information for verifying content at runtime
- populateMDResults(manifestBytes, signerInfo);
- signerInfos.add(signerInfo);
- // check for tsa signers
- Certificate[] tsaCerts = processor.getTSACertificates();
- Date signingTime = processor.getSigningTime();
- if (tsaCerts != null && signingTime != null) {
- SignerInfoImpl tsaSignerInfo = new SignerInfoImpl(tsaCerts, null, digAlg);
- if (tsaSignerInfos == null)
- tsaSignerInfos = new HashMap<>(2);
- tsaSignerInfos.put(signerInfo, new Object[] {tsaSignerInfo, signingTime});
- }
- }
-
- /**
- * Verify the digest listed in each entry in the .SF file with corresponding section in the manifest
- * @throws SignatureException
- */
- private void verifyManifestAndSignatureFile(byte[] manifestBytes, byte[] sfBytes) throws SignatureException {
-
- String sf = new String(sfBytes, StandardCharsets.UTF_8);
- sf = stripContinuations(sf);
-
- // check if there -Digest-Manfiest: header in the file
- int off = sf.indexOf(digestManifestSearch);
- if (off != -1) {
- int start = sf.lastIndexOf('\n', off);
- String manifestDigest = null;
- if (start != -1) {
- // Signature-Version has to start the file, so there
- // should always be a newline at the start of
- // Digest-Manifest
- String digestName = sf.substring(start + 1, off);
- if (digestName.equalsIgnoreCase(MD5_STR))
- manifestDigest = calculateDigest(getMessageDigest(MD5_STR), manifestBytes);
- else if (digestName.equalsIgnoreCase(SHA1_STR))
- manifestDigest = calculateDigest(getMessageDigest(SHA1_STR), manifestBytes);
- else
- manifestDigest = calculateDigest(getMessageDigest(digestName), manifestBytes);
- off += digestManifestSearchLen;
-
- // find out the index of first '\n' after the -Digest-Manifest:
- int nIndex = sf.indexOf('\n', off);
- String digestValue = sf.substring(off, nIndex - 1);
-
- // check if the the computed digest value of manifest file equals to the digest value in the .sf file
- if (!digestValue.equals(manifestDigest)) {
- SignatureException se = new SignatureException(NLS.bind(SignedContentMessages.Security_File_Is_Tampered, new String[] {String.valueOf(signedBundle.getBaseFile())}));
- signedBundleHook.log(se.getMessage(), FrameworkLogEntry.ERROR, se);
- throw se;
- }
- }
- }
- }
-
- private void populateMDResults(byte mfBuf[], SignerInfo signerInfo) {
- // need to make a string from the MF file data bytes
- String mfStr = new String(mfBuf, StandardCharsets.UTF_8);
-
- // start parsing each entry in the MF String
- int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME);
- int length = mfStr.length();
-
- while ((entryStartOffset != -1) && (entryStartOffset < length)) {
-
- // get the start of the next 'entry', i.e. the end of this entry
- int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1);
- if (entryEndOffset == -1) {
- // if there is no next entry, then the end of the string
- // is the end of this entry
- entryEndOffset = mfStr.length();
- }
-
- // get the string for this entry only, since the entryStartOffset
- // points to the '\n' before the 'Name: ' we increase it by 1
- // this is guaranteed to not go past end-of-string and be less
- // then entryEndOffset.
- String entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset);
- entryStr = stripContinuations(entryStr);
-
- // increment the offset to the ending entry for the next iteration of the loop ...
- entryStartOffset = entryEndOffset;
-
- // entry points to the start of the next 'entry'
- String entryName = getEntryFileName(entryStr);
-
- // if we could retrieve an entry name, then we will extract
- // digest type list, and the digest value list
- if (entryName != null) {
- String aDigestLine = getDigestLine(entryStr, signerInfo.getMessageDigestAlgorithm());
-
- if (aDigestLine != null) {
- String msgDigestAlgorithm = getDigestAlgorithmFromString(aDigestLine);
- if (!msgDigestAlgorithm.equalsIgnoreCase(signerInfo.getMessageDigestAlgorithm()))
- continue; // TODO log error?
- byte digestResult[] = getDigestResultsList(aDigestLine);
-
- //
- // only insert this entry into the table if its
- // "well-formed",
- // i.e. only if we could extract its name, digest types, and
- // digest-results
- //
- // sanity check, if the 2 lists are non-null, then their
- // counts must match
- //
- // if ((msgDigestObj != null) && (digestResultsList != null)
- // && (1 != digestResultsList.length)) {
- // throw new RuntimeException(
- // "Errors occurs when parsing the manifest file stream!"); //$NON-NLS-1$
- // }
- @SuppressWarnings("unchecked")
- List<Object>[] mdResult = (List<Object>[]) contentMDResults.get(entryName);
- if (mdResult == null) {
- @SuppressWarnings("unchecked")
- List<Object>[] arrayLists = new ArrayList[2];
- mdResult = arrayLists;
- mdResult[0] = new ArrayList<>();
- mdResult[1] = new ArrayList<>();
- contentMDResults.put(entryName, mdResult);
- }
- mdResult[0].add(signerInfo);
- mdResult[1].add(digestResult);
- } // could get lines of digest entries in this MF file entry
- } // could retrieve entry name
- }
- }
-
- private static byte[] getDigestResultsList(String digestLines) {
- byte resultsList[] = null;
- if (digestLines != null) {
- // for each digest-line retrieve the digest result
- // for (int i = 0; i < digestLines.length; i++) {
- String sDigestLine = digestLines;
- int indexDigest = sDigestLine.indexOf(MF_DIGEST_PART);
- indexDigest += MF_DIGEST_PART.length();
- // if there is no data to extract for this digest value
- // then we will fail...
- if (indexDigest >= sDigestLine.length()) {
- resultsList = null;
- // break;
- }
- // now attempt to base64 decode the result
- String sResult = sDigestLine.substring(indexDigest);
- try {
- resultsList = Base64.decode(sResult.getBytes());
- } catch (Throwable t) {
- // malformed digest result, no longer processing this entry
- resultsList = null;
- }
- }
- return resultsList;
- }
-
- private static String getDigestAlgorithmFromString(String digestLines) {
- if (digestLines != null) {
- // String sDigestLine = digestLines[i];
- int indexDigest = digestLines.indexOf(MF_DIGEST_PART);
- String sDigestAlgType = digestLines.substring(0, indexDigest);
- if (sDigestAlgType.equalsIgnoreCase(MD5_STR)) {
- // remember the "algorithm type"
- return MD5_STR;
- } else if (sDigestAlgType.equalsIgnoreCase(SHA1_STR)) {
- // remember the "algorithm type" object
- return SHA1_STR;
- } else {
- return sDigestAlgType;
- }
- }
- return null;
- }
-
- private static String getEntryFileName(String manifestEntry) {
- // get the beginning of the name
- int nameStart = manifestEntry.indexOf(MF_ENTRY_NAME);
- if (nameStart == -1) {
- return null;
- }
- // check where the name ends
- int nameEnd = manifestEntry.indexOf('\n', nameStart);
- if (nameEnd == -1) {
- return null;
- }
- // if there is a '\r' before the '\n', then we'll strip it
- if (manifestEntry.charAt(nameEnd - 1) == '\r') {
- nameEnd--;
- }
- // get to the beginning of the actual name...
- nameStart += MF_ENTRY_NAME.length();
- if (nameStart >= nameEnd) {
- return null;
- }
- return manifestEntry.substring(nameStart, nameEnd);
- }
-
- /**
- * Returns the Base64 encoded digest of the passed set of bytes.
- */
- private static String calculateDigest(MessageDigest digest, byte[] bytes) {
- return new String(Base64.encode(digest.digest(bytes)), StandardCharsets.UTF_8);
- }
-
- synchronized MessageDigest getMessageDigest(String algorithm) {
- try {
- return MessageDigest.getInstance(algorithm);
- } catch (NoSuchAlgorithmException e) {
- signedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
- }
- return null;
- }
-
- /**
- * Read the .SF file abd assuming that same digest algorithm will be used through out the whole
- * .SF file. That digest algorithm name in the last entry will be returned.
- *
- * @param SFBuf a .SF file in bytes
- * @return the digest algorithm name used in the .SF file
- */
- private static String getDigAlgFromSF(byte SFBuf[]) {
- // need to make a string from the MF file data bytes
- String mfStr = new String(SFBuf, StandardCharsets.UTF_8);
- String entryStr = null;
-
- // start parsing each entry in the MF String
- int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME);
- int length = mfStr.length();
-
- if ((entryStartOffset != -1) && (entryStartOffset < length)) {
-
- // get the start of the next 'entry', i.e. the end of this entry
- int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1);
- if (entryEndOffset == -1) {
- // if there is no next entry, then the end of the string
- // is the end of this entry
- entryEndOffset = mfStr.length();
- }
-
- // get the string for this entry only, since the entryStartOffset
- // points to the '\n' before the 'Name: ' we increase it by 1
- // this is guaranteed to not go past end-of-string and be less
- // then entryEndOffset.
- entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset);
- entryStr = stripContinuations(entryStr);
- }
-
- if (entryStr != null) {
- // process the entry to retrieve the digest algorith name
- String digestLine = getDigestLine(entryStr, null);
-
- // throw parsing
- return getMessageDigestName(digestLine);
- }
- return null;
- }
-
- /**
- *
- * @param manifestEntry contains a single MF file entry of the format
- * "Name: foo"
- * "MD5-Digest: [base64 encoded MD5 digest data]"
- * "SHA1-Digest: [base64 encoded SHA1 digest dat]"
- *
- * @param desireDigestAlg a string representing the desire digest value to be returned if there are
- * multiple digest lines.
- * If this value is null, return whatever digest value is in the entry.
- *
- * @return this function returns a digest line based on the desire digest algorithm value
- * (since only MD5 and SHA1 are recognized here),
- * or a 'null' will be returned if none of the digest algorithms
- * were recognized.
- */
- private static String getDigestLine(String manifestEntry, String desireDigestAlg) {
- String result = null;
-
- // find the first digest line
- int indexDigest = manifestEntry.indexOf(MF_DIGEST_PART);
- // if we didn't find any digests at all, then we are done
- if (indexDigest == -1)
- return null;
-
- // while we continue to find digest entries
- // note: in the following loop we bail if any of the lines
- // look malformed...
- while (indexDigest != -1) {
- // see where this digest line begins (look to left)
- int indexStart = manifestEntry.lastIndexOf('\n', indexDigest);
- if (indexStart == -1)
- return null;
- // see where it ends (look to right)
- int indexEnd = manifestEntry.indexOf('\n', indexDigest);
- if (indexEnd == -1)
- return null;
- // strip off ending '\r', if any
- int indexEndToUse = indexEnd;
- if (manifestEntry.charAt(indexEndToUse - 1) == '\r')
- indexEndToUse--;
- // indexStart points to the '\n' before this digest line
- int indexStartToUse = indexStart + 1;
- if (indexStartToUse >= indexEndToUse)
- return null;
-
- // now this may be a valid digest line, parse it a bit more
- // to see if this is a preferred digest algorithm
- String digestLine = manifestEntry.substring(indexStartToUse, indexEndToUse);
- String digAlg = getMessageDigestName(digestLine);
- if (desireDigestAlg != null) {
- if (desireDigestAlg.equalsIgnoreCase(digAlg))
- return digestLine;
- }
-
- // desireDigestAlg is null, always return the digestLine
- result = digestLine;
-
- // iterate to next digest line in this entry
- indexDigest = manifestEntry.indexOf(MF_DIGEST_PART, indexEnd);
- }
-
- // if we couldn't find any digest lines, then we are done
- return result;
- }
-
- /**
- * Return the Message Digest name
- *
- * @param digLine the message digest line is in the following format. That is in the
- * following format:
- * DIGEST_NAME-digest: digest value
- * @return a string representing a message digest.
- */
- private static String getMessageDigestName(String digLine) {
- String rtvValue = null;
- if (digLine != null) {
- int indexDigest = digLine.indexOf(MF_DIGEST_PART);
- if (indexDigest != -1) {
- rtvValue = digLine.substring(0, indexDigest);
- }
- }
- return rtvValue;
- }
-
- private static String stripContinuations(String entry) {
- if (entry.indexOf("\n ") < 0 && entry.indexOf("\r ") < 0) //$NON-NLS-1$//$NON-NLS-2$
- return entry;
- StringBuilder buffer = new StringBuilder(entry);
- removeAll(buffer, "\r\n "); //$NON-NLS-1$
- removeAll(buffer, "\n "); //$NON-NLS-1$
- removeAll(buffer, "\r "); //$NON-NLS-1$
- return buffer.toString();
- }
-
- private static StringBuilder removeAll(StringBuilder buffer, String toRemove) {
- int index = buffer.indexOf(toRemove);
- int length = toRemove.length();
- while (index > 0) {
- buffer.replace(index, index + length, ""); //$NON-NLS-1$
- index = buffer.indexOf(toRemove, index);
- }
- return buffer;
- }
-
- private static byte[] readIntoArray(BundleEntry be) throws IOException {
- int size = (int) be.getSize();
- InputStream is = be.getInputStream();
- try {
- byte b[] = new byte[size];
- int rc = readFully(is, b);
- if (rc != size) {
- throw new IOException("Couldn't read all of " + be.getName() + ": " + rc + " != " + size); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- }
- return b;
- } finally {
- try {
- is.close();
- } catch (IOException e) {
- // do nothing;
- }
- }
- }
-
- private static int readFully(InputStream is, byte b[]) throws IOException {
- int count = b.length;
- int offset = 0;
- int rc;
- while ((rc = is.read(b, offset, count)) > 0) {
- count -= rc;
- offset += rc;
- }
- return offset;
- }
-}

Back to the top