Skip to content

Commit

Permalink
Refactor code that includes delegations in Snapshots
Browse files Browse the repository at this point in the history
Signed-off-by: Simão Mata <simao.mata@here.com>
  • Loading branch information
simao authored and Jochen Schneider committed Mar 19, 2019
1 parent 83574dc commit dd3fab7
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import java.time.Instant

import akka.http.scaladsl.model.Uri
import com.advancedtelematic.libats.data.DataType.Checksum
import com.advancedtelematic.libtuf.crypt.CanonicalJson._
import com.advancedtelematic.libtuf.data.ClientDataType.{MetaItem, MetaPath, _}
import com.advancedtelematic.libtuf.data.TufDataType.{JsonSignedPayload, RepoId, TargetFilename}
import io.circe.syntax._
import com.advancedtelematic.libtuf.data.TufCodecs._
import com.advancedtelematic.libtuf.crypt.CanonicalJson._
import com.advancedtelematic.libtuf.data.TufDataType.{JsonSignedPayload, RepoId, TargetFilename}
import com.advancedtelematic.libtuf_server.crypto.Sha256Digest
import io.circe.{Decoder, Json}
import io.circe.Decoder
import io.circe.syntax._

object RepositoryDataType {
object StorageMethod extends Enumeration {
Expand All @@ -25,7 +25,11 @@ object RepositoryDataType {

case class SignedRole[T : TufRole](repoId: RepoId, content: JsonSignedPayload, checksum: Checksum, length: Long, version: Int, expiresAt: Instant) {
def role(implicit dec: Decoder[T]): T =
RepositoryDataType.role[T](content.signed)
content.signed.as[T] match {
case Left(err) =>
throw new IllegalArgumentException(s"Could not decode a role saved in database as ${implicitly[TufRole[T]]} but not parseable as such a type: $err")
case Right(p) => p
}

def asMetaRole: (MetaPath, MetaItem) = {
val hashes = Map(checksum.method -> checksum.hash)
Expand All @@ -35,19 +39,6 @@ object RepositoryDataType {
def tufRole: TufRole[T] = implicitly[TufRole[T]]
}

private def role[T: TufRole](json: Json)(implicit dec: Decoder[T]): T = json.as[T] match {
case Left(err) =>
throw new IllegalArgumentException(s"Could not decode a role saved in database as ${implicitly[TufRole[T]]} but not parseable as such a type: $err")
case Right(p) => p
}

def asMetaItem(content: JsonSignedPayload)(implicit dec: Decoder[TargetsRole]): MetaItem = {
val canonicalJson = content.asJson.canonical
val checksum = Sha256Digest.digest(canonicalJson.getBytes)
val hashes = Map(checksum.method -> checksum.hash)
MetaItem(hashes, canonicalJson.length, role[TargetsRole](content.signed).version)
}

object SignedRole {
def withChecksum[T : TufRole](repoId: RepoId, content: JsonSignedPayload, version: Int, expireAt: Instant): SignedRole[T] = {
val canonicalJson = content.asJson.canonical
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import com.advancedtelematic.libats.slick.db.SlickExtensions._
import com.advancedtelematic.libats.slick.codecs.SlickRefined._
import com.advancedtelematic.libats.slick.db.SlickUUIDKey._
import com.advancedtelematic.libats.slick.db.SlickAnyVal._
import com.advancedtelematic.libtuf.data.ClientDataType.{DelegatedRoleName, TufRole}
import com.advancedtelematic.libtuf.data.ClientDataType.{DelegatedRoleName, SnapshotRole, TimestampRole, TufRole}
import com.advancedtelematic.libtuf_server.data.Requests.TargetComment
import com.advancedtelematic.libtuf_server.data.TufSlickMappings._
import com.advancedtelematic.tuf.reposerver.db.DBDataType.{DbDelegation, DbSignedRole}
Expand Down Expand Up @@ -289,8 +289,8 @@ trait DelegationRepositorySupport extends DatabaseSupport {

protected [db] class DelegationRepository()(implicit db: Database, ec: ExecutionContext) {

def find(repoId: RepoId, roleName: DelegatedRoleName): Future[DbDelegation] = db.run {
Schema.delegations.filter(_.repoId === repoId).filter(_.roleName === roleName).result.failIfNotSingle(Errors.DelegationNotFound)
def find(repoId: RepoId, roleNames: DelegatedRoleName*): Future[DbDelegation] = db.run {
Schema.delegations.filter(_.repoId === repoId).filter(_.roleName.inSet(roleNames)).result.failIfNotSingle(Errors.DelegationNotFound)
}

def persist(repoId: RepoId, roleName: DelegatedRoleName, content: JsonSignedPayload): Future[Unit] = db.run {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,66 @@
package com.advancedtelematic.tuf.reposerver.delegations

import java.time.Instant

import cats.data.Validated.{Invalid, Valid}
import cats.data.ValidatedNel
import com.advancedtelematic.libats.data.RefinedUtils._
import com.advancedtelematic.libtuf.crypt.TufCrypto
import com.advancedtelematic.libtuf.data.ClientCodecs._
import com.advancedtelematic.libtuf.data.ClientDataType.{DelegatedRoleName, Delegation, MetaItem, MetaPath, TargetsRole, ValidMetaPath}
import com.advancedtelematic.libtuf.data.TufDataType.{JsonSignedPayload, RepoId, SignedPayload}
import com.advancedtelematic.libtuf_server.keyserver.KeyserverClient
import com.advancedtelematic.tuf.reposerver.data.RepositoryDataType.{SignedRole, asMetaItem}
import com.advancedtelematic.libtuf_server.crypto.Sha256Digest
import com.advancedtelematic.tuf.reposerver.data.RepositoryDataType.SignedRole
import com.advancedtelematic.tuf.reposerver.db.{DelegationRepositorySupport, SignedRoleRepositorySupport}
import com.advancedtelematic.tuf.reposerver.http.{Errors, RepoRoleRefresh, RoleSigner}
import eu.timepit.refined.api.Refined
import com.advancedtelematic.libats.data.RefinedUtils._
import com.advancedtelematic.tuf.reposerver.http._
import slick.jdbc.MySQLProfile.api._

import scala.async.Async._
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try

class SignedRoleDelegationsFind()(implicit val db: Database, val ec: ExecutionContext) extends DelegationRepositorySupport {
import cats.implicits._
import com.advancedtelematic.libtuf.crypt.CanonicalJson._
import com.advancedtelematic.libtuf.data.TufCodecs._
import io.circe.syntax._

def findSignedTargetRoleDelegations(targetRole: SignedRole[TargetsRole]): Future[Map[MetaPath, MetaItem]] = {
val delegatedRoleNames = targetRole.role.delegations.map(_.roles.map(_.name)).getOrElse(List.empty)
val delegationsF =
delegatedRoleNames
.map { name => delegationsRepo.find(targetRole.repoId, name).map((name, _)) }
.sequence
.recover { case Errors.DelegationNotFound => List.empty }

for {
delegations <- delegationsF
delegationsAsMetaItems <- delegations.map { case (name, d) =>
Future.fromTry { (name.value + ".json").refineTry[ValidMetaPath].product(asMetaItem(d.content)) }
}.sequence
} yield delegationsAsMetaItems.toMap
}

private def asMetaItem(content: JsonSignedPayload): Try[MetaItem] = {
val canonicalJson = content.asJson.canonical
val checksum = Sha256Digest.digest(canonicalJson.getBytes)
val hashes = Map(checksum.method -> checksum.hash)
val versionT = content.signed.hcursor.downField("version").as[Int].toTry

versionT.map { version => MetaItem(hashes, canonicalJson.length, version) }
}
}


class DelegationsManagement()(implicit val db: Database, val ec: ExecutionContext)
extends DelegationRepositorySupport with SignedRoleRepositorySupport {

def create(repoId: RepoId, roleName: DelegatedRoleName, delegationMetadata: SignedPayload[TargetsRole]): Future[Unit] = async {
def create(repoId: RepoId, roleName: DelegatedRoleName, delegationMetadata: SignedPayload[TargetsRole])
(implicit signedRoleGeneration: SignedRoleGeneration): Future[Unit] = async {
val targetsRole = await(signedRoleRepository.find[TargetsRole](repoId)).role
val delegation = findDelegationMetadataByName(targetsRole, roleName)

validateDelegationMetadataSignatures(targetsRole, delegation, delegationMetadata) match {
case Valid(_) =>
await(delegationsRepo.persist(repoId, roleName, delegationMetadata.asJsonSignedPayload))
await(signedRoleGeneration.regenerateSnapshots(repoId))
case Invalid(err) =>
throw Errors.PayloadSignatureInvalid(err)
}
Expand All @@ -40,17 +72,6 @@ class DelegationsManagement()(implicit val db: Database, val ec: ExecutionContex
private def findDelegationMetadataByName(targetsRole: TargetsRole, delegatedRoleName: DelegatedRoleName): Delegation = {
targetsRole.delegations.flatMap(_.roles.find(_.name == delegatedRoleName)).getOrElse(throw Errors.DelegationNotDefined)
}

def findDelegations(targets: SignedRole[TargetsRole]): Future[Map[MetaPath, MetaItem]] = {
val delegatedRoleNames = targets.role.delegations.map(_.roles.map(_.name)).getOrElse(Nil)
val delegations = Future.sequence(delegatedRoleNames.map { name => find(targets.repoId, name).map((name, _)) })
.recover { case Errors.DelegationNotFound => Nil }

delegations.map(_.map { case (name: DelegatedRoleName, d: JsonSignedPayload) =>
(name.value + ".json").refineTry[ValidMetaPath].get -> asMetaItem(d)
}.toMap)
}

private def validateDelegationMetadataSignatures(targetsRole: TargetsRole,
delegation: Delegation,
delegationMetadata: SignedPayload[TargetsRole]): ValidatedNel[String, SignedPayload[TargetsRole]] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import com.advancedtelematic.libtuf.data.ClientDataType.TufRole._
import scala.async.Async.{async, await}
import scala.concurrent.{ExecutionContext, Future}
import com.advancedtelematic.libtuf_server.keyserver.KeyserverClient
import com.advancedtelematic.tuf.reposerver.delegations.DelegationsManagement
import com.advancedtelematic.tuf.reposerver.delegations.{SignedRoleDelegationsFind}
import com.advancedtelematic.tuf.reposerver.http.RoleChecksumHeader.RoleChecksum
import com.advancedtelematic.tuf.reposerver.target_store.TargetStore
import org.slf4j.LoggerFactory
Expand All @@ -33,8 +33,6 @@ class OfflineSignedRoleStorage(keyserverClient: KeyserverClient)

private val signedRoleGeneration = new SignedRoleGeneration(keyserverClient)

private val delegationsManagement = new DelegationsManagement()

def store(repoId: RepoId, signedPayload: SignedPayload[TargetsRole]): Future[ValidatedNel[String, (Seq[TargetItem], SignedRole[TargetsRole])]] =
for {
validatedPayloadSig <- payloadSignatureIsValid(repoId, signedPayload)
Expand All @@ -44,8 +42,8 @@ class OfflineSignedRoleStorage(keyserverClient: KeyserverClient)
case Valid(items) =>
val signedTargetRole = SignedRole.withChecksum[TargetsRole](repoId, signedPayload.asJsonSignedPayload, signedPayload.signed.version, signedPayload.signed.expires)
for {
(targets, timestamps) <- signedRoleGeneration.regenerateSignedDependent(repoId, signedTargetRole, signedPayload.signed.expires)
_ <- signedRoleRepository.storeAll(targetItemRepo)(repoId: RepoId, List(signedTargetRole, targets, timestamps), items)
(targets, timestamps) <- signedRoleGeneration.freshSignedDependent(repoId, signedTargetRole, signedPayload.signed.expires)
_ <- signedRoleRepository.storeAll(targetItemRepo)(repoId: RepoId, List(signedTargetRole, targets, timestamps), items)
} yield (existingTargets, signedTargetRole).validNel
case i @ Invalid(_) =>
FastFuture.successful(i)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class RepoResource(keyserverClient: KeyserverClient, namespaceValidation: Namesp
with SignedRoleRepositorySupport
with Settings {

private val signedRoleGeneration = new SignedRoleGeneration(keyserverClient)
private implicit val signedRoleGeneration = new SignedRoleGeneration(keyserverClient)
private val offlineSignedRoleStorage = new OfflineSignedRoleStorage(keyserverClient)
private val delegations = new DelegationsManagement()

Expand Down Expand Up @@ -213,7 +213,7 @@ class RepoResource(keyserverClient: KeyserverClient, namespaceValidation: Namesp
} ~
path("delegations" / DelegatedRoleUriPath) { delegatedRoleName =>
(put & entity(as[SignedPayload[TargetsRole]])) { payload =>
complete(signedRoleGeneration.createDelegatedRole(repoId, delegatedRoleName, payload).map(_ => StatusCodes.NoContent))
complete(delegations.create(repoId, delegatedRoleName, payload).map(_ => StatusCodes.NoContent))
} ~
get {
complete(delegations.find(repoId, delegatedRoleName))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,32 @@ package com.advancedtelematic.tuf.reposerver.http
import java.time.Instant
import java.time.temporal.ChronoUnit

import com.advancedtelematic.libtuf.data.ClientCodecs._
import com.advancedtelematic.libtuf.data.ClientDataType.{MetaItem, MetaPath, SnapshotRole, TargetsRole, TimestampRole, TufRole, _}
import com.advancedtelematic.libtuf.data.TufDataType.{JsonSignedPayload, RepoId}
import com.advancedtelematic.libtuf.data.TufDataType.RepoId
import com.advancedtelematic.libtuf_server.keyserver.KeyserverClient
import com.advancedtelematic.tuf.reposerver.data.RepositoryDataType.SignedRole
import com.advancedtelematic.tuf.reposerver.db.SignedRoleRepositorySupport
import com.advancedtelematic.tuf.reposerver.delegations.{SignedRoleDelegationsFind, DelegationsManagement}
import io.circe.syntax._
import io.circe.{Decoder, Encoder}
import slick.jdbc.MySQLProfile.api._
import com.advancedtelematic.libtuf.data.ClientCodecs._
import com.advancedtelematic.tuf.reposerver.delegations.DelegationsManagement

import scala.async.Async.{async, await}
import scala.concurrent.{ExecutionContext, Future}


class RoleSigner(repoId: RepoId, keyserverClient: KeyserverClient)(implicit ec: ExecutionContext) {
protected class RepoRoleSigner(repoId: RepoId, keyserverClient: KeyserverClient)(implicit ec: ExecutionContext) {
def apply[T : Encoder](role: T)(implicit tufRole: TufRole[T]): Future[SignedRole[T]] = {
keyserverClient.sign(repoId, tufRole.roleType, role.asJson).map { signedRole =>
SignedRole.withChecksum[T](repoId, signedRole, role.version, role.expires)
}
}
}

class RepoRoleRefresh(signFn: RoleSigner)(implicit val db: Database, val ec: ExecutionContext) extends SignedRoleRepositorySupport {
val roleRefresh = new RoleRefresh(signFn)
class RepoRoleRefresh(keyserverClient: KeyserverClient)(implicit val db: Database, val ec: ExecutionContext) extends SignedRoleRepositorySupport {
val roleRefresh: RepoId => RoleRefresh = repoId => new RoleRefresh(new RepoRoleSigner(repoId, keyserverClient))
val delegationsManagement = new DelegationsManagement()
val targetRoleDelegationFind = new SignedRoleDelegationsFind()

private def findExisting[T](repoId: RepoId)(implicit tufRole: TufRole[T]): Future[SignedRole[T]] = {
signedRoleRepository.find[T](repoId)
Expand All @@ -43,38 +43,38 @@ class RepoRoleRefresh(signFn: RoleSigner)(implicit val db: Database, val ec: Exe
val existingTargets = await(findExisting[TargetsRole](repoId))
val existingSnapshots = await(findExisting[SnapshotRole](repoId))
val existingTimestamps = await(findExisting[TimestampRole](repoId))
val delegations = await(delegationsManagement.findDelegations(existingTargets))
val (newTargets, dependencies) = await(roleRefresh.refreshTargets(existingTargets, existingTimestamps, existingSnapshots, delegations))
val existingDelegations = await(targetRoleDelegationFind.findSignedTargetRoleDelegations(existingTargets))
val (newTargets, dependencies) = await(roleRefresh(repoId).refreshTargets(existingTargets, existingTimestamps, existingSnapshots, existingDelegations))
await(commitRefresh(newTargets, dependencies))
}

def refreshSnapshots(repoId: RepoId): Future[SignedRole[SnapshotRole]] = async {
val existingTargets = await(findExisting[TargetsRole](repoId))
val existingSnapshots = await(findExisting[SnapshotRole](repoId))
val existingTimestamps = await(findExisting[TimestampRole](repoId))
val delegations = await(delegationsManagement.findDelegations(existingTargets))
val delegations = await(targetRoleDelegationFind.findSignedTargetRoleDelegations(existingTargets))

val (newSnapshots, dependencies) = await(roleRefresh.refreshSnapshots(existingSnapshots, existingTimestamps, existingTargets, delegations))
val (newSnapshots, dependencies) = await(roleRefresh(repoId).refreshSnapshots(existingSnapshots, existingTimestamps, existingTargets, delegations))
await(commitRefresh(newSnapshots, dependencies))
}

def refreshTimestamp(repoId: RepoId): Future[SignedRole[TimestampRole]] = async {
val existingTimestamp = await(findExisting[TimestampRole](repoId))
val existingSnapshots = await(findExisting[SnapshotRole](repoId))
val newTimestamp = await(roleRefresh.refreshTimestamps(existingTimestamp, existingSnapshots))
val newTimestamp = await(roleRefresh(repoId).refreshTimestamps(existingTimestamp, existingSnapshots))
await(commitRefresh(newTimestamp, List.empty))
}
}

class RoleRefresh(signFn: RoleSigner)(implicit ec: ExecutionContext) {
protected class RoleRefresh(signFn: RepoRoleSigner)(implicit ec: ExecutionContext) {

def refreshTargets(existingTargets: SignedRole[TargetsRole],
existingTimestamps: SignedRole[TimestampRole],
existingSnapshots: SignedRole[SnapshotRole],
delegations: Map[MetaPath, MetaItem]): Future[(SignedRole[TargetsRole], List[SignedRole[_]])] = async {
existingDelegations: Map[MetaPath, MetaItem]): Future[(SignedRole[TargetsRole], List[SignedRole[_]])] = async {
val newTargetsRole = refreshRole[TargetsRole](existingTargets)
val signedTargets = await(signFn(newTargetsRole))
val (newSnapshots, dependencies) = await(refreshSnapshots(existingSnapshots, existingTimestamps, signedTargets, delegations))
val (newSnapshots, dependencies) = await(refreshSnapshots(existingSnapshots, existingTimestamps, signedTargets, existingDelegations))

(signedTargets, newSnapshots :: dependencies)
}
Expand All @@ -85,11 +85,10 @@ class RoleRefresh(signFn: RoleSigner)(implicit ec: ExecutionContext) {
delegations: Map[MetaPath, MetaItem]): Future[(SignedRole[SnapshotRole], List[SignedRole[_]])] = async {
val refreshed = refreshRole[SnapshotRole](existingSnapshots)

val newMeta: Map[MetaPath, MetaItem] = existingSnapshots.role.meta + newTargets.asMetaRole ++ delegations
val newMeta = existingSnapshots.role.meta + newTargets.asMetaRole ++ delegations
val newSnapshot = SnapshotRole(newMeta, refreshed.expires, refreshed.version)

val signedSnapshot = await(signFn(newSnapshot))

val timestampRole = await(refreshTimestamps(existingTimestamps, signedSnapshot))

(signedSnapshot, List(timestampRole))
Expand Down
Loading

0 comments on commit dd3fab7

Please sign in to comment.