Skip to content

Commit

Permalink
Add SRI check to verify the resource is correct
Browse files Browse the repository at this point in the history
  • Loading branch information
exoego committed Jul 5, 2019
1 parent 8f1aa0c commit 6ac3ef7
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 43 deletions.
32 changes: 2 additions & 30 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ val asmDep = "org.scala-lang.modules" % "scala-asm"
val jlineDep = "jline" % "jline" % versionProps("jline.version")
val testInterfaceDep = "org.scala-sbt" % "test-interface" % "1.0"
val diffUtilsDep = "com.googlecode.java-diff-utils" % "diffutils" % "1.3.0"
val jqueryDep = "org.webjars" % "jquery" % "3.4.1"

lazy val publishSettings : Seq[Setting[_]] = Seq(
credentials ++= {
Expand Down Expand Up @@ -502,35 +501,8 @@ lazy val scaladoc = configureAsSubproject(project)
name := "scala-compiler-doc",
description := "Scala Documentation Generator",
includeFilter in unmanagedResources in Compile := "*.html" | "*.css" | "*.gif" | "*.png" | "*.js" | "*.txt" | "*.svg" | "*.eot" | "*.woff" | "*.ttf",
libraryDependencies += jqueryDep,
resourceGenerators in Compile += Def.task {
val resourcesFromJars = Map(
// resourceName -> jarName
"jquery.min.js" -> jqueryDep.name
)
val dest = (resourceManaged.value / "webjars").getAbsoluteFile
IO.createDirectory(dest)

def extractResourceFromJar(classpathes: Keys.Classpath, jarName: String, resourceName: String): Option[File] = {
IO.withTemporaryDirectory { tmpDir =>
classpathes.collectFirst {
case classpathEntry if classpathEntry.get(artifact.key).exists(_.name.contains(jarName)) =>
val jarFile = classpathEntry.data
val extractedFiles = IO.unzip(jarFile, tmpDir, f => f.endsWith(resourceName))
val destFile = dest / resourceName
IO.copyFile(extractedFiles.head, destFile)
destFile
}
}
}

for {
(resourceName, jarName) <- resourcesFromJars.toSeq
extractedFile <- extractResourceFromJar((dependencyClasspath in Compile).value, jarName, resourceName)
} yield {
extractedFile
}
}.taskValue
libraryDependencies ++= ScaladocSettings.webjarResoources,
resourceGenerators in Compile += ScaladocSettings.extractResourcesFromWebjar
)
.dependsOn(compiler)

Expand Down
25 changes: 25 additions & 0 deletions project/ScaladocSettings.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package scala.build

import sbt._
import sbt.Keys.{ artifact, dependencyClasspath, moduleID, resourceManaged }

object ScaladocSettings {

val webjarResoources = Seq(
"org.webjars" % "jquery" % "3.4.1"
)

def extractResourcesFromWebjar = Def.task {
def isWebjar(s: Attributed[File]): Boolean =
s.get(artifact.key).isDefined && s.get(moduleID.key).exists(_.organization == "org.webjars")
val dest = (resourceManaged.value / "webjars").getAbsoluteFile
IO.createDirectory(dest)
val classpathes = (dependencyClasspath in Compile).value
val files: Seq[File] = classpathes.filter(isWebjar).flatMap { classpathEntry =>
val jarFile = classpathEntry.data
IO.unzip(jarFile, dest)
}
(files ** "*.min.js").get()
}

}
37 changes: 24 additions & 13 deletions src/scaladoc/scala/tools/nsc/doc/html/HtmlFactory.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import java.io.{ File => JFile }
import io.{ Streamable, Directory }
import scala.collection._
import page.diagram._
import scala.io.Source
import scala.reflect.internal.Reporter

/** A class that can generate Scaladoc sites to some fixed root folder.
Expand Down Expand Up @@ -94,8 +95,8 @@ class HtmlFactory(val universe: doc.Universe, val reporter: Reporter) {
"ownerbg2.gif"
)

def webjarResources = List(
"jquery.min.js"
final def webjarResources = List(
("jquery.min.js", "CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=")
)

/** Generates the Scaladoc site for a model into the site root.
Expand All @@ -117,21 +118,31 @@ class HtmlFactory(val universe: doc.Universe, val reporter: Reporter) {
finally out.close()
}

def copyWebjarResource(subPath: String): Unit = {
val bytes = new Streamable.Bytes {
val p = "/" + subPath
val inputStream = getClass.getResourceAsStream(p)
assert(inputStream != null, p)
}.toByteArray()
val dest = Directory(siteRoot) / "lib" / subPath
def copyWebjarResource(resourceName: String, expectedSRI: String): Unit = {
import java.security.MessageDigest
import java.util.Base64
val md = MessageDigest.getInstance("SHA-256")
val base64encoder = Base64.getEncoder

// https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
def calsSubResourceIntegrity(input: String): String = {
val messageDigest = md.digest(input.getBytes)
val base64encoded = base64encoder.encode(messageDigest)
new String(base64encoded, "UTF-8")
}
val fileContent = Source.fromResource(resourceName).mkString
if (expectedSRI != calsSubResourceIntegrity(fileContent))
throw new Exception(s"Subresource Integrity unmatched on ${resourceName}. Could be wrong webjar or hijacked")

val dest = Directory(siteRoot) / "lib" / resourceName
dest.parent.createDirectory()
val out = dest.toFile.bufferedOutput()
try out.write(bytes, 0, bytes.length)
finally out.close()
dest.toFile.writeAll(fileContent)
}

libResources foreach (s => copyResource("lib/" + s))
webjarResources foreach (s => copyWebjarResource(s))
webjarResources foreach { case (resourceName, integrity) =>
copyWebjarResource(resourceName, integrity)
}

IndexScript(universe) writeFor this

Expand Down

0 comments on commit 6ac3ef7

Please sign in to comment.