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