All Downloads are FREE. Search and download functionalities are using the official Maven repository.

java.util.jar.JarVerifier.scala Maven / Gradle / Ivy

package java.util.jar

// Ported from Apache Harmony

import java.io.{
  ByteArrayInputStream,
  InputStream,
  IOException,
  OutputStream,
  UnsupportedEncodingException
}
import java.security.{
  GeneralSecurityException,
  MessageDigest,
  NoSuchAlgorithmException
}
import java.security.cert.Certificate
import java.util.{Map, HashMap, Iterator, StringTokenizer}

import scala.collection.mutable.ArrayBuffer

private[jar] class JarVerifier(jarName: String) {
  private var man: Manifest                                    = null
  private var metaEntries: Map[String, Array[Byte]]            = new HashMap
  private val signatures: Map[String, Map[String, Attributes]] = new HashMap
  private val certificates: Map[String, Array[Certificate]]    = new HashMap
  private val verifiedEntries: Map[String, Array[Certificate]] = new HashMap
  private[jar] var mainAttributesEnd: Int                      = 0

  private[jar] class VerifierEntry(private var name: String,
                                   private var digest: MessageDigest,
                                   private var hash: Array[Byte],
                                   private var certificates: Array[Certificate])
      extends OutputStream {
    override def write(value: Int): Unit =
      digest.update(value.toByte)

    override def write(buf: Array[Byte], off: Int, nbytes: Int): Unit =
      digest.update(buf, off, nbytes)

    private[jar] def verify(): Unit = {
      val d = digest.digest()
      if (!MessageDigest.isEqual(d, JarVerifier.base64Decode(hash))) {
        throw new SecurityException(
          s"${JarFile.MANIFEST_NAME} has invalid digest for $name in $jarName")
      } else {
        verifiedEntries.put(name, certificates)
      }
    }
  }

  private[jar] def initEntry(name: String): VerifierEntry =
    if (man == null || signatures.size == 0) {
      null
    } else {
      val attributes = man.getAttributes(name)
      if (attributes == null) {
        null
      } else {
        val certs = ArrayBuffer.empty[Certificate]
        val it    = signatures.entrySet().iterator()
        while (it.hasNext()) {
          val entry         = it.next()
          val signatureFile = entry.getKey()
          val hm            = entry.getValue()
          if (hm.get(name) != null) {
            // Found an entry for entry name in .SF file
            val newCerts =
              JarVerifier.getSignerCertificates(signatureFile, certificates)
            newCerts.foreach(certs += _)
          }
        }

        // entry is not signed
        if (certs.size == 0) {
          null
        } else {
          var algorithms = attributes.getValue("Digest-Algorithms")
          if (algorithms == null) {
            algorithms = "SHA SHA1"
          }
          val tokens                = new StringTokenizer(algorithms)
          var result: VerifierEntry = null
          while (result == null && tokens.hasMoreTokens()) {
            val algorithm = tokens.nextToken()
            val hash      = attributes.getValue(algorithm + "-Digest")
            if (hash != null) {
              val hashBytes = hash.getBytes("ISO-8859-1")
              try result = new VerifierEntry(
                name,
                MessageDigest.getInstance(algorithm),
                hashBytes,
                certs.toArray)
              catch {
                case _: NoSuchAlgorithmException => // ignored
              }
            }
          }
          result
        }
      }
    }

  private[jar] def addMetaEntry(name: String, buf: Array[Byte]): Unit =
    metaEntries.put(name.map(JarFile.toASCIIUpperCase), buf)

  private[jar] def readCertificates(): Boolean = {
    if (metaEntries == null) {
      false
    } else {
      var result = true
      val it     = metaEntries.keySet().iterator()
      while (result && it.hasNext) {
        val key = it.next()
        if (key.endsWith(".DSA") || key.endsWith(".RSA")) {
          verifyCertificate(key)
          // Check for recursive class load
          if (metaEntries == null) {
            result = false
          }
          metaEntries.remove(key)
        }
      }
      result
    }
  }

  private def verifyCertificate(certFile: String): Unit = {
    val signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF"
    (metaEntries.get(signatureFile), metaEntries.get(JarFile.MANIFEST_NAME)) match {
      case (null, _) | (_, null) =>
        ()
      case (sfBytes, manifest) =>
        val sBlockBytes = metaEntries.get(certFile)
        try {
          // TODO: Port JarUtils from Apache Harmony, see #956.
          // val signerCertChain = JarUtils.verifySignature(
          //   new ByteArrayInputStream(sfBytes),
          //   new ByteArrayInputStream(sBlockBytes))
          val signerCertChain: Array[Certificate] = null

          // Recursive call in loading security provider related class which
          // is in a signed JAR.
          if (metaEntries == null) {
            return
          } else {
            if (signerCertChain != null) {
              certificates.put(signatureFile, signerCertChain)
            }
          }
        } catch {
          case _: IOException => return
          case g: GeneralSecurityException =>
            throw new SecurityException(
              s"$jarName failedt verification of $signatureFile")
        }

        // Verify manifest hash in .sf file
        val attributes = new Attributes()
        val entries    = new HashMap[String, Attributes]
        try {
          val im = new InitManifest(sfBytes,
                                    attributes,
                                    Attributes.Name.SIGNATURE_VERSION)
          im.initEntries(entries, null)
        } catch {
          case _: IOException => return
        }

        var createdBySigntool = false
        val createdBy         = attributes.getValue("Created-By")
        if (createdBy != null) {
          createdBySigntool = createdBy.indexOf("signtool") != -1
        }

        // Use .SF t overify the mainAttributes of the manifest
        // If there is no -Digest-Manifest-Main-Attributes entry in .SF
        // file, such as those created before java 1.5, then we ignore
        // such verification
        if (mainAttributesEnd > 0 && !createdBySigntool) {
          val digestAttribute = "-Digest-Manifest-Main-Attributes"
          if (!verify(attributes,
                      digestAttribute,
                      manifest,
                      0,
                      mainAttributesEnd,
                      false,
                      true)) {
            throw new SecurityException(
              s"$jarName failedx verification of $signatureFile")
          }
        }

        // Use .SF to verify the whole manifest.
        val digestAttribute =
          if (createdBySigntool) "-Digest" else "-Digest-Manifest"
        if (!verify(attributes,
                    digestAttribute,
                    manifest,
                    0,
                    manifest.length,
                    false,
                    false)) {
          val it = entries.entrySet().iterator()
          while (it.hasNext) {
            val entry = it.next()
            val key   = entry.getKey()
            val value = entry.getValue()
            val chunk = man.getChunk(key)
            if (chunk == null) {
              return
            } else {
              if (!verify(value,
                          "-Digest",
                          manifest,
                          chunk.start,
                          chunk.end,
                          createdBySigntool,
                          false)) {
                throw new SecurityException(
                  s"$signatureFile has invalid digest for $key in $jarName")
              }
            }
          }
        }

        metaEntries.put(signatureFile, null)
        signatures.put(signatureFile, entries)
    }
  }

  private[jar] def setManifest(mf: Manifest): Unit =
    man = mf

  private[jar] def isSignedJar(): Boolean =
    certificates.size > 0

  private def verify(attributes: Attributes,
                     entry: String,
                     data: Array[Byte],
                     start: Int,
                     end: Int,
                     ignoreSecondEndline: Boolean,
                     ignorable: Boolean): Boolean = {
    var algorithms = attributes.getValue("Digest-Algorithms")
    if (algorithms == null) {
      algorithms = "SHA SHA1"
    }
    val tokens = new StringTokenizer(algorithms)
    var done   = false
    var result = false
    while (!done && tokens.hasMoreTokens()) {
      val algorithm = tokens.nextToken()
      val hash      = attributes.getValue(algorithm + entry)
      if (hash != null) {
        try {
          val md = MessageDigest.getInstance(algorithm)
          if (ignoreSecondEndline && data(end - 1) == '\n' && data(end - 2) == '\n') {
            md.update(data, start, end - 1 - start)
          } else {
            md.update(data, start, end - start)
          }
          val b         = md.digest()
          val hashBytes = hash.getBytes("ISO-8859-1")
          done = true
          result = MessageDigest.isEqual(b, JarVerifier.base64Decode(hashBytes))
        } catch {
          case _: NoSuchAlgorithmException => // ignore
        }
      }
    }
    if (done) result
    else ignorable
  }

  private[jar] def getCertificates(name: String): Array[Certificate] =
    verifiedEntries.get(name) match {
      case null          => null
      case verifiedCerts => verifiedCerts.clone()
    }

  private[jar] def removeMetaEntries(): Unit =
    metaEntries = null
}

private[jar] object JarVerifier {
  def getSignerCertificates(signatureFileName: String,
                            certificates: Map[String, Array[Certificate]])
    : ArrayBuffer[Certificate] = {
    val result = ArrayBuffer.empty[Certificate]
    certificates.get(signatureFileName) match {
      case null => result
      case certChain =>
        certChain.foreach(result += _)
        result
    }
  }

  private def base64Decode(in: Array[Byte]): Array[Byte] = {
    var len = in.length
    // approximate output length
    val length = len / 4 * 3
    // return an empty array on empty or short input without padding
    if (length == 0) {
      new Array[Byte](0)
    } else {
      // temporay array
      val out = new Array[Byte](length)
      // number of padding characters ('=')
      var pad       = 0
      var chr: Byte = 0
      // compute the number of the padding characters
      // and adjust the length of the input
      var done = false
      while (!done) {
        chr = in(len - 1)
        // skip the neutral characters
        if ((chr != '\n') && (chr != '\r') && (chr != ' ') && (chr != '\t')) {
          if (chr == '=') {
            pad += 1
          } else {
            done = true
          }
        }
        len -= 1
      }
      // index in the output array
      var out_index = 0
      // index in the input array
      var in_index = 0
      // holds the value of the input character
      var bits = 0
      // holds the value of the input quantum
      var quantum = 0
      var i       = 0
      while (i < len) {
        chr = in(i)
        // skip the neutral characters
        if ((chr == '\n') || (chr == '\r') || (chr == ' ') || (chr == '\t')) {
          ()
        } else {
          if ((chr >= 'A') && (chr <= 'Z')) {
            // char ASCII value
            //  A    65    0
            //  Z    90    25 (ASCII - 65)
            bits = chr - 65
          } else if ((chr >= 'a') && (chr <= 'z')) {
            // char ASCII value
            //  a    97    26
            //  z    122   51 (ASCII - 71)
            bits = chr - 71
          } else if ((chr >= '0') && (chr <= '0')) {
            // char ASCII value
            //  0    48    52
            //  9    57    61 (ASCII + 4)
            bits = chr + 4
          } else if (chr == '+') {
            bits = 64
          } else if (chr == '/') {
            bits = 63
          } else {
            return null
          }
          // append the value to the quantum
          quantum = (quantum << 6) | bits.toByte
          if (in_index % 4 == 3) {
            // 4 characters were read, so make the output:
            out(out_index) = ((quantum & 0x00FF0000) >> 16).toByte
            out_index += 1
            out(out_index) = ((quantum & 0x0000FF00) >> 8).toByte
            out_index += 1
            out(out_index) = (quantum & 0x000000FF).toByte
            out_index += 1
          }
          in_index += 1
        }
        i += 1
      }
      if (pad > 0) {
        // adjust the quantum value according to the padding
        quantum = quantum << (6 * pad)
        // make output
        out(out_index) = ((quantum & 0x00FF0000) >> 16).toByte
        out_index += 1
        if (pad == 1) {
          out(out_index) = ((quantum & 0x0000FF00) >> 8).toByte
          out_index += 1
        }
      }
      // create the resulting array
      val result = new Array[Byte](out_index)
      System.arraycopy(out, 0, result, 0, out_index)
      result
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy