Skip to content

Commit

Permalink
Merge pull request #544 from bclouser/tdx-master
Browse files Browse the repository at this point in the history
Merge latest toradex changes into master
  • Loading branch information
bclouser authored Jul 6, 2023
2 parents 3ec9bc7 + 0baa3c4 commit eb9f41b
Show file tree
Hide file tree
Showing 21 changed files with 399 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.advancedtelematic.tuf.keyserver.data

import java.time.Instant
import java.util.UUID

import com.advancedtelematic.libats.data.UUIDKey.{UUIDKey, UUIDKeyObj}
import com.advancedtelematic.libtuf.data.ClientDataType.RootRole
import com.advancedtelematic.libtuf.data.ClientDataType.{RootRole, TufRole}
import com.advancedtelematic.libtuf.data.TufDataType.RoleType.RoleType
import com.advancedtelematic.libtuf.data.TufDataType.{KeyId, KeyType, RepoId, SignedPayload, TufKey, TufKeyPair, TufPrivateKey}
import com.advancedtelematic.tuf.keyserver.data.KeyServerDataType.KeyGenRequestStatus.KeyGenRequestStatus

import java.time.Instant
import java.util.UUID
import scala.util.Try

object KeyServerDataType {
Expand All @@ -30,18 +29,20 @@ object KeyServerDataType {
require(keyType.crypto.validKeySize(keySize), s"Invalid keysize ($keySize) for $keyType")
}

object SignedRootRole {
import com.advancedtelematic.libtuf.data.ClientCodecs._

def fromSignedPayload(repoId: RepoId, payload: SignedPayload[RootRole]): SignedRootRole = {
val content = SignedPayload(payload.signatures, payload.signed, payload.json)
SignedRootRole(repoId, content, payload.signed.expires, payload.signed.version)
}
implicit class SignedPayloadDbOps(value: SignedPayload[RootRole]) {
def toDbSignedRole(repoId: RepoId): SignedRootRole =
SignedRootRole(repoId, value, value.signed.expires, value.signed.version)
}

case class SignedRootRole(repoId: RepoId, content: SignedPayload[RootRole], expiresAt: Instant, version: Int)

case class Key(id: KeyId, repoId: RepoId, roleType: RoleType, keyType: KeyType, publicKey: TufKey, privateKey: TufPrivateKey) {
def toTufKeyPair: Try[TufKeyPair] = keyType.crypto.castToKeyPair(publicKey, privateKey)
}

implicit class TufKeyDbOps(value: TufKeyPair) {
def toDbKey(repoId: RepoId, roleType: RoleType): Key = {
Key(value.pubkey.id, repoId, roleType, value.pubkey.keytype, value.pubkey, value.privkey)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package com.advancedtelematic.tuf.keyserver.db

import com.advancedtelematic.libtuf.data.TufDataType._
import com.advancedtelematic.tuf.keyserver.data.KeyServerDataType._
import com.advancedtelematic.tuf.keyserver.data.KeyServerDataType.KeyGenRequestStatus
import com.advancedtelematic.tuf.keyserver.data.KeyServerDataType.KeyGenRequestStatus.KeyGenRequestStatus
import com.advancedtelematic.libats.http.Errors.{EntityAlreadyExists, MissingEntity}
import com.advancedtelematic.libats.slick.codecs.SlickRefined._
import com.advancedtelematic.libtuf.data.ClientDataType.RootRole
import com.advancedtelematic.libats.slick.db.SlickUUIDKey._
import com.advancedtelematic.libats.slick.db.SlickExtensions._
import com.advancedtelematic.libats.slick.db.SlickUUIDKey._
import com.advancedtelematic.libtuf.data.ClientDataType.RootRole
import com.advancedtelematic.libtuf.data.TufDataType.RoleType.RoleType
import com.advancedtelematic.libtuf.data.TufDataType._
import com.advancedtelematic.tuf.keyserver.data.KeyServerDataType.KeyGenRequestStatus.KeyGenRequestStatus
import com.advancedtelematic.tuf.keyserver.data.KeyServerDataType.{KeyGenRequestStatus, _}
import com.advancedtelematic.tuf.keyserver.db.SlickMappings._
import slick.jdbc.MySQLProfile.api._

import scala.concurrent.{ExecutionContext, Future}
import SlickMappings._

trait DatabaseSupport {
val ec: ExecutionContext
Expand Down Expand Up @@ -98,15 +98,12 @@ object KeyRepository {
}

protected [db] class KeyRepository()(implicit db: Database, ec: ExecutionContext) {
import Schema.keys
import KeyRepository._
import Schema.keys
import com.advancedtelematic.libats.slick.db.SlickPipeToUnit.pipeToUnit

def persist(key: Key): Future[Unit] = db.run(persistAction(key))

protected [db] def deleteRepoKeys(repoId: RepoId, keysToDelete: Set[KeyId]): DBIO[Unit] =
keys.filter(_.repoId === repoId).filter(_.id.inSet(keysToDelete)).delete.map(_ => ())

protected [db] def keepOnlyKeys(repoId: RepoId, keysToKeep: Set[KeyId]): DBIO[Unit] =
keys.filter(_.repoId === repoId).filterNot(_.id.inSet(keysToKeep)).delete.map(_ => ())

Expand Down Expand Up @@ -154,6 +151,18 @@ protected[db] class SignedRootRoleRepository()(implicit db: Database, ec: Execut
keyRepository.keepOnlyKeys(signedRootRole.repoId, keysToKeep).andThen(persistAction(signedRootRole).transactionally)
}

def persistWithKeys(keyRepository: KeyRepository)(signedRootRole: SignedRootRole,
newKeys: Map[RoleType, List[TufKeyPair]]): Future[Unit] = db.run {
val keys = newKeys
.flatMap { case (roleType, keyPairs) => keyPairs.map(_.toDbKey(signedRootRole.repoId, roleType)) }
.toSeq

DBIO.seq(
keyRepository.persistAllAction(keys),
persistAction(signedRootRole)
).transactionally
}

protected [db] def persistAction(signedRootRole: SignedRootRole): DBIO[Unit] = {
(signedRootRoles += signedRootRole)
.handleIntegrityErrors(RootRoleExists)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ object Errors {
val RepoRootKeysNotFound = RawError(ErrorCodes.KeyServer.RepoRootKeysNotFound, StatusCodes.NotFound, "Repository root keys not available/offline")
val RoleKeysNotFound = RawError(ErrorCodes.KeyServer.RoleKeysNotFound, StatusCodes.NotFound, "There are no keys for this repoid/roletype")
val PrivateKeysNotFound = RawError(ErrorCodes.KeyServer.PrivateKeysNotFound, StatusCodes.PreconditionFailed, "There are no private keys for that role")
val KeysOffline = RawError(ErrorCodes.KeyServer.KeysOffline, StatusCodes.PreconditionFailed, "private keys are offline")
val KeysReadError = RawError(ErrorCodes.KeyServer.KeysReadError, StatusCodes.InternalServerError, "private keys could not be read from the database")

def KeyGenerationFailed(repoId: RepoId, errors: Map[KeyGenId, String]) =
JsonError(ErrorCodes.KeyServer.KeyGenerationFailed, StatusCodes.InternalServerError, errors.asJson, "Could not generate keys")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}

class RootRoleResource()
(implicit val db: Database, val ec: ExecutionContext, mat: Materializer)
(implicit val db: Database, val ec: ExecutionContext)
extends KeyGenRequestSupport with Settings {
import ClientRootGenRequest._
import akka.http.scaladsl.server.Directives._
Expand Down Expand Up @@ -78,6 +78,11 @@ class RootRoleResource()
complete(f)
}
} ~
(path("rotate") & put) {
onSuccess(rootRoleKeyEdit.rotate(repoId)) {
complete(StatusCodes.OK)
}
} ~
path("1") {
val f = signedRootRoles
.findByVersion(repoId, version = 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import slick.jdbc.MySQLProfile.api._

class TufKeyserverRoutes(dependencyChecks: Seq[HealthCheck] = Seq.empty, metricsRoutes: Route = Directives.reject,
metricRegistry: MetricRegistry = MetricsSupport.metricRegistry)
(implicit val db: Database, val ec: ExecutionContext, system: ActorSystem, mat: Materializer)
(implicit val db: Database, val ec: ExecutionContext, mat: Materializer)
extends VersionInfo {

import Directives._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ extends KeyRepositorySupport with SignedRootRoleSupport {
}

private def persistSignedPayload(repoId: RepoId)(signedPayload: SignedPayload[RootRole]): Future[SignedRootRole] = {
val signedRootRole = SignedRootRole.fromSignedPayload(repoId, signedPayload)
val signedRootRole = signedPayload.toDbSignedRole(repoId)
signedRootRoleRepo.persist(signedRootRole).map(_ => signedRootRole)
}

Expand All @@ -115,7 +115,7 @@ extends KeyRepositorySupport with SignedRootRoleSupport {
userSignedIsValid <- offlineSignedParsedV match {
case Valid(offlineSignedParsed) =>
val newOnlineKeys = offlineSignedParsed.signed.keys.values.map(_.id).toSet
val signedRootRole = SignedRootRole.fromSignedPayload(repoId, offlineSignedParsed)
val signedRootRole = offlineSignedParsed.toDbSignedRole(repoId)
signedRootRoleRepo.persistAndKeepRepoKeys(keyRepo)(signedRootRole, newOnlineKeys).map(_ => Valid(signedRootRole))

case r@Invalid(_) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package com.advancedtelematic.tuf.keyserver.roles
import akka.http.scaladsl.util.FastFuture
import com.advancedtelematic.libtuf.crypt.TufCrypto
import com.advancedtelematic.libtuf.data.TufDataType.RoleType.RoleType
import com.advancedtelematic.libtuf.data.TufDataType.{RepoId, _}
import com.advancedtelematic.tuf.keyserver.db._
import com.advancedtelematic.libtuf.data.TufDataType.{RepoId, *}
import com.advancedtelematic.tuf.keyserver.db.*
import com.advancedtelematic.tuf.keyserver.http.Errors
import io.circe.Json
import slick.jdbc.MySQLProfile.api._
import io.circe.syntax.EncoderOps
import io.circe.{Encoder, Json}
import slick.jdbc.MySQLProfile.api.*

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

class RoleSigning()(implicit val db: Database, val ec: ExecutionContext)
Expand Down Expand Up @@ -45,6 +46,17 @@ class RoleSigning()(implicit val db: Database, val ec: ExecutionContext)
}
}

protected [roles] def signWithPrivateKeys[T : Encoder](payload: T, privateKeys: Seq[TufKeyPair]): SignedPayload[T] = {
val payloadJson = payload.asJson

val signatures = privateKeys.toList.map { key =>
val signature = TufCrypto.signPayload(key.privkey, payloadJson)
ClientSignature(key.pubkey.id, signature.method, signature.sig)
}

SignedPayload(signatures, payload, payloadJson)
}

private def fetchPrivateKey(key: TufKey): Future[TufPrivateKey] =
keyRepo.find(key.id).recoverWith {
case KeyRepository.KeyNotFound =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package com.advancedtelematic.tuf.keyserver.roles

import com.advancedtelematic.libtuf.data.TufDataType.{KeyId, RepoId, TufKeyPair}
import akka.http.scaladsl.util.FastFuture
import cats.implicits._
import com.advancedtelematic.tuf.keyserver.db.KeyRepositorySupport
import com.advancedtelematic.libtuf.crypt.TufCrypto
import com.advancedtelematic.libtuf.data.ClientCodecs._
import com.advancedtelematic.libtuf.data.RootManipulationOps._
import com.advancedtelematic.libtuf.data.TufDataType.RoleType.RoleType
import com.advancedtelematic.libtuf.data.TufDataType.{KeyId, KeyType, RepoId, RoleType, TufKeyPair}
import com.advancedtelematic.tuf.keyserver.data.KeyServerDataType.SignedPayloadDbOps
import com.advancedtelematic.tuf.keyserver.db.KeyRepository.KeyNotFound
import com.advancedtelematic.tuf.keyserver.db.{KeyRepositorySupport, SignedRootRoleSupport}
import com.advancedtelematic.tuf.keyserver.http.Errors
import slick.jdbc.MySQLProfile.api._

import scala.async.Async.{async, await}
import scala.concurrent.{ExecutionContext, Future}
import com.advancedtelematic.tuf.keyserver.db.KeyRepository.KeyNotFound
import com.advancedtelematic.libtuf.data.TufDataType.RoleType.RoleType
import slick.jdbc.MySQLProfile.api._
import cats.implicits._
import com.advancedtelematic.libtuf.data.RootManipulationOps._

class RootRoleKeyEdit()
(implicit val db: Database, val ec: ExecutionContext)
extends KeyRepositorySupport {
extends KeyRepositorySupport with SignedRootRoleSupport {
val roleSigning = new RoleSigning()
val signedRootRole = new SignedRootRoles()

Expand All @@ -22,6 +28,50 @@ class RootRoleKeyEdit()
_ <- keyRepo.delete(keyId)
} yield ()


def rotate(repoId: RepoId): Future[Unit] = async {
val unsigned = await(signedRootRole.findForSign(repoId))
var newRoot = unsigned

val newRootKeys = List.fill(newRoot.roles.get(RoleType.ROOT).map(_.keyids).toList.flatten.size) {
TufCrypto.generateKeyPair(KeyType.default, KeyType.default.crypto.defaultKeySize)
}

val newTargetsKeys = List.fill(newRoot.roles.get(RoleType.TARGETS).map(_.keyids).toList.flatten.size) {
TufCrypto.generateKeyPair(KeyType.default, KeyType.default.crypto.defaultKeySize)
}

if (newTargetsKeys.nonEmpty) {
newRoot = newRoot.withRoleKeys(RoleType.TARGETS, newTargetsKeys.map(_.pubkey):_*)
}

if (newRootKeys.nonEmpty) {
newRoot = newRoot.withRoleKeys(RoleType.ROOT, newRootKeys.map(_.pubkey):_*)
}

val oldPrivateKeys = await {
keyRepo.findAll(unsigned.roleKeys(RoleType.ROOT).map(_.id))
.recoverWith {
case KeyNotFound =>
FastFuture.failed(Errors.KeysOffline)
}
}

val oldKeyPairs = oldPrivateKeys.map { k =>
k.toTufKeyPair.toEither.valueOr(_ => throw Errors.KeysReadError)
}

val signedPayload = roleSigning.signWithPrivateKeys(newRoot, newRootKeys ++ oldKeyPairs)

val newKeys = Map(
RoleType.ROOT -> newRootKeys,
RoleType.TARGETS -> newTargetsKeys,
)

await(signedRootRoleRepo.persistWithKeys(keyRepo)(signedPayload.toDbSignedRole(repoId), newKeys))
}


def findAllKeyPairs(repoId: RepoId, roleType: RoleType): Future[Seq[TufKeyPair]] =
for {
rootRole <- signedRootRole.findLatest(repoId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,6 @@ class RootRoleResourceSpec extends TufKeyserverSpec
}

Post(apiUri(s"root/${repoId.show}/targets"), Json.Null) ~> routes ~> check {
println(responseAs[Json].noSpaces)

status shouldBe StatusCodes.PreconditionFailed
}
}
Expand Down Expand Up @@ -895,6 +893,57 @@ class RootRoleResourceSpec extends TufKeyserverSpec
offlineTargetsKeys.threshold shouldBe 1
}

test("rotate key fails with specific error when keys are offline") {
val repoId = RepoId.generate()
generateRootRole(repoId, Ed25519KeyType).futureValue

val oldRoot = fetchLatestRootOk(repoId).signed
val rootKeyId = oldRoot.roles(RoleType.ROOT).keyids.headOption.value

Delete (apiUri(s"root/${repoId.show}/private_keys/${rootKeyId.value}")) ~> routes ~> check {
status shouldBe StatusCodes.NoContent
}

val error = Put(apiUri(s"root/${repoId.show}/rotate")) ~> routes ~> check {
status shouldBe StatusCodes.PreconditionFailed
responseAs[ErrorRepresentation]
}

error.code shouldBe ErrorCodes.KeyServer.KeysOffline
}

test("rotates the key") {
val repoId = RepoId.generate()
generateRootRole(repoId, Ed25519KeyType).futureValue

val oldRoot = fetchLatestRootOk(repoId).signed

Put(apiUri(s"root/${repoId.show}/rotate")) ~> routes ~> check {
status shouldBe StatusCodes.OK
}

val newSignedRoot = fetchLatestRootOk(repoId)
val newRoot = newSignedRoot.signed

newRoot.version shouldBe oldRoot.version + 1

val newKeys = newRoot.roles.filterKeys(r => r == RoleType.ROOT || r == RoleType.TARGETS)
.values.flatMap(_.keyids).toSet
val oldKeys = oldRoot.roles.filterKeys(r => r == RoleType.ROOT || r == RoleType.TARGETS)
.values.flatMap(_.keyids).toSet

newKeys.intersect(oldKeys) shouldBe empty

val signedWithKeys = newSignedRoot.signatures.map(_.keyid)

val oldRootKeys = oldRoot.roles(RoleType.ROOT).keyids
val newRootKeys = newRoot.roles(RoleType.ROOT).keyids

signedWithKeys should contain allElementsOf oldRootKeys
signedWithKeys should contain allElementsOf newRootKeys
}


def fetchLatestRootOk(repoId: RepoId): SignedPayload[RootRole] = {
Get(apiUri(s"root/${repoId.show}")) ~> routes ~> check {
status shouldBe StatusCodes.OK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ trait KeyserverClient {

def fetchUnsignedRoot(repoId: RepoId): Future[RootRole]

def rotateRoot(repoId: RepoId): Future[Unit]

def updateRoot(repoId: RepoId, signedPayload: SignedPayload[RootRole]): Future[Unit]

def deletePrivateKey(repoId: RepoId, keyId: KeyId): Future[Unit]
Expand Down Expand Up @@ -151,4 +153,12 @@ class KeyserverHttpClient(uri: Uri, httpClient: HttpRequest => Future[HttpRespon
val req = HttpRequest(HttpMethods.PUT, uri = apiUri(Path("root") / repoId.show / "roles" / "remote-sessions"))
execHttpUnmarshalled[Unit](req).ok
}

override def rotateRoot(repoId: RepoId): Future[Unit] = {
val req = HttpRequest(HttpMethods.PUT, uri = apiUri(Path("root") / repoId.show / "rotate"))
execHttpUnmarshalled[Unit](req).handleErrors {
case RemoteServiceError(_, StatusCodes.PreconditionFailed, _, _, _, _) =>
Future.failed(RoleKeyNotFound)
}
}
}
Loading

0 comments on commit eb9f41b

Please sign in to comment.