From 0a57143388335a7c8f35acb8ddee1a00dd8132d6 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Tue, 28 Jun 2016 00:26:56 -0700 Subject: [PATCH 01/17] SECURITY.md: Grammar. Typo. Disclaimer emphasis. [ci skip] --- SECURITY.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 4f05753..7e15b42 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,5 +1,7 @@ ## DISCLAIMER -The current author are not security experts and this project has not been subjected to a third-party security audit. +The current author(s) are not security experts and this project has not been subjected to a third-party security audit. + +USE AT YOUR OWN RISK. Caveat emptor. ## Responsible disclosure; Security contact info @@ -68,7 +70,7 @@ Out of scope per our assumptions: * Compromise of GitHub * Compromise of Travis CI API * Compromise of the machine on which Savage resides -* Compromise of out outbound communications with GitHub +* Compromise of our outbound communications with GitHub * Allowing modification of a sensitive file due to incorrect whitelist settings Within scope: From fbec46432829623013742372637f597702d9993d Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sun, 3 Jul 2016 17:53:45 -0700 Subject: [PATCH 02/17] Fix #35 by adding Edited & Unknown cases to PullRequestAction --- .../github/pr_action/PullRequestAction.scala | 25 +++++++++++-------- .../savage/github/util/package.scala | 2 +- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/main/scala/com/getbootstrap/savage/github/pr_action/PullRequestAction.scala b/src/main/scala/com/getbootstrap/savage/github/pr_action/PullRequestAction.scala index dc00ca0..9e2968b 100644 --- a/src/main/scala/com/getbootstrap/savage/github/pr_action/PullRequestAction.scala +++ b/src/main/scala/com/getbootstrap/savage/github/pr_action/PullRequestAction.scala @@ -1,17 +1,18 @@ package com.getbootstrap.savage.github.pr_action object PullRequestAction { - def apply(name: String): Option[PullRequestAction] = { + def apply(name: String): PullRequestAction = { name match { - case Assigned.Name => Some(Assigned) - case Unassigned.Name => Some(Unassigned) - case Labeled.Name => Some(Labeled) - case Unlabeled.Name => Some(Unlabeled) - case Opened.Name => Some(Opened) - case Closed.Name => Some(Closed) - case Reopened.Name => Some(Reopened) - case Synchronize.Name => Some(Synchronize) - case _ => None + case Assigned.Name => Assigned + case Unassigned.Name => Unassigned + case Labeled.Name => Labeled + case Unlabeled.Name => Unlabeled + case Opened.Name => Opened + case Closed.Name => Closed + case Reopened.Name => Reopened + case Synchronize.Name => Synchronize + case Edited.Name => Edited + case _ => Unknown(name) } } } @@ -42,3 +43,7 @@ object Reopened extends PullRequestAction { object Synchronize extends PullRequestAction { override val Name = "synchronize" } +object Edited extends PullRequestAction { + override val Name = "edited" +} +case class Unknown(Name: String) extends PullRequestAction diff --git a/src/main/scala/com/getbootstrap/savage/github/util/package.scala b/src/main/scala/com/getbootstrap/savage/github/util/package.scala index 15ffe0c..2852e0e 100644 --- a/src/main/scala/com/getbootstrap/savage/github/util/package.scala +++ b/src/main/scala/com/getbootstrap/savage/github/util/package.scala @@ -33,7 +33,7 @@ package object util { def asPullRemote: String = s"https://github.com/${repoId.generateId}.git" } implicit class RichPullRequestPayload(val payload: PullRequestPayload) extends AnyVal { - def action: PullRequestAction = PullRequestAction(payload.getAction).get + def action: PullRequestAction = PullRequestAction(payload.getAction) } implicit class RichUser(val user: User) extends AnyVal { def username: GitHubUser = GitHubUser(user.getLogin) From 8b0013aed2351a5d79d19dced676054bb4e70bc9 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Wed, 6 Jul 2016 20:30:54 -0700 Subject: [PATCH 03/17] Update copyright notice --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index be6d760..00401c8 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Chris Rebert +Copyright (c) 2014-2016 The Savage Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From dee11b91b7fdc032723c4238fee0ab87ff3d9f95 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Wed, 6 Jul 2016 20:50:49 -0700 Subject: [PATCH 04/17] Update dependencies & tools --- .travis.yml | 4 ++-- assembly.sbt | 4 ---- build.sbt | 2 +- project/build.properties | 2 +- project/plugins.sbt | 4 ++-- 5 files changed, 6 insertions(+), 10 deletions(-) delete mode 100644 assembly.sbt diff --git a/.travis.yml b/.travis.yml index 6155f19..75d0cf3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ sudo: false language: scala scala: - - 2.11.5 + - 2.11.8 script: - - sbt ++2.11.5 test + - sbt ++2.11.8 test # avoid unnecessary cache updates - find $HOME/.sbt -name "*.lock" | xargs rm - find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm diff --git a/assembly.sbt b/assembly.sbt deleted file mode 100644 index 473f907..0000000 --- a/assembly.sbt +++ /dev/null @@ -1,4 +0,0 @@ -import AssemblyKeys._ - -assemblySettings - diff --git a/build.sbt b/build.sbt index d1cc7f5..442bace 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ name := "savage" version := "1.0" -scalaVersion := "2.11.5" +scalaVersion := "2.11.8" mainClass := Some("com.getbootstrap.savage.server.Boot") diff --git a/project/build.properties b/project/build.properties index 817bc38..43b8278 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.9 +sbt.version=0.13.11 diff --git a/project/plugins.sbt b/project/plugins.sbt index 49c4f3e..d7757b8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,3 @@ -addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.2") +addSbtPlugin("io.spray" % "sbt-revolver" % "0.8.0") -addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2") +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3") From 4b33977f20c1a81f7bfa2367b9e20932cce3f3d3 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Fri, 8 Jul 2016 00:34:23 -0700 Subject: [PATCH 05/17] Remove the ignore-branches-from-watched-repo setting Build PRs of project members in addition to non-members. If this results in redundant builds you wish to eliminate, you're advised to refactor members' builds to a more Savage-based workflow. --- README.md | 2 -- src/main/resources/application.conf | 1 - .../getbootstrap/savage/server/PullRequestEventHandler.scala | 1 - src/main/scala/com/getbootstrap/savage/server/Settings.scala | 1 - 4 files changed, 5 deletions(-) diff --git a/README.md b/README.md index 4342246..7fd81d4 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,6 @@ savage { github-repo-to-watch = "twbs/bootstrap" // Full name of GitHub repo to push test branches to github-test-repo = "twbs/bootstrap-tests" - // Ignore pull requests whose branch is from the watched repo (and is thus from a project team member) - ignore-branches-from-watched-repo = true // Pull requests must target one of these branches in the watched repo allowed-base-branches = [ "master" ] // List of GitHub organization names whose public members Savage should trust to authorize retries of builds diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 9af89ec..f2782b3 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -28,7 +28,6 @@ savage { travis-timeout = 2 hours github-repo-to-watch = "twbs/bootstrap" github-test-repo = "twbs-savage/bootstrap" - ignore-branches-from-watched-repo = true allowed-base-branches = [ "master", "v4-dev", "v3-dev" ] trusted-orgs = [ "twbs" ] whitelist = [ diff --git a/src/main/scala/com/getbootstrap/savage/server/PullRequestEventHandler.scala b/src/main/scala/com/getbootstrap/savage/server/PullRequestEventHandler.scala index aa207b9..c09d596 100644 --- a/src/main/scala/com/getbootstrap/savage/server/PullRequestEventHandler.scala +++ b/src/main/scala/com/getbootstrap/savage/server/PullRequestEventHandler.scala @@ -96,7 +96,6 @@ class PullRequestEventHandler( if (settings.AllowedBaseBranches.contains(destBranch)) { prHead.getRepo.repositoryId match { case None => log.error(s"Received event from GitHub about repository with unsafe name") - case Some(settings.MainRepoId) if settings.IgnoreBranchesFromMainRepo => log.info("Ignoring PR whose branch is from the main repo, per settings.") case Some(foreignRepo) => { val baseSha = bsBase.commitSha val headSha = prHead.commitSha diff --git a/src/main/scala/com/getbootstrap/savage/server/Settings.scala b/src/main/scala/com/getbootstrap/savage/server/Settings.scala index a5e2020..5a2041a 100644 --- a/src/main/scala/com/getbootstrap/savage/server/Settings.scala +++ b/src/main/scala/com/getbootstrap/savage/server/Settings.scala @@ -26,7 +26,6 @@ class SettingsImpl(config: Config) extends Extension { val Whitelist: FilePathWhitelist = new FilePathWhitelist(config.getStringList("savage.whitelist").asScala) val Watchlist: FilePathWatchlist = new FilePathWatchlist(config.getStringList("savage.file-watchlist").asScala) val BranchPrefix: String = config.getString("savage.branch-prefix") - val IgnoreBranchesFromMainRepo: Boolean = config.getBoolean("savage.ignore-branches-from-watched-repo") val AllowedBaseBranches: Set[Branch] = config.getStringList("savage.allowed-base-branches").asScala.flatMap{ Branch(_) }.toSet val TrustedOrganizations: Set[String] = config.getStringList("savage.trusted-orgs").asScala.toSet val SetCommitStatus: Boolean = config.getBoolean("savage.set-commit-status") From 5f4f917ce80fca357c6342c26d341215ad5901e4 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Fri, 8 Jul 2016 01:02:25 -0700 Subject: [PATCH 06/17] Switch to non-hierarchical GitHub commit status context string (#41) For brevity's sake, because GitHub uses a truncating fixed-width field to display statuses. h/t to @houndci for the idea. --- .../com/getbootstrap/savage/github/commit_status/Status.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/com/getbootstrap/savage/github/commit_status/Status.scala b/src/main/scala/com/getbootstrap/savage/github/commit_status/Status.scala index e3b2d5d..0adc795 100644 --- a/src/main/scala/com/getbootstrap/savage/github/commit_status/Status.scala +++ b/src/main/scala/com/getbootstrap/savage/github/commit_status/Status.scala @@ -3,7 +3,7 @@ package com.getbootstrap.savage.github.commit_status import org.eclipse.egit.github.core.{CommitStatus => RawCommitStatus} object Status { - private val context = "continuous-integration/savage" + private val context = "savage" } trait Status { def description: String From b122272d680bff5efd4f4b225bd6dfd7b955a4b6 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Fri, 8 Jul 2016 01:23:47 -0700 Subject: [PATCH 07/17] Add option to show preview URL for the PR in the GitHub comment (#42) Refs https://github.com/twbs/bootstrap/pull/20179 --- README.md | 4 ++++ src/main/resources/application.conf | 1 + .../com/getbootstrap/savage/server/PullRequestCommenter.scala | 2 ++ src/main/scala/com/getbootstrap/savage/server/Settings.scala | 1 + 4 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 7fd81d4..dfb5181 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,10 @@ savage { // pushing the branch to GitHub, Savage will assume something went wrong and delete the branch to // keep the test repo's branches tidy. travis-timeout = 2 hours + // Include a link to the "preview URL" of the PR in the GitHub comment? + // Probably only makes sense if you're Bootstrap. + // Otherwise, you'll need to edit the hardcoded URL template string. + show-preview-urls = false // Full name of GitHub repo to watch for new pull requests github-repo-to-watch = "twbs/bootstrap" // Full name of GitHub repo to push test branches to diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index f2782b3..d6b2f1a 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -26,6 +26,7 @@ savage { squelch-invalid-http-logging = true set-commit-status = true travis-timeout = 2 hours + show-preview-urls = true github-repo-to-watch = "twbs/bootstrap" github-test-repo = "twbs-savage/bootstrap" allowed-base-branches = [ "master", "v4-dev", "v3-dev" ] diff --git a/src/main/scala/com/getbootstrap/savage/server/PullRequestCommenter.scala b/src/main/scala/com/getbootstrap/savage/server/PullRequestCommenter.scala index b31e08f..1701d90 100644 --- a/src/main/scala/com/getbootstrap/savage/server/PullRequestCommenter.scala +++ b/src/main/scala/com/getbootstrap/savage/server/PullRequestCommenter.scala @@ -15,12 +15,14 @@ class PullRequestCommenter extends GitHubActorWithLogging { case PullRequestBuildResult(prNum, commitSha, buildUrl, succeeded) => { val mythicalStatus = if (succeeded) { "**CONFIRMED**" } else { "**BUSTED**" } val plainStatus = if (succeeded) { "**Tests passed.**" } else { "**Tests failed.**" } + val previewInfo = if (settings.ShowPreviewUrls) { "Docs preview: http://preview.twbsapps.com/c/${commitSha.sha}" } else { "" } val commentMarkdown = s""" |${plainStatus} Automated cross-browser testing via Sauce Labs and Travis CI shows that the JavaScript changes in this pull request are: ${mythicalStatus} | |Commit: ${commitSha.sha} |Build details: ${buildUrl} + |${previewInfo} | |(*Please note that this is a [fully automated](https://github.com/twbs/savage) comment.*) """.stripMargin diff --git a/src/main/scala/com/getbootstrap/savage/server/Settings.scala b/src/main/scala/com/getbootstrap/savage/server/Settings.scala index 5a2041a..2a31f49 100644 --- a/src/main/scala/com/getbootstrap/savage/server/Settings.scala +++ b/src/main/scala/com/getbootstrap/savage/server/Settings.scala @@ -30,6 +30,7 @@ class SettingsImpl(config: Config) extends Extension { val TrustedOrganizations: Set[String] = config.getStringList("savage.trusted-orgs").asScala.toSet val SetCommitStatus: Boolean = config.getBoolean("savage.set-commit-status") val TravisTimeout: FiniteDuration = config.getFiniteDuration("savage.travis-timeout") + val ShowPreviewUrls: Boolean = config.getBoolean("savage.show-preview-urls") } object Settings extends ExtensionId[SettingsImpl] with ExtensionIdProvider { override def lookup() = Settings From 3c23c0c662515846cf00f4668332d928bf2e7ad0 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sun, 10 Jul 2016 12:16:50 -0700 Subject: [PATCH 08/17] Add missing "s" prefix so string gets interpolated Fixup to #42 Refs https://github.com/twbs/bootstrap/pull/20267#issuecomment-231597458 --- .../com/getbootstrap/savage/server/PullRequestCommenter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/com/getbootstrap/savage/server/PullRequestCommenter.scala b/src/main/scala/com/getbootstrap/savage/server/PullRequestCommenter.scala index 1701d90..c7ee038 100644 --- a/src/main/scala/com/getbootstrap/savage/server/PullRequestCommenter.scala +++ b/src/main/scala/com/getbootstrap/savage/server/PullRequestCommenter.scala @@ -15,7 +15,7 @@ class PullRequestCommenter extends GitHubActorWithLogging { case PullRequestBuildResult(prNum, commitSha, buildUrl, succeeded) => { val mythicalStatus = if (succeeded) { "**CONFIRMED**" } else { "**BUSTED**" } val plainStatus = if (succeeded) { "**Tests passed.**" } else { "**Tests failed.**" } - val previewInfo = if (settings.ShowPreviewUrls) { "Docs preview: http://preview.twbsapps.com/c/${commitSha.sha}" } else { "" } + val previewInfo = if (settings.ShowPreviewUrls) { s"Docs preview: http://preview.twbsapps.com/c/${commitSha.sha}" } else { "" } val commentMarkdown = s""" |${plainStatus} Automated cross-browser testing via Sauce Labs and Travis CI shows that the JavaScript changes in this pull request are: ${mythicalStatus} From aa9310209dc3e88e1c3b24eaf2b21a0d1bf57ba1 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sat, 21 Jan 2017 15:38:26 -0800 Subject: [PATCH 09/17] Bump sbt to v0.13.13 (#44) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 43b8278..27e88aa 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.11 +sbt.version=0.13.13 From a03672a740636e1f7819ecb2df1e2f4d4e067a7e Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sat, 21 Jan 2017 15:44:15 -0800 Subject: [PATCH 10/17] Bump Eclipse GitHub library to v4.6.0.201612231935-r (#45) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 442bace..5707fb6 100644 --- a/build.sbt +++ b/build.sbt @@ -12,7 +12,7 @@ resolvers += "Eclipse Foundation Releases" at "https://repo.eclipse.org/content/ resolvers += "Eclipse Foundation Snapshots" at "https://repo.eclipse.org/content/repositories/snapshots/" -libraryDependencies += "org.eclipse.mylyn.github" % "org.eclipse.egit.github.core" % "4.0.0.201503231230-m1" +libraryDependencies += "org.eclipse.mylyn.github" % "org.eclipse.egit.github.core" % "4.6.0.201612231935-r" // egit-github needs Gson, but doesn't explicitly require it libraryDependencies += "com.google.code.gson" % "gson" % "2.3.1" From 131b13b327b31ce662619d12e7b3f8778600748c Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sat, 21 Jan 2017 15:46:43 -0800 Subject: [PATCH 11/17] Bump logback-classic to v1.1.9 (#46) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 5707fb6..bc09b7f 100644 --- a/build.sbt +++ b/build.sbt @@ -17,7 +17,7 @@ libraryDependencies += "org.eclipse.mylyn.github" % "org.eclipse.egit.github.cor // egit-github needs Gson, but doesn't explicitly require it libraryDependencies += "com.google.code.gson" % "gson" % "2.3.1" -libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.1.2" +libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.1.9" libraryDependencies ++= { val akkaV = "2.3.9" From 245b0b70fcd588aab91cd84c7c5c56491a5a785f Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sat, 21 Jan 2017 15:49:59 -0800 Subject: [PATCH 12/17] Bump Gson to v2.8.0 (#47) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index bc09b7f..cdc8c59 100644 --- a/build.sbt +++ b/build.sbt @@ -15,7 +15,7 @@ resolvers += "Eclipse Foundation Snapshots" at "https://repo.eclipse.org/content libraryDependencies += "org.eclipse.mylyn.github" % "org.eclipse.egit.github.core" % "4.6.0.201612231935-r" // egit-github needs Gson, but doesn't explicitly require it -libraryDependencies += "com.google.code.gson" % "gson" % "2.3.1" +libraryDependencies += "com.google.code.gson" % "gson" % "2.8.0" libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.1.9" From 7ab9939337c57e361d496702d407a524495e41b6 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sat, 21 Jan 2017 15:56:46 -0800 Subject: [PATCH 13/17] Bump Spray to v1.3.4 (#48) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index cdc8c59..ca27b59 100644 --- a/build.sbt +++ b/build.sbt @@ -21,7 +21,7 @@ libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.1.9" libraryDependencies ++= { val akkaV = "2.3.9" - val sprayV = "1.3.2" + val sprayV = "1.3.4" Seq( "io.spray" %% "spray-can" % sprayV, "io.spray" %% "spray-routing" % sprayV, From 8ffa0b5676a56957a5212ef495300625f4936532 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sat, 21 Jan 2017 16:10:10 -0800 Subject: [PATCH 14/17] Bump Akka to v2.3.16 (#49) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ca27b59..5ea66c6 100644 --- a/build.sbt +++ b/build.sbt @@ -20,7 +20,7 @@ libraryDependencies += "com.google.code.gson" % "gson" % "2.8.0" libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.1.9" libraryDependencies ++= { - val akkaV = "2.3.9" + val akkaV = "2.3.16" val sprayV = "1.3.4" Seq( "io.spray" %% "spray-can" % sprayV, From 266c7a5013c7f8b6f5b3201c2ea09b3dcef2dcd0 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sat, 21 Jan 2017 16:17:58 -0800 Subject: [PATCH 15/17] Bump spray-json to v1.3.3 (#51) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 5ea66c6..0eea957 100644 --- a/build.sbt +++ b/build.sbt @@ -26,7 +26,7 @@ libraryDependencies ++= { "io.spray" %% "spray-can" % sprayV, "io.spray" %% "spray-routing" % sprayV, "io.spray" %% "spray-testkit" % sprayV % "test", - "io.spray" %% "spray-json" % "1.3.1", + "io.spray" %% "spray-json" % "1.3.3", "com.typesafe.akka" %% "akka-actor" % akkaV, "com.typesafe.akka" %% "akka-slf4j" % akkaV, "com.typesafe.akka" %% "akka-testkit" % akkaV % "test", From b9b820d8af774e1f1b73d582775cc04b8fdef301 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sat, 21 Jan 2017 22:37:12 -0800 Subject: [PATCH 16/17] Implement SHA1-RSA signature verification logic using JCA & Bouncy Castle (#53) To be used for Travis's new authentication mechanism Refs #43 --- build.sbt | 3 ++ .../com/getbootstrap/savage/crypto/Pem.scala | 41 +++++++++++++++++++ .../savage/crypto/RsaPublicKey.scala | 16 ++++++++ .../savage/crypto/Sha1WithRsa.scala | 27 ++++++++++++ .../crypto/SignatureVerificationStatus.scala | 9 ++++ 5 files changed, 96 insertions(+) create mode 100644 src/main/scala/com/getbootstrap/savage/crypto/Pem.scala create mode 100644 src/main/scala/com/getbootstrap/savage/crypto/RsaPublicKey.scala create mode 100644 src/main/scala/com/getbootstrap/savage/crypto/Sha1WithRsa.scala create mode 100644 src/main/scala/com/getbootstrap/savage/crypto/SignatureVerificationStatus.scala diff --git a/build.sbt b/build.sbt index 0eea957..fecc1a8 100644 --- a/build.sbt +++ b/build.sbt @@ -19,6 +19,9 @@ libraryDependencies += "com.google.code.gson" % "gson" % "2.8.0" libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.1.9" +// For reading PEM ("-----BEGIN PUBLIC KEY-----"), which Travis's API uses for its public key. +libraryDependencies += "org.bouncycastle" % "bcpkix-jdk15on" % "1.56" + libraryDependencies ++= { val akkaV = "2.3.16" val sprayV = "1.3.4" diff --git a/src/main/scala/com/getbootstrap/savage/crypto/Pem.scala b/src/main/scala/com/getbootstrap/savage/crypto/Pem.scala new file mode 100644 index 0000000..47496f1 --- /dev/null +++ b/src/main/scala/com/getbootstrap/savage/crypto/Pem.scala @@ -0,0 +1,41 @@ +package com.getbootstrap.savage.crypto + +import scala.util.{Try,Success,Failure} +import java.io.StringReader +import java.security.spec.X509EncodedKeySpec +import org.bouncycastle.util.io.pem.PemReader +import org.bouncycastle.util.io.pem.PemObject + + +sealed class MalformedPemException(cause: Throwable) extends RuntimeException("The given data did not conform to the PEM format!", cause) + +sealed class UnexpectedPemDataTypeException(expectedType: String, pemObj: PemObject) + extends RuntimeException(s"PEM contained data of unexpected type! Expected: ${expectedType} Actual: ${pemObj.getType}") + +// PEM is the name for the format that involves "-----BEGIN PUBLIC KEY-----" etc. +object Pem { + private val PublicKeyPemType = "PUBLIC KEY" + + @throws[MalformedPemException]("if there is a problem decoding the PEM data") + private def decode(pem: String): PemObject = { + val pemReader = new PemReader(new StringReader(pem)) + val pemObjTry = Try { pemReader.readPemObject() } + val closeTry = Try { pemReader.close() } + (pemObjTry, closeTry) match { + case (Failure(readExc), _) => throw new MalformedPemException(readExc) + case (_, Failure(closeExc)) => throw new MalformedPemException(closeExc) + case (Success(pemObj), Success(_)) => pemObj + } + } + + // Decodes PKCS8 data in PEM format into a X509EncodedKeySpec + // which can be handled by sun.security.rsa.RSAKeyFactory + @throws[UnexpectedPemDataTypeException]("if the PEM contains non-public-key data") + def decodePublicKeyIntoSpec(publicKeyInPem: String): X509EncodedKeySpec = { + val pemObj = decode(publicKeyInPem) + pemObj.getType match { + case PublicKeyPemType => new X509EncodedKeySpec(pemObj.getContent) + case unexpectedType => throw new UnexpectedPemDataTypeException(PublicKeyPemType, pemObj) + } + } +} diff --git a/src/main/scala/com/getbootstrap/savage/crypto/RsaPublicKey.scala b/src/main/scala/com/getbootstrap/savage/crypto/RsaPublicKey.scala new file mode 100644 index 0000000..a91c6ed --- /dev/null +++ b/src/main/scala/com/getbootstrap/savage/crypto/RsaPublicKey.scala @@ -0,0 +1,16 @@ +package com.getbootstrap.savage.crypto + +import scala.util.Try +import java.security.KeyFactory +import java.security.PublicKey +import java.security.spec.X509EncodedKeySpec + + +sealed case class RsaPublicKey private(publicKey: PublicKey) + +object RsaPublicKey { + private val rsaKeyFactory = KeyFactory.getInstance("RSA") // Supported in all spec-compliant JVMs + + def fromX509Spec(keySpec: X509EncodedKeySpec): Try[RsaPublicKey] = Try{ rsaKeyFactory.generatePublic(keySpec) }.map{ new RsaPublicKey(_) } + def fromPem(pem: String): Try[RsaPublicKey] = Try{ Pem.decodePublicKeyIntoSpec(pem) }.flatMap{ fromX509Spec(_) } +} diff --git a/src/main/scala/com/getbootstrap/savage/crypto/Sha1WithRsa.scala b/src/main/scala/com/getbootstrap/savage/crypto/Sha1WithRsa.scala new file mode 100644 index 0000000..c01afee --- /dev/null +++ b/src/main/scala/com/getbootstrap/savage/crypto/Sha1WithRsa.scala @@ -0,0 +1,27 @@ +package com.getbootstrap.savage.crypto + +import java.security.Signature +import java.security.SignatureException +import java.security.InvalidKeyException + + +object Sha1WithRsa { + private val signatureAlgorithmName = "SHA1withRSA" // Supported in all spec-compliant JVMs + private def newSignatureVerifier(): Signature = Signature.getInstance(signatureAlgorithmName) + + def verifySignature(signature: Array[Byte], publicKey: RsaPublicKey, signedData: Array[Byte]): SignatureVerificationStatus = { + val verifier = newSignatureVerifier() + try { + verifier.initVerify(publicKey.publicKey) + verifier.update(signedData) + verifier.verify(signature) match { + case true => SuccessfullyVerified + case false => FailedVerification + } + } + catch { + case keyExc:InvalidKeyException => ExceptionDuringVerification(keyExc) + case sigExc:SignatureException => ExceptionDuringVerification(sigExc) + } + } +} diff --git a/src/main/scala/com/getbootstrap/savage/crypto/SignatureVerificationStatus.scala b/src/main/scala/com/getbootstrap/savage/crypto/SignatureVerificationStatus.scala new file mode 100644 index 0000000..2697335 --- /dev/null +++ b/src/main/scala/com/getbootstrap/savage/crypto/SignatureVerificationStatus.scala @@ -0,0 +1,9 @@ +package com.getbootstrap.savage.crypto + +sealed trait SignatureVerificationStatus + +object SuccessfullyVerified extends SignatureVerificationStatus + +trait FailedVerification extends SignatureVerificationStatus +object FailedVerification extends SignatureVerificationStatus +case class ExceptionDuringVerification(error: Throwable) extends FailedVerification From 3b70c26fa798a96238cf98e0f36194e51a7a089b Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Mon, 23 Jan 2017 15:41:14 -0800 Subject: [PATCH 17/17] Move HmacSha1 from util package to crypto package (#54) For more logical organization. HmacSha1 is used to verify the integrity and authenticity of requests from GitHub. --- .../com/getbootstrap/savage/{util => crypto}/HmacSha1.scala | 3 ++- .../getbootstrap/savage/server/HubSignatureDirectives.scala | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) rename src/main/scala/com/getbootstrap/savage/{util => crypto}/HmacSha1.scala (91%) diff --git a/src/main/scala/com/getbootstrap/savage/util/HmacSha1.scala b/src/main/scala/com/getbootstrap/savage/crypto/HmacSha1.scala similarity index 91% rename from src/main/scala/com/getbootstrap/savage/util/HmacSha1.scala rename to src/main/scala/com/getbootstrap/savage/crypto/HmacSha1.scala index 93340ac..2af880b 100644 --- a/src/main/scala/com/getbootstrap/savage/util/HmacSha1.scala +++ b/src/main/scala/com/getbootstrap/savage/crypto/HmacSha1.scala @@ -1,9 +1,10 @@ -package com.getbootstrap.savage.util +package com.getbootstrap.savage.crypto import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec import java.security.{NoSuchAlgorithmException, InvalidKeyException, SignatureException} import java.security.MessageDigest +import com.getbootstrap.savage.util.HexByteArray object HmacSha1 { private val HmacSha1Algorithm = "HmacSHA1" diff --git a/src/main/scala/com/getbootstrap/savage/server/HubSignatureDirectives.scala b/src/main/scala/com/getbootstrap/savage/server/HubSignatureDirectives.scala index a0d4153..0308757 100644 --- a/src/main/scala/com/getbootstrap/savage/server/HubSignatureDirectives.scala +++ b/src/main/scala/com/getbootstrap/savage/server/HubSignatureDirectives.scala @@ -3,7 +3,8 @@ package com.getbootstrap.savage.server import scala.util.{Try,Success,Failure} import spray.routing.{Directive1, MalformedHeaderRejection, MalformedRequestContentRejection, ValidationRejection} import spray.routing.directives.{BasicDirectives, HeaderDirectives, RouteDirectives, MarshallingDirectives} -import com.getbootstrap.savage.util.{HmacSha1,Utf8ByteArray} +import com.getbootstrap.savage.crypto.HmacSha1 +import com.getbootstrap.savage.util.Utf8ByteArray trait HubSignatureDirectives { import BasicDirectives.provide