Package org.eclipse.osgi.internal.signedcontent

Source Code of org.eclipse.osgi.internal.signedcontent.SignatureBlockProcessor

/*******************************************************************************
* Copyright (c) 2007, 2008 IBM Corporation and others. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors: IBM Corporation - initial API and implementation
******************************************************************************/
package org.eclipse.osgi.internal.signedcontent;

import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.*;
import java.util.Map.Entry;
import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.signedcontent.SignerInfo;
import org.eclipse.osgi.util.NLS;

public class SignatureBlockProcessor implements SignedContentConstants {
  private final SignedBundleFile signedBundle;
  private ArrayList signerInfos = new ArrayList();
  private HashMap contentMDResults = new HashMap();
  // map of tsa singers keyed by SignerInfo -> {tsa_SignerInfo, signingTime}
  private HashMap tsaSignerInfos;
  private final int supportFlags;

  public SignatureBlockProcessor(SignedBundleFile signedContent, int supportFlags) {
    this.signedBundle = signedContent;
    this.supportFlags = supportFlags;
  }

  public SignedContentImpl process() throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
    BundleFile wrappedBundleFile = signedBundle.getWrappedBundleFile();
    BundleEntry be = wrappedBundleFile.getEntry(META_INF_MANIFEST_MF);
    if (be == null)
      return createUnsignedContent();

    // read all the signature block file names into a list
    Enumeration en = wrappedBundleFile.getEntryPaths(META_INF);
    List signers = new ArrayList(2);
    while (en.hasMoreElements()) {
      String name = (String) 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
    Iterator iSigners = signers.iterator();
    for (int i = 0; iSigners.hasNext(); i++)
      processSigner(wrappedBundleFile, manifestBytes, (String) iSigners.next());

    // done processing now create a SingedContent to return
    SignerInfo[] allSigners = (SignerInfo[]) signerInfos.toArray(new SignerInfo[signerInfos.size()]);
    for (Iterator iResults = contentMDResults.entrySet().iterator(); iResults.hasNext();) {
      Entry entry = (Entry) iResults.next();
      ArrayList[] value = (ArrayList[]) entry.getValue();
      SignerInfo[] entrySigners = (SignerInfo[]) value[0].toArray(new SignerInfo[value[0].size()]);
      byte[][] entryResults = (byte[][]) 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 = bf.getBaseFile() != null ? bf.getBaseFile().toString() : null;
    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);
    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);
        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[] {signedBundle.getBaseFile().toString()}));
          SignedBundleHook.log(se.getMessage(), FrameworkLogEntry.ERROR, se);
          throw se;
        }
      }
    }
  }

  private void populateMDResults(byte mfBuf[], SignerInfo signerInfo) throws NoSuchAlgorithmException {
    // need to make a string from the MF file data bytes
    String mfStr = new String(mfBuf);

    // 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' befor 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);

      // 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$
          //          }
          ArrayList[] mdResult = (ArrayList[]) contentMDResults.get(entryName);
          if (mdResult == null) {
            mdResult = new ArrayList[2];
            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
      // increment the offset to the ending entry...
      entryStartOffset = entryEndOffset;
    }
  }

  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) throws NoSuchAlgorithmException {
    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 {
        // unknown algorithm type, we will stop processing this entry
        // break;
        throw new NoSuchAlgorithmException(NLS.bind(SignedContentMessages.Algorithm_Not_Supported, 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)));
  }

  static 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);
    String entryStr = null;

    // 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' befor 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);
      break;
    }

    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) //$NON-NLS-1$
      return entry;
    StringBuffer buffer = new StringBuffer(entry.length());
    int cont = entry.indexOf("\n "); //$NON-NLS-1$
    int start = 0;
    while (cont >= 0) {
      buffer.append(entry.substring(start, cont - 1));
      start = cont + 2;
      cont = cont + 2 < entry.length() ? entry.indexOf("\n ", cont + 2) : -1; //$NON-NLS-1$
    }
    // get the last one continuation
    if (start < entry.length())
      buffer.append(entry.substring(start));
    return buffer.toString();
  }

  private static byte[] readIntoArray(BundleEntry be) throws IOException {
    int size = (int) be.getSize();
    InputStream is = be.getInputStream();
    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;
  }

  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;
  }
}
TOP

Related Classes of org.eclipse.osgi.internal.signedcontent.SignatureBlockProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.