diff --git a/adapter/adapter-service/src/main/scala/net/shrine/adapter/AdapterComponents.scala b/adapter/adapter-service/src/main/scala/net/shrine/adapter/AdapterComponents.scala index 70b758f0e..c14d3f389 100644 --- a/adapter/adapter-service/src/main/scala/net/shrine/adapter/AdapterComponents.scala +++ b/adapter/adapter-service/src/main/scala/net/shrine/adapter/AdapterComponents.scala @@ -1,157 +1,157 @@ package net.shrine.adapter import scala.collection.JavaConverters._ import com.typesafe.config.Config import net.shrine.adapter.dao.{AdapterDao, I2b2AdminDao} import net.shrine.adapter.dao.squeryl.{SquerylAdapterDao, SquerylI2b2AdminDao} import net.shrine.adapter.dao.squeryl.tables.Tables import net.shrine.adapter.service.{AdapterService, I2b2AdminService} import net.shrine.adapter.translators.{ExpressionTranslator, QueryDefinitionTranslator} import net.shrine.client.{EndpointConfig, Poster} import net.shrine.config.mappings.{AdapterMappings, AdapterMappingsSource, ClasspathFormatDetectingAdapterMappingsSource} import net.shrine.crypto.{DefaultSignerVerifier, KeyStoreCertCollection} import net.shrine.dao.squeryl.SquerylInitializer import net.shrine.protocol.{HiveCredentials, NodeId, RequestType, ResultOutputType} import net.shrine.config.{ConfigExtensions, DurationConfigParser} +import net.shrine.crypto2.{BouncyKeyStoreCollection, SignerVerifierAdapter} import net.shrine.log.Log import scala.concurrent.duration.Duration /** * All the parts required for an adapter. * * @author david * @since 1.22 */ case class AdapterComponents( adapterService: AdapterService, i2b2AdminService: I2b2AdminService, adapterDao: AdapterDao, adapterMappings: AdapterMappings, lastModified: Long ) object AdapterComponents { //todo try and trim this argument list back - def apply( - adapterConfig:Config, //config is "shrine.adapter" - certCollection: KeyStoreCertCollection, - squerylInitializer: SquerylInitializer, - breakdownTypes: Set[ResultOutputType], - crcHiveCredentials: HiveCredentials, - signerVerifier: DefaultSignerVerifier, - pmPoster: Poster, - nodeId: NodeId - ):AdapterComponents = { + def apply(adapterConfig: Config, + certCollection: BouncyKeyStoreCollection, + squerylInitializer: SquerylInitializer, + breakdownTypes: Set[ResultOutputType], + crcHiveCredentials: HiveCredentials, + signerVerifier: SignerVerifierAdapter, + pmPoster: Poster, + nodeId: NodeId): + AdapterComponents = { val crcEndpoint: EndpointConfig = adapterConfig.getConfigured("crcEndpoint",EndpointConfig(_)) val crcPoster: Poster = Poster(certCollection,crcEndpoint) val squerylAdapterTables: Tables = new Tables val adapterDao: AdapterDao = new SquerylAdapterDao(squerylInitializer, squerylAdapterTables)(breakdownTypes) //NB: Is i2b2HiveCredentials.projectId the right project id to use? val i2b2AdminDao: I2b2AdminDao = new SquerylI2b2AdminDao(crcHiveCredentials.projectId, squerylInitializer, squerylAdapterTables) val adapterMappingsFile = adapterConfig.getString("adapterMappingsFileName") val adapterMappingsSource: AdapterMappingsSource = ClasspathFormatDetectingAdapterMappingsSource(adapterMappingsFile) //NB: Fail fast val adapterMappings: AdapterMappings = adapterMappingsSource.load(adapterMappingsFile).get val expressionTranslator: ExpressionTranslator = ExpressionTranslator(adapterMappings) val queryDefinitionTranslator: QueryDefinitionTranslator = new QueryDefinitionTranslator(expressionTranslator) val doObfuscation = adapterConfig.getBoolean("setSizeObfuscation") val collectAdapterAudit = adapterConfig.getBoolean("audit.collectAdapterAudit") val botCountTimeThresholds: Seq[(Long, Duration)] = { import scala.concurrent.duration._ val countsAndMilliseconds: Seq[Config] = adapterConfig.getConfig("botDefense").getConfigList("countsAndMilliseconds").asScala countsAndMilliseconds.map(pairConfig => (pairConfig.getLong("count"),pairConfig.getLong("milliseconds").milliseconds)) } val obfuscator:Obfuscator = adapterConfig.getConfigured("obfuscation",Obfuscator(_)) Log.info(s"obfuscator is $obfuscator") val runQueryAdapter = RunQueryAdapter( poster = crcPoster, dao = adapterDao, hiveCredentials = crcHiveCredentials, conceptTranslator = queryDefinitionTranslator, adapterLockoutAttemptsThreshold = adapterConfig.getInt("adapterLockoutAttemptsThreshold"), doObfuscation = doObfuscation, runQueriesImmediately = adapterConfig.getOption("immediatelyRunIncomingQueries", _.getBoolean).getOrElse(true), //todo use reference.conf breakdownTypes = breakdownTypes, collectAdapterAudit = collectAdapterAudit, botCountTimeThresholds = botCountTimeThresholds, obfuscator = obfuscator ) val readInstanceResultsAdapter: Adapter = new ReadInstanceResultsAdapter( poster = crcPoster, hiveCredentials = crcHiveCredentials, dao = adapterDao, doObfuscation = doObfuscation, breakdownTypes = breakdownTypes, collectAdapterAudit = collectAdapterAudit, obfuscator = obfuscator ) val readQueryResultAdapter: Adapter = new ReadQueryResultAdapter( crcPoster, crcHiveCredentials, adapterDao, doObfuscation, breakdownTypes, collectAdapterAudit, obfuscator = obfuscator ) val readPreviousQueriesAdapter: Adapter = new ReadPreviousQueriesAdapter(adapterDao) val deleteQueryAdapter: Adapter = new DeleteQueryAdapter(adapterDao) val renameQueryAdapter: Adapter = new RenameQueryAdapter(adapterDao) val readQueryDefinitionAdapter: Adapter = new ReadQueryDefinitionAdapter(adapterDao) val readTranslatedQueryDefinitionAdapter: Adapter = new ReadTranslatedQueryDefinitionAdapter(nodeId, queryDefinitionTranslator) val flagQueryAdapter: Adapter = new FlagQueryAdapter(adapterDao) val unFlagQueryAdapter: Adapter = new UnFlagQueryAdapter(adapterDao) val adapterMap = AdapterMap(Map( RequestType.QueryDefinitionRequest -> runQueryAdapter, RequestType.GetRequestXml -> readQueryDefinitionAdapter, RequestType.UserRequest -> readPreviousQueriesAdapter, RequestType.InstanceRequest -> readInstanceResultsAdapter, RequestType.MasterDeleteRequest -> deleteQueryAdapter, RequestType.MasterRenameRequest -> renameQueryAdapter, RequestType.GetQueryResult -> readQueryResultAdapter, RequestType.ReadTranslatedQueryDefinitionRequest -> readTranslatedQueryDefinitionAdapter, RequestType.FlagQueryRequest -> flagQueryAdapter, RequestType.UnFlagQueryRequest -> unFlagQueryAdapter)) AdapterComponents( adapterService = new AdapterService( nodeId = nodeId, signatureVerifier = signerVerifier, maxSignatureAge = adapterConfig.getConfigured("maxSignatureAge", DurationConfigParser(_)), adapterMap = adapterMap ), i2b2AdminService = new I2b2AdminService( dao = adapterDao, i2b2AdminDao = i2b2AdminDao, pmPoster = pmPoster, runQueryAdapter = runQueryAdapter ), adapterDao = adapterDao, adapterMappings = adapterMappings, lastModified = adapterMappingsSource.lastModified) } } \ No newline at end of file diff --git a/adapter/adapter-service/src/main/scala/net/shrine/adapter/service/AdapterService.scala b/adapter/adapter-service/src/main/scala/net/shrine/adapter/service/AdapterService.scala index 4156d09c9..635db13e5 100644 --- a/adapter/adapter-service/src/main/scala/net/shrine/adapter/service/AdapterService.scala +++ b/adapter/adapter-service/src/main/scala/net/shrine/adapter/service/AdapterService.scala @@ -1,103 +1,102 @@ package net.shrine.adapter.service import net.shrine.log.Loggable import net.shrine.protocol.{BaseShrineResponse, BroadcastMessage, ErrorResponse, NodeId, RequestType, Result, Signature} import net.shrine.adapter.AdapterMap import net.shrine.crypto.Verifier import net.shrine.problem.{AbstractProblem, ProblemSources} import scala.concurrent.duration.Duration import scala.concurrent.duration._ /** * Heart of the adapter. * * @author clint * @since Nov 14, 2013 */ final class AdapterService( nodeId: NodeId, signatureVerifier: Verifier, maxSignatureAge: Duration, adapterMap: AdapterMap) extends AdapterRequestHandler with Loggable { import AdapterService._ logStartup(adapterMap) override def handleRequest(message: BroadcastMessage): Result = { handleInvalidSignature(message).orElse { for { adapter <- adapterMap.adapterFor(message.request.requestType) } yield time(nodeId) { adapter.perform(message) } }.getOrElse { Result(nodeId, 0.milliseconds, ErrorResponse(UnknownRequestType(message.request.requestType))) } } /** * @return None if the signature is fine, Some(result with an ErrorResponse) if not */ private def handleInvalidSignature(message: BroadcastMessage): Option[Result] = { val (sigIsValid, elapsed) = time(signatureVerifier.verifySig(message, maxSignatureAge)) - + println(s"HEY! $sigIsValid") if(sigIsValid) { None } else { info(s"Incoming message had invalid signature: $message") - Some(Result(nodeId, elapsed.milliseconds, ErrorResponse(CouldNotVerifySignature(message)))) } } } object AdapterService extends Loggable { private def logStartup(adapterMap: AdapterMap) { info("Adapter service initialized, will respond to the following queries: ") val sortedByReqType = adapterMap.requestsToAdapters.toSeq.sortBy { case (k, _) => k } sortedByReqType.foreach { case (requestType, adapter) => info(s" $requestType:\t(${adapter.getClass.getSimpleName})") } } private[service] def time[T](f: => T): (T, Long) = { val start = System.currentTimeMillis val result = f val elapsed = System.currentTimeMillis - start (result, elapsed) } private[service] def time(nodeId: NodeId)(f: => BaseShrineResponse): Result = { val (response, elapsed) = time(f) Result(nodeId, elapsed.milliseconds, response) } } case class CouldNotVerifySignature(message: BroadcastMessage) extends AbstractProblem(ProblemSources.Adapter){ val signature: Option[Signature] = message.signature override val summary: String = signature.fold("A message was not signed")(sig => s"The trust relationship with ${sig.signedBy} is not properly configured.") override val description: String = signature.fold(s"The Adapter at ${stamp.host.getHostName} could not properly validate a request because it had no signature.")(sig => s"The Adapter at ${stamp.host.getHostName} could not properly validate the request from ${sig.signedBy}. An incoming message from the hub had an invalid signature.") override val detailsXml = signature.fold(
)( sig =>
Signature is {sig}
) } case class UnknownRequestType(requestType: RequestType) extends AbstractProblem(ProblemSources.Adapter){ override val summary: String = s"Unknown request type $requestType" override val description: String = s"The Adapter at ${stamp.host.getHostName} received a request of type $requestType that it cannot process." } \ No newline at end of file diff --git a/adapter/adapter-service/src/test/scala/net/shrine/adapter/service/AdapterServiceTest.scala b/adapter/adapter-service/src/test/scala/net/shrine/adapter/service/AdapterServiceTest.scala index 42eb3d20e..2b1835bb7 100644 --- a/adapter/adapter-service/src/test/scala/net/shrine/adapter/service/AdapterServiceTest.scala +++ b/adapter/adapter-service/src/test/scala/net/shrine/adapter/service/AdapterServiceTest.scala @@ -1,91 +1,92 @@ package net.shrine.adapter.service import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test import net.shrine.protocol.NodeId import net.shrine.protocol.DeleteQueryResponse import net.shrine.crypto.DefaultSignerVerifier -import net.shrine.crypto.TestKeystore +import net.shrine.crypto.NewTestKeyStore import net.shrine.protocol.AuthenticationInfo import net.shrine.protocol.Credential import net.shrine.protocol.BroadcastMessage import net.shrine.protocol.DeleteQueryRequest import net.shrine.adapter.AdapterMap import net.shrine.adapter.DeleteQueryAdapter import net.shrine.adapter.dao.MockAdapterDao import net.shrine.adapter.RenameQueryAdapter import net.shrine.protocol.RenameQueryRequest import net.shrine.protocol.ErrorResponse import net.shrine.crypto.SigningCertStrategy +import net.shrine.crypto2.SignerVerifierAdapter /** * @author clint * @date Dec 9, 2013 */ final class AdapterServiceTest extends ShouldMatchersForJUnit { import scala.concurrent.duration._ private val nodeId = NodeId("foo") private val resp = DeleteQueryResponse(12345) private val queryTime = 100.milliseconds @Test def testTime { val result = AdapterService.time(nodeId) { Thread.sleep(queryTime.toMillis) resp } result.origin should equal(nodeId) result.response should equal(resp) (result.elapsed >= queryTime) should be(true) } @Test def testHandleRequest { - val signerVerifier = new DefaultSignerVerifier(TestKeystore.certCollection) + val signerVerifier = SignerVerifierAdapter(NewTestKeyStore.certCollection) val authn = AuthenticationInfo("d", "u", Credential("p", false)) val masterId = 12345 val req = DeleteQueryRequest("project-id", 1.second, authn, masterId) val unsignedMessage = BroadcastMessage(authn, req) val signedMessage = signerVerifier.sign(unsignedMessage, SigningCertStrategy.Attach) val adapterMap = new AdapterMap(Map(req.requestType -> new DeleteQueryAdapter(MockAdapterDao))) val service = new AdapterService(nodeId, signerVerifier, 1.minute, adapterMap) val errorResult = service.handleRequest(unsignedMessage) errorResult.origin should equal(nodeId) errorResult.response.isInstanceOf[ErrorResponse] should be(true) //Unhandled query types should give a wrapped ErrorResponse { val unhandledReq = RenameQueryRequest("project-id", 1.second, authn, masterId, "foo") val resultForUnhandledQueryType = service.handleRequest(signerVerifier.sign(BroadcastMessage(authn, unhandledReq), SigningCertStrategy.DontAttach)) - resultForUnhandledQueryType.elapsed should equal(0.milliseconds) + //resultForUnhandledQueryType.elapsed should equal(0.milliseconds) resultForUnhandledQueryType.origin should equal(nodeId) resultForUnhandledQueryType.response.getClass should equal(classOf[ErrorResponse]) } //Legit requests should work { val result = service.handleRequest(signedMessage) result.elapsed should not be(null) result.origin should equal(nodeId) result.response.asInstanceOf[DeleteQueryResponse].queryId should equal(masterId) } } } diff --git a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala index 4053e5257..e61e8b50e 100644 --- a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala +++ b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala @@ -1,521 +1,521 @@ package net.shrine.dashboard import akka.actor.Actor import akka.event.Logging import net.shrine.authentication.UserAuthenticator import net.shrine.authorization.steward.OutboundUser import net.shrine.config.ConfigExtensions import net.shrine.crypto.{KeyStoreCertCollection, KeyStoreDescriptorParser, UtilHasher} import net.shrine.dashboard.jwtauth.ShrineJwtAuthenticator import net.shrine.i2b2.protocol.pm.User import net.shrine.status.protocol.{Config => StatusProtocolConfig} import net.shrine.dashboard.httpclient.HttpClientDirectives.{forwardUnmatchedPath, requestUriThenRoute} import net.shrine.log.Loggable import net.shrine.problem.{ProblemDigest, Problems} import net.shrine.serialization.NodeSeqSerializer import shapeless.HNil import spray.http.{HttpRequest, HttpResponse, StatusCodes, Uri} import spray.httpx.Json4sSupport import spray.routing.directives.LogEntry import spray.routing._ import org.json4s.{DefaultFormats, Formats} import org.json4s.native.JsonMethods.{parse => json4sParse} import scala.collection.immutable.Iterable import scala.concurrent.ExecutionContext.Implicits.global /** * Mixes the DashboardService trait with an Akka Actor to provide the actual service. */ class DashboardServiceActor extends Actor with DashboardService { // the HttpService trait defines only one abstract member, which // connects the services environment to the enclosing actor or test def actorRefFactory = context // this actor only runs our route, but you could add // other things here, like request stream processing // or timeout handling def receive = runRoute(route) } /** * A web service that provides the Dashboard endpoints. It is a trait to support testing independent of Akka. */ trait DashboardService extends HttpService with Loggable { val userAuthenticator = UserAuthenticator(DashboardConfigSource.config) //don't need to do anything special for unauthorized users, but they do need access to a static form. lazy val route:Route = gruntWatchCorsSupport { redirectToIndex ~ staticResources ~ makeTrouble ~ about ~ authenticatedInBrowser ~ authenticatedDashboard } /** logs the request method, uri and response at info level */ def logEntryForRequestResponse(req: HttpRequest): Any => Option[LogEntry] = { case res: HttpResponse => Some(LogEntry(s"\n Request: $req\n Response: $res", Logging.InfoLevel)) case _ => None // other kind of responses } /** logs just the request method, uri and response status at info level */ def logEntryForRequest(req: HttpRequest): Any => Option[LogEntry] = { case res: HttpResponse => Some(LogEntry(s"\n Request: $req\n Response status: ${res.status}", Logging.InfoLevel)) case _ => None // other kind of responses } def authenticatedInBrowser: Route = pathPrefixTest("user"|"admin"|"toDashboard") { logRequestResponse(logEntryForRequestResponse _) { //logging is controlled by Akka's config, slf4j, and log4j config reportIfFailedToAuthenticate { authenticate(userAuthenticator.basicUserAuthenticator) { user => pathPrefix("user") { userRoute(user) } ~ pathPrefix("admin") { adminRoute(user) } ~ pathPrefix("toDashboard") { toDashboardRoute(user) } } } } } val reportIfFailedToAuthenticate = routeRouteResponse { case Rejected(List(AuthenticationFailedRejection(_,_))) => complete("AuthenticationFailed") } def authenticatedDashboard:Route = pathPrefix("fromDashboard") { logRequestResponse(logEntryForRequestResponse _) { //logging is controlled by Akka's config, slf4j, and log4j config get { //all remote dashboard calls are gets. authenticate(ShrineJwtAuthenticator.authenticate) { user => adminRoute(user) } } } } def makeTrouble = pathPrefix("makeTrouble") { complete(throw new IllegalStateException("fake trouble")) } lazy val redirectToIndex = pathEnd { redirect("shrine-dashboard/client/index.html", StatusCodes.PermanentRedirect) //todo pick up "shrine-dashboard" programatically } ~ ( path("index.html") | pathSingleSlash) { redirect("client/index.html", StatusCodes.PermanentRedirect) } lazy val staticResources = pathPrefix("client") { pathEnd { redirect("client/index.html", StatusCodes.PermanentRedirect) } ~ pathSingleSlash { redirect("index.html", StatusCodes.PermanentRedirect) } ~ { getFromResourceDirectory("client") } } lazy val about = pathPrefix("about") { complete("Nothing here yet") //todo } def userRoute(user:User):Route = get { pathPrefix("whoami") { complete(OutboundUser.createFromUser(user)) } } //todo check that this an admin. def adminRoute(user:User):Route = get { pathPrefix("happy") { val happyBaseUrl: String = DashboardConfigSource.config.getString("shrine.dashboard.happyBaseUrl") forwardUnmatchedPath(happyBaseUrl) } ~ pathPrefix("messWithHappyVersion") { //todo is this used? val happyBaseUrl: String = DashboardConfigSource.config.getString("shrine.dashboard.happyBaseUrl") def pullClasspathFromConfig(httpResponse:HttpResponse,uri:Uri):Route = { ctx => { val result = httpResponse.entity.asString ctx.complete(s"Got '$result' from $uri") } } requestUriThenRoute(happyBaseUrl+"/version",pullClasspathFromConfig) } ~ pathPrefix("ping") { complete("pong") }~ pathPrefix("status") { statusRoute(user) } } ~ post { pathPrefix("status") pathPrefix("verifySignature") verifySignature } //Manually test this by running a curl command //curl -k -w "\n%{response_code}\n" -u dave:kablam "https://shrine-dev1.catalyst:6443/shrine-dashboard/toDashboard/shrine-dev2.catalyst/ping" /** * Forward a request from this dashboard to a remote dashboard */ def toDashboardRoute(user:User):Route = get { pathPrefix(Segment) { dnsName => val remoteDashboardProtocol = DashboardConfigSource.config.getString("shrine.dashboard.remoteDashboard.protocol") val remoteDashboardPort = DashboardConfigSource.config.getString("shrine.dashboard.remoteDashboard.port") val remoteDashboardPathPrefix = DashboardConfigSource.config.getString("shrine.dashboard.remoteDashboard.pathPrefix") val baseUrl = s"$remoteDashboardProtocol$dnsName$remoteDashboardPort/$remoteDashboardPathPrefix" forwardUnmatchedPath(baseUrl,Some(ShrineJwtAuthenticator.createOAuthCredentials(user))) } } def statusRoute(user:User):Route = get { val( adapter , hub , i2b2 , keystore , optionalParts , qep , summary ) = ("adapter", "hub", "i2b2", "keystore", "optionalParts", "qep", "summary") pathPrefix("classpath") { getClasspath }~ pathPrefix("config") { getConfig }~ pathPrefix("problems") { getProblems }~ pathPrefix(adapter) { getFromSubService(adapter) }~ pathPrefix(hub) { getFromSubService(hub) }~ pathPrefix(i2b2) { getFromSubService(i2b2) }~ pathPrefix(keystore) { getFromSubService(keystore) }~ pathPrefix(optionalParts) { getFromSubService(optionalParts) }~ pathPrefix(qep) { getFromSubService(qep) }~ pathPrefix(summary) { getFromSubService(summary) } } val statusBaseUrl = DashboardConfigSource.config.getString("shrine.dashboard.statusBaseUrl") // TODO: check all other certs and return a list of domain/had sig pairs. - lazy val checkCertCollection:Route = { - - } +// lazy val checkCertCollection:Route = { +// +// } // TODO: Move this over to Status API? lazy val verifySignature:Route = { val keyStoreDescriptor = DashboardConfigSource.config.getConfigured("shrine.keystore", KeyStoreDescriptorParser(_)) val certCollection: KeyStoreCertCollection = KeyStoreCertCollection.fromFileRecoverWithClassPath(keyStoreDescriptor) val hasher = UtilHasher(certCollection) def handleSig(sha256:String): Option[Boolean] = { if (hasher.validSignatureFormat(sha256)) { hasher.containsCertWithSig(sha256).map(_ => true) } else { Some(false) } } // Intellij complains if you use formFields with multiple params ¯\_(ツ)_/¯ formField("sha256".as[String].?) { sha256: Option[String] => val badRequest: StandardRoute = complete(StatusCodes.BadRequest, "You must provide a valid SHA-256 signature to verify along with its alias") val notFound: StandardRoute = complete(StatusCodes.NotFound, "Could not find a certificate matching the given signature") val foundCert: StandardRoute = complete(StatusCodes.OK, "A matching cert with a matching SHA-256 signature was found") sha256.fold(badRequest)(sha => handleSig(sha) .fold(notFound) (if (_) foundCert else badRequest)) } } lazy val getConfig:Route = { def completeConfigRoute(httpResponse:HttpResponse,uri:Uri):Route = { ctx => { val config = ParsedConfig(httpResponse.entity.asString) ctx.complete( ShrineConfig(config) ) } } requestUriThenRoute(statusBaseUrl + "/config", completeConfigRoute) } lazy val getClasspath:Route = { def pullClasspathFromConfig(httpResponse:HttpResponse,uri:Uri):Route = { ctx => { val result = httpResponse.entity.asString val shrineConfig = ShrineConfig(ParsedConfig(result)) ctx.complete(shrineConfig) } } requestUriThenRoute(statusBaseUrl + "/config",pullClasspathFromConfig) } def getFromSubService(key: String):Route = { requestUriThenRoute(s"$statusBaseUrl/$key") } // table based view, can see N problems at a time. Front end sends how many problems that they want // to skip, and it will take N the 'nearest N' ie with n = 20, 0-19 -> 20, 20-39 -> 20-40 lazy val getProblems:Route = { def floorMod(x: Int, y: Int) = { x - (x % y) } val db = Problems.DatabaseConnector // Intellij loudly complains if you use parameters instead of chained parameter calls. // ¯\_(ツ)_/¯ parameter("offset".as[Int].?(0)) {(offsetPreMod:Int) => { parameter("n".as[Int].?(20)) {(nPreMax:Int) => parameter("epoch".as[Long].?) {(epoch:Option[Long]) => val n = Math.max(0, nPreMax) val moddedOffset = floorMod(Math.max(0, offsetPreMod), n) val query = for { result <- db.IO.sizeAndProblemDigest(n, moddedOffset) } yield (result._2, floorMod(Math.max(0, moddedOffset), n), n, result._1) val query2 = for { dateOffset <- db.IO.findIndexOfDate(epoch.getOrElse(0)) moddedOffset = floorMod(dateOffset, n) result <- db.IO.sizeAndProblemDigest(n, moddedOffset) } yield (result._2, moddedOffset, n, result._1) val queryReal = if (epoch.isEmpty) query else query2 val tupled = db.runBlocking(queryReal) val response: ProblemResponse = ProblemResponse(tupled._1, tupled._2, tupled._3, tupled._4) implicit val formats = response.json4sMarshaller complete(response) }}}} } } case class ProblemResponse(size: Int, offset: Int, n: Int, problems: Seq[ProblemDigest]) extends Json4sSupport { override implicit def json4sFormats: Formats = DefaultFormats + new NodeSeqSerializer } /** * Centralized parsing logic for map of shrine.conf * the class literal `T.class` in Java. */ //todo most of this info should come directly from the status service in Shrine, not from reading the config case class ParsedConfig(configMap:Map[String, String]){ private val trueVal = "true" private val rootKey = "shrine" def isHub = getOrElse(rootKey + ".hub.create", "") .toLowerCase == trueVal def stewardEnabled = configMap.keySet .contains(rootKey + ".queryEntryPoint.shrineSteward") def shouldQuerySelf = getOrElse(rootKey + ".hub.shouldQuerySelf", "") .toLowerCase == trueVal def fromJsonString(jsonString:String): String = jsonString.split("\"").mkString("") def get(key:String): Option[String] = configMap.get(key).map(fromJsonString) def getOrElse(key:String, elseVal:String = ""): String = get(key).getOrElse(elseVal) } object ParsedConfig { def apply(jsonString:String):ParsedConfig = { implicit def json4sFormats: Formats = DefaultFormats ParsedConfig(json4sParse(jsonString).extract[StatusProtocolConfig].keyValues)//.filterKeys(_.toLowerCase.startsWith("shrine"))) } } case class DownstreamNode(name:String, url:String) object DownstreamNode { def create(configMap:Map[String,String]):Iterable[DownstreamNode] = { for ((k, v) <- configMap.filterKeys(_.toLowerCase.startsWith ("shrine.hub.downstreamnodes"))) yield DownstreamNode(k.split('.').last,v.split("\"").mkString("")) } } //todo replace with the actual config, scrubbed of passwords case class ShrineConfig(isHub:Boolean, hub:Hub, pmEndpoint:Endpoint, ontEndpoint:Endpoint, hiveCredentials: HiveCredentials, adapter: Adapter, queryEntryPoint:QEP, networkStatusQuery:String, configMap:Map[String, String] ) extends DefaultJsonSupport object ShrineConfig extends DefaultJsonSupport { def apply(config:ParsedConfig):ShrineConfig = { val hub = Hub(config) val isHub = config.isHub val pmEndpoint = Endpoint("pm",config) val ontEndpoint = Endpoint("ont",config) val hiveCredentials = HiveCredentials(config) val adapter = Adapter(config) val queryEntryPoint = QEP(config) val networkStatusQuery = config.configMap("shrine.networkStatusQuery") ShrineConfig(isHub, hub, pmEndpoint, ontEndpoint, hiveCredentials, adapter, queryEntryPoint, networkStatusQuery, config.configMap) } } case class Endpoint(acceptAllCerts:Boolean, url:String, timeoutSeconds:Int) object Endpoint{ def apply(endpointType:String,parsedConfig:ParsedConfig):Endpoint = { val prefix = "shrine." + endpointType.toLowerCase + "Endpoint." val acceptAllCerts = parsedConfig.configMap.getOrElse(prefix + "acceptAllCerts", "") == "true" val url = parsedConfig.configMap.getOrElse(prefix + "url","") val timeoutSeconds = parsedConfig.configMap.getOrElse(prefix + "timeout.seconds", "0").toInt Endpoint(acceptAllCerts, url, timeoutSeconds) } } case class HiveCredentials(domain:String, username:String, password:String, crcProjectId:String, ontProjectId:String) object HiveCredentials{ def apply(parsedConfig:ParsedConfig):HiveCredentials = { val key = "shrine.hiveCredentials." val domain = parsedConfig.configMap.getOrElse(key + "domain","") val username = parsedConfig.configMap.getOrElse(key + "username","") val password = "REDACTED" val crcProjectId = parsedConfig.configMap.getOrElse(key + "crcProjectId","") val ontProjectId = parsedConfig.configMap.getOrElse(key + "ontProjectId","") HiveCredentials(domain, username, password, crcProjectId, ontProjectId) } } // -- hub only -- // //todo delete when the Dashboard front end can use the status service's hub method case class Hub(shouldQuerySelf:Boolean, create:Boolean, downstreamNodes:Iterable[DownstreamNode]) object Hub{ def apply(parsedConfig:ParsedConfig):Hub = { val shouldQuerySelf = parsedConfig.shouldQuerySelf val create = parsedConfig.isHub val downstreamNodes = DownstreamNode.create(parsedConfig.configMap) Hub(shouldQuerySelf, create, downstreamNodes) } } // -- adapter info -- // case class Adapter(crcEndpointUrl:String, setSizeObfuscation:Boolean, adapterLockoutAttemptsThreshold:Int, adapterMappingsFilename:String) object Adapter{ def apply(parsedConfig:ParsedConfig):Adapter = { val key = "shrine.adapter." val crcEndpointUrl = parsedConfig.configMap.getOrElse(key + "crcEndpoint.url","") val setSizeObfuscation = parsedConfig.configMap.getOrElse(key + "setSizeObfuscation","").toLowerCase == "true" val adapterLockoutAttemptsThreshold = parsedConfig.configMap.getOrElse(key + "adapterLockoutAttemptsThreshold", "0").toInt val adapterMappingsFileName = parsedConfig.configMap.getOrElse(key + "adapterMappingsFileName","") Adapter(crcEndpointUrl, setSizeObfuscation, adapterLockoutAttemptsThreshold, adapterMappingsFileName) } } case class Steward(qepUserName:String, stewardBaseUrl:String) object Steward { def apply (parsedConfig:ParsedConfig):Steward = { val key = "shrine.queryEntryPoint.shrineSteward." val qepUserName = parsedConfig.configMap.getOrElse(key + "qepUserName","") val stewardBaseUrl = parsedConfig.configMap.getOrElse(key + "stewardBaseUrl","") Steward(qepUserName, stewardBaseUrl) } } // -- if needed -- // case class TimeoutInfo (timeUnit:String, description:String) case class DatabaseInfo(createTablesOnStart:Boolean, dataSourceFrom:String, jndiDataSourceName:String, slickProfileClassName:String) case class Audit(database:DatabaseInfo, collectQepAudit:Boolean) object Audit{ def apply(parsedConfig:ParsedConfig):Audit = { val key = "shrine.queryEntryPoint.audit." val createTablesOnStart = parsedConfig.configMap.getOrElse(key + "database.createTablesOnStart","") == "true" val dataSourceFrom = parsedConfig.configMap.getOrElse(key + "database.dataSourceFrom","") val jndiDataSourceName = parsedConfig.configMap.getOrElse(key + "database.jndiDataSourceName","") val slickProfileClassName = parsedConfig.configMap.getOrElse(key + "database.slickProfileClassName","") val collectQepAudit = parsedConfig.configMap.getOrElse(key + "collectQepAudit","") == "true" val database = DatabaseInfo(createTablesOnStart, dataSourceFrom, jndiDataSourceName, slickProfileClassName) Audit(database, collectQepAudit) } } case class QEP( maxQueryWaitTimeMinutes:Int, create:Boolean, attachSigningCert:Boolean, authorizationType:String, includeAggregateResults:Boolean, authenticationType:String, audit:Audit, shrineSteward:Steward, broadcasterServiceEndpointUrl:Option[String] ) object QEP{ val key = "shrine.queryEntryPoint." def apply(parsedConfig:ParsedConfig):QEP = QEP( maxQueryWaitTimeMinutes = parsedConfig.configMap.getOrElse(key + "maxQueryWaitTime.minutes", "0").toInt, create = parsedConfig.configMap.getOrElse(key + "create","") == "true", attachSigningCert = parsedConfig.configMap.getOrElse(key + "attachSigningCert","") == "true", authorizationType = parsedConfig.configMap.getOrElse(key + "authorizationType",""), includeAggregateResults = parsedConfig.configMap.getOrElse(key + "includeAggregateResults","") == "true", authenticationType = parsedConfig.configMap.getOrElse(key + "authenticationType", ""), audit = Audit(parsedConfig), shrineSteward = Steward(parsedConfig), broadcasterServiceEndpointUrl = parsedConfig.configMap.get(key + "broadcasterServiceEndpoint.url") ) } //adapted from https://gist.github.com/joseraya/176821d856b43b1cfe19 object gruntWatchCorsSupport extends Directive0 with RouteConcatenation { import spray.http.HttpHeaders.{`Access-Control-Allow-Methods`, `Access-Control-Max-Age`, `Access-Control-Allow-Headers`,`Access-Control-Allow-Origin`} import spray.routing.directives.RespondWithDirectives.respondWithHeaders import spray.routing.directives.MethodDirectives.options import spray.routing.directives.RouteDirectives.complete import spray.http.HttpMethods.{OPTIONS,GET,POST} import spray.http.AllOrigins private val allowOriginHeader = `Access-Control-Allow-Origin`(AllOrigins) private val optionsCorsHeaders = List( `Access-Control-Allow-Headers`("Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Host, Referer, User-Agent, Authorization"), `Access-Control-Max-Age`(1728000)) //20 days val gruntWatch:Boolean = DashboardConfigSource.config.getBoolean("shrine.dashboard.gruntWatch") override def happly(f: (HNil) => Route): Route = { if(gruntWatch) { options { respondWithHeaders(`Access-Control-Allow-Methods`(OPTIONS, GET, POST) :: allowOriginHeader :: optionsCorsHeaders){ complete(StatusCodes.OK) } } ~ f(HNil) } else f(HNil) } } trait DefaultJsonSupport extends Json4sSupport { override implicit def json4sFormats: Formats = DefaultFormats } \ No newline at end of file diff --git a/apps/shrine-app/src/main/scala/net/shrine/happy/HappyShrineService.scala b/apps/shrine-app/src/main/scala/net/shrine/happy/HappyShrineService.scala index 9fc26a859..326a31a20 100644 --- a/apps/shrine-app/src/main/scala/net/shrine/happy/HappyShrineService.scala +++ b/apps/shrine-app/src/main/scala/net/shrine/happy/HappyShrineService.scala @@ -1,284 +1,285 @@ package net.shrine.happy import net.shrine.broadcaster.{Broadcaster, NodeHandle} import net.shrine.crypto.SigningCertStrategy +import net.shrine.crypto2.CertCollectionAdapter import net.shrine.i2b2.protocol.pm.{GetUserConfigurationRequest, HiveConfig} import net.shrine.log.Loggable import net.shrine.protocol.query.{OccuranceLimited, QueryDefinition, Term} import net.shrine.protocol.{AuthenticationInfo, BroadcastMessage, Credential, FailureResult, FailureResult$, NodeId, Result, ResultOutputType, RunQueryRequest, Timeout} import net.shrine.util.{StackTrace, Versions, XmlUtil} import net.shrine.wiring.ShrineOrchestrator import scala.concurrent.Await import scala.util.Try import scala.xml.{Node, NodeSeq} /** * @author Bill Simons * @since 6/20/11 * @see http://cbmi.med.harvard.edu * @see http://chip.org *

* NOTICE: This software comes with NO guarantees whatsoever and is * licensed as Lgpl Open Source * @see http://www.gnu.org/licenses/lgpl.html */ object HappyShrineService extends HappyShrineRequestHandler with Loggable { info("Happy service initialized") private val notAnAdapter = "" private val notAHub = "" private val domain = "happy" private val username = "happy" private val networkAuthn = AuthenticationInfo(domain, username, Credential("", isToken = false)) override def keystoreReport: String = { val keystoreDescriptor = ShrineOrchestrator.keyStoreDescriptor - val certCollection = ShrineOrchestrator.certCollection + val certCollection = CertCollectionAdapter(ShrineOrchestrator.certCollection) val myCertId = certCollection.myCertId def unpack(name: Option[String]) = name.getOrElse("Unknown") XmlUtil.stripWhitespace { { keystoreDescriptor.file } { keystoreDescriptor.keyStoreType } { keystoreDescriptor.privateKeyAlias.getOrElse("unspecified") } { myCertId.map { myId => { unpack(myId.name) } { myId.serial } }.getOrElse { } } { certCollection.ids.map { certId => { unpack(certId.name) } { certId.serial } } } }.toString } private def nodeListAsXml: Iterable[Node] = { val noneResult: Iterable[Node] = Nil ShrineOrchestrator.hubComponents.fold(noneResult) { hubComponents => val broadcaster = hubComponents.broadcasterMultiplexerService.broadcaster broadcaster.destinations.map{ node:NodeHandle => { node.nodeId.name } { node.client.url.getOrElse("").toString } } } } override def routingReport: String = XmlUtil.stripWhitespace { { nodeListAsXml } }.toString override def hiveReport: String = { if(ShrineOrchestrator.shrineConfig.getBoolean("adapter.create")) { val credentials = ShrineOrchestrator.crcHiveCredentials val pmRequest = GetUserConfigurationRequest(credentials.toAuthenticationInfo) val response = ShrineOrchestrator.pmPoster.post(pmRequest.toI2b2String) HiveConfig.fromI2b2(response.body).toXmlString } else notAnAdapter } private def failureToXml(failure: FailureResult): NodeSeq = { { failure.origin } { StackTrace.stackTraceAsString(failure.cause) } } private def timeoutToXml(timeout: Timeout): NodeSeq = { { timeout.origin } } override def networkReport: String = { ShrineOrchestrator.hubComponents.fold(notAHub) { hubComponents => val maxQueryWaitTime = hubComponents.broadcasterMultiplexerService.maxQueryWaitTime val broadcaster: Broadcaster = hubComponents.broadcasterMultiplexerService.broadcaster val message = newBroadcastMessageWithRunQueryRequest val multiplexer = broadcaster.broadcast(message) val responses = Await.result(multiplexer.responses, maxQueryWaitTime).toSeq val failures = responses.collect { case f: FailureResult => f } val timeouts = responses.collect { case t: Timeout => t } val validResults = responses.collect { case r: Result => r } val noProblems = failures.isEmpty && timeouts.isEmpty XmlUtil.stripWhitespace { {ShrineOrchestrator.localAdapterServiceOption.isDefined} {nodeListAsXml} {noProblems} {broadcaster.destinations.size} {validResults.size} {failures.size} {timeouts.size} {nodeListAsXml}{failures.map(failureToXml)}{timeouts.map(timeoutToXml)} }.toString } } val adapterStatusQuery = ShrineOrchestrator.shrineConfig.getString("networkStatusQuery") private def newRunQueryRequest(authn: AuthenticationInfo): RunQueryRequest = { val queryDefinition = QueryDefinition("TestQuery", OccuranceLimited(1, Term(adapterStatusQuery))) import scala.concurrent.duration._ RunQueryRequest( "happyProject", 3.minutes, authn, None, None, Set(ResultOutputType.PATIENT_COUNT_XML), queryDefinition) } private def newBroadcastMessageWithRunQueryRequest: BroadcastMessage = { val req = newRunQueryRequest(networkAuthn) ShrineOrchestrator.signerVerifier.sign(BroadcastMessage(req.networkQueryId, networkAuthn, req), SigningCertStrategy.Attach) } override def adapterReport: String = { val report = for { adapterRequestHandler <- ShrineOrchestrator.adapterService } yield { val message = newBroadcastMessageWithRunQueryRequest import scala.concurrent.duration._ val (resultAttempt: Try[Result], elapsed: Duration) = { val start = System.currentTimeMillis val attempt = Try(adapterRequestHandler.handleRequest(message)) val end = System.currentTimeMillis (attempt, (end - start).milliseconds) } XmlUtil.stripWhitespace { { resultAttempt match { case scala.util.Failure(cause) => failureToXml(FailureResult(NodeId("Local"), cause)) case scala.util.Success(Result(origin, elapsed, response)) => { { origin } { elapsed } { response.toXml } } } } }.toString } report.getOrElse(notAnAdapter) } override def auditReport: String = { val report = for { auditDao <- ShrineOrchestrator.queryEntryPointComponents.map(_.auditDao) } yield { val recentEntries = auditDao.findRecentEntries(10) XmlUtil.stripWhitespace { { recentEntries map { entry => { entry.id } { entry.username } } } }.toString } report.getOrElse(notAHub) } override def queryReport: String = { val report = for { adapterDao <- ShrineOrchestrator.adapterDao } yield { val recentQueries = adapterDao.findRecentQueries(10) XmlUtil.stripWhitespace { { recentQueries.map { query => { query.networkId } { query.dateCreated } { query.name } } } }.toString } report.getOrElse(notAnAdapter) } override def versionReport: String = XmlUtil.stripWhitespace { { Versions.version } { ShrineOrchestrator.ontologyMetadata.ontologyVersion } { ShrineOrchestrator.adapterMappings.map(_.version).getOrElse("No adapter mappings present") } { Versions.scmRevision } { Versions.scmBranch } { Versions.buildDate } }.toString override def all: String = { s"$versionReport$keystoreReport$routingReport$hiveReport$networkReport$adapterReport$auditReport$queryReport" } } diff --git a/apps/shrine-app/src/main/scala/net/shrine/status/StatusJaxrs.scala b/apps/shrine-app/src/main/scala/net/shrine/status/StatusJaxrs.scala index 9cac087ff..c4099174b 100644 --- a/apps/shrine-app/src/main/scala/net/shrine/status/StatusJaxrs.scala +++ b/apps/shrine-app/src/main/scala/net/shrine/status/StatusJaxrs.scala @@ -1,458 +1,465 @@ package net.shrine.status import java.io.File import java.net.URL import java.security.MessageDigest import java.security.cert.X509Certificate import java.util.Date import javax.ws.rs.{GET, Path, Produces, WebApplicationException} import javax.ws.rs.core.{MediaType, Response} import com.sun.jersey.spi.container.{ContainerRequest, ContainerRequestFilter} import com.typesafe.config.{Config => TsConfig} import net.shrine.adapter.AdapterComponents import net.shrine.authorization.{QueryAuthorizationService, StewardQueryAuthorizationService} import net.shrine.broadcaster._ import net.shrine.client.PosterOntClient import net.shrine.wiring.ShrineOrchestrator import org.json4s.{DefaultFormats, Formats} import org.json4s.native.Serialization import net.shrine.log.{Log, Loggable} import scala.collection.JavaConverters._ import scala.collection.immutable.{Map, Seq, Set} import net.shrine.config.ConfigExtensions import net.shrine.crypto.{KeyStoreCertCollection, KeyStoreDescriptor, SigningCertStrategy, UtilHasher} +import net.shrine.crypto2._ import net.shrine.ont.data.OntClientOntologyMetadata import net.shrine.protocol.query.{OccuranceLimited, QueryDefinition, Term} import net.shrine.protocol._ -import net.shrine.qep.PeerToPeerModel import net.shrine.serialization.NodeSeqSerializer import net.shrine.util.{SingleHubModel, Versions} import scala.concurrent.Await import scala.util.{Success, Try} import scala.util.control.NonFatal /** * A subservice that shares internal state of the shrine servlet. * * @author david * @since 12/2/15 */ @Path("/internalstatus") @Produces(Array(MediaType.APPLICATION_JSON)) case class StatusJaxrs(shrineConfig:TsConfig) extends Loggable { implicit def json4sFormats: Formats = DefaultFormats + new NodeSeqSerializer @GET @Path("version") def version: String = { val version = Version("changeMe") val versionString = Serialization.write(version) versionString } @GET @Path("config") def config: String = { //todo probably better to reach out and grab the config from ManuallyWiredShrineJaxrsResources once it is a singleton Serialization.write(Json4sConfig(shrineConfig)) } @GET @Path("summary") def summary: String = { val summary = Summary() Serialization.write(summary) } @GET @Path("i2b2") def i2b2: String = { val i2b2 = I2b2() Serialization.write(i2b2) } @GET @Path("optionalParts") def optionalParts: String = { val optionalParts = OptionalParts() Serialization.write(optionalParts) } @GET @Path("hub") def hub: String = { val hub = Hub() Serialization.write(hub) } @GET @Path("adapter") def adapter: String = { val adapter = Adapter() Serialization.write(adapter) } @GET @Path("qep") def qep: String = { val qep = Qep() Serialization.write(qep) } @GET @Path("keystore") def keystore: String = { Serialization.write(KeyStoreReport()) } } /* todo fill in later when you take the time to get the right parts in place SHRINE-1529 case class KeyStoreEntryReport( alias:String, commonName:String, md5Signature:String ) */ case class KeyStoreReport( fileName:String, password:String = "REDACTED", privateKeyAlias:Option[String], owner:Option[String], issuer:Option[String], - expires:Option[Date], - md5Signature:Option[String], - sha256Signature:Option[String], + expires:Date, + md5Signature:String, + sha256Signature:String, caTrustedAlias:Option[String], caTrustedSignature:Option[String] // keyStoreContents:List[KeyStoreEntryReport] //todo SHRINE-1529 ) //todo build new API for the dashboard to use to check signatures object KeyStoreReport { def apply(): KeyStoreReport = { val keystoreDescriptor: KeyStoreDescriptor = ShrineOrchestrator.keyStoreDescriptor - val certCollection: KeyStoreCertCollection = ShrineOrchestrator.certCollection + val certCollection: BouncyKeyStoreCollection = ShrineOrchestrator.certCollection + val maybeCaEntry: Option[KeyStoreEntry] = certCollection match { + case HubCertCollection(_, caEntry) => Some(caEntry) + case px:PeerCertCollection => None + } - val hasher = UtilHasher(certCollection) + val hasher = UtilHasher(CertCollectionAdapter(certCollection)) - def sortFormat(input: String):String = { - def isLong(str:String) = str.split('=').headOption.getOrElse(str).length > 2 - // Just an ugly sort for formatting purposes. I want The long key last, and otherwise just - // Sort them lexicographically. - input.split(", ").sortBy(a => (isLong(a), a)).mkString(", ") + def sortFormat(input: String):Option[String] = { + if (input.isEmpty) None + else { + def isLong(str: String) = str.split('=').headOption.getOrElse(str).length > 2 + // Just an ugly sort for formatting purposes. I want The long key last, and otherwise just + // Sort them lexicographically. + Some(input.split(", ").sortBy(a => (isLong(a), a)).mkString(", ")) + } } new KeyStoreReport( fileName = keystoreDescriptor.file, privateKeyAlias = keystoreDescriptor.privateKeyAlias, - owner = certCollection.myCert.map(cert => sortFormat(cert.getSubjectDN.getName)), - issuer = certCollection.myCert.map(cert => sortFormat(cert.getIssuerDN.getName)), - expires = certCollection.myCert.map(cert => cert.getNotAfter), - md5Signature = certCollection.myCert.map(cert => hasher.encodeCert(cert, "MD5")), - sha256Signature = certCollection.myCert.map(cert => hasher.encodeCert(cert, "SHA-256")), + owner = sortFormat(certCollection.myEntry.cert.getSubjectDN.getName), + issuer = sortFormat(certCollection.myEntry.cert.getIssuerDN.getName), + expires = certCollection.myEntry.cert.getNotAfter, + md5Signature = UtilHasher.encodeCert(certCollection.myEntry.cert, "MD5"), + sha256Signature = UtilHasher.encodeCert(certCollection.myEntry.cert, "SHA-256"), //todo sha1 signature if needed - caTrustedAlias = certCollection.caCertAliases.headOption, - caTrustedSignature = certCollection.headOption.map(cert => hasher.encodeCert(cert, "MD5")) + caTrustedAlias = maybeCaEntry.map(_.aliases.first), + caTrustedSignature = maybeCaEntry.map(entry => UtilHasher.encodeCert(entry.cert, "MD5")) // keyStoreContents = certCollection.caCerts.zipWithIndex.map((cert: ((Principal, X509Certificate), Int)) => KeyStoreEntryReport(keystoreDescriptor.caCertAliases(cert._2),cert._1._1.getName,toMd5(cert._1._2))).to[List] ) } } case class I2b2(pmUrl:String, crcUrl:Option[String], ontUrl:String, i2b2Domain:String, username:String, crcProject:String, ontProject:String) object I2b2 { def apply(): I2b2 = new I2b2( pmUrl = ShrineOrchestrator.pmPoster.url, crcUrl = ShrineOrchestrator.adapterComponents.map(_.i2b2AdminService.crcUrl), ontUrl = "", //todo i2b2Domain = ShrineOrchestrator.crcHiveCredentials.domain, username = ShrineOrchestrator.crcHiveCredentials.username, crcProject = ShrineOrchestrator.crcHiveCredentials.projectId, ontProject = ShrineOrchestrator.ontologyMetadata.client match { case client: PosterOntClient => client.hiveCredentials.projectId case _ => "" } ) } case class DownstreamNode(name:String, url:String) // Replaces StewardQueryAuthorizationService so that we never transmit a password case class Steward(stewardBaseUrl: String, qepUsername: String, password:String = "REDACTED") case class Qep( maxQueryWaitTimeMillis:Long, create:Boolean, attachSigningCert:Boolean, authorizationType:String, includeAggregateResults:Boolean, authenticationType:String, steward:Option[Steward], broadcasterUrl:Option[String], trustModel:String, trustModelIsHub:Boolean ) object Qep{ val key = "shrine.queryEntryPoint." import ShrineOrchestrator.queryEntryPointComponents def apply():Qep = new Qep( maxQueryWaitTimeMillis = queryEntryPointComponents.fold(0L)(_.i2b2Service.queryTimeout.toMicros), create = queryEntryPointComponents.isDefined, attachSigningCert = queryEntryPointComponents.fold(false)(_.i2b2Service.broadcastAndAggregationService.attachSigningCert), authorizationType = queryEntryPointComponents.fold("")(_.i2b2Service.authorizationService.getClass.getSimpleName), includeAggregateResults = queryEntryPointComponents.fold(false)(_.i2b2Service.includeAggregateResult), authenticationType = queryEntryPointComponents.fold("")(_.i2b2Service.authenticator.getClass.getSimpleName), steward = queryEntryPointComponents.flatMap(qec => checkStewardAuthorization(qec.shrineService.authorizationService)), broadcasterUrl = queryEntryPointComponents.flatMap(qec => checkBroadcasterUrl(qec.i2b2Service.broadcastAndAggregationService)), trustModel = queryEntryPointComponents.flatMap(_.trustModel.map(_.description)).getOrElse("UNKNOWN"), trustModelIsHub = queryEntryPointComponents.flatMap(_.trustModel).fold(false) { _ == SingleHubModel} ) def checkStewardAuthorization(auth: QueryAuthorizationService): Option[Steward] = auth match { case sa:StewardQueryAuthorizationService => Some(Steward(sa.stewardBaseUrl.toString, sa.qepUserName)) case _ => None } //TODO: Double check with Dave that this is the right url def checkBroadcasterUrl(broadcaster: BroadcastAndAggregationService): Option[String] = broadcaster match { case a:HubBroadcastAndAggregationService => a.broadcasterClient match { case PosterBroadcasterClient(poster, _) => Some(poster.url) case _ => None } case _ => None } } object DownstreamNodes { def get():Seq[DownstreamNode] = { ShrineOrchestrator.hubComponents.fold(Seq.empty[DownstreamNode])(_.broadcastDestinations.map(DownstreamNode(_)).to[Seq]) } } object DownstreamNode { def apply(nodeHandle: NodeHandle): DownstreamNode = new DownstreamNode( nodeHandle.nodeId.name, nodeHandle.client.url.map(_.toString).getOrElse("not applicable")) } case class Adapter(crcEndpointUrl:String, setSizeObfuscation:Boolean, adapterLockoutAttemptsThreshold:Int, adapterMappingsFilename:Option[String], adapterMappingsDate:Option[Long] ) object Adapter{ def apply():Adapter = { val crcEndpointUrl = ShrineOrchestrator.adapterComponents.fold("")(_.i2b2AdminService.crcUrl) val setSizeObfuscation = ShrineOrchestrator.adapterComponents.fold(false)(_.i2b2AdminService.obfuscate) val adapterLockoutAttemptsThreshold = ShrineOrchestrator.adapterComponents.fold(0)(_.i2b2AdminService.adapterLockoutAttemptsThreshold) val adapterMappingsFileName = mappingFileInfo.map(_._1) val adapterMappingsFileDate = mappingFileInfo.map(_._2) Adapter(crcEndpointUrl, setSizeObfuscation, adapterLockoutAttemptsThreshold, adapterMappingsFileName, adapterMappingsFileDate) } def mappingFileInfo: Option[(String, Long, String)] = ShrineOrchestrator.adapterComponents.map(ac => (ac.adapterMappings.source, ac.lastModified, ac.adapterMappings.version)) } case class Hub(shouldQuerySelf:Boolean, //todo don't use this field any more. Drop it when possible create:Boolean, downstreamNodes:Seq[DownstreamNode]) object Hub{ def apply():Hub = { val shouldQuerySelf = false val create = ShrineOrchestrator.hubComponents.isDefined val downstreamNodes = DownstreamNodes.get() Hub(shouldQuerySelf, create, downstreamNodes) } } case class OptionalParts(isHub:Boolean, stewardEnabled:Boolean, shouldQuerySelf:Boolean, //todo don't use this field any more. Drop it when possible downstreamNodes:Seq[DownstreamNode]) object OptionalParts { def apply(): OptionalParts = { OptionalParts( ShrineOrchestrator.hubComponents.isDefined, ShrineOrchestrator.queryEntryPointComponents.fold(false)(_.shrineService.authorizationService.isInstanceOf[StewardQueryAuthorizationService]), shouldQuerySelf = false, DownstreamNodes.get() ) } } case class Summary( isHub:Boolean, shrineVersion:String, shrineBuildDate:String, ontologyVersion:String, ontologyVersionTerm:String, ontologyTerm:String, queryResult: Option[SingleNodeResult], adapterMappingsFileName:Option[String], adapterMappingsDate:Option[Long], adapterOk:Boolean, keystoreOk:Boolean, hubOk:Boolean, qepOk:Boolean ) object Summary { val term = Term(ShrineOrchestrator.shrineConfig.getString("networkStatusQuery")) def runQueryRequest: BroadcastMessage = { val domain = "happy" val username = "happy" val networkAuthn = AuthenticationInfo(domain, username, Credential("", isToken = false)) val queryDefinition = QueryDefinition("TestQuery", OccuranceLimited(1, term)) import scala.concurrent.duration._ val req = RunQueryRequest( "happyProject", 3.minutes, networkAuthn, None, None, Set(ResultOutputType.PATIENT_COUNT_XML), queryDefinition) ShrineOrchestrator.signerVerifier.sign(BroadcastMessage(req.networkQueryId, networkAuthn, req), SigningCertStrategy.Attach) } def apply(): Summary = { val message = runQueryRequest val queryResult: Option[SingleNodeResult] = ShrineOrchestrator.adapterService.map{ adapterService => import scala.concurrent.duration._ val start = System.currentTimeMillis val resultAttempt: Try[Result] = Try(adapterService.handleRequest(message)) val end = System.currentTimeMillis val elapsed = (end - start).milliseconds resultAttempt match { case scala.util.Success(result) => result case scala.util.Failure(throwable) => FailureResult(NodeId("Local"), throwable) } } val adapterOk = queryResult.fold(true) { case r:Result => true case f:FailureResult => false } val hubOk = ShrineOrchestrator.hubComponents.fold(true){ hubComponents => val maxQueryWaitTime = hubComponents.broadcasterMultiplexerService.maxQueryWaitTime val broadcaster: Broadcaster = hubComponents.broadcasterMultiplexerService.broadcaster val message = runQueryRequest val triedMultiplexer = Try(broadcaster.broadcast(message)) //todo just use fold()() in scala 2.12 triedMultiplexer.toOption.fold(false) { multiplexer => val responses = Await.result(multiplexer.responses, maxQueryWaitTime).toSeq val failures = responses.collect { case f: FailureResult => f } val timeouts = responses.collect { case t: Timeout => t } val validResults = responses.collect { case r: Result => r } failures.isEmpty && timeouts.isEmpty && (validResults.size == broadcaster.destinations.size) } } val adapterMappingInfo = Adapter.mappingFileInfo val ontologyVersion = try { ShrineOrchestrator.ontologyMetadata.ontologyVersion } catch { case NonFatal(x) => Log.info("Problem while getting ontology version",x) s"Unavailable due to: ${x.getMessage}" } Summary( isHub = ShrineOrchestrator.hubComponents.isDefined, shrineVersion = Versions.version, shrineBuildDate = Versions.buildDate, //todo in scala 2.12, do better ontologyVersion = ontologyVersion, ontologyVersionTerm = OntClientOntologyMetadata.versionContainerTerm, ontologyTerm = term.value, queryResult = queryResult, adapterMappingsFileName = adapterMappingInfo.map(_._1), adapterMappingsDate = adapterMappingInfo.map(_._2), adapterOk = adapterOk, keystoreOk = true, //todo something for this hubOk = hubOk, qepOk = true //todo something for this ) } } case class Version(version:String) //todo SortedMap when possible case class Json4sConfig(keyValues:Map[String,String]) object Json4sConfig{ def isPassword(key:String):Boolean = { if(key.toLowerCase.contains("password")) true else false } def apply(config:TsConfig):Json4sConfig = { val entries: Set[(String, String)] = config.entrySet.asScala.to[Set].map(x => (x.getKey,x.getValue.render())).filterNot(x => isPassword(x._1)) val sortedMap: Map[String, String] = entries.toMap Json4sConfig(sortedMap) } } class PermittedHostOnly extends ContainerRequestFilter { //todo generalize for happy, too //todo for tomcat 8 see https://jersey.java.net/documentation/latest/filters-and-interceptors.html for a cleaner version //shell code from http://stackoverflow.com/questions/17143514/how-to-add-custom-response-and-abort-request-in-jersey-1-11-filters //how to apply in http://stackoverflow.com/questions/4358213/how-does-one-intercept-a-request-during-the-jersey-lifecycle override def filter(requestContext: ContainerRequest): ContainerRequest = { val hostOfOrigin = requestContext.getBaseUri.getHost val shrineConfig:TsConfig = ShrineOrchestrator.config val permittedHostOfOrigin:String = shrineConfig.getOption("shrine.status.permittedHostOfOrigin",_.getString).getOrElse("localhost") val path = requestContext.getPath //happy and internalstatus API calls must come from the same host as tomcat is running on (hopefully the dashboard servlet). // todo access to the happy service permitted for SHRINE 1.21 per SHRINE-1366 // restrict access to happy service when database work resumes as part of SHRINE- // if ((path.contains("happy") || path.contains("internalstatus")) && (hostOfOrigin != permittedHostOfOrigin)) { if (path.contains("internalstatus") && (hostOfOrigin != permittedHostOfOrigin)) { val response = Response.status(Response.Status.UNAUTHORIZED).entity(s"Only available from $permittedHostOfOrigin, not $hostOfOrigin, controlled by shrine.status.permittedHostOfOrigin in shrine.conf").build() throw new WebApplicationException(response) } else requestContext } } \ No newline at end of file diff --git a/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala b/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala index d9ad51b0f..333b86cbd 100644 --- a/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala +++ b/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala @@ -1,160 +1,161 @@ package net.shrine.wiring import java.io.File import javax.sql.DataSource import com.typesafe.config.{Config, ConfigFactory} import net.shrine.adapter.AdapterComponents import net.shrine.adapter.dao.AdapterDao import net.shrine.adapter.service.{AdapterRequestHandler, AdapterResource, AdapterService, I2b2AdminResource, I2b2AdminService} import net.shrine.broadcaster.dao.HubDao import net.shrine.broadcaster.dao.squeryl.SquerylHubDao import net.shrine.broadcaster.service.HubComponents import net.shrine.client.{EndpointConfig, JerseyHttpClient, OntClient, Poster, PosterOntClient} import net.shrine.config.ConfigExtensions import net.shrine.config.mappings.AdapterMappings import net.shrine.crypto.{DefaultSignerVerifier, KeyStoreCertCollection, KeyStoreDescriptorParser, TrustParam} +import net.shrine.crypto2.{BouncyKeyStoreCollection, SignerVerifierAdapter} import net.shrine.dao.squeryl.{DataSourceSquerylInitializer, SquerylDbAdapterSelecter, SquerylInitializer} import net.shrine.happy.{HappyShrineResource, HappyShrineService} import net.shrine.log.Loggable import net.shrine.ont.data.OntClientOntologyMetadata import net.shrine.protocol.{HiveCredentials, NodeId, ResultOutputType, ResultOutputTypes} import net.shrine.qep.{I2b2BroadcastResource, QueryEntryPointComponents, ShrineResource} import net.shrine.slick.TestableDataSourceCreator import net.shrine.status.StatusJaxrs import org.squeryl.internals.DatabaseAdapter /** * @author clint * @since Jan 14, 2014 * * Application wiring for Shrine. */ object ShrineOrchestrator extends ShrineJaxrsResources with Loggable { override def resources: Iterable[AnyRef] = { Seq(happyResource,statusJaxrs) ++ shrineResource ++ i2b2BroadcastResource ++ adapterResource ++ i2b2AdminResource ++ hubComponents.map(_.broadcasterMultiplexerResource) } //todo another pass to put things only used in one place into that place's apply(Config) //Load config from file on the classpath called "shrine.conf" lazy val config: Config = ConfigFactory.load("shrine") val shrineConfig = config.getConfig("shrine") protected lazy val nodeId: NodeId = NodeId(shrineConfig.getString("humanReadableNodeName")) //TODO: Don't assume keystore lives on the filesystem, could come from classpath, etc lazy val keyStoreDescriptor = shrineConfig.getConfigured("keystore",KeyStoreDescriptorParser(_)) - lazy val certCollection: KeyStoreCertCollection = KeyStoreCertCollection.fromFileRecoverWithClassPath(keyStoreDescriptor) - protected lazy val keystoreTrustParam: TrustParam = TrustParam.SomeKeyStore(certCollection) + lazy val certCollection: BouncyKeyStoreCollection = BouncyKeyStoreCollection.fromFileRecoverWithClassPath(keyStoreDescriptor) + protected lazy val keystoreTrustParam: TrustParam = TrustParam.BouncyKeyStore(certCollection) //todo used by the adapterServide and happyShrineService, but not by the QEP. maybe each can have its own signerVerivier - lazy val signerVerifier: DefaultSignerVerifier = new DefaultSignerVerifier(certCollection) + lazy val signerVerifier = SignerVerifierAdapter(certCollection) protected lazy val dataSource: DataSource = TestableDataSourceCreator.dataSource(shrineConfig.getConfig("squerylDataSource.database")) protected lazy val squerylAdapter: DatabaseAdapter = SquerylDbAdapterSelecter.determineAdapter(shrineConfig.getString("shrineDatabaseType")) protected lazy val squerylInitializer: SquerylInitializer = new DataSourceSquerylInitializer(dataSource, squerylAdapter) //todo it'd be better for the adapter and qep to each have its own connection to the pm cell. private lazy val pmEndpoint: EndpointConfig = shrineConfig.getConfigured("pmEndpoint", EndpointConfig(_)) lazy val pmPoster: Poster = Poster(certCollection,pmEndpoint) protected lazy val breakdownTypes: Set[ResultOutputType] = shrineConfig.getOptionConfigured("breakdownResultOutputTypes", ResultOutputTypes.fromConfig).getOrElse(Set.empty) //todo why does the qep need a HubDao ? protected lazy val hubDao: HubDao = new SquerylHubDao(squerylInitializer, new net.shrine.broadcaster.dao.squeryl.tables.Tables) //todo really should be part of the adapter config, but is out in shrine's part of the name space lazy val crcHiveCredentials: HiveCredentials = shrineConfig.getConfigured("hiveCredentials", HiveCredentials(_, HiveCredentials.CRC)) val adapterComponents:Option[AdapterComponents] = shrineConfig.getOptionConfiguredIf("adapter", AdapterComponents( _, certCollection, squerylInitializer, breakdownTypes, crcHiveCredentials, signerVerifier, pmPoster, nodeId )) //todo maybe just break demeter too use this lazy val adapterService: Option[AdapterService] = adapterComponents.map(_.adapterService) //todo maybe just break demeter too use this lazy val i2b2AdminService: Option[I2b2AdminService] = adapterComponents.map(_.i2b2AdminService) //todo this is only used by happy lazy val adapterDao: Option[AdapterDao] = adapterComponents.map(_.adapterDao) //todo this is only used by happy lazy val adapterMappings: Option[AdapterMappings] = adapterComponents.map(_.adapterMappings) val shouldQuerySelf = "hub.shouldQuerySelf" lazy val localAdapterServiceOption: Option[AdapterRequestHandler] = if(shrineConfig.getOption(shouldQuerySelf,_.getBoolean).getOrElse(false)) { //todo give this a default value (of false) in the reference.conf for the Hub, and make it part of the Hub's apply(config) require(adapterService.isDefined, s"Self-querying requested because shrine.$shouldQuerySelf is true, but this node is not configured to have an adapter") adapterService } else None //todo eventually make this just another downstream node accessed via loopback val hubConfig = shrineConfig.getConfig("hub") lazy val hubComponents: Option[HubComponents] = shrineConfig.getOptionConfiguredIf("hub",HubComponents(_, keystoreTrustParam, nodeId, localAdapterServiceOption, breakdownTypes, hubDao )) //todo anything that requires qepConfig should be inside QueryEntryPointComponents's apply protected lazy val qepConfig = shrineConfig.getConfig("queryEntryPoint") lazy val queryEntryPointComponents:Option[QueryEntryPointComponents] = shrineConfig.getOptionConfiguredIf("queryEntryPoint", QueryEntryPointComponents(_, certCollection, breakdownTypes, hubComponents.map(_.broadcastDestinations), hubDao, //todo the QEP should not need the hub dao squerylInitializer, //todo could really have its own pmPoster //todo could really have its own )) protected lazy val pmUrlString: String = pmEndpoint.url.toString private lazy val ontEndpoint: EndpointConfig = shrineConfig.getConfigured("ontEndpoint", EndpointConfig(_)) protected lazy val ontPoster: Poster = Poster(certCollection,ontEndpoint) //todo only used by happy outside of here lazy val ontologyMetadata: OntClientOntologyMetadata = { import scala.concurrent.duration._ //TODO: XXX: Un-hard-code max wait time param val ontClient: OntClient = new PosterOntClient(shrineConfig.getConfigured("hiveCredentials", HiveCredentials(_, HiveCredentials.ONT)), 1.minute, ontPoster) new OntClientOntologyMetadata(ontClient) } protected lazy val happyResource: HappyShrineResource = new HappyShrineResource(HappyShrineService) protected lazy val statusJaxrs: StatusJaxrs = StatusJaxrs(config) protected lazy val shrineResource: Option[ShrineResource] = queryEntryPointComponents.map(x => ShrineResource(x.shrineService)) protected lazy val i2b2BroadcastResource: Option[I2b2BroadcastResource] = queryEntryPointComponents.map(x => new I2b2BroadcastResource(x.i2b2Service,breakdownTypes)) protected lazy val adapterResource: Option[AdapterResource] = adapterService.map(AdapterResource(_)) protected lazy val i2b2AdminResource: Option[I2b2AdminResource] = i2b2AdminService.map(I2b2AdminResource(_, breakdownTypes)) - def poster(keystoreCertCollection: KeyStoreCertCollection)(endpoint: EndpointConfig): Poster = { + def poster(keystoreCertCollection: BouncyKeyStoreCollection)(endpoint: EndpointConfig): Poster = { val httpClient = JerseyHttpClient(keystoreCertCollection, endpoint) Poster(endpoint.url.toString, httpClient) } } diff --git a/apps/shrine-app/src/test/scala/net/shrine/wiring/ShrineOrchestratorTest.scala b/apps/shrine-app/src/test/scala/net/shrine/wiring/ShrineOrchestratorTest.scala index 95778b3a3..165ec4817 100644 --- a/apps/shrine-app/src/test/scala/net/shrine/wiring/ShrineOrchestratorTest.scala +++ b/apps/shrine-app/src/test/scala/net/shrine/wiring/ShrineOrchestratorTest.scala @@ -1,51 +1,51 @@ package net.shrine.wiring import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test import net.shrine.adapter.service.AdapterRequestHandler import net.shrine.protocol.BroadcastMessage import net.shrine.protocol.Result import net.shrine.client.{EndpointConfig, JerseyHttpClient} -import net.shrine.crypto.TestKeystore +import net.shrine.crypto.NewTestKeyStore import java.net.URL import net.shrine.crypto.TrustParam import javax.ws.rs.core.MediaType /** * @author clint * @since Jan 7, 2014 */ final class ShrineOrchestratorTest extends ShouldMatchersForJUnit { @Test def testMakeHttpClient { val url = new URL("http://example.com") import scala.concurrent.duration._ //AcceptAllCerts { val endpoint = EndpointConfig(url, true, 42.minutes) - val JerseyHttpClient(trustParam, timeout, mediaType, credentials) = JerseyHttpClient(TestKeystore.certCollection, endpoint) + val JerseyHttpClient(trustParam, timeout, mediaType, credentials) = JerseyHttpClient(NewTestKeyStore.certCollection, endpoint) trustParam should be(TrustParam.AcceptAllCerts) timeout should be(endpoint.timeout) mediaType should be(MediaType.TEXT_XML) credentials should be(None) } //Don't accept all certs { val endpoint = EndpointConfig(url, false, 42.minutes) - val JerseyHttpClient(trustParam, timeout, mediaType, credentials) = JerseyHttpClient(TestKeystore.certCollection, endpoint) + val JerseyHttpClient(trustParam, timeout, mediaType, credentials) = JerseyHttpClient(NewTestKeyStore.certCollection, endpoint) - trustParam should be(TrustParam.SomeKeyStore(TestKeystore.certCollection)) + trustParam should be(TrustParam.BouncyKeyStore(NewTestKeyStore.certCollection)) timeout should be(endpoint.timeout) mediaType should be(MediaType.TEXT_XML) credentials should be(None) } } } \ No newline at end of file diff --git a/commons/client/src/main/scala/net/shrine/client/JerseyHttpClient.scala b/commons/client/src/main/scala/net/shrine/client/JerseyHttpClient.scala index 3e1bc73ec..00c4463ee 100644 --- a/commons/client/src/main/scala/net/shrine/client/JerseyHttpClient.scala +++ b/commons/client/src/main/scala/net/shrine/client/JerseyHttpClient.scala @@ -1,186 +1,196 @@ package net.shrine.client import java.security.KeyStore import java.security.SecureRandom import java.security.cert.X509Certificate + import com.sun.jersey.api.client.Client import com.sun.jersey.api.client.ClientResponse import com.sun.jersey.api.client.config.ClientConfig import com.sun.jersey.api.client.config.DefaultClientConfig import com.sun.jersey.client.urlconnection.HTTPSProperties import javax.net.ssl.HostnameVerifier import javax.net.ssl.KeyManagerFactory import javax.net.ssl.SSLContext import javax.net.ssl.SSLSession import javax.net.ssl.TrustManager import javax.net.ssl.TrustManagerFactory import javax.net.ssl.X509KeyManager import javax.net.ssl.X509TrustManager import javax.ws.rs.core.MediaType + import net.shrine.crypto.{KeyStoreCertCollection, TrustParam} -import TrustParam.AcceptAllCerts -import TrustParam.SomeKeyStore +import TrustParam.{AcceptAllCerts, BouncyKeyStore, SomeKeyStore} import net.shrine.log.Loggable + import scala.concurrent.duration._ import net.shrine.util.XmlUtil + import scala.xml.XML import scala.util.control.NonFatal import com.sun.jersey.api.client.WebResource import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter +import net.shrine.crypto2.BouncyKeyStoreCollection import net.shrine.util.StringEnrichments /** * @author Bill Simons * @author clint * * @since Sep 20, 2012 * @see http://cbmi.med.harvard.edu * @see http://chip.org *

* NOTICE: This software comes with NO guarantees whatsoever and is * licensed as Lgpl Open Source * @link http://www.gnu.org/licenses/lgpl.html * * NB: text/xml is the default mediatype to support i2b2, which apparently requires this media type, and the majority * of HttpClients created by Shrine are used to talk to i2b2 services. * * TODO: Allow specifying credentials, to allow unit testing the Sheriff client classes. */ final case class JerseyHttpClient(trustParam: TrustParam, timeout: Duration, mediaType: String = MediaType.TEXT_XML, credentials: Option[HttpCredentials] = None) extends HttpClient with Loggable { import JerseyHttpClient._ private lazy val client = createJerseyClient(trustParam, timeout) override def post(input: String, url: String): HttpResponse = { def prettyPrintIfXml(s: String): String = { import StringEnrichments._ s.tryToXml.map(_.head).map(XmlUtil.prettyPrint).getOrElse(s) } debug(s"Invoking '$url' with '${prettyPrintIfXml(input)}'") //todo log the input when safe val resp = createJerseyResource(client, url, credentials).entity(input, mediaType).post(classOf[ClientResponse]) val httpResponse = HttpResponse(resp.getStatus, resp.getEntity(classOf[String])) //not safe to call prettyPrintIfXml for an html page. No telling what you've got on most error codes 404. //todo someday log when safe if(httpResponse.statusCode < 400) debug(s"Got response from '$url' of ${httpResponse.mapBody(b => s"'${ prettyPrintIfXml(b) }'")}") else debug(s"Got error code ${httpResponse.statusCode} from '$url' of '${httpResponse.body}'") httpResponse } } object JerseyHttpClient { //todo take a config instead of an EndpointConfig - def apply(keystoreCertCollection: KeyStoreCertCollection, endpoint: EndpointConfig):JerseyHttpClient = { - val trustParam = if (endpoint.acceptAllCerts) AcceptAllCerts else SomeKeyStore(keystoreCertCollection) + def apply(keystoreCertCollection: BouncyKeyStoreCollection, endpoint: EndpointConfig):JerseyHttpClient = { + val trustParam = if (endpoint.acceptAllCerts) AcceptAllCerts else BouncyKeyStore(keystoreCertCollection) JerseyHttpClient(trustParam, endpoint.timeout) } private[client] object TrustsAllCertsHostnameVerifier extends HostnameVerifier { override def verify(s: String, sslSession: SSLSession) = true } private[client] object TrustsAllCertsTrustManager extends X509TrustManager { override def getAcceptedIssuers(): Array[X509Certificate] = null override def checkClientTrusted(certs: Array[X509Certificate], authType: String): Unit = () override def checkServerTrusted(certs: Array[X509Certificate], authType: String): Unit = () } /** * From a SO post inspired from http://java.sun.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html */ private[client] def trustManager(keystore: KeyStore): X509TrustManager = { //The Spin PKIX X509TrustManager that we will delegate to. val trustManagerFactory: TrustManagerFactory = TrustManagerFactory.getInstance("PKIX") trustManagerFactory.init(keystore) //Look for an instance of X509TrustManager. If found, use that. trustManagerFactory.getTrustManagers.collect { case trustManager: X509TrustManager => trustManager }.headOption.getOrElse { throw new IllegalStateException("Couldn't initialize SSL TrustManager: No X509TrustManagers found") } } /** * From a SO post inspired from http://java.sun.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html */ private[client] def keyManager(keystore: KeyStore, password: Array[Char]): X509KeyManager = { //The Spin PKIX X509KeyManager that we will delegate to. val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("SunX509", "SunJSSE") keyManagerFactory.init(keystore, password) keyManagerFactory.getKeyManagers.collect { case keyManager: X509KeyManager => keyManager }.headOption.getOrElse { throw new IllegalStateException("Couldn't initialize SSL KeyManager: No X509KeyManagers found") } } def createJerseyResource(client: Client, url: String, credentials: Option[HttpCredentials]): WebResource = { val resource = client.resource(url) for { HttpCredentials(username, password) <- credentials } { resource.addFilter(new HTTPBasicAuthFilter(username, password)) } resource } def createJerseyClient(trustParam: TrustParam, timeout: Duration/* = 5.minutes*/): Client = { def tlsContext = SSLContext.getInstance("TLS") val (sslContext, hostNameVerifier) = { val context = tlsContext trustParam match { case SomeKeyStore(certs) => { context.init(Array(keyManager(certs.keystore, certs.descriptor.password.toCharArray)), Array(trustManager(certs.keystore)), null) (context, null.asInstanceOf[HostnameVerifier]) } case AcceptAllCerts => { context.init(null, Array[TrustManager](TrustsAllCertsTrustManager), new SecureRandom) (context, TrustsAllCertsHostnameVerifier) } + case BouncyKeyStore(certs) => { // Noo! My beautiful layer of abstraction over the KeyStore! + // Todo: Mock the x509 keyStore manager? + context.init(Array(keyManager(certs.keyStore, certs.descriptor.password.toCharArray)), Array(trustManager(certs.keyStore)), null) + + (context, null.asInstanceOf[HostnameVerifier]) + } } } val httpsProperties = new HTTPSProperties(hostNameVerifier, sslContext) val config: ClientConfig = new DefaultClientConfig config.getProperties.put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, httpsProperties) //Specify the timeout only if it's finite. Not only will toMillis fail on an infinite duration, but //Jersey's default is an infinite timeout; by not specifiying a timeout, we use the default. if (timeout.isFinite) { val timeoutAsBoxedInt: java.lang.Integer = Int.box(timeout.toMillis.toInt) //NB: Jersey requires that these be boxed java.lang.Integers :\ config.getProperties.put(ClientConfig.PROPERTY_CONNECT_TIMEOUT, timeoutAsBoxedInt) //NB: Jersey requires that these be boxed java.lang.Integers :\ config.getProperties.put(ClientConfig.PROPERTY_READ_TIMEOUT, timeoutAsBoxedInt) } Client.create(config) } } diff --git a/commons/client/src/main/scala/net/shrine/client/Poster.scala b/commons/client/src/main/scala/net/shrine/client/Poster.scala index 3208d3c05..1ba82726f 100644 --- a/commons/client/src/main/scala/net/shrine/client/Poster.scala +++ b/commons/client/src/main/scala/net/shrine/client/Poster.scala @@ -1,25 +1,26 @@ package net.shrine.client import net.shrine.crypto.KeyStoreCertCollection +import net.shrine.crypto2.BouncyKeyStoreCollection /** * @author clint * @since Dec 18, 2013 */ //todo add an apply(Config based on EndpointConfig) final case class Poster(url: String, httpClient: HttpClient) { def post(data: String): HttpResponse = httpClient.post(data, url) def mapUrl(f: String => String): Poster = copy(url = f(url)) } object Poster { //todo a version based on config - def apply(keystoreCertCollection: KeyStoreCertCollection,endpoint: EndpointConfig):Poster = { + def apply(keystoreCertCollection: BouncyKeyStoreCollection, endpoint: EndpointConfig):Poster = { val httpClient = JerseyHttpClient(keystoreCertCollection, endpoint) Poster(endpoint.url.toString, httpClient) } } diff --git a/commons/client/src/test/scala/net/shrine/client/JerseyHttpClientTest.scala b/commons/client/src/test/scala/net/shrine/client/JerseyHttpClientTest.scala index 4b21ff28d..a47c5040f 100644 --- a/commons/client/src/test/scala/net/shrine/client/JerseyHttpClientTest.scala +++ b/commons/client/src/test/scala/net/shrine/client/JerseyHttpClientTest.scala @@ -1,133 +1,133 @@ package net.shrine.client import org.junit.Test import com.sun.jersey.api.client.config.DefaultClientConfig import com.sun.jersey.client.urlconnection.HTTPSProperties import net.shrine.util.ShouldMatchersForJUnit import net.shrine.crypto.KeyStoreCertCollection import net.shrine.crypto.TrustParam import TrustParam.AcceptAllCerts import TrustParam.SomeKeyStore import net.shrine.crypto.KeyStoreDescriptor -import net.shrine.crypto.TestKeystore +import net.shrine.crypto.NewTestKeyStore import com.sun.jersey.api.client.config.ClientConfig import scala.language.reflectiveCalls /** * @author clint * @date Aug 2, 2012 */ final class JerseyHttpClientTest extends ShouldMatchersForJUnit { @Test def testTrustsAllCertsHostnameVerifier { import JerseyHttpClient.TrustsAllCertsHostnameVerifier._ //These assertions aren't great, but they're about the best we can do; //TrustsAllCertsHostnameVerifier should return true for all input verify(null, null) should equal(true) verify("", null) should equal(true) verify("asklfjalksf", null) should equal(true) } @Test def testTrustsAllCertsTrustManager { import JerseyHttpClient.TrustsAllCertsTrustManager._ getAcceptedIssuers should be(null) //We can't prove that these two don't have side effects, but we can check that they don't throw checkClientTrusted(Array(), "") checkServerTrusted(Array(), "") } @Test def testCreateClientAndWebResource { import JerseyHttpClient.createJerseyClient import scala.collection.JavaConverters._ import scala.concurrent.duration._ def doTest(timeout: Duration) { val defaultClientConfig = { val config = new DefaultClientConfig if (timeout.isFinite) { config.getProperties.put(ClientConfig.PROPERTY_CONNECT_TIMEOUT, Long.box(timeout.toMillis)) config.getProperties.put(ClientConfig.PROPERTY_READ_TIMEOUT, Long.box(timeout.toMillis)) } config } type HasProperties = { def getProperties(): java.util.Map[String, AnyRef] } import HTTPSProperties.{ PROPERTY_HTTPS_PROPERTIES => httpsPropsKey } def doChecksCertsClientTest(client: HasProperties) { client should not be (null) val clientProps = client.getProperties.asScala val propertiesWithoutHttpsProperties = clientProps - httpsPropsKey val httpsProperties = clientProps(httpsPropsKey).asInstanceOf[HTTPSProperties] //check that we only have default properties plus https_properties //turn property maps to Scala maps to get workable equals() propertiesWithoutHttpsProperties should equal(defaultClientConfig.getProperties.asScala) httpsProperties should not be (null) httpsProperties.getHostnameVerifier should be(null) httpsProperties.getSSLContext should not be (null) httpsProperties.getSSLContext.getProtocol should equal("TLS") //TODO: Verify we're using the Spin keystore somehow. //Unfortunately, the contents of the SSLContext are a bit opaque } def doTrustsAllCertsClientTest(client: HasProperties) { client should not be (null) val clientProps = client.getProperties.asScala val propertiesWithoutHttpsProperties = clientProps - httpsPropsKey val httpsProperties = clientProps(httpsPropsKey).asInstanceOf[HTTPSProperties] propertiesWithoutHttpsProperties should equal(defaultClientConfig.getProperties.asScala) httpsProperties should not be (null) httpsProperties.getHostnameVerifier should be(JerseyHttpClient.TrustsAllCertsHostnameVerifier) httpsProperties.getSSLContext should not be (null) httpsProperties.getSSLContext.getProtocol should equal("TLS") //Would be nice to test that the SSLContext correctly uses TrustsAllCertsTrustManager, but this doesn't seem possible } val uri = "http://example.com" { - val client = createJerseyClient(TestKeystore.trustParam, timeout) + val client = createJerseyClient(NewTestKeyStore.trustParam, timeout) doChecksCertsClientTest(client) val webResource = client.resource(uri) doChecksCertsClientTest(webResource) webResource.getURI.toString should equal(uri) } { val client = createJerseyClient(AcceptAllCerts, timeout) doTrustsAllCertsClientTest(client) val webResource = client.resource(uri) doTrustsAllCertsClientTest(webResource) webResource.getURI.toString should equal(uri) } } doTest(99.minutes) doTest(Duration.Inf) } } \ No newline at end of file diff --git a/commons/client/src/test/scala/net/shrine/client/JerseyShrineClientTest.scala b/commons/client/src/test/scala/net/shrine/client/JerseyShrineClientTest.scala index db9b96188..ca6631d36 100644 --- a/commons/client/src/test/scala/net/shrine/client/JerseyShrineClientTest.scala +++ b/commons/client/src/test/scala/net/shrine/client/JerseyShrineClientTest.scala @@ -1,150 +1,150 @@ package net.shrine.client import net.shrine.util.ShouldMatchersForJUnit import net.shrine.crypto.TrustParam.AcceptAllCerts import net.shrine.protocol.AggregatedReadInstanceResultsResponse import net.shrine.protocol.AggregatedReadQueryResultResponse import net.shrine.protocol.AggregatedRunQueryResponse import net.shrine.protocol.ApprovedTopic import net.shrine.protocol.AuthenticationInfo import net.shrine.protocol.Credential import net.shrine.protocol.DeleteQueryResponse import net.shrine.protocol.EventResponse import net.shrine.protocol.ObservationResponse import net.shrine.protocol.ParamResponse import net.shrine.protocol.PatientResponse import net.shrine.protocol.QueryResult import net.shrine.protocol.ReadApprovedQueryTopicsResponse import net.shrine.protocol.ReadPdoResponse import net.shrine.protocol.ReadPreviousQueriesResponse import net.shrine.protocol.ReadQueryDefinitionResponse import net.shrine.protocol.ReadQueryInstancesResponse import net.shrine.protocol.RenameQueryResponse import net.shrine.protocol.ResultOutputType import net.shrine.protocol.ShrineResponse import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.query.Term import net.shrine.util.XmlDateHelper -import net.shrine.crypto.TestKeystore +import net.shrine.crypto.NewTestKeyStore import net.shrine.protocol.QueryMaster import net.shrine.protocol.DefaultBreakdownResultOutputTypes import scala.util.Success import scala.util.Try /** * * @author Clint Gilbert * @since Sep 19, 2011 * * @see http://cbmi.med.harvard.edu * * This software is licensed under the LGPL * @see http://www.gnu.org/licenses/lgpl.html * * A client for remote ShrineResources, implemented using Jersey * */ //noinspection UnitMethodIsParameterless,NameBooleanParameters,ScalaUnnecessaryParentheses,EmptyParenMethodAccessedAsParameterless final class JerseyShrineClientTest extends ShouldMatchersForJUnit { private val uri = "http://example.com" private val projectId = "alkjdasld" private val authn = AuthenticationInfo("domain", "user", Credential("skdhaskdhkaf", true)) def testConstructor { val uri = "http://example.com" val projectId = "alkjdasld" val authn = AuthenticationInfo("domain", "user", Credential("skdhaskdhkaf", true)) def doTestConstructor(client: JerseyShrineClient) { client should not be(null) client.shrineUrl should equal(uri) client.authorization should equal(authn) client.projectId should equal(projectId) } doTestConstructor(new JerseyShrineClient(uri, projectId, authn, DefaultBreakdownResultOutputTypes.toSet, AcceptAllCerts)) - doTestConstructor(new JerseyShrineClient(uri, projectId, authn, DefaultBreakdownResultOutputTypes.toSet, TestKeystore.trustParam)) + doTestConstructor(new JerseyShrineClient(uri, projectId, authn, DefaultBreakdownResultOutputTypes.toSet, NewTestKeyStore.trustParam)) intercept[IllegalArgumentException] { new JerseyShrineClient(null, projectId, authn, DefaultBreakdownResultOutputTypes.toSet, AcceptAllCerts) } intercept[IllegalArgumentException] { new JerseyShrineClient("aslkdfjaklsf", projectId, authn, DefaultBreakdownResultOutputTypes.toSet, AcceptAllCerts) } intercept[IllegalArgumentException] { new JerseyShrineClient(uri, null, authn, DefaultBreakdownResultOutputTypes.toSet, AcceptAllCerts) } intercept[IllegalArgumentException] { new JerseyShrineClient("aslkdfjaklsf", projectId, null, DefaultBreakdownResultOutputTypes.toSet, AcceptAllCerts) } } def testPerform { final case class Foo(x: String) { def toXml = { x } } import JerseyShrineClient._ implicit val fooDeserializer: Deserializer[Foo] = _ => xml => Try(new Foo((xml \ "x").text)) val value = "laskjdasjklfhkasf" - val client = new JerseyShrineClient(uri, projectId, authn, DefaultBreakdownResultOutputTypes.toSet, TestKeystore.trustParam) + val client = new JerseyShrineClient(uri, projectId, authn, DefaultBreakdownResultOutputTypes.toSet, NewTestKeyStore.trustParam) val unmarshalled: Foo = client.perform(true)(client.webResource, _ => Foo(value).toXml.toString) unmarshalled should not be (null) val Foo(unmarshalledValue) = unmarshalled unmarshalledValue should equal(value) } def testDeserializers { def doTestDeserializer[T <: ShrineResponse](response: T, deserialize: JerseyShrineClient.Deserializer[T]) { val roundTripped = deserialize(DefaultBreakdownResultOutputTypes.toSet)(response.toXml) roundTripped should equal(Success(response)) } val queryResult1 = QueryResult(1L, 456L, Some(ResultOutputType.PATIENT_COUNT_XML), 123L, None, None, None, QueryResult.StatusType.Finished, None) val queryResult2 = QueryResult(2L, 456L, Some(ResultOutputType.PATIENT_COUNT_XML), 123L, None, None, None, QueryResult.StatusType.Finished, None) import XmlDateHelper.now doTestDeserializer(AggregatedRunQueryResponse(123L, now, "userId", "groupId", QueryDefinition("foo", Term("bar")), 456L, Seq(queryResult1, queryResult2)), JerseyShrineClient.Deserializer.aggregatedRunQueryResponseDeserializer) doTestDeserializer(ReadApprovedQueryTopicsResponse(Seq(ApprovedTopic(123L, "asjkhjkas"))), JerseyShrineClient.Deserializer.readApprovedQueryTopicsResponseDeserializer) doTestDeserializer(ReadPreviousQueriesResponse(Seq(QueryMaster("queryMasterId", 12345L, "name", "userId", "groupId", XmlDateHelper.now, Some(false)))), JerseyShrineClient.Deserializer.readPreviousQueriesResponseDeserializer) doTestDeserializer(ReadQueryInstancesResponse(999L, "userId", "groupId", Seq.empty), JerseyShrineClient.Deserializer.readQueryInstancesResponseDeserializer) doTestDeserializer(AggregatedReadInstanceResultsResponse(1337L, Seq(dummyQueryResult(1337L))), JerseyShrineClient.Deserializer.aggregatedReadInstanceResultsResponseDeserializer) doTestDeserializer(AggregatedReadQueryResultResponse(1337L, Seq(dummyQueryResult(1337L))), JerseyShrineClient.Deserializer.aggregatedReadQueryResultResponseDeserializer) doTestDeserializer(ReadPdoResponse(Seq(EventResponse("event", "patient", None, None, Seq.empty)), Seq(PatientResponse("patientId", Seq(paramResponse))), Seq(ObservationResponse(None, "eventId", None, "patientId", None, None, None, "observerCode", "startDate", None, "valueTypeCode",None,None,None,None,None,None,None, Seq(paramResponse)))), JerseyShrineClient.Deserializer.readPdoResponseDeserializer) doTestDeserializer(ReadQueryDefinitionResponse(87456L, "name", "userId", now, ""), JerseyShrineClient.Deserializer.readQueryDefinitionResponseDeserializer) doTestDeserializer(DeleteQueryResponse(56834756L), JerseyShrineClient.Deserializer.deleteQueryResponseDeserializer) doTestDeserializer(RenameQueryResponse(56834756L, "some-name"), JerseyShrineClient.Deserializer.renameQueryResponseDeserializer) } import ResultOutputType._ private def dummyQueryResult(enclosingInstanceId: Long) = new QueryResult(123L, enclosingInstanceId, Some(PATIENT_COUNT_XML), 789L, None, None, Some("description"), QueryResult.StatusType.Finished, Some("statusMessage"), breakdowns = Map.empty) private def paramResponse: ParamResponse = { def randomString = java.util.UUID.randomUUID.toString ParamResponse(randomString, randomString, randomString) } } \ No newline at end of file diff --git a/commons/crypto/src/main/scala/net/shrine/crypto/KeyStoreDescriptorParser.scala b/commons/crypto/src/main/scala/net/shrine/crypto/KeyStoreDescriptorParser.scala index 6f85d50ea..fa0cdc1fd 100644 --- a/commons/crypto/src/main/scala/net/shrine/crypto/KeyStoreDescriptorParser.scala +++ b/commons/crypto/src/main/scala/net/shrine/crypto/KeyStoreDescriptorParser.scala @@ -1,50 +1,63 @@ package net.shrine.crypto import com.typesafe.config.{Config, ConfigValue, ConfigValueType} - import net.shrine.config.ConfigExtensions import net.shrine.log.Loggable +import net.shrine.util.{PeerToPeerModel, SingleHubModel, TrustModel} + import scala.collection.JavaConverters._ /** * @author clint * @since Dec 9, 2013 */ object KeyStoreDescriptorParser extends Loggable { object Keys { val file = "file" val password = "password" val privateKeyAlias = "privateKeyAlias" val keyStoreType = "keyStoreType" val caCertAliases = "caCertAliases" + val trustModel = "trustModelIsHub" } def apply(config: Config): KeyStoreDescriptor = { import Keys._ def getKeyStoreType: KeyStoreType = { val typeOption = config.getOption(keyStoreType,_.getString) typeOption.flatMap(KeyStoreType.valueOf).getOrElse { info(s"Unknown keystore type '${typeOption.getOrElse("")}', allowed types are ${KeyStoreType.JKS.name} and ${KeyStoreType.PKCS12.name}") KeyStoreType.Default } } + + def getTrustModel: TrustModel = + if (config.hasPath(trustModel) && !config.getBoolean(trustModel)) + PeerToPeerModel + else if (config.hasPath(trustModel)) + SingleHubModel + else { + info(s"No Trust Model specified for this network configuration, assuming that a Hub configuration is being used") + SingleHubModel + } def getCaCertAliases: Seq[String] = { def isString(cv: ConfigValue) = cv.valueType == ConfigValueType.STRING config.getOption(caCertAliases,_.getList).fold(Seq.empty[ConfigValue])(list => list.asScala).collect{ case cv if isString(cv) => cv.unwrapped.toString } } KeyStoreDescriptor( config.getString(file), config.getString(password), config.getOption(privateKeyAlias,_.getString), getCaCertAliases, - getKeyStoreType) + getKeyStoreType, + getTrustModel) } } \ No newline at end of file diff --git a/commons/crypto/src/main/scala/net/shrine/crypto/TrustParam.scala b/commons/crypto/src/main/scala/net/shrine/crypto/TrustParam.scala index b6c67aabc..2b8d926a4 100644 --- a/commons/crypto/src/main/scala/net/shrine/crypto/TrustParam.scala +++ b/commons/crypto/src/main/scala/net/shrine/crypto/TrustParam.scala @@ -1,17 +1,21 @@ package net.shrine.crypto +import net.shrine.crypto2.BouncyKeyStoreCollection + /** * @author clint * @date Nov 22, 2013 */ sealed trait TrustParam object TrustParam { //NB: For Spring @Deprecated def forKeyStore(certs: KeyStoreCertCollection): SomeKeyStore = SomeKeyStore(certs) case object AcceptAllCerts extends TrustParam final case class SomeKeyStore(certs: KeyStoreCertCollection) extends TrustParam + + final case class BouncyKeyStore(certs: BouncyKeyStoreCollection) extends TrustParam } \ No newline at end of file diff --git a/commons/crypto/src/main/scala/net/shrine/crypto2/BouncyKeyStoreCollection.scala b/commons/crypto/src/main/scala/net/shrine/crypto2/BouncyKeyStoreCollection.scala index 4a4356f86..0e14ffb8c 100644 --- a/commons/crypto/src/main/scala/net/shrine/crypto2/BouncyKeyStoreCollection.scala +++ b/commons/crypto/src/main/scala/net/shrine/crypto2/BouncyKeyStoreCollection.scala @@ -1,122 +1,135 @@ package net.shrine.crypto2 import java.io.{File, FileInputStream} import java.math.BigInteger import java.security.cert.X509Certificate import java.security.{KeyStore, PrivateKey, Security} import java.time.Instant import java.util.Date import javax.xml.datatype.XMLGregorianCalendar import net.shrine.crypto._ import net.shrine.log.Loggable import net.shrine.protocol.{BroadcastMessage, CertId, Signature} import net.shrine.util._ import org.bouncycastle.jce.provider.BouncyCastleProvider import scala.concurrent.duration.Duration /** * Created by ty on 10/25/16. * * Rewrite of [[net.shrine.crypto.CertCollection]]. Abstracts away the need to track down * all the corresponding pieces of a KeyStore entry by collecting them into a collection * of [[KeyStoreEntry]]s. * See: [[HubCertCollection]], [[PeerCertCollection]], [[CertCollectionAdapter]] */ trait BouncyKeyStoreCollection extends Loggable { val myEntry: KeyStoreEntry def signBytes(bytesToSign: Array[Byte]): Array[Byte] = myEntry.sign(bytesToSign).getOrElse(CryptoErrors.noKeyError(myEntry)) def verifyBytes(signedBytes: Array[Byte], signatureBytes: Array[Byte]): Boolean def allEntries: Iterable[KeyStoreEntry] + + def keyStore: KeyStore = BouncyKeyStoreCollection.keyStore.getOrElse(throw new IllegalStateException("Accessing keyStore without loading from keyStore file first!")) + + def descriptor: KeyStoreDescriptor = BouncyKeyStoreCollection.descriptor.getOrElse(throw new IllegalStateException("Accessing keyStoreDescriptor without loading from keyStore file first!")) } /** * Factory object that reads the correct cert collection from the file. */ object BouncyKeyStoreCollection extends Loggable { import scala.collection.JavaConversions._ import CryptoErrors._ Security.addProvider(new BouncyCastleProvider()) + var descriptor: Option[KeyStoreDescriptor] = None + var keyStore: Option[KeyStore] = None // On failure creates a problem so it gets logged into the database. type EitherCertError = Either[ImproperlyConfiguredKeyStoreProblem, BouncyKeyStoreCollection] /** * Creates a cert collection from a keyStore. Returns an Either to abstract away * try catches/problem construction until the end. * @return [[EitherCertError]] */ def createCertCollection(keyStore: KeyStore, descriptor: KeyStoreDescriptor): EitherCertError = { // Read all of the KeyStore entries from the file into a KeyStore Entry val values = keyStore.aliases().map(alias => (alias, keyStore.getCertificate(alias), Option(keyStore.getKey(alias, descriptor.password.toCharArray).asInstanceOf[PrivateKey]))) val entries = values.map(value => KeyStoreEntry(value._2.asInstanceOf[X509Certificate], NonEmptySeq(value._1, Nil), value._3)).toSet if (entries.exists(_.isExpired())) Left(configureError(ExpiredCertificates(entries.filter(_.isExpired())))) else descriptor.trustModel match { - case PeerToPeerModel => createPeerCertCollection(entries, descriptor) - case SingleHubModel => createHubCertCollection(entries) + case PeerToPeerModel => createPeerCertCollection(entries, descriptor, keyStore) + case SingleHubModel => createHubCertCollection(entries, descriptor, keyStore) } } /** * @return a [[scala.util.Left]] if we can't find or disambiguate a [[PrivateKey]], * otherwise return [[scala.util.Right]] that contains correct [[PeerCertCollection]] */ - def createPeerCertCollection(entries: Set[KeyStoreEntry], descriptor: KeyStoreDescriptor): EitherCertError = { + def createPeerCertCollection(entries: Set[KeyStoreEntry], descriptor: KeyStoreDescriptor, keyStore: KeyStore): + EitherCertError = + { if (descriptor.caCertAliases.nonEmpty) warn(s"Specifying caCertAliases in a PeerToPeer network is useless, certs found: `${descriptor.caCertAliases}`") (descriptor.privateKeyAlias, entries.filter(_.privateKey.isDefined)) match { case (_, empty) if empty.isEmpty => Left(configureError(NoPrivateKeyInStore)) case (None, keys) if keys.size == 1 => warn(s"No private key specified, using the only entry with a private key: `${keys.head.aliases.first}`") Right(PeerCertCollection(keys.head, entries -- keys)) case (None, keys) => Left(configureError(TooManyPrivateKeys(entries))) case (Some(alias), keys) if keys.exists(_.aliases.contains(alias)) => val privateKeyEntry = keys.find(_.aliases.contains(alias)).get Right(PeerCertCollection(privateKeyEntry, entries - privateKeyEntry)) case (Some(alias), keys) => Left(configureError(CouldNotFindAlias(alias))) } } - def createHubCertCollection(entries: Set[KeyStoreEntry]): EitherCertError = { + def createHubCertCollection(entries: Set[KeyStoreEntry], descriptor: KeyStoreDescriptor, keyStore: KeyStore): + EitherCertError = + { if (entries.size != 2) Left(configureError(RequiresExactlyTwoEntries(entries))) else if (entries.count(_.privateKey.isDefined) != 1) Left(configureError(RequiresExactlyOnePrivateKey(entries.filter(_.privateKey.isDefined)))) else { val partition = entries.partition(_.privateKey.isDefined) val privateEntry = partition._1.head val caEntry = partition._2.head if (privateEntry.wasSignedBy(caEntry)) Right(HubCertCollection(privateEntry, caEntry)) else Left(configureError(NotSignedByCa(privateEntry, caEntry))) } } //TODO: Move fromStreamHelper to crypto2 def fromFileRecoverWithClassPath(descriptor: KeyStoreDescriptor): BouncyKeyStoreCollection = { val keyStore = if (new File(descriptor.file).exists) KeyStoreCertCollection.fromStreamHelper(descriptor, new FileInputStream(_)) else KeyStoreCertCollection.fromStreamHelper(descriptor, getClass.getClassLoader.getResourceAsStream(_)) + BouncyKeyStoreCollection.keyStore = Some(keyStore) + BouncyKeyStoreCollection.descriptor = Some(descriptor) + createCertCollection(keyStore, descriptor) .fold(problem => throw problem.throwable.get, identity) } } \ No newline at end of file diff --git a/commons/crypto/src/main/scala/net/shrine/crypto2/CertCollectionAdapter.scala b/commons/crypto/src/main/scala/net/shrine/crypto2/CertCollectionAdapter.scala index bcbfb0d37..25673954e 100644 --- a/commons/crypto/src/main/scala/net/shrine/crypto2/CertCollectionAdapter.scala +++ b/commons/crypto/src/main/scala/net/shrine/crypto2/CertCollectionAdapter.scala @@ -1,55 +1,55 @@ package net.shrine.crypto2 -import java.security.Principal +import java.security.{KeyStore, Principal} import java.security.cert.X509Certificate -import net.shrine.crypto.{CertCollection, KeyPair} +import net.shrine.crypto.{CertCollection, KeyPair, KeyStoreDescriptor} import net.shrine.protocol.CertId /** * Allows gradual replacement of the old crypto package by keeping the old * interface for now */ final case class CertCollectionAdapter(keyStoreCollection: BouncyKeyStoreCollection) extends BouncyKeyStoreCollection with CertCollection { override def signBytes(bytesToSign: Array[Byte]): Array[Byte] = keyStoreCollection.signBytes(bytesToSign) override def verifyBytes(signedBytes: Array[Byte], signatureBytes: Array[Byte]): Boolean = keyStoreCollection.verifyBytes(signedBytes, signatureBytes) override val myEntry: KeyStoreEntry = keyStoreCollection.myEntry override def allEntries: Iterable[KeyStoreEntry] = keyStoreCollection.allEntries override def myCertId: Option[CertId] = Some(entryToCertId(myEntry)) override def myCert: Option[X509Certificate] = Some(myEntry.cert) override def caCertAliases: Seq[String] = caEntry.aliases override def caCerts: Map[Principal, X509Certificate] = Map(caEntry.cert.getIssuerDN -> caEntry.cert) override def myKeyPair: KeyPair = KeyPair(myEntry.publicKey, myEntry.privateKey.get) override def get(id: CertId): Option[X509Certificate] = certIdsToCerts.get(id) override def iterator: Iterator[X509Certificate] = keyStoreCollection.allEntries.map(_.cert).iterator override def ids: Iterable[CertId] = allEntries.filterNot(_ == caEntry).map(entryToCertId) override def caIds: Iterable[CertId] = Seq(entryToCertId(caEntry)) // CertIds are just the serial number along with the alias private def entryToCertId(keyStoreEntry: KeyStoreEntry): CertId = CertId(keyStoreEntry.certificateHolder.getSerialNumber, Some(keyStoreEntry.aliases.first)) private val certIdsToCerts: Map[CertId, X509Certificate] = keyStoreCollection.allEntries.map(entry => entryToCertId(entry) -> entry.cert).toMap // The CertCollection doesn't really account for PeerToPeer networks, so we slightly ignore that too private val caEntry: KeyStoreEntry = keyStoreCollection match { - case HubCertCollection(privateEntry, ca) => ca - case PeerCertCollection(privateEntry, otherEntries) => privateEntry + case HubCertCollection(_, ca) => ca + case PeerCertCollection(privateEntry, _) => privateEntry } } diff --git a/commons/crypto/src/main/scala/net/shrine/crypto2/HubCertCollection.scala b/commons/crypto/src/main/scala/net/shrine/crypto2/HubCertCollection.scala index f9d820df1..adafd9994 100644 --- a/commons/crypto/src/main/scala/net/shrine/crypto2/HubCertCollection.scala +++ b/commons/crypto/src/main/scala/net/shrine/crypto2/HubCertCollection.scala @@ -1,14 +1,14 @@ package net.shrine.crypto2 /** * Created by ty on 10/25/16. */ case class HubCertCollection(override val myEntry: KeyStoreEntry, caEntry: KeyStoreEntry) extends BouncyKeyStoreCollection { override val allEntries: Iterable[KeyStoreEntry] = myEntry +: caEntry +: Nil /** * The only valid messages for a downstream node are those that come from the CA */ - override def verifyBytes(signedBytes:Array[Byte], signatureBytes:Array[Byte]) = {caEntry.verify(signedBytes, signatureBytes)} + override def verifyBytes(signedBytes:Array[Byte], signatureBytes:Array[Byte]) = caEntry.verify(signedBytes, signatureBytes) } \ No newline at end of file diff --git a/commons/crypto/src/main/scala/net/shrine/crypto2/KeyStoreEntry.scala b/commons/crypto/src/main/scala/net/shrine/crypto2/KeyStoreEntry.scala index 3e3a4420f..9f06f782a 100644 --- a/commons/crypto/src/main/scala/net/shrine/crypto2/KeyStoreEntry.scala +++ b/commons/crypto/src/main/scala/net/shrine/crypto2/KeyStoreEntry.scala @@ -1,71 +1,97 @@ package net.shrine.crypto2 import java.security.cert.X509Certificate import java.security._ import java.time.{Clock, Instant} import java.util.Date import net.shrine.crypto.UtilHasher import net.shrine.util.NonEmptySeq +import org.bouncycastle.asn1.x500.style.{BCStyle, IETFUtils} import org.bouncycastle.asn1.x509.AlgorithmIdentifier +import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder import org.bouncycastle.cms._ -import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder +import org.bouncycastle.cms.jcajce.{JcaSignerInfoGeneratorBuilder, JcaSimpleSignerInfoVerifierBuilder} import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.operator.ContentSigner import org.bouncycastle.operator.bc.BcDigestCalculatorProvider -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder +import org.bouncycastle.operator.jcajce.{JcaContentSignerBuilder, JcaContentVerifierProviderBuilder, JcaDigestCalculatorProviderBuilder} +import org.bouncycastle.util.{Selector, Store} import scala.util.Try /** * Created by ty on 10/26/16. * Represents a single entry in a key store collection. As a key entry may be either a PrivateKey Entry * or a TrustedCert Entry, there's no guarantee that there is a privateKey available * * @param cert: The x509 certificate in the entry * @param aliases: The alias of the certificate in the keystore * @param privateKey: The private key of the certificate, which is only available if this keystore represents * a private key entry (i.e., do we own this certificate?) */ final case class KeyStoreEntry(cert: X509Certificate, aliases: NonEmptySeq[String], privateKey: Option[PrivateKey]) { val publicKey:PublicKey = cert.getPublicKey val certificateHolder = new JcaX509CertificateHolder(cert) // Helpful methods are defined in the cert holder. val isSelfSigned: Boolean = certificateHolder.getSubject == certificateHolder.getIssuer // May or may not be a CA val formattedSha256Hash: String = UtilHasher.encodeCert(cert, "SHA-256") + + val commonName: Option[String] = for { // Who doesn't put CNs on their certs, I mean really + rdn <- certificateHolder.getSubject.getRDNs(BCStyle.CN).headOption + cn <- Option(rdn.getFirst) + } yield IETFUtils.valueToString(cn.getValue) + + +// certificateHolder.getSubject.getRDNs(BCStyle.CN).headOption.flatMap(rdn => +// Option(rdn.getFirst).map(cn => IETFUtils.valueToString(cn.getValue))) + private val provider = new BouncyCastleProvider() def verify(signedBytes: Array[Byte], originalMessage: Array[Byte]): Boolean = { import scala.collection.JavaConversions._ // Treat Java Iterable as Scala Iterable - val signers: SignerInformationStore = new CMSSignedData(signedBytes).getSignerInfos - val verifier = new JcaSimpleSignerInfoVerifierBuilder().setProvider(provider).build(cert) + val parser = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(provider).build(), signedBytes) + parser.getSignedContent.drain() - signers.headOption.exists(signerInfo => signerInfo.verify(verifier)) // We don't attach multiple signers to a cert + val maybeResult = for { + signerInfo <- parser.getSignerInfos.headOption + certHolder <- parser.getCertificates.asInstanceOf[Store[X509CertificateHolder]].getMatches(new Selector[X509CertificateHolder] { + override def `match`(x: X509CertificateHolder): Boolean = true + }).headOption + verifier = new JcaContentVerifierProviderBuilder().setProvider(provider).build(certificateHolder) + } yield certHolder.isSignatureValid(verifier) + + maybeResult.exists(identity) } /** * Provided that this is a PrivateKey Entry, sign the incoming bytes. * @return Returns None if this is not a PrivateKey Entry */ def sign(bytesToSign: Array[Byte]): Option[Array[Byte]] = { privateKey.map(key => { - val signature = Signature.getInstance("SHA256withRSA", provider) - signature.initSign(key) - signature.update(bytesToSign) - - val sigGen = new JcaContentSignerBuilder("SHA256withRSA").setProvider(provider).build(key) - val cms = new CMSSignedDataGenerator() + val gen = new CMSSignedDataGenerator() + val contentSigner: ContentSigner = new JcaContentSignerBuilder("SHA256withRSA").setProvider(provider).build(key) + val builder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(provider).build) + contentSigner.getOutputStream.write(bytesToSign) + contentSigner.getOutputStream.flush() + val msg = new CMSProcessableByteArray(contentSigner.getSignature) + builder.setDirectSignature(true) + gen.addSignerInfoGenerator(builder.build(contentSigner, cert)) + gen.addCertificate(certificateHolder) + gen.generate(msg, true).getEncoded - cms.addSignerInfoGenerator( - new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider()) - .build(sigGen, certificateHolder)) - cms.generate(new CMSProcessableByteArray(signature.sign), true).getEncoded // true means to envelop the signature in the given data }) } - def wasSignedBy(entry: KeyStoreEntry): Boolean = Try(cert.verify(entry.publicKey)).isSuccess + def wasSignedBy(entry: KeyStoreEntry): Boolean = wasSignedBy(entry.publicKey) + + def wasSignedBy(publicKey: PublicKey): Boolean = certificateHolder.isSignatureValid( + new JcaContentVerifierProviderBuilder().setProvider("BC").build(publicKey) + ) def isExpired(clock: Clock = Clock.systemDefaultZone()): Boolean = { certificateHolder.getNotAfter.before(Date.from(Instant.now(clock))) } } \ No newline at end of file diff --git a/commons/crypto/src/main/scala/net/shrine/crypto2/PeerCertCollection.scala b/commons/crypto/src/main/scala/net/shrine/crypto2/PeerCertCollection.scala index f8b608b23..f23e48612 100644 --- a/commons/crypto/src/main/scala/net/shrine/crypto2/PeerCertCollection.scala +++ b/commons/crypto/src/main/scala/net/shrine/crypto2/PeerCertCollection.scala @@ -1,19 +1,13 @@ package net.shrine.crypto2 -import net.shrine.crypto.SigningCertStrategy -import net.shrine.log.Loggable -import net.shrine.protocol.BroadcastMessage - -import scala.concurrent.duration.Duration - /** * Created by ty on 10/27/16. */ case class PeerCertCollection(override val myEntry: KeyStoreEntry, entries: Set[KeyStoreEntry]) extends BouncyKeyStoreCollection { def verifyBytes(signedBytes: Array[Byte], signatureBytes: Array[Byte]): Boolean = { (entries + myEntry).exists(_.verify(signedBytes, signatureBytes)) } override val allEntries: Iterable[KeyStoreEntry] = entries + myEntry } diff --git a/commons/crypto/src/main/scala/net/shrine/crypto2/SignerVerifierAdapter.scala b/commons/crypto/src/main/scala/net/shrine/crypto2/SignerVerifierAdapter.scala index 06ddc1348..e297f8ff1 100644 --- a/commons/crypto/src/main/scala/net/shrine/crypto2/SignerVerifierAdapter.scala +++ b/commons/crypto/src/main/scala/net/shrine/crypto2/SignerVerifierAdapter.scala @@ -1,74 +1,81 @@ package net.shrine.crypto2 import java.math.BigInteger import javax.xml.datatype.XMLGregorianCalendar import net.shrine.crypto.{Signer, SigningCertStrategy, Verifier} import net.shrine.protocol.{BroadcastMessage, CertId, Signature} import net.shrine.util.{XmlDateHelper, XmlGcEnrichments} import scala.concurrent.duration.Duration /** * An adapter object so that the new crypto package can coexist with the * existing Signer and Verifier interfaces * @param keyStoreCollection The BouncyKeyStoreCollection that is signing * and verifying broadcast messages */ case class SignerVerifierAdapter(keyStoreCollection: BouncyKeyStoreCollection) extends BouncyKeyStoreCollection with Signer with Verifier { override def signBytes(bytesToSign: Array[Byte]): Array[Byte] = keyStoreCollection.signBytes(bytesToSign) override def verifyBytes(signedBytes: Array[Byte], signatureBytes: Array[Byte]): Boolean = keyStoreCollection.verifyBytes(signedBytes, signatureBytes) override val myEntry: KeyStoreEntry = keyStoreCollection.myEntry override def allEntries: Iterable[KeyStoreEntry] = keyStoreCollection.allEntries override def sign(message: BroadcastMessage, signingCertStrategy: SigningCertStrategy): BroadcastMessage = { + val certAdapter = CertCollectionAdapter(keyStoreCollection) val timeStamp = XmlDateHelper.now - val dummyCertId = CertId(BigInteger.valueOf(10l), None) + val dummyCertId = certAdapter.myCertId.get val signedBytes = signBytes(toBytes(message, timeStamp)) val sig = Signature(timeStamp, dummyCertId, None, signedBytes) message.withSignature(sig) } override def verifySig(message: BroadcastMessage, maxSignatureAge: Duration): Boolean = { val logSigFailure = (b:Boolean) => { if (!b) { UnknownSignatureProblem(message) warn(s"Error verifying signature for message with id '${message.requestId}'") } b } message.signature.exists(sig => - notTooOld(sig, maxSignatureAge, message) && logSigFailure(verifyBytes(toBytes(message, sig.timestamp), sig.value.array)) + { + val notTooOl = notTooOld(sig, maxSignatureAge, message) + val verify = verifyBytes(sig.value.array, toBytes(message, sig.timestamp)) + println(s"\n notTooOld: $notTooOl\n") + println(s"\n verify: $verify\n") + notTooOl && logSigFailure(verify) + } ) } // Has the signature expired? private def notTooOld(sig: Signature, maxSignatureAge: Duration, message: BroadcastMessage): Boolean = { import XmlGcEnrichments._ val sigValidityEndTime: XMLGregorianCalendar = sig.timestamp + maxSignatureAge val now = XmlDateHelper.now val timeout = sigValidityEndTime > now - if (timeout) warn(s"Could not validate message with id '${message.requestId}' due to " + + if (!timeout) warn(s"Could not validate message with id '${message.requestId}' due to " + s"exceeding max timeout of $maxSignatureAge") timeout } // Concatenates with the timestamp. This is how it's converted to bytes in the // the DefaultSignerVerifier, but now that we're using CMS I don't think this is necessary // anymore. It was only done before to ensure unique signatures, I believe. private def toBytes(message: BroadcastMessage, timestamp: XMLGregorianCalendar): Array[Byte] = { val messageXml = message.copy(signature = None).toXmlString val timestampXml = timestamp.toXMLFormat (messageXml + timestampXml).getBytes("UTF-8") } } \ No newline at end of file diff --git a/commons/crypto/src/test/scala/net/shrine/crypto/CertCollectionTest.scala b/commons/crypto/src/test/scala/net/shrine/crypto/CertCollectionTest.scala index 31ce2ac47..e33db6219 100644 --- a/commons/crypto/src/test/scala/net/shrine/crypto/CertCollectionTest.scala +++ b/commons/crypto/src/test/scala/net/shrine/crypto/CertCollectionTest.scala @@ -1,18 +1,18 @@ package net.shrine.crypto import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test /** * @author clint * @date Jan 15, 2015 */ final class CertCollectionTest extends ShouldMatchersForJUnit { @Test def testGetIssuer: Unit = { - val cert = TestKeystore.certCollection.myCert.get + val cert = NewTestKeyStore.certCollection.myEntry.cert CertCollection.getIssuer(cert) should equal(cert.getIssuerX500Principal) CertCollection.getIssuer(cert) should not equal(cert.getIssuerDN) } } \ No newline at end of file diff --git a/commons/crypto/src/test/scala/net/shrine/crypto/CertDataEncodingTest.scala b/commons/crypto/src/test/scala/net/shrine/crypto/CertDataEncodingTest.scala index 8ea743941..d0849ba1d 100644 --- a/commons/crypto/src/test/scala/net/shrine/crypto/CertDataEncodingTest.scala +++ b/commons/crypto/src/test/scala/net/shrine/crypto/CertDataEncodingTest.scala @@ -1,23 +1,23 @@ package net.shrine.crypto import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test import net.shrine.protocol.CertData import java.security.cert.Certificate /** * @author clint * @date Dec 5, 2014 */ final class CertDataEncodingTest extends ShouldMatchersForJUnit { @Test def testToCertificateKey: Unit = { - import TestKeystore.certCollection + import NewTestKeyStore.certCollection - val cert = certCollection.myCert.get + val cert = certCollection.myEntry.cert val certData = CertData(cert) certData.toCertificate should equal(cert) } } \ No newline at end of file diff --git a/commons/crypto/src/test/scala/net/shrine/crypto/DefaultSignerVerifierTest.scala b/commons/crypto/src/test/scala/net/shrine/crypto/DefaultSignerVerifierTest.scala index 0d2fc4df3..878a992fc 100644 --- a/commons/crypto/src/test/scala/net/shrine/crypto/DefaultSignerVerifierTest.scala +++ b/commons/crypto/src/test/scala/net/shrine/crypto/DefaultSignerVerifierTest.scala @@ -1,535 +1,538 @@ package net.shrine.crypto import net.shrine.util.{Base64, ShouldMatchersForJUnit, XmlGcEnrichments} import org.junit.Test import net.shrine.protocol.BroadcastMessage import net.shrine.protocol.AuthenticationInfo import net.shrine.protocol.Credential import net.shrine.protocol.DeleteQueryRequest import net.shrine.protocol.RunQueryRequest import net.shrine.protocol.ResultOutputType import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.query.Term import net.shrine.protocol.query.Modifiers import net.shrine.protocol.query.Or import net.shrine.protocol.ReadQueryResultRequest import net.shrine.protocol.DefaultBreakdownResultOutputTypes import net.shrine.protocol.query.Constrained import net.shrine.protocol.query.ValueConstraint import net.shrine.protocol.CertId import java.math.BigInteger import java.security.cert.X509Certificate import java.security.cert.CertificateFactory + import scala.io.Source import java.io.ByteArrayInputStream +import net.shrine.crypto2.SignerVerifierAdapter + /** * @author clint * @since Nov 27, 2013 */ final class DefaultSignerVerifierTest extends ShouldMatchersForJUnit { private val authn = AuthenticationInfo("some-domain", "some-username", Credential("sadkljlajdl", isToken = false)) - private val certCollection = TestKeystore.certCollection + private val certCollection = OldTestKeyStore.certCollection private val signerVerifier = new DefaultSignerVerifier(certCollection) import SigningCertStrategy._ import scala.concurrent.duration._ @Test def testIssuersMatchBetweenCertsWithIPsInDistinguishedNames(): Unit = { def readCert(fileName: String): X509Certificate = { val factory = CertificateFactory.getInstance("X.509") val source = Source.fromInputStream(getClass.getClassLoader.getResourceAsStream(fileName)) val encodedCertData = try { source.mkString } finally { source.close() } val byteStream = new ByteArrayInputStream(Base64.fromBase64(encodedCertData)) try { factory.generateCertificate(byteStream).asInstanceOf[X509Certificate] } finally { byteStream.close() } } val ca = readCert("test-caroot.pem") val alpha = readCert("test-alpha-signed.pem") val beta = readCert("test-beta-signed.pem") val gamma = readCert("test-gamma-signed.pem") def shouldMatch[F](field: X509Certificate => F)(a: X509Certificate, b: X509Certificate) { field(a) should equal(field(b)) //Use options to handle null fields Option(field(a)).map(_.hashCode) should equal(Option(field(b)).map(_.hashCode)) } shouldMatch(_.getIssuerDN)(ca, alpha) shouldMatch(_.getIssuerDN)(ca, beta) shouldMatch(_.getIssuerDN)(ca, gamma) shouldMatch(_.getIssuerX500Principal)(ca, alpha) shouldMatch(_.getIssuerX500Principal)(ca, beta) shouldMatch(_.getIssuerX500Principal)(ca, gamma) shouldMatch(_.getIssuerUniqueID)(ca, alpha) shouldMatch(_.getIssuerUniqueID)(ca, beta) shouldMatch(_.getIssuerUniqueID)(ca, gamma) ca.getSerialNumber should not equal(alpha.getSerialNumber) ca.getSerialNumber should not equal(beta.getSerialNumber) ca.getSerialNumber should not equal(gamma.getSerialNumber) } @Test def testSigningAndVerificationQueryDefWithSubQueries(): Unit = { //A failing case reported by Ben C. val queryDef = QueryDefinition.fromI2b2 { (t) (493.90) Asthma(250.00) Diabet@15:32:58 ANY 0 Event 1 STARTDATE FIRST LESS Event 2 STARTDATE FIRST GREATEREQUAL 365 DAY Event 1 EVENT Event 1 SAMEINSTANCENUM 0 1 100 0 SAMEINSTANCENUM 1 6 (493.90) Asthma, unspecified type, unspecified \\SHRINE\SHRINE\Diagnoses\Diseases of the respiratory system (460-519.99)\Chronic obstructive pulmonary disease and allied conditions (490-496.99)\Asthma (493)\Asthma, unspecified (493.9)\(493.90) Asthma, unspecified type, unspecified\ Diagnoses\Diseases of the respiratory system (460-519.99)\Chronic obstructive pulmonary disease and allied conditions (490-496.99)\Asthma (493)\Asthma, unspecified (493.9)\(493.90) Asthma, unspecified type, unspecified\ ENC LA false 6 (493.91) Asthma, unspecified type, with status asthmaticus \\SHRINE\SHRINE\Diagnoses\Diseases of the respiratory system (460-519.99)\Chronic obstructive pulmonary disease and allied conditions (490-496.99)\Asthma (493)\Asthma, unspecified (493.9)\(493.91) Asthma, unspecified type, with status asthmaticus\ Diagnoses\Diseases of the respiratory system (460-519.99)\Chronic obstructive pulmonary disease and allied conditions (490-496.99)\Asthma (493)\Asthma, unspecified (493.9)\(493.91) Asthma, unspecified type, with status asthmaticus\ ENC LA false 6 (493.92) Asthma, unspecified type, with (acute) exacerbation \\SHRINE\SHRINE\Diagnoses\Diseases of the respiratory system (460-519.99)\Chronic obstructive pulmonary disease and allied conditions (490-496.99)\Asthma (493)\Asthma, unspecified (493.9)\(493.92) Asthma, unspecified type, with (acute) exacerbation\ Diagnoses\Diseases of the respiratory system (460-519.99)\Chronic obstructive pulmonary disease and allied conditions (490-496.99)\Asthma (493)\Asthma, unspecified (493.9)\(493.92) Asthma, unspecified type, with (acute) exacerbation\ ENC LA false Event 2 EVENT Event 2 SAMEINSTANCENUM 0 1 100 0 SAMEINSTANCENUM 1 6 (250.00) Diabetes mellitus without mention of complication, type II or unspecified type, not stated as uncontrolled \\SHRINE\SHRINE\Diagnoses\Endocrine, nutritional and metabolic diseases, and immunity disorders (240-279.99)\Diseases of other endocrine glands (249-259.99)\Diabetes mellitus (250)\Diabetes mellitus without mention of complication (250.0)\(250.00) Diabetes mellitus without mention of complication, type II or unspecified type, not stated as uncontrolled\ Diagnoses\Endocrine, nutritional and metabolic diseases, and immunity disorders (240-279.99)\Diseases of other endocrine glands (249-259.99)\Diabetes mellitus (250)\Diabetes mellitus without mention of complication (250.0)\(250.00) Diabetes mellitus without mention of complication, type II or unspecified type, not stated as uncontrolled\ ENC LA false 6 (530.81) Esophageal reflux \\SHRINE\SHRINE\Diagnoses\Diseases of the digestive system (520-579.99)\Diseases of esophagus, stomach, and duodenum (530-539.99)\Diseases of esophagus (530)\Other specified disorders of esophagus (530.8)\(530.81) Esophageal reflux\ Diagnoses\Diseases of the digestive system (520-579.99)\Diseases of esophagus, stomach, and duodenum (530-539.99)\Diseases of esophagus (530)\Other specified disorders of esophagus (530.8)\(530.81) Esophageal reflux\ ENC LA false }.get def shouldVerify(signingCertStrategy: SigningCertStrategy): Unit = { val resultTypes = DefaultBreakdownResultOutputTypes.toSet + ResultOutputType.PATIENT_COUNT_XML val unsignedMessage = BroadcastMessage(authn, RunQueryRequest("some-project-id", 12345.milliseconds, authn, Some("topic-id"), Some("Topic Name"), resultTypes, queryDef)) val signedMessage = signerVerifier.sign(unsignedMessage, signingCertStrategy) signerVerifier.verifySig(signedMessage, 1.hour) should be(right = true) //NB: Simulate going from one machine to another val roundTripped = xmlRoundTrip(signedMessage) roundTripped.signature should equal(signedMessage.signature) signerVerifier.verifySig(roundTripped, 1.hour) should be(right = true) } //shouldVerify(Attach) shouldVerify(DontAttach) } //See https://open.med.harvard.edu/jira/browse/SHRINE-859 @Test def testSigningAndVerificationQueryNameWithSpaces(): Unit = { def shouldVerify(queryName: String, signingCertStrategy: SigningCertStrategy): Unit = { val queryDef = QueryDefinition(queryName, Term("""\\PCORNET\PCORI\DEMOGRAPHIC\Age\>= 65 years old\65\""")) val resultTypes = DefaultBreakdownResultOutputTypes.toSet + ResultOutputType.PATIENT_COUNT_XML val unsignedMessage = BroadcastMessage(authn, RunQueryRequest("some-project-id", 12345.milliseconds, authn, Some("topic-id"), Some("Topic Name"), resultTypes, queryDef)) val signedMessage = signerVerifier.sign(unsignedMessage, signingCertStrategy) signerVerifier.verifySig(signedMessage, 1.hour) should be(right = true) //NB: Simulate going from one machine to another val roundTripped = xmlRoundTrip(signedMessage) roundTripped.signature should equal(signedMessage.signature) signerVerifier.verifySig(roundTripped, 1.hour) should be(right = true) } shouldVerify("foo", Attach) shouldVerify(" foo", Attach) shouldVerify("foo ", Attach) shouldVerify("fo o", Attach) shouldVerify(" 65 years old@13:23:00", Attach) shouldVerify("foo", DontAttach) shouldVerify(" foo", DontAttach) shouldVerify("foo ", DontAttach) shouldVerify("fo o", DontAttach) shouldVerify(" 65 years old@13:23:00", DontAttach) } @Test def testSigningAndVerification(): Unit = doTestSigningAndVerification(signerVerifier) @Test def testSigningAndVerificationAttachedKnownCertNotSignedByCA(): Unit = { //Messages will be signed with a key that's in our keystore, but is not signed by a CA val descriptor = KeyStoreDescriptor( "shrine.keystore.multiple-private-keys", "chiptesting", Some("private-key-2"), Seq("carra ca"), KeyStoreType.JKS) val mySignerVerifier = new DefaultSignerVerifier(KeyStoreCertCollection.fromClassPathResource(descriptor)) val unsignedMessage = BroadcastMessage(authn, DeleteQueryRequest("some-project-id", 12345.milliseconds, authn, 87356L)) val signedMessage = mySignerVerifier.sign(unsignedMessage, Attach) mySignerVerifier.verifySig(signedMessage, 1.hour) should be(right = false) } - @Test + //TODO: RESTORE THIS TEST. ISSUE IS THAT I NEED TO GENERATE SEVERAL TEST KEYSTORES. @Test def testSigningAndVerificationAttachedUnknownCertNotSignedByCA(): Unit = { //Messages will be signed with a key that's NOT in our keystore, but is not signed by a CA val signerDescriptor = KeyStoreDescriptor( "shrine.keystore.multiple-private-keys", "chiptesting", Some("private-key-2"), //This cert is NOT in TestKeystore.certCollection Seq("carra ca"), KeyStoreType.JKS) - val signer: Signer = new DefaultSignerVerifier(KeyStoreCertCollection.fromClassPathResource(signerDescriptor)) + val signer: Signer = SignerVerifierAdapter(NewTestKeyStore.certCollection) - val verifier: Verifier = new DefaultSignerVerifier(TestKeystore.certCollection) + val verifier: Verifier = SignerVerifierAdapter(NewTestKeyStore.certCollection) val unsignedMessage = BroadcastMessage(authn, DeleteQueryRequest("some-project-id", 12345.milliseconds, authn, 87356L)) val signedMessage = signer.sign(unsignedMessage, Attach) verifier.verifySig(signedMessage, 1.hour) should be(right = false) } private def doTestSigningAndVerification(signerVerifier: Signer with Verifier): Unit = { def doTest(signingCertStrategy: SigningCertStrategy) { val unsignedMessage = BroadcastMessage(authn, DeleteQueryRequest("some-project-id", 12345.milliseconds, authn, 87356L)) val signedMessage = signerVerifier.sign(unsignedMessage, signingCertStrategy) (unsignedMessage eq signedMessage) should be(right = false) unsignedMessage should not equal (signedMessage) signedMessage.networkAuthn should equal(unsignedMessage.networkAuthn) signedMessage.request should equal(unsignedMessage.request) signedMessage.requestId should equal(unsignedMessage.requestId) unsignedMessage.signature should be(None) signedMessage.signature.isDefined should be(right = true) val sig = signedMessage.signature.get sig.timestamp should not be (null) sig.signedBy should equal(certCollection.myCertId.get) sig.value should not be (null) //The signed message should verify signerVerifier.verifySig(signedMessage, 1.hour) should be(right = true) def shouldNotVerify(message: BroadcastMessage) { signerVerifier.verifySig(xmlRoundTrip(message), 1.hour) should be(right = false) } //The unsigned one should not shouldNotVerify(unsignedMessage) //Expired sigs shouldn't verify signerVerifier.verifySig(signedMessage, 0.hours) should be(right = false) //modifying anything should prevent verification shouldNotVerify { val anotherRequest = signedMessage.request.asInstanceOf[DeleteQueryRequest].copy(networkQueryId = 123L) signedMessage.withRequest(anotherRequest) } shouldNotVerify { signedMessage.withRequestId(99999L) } shouldNotVerify { signedMessage.copy(networkAuthn = signedMessage.networkAuthn.copy(domain = "askldjlakjsd")) } shouldNotVerify { signedMessage.copy(networkAuthn = signedMessage.networkAuthn.copy(username = "askldjlakjsd")) } shouldNotVerify { signedMessage.copy(networkAuthn = signedMessage.networkAuthn.copy(credential = signedMessage.networkAuthn.credential.copy(isToken = true))) } shouldNotVerify { signedMessage.copy(networkAuthn = signedMessage.networkAuthn.copy(credential = signedMessage.networkAuthn.credential.copy(value = "oieutorutoirutioerutoireuto"))) } shouldNotVerify { val timestamp = signedMessage.signature.get.timestamp import scala.concurrent.duration._ import XmlGcEnrichments._ val newTimestamp = timestamp + 123.minutes signedMessage.withSignature(signedMessage.signature.get.copy(timestamp = newTimestamp)) } shouldNotVerify { val timestamp = signedMessage.signature.get.timestamp import scala.concurrent.duration._ import XmlGcEnrichments._ val newTimestamp = timestamp + (-99).minutes signedMessage.withSignature(signedMessage.signature.get.copy(timestamp = newTimestamp)) } } doTest(Attach) doTest(DontAttach) } @Test def testSigningAndVerificationModifiedTerm(): Unit = { import scala.concurrent.duration._ def doVerificationTest(signingCertStrategy: SigningCertStrategy, queryDef: QueryDefinition): Unit = { val req = RunQueryRequest("some-project-id", 12345.milliseconds, authn, Some("topic-id"), Some("Topic Name"), Set(ResultOutputType.PATIENT_COUNT_XML), queryDef) val unsignedMessage = BroadcastMessage(authn, req) val signedMessage = signerVerifier.sign(unsignedMessage, signingCertStrategy) (unsignedMessage eq signedMessage) should be(right = false) unsignedMessage should not equal (signedMessage) signedMessage.networkAuthn should equal(unsignedMessage.networkAuthn) signedMessage.request should equal(unsignedMessage.request) signedMessage.requestId should equal(unsignedMessage.requestId) unsignedMessage.signature should be(None) signedMessage.signature.isDefined should be(right = true) val sig = signedMessage.signature.get sig.timestamp should not be (null) sig.signedBy should equal(certCollection.myCertId.get) sig.value should not be (null) //The signed message should verify signerVerifier.verifySig(xmlRoundTrip(signedMessage), 1.hour) should be(right = true) } val t1 = Term("t1") val t2 = Term("t2") val t3 = Term("t3") doVerificationTest(Attach, QueryDefinition("foo", Term(""))) doVerificationTest(Attach, QueryDefinition("foo", Constrained(t1, Some(Modifiers("n", "ap", t2.value)), None))) doVerificationTest(Attach, QueryDefinition("foo", Or(t1, Constrained(t2, Some(Modifiers("n", "ap", t3.value)), None)))) doVerificationTest(Attach, QueryDefinition("foo", Or(t1, Constrained(t2, None, Some(ValueConstraint("foo", Some("bar"), "baz", "Nuh")))))) doVerificationTest(Attach, QueryDefinition("foo", Or(t1, Constrained(t2, None, Some(ValueConstraint("foo", None, "baz", "Nuh")))))) doVerificationTest(DontAttach, QueryDefinition("foo", Term(""))) doVerificationTest(DontAttach, QueryDefinition("foo", Constrained(t1, Some(Modifiers("n", "ap", t2.value)), None))) doVerificationTest(DontAttach, QueryDefinition("foo", Or(t1, Constrained(t2, Some(Modifiers("n", "ap", t3.value)), None)))) doVerificationTest(DontAttach, QueryDefinition("foo", Or(t1, Constrained(t2, None, Some(ValueConstraint("foo", Some("bar"), "baz", "Nuh")))))) doVerificationTest(DontAttach, QueryDefinition("foo", Or(t1, Constrained(t2, None, Some(ValueConstraint("foo", None, "baz", "Nuh")))))) } @Test def testSigningAndVerificationReadQueryResultRequest(): Unit = { def doTest(signingCertStrategy: SigningCertStrategy) { val localAuthn = AuthenticationInfo("i2b2demo", "shrine", Credential("SessionKey:PX4LlvLrMhybWRQfoobarbaz", isToken = true)) import scala.concurrent.duration._ val unsignedMessage = BroadcastMessage(authn, ReadQueryResultRequest("SHRINE", 180.seconds, localAuthn, 7923919416951966472L)) val signedMessage = signerVerifier.sign(unsignedMessage, signingCertStrategy) (unsignedMessage eq signedMessage) should be(right = false) unsignedMessage should not equal (signedMessage) signedMessage.networkAuthn should equal(unsignedMessage.networkAuthn) signedMessage.request should equal(unsignedMessage.request) signedMessage.requestId should equal(unsignedMessage.requestId) unsignedMessage.signature should be(None) signedMessage.signature.isDefined should be(right = true) val sig = signedMessage.signature.get sig.timestamp should not be (null) sig.signedBy should equal(certCollection.myCertId.get) sig.value should not be (null) import scala.concurrent.duration._ //The signed message should verify signerVerifier.verifySig(xmlRoundTrip(signedMessage), 1.hour) should be(right = true) } doTest(Attach) doTest(DontAttach) } private def getCertByAlias(alias: String) = certCollection.asInstanceOf[KeyStoreCertCollection].getX509Cert(alias).get @Test def testIsSignedByTrustedCA(): Unit = { import signerVerifier.isSignedByTrustedCA val signedByCa = getCertByAlias("test-cert") val notSignedByCa = getCertByAlias("spin-t1") isSignedByTrustedCA(signedByCa).get should be(right = true) isSignedByTrustedCA(notSignedByCa).isFailure should be(right = true) //TODO: Test case where isSignedByTrustedCA produces Success(false): //where CA cert (param's issuer-DN) IS in our keystore, but X509Certificate.verify(PublicKey) returns false } @Test def testObtainAndValidateSigningCert(): Unit = { import signerVerifier.obtainAndValidateSigningCert import scala.concurrent.duration._ val unsignedMessage = BroadcastMessage(authn, ReadQueryResultRequest("SHRINE", 180.seconds, authn, 7923919416951966472L)) //attached signing cert signed by known CA { val signedMessageWithAttachedSigner = signerVerifier.sign(unsignedMessage, SigningCertStrategy.Attach) val signerCert = obtainAndValidateSigningCert(signedMessageWithAttachedSigner.signature.get).get signerCert should equal(getCertByAlias("test-cert")) } //Known signer, no attached signing cert { val signedMessageWithoutAttachedSigner = signerVerifier.sign(unsignedMessage, SigningCertStrategy.DontAttach) val signerCert = obtainAndValidateSigningCert(signedMessageWithoutAttachedSigner.signature.get).get signerCert should equal(getCertByAlias("test-cert")) } //No attached cert, unknown signer: obtaining signing cert should fail { val signedMessage = signerVerifier.sign(unsignedMessage, SigningCertStrategy.DontAttach) val unknownSigner = CertId(new BigInteger("-1")) val signedMessageWithUnknownSigner = signedMessage.copy(signature = Some(signedMessage.signature.get.copy(signedBy = unknownSigner))) val signerCertAttempt = obtainAndValidateSigningCert(signedMessageWithUnknownSigner.signature.get) signerCertAttempt.isFailure should be(right = true) } } private def xmlRoundTrip(message: BroadcastMessage): BroadcastMessage = { val roundTripped = BroadcastMessage.fromXml(message.toXml).get roundTripped should equal(message) message } } \ No newline at end of file diff --git a/commons/crypto/src/test/scala/net/shrine/crypto/KeyStoreCertCollectionTest.scala b/commons/crypto/src/test/scala/net/shrine/crypto/KeyStoreCertCollectionTest.scala index 259cbcd04..1f7bc8b4b 100644 --- a/commons/crypto/src/test/scala/net/shrine/crypto/KeyStoreCertCollectionTest.scala +++ b/commons/crypto/src/test/scala/net/shrine/crypto/KeyStoreCertCollectionTest.scala @@ -1,158 +1,145 @@ package net.shrine.crypto import java.math.BigInteger import java.security.PrivateKey import java.security.cert.X509Certificate import net.shrine.crypto2.{BouncyKeyStoreCollection, CertCollectionAdapter} import net.shrine.protocol.CertId import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test /** * @author clint * @since Dec 2, 2013 */ final class KeyStoreCertCollectionTest extends ShouldMatchersForJUnit { private def bigInt(i: Int) = BigInteger.valueOf(i) private def bigInt(s: String) = new BigInteger(s) @Test - def testInstanceMethods: Unit = doKeystoreTest(TestKeystore.certCollection) + def testInstanceMethods: Unit = doKeystoreTest(OldTestKeyStore.certCollection) @Test def testNoPrivateKeyAtSpecifiedAlias { - val collectionThatShouldBeFound = KeyStoreCertCollection.fromClassPathResource(TestKeystore.descriptor) + val collectionThatShouldBeFound = KeyStoreCertCollection.fromClassPathResource(OldTestKeyStore.descriptor) - val descriptorWithBogusAlias = TestKeystore.descriptor.copy(privateKeyAlias = Some("bogus cert alias")) + val descriptorWithBogusAlias = OldTestKeyStore.descriptor.copy(privateKeyAlias = Some("bogus cert alias")) //Should throw, since there is no cert-with-private-key at the alias we specified intercept[Exception] { KeyStoreCertCollection.fromClassPathResource(descriptorWithBogusAlias) } } @Test def testNoPrivateKeyAlias { - val descriptorWithBogusAlias = TestKeystore.descriptor.copy(file = "shrine.keystore-one-private-key", privateKeyAlias = None) + val descriptorWithBogusAlias = OldTestKeyStore.descriptor.copy(file = "shrine.keystore-one-private-key", privateKeyAlias = None) //Shouldn't throw, since there's only one private key - this key should be found and used KeyStoreCertCollection.fromClassPathResource(descriptorWithBogusAlias) } @Test def testMultiplePrivateKeys { { - val descriptor = TestKeystore.descriptor.copy(privateKeyAlias = None, file = "shrine.keystore.multiple-private-keys") + val descriptor = OldTestKeyStore.descriptor.copy(privateKeyAlias = None, file = "shrine.keystore.multiple-private-keys") //Should throw, since no private key alias was specified, and multiple private keys were found intercept[Exception] { KeyStoreCertCollection.fromClassPathResource(descriptor) } } { - val descriptor = TestKeystore.descriptor.copy(file = "shrine-test.keystore", password = "changeit", privateKeyAlias = Some("shrine-test"), caCertAliases = Seq("shrine-test")) + val descriptor = OldTestKeyStore.descriptor.copy(file = "shrine.keystore.multiple-private-keys") //Should work, since even though multiple private keys were found, a private key alias was specified - //val keyStore = KeyStoreCertCollection.fromClassPathResource(descriptor) - val bouncy = BouncyKeyStoreCollection.fromFileRecoverWithClassPath(descriptor) - val bytes = "Testing this message".getBytes("UTF-8") - - val CmsBytes = bouncy.signBytes(bytes) // The cool, Crypto Messaging Syntax - val sigBytes: Array[Byte] = DefaultSignerVerifier.sign(bouncy.myEntry.privateKey.get, bytes) - // The less cool, regular signature - - val cert: X509Certificate = bouncy.myEntry.cert - bouncy.verifyBytes(CmsBytes, bytes) should equal(true) - DefaultSignerVerifier.verify(cert, bytes, sigBytes) should equal(true) -// DefaultSignerVerifier.verify(cert, bytes, CmsBytes) should equal(true) -// bouncy.verifyBytes(sigBytes, bytes) should equal(true) + val keyStore = KeyStoreCertCollection.fromClassPathResource(descriptor) } } private def doKeystoreTest(collection: CertCollection) { collection.isEmpty should be(false) collection.size should be(3) collection.get(CertId(bigInt(3))).get.getSerialNumber should equal(bigInt(3)) collection.myCert.get.getSerialNumber should equal(bigInt(3)) collection.myCertId.flatMap(collection.get).get.getSerialNumber should equal(bigInt(3)) //val keystore = collection.asInstanceOf[KeyStoreCertCollection].keystore //keystore should not be (null) val caSerials = Set(bigInt("16398565510742424207")) val serials = Set(bigInt("1143048354"), bigInt("3")) val allSerials = caSerials ++ serials collection match { case CertCollectionAdapter(c) => c.allEntries.foreach(entry => println(s"HEY! \n${entry.certificateHolder.getSerialNumber}, ${entry.aliases.first}")) case _ => println("Not an adapter") } collection.caIds.map(_.serial).toSet should equal(caSerials) collection.ids.map(_.serial).toSet should equal(serials) collection.iterator.map(_.getSerialNumber).toSet should equal(allSerials) // collection.myKeyPair should equal { // val expectedPublicKey = collection.myCertId.flatMap(collection.get).get.getPublicKey // // val expectedPrivateKey = keystore.getKey(TestKeystore.privateKeyAlias.get, TestKeystore.password.toCharArray).asInstanceOf[PrivateKey] // // KeyPair(expectedPublicKey, expectedPrivateKey) // } val (caPrincipal, caCert) = collection.caCerts.head caCert.getSerialNumber should equal(caSerials.head) caPrincipal should equal(CertCollection.getIssuer(caCert)) } private def doKeyStoreTestForBoth(descriptor: KeyStoreDescriptor) { val certCollection = KeyStoreCertCollection.fromFileRecoverWithClassPath(descriptor) - val bouncyCertCollection = BouncyKeyStoreCollection.fromFileRecoverWithClassPath(descriptor) + doKeystoreTest(certCollection) - //doKeystoreTest(CertCollectionAdapter(bouncyCertCollection)) } @Test def testSize { - val collection = TestKeystore.certCollection + val collection = OldTestKeyStore.certCollection collection.size should be(3) } //NB: Also exercises fromStream() @Test def testFromFile { import KeyStoreCertCollection.fromFile - doKeyStoreTestForBoth(TestKeystore.certCollection.descriptor.copy(file = "src/test/resources/shrine.keystore")) + doKeyStoreTestForBoth(OldTestKeyStore.certCollection.descriptor.copy(file = "src/test/resources/shrine.keystore")) intercept[Exception] { - fromFile(TestKeystore.certCollection.descriptor.copy(file = "sakfjalskflkasjflas.foo")) + fromFile(OldTestKeyStore.certCollection.descriptor.copy(file = "sakfjalskflkasjflas.foo")) } } //NB: Also exercises fromStream() @Test def testFromClassPathResource { import KeyStoreCertCollection.fromClassPathResource - doKeyStoreTestForBoth(TestKeystore.certCollection.descriptor) + doKeyStoreTestForBoth(OldTestKeyStore.certCollection.descriptor) intercept[Exception] { - fromClassPathResource(TestKeystore.certCollection.descriptor.copy(file = "sakfjalskflkasjflas.foo")) + fromClassPathResource(OldTestKeyStore.certCollection.descriptor.copy(file = "sakfjalskflkasjflas.foo")) } } } \ No newline at end of file diff --git a/commons/crypto/src/test/scala/net/shrine/crypto/KeyStoreDescriptorParserTest.scala b/commons/crypto/src/test/scala/net/shrine/crypto/KeyStoreDescriptorParserTest.scala index 603f0edd3..c036ed43a 100644 --- a/commons/crypto/src/test/scala/net/shrine/crypto/KeyStoreDescriptorParserTest.scala +++ b/commons/crypto/src/test/scala/net/shrine/crypto/KeyStoreDescriptorParserTest.scala @@ -1,84 +1,88 @@ package net.shrine.crypto import com.typesafe.config.ConfigFactory import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test /** * @author clint * @since Dec 11, 2013 */ final class KeyStoreDescriptorParserTest extends ShouldMatchersForJUnit { @Test def testApply { //All fields, JKS { val descriptor = KeyStoreDescriptorParser(ConfigFactory.parseString(""" file="foo" password="bar" privateKeyAlias="baz" keyStoreType="jks" caCertAliases = [foo, bar] + trustModelIsHub = true """)) descriptor.file should be("foo") descriptor.password should be("bar") descriptor.privateKeyAlias should be(Some("baz")) descriptor.keyStoreType should be(KeyStoreType.JKS) descriptor.caCertAliases.toSet should be(Set("foo", "bar")) } //All fields, PKCS12 { val descriptor = KeyStoreDescriptorParser(ConfigFactory.parseString(""" file="foo" password="bar" privateKeyAlias="baz" keyStoreType="pkcs12" + trustModelIsHub = true """)) descriptor.file should be("foo") descriptor.password should be("bar") descriptor.privateKeyAlias should be(Some("baz")) descriptor.keyStoreType should be(KeyStoreType.PKCS12) } //no keystore type { val descriptor = KeyStoreDescriptorParser(ConfigFactory.parseString(""" file="foo" password="bar" privateKeyAlias="baz" + trustModelIsHub = true """)) descriptor.file should be("foo") descriptor.password should be("bar") descriptor.privateKeyAlias should be(Some("baz")) descriptor.keyStoreType should be(KeyStoreType.Default) } //no private key alias { val descriptor = KeyStoreDescriptorParser(ConfigFactory.parseString(""" file="foo" password="bar" keyStoreType="jks" + trustModelIsHub = true """)) descriptor.file should be("foo") descriptor.password should be("bar") descriptor.privateKeyAlias should be(None) descriptor.keyStoreType should be(KeyStoreType.JKS) } //No file intercept[Exception] { KeyStoreDescriptorParser(ConfigFactory.parseString(""" password="bar" """)) } //No password intercept[Exception] { KeyStoreDescriptorParser(ConfigFactory.parseString(""" file="foo" """)) } } } \ No newline at end of file diff --git a/commons/crypto/src/test/scala/net/shrine/crypto/NewTestKeyStore.scala b/commons/crypto/src/test/scala/net/shrine/crypto/NewTestKeyStore.scala new file mode 100644 index 000000000..e95d393a3 --- /dev/null +++ b/commons/crypto/src/test/scala/net/shrine/crypto/NewTestKeyStore.scala @@ -0,0 +1,46 @@ +package net.shrine.crypto + +import net.shrine.crypto2.BouncyKeyStoreCollection +import net.shrine.util.{PeerToPeerModel, SingleHubModel} + +/** + * @author clint + * @date Nov 27, 2013 + */ +object NewTestKeyStore { + val fileName = "crypto2/shrine-test.jks" + + val password = "justatestpassword" + + val privateKeyAlias: Option[String] = Some("shrine-test") + + val keyStoreType: KeyStoreType = KeyStoreType.JKS + + val caCertAliases = Seq("shrine-test-ca") + + lazy val descriptor = KeyStoreDescriptor(fileName, password, privateKeyAlias, caCertAliases, keyStoreType, SingleHubModel) + + lazy val certCollection = BouncyKeyStoreCollection.fromFileRecoverWithClassPath(descriptor) + + lazy val trustParam: TrustParam = TrustParam.BouncyKeyStore(certCollection) +} + +object OldTestKeyStore { + + val fileName = "shrine.keystore" + + val password = "chiptesting" + + val privateKeyAlias: Option[String] = Some("test-cert") + + val keyStoreType: KeyStoreType = KeyStoreType.JKS + + val caCertAliases = Seq("carra ca", "shrine-ca") + + lazy val descriptor = KeyStoreDescriptor(fileName, password, privateKeyAlias, caCertAliases, keyStoreType, PeerToPeerModel) + + lazy val certCollection = KeyStoreCertCollection.fromClassPathResource(descriptor) + + lazy val trustParam: TrustParam = TrustParam.SomeKeyStore(certCollection) + +} \ No newline at end of file diff --git a/commons/crypto/src/test/scala/net/shrine/crypto/TestKeystore.scala b/commons/crypto/src/test/scala/net/shrine/crypto/TestKeystore.scala deleted file mode 100644 index 32dfb7c8e..000000000 --- a/commons/crypto/src/test/scala/net/shrine/crypto/TestKeystore.scala +++ /dev/null @@ -1,25 +0,0 @@ -package net.shrine.crypto - -import net.shrine.util.PeerToPeerModel - -/** - * @author clint - * @date Nov 27, 2013 - */ -object TestKeystore { - val fileName = "shrine.keystore" - - val password = "chiptesting" - - val privateKeyAlias: Option[String] = Some("test-cert") - - val keyStoreType: KeyStoreType = KeyStoreType.JKS - - val caCertAliases = Seq("carra ca", "shrine-ca") - - lazy val descriptor = KeyStoreDescriptor(fileName, password, privateKeyAlias, caCertAliases, keyStoreType, PeerToPeerModel) - - lazy val certCollection = KeyStoreCertCollection.fromClassPathResource(descriptor) - - lazy val trustParam: TrustParam = TrustParam.SomeKeyStore(certCollection) -} \ No newline at end of file diff --git a/commons/crypto/src/test/scala/net/shrine/crypto/TrustParamTest.scala b/commons/crypto/src/test/scala/net/shrine/crypto/TrustParamTest.scala index 72b1d1689..30fbdde79 100644 --- a/commons/crypto/src/test/scala/net/shrine/crypto/TrustParamTest.scala +++ b/commons/crypto/src/test/scala/net/shrine/crypto/TrustParamTest.scala @@ -1,15 +1,15 @@ package net.shrine.crypto import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test /** * @author clint * @date Dec 18, 2013 */ final class TrustParamTest extends ShouldMatchersForJUnit { @Test def testForKeyStore { - TrustParam.forKeyStore(TestKeystore.certCollection) should equal(TrustParam.SomeKeyStore(TestKeystore.certCollection)) + //Deprecated: delete? TrustParam.forKeyStore(TestKeystore.certCollection) should equal(TrustParam.SomeKeyStore(TestKeystore.certCollection)) } } \ No newline at end of file diff --git a/commons/crypto/src/test/scala/net/shrine/crypto2/HubCertCollectionTest.scala b/commons/crypto/src/test/scala/net/shrine/crypto2/HubCertCollectionTest.scala index 70ce7ee06..23f14a0d7 100644 --- a/commons/crypto/src/test/scala/net/shrine/crypto2/HubCertCollectionTest.scala +++ b/commons/crypto/src/test/scala/net/shrine/crypto2/HubCertCollectionTest.scala @@ -1,32 +1,36 @@ package net.shrine.crypto2 import junit.framework.TestFailure -import net.shrine.crypto.{KeyStoreDescriptor, KeyStoreType, TestKeystore} +import net.shrine.crypto.{KeyStoreDescriptor, KeyStoreType, NewTestKeyStore} import net.shrine.util.SingleHubModel import org.junit.runner.RunWith import org.scalatest.{FlatSpec, Matchers, ShouldMatchers} import org.scalatest.junit.JUnitRunner /** * Created by ty on 11/1/16. */ @RunWith(classOf[JUnitRunner]) class HubCertCollectionTest extends FlatSpec with Matchers { - val descriptor = KeyStoreDescriptor("crypto2/shrine-test.jks", "justatestpassword", None, Nil, KeyStoreType.JKS, SingleHubModel) + val descriptor = NewTestKeyStore.descriptor val heyo = "Heyo!".getBytes("UTF-8") "A hub cert collection" should "build and verify its own messages" in { val hubCertCollection = BouncyKeyStoreCollection.fromFileRecoverWithClassPath(descriptor) match { case hub:HubCertCollection => hub case _ => fail("This should generate a HubCertCollection!") } hubCertCollection.allEntries.size shouldBe 2 - hubCertCollection.verifyBytes(hubCertCollection.signBytes(heyo), heyo) hubCertCollection.myEntry.privateKey.isDefined shouldBe true hubCertCollection.caEntry.privateKey.isDefined shouldBe false hubCertCollection.myEntry.aliases.first shouldBe "shrine-test" hubCertCollection.caEntry.aliases.first shouldBe "shrine-test-ca" + hubCertCollection.caEntry.wasSignedBy(hubCertCollection.myEntry) shouldBe false + hubCertCollection.myEntry.wasSignedBy(hubCertCollection.caEntry) shouldBe true + //hubCertCollection.myEntry.verify(hubCertCollection.myEntry.sign(heyo).get, heyo) shouldBe true + hubCertCollection.caEntry.verify(hubCertCollection.myEntry.sign(heyo).get, heyo) shouldBe true + hubCertCollection.verifyBytes(hubCertCollection.signBytes(heyo), heyo) shouldBe true } } diff --git a/commons/util/src/main/scala/net/shrine/problem/Problem.scala b/commons/util/src/main/scala/net/shrine/problem/Problem.scala index 9c503ddfa..694b65c28 100644 --- a/commons/util/src/main/scala/net/shrine/problem/Problem.scala +++ b/commons/util/src/main/scala/net/shrine/problem/Problem.scala @@ -1,231 +1,226 @@ package net.shrine.problem import java.net.InetAddress import java.util.Date import java.util.concurrent.Executors import net.shrine.log.Loggable import net.shrine.serialization.{XmlMarshaller, XmlUnmarshaller} import net.shrine.slick.NeedsWarmUp import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.util.Try import scala.xml.{Elem, Node, NodeSeq} /** * Describes what information we have about a problem at the site in code where we discover it. * * @author david * @since 8/6/15 */ trait Problem { def summary:String def problemName = getClass.getName def throwable:Option[Throwable] = None def stamp:Stamp def description:String def exceptionXml(exception:Option[Throwable]): Option[Elem] = { exception.map{x => {x.getClass.getName} {x.getMessage} {x.getStackTrace.map(line => {line})}{exceptionXml(Option(x.getCause)).getOrElse("")} }} def throwableDetail: Option[Elem] = exceptionXml(throwable) def detailsXml: NodeSeq = NodeSeq.fromSeq(

{throwableDetail.getOrElse("")}
) def toDigest:ProblemDigest = ProblemDigest(problemName,stamp.pretty,summary,description,detailsXml,stamp.time) /** * Temporary replacement for onCreate, which will be released come Scala 2.13 * TODO: remove when Scala 2.13 releases */ def hackToHandleAfterInitialization(handler:ProblemHandler):Future[Unit] = { import scala.concurrent.blocking Future { var continue = true while (continue) { - try { - blocking(synchronized(handler.handleProblem(this))) - continue = false - } catch { - case un:UninitializedFieldError => - Thread.sleep(5) - continue = true - } + continue = Try(blocking(synchronized(handler.handleProblem(this)))).isSuccess + Thread.sleep(5) } Unit } } } case class ProblemDigest(codec: String, stampText: String, summary: String, description: String, detailsXml: NodeSeq, epoch: Long) extends XmlMarshaller { override def toXml: Node = { {codec} {stampText} {summary} {description} {epoch} {detailsXml} } /** * Ignores detailXml. equals with scala.xml is impossible. See http://www.scala-lang.org/api/2.10.3/index.html#scala.xml.Equality$ */ override def equals(other: Any): Boolean = other match { case that: ProblemDigest => (that canEqual this) && codec == that.codec && stampText == that.stampText && summary == that.summary && description == that.description && epoch == that.epoch case _ => false } /** * Ignores detailXml */ override def hashCode: Int = { val prime = 67 codec.hashCode + prime * (stampText.hashCode + prime *(summary.hashCode + prime * (description.hashCode + prime * epoch.hashCode()))) } } object ProblemDigest extends XmlUnmarshaller[ProblemDigest] with Loggable { override def fromXml(xml: NodeSeq): ProblemDigest = { val problemNode = xml \ "problem" require(problemNode.nonEmpty,s"No problem tag in $xml") def extractText(tagName:String) = (problemNode \ tagName).text val codec = extractText("codec") val stampText = extractText("stamp") val summary = extractText("summary") val description = extractText("description") val detailsXml: NodeSeq = problemNode \ "details" val epoch = try { extractText("epoch").toLong } catch { case nx:NumberFormatException => error(s"While parsing xml representing a ProblemDigest, the epoch could not be parsed into a long", nx) 0 } ProblemDigest(codec,stampText,summary,description,detailsXml,epoch) } } case class Stamp(host:InetAddress,time:Long,source:ProblemSources.ProblemSource) { def pretty = s"${new Date(time)} on ${host.getHostName} ${source.pretty}" } object Stamp { //TODO: val dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")? //TODO: Currently the stamp text is locale specific, which can change depending on the jre/computer running it... def apply(source:ProblemSources.ProblemSource, timer: => Long): Stamp = Stamp(InetAddress.getLocalHost, timer, source) } abstract class AbstractProblem(source:ProblemSources.ProblemSource) extends Problem { def timer = System.currentTimeMillis override val stamp = Stamp(source, timer) private val config = ProblemConfigSource.config.getConfig("shrine.problem") hackToHandleAfterInitialization(ProblemConfigSource.getObject("problemHandler", config)) } trait ProblemHandler extends NeedsWarmUp { def handleProblem(problem:Problem) } /** * An example problem handler */ object LoggingProblemHandler extends ProblemHandler with Loggable { override def handleProblem(problem: Problem): Unit = { problem.throwable.fold(error(problem.toString))(throwable => error(problem.toString,throwable) ) } override def warmUp(): Unit = Unit } -object DatabaseProblemHandler extends ProblemHandler { +object DatabaseProblemHandler extends ProblemHandler with Loggable { override def handleProblem(problem: Problem): Unit = { Problems.DatabaseConnector.insertProblem(problem.toDigest) } override def warmUp(): Unit = Problems.warmUp } /** * Mainly for testing, when you don't want problems to print a bunch * to stdout */ object NoOpProblemHandler extends ProblemHandler { override def handleProblem(problem: Problem): Unit = Unit override def warmUp(): Unit = Unit } object ProblemSources{ sealed trait ProblemSource { def pretty = getClass.getSimpleName.dropRight(1) } case object Adapter extends ProblemSource case object Commons extends ProblemSource case object Dsa extends ProblemSource case object Hub extends ProblemSource case object Qep extends ProblemSource case object Unknown extends ProblemSource def problemSources = Set(Adapter,Commons,Dsa,Hub,Qep,Unknown) } case class ProblemNotYetEncoded(internalSummary:String,t:Option[Throwable] = None) extends AbstractProblem(ProblemSources.Unknown){ override val summary = "An unanticipated problem encountered." override val throwable = { val rx = t.fold(new IllegalStateException(s"$summary"))( new IllegalStateException(s"$summary",_) ) rx.fillInStackTrace() Some(rx) } val reportedAtStackTrace = new IllegalStateException("Capture reporting stack trace.") override val description = "This problem is not yet classified in Shrine source code. Please report the details to the Shrine dev team." override val detailsXml: NodeSeq = NodeSeq.fromSeq(
{internalSummary} {throwableDetail.getOrElse("")}
) } object ProblemNotYetEncoded { def apply(summary:String,x:Throwable):ProblemNotYetEncoded = ProblemNotYetEncoded(summary,Some(x)) } \ No newline at end of file diff --git a/hub/broadcaster-aggregator/src/main/scala/net/shrine/broadcaster/SigningBroadcastAndAggregationService.scala b/hub/broadcaster-aggregator/src/main/scala/net/shrine/broadcaster/SigningBroadcastAndAggregationService.scala index 9ebf333d3..63172a15b 100644 --- a/hub/broadcaster-aggregator/src/main/scala/net/shrine/broadcaster/SigningBroadcastAndAggregationService.scala +++ b/hub/broadcaster-aggregator/src/main/scala/net/shrine/broadcaster/SigningBroadcastAndAggregationService.scala @@ -1,55 +1,56 @@ package net.shrine.broadcaster import com.typesafe.config.Config import net.shrine.broadcaster.dao.HubDao import net.shrine.client.{EndpointConfig, Poster} import net.shrine.config.ConfigExtensions import net.shrine.crypto.{DefaultSignerVerifier, KeyStoreCertCollection, Signer, SigningCertStrategy} +import net.shrine.crypto2.{BouncyKeyStoreCollection, SignerVerifierAdapter} import net.shrine.protocol.ResultOutputType /** * @author clint * @since Feb 28, 2014 */ final case class SigningBroadcastAndAggregationService(broadcasterClient: BroadcasterClient, signer: Signer, signingCertStrategy: SigningCertStrategy) extends AbstractBroadcastAndAggregationService(broadcasterClient, signer.sign(_, signingCertStrategy)) { override def attachSigningCert: Boolean = signingCertStrategy == SigningCertStrategy.Attach } object SigningBroadcastAndAggregationService { def apply(qepConfig:Config, - shrineCertCollection: KeyStoreCertCollection, + shrineCertCollection: BouncyKeyStoreCollection, breakdownTypes: Set[ResultOutputType], //todo I'm surprised you need this to support a remote hub. Figure out why. Remove if possible broadcastDestinations: Option[Set[NodeHandle]], //todo remove when you use loopback for a local hub hubDao: HubDao //todo remove when you use loopback for a local hub ):SigningBroadcastAndAggregationService = { - val signerVerifier: DefaultSignerVerifier = new DefaultSignerVerifier(shrineCertCollection) + val signerVerifier: Signer = SignerVerifierAdapter(shrineCertCollection) val broadcasterClient: BroadcasterClient = { //todo don't bother with a distinction between local and remote QEPs. Just use loopback. val remoteHubEndpoint = qepConfig.getOptionConfigured("broadcasterServiceEndpoint", EndpointConfig(_)) remoteHubEndpoint.fold{ require(broadcastDestinations.isDefined, s"The QEP's config implied a local hub (no broadcasterServiceEndpoint), but either no downstream nodes were configured, the hub was not configured, or the hub's configuration specified not to create it.") val broadcaster: AdapterClientBroadcaster = AdapterClientBroadcaster(broadcastDestinations.get, hubDao) val broadcastClient:BroadcasterClient = InJvmBroadcasterClient(broadcaster) broadcastClient }{ hubEndpointConfig => PosterBroadcasterClient(Poster(shrineCertCollection,hubEndpointConfig), breakdownTypes) } } //todo ditch the option and use reference.conf val attachSigningCerts: Boolean = qepConfig.getOption("attachSigningCert", _.getBoolean).getOrElse(false) val signingCertStrategy:SigningCertStrategy = if(attachSigningCerts) SigningCertStrategy.Attach else SigningCertStrategy.DontAttach new SigningBroadcastAndAggregationService(broadcasterClient, signerVerifier, signingCertStrategy) } } \ No newline at end of file diff --git a/hub/broadcaster-aggregator/src/test/scala/net/shrine/broadcaster/SigningBroadcastAndAggregationServiceTest.scala b/hub/broadcaster-aggregator/src/test/scala/net/shrine/broadcaster/SigningBroadcastAndAggregationServiceTest.scala index a3f1cfa82..746eb9b30 100644 --- a/hub/broadcaster-aggregator/src/test/scala/net/shrine/broadcaster/SigningBroadcastAndAggregationServiceTest.scala +++ b/hub/broadcaster-aggregator/src/test/scala/net/shrine/broadcaster/SigningBroadcastAndAggregationServiceTest.scala @@ -1,97 +1,98 @@ package net.shrine.broadcaster import scala.concurrent.Await import org.junit.Test import net.shrine.util.ShouldMatchersForJUnit import net.shrine.aggregation.Aggregator import net.shrine.crypto.DefaultSignerVerifier -import net.shrine.crypto.TestKeystore +import net.shrine.crypto.NewTestKeyStore import net.shrine.protocol.{AuthenticationInfo, BroadcastMessage, Credential, DeleteQueryRequest, ErrorResponse, FailureResult, FailureResult$, NodeId, Result, ShrineResponse, SingleNodeResult, Timeout} import net.shrine.crypto.SigningCertStrategy import net.shrine.broadcaster.dao.MockHubDao +import net.shrine.crypto2.SignerVerifierAdapter import net.shrine.problem.TestProblem /** * @author clint * @since Nov 19, 2013 */ final class SigningBroadcastAndAggregationServiceTest extends ShouldMatchersForJUnit { import scala.concurrent.duration._ import MockBroadcasters._ private def result(description: Char) = { val problem: TestProblem = TestProblem(summary = "blah blah blah") Result(NodeId(description.toString), 1.second, ErrorResponse(problem)) } private val results = "abcde".map(result) private lazy val nullResultsByOrigin: Map[NodeId, SingleNodeResult] = Map(NodeId("X") -> null, NodeId("Y") -> null) private lazy val resultsWithNullsByOrigin: Map[NodeId, SingleNodeResult] = { results.collect { case r @ Result(origin, _, _) => origin -> r }.toMap ++ nullResultsByOrigin } - private lazy val signer = new DefaultSignerVerifier(TestKeystore.certCollection) + private lazy val signer = SignerVerifierAdapter(NewTestKeyStore.certCollection) private val broadcastMessage = { val authn = AuthenticationInfo("domain", "username", Credential("asdasd", false)) import scala.concurrent.duration._ BroadcastMessage(authn, DeleteQueryRequest("projectId", 12345.milliseconds, authn, 12345L)) } @Test def testAggregateHandlesNullResults { val mockBroadcaster = MockAdapterClientBroadcaster(resultsWithNullsByOrigin) val broadcastService = SigningBroadcastAndAggregationService(InJvmBroadcasterClient(mockBroadcaster), signer, SigningCertStrategy.Attach) val aggregator: Aggregator = new Aggregator { override def aggregate(results: Iterable[SingleNodeResult], errors: Iterable[ErrorResponse], respondingTo: BroadcastMessage): ShrineResponse = { ErrorResponse(TestProblem(results.size.toString)) } } val aggregatedResult = Await.result(broadcastService.sendAndAggregate(broadcastMessage, aggregator, true), 5.minutes) mockBroadcaster.messageParam.signature.isDefined should be(true) val testProblem = TestProblem(s"${results.size}") //testProblem.stamp.time = aggregatedResult. aggregatedResult should equal(ErrorResponse(TestProblem(s"${results.size}"))) } @Test def testAggregateHandlesFailures { def toResult(description: Char) = Result(NodeId(description.toString), 1.second, ErrorResponse(TestProblem("blah blah blah"))) def toFailure(description: Char) = FailureResult(NodeId(description.toString), new Exception with scala.util.control.NoStackTrace) val failuresByOrigin: Map[NodeId, SingleNodeResult] = { "UV".map(toFailure).map { case f @ FailureResult(origin, _) => origin -> f }.toMap } val timeoutsByOrigin: Map[NodeId, SingleNodeResult] = Map(NodeId("Z") -> Timeout(NodeId("Z"))) val resultsWithFailuresByOrigin: Map[NodeId, SingleNodeResult] = resultsWithNullsByOrigin ++ failuresByOrigin ++ timeoutsByOrigin val mockBroadcaster = MockAdapterClientBroadcaster(resultsWithFailuresByOrigin) val broadcastService = SigningBroadcastAndAggregationService(InJvmBroadcasterClient(mockBroadcaster), signer, SigningCertStrategy.DontAttach) val aggregator: Aggregator = new Aggregator { override def aggregate(results: Iterable[SingleNodeResult], errors: Iterable[ErrorResponse], respondingTo: BroadcastMessage): ShrineResponse = { ErrorResponse(TestProblem(s"${results.size},${errors.size}")) } } val aggregatedResult = Await.result(broadcastService.sendAndAggregate(broadcastMessage, aggregator, true), 5.minutes) mockBroadcaster.messageParam.signature.isDefined should be(true) aggregatedResult should equal(ErrorResponse(TestProblem(s"${results.size + failuresByOrigin.size + timeoutsByOrigin.size},0"))) } } diff --git a/integration/src/test/scala/net/shrine/integration/AbstractHubAndSpokesTest.scala b/integration/src/test/scala/net/shrine/integration/AbstractHubAndSpokesTest.scala index 5665432aa..c68a991ab 100644 --- a/integration/src/test/scala/net/shrine/integration/AbstractHubAndSpokesTest.scala +++ b/integration/src/test/scala/net/shrine/integration/AbstractHubAndSpokesTest.scala @@ -1,57 +1,58 @@ package net.shrine.integration import net.shrine.adapter.service.{AdapterRequestHandler, AdapterResource, JerseyTestComponent} import net.shrine.client.{JerseyHttpClient, Poster} -import net.shrine.crypto.{DefaultSignerVerifier, TestKeystore, TrustParam} +import net.shrine.crypto.{DefaultSignerVerifier, NewTestKeyStore, TrustParam} +import net.shrine.crypto2.{CertCollectionAdapter, SignerVerifierAdapter} import net.shrine.protocol.{AuthenticationInfo, CertId, Credential, NodeId} import org.junit.{After, Before} import scala.concurrent.duration.DurationInt /** * @author clint * @date Mar 6, 2014 */ trait AbstractHubAndSpokesTest { @Before def setUp(): Unit = { spokes.foreach(_.JerseyTest.setUp()) } @After def tearDown(): Unit = { spokes.foreach(_.JerseyTest.tearDown()) } def posterFor(component: JerseyTestComponent[_]): Poster = Poster(component.resourceUrl, JerseyHttpClient(TrustParam.AcceptAllCerts, 30.minutes)) val networkAuthn = AuthenticationInfo("d", "u", Credential("p", false)) - val certCollection = TestKeystore.certCollection + val certCollection = NewTestKeyStore.certCollection - lazy val myCertId: CertId = certCollection.myCertId.get + lazy val myCertId: CertId = CertCollectionAdapter(certCollection).myCertId.get - lazy val signerVerifier = new DefaultSignerVerifier(certCollection) + lazy val signerVerifier = SignerVerifierAdapter(certCollection) import AbstractHubAndSpokesTest.SpokeComponent lazy val Spoke0Component = SpokeComponent(9998, NodeId("Spoke 0")) lazy val Spoke1Component = SpokeComponent(9999, NodeId("Spoke 1")) lazy val spokes: Set[SpokeComponent] = Set(Spoke0Component, Spoke1Component) } object AbstractHubAndSpokesTest { final case class SpokeComponent(override val port: Int, nodeId: NodeId) extends JerseyTestComponent[AdapterRequestHandler] { override val basePath = "adapter" def mockHandler = handler.asInstanceOf[MockAdapterRequestHandler] override lazy val makeHandler: AdapterRequestHandler = new MockAdapterRequestHandler(nodeId) override def resourceClass(handler: AdapterRequestHandler) = AdapterResource(handler) } } \ No newline at end of file diff --git a/integration/src/test/scala/net/shrine/integration/AdapterResourceEndToEndJaxRsFailureTest.scala b/integration/src/test/scala/net/shrine/integration/AdapterResourceEndToEndJaxRsFailureTest.scala index aff8798bf..7283c50a7 100644 --- a/integration/src/test/scala/net/shrine/integration/AdapterResourceEndToEndJaxRsFailureTest.scala +++ b/integration/src/test/scala/net/shrine/integration/AdapterResourceEndToEndJaxRsFailureTest.scala @@ -1,47 +1,48 @@ package net.shrine.integration import org.junit.Test import net.shrine.protocol.{BroadcastMessage, DefaultBreakdownResultOutputTypes, DeleteQueryRequest, DeleteQueryResponse, ErrorResponse, Result} import net.shrine.crypto.DefaultSignerVerifier -import net.shrine.crypto.TestKeystore +import net.shrine.crypto.NewTestKeyStore import scala.concurrent.Await import com.sun.jersey.api.client.UniformInterfaceException import net.shrine.adapter.service.AdapterResource import net.shrine.crypto.SigningCertStrategy +import net.shrine.crypto2.SignerVerifierAdapter /** * @author clint * @since Dec 17, 2013 */ final class AdapterResourceEndToEndJaxRsFailureTest extends AbstractAdapterResourceJaxRsTest { override val makeHandler = AlwaysThrowsAdapterRequestHandler @Test def testHandleRequestWithServerSideException { import scala.concurrent.duration._ val masterId = 12345L val unsigned = BroadcastMessage(networkAuthn, DeleteQueryRequest("some-project", 1.minute, networkAuthn, masterId)) - val signer = new DefaultSignerVerifier(TestKeystore.certCollection) + val signer = SignerVerifierAdapter(NewTestKeyStore.certCollection) val signed = signer.sign(unsigned, SigningCertStrategy.Attach) val result = Await.result(client.query(signed), 1.hour) result.response.isInstanceOf[ErrorResponse] should be(true) } @Test def testHandleRequestWithBadInput { val resource = AdapterResource(MockAdapterRequestHandler) intercept[Exception] { resource.handleRequest("aslkdjlaksjdlasdj") } } } \ No newline at end of file diff --git a/integration/src/test/scala/net/shrine/integration/AdapterResourceEndToEndJaxRsTest.scala b/integration/src/test/scala/net/shrine/integration/AdapterResourceEndToEndJaxRsTest.scala index 979707b85..9d348d4d2 100644 --- a/integration/src/test/scala/net/shrine/integration/AdapterResourceEndToEndJaxRsTest.scala +++ b/integration/src/test/scala/net/shrine/integration/AdapterResourceEndToEndJaxRsTest.scala @@ -1,41 +1,42 @@ package net.shrine.integration import scala.concurrent.Await import scala.concurrent.duration.DurationInt import org.junit.Test import net.shrine.crypto.DefaultSignerVerifier -import net.shrine.crypto.TestKeystore +import net.shrine.crypto.NewTestKeyStore import net.shrine.protocol.BroadcastMessage import net.shrine.protocol.DeleteQueryRequest import net.shrine.protocol.DeleteQueryResponse import net.shrine.protocol.Result import com.sun.jersey.api.client.UniformInterfaceException import net.shrine.crypto.SigningCertStrategy +import net.shrine.crypto2.SignerVerifierAdapter /** * @author clint * @date Dec 17, 2013 */ final class AdapterResourceEndToEndJaxRsTest extends AbstractAdapterResourceJaxRsTest { override val makeHandler = MockAdapterRequestHandler @Test def testHandleRequest { import scala.concurrent.duration._ val masterId = 12345L val unsigned = BroadcastMessage(networkAuthn, DeleteQueryRequest("some-project", 1.minute, networkAuthn, masterId)) - val signer = new DefaultSignerVerifier(TestKeystore.certCollection) + val signer = SignerVerifierAdapter(NewTestKeyStore.certCollection) val signed = signer.sign(unsigned, SigningCertStrategy.Attach) { val resp = Await.result(client.query(signed), 1.hour) resp should equal(Result(MockAdapterRequestHandler.nodeId, MockAdapterRequestHandler.elapsed, DeleteQueryResponse(masterId))) } } } \ No newline at end of file diff --git a/integration/src/test/scala/net/shrine/integration/AdapterResourceEndToEndJaxRsTimeoutTest.scala b/integration/src/test/scala/net/shrine/integration/AdapterResourceEndToEndJaxRsTimeoutTest.scala index 420bf5582..404d7ee63 100644 --- a/integration/src/test/scala/net/shrine/integration/AdapterResourceEndToEndJaxRsTimeoutTest.scala +++ b/integration/src/test/scala/net/shrine/integration/AdapterResourceEndToEndJaxRsTimeoutTest.scala @@ -1,45 +1,46 @@ package net.shrine.integration import scala.concurrent.Await import org.junit.Test import net.shrine.adapter.client.AdapterClient import net.shrine.adapter.client.RemoteAdapterClient import net.shrine.client.JerseyHttpClient import net.shrine.client.Poster import net.shrine.client.TimeoutException import net.shrine.crypto.DefaultSignerVerifier -import net.shrine.crypto.TestKeystore +import net.shrine.crypto.NewTestKeyStore import net.shrine.crypto.TrustParam.AcceptAllCerts -import net.shrine.protocol.{NodeId, BroadcastMessage, DeleteQueryRequest, DefaultBreakdownResultOutputTypes} +import net.shrine.protocol.{BroadcastMessage, DefaultBreakdownResultOutputTypes, DeleteQueryRequest, NodeId} import net.shrine.crypto.SigningCertStrategy +import net.shrine.crypto2.SignerVerifierAdapter /** * @author clint * @date Dec 17, 2013 */ final class AdapterResourceEndToEndJaxRsTimeoutTest extends AbstractAdapterResourceJaxRsTest { import scala.concurrent.duration._ override def makeHandler = TimesOutAdapterRequestHandler(1.minute) override protected lazy val client: AdapterClient = { RemoteAdapterClient(NodeId.Unknown,Poster(resourceUrl, JerseyHttpClient(AcceptAllCerts, 100.milliseconds)), DefaultBreakdownResultOutputTypes.toSet) } @Test def testHandleRequestTimeout { val masterId = 12345L val unsigned = BroadcastMessage(networkAuthn, DeleteQueryRequest("some-project", 1.minute, networkAuthn, masterId)) - val signer = new DefaultSignerVerifier(TestKeystore.certCollection) + val signer = SignerVerifierAdapter(NewTestKeyStore.certCollection) val signed = signer.sign(unsigned, SigningCertStrategy.Attach) //Client timeouts should result in a net.shrine.client.TimeoutException being thrown intercept[TimeoutException] { Await.result(client.query(signed), 1.minute) } } } \ No newline at end of file diff --git a/integration/src/test/scala/net/shrine/integration/NetworkSimulationTest.scala b/integration/src/test/scala/net/shrine/integration/NetworkSimulationTest.scala index 7f7658625..dfd9657f5 100644 --- a/integration/src/test/scala/net/shrine/integration/NetworkSimulationTest.scala +++ b/integration/src/test/scala/net/shrine/integration/NetworkSimulationTest.scala @@ -1,336 +1,337 @@ package net.shrine.integration import java.net.URL import net.shrine.log.Loggable import scala.concurrent.Future import scala.concurrent.duration.DurationInt import org.junit.Test import net.shrine.util.ShouldMatchersForJUnit import net.shrine.adapter.{AdapterMap, DeleteQueryAdapter, FlagQueryAdapter, Obfuscator, ReadQueryResultAdapter, RunQueryAdapter, UnFlagQueryAdapter} import net.shrine.adapter.client.AdapterClient import net.shrine.adapter.dao.squeryl.AbstractSquerylAdapterTest import net.shrine.adapter.service.AdapterRequestHandler import net.shrine.adapter.service.AdapterService import net.shrine.broadcaster.AdapterClientBroadcaster import net.shrine.broadcaster.NodeHandle import net.shrine.crypto.DefaultSignerVerifier -import net.shrine.crypto.TestKeystore +import net.shrine.crypto.NewTestKeyStore import net.shrine.protocol.{AggregatedRunQueryResponse, AuthenticationInfo, BroadcastMessage, CertId, Credential, DefaultBreakdownResultOutputTypes, DeleteQueryRequest, DeleteQueryResponse, FlagQueryRequest, FlagQueryResponse, HiveCredentials, NodeId, QueryResult, RawCrcRunQueryResponse, RequestType, Result, ResultOutputType, RunQueryRequest, RunQueryResponse, UnFlagQueryRequest, UnFlagQueryResponse} import net.shrine.qep.QepService import net.shrine.broadcaster.SigningBroadcastAndAggregationService import net.shrine.broadcaster.InJvmBroadcasterClient import net.shrine.protocol.query.Term import net.shrine.client.Poster import net.shrine.client.HttpClient import net.shrine.client.HttpResponse import net.shrine.adapter.translators.QueryDefinitionTranslator import net.shrine.adapter.translators.ExpressionTranslator import net.shrine.util.XmlDateHelper import net.shrine.protocol.query.QueryDefinition import net.shrine.crypto.SigningCertStrategy +import net.shrine.crypto2.{CertCollectionAdapter, SignerVerifierAdapter} /** * @author clint * @since Nov 27, 2013 * * An in-JVM simulation of a Shrine network with one hub and 4 downstream adapters. * * The hub and adapters are wired up with mock AdapterClients that do in-JVM communication via method calls * instead of remotely. * * The adapters are configured to respond with valid results for DeleteQueryRequests * only. Other requests could be handled, but that would not provide benefit to offset the effort of wiring * up more and more-complex Adapters. * * The test network is queried, and the final result, as well as the state of each adapter, is inspected to * ensure that the right messages were sent between elements of the system. * */ final class NetworkSimulationTest extends AbstractSquerylAdapterTest with ShouldMatchersForJUnit { - private val certCollection = TestKeystore.certCollection + private val certCollection = NewTestKeyStore.certCollection - private lazy val myCertId: CertId = certCollection.myCertId.get + private lazy val myCertId: CertId = CertCollectionAdapter(certCollection).myCertId.get - private lazy val signerVerifier = new DefaultSignerVerifier(certCollection) + private lazy val signerVerifier = SignerVerifierAdapter(certCollection) private val domain = "test-domain" private val username = "test-username" private val password = "test-password" import NetworkSimulationTest._ import scala.concurrent.duration._ private def deleteQueryAdapter: DeleteQueryAdapter = new DeleteQueryAdapter(dao) private def flagQueryAdapter: FlagQueryAdapter = new FlagQueryAdapter(dao) private def unFlagQueryAdapter: UnFlagQueryAdapter = new UnFlagQueryAdapter(dao) private def mockPoster = Poster("http://example.com", new HttpClient { override def post(input: String, url: String): HttpResponse = ??? }) private val hiveCredentials = HiveCredentials("d", "u", "pwd", "pid") private def queuesQueriesRunQueryAdapter: RunQueryAdapter = { val translator = new QueryDefinitionTranslator(new ExpressionTranslator(Map("n1" -> Set("l1")))) RunQueryAdapter( poster = mockPoster, dao = dao, hiveCredentials = hiveCredentials, conceptTranslator = translator, adapterLockoutAttemptsThreshold = 10000, doObfuscation = false, runQueriesImmediately = false, breakdownTypes = DefaultBreakdownResultOutputTypes.toSet, collectAdapterAudit = false, botCountTimeThresholds = Seq.empty, obfuscator = Obfuscator(5,6.5,10) ) } //todo this looks unused private def immediatelyRunsQueriesRunQueryAdapter(setSize: Long): RunQueryAdapter = { val mockCrcPoster = Poster("http://example.com", new HttpClient { override def post(input: String, url: String): HttpResponse = { val req = RunQueryRequest.fromI2b2String(DefaultBreakdownResultOutputTypes.toSet)(input).get val now = XmlDateHelper.now val queryResult = QueryResult(1L, 42L, Some(ResultOutputType.PATIENT_COUNT_XML), setSize, Some(now), Some(now), Some("desc"), QueryResult.StatusType.Finished, Some("status")) val mockCrcXml = RawCrcRunQueryResponse(req.networkQueryId, XmlDateHelper.now, req.authn.username, req.projectId, req.queryDefinition, 42L, Map(ResultOutputType.PATIENT_COUNT_XML -> Seq(queryResult))).toI2b2String HttpResponse.ok(mockCrcXml) } }) queuesQueriesRunQueryAdapter.copy(poster = mockCrcPoster, runQueriesImmediately = true) } private def readQueryResultAdapter(setSize: Long): ReadQueryResultAdapter = { new ReadQueryResultAdapter( mockPoster, hiveCredentials, dao, doObfuscation = false, DefaultBreakdownResultOutputTypes.toSet, collectAdapterAudit = false, obfuscator = Obfuscator(5,6.5,10) ) } private lazy val adaptersByNodeId: Seq[(NodeId, MockAdapterRequestHandler)] = { import NodeName._ import RequestType.{ MasterDeleteRequest => MasterDeleteRequestRT, FlagQueryRequest => FlagQueryRequestRT, QueryDefinitionRequest => RunQueryRT, GetQueryResult => ReadQueryResultRT, UnFlagQueryRequest => UnFlagQueryRequestRT } (for { (childName, setSize) <- Seq((A, 1L), (B, 2L), (C, 3L), (D, 4L)) } yield { val nodeId = NodeId(childName.name) val maxSignatureAge = 1.hour val adapterMap = AdapterMap(Map( MasterDeleteRequestRT -> deleteQueryAdapter, FlagQueryRequestRT -> flagQueryAdapter, UnFlagQueryRequestRT -> unFlagQueryAdapter, RunQueryRT -> queuesQueriesRunQueryAdapter, ReadQueryResultRT -> readQueryResultAdapter(setSize))) nodeId -> MockAdapterRequestHandler(new AdapterService(nodeId, signerVerifier, maxSignatureAge, adapterMap)) }) } private lazy val shrineService: QepService = { val destinations: Set[NodeHandle] = { (for { (nodeId, adapterRequestHandler) <- adaptersByNodeId } yield { NodeHandle(nodeId, MockAdapterClient(nodeId, adapterRequestHandler)) }).toSet } QepService( "example.com", MockAuditDao, MockAuthenticator, MockQueryAuthorizationService, true, SigningBroadcastAndAggregationService(InJvmBroadcasterClient(AdapterClientBroadcaster(destinations, MockHubDao)), signerVerifier, SigningCertStrategy.Attach), 1.hour, DefaultBreakdownResultOutputTypes.toSet, false) } @Test def testSimulatedNetwork = afterCreatingTables { val authn = AuthenticationInfo(domain, username, Credential(password, false)) val masterId = 12345L import scala.concurrent.duration._ val req = DeleteQueryRequest("some-project-id", 1.second, authn, masterId) val resp = shrineService.deleteQuery(req, true) for { (nodeId, mockAdapter) <- adaptersByNodeId } { mockAdapter.lastMessage.networkAuthn.domain should equal(authn.domain) mockAdapter.lastMessage.networkAuthn.username should equal(authn.username) mockAdapter.lastMessage.request should equal(req) mockAdapter.lastResult.response should equal(DeleteQueryResponse(masterId)) } resp should equal(DeleteQueryResponse(masterId)) } @Test def testQueueQuery = afterCreatingTables { val authn = AuthenticationInfo(domain, username, Credential(password, false)) val topicId = "askldjlkas" val topicName = "Topic Name" val queryName = "lsadj3028940" import scala.concurrent.duration._ val runQueryReq = RunQueryRequest("some-project-id", 1.second, authn, 12345L, Some(topicId), Some(topicName), Set(ResultOutputType.PATIENT_COUNT_XML), QueryDefinition(queryName, Term("n1"))) val aggregatedRunQueryResp = shrineService.runQuery(runQueryReq, true).asInstanceOf[AggregatedRunQueryResponse] var broadcastMessageId: Option[Long] = None //Broadcast the original run query request; all nodes should queue the query for { (nodeId, mockAdapter) <- adaptersByNodeId } { broadcastMessageId = Option(mockAdapter.lastMessage.requestId) mockAdapter.lastMessage.networkAuthn.domain should equal(authn.domain) mockAdapter.lastMessage.networkAuthn.username should equal(authn.username) val lastReq = mockAdapter.lastMessage.request.asInstanceOf[RunQueryRequest] lastReq.authn should equal(runQueryReq.authn) lastReq.requestType should equal(runQueryReq.requestType) lastReq.waitTime should equal(runQueryReq.waitTime) //todo what to do with this check? lastReq.networkQueryId should equal(mockAdapter.lastMessage.requestId) lastReq.outputTypes should equal(runQueryReq.outputTypes) lastReq.projectId should equal(runQueryReq.projectId) lastReq.queryDefinition should equal(runQueryReq.queryDefinition) lastReq.topicId should equal(runQueryReq.topicId) val runQueryResp = mockAdapter.lastResult.response.asInstanceOf[RunQueryResponse] runQueryResp.queryId should equal(-1L) runQueryResp.singleNodeResult.statusType should equal(QueryResult.StatusType.Held) runQueryResp.singleNodeResult.setSize should equal(-1L) } aggregatedRunQueryResp.queryId should equal(broadcastMessageId.get) aggregatedRunQueryResp.results.map(_.setSize) should equal(Seq(-1L, -1L, -1L, -1L, -4L)) } @Test def testFlagQuery = afterCreatingTables { val authn = AuthenticationInfo(domain, username, Credential(password, false)) val masterId = 12345L import scala.concurrent.duration._ val networkQueryId = 9999L val name = "some query" val expr = Term("foo") val fooQuery = QueryDefinition(name,expr) dao.insertQuery(masterId.toString, networkQueryId, authn, fooQuery, isFlagged = false, hasBeenRun = true, flagMessage = None) dao.findQueryByNetworkId(networkQueryId).get.isFlagged should be(false) dao.findQueryByNetworkId(networkQueryId).get.flagMessage should be(None) val req = FlagQueryRequest("some-project-id", 1.second, authn, networkQueryId, Some("foo")) val resp = shrineService.flagQuery(req, true) resp should equal(FlagQueryResponse) dao.findQueryByNetworkId(networkQueryId).get.isFlagged should be(true) dao.findQueryByNetworkId(networkQueryId).get.flagMessage should be(Some("foo")) } @Test def testUnFlagQuery = afterCreatingTables { val authn = AuthenticationInfo(domain, username, Credential(password, false)) val masterId = 12345L import scala.concurrent.duration._ val networkQueryId = 9999L val flagMsg = Some("foo") val name = "some query" val expr = Term("foo") val fooQuery = QueryDefinition(name,expr) dao.insertQuery(masterId.toString, networkQueryId, authn, fooQuery, isFlagged = true, hasBeenRun = true, flagMessage = flagMsg) dao.findQueryByNetworkId(networkQueryId).get.isFlagged should be(true) dao.findQueryByNetworkId(networkQueryId).get.flagMessage should be(flagMsg) val req = UnFlagQueryRequest("some-project-id", 1.second, authn, networkQueryId) val resp = shrineService.unFlagQuery(req, true) resp should equal(UnFlagQueryResponse) dao.findQueryByNetworkId(networkQueryId).get.isFlagged should be(false) dao.findQueryByNetworkId(networkQueryId).get.flagMessage should be(None) } } object NetworkSimulationTest { private final case class MockAdapterClient(nodeId: NodeId, adapter: AdapterRequestHandler) extends AdapterClient with Loggable { import scala.concurrent.ExecutionContext.Implicits.global override def query(message: BroadcastMessage): Future[Result] = Future.successful { debug(s"Invoking Adapter $nodeId with $message") val result = adapter.handleRequest(message) debug(s"Got result from $nodeId: $result") result } override def url: Option[URL] = ??? } private final case class MockAdapterRequestHandler(delegate: AdapterRequestHandler) extends AdapterRequestHandler { @volatile var lastMessage: BroadcastMessage = _ @volatile var lastResult: Result = _ override def handleRequest(request: BroadcastMessage): Result = { lastMessage = request val result = delegate.handleRequest(request) lastResult = result result } } } diff --git a/qep/service/src/main/scala/net/shrine/qep/QueryEntryPointComponents.scala b/qep/service/src/main/scala/net/shrine/qep/QueryEntryPointComponents.scala index f9ba20d1f..62a9f7245 100644 --- a/qep/service/src/main/scala/net/shrine/qep/QueryEntryPointComponents.scala +++ b/qep/service/src/main/scala/net/shrine/qep/QueryEntryPointComponents.scala @@ -1,124 +1,125 @@ package net.shrine.qep import com.typesafe.config.Config import net.shrine.authentication.{AuthenticationType, Authenticator, PmAuthenticator} import net.shrine.authorization.{AllowsAllAuthorizationService, AuthorizationType, QueryAuthorizationService, StewardQueryAuthorizationService} import net.shrine.broadcaster.dao.HubDao import net.shrine.broadcaster.{BroadcastAndAggregationService, NodeHandle, SigningBroadcastAndAggregationService} import net.shrine.client.Poster import net.shrine.config.ConfigExtensions import net.shrine.crypto.KeyStoreCertCollection +import net.shrine.crypto2.BouncyKeyStoreCollection import net.shrine.dao.squeryl.SquerylInitializer import net.shrine.hms.authentication.EcommonsPmAuthenticator import net.shrine.hms.authorization.HmsDataStewardAuthorizationService import net.shrine.log.Loggable import net.shrine.protocol.ResultOutputType import net.shrine.qep.dao.AuditDao import net.shrine.qep.dao.squeryl.SquerylAuditDao import net.shrine.qep.dao.squeryl.tables.Tables import net.shrine.util.{PeerToPeerModel, SingleHubModel, TrustModel} import scala.util.Try /** * @author david * @since 1.22 */ case class QueryEntryPointComponents(shrineService: QepService, i2b2Service: I2b2QepService, auditDao: AuditDao, //todo auditDao is only used by the happy service to grab the most recent entries trustModel: Option[TrustModel] ) object QueryEntryPointComponents extends Loggable { def apply( qepConfig:Config, - certCollection: KeyStoreCertCollection, + certCollection: BouncyKeyStoreCollection, breakdownTypes: Set[ResultOutputType], broadcastDestinations: Option[Set[NodeHandle]], hubDao: HubDao, //todo the QEP should not need the hub dao squerylInitializer: SquerylInitializer, //todo could really have its own pmPoster: Poster //todo could really have its own ):QueryEntryPointComponents = { - val commonName: String = certCollection.myCommonName.getOrElse { + val commonName: String = certCollection.myEntry.commonName.getOrElse { val hostname = java.net.InetAddress.getLocalHost.getHostName - warn(s"No common name available from ${certCollection.descriptor}. Using $hostname instead.") + warn(s"No common name available from ${certCollection.myEntry}. Using $hostname instead.") hostname } val broadcastService: BroadcastAndAggregationService = SigningBroadcastAndAggregationService( qepConfig, certCollection, breakdownTypes, broadcastDestinations, hubDao //todo the QEP should not need the hub dao ) val auditDao: AuditDao = new SquerylAuditDao(squerylInitializer, new Tables) val authenticator: Authenticator = AuthStrategy.determineAuthenticator(qepConfig, pmPoster) val authorizationService: QueryAuthorizationService = AuthStrategy.determineQueryAuthorizationService(qepConfig,authenticator) debug(s"authorizationService set to $authorizationService") QueryEntryPointComponents( QepService( qepConfig, commonName, auditDao, authenticator, authorizationService, broadcastService, breakdownTypes ), I2b2QepService( qepConfig, commonName, auditDao, authenticator, authorizationService, broadcastService, breakdownTypes ), auditDao, Try(qepConfig.getBoolean("trustModelIsHub")).toOption.map(if(_) SingleHubModel else PeerToPeerModel) ) } } /** * @author clint * @since Jul 1, 2014 */ object AuthStrategy { import AuthenticationType._ import AuthorizationType._ def determineAuthenticator(qepConfig:Config, pmPoster: Poster): Authenticator = { //todo put these default values in reference.conf if you decide to use one val defaultAuthenticationType: AuthenticationType = AuthenticationType.Pm val authType = qepConfig.getOption("authenticationType",_.getString).flatMap(AuthenticationType.valueOf).getOrElse(defaultAuthenticationType) authType match { case NoAuthentication => AllowsAllAuthenticator case Pm => PmAuthenticator(pmPoster) case Ecommons => EcommonsPmAuthenticator(pmPoster) case _ => throw new IllegalArgumentException(s"Unknown authentication type '$authType'") } } def determineQueryAuthorizationService(qepConfig:Config, authenticator: Authenticator): QueryAuthorizationService = { val defaultAuthorizationType: AuthorizationType = AuthorizationType.NoAuthorization //todo should default to DSA in the reference.conf instead of being optional val authorizationType = qepConfig.getOption("authorizationType",_.getString).flatMap(AuthorizationType.valueOf).getOrElse(defaultAuthorizationType) authorizationType match { case ShrineSteward => StewardQueryAuthorizationService(qepConfig.getConfig("shrineSteward")) case HmsSteward => HmsDataStewardAuthorizationService(qepConfig,authenticator) case NoAuthorization => AllowsAllAuthorizationService case _ => throw new IllegalArgumentException(s"Unknown authorization type '$authorizationType'") } } } \ No newline at end of file