From 92d0731d4a17cfe2ba262c49d905bc0d397480e9 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Thu, 3 Jan 2019 21:59:39 +0100 Subject: [PATCH] Optional sbt-gpg integration (#75) --- README.md | 10 +++-- build.sbt | 6 +-- src/main/scala/GpgHelpers.scala | 18 ++++++++ src/main/scala/ReproducibleBuildsPlugin.scala | 44 ++++--------------- .../native-packager/test | 1 - .../sbt-reproducible-builds/osgi/test | 1 - .../simple/project/plugins.sbt | 5 ++- .../sbt-reproducible-builds/simple/test | 1 - 8 files changed, 40 insertions(+), 46 deletions(-) create mode 100644 src/main/scala/GpgHelpers.scala diff --git a/README.md b/README.md index f7450d2..e7494ce 100644 --- a/README.md +++ b/README.md @@ -38,15 +38,19 @@ As this plugin as well as the sbt-osgi plugin redefine the `packageBin` task, it ### Describe your build as a 'buildinfo' certification -You can now generate a signed description of the build environment with the -sbt task `signedReproducibleBuildsCertification`. This certification will +You can now generate a description of the build environment with the +sbt task `reproducibleBuildsCertification`. This certification will also be included when publishing your project. If you want to disable this, you can set `publishCertification := false`. +To sign the certification, configure [sbt-gpg](https://github.com/jodersky/sbt-gpg) +and either simply `publishLocal`, or, for example if you have `publishCertification := false`, +`reproducible-builds:publishLocal`. + ### Sharing certifications If you are a (3rd-party or 'official') rebuilder, you can use the -'reproducible-builds:publish' task to publish the buildinfo to a +`reproducible-builds:publish` task to publish the buildinfo to a [reproducible-builds-certification-repository](http://github.com/raboof/reproducible-builds-certification-repository) instance. #### Uploading certifications from Travis diff --git a/build.sbt b/build.sbt index cdb3f21..b50eebb 100644 --- a/build.sbt +++ b/build.sbt @@ -17,13 +17,14 @@ enablePlugins(SbtPlugin) enablePlugins(ScriptedPlugin) libraryDependencies += "net.bzzt" % "reproducible-builds-jvm-stripper" % "0.9" -libraryDependencies += "com.jsuereth" % "sbt-pgp" % sbtPgpVersion libraryDependencies += "io.spray" %% "spray-json" % "1.3.5" libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" // Optional integration: addSbtPlugin("com.typesafe.sbt" %% "sbt-native-packager" % "1.3.15" % Provided) +addSbtPlugin("io.crashbox" %% "sbt-gpg" % "0.2.0" % Provided) +// addSbtPlugin("com.jsuereth" % "sbt-pgp" % sbtPgpVersion % Provided) // Dogfood^WChampagne time! import net.bzzt.reproduciblebuilds.ReproducibleBuildsPlugin._ @@ -35,6 +36,3 @@ scriptedLaunchOpts := { scriptedLaunchOpts.value ++ Seq("-Xmx1024M", "-Dplugin.version=" + version.value) } scriptedBufferLog := false - -// Transitive plugin dependency: -addSbtPlugin("com.jsuereth" % "sbt-pgp" % sbtPgpVersion) diff --git a/src/main/scala/GpgHelpers.scala b/src/main/scala/GpgHelpers.scala new file mode 100644 index 0000000..989c81d --- /dev/null +++ b/src/main/scala/GpgHelpers.scala @@ -0,0 +1,18 @@ +package net.bzzt.reproduciblebuilds + +import io.crashbox.gpg.SbtGpg.autoImport.{gpg, gpgWarnOnFailure} +import io.crashbox.gpg.SbtGpg.packagedArtifactsImpl +import sbt.Keys.{packagedArtifacts, streams} +import sbt.Setting + +object GpgHelpers { + val settings: Seq[Setting[_]] = + Seq( + packagedArtifacts := { + packagedArtifactsImpl( + packagedArtifacts.value, + gpg.value, + gpgWarnOnFailure.value)(streams.value.log.warn(_)) + } + ) +} diff --git a/src/main/scala/ReproducibleBuildsPlugin.scala b/src/main/scala/ReproducibleBuildsPlugin.scala index d98cecb..c158a45 100644 --- a/src/main/scala/ReproducibleBuildsPlugin.scala +++ b/src/main/scala/ReproducibleBuildsPlugin.scala @@ -5,16 +5,11 @@ import java.nio.charset.Charset import java.nio.file.Files import scala.concurrent.duration._ - import gigahorse.GigahorseSupport import sbt.{io => _, _} import sbt.Keys._ import sbt.Classpaths._ import sbt.plugins.JvmPlugin -import com.typesafe.sbt.pgp -import com.typesafe.sbt.pgp.PgpSigner -import com.typesafe.sbt.pgp.PgpKeys._ -import com.typesafe.sbt.pgp.PgpSettings.{pgpPassphrase => _, pgpSecretRing => _, pgpSigningKey => _, useGpgAgent => _, _} import io.github.zlika.reproducible._ import org.apache.ivy.core.IvyPatternHelper import sbt.io.syntax.{URI, uri} @@ -32,6 +27,9 @@ object ReproducibleBuildsPlugin extends AutoPlugin { val universalPluginOnClasspath = Try(getClass.getClassLoader.loadClass("com.typesafe.sbt.packager.universal.UniversalPlugin")).isSuccess + val gpgPluginOnClasspath = + Try(getClass.getClassLoader.loadClass("io.crashbox.gpg.SbtGpg")).isSuccess + override def requires: Plugins = JvmPlugin val ReproducibleBuilds = config("reproducible-builds") @@ -41,7 +39,6 @@ object ReproducibleBuildsPlugin extends AutoPlugin { val hostname = settingKey[String]("The hostname to include when publishing 3rd-party attestations") val reproducibleBuildsCertification = taskKey[File]("Create a Reproducible Builds certification") - val signedReproducibleBuildsCertification = taskKey[File]("Create a signed Reproducible Builds certification") val reproducibleBuildsCheckCertification = taskKey[Unit]("Download and compare Reproducible Builds certifications") val bzztNetResolver = Resolver.url("repo.bzzt.net", url("http://repo.bzzt.net:8000"))(Patterns().withArtifactPatterns(Vector( @@ -99,11 +96,6 @@ object ReproducibleBuildsPlugin extends AutoPlugin { if (publishCertification.value) generatedArtifact else Map.empty[Artifact, File] }, - signedReproducibleBuildsCertification := { - val file = reproducibleBuildsCertification.value - val signer = new CleartextCommandLineGpgSigner(gpgCommand.value, useGpgAgent.value, pgpSigningKey.value, pgpPassphrase.value) - signer.sign(file, new File(file.getAbsolutePath + pgp.gpgExtension), streams.value) - }, reproducibleBuildsCheckCertification := { val ours = Certification( organization.value, @@ -167,7 +159,8 @@ object ReproducibleBuildsPlugin extends AutoPlugin { generatedArtifact artifacts.map { case (key, value) => (key.withExtraAttributes(key.extraAttributes ++ Map("host"->hostname.value, "timestamp" -> (System.currentTimeMillis() / 1000l).toString)), value) } }, - publishTo := Some(bzztNetResolver), + publishTo := Some(bzztNetResolver) + ) ++ gpgPluginSettings ++ Seq( publishConfiguration := { publishConfig( publishMavenStyle.value, @@ -209,6 +202,10 @@ object ReproducibleBuildsPlugin extends AutoPlugin { publishM2 := publishTask(publishM2Configuration).value )) + private def gpgPluginSettings = + if (gpgPluginOnClasspath) GpgHelpers.settings + else Seq.empty + def postProcessJar(jar: File): File = postProcessWith(jar, new ZipStripper() .addFileStripper("META-INF/MANIFEST.MF", new ManifestStripper()) .addFileStripper("META-INF/maven/\\S*/pom.properties", new PomPropertiesStripper())) @@ -232,29 +229,6 @@ object ReproducibleBuildsPlugin extends AutoPlugin { } } - /** - * A GpgSigner that uses the command-line to run gpg. - * - * Taken from sbt-pgp, but: - * * changed '--detach-sign' to '--clearsign' - * (needs to be '--clearsign' rather than '--clear-sign' to support gnupg 1.4.x) - * * removed 'secRing' (see https://github.com/sbt/sbt-pgp/issues/126) - */ - private class CleartextCommandLineGpgSigner(command: String, agent: Boolean, optKey: Option[Long], optPassphrase: Option[Array[Char]]) extends PgpSigner { - def sign(file: File, signatureFile: File, s: TaskStreams): File = { - if (signatureFile.exists) IO.delete(signatureFile) - val passargs: Seq[String] = (optPassphrase map { passArray => passArray mkString "" } map { pass => Seq("--passphrase", pass) }) getOrElse Seq.empty - val keyargs: Seq[String] = optKey map (k => Seq("--default-key", "0x%x" format (k))) getOrElse Seq.empty - val args = passargs ++ Seq("--clearsign", "--armor") ++ (if (agent) Seq("--use-agent") else Seq.empty) ++ keyargs - sys.process.Process(command, args ++ Seq("--output", signatureFile.getAbsolutePath, file.getAbsolutePath)) !< match { - case 0 => () - case n => sys.error("Failure running gpg --clearsign. Exit code: " + n) - } - signatureFile - } - override val toString: String = "RB GPG-Command(" + command + ")" - } - /** * Determine the target filename. * diff --git a/src/sbt-test/sbt-reproducible-builds/native-packager/test b/src/sbt-test/sbt-reproducible-builds/native-packager/test index 1fecc7e..0cd770a 100644 --- a/src/sbt-test/sbt-reproducible-builds/native-packager/test +++ b/src/sbt-test/sbt-reproducible-builds/native-packager/test @@ -4,5 +4,4 @@ $ must-mirror target/scala-2.12/stripped/native-packager_2.12-0.1.0-SNAPSHOT.jar > reproducibleBuildsCertification $ exists target/scala-2.12/native-packager_2.12-0.1.0-SNAPSHOT.buildinfo # Not on travis: -#> signedReproducibleBuildsCertification #> reproducibleBuildsCheckCertification diff --git a/src/sbt-test/sbt-reproducible-builds/osgi/test b/src/sbt-test/sbt-reproducible-builds/osgi/test index cbd92fc..fef7ed9 100644 --- a/src/sbt-test/sbt-reproducible-builds/osgi/test +++ b/src/sbt-test/sbt-reproducible-builds/osgi/test @@ -4,5 +4,4 @@ $ must-mirror target/scala-2.12/stripped/osgi_2.12-0.1.0-SNAPSHOT.jar expected/o > reproducibleBuildsCertification $ exists target/scala-2.12/osgi_2.12-0.1.0-SNAPSHOT.buildinfo # Not on travis: -#> signedReproducibleBuildsCertification #> reproducibleBuildsCheckCertification diff --git a/src/sbt-test/sbt-reproducible-builds/simple/project/plugins.sbt b/src/sbt-test/sbt-reproducible-builds/simple/project/plugins.sbt index a667dce..ef9adb5 100644 --- a/src/sbt-test/sbt-reproducible-builds/simple/project/plugins.sbt +++ b/src/sbt-test/sbt-reproducible-builds/simple/project/plugins.sbt @@ -5,4 +5,7 @@ sys.props.get("plugin.version") match { } // Included but not used, to catch problems with that combination: -addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.15") \ No newline at end of file +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.15") + +// to easily test sbt-gpg integration manually +addSbtPlugin("io.crashbox" % "sbt-gpg" % "0.2.0") diff --git a/src/sbt-test/sbt-reproducible-builds/simple/test b/src/sbt-test/sbt-reproducible-builds/simple/test index a003074..205645b 100644 --- a/src/sbt-test/sbt-reproducible-builds/simple/test +++ b/src/sbt-test/sbt-reproducible-builds/simple/test @@ -4,5 +4,4 @@ $ must-mirror target/scala-2.12/stripped/simple_2.12-0.1.0-SNAPSHOT.jar expected > reproducibleBuildsCertification $ exists target/scala-2.12/simple_2.12-0.1.0-SNAPSHOT.buildinfo # Not on travis: -#> signedReproducibleBuildsCertification #> reproducibleBuildsCheckCertification