diff options
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.java | 514 |
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; - } -} |