Skip to content

Commit

Permalink
feat: add revocation for JWT credentials (#934)
Browse files Browse the repository at this point in the history
Signed-off-by: Shota Jolbordi <[email protected]>
  • Loading branch information
shotexa authored Mar 19, 2024
1 parent a699628 commit 88b7fa5
Show file tree
Hide file tree
Showing 77 changed files with 2,976 additions and 141 deletions.
12 changes: 6 additions & 6 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Link to any discussion, related issues and bug reports to give the context to he
Link to existing ADR (Architecture Decision Record), if any. If relevant, describe other approaches explored and the selected approach. Documenting why the methods were not selected will create a knowledge base for future reference, helping prevent others from revisiting less optimal ideas.

### Checklist:
- [] My PR follows the [contribution guidelines](https://github.com/hyperledger-labs/open-enterprise-agent/blob/main/CONTRIBUTING.md) of this project
- [] My PR is free of third-party dependencies that don't comply with the [Allowlist](https://toc.hyperledger.org/governing-documents/allowed-third-party-license-policy.html#approved-licenses-for-allowlist)
- [] I have commented my code, particularly in hard-to-understand areas
- [] I have made corresponding changes to the documentation
- [] I have added tests that prove my fix is effective or that my feature works
- [] I have checked the PR title to follow the [conventional commit specification](https://www.conventionalcommits.org/en/v1.0.0/)
- [ ] My PR follows the [contribution guidelines](https://github.com/hyperledger-labs/open-enterprise-agent/blob/main/CONTRIBUTING.md) of this project
- [ ] My PR is free of third-party dependencies that don't comply with the [Allowlist](https://toc.hyperledger.org/governing-documents/allowed-third-party-license-policy.html#approved-licenses-for-allowlist)
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] I have checked the PR title to follow the [conventional commit specification](https://www.conventionalcommits.org/en/v1.0.0/)
14 changes: 7 additions & 7 deletions .github/workflows/performance-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ on:

env:
BENCHMARKING_DIR: "tests/performance-tests/atala-performance-tests-k6"
NODE_AUTH_TOKEN: ${{ secrets.ATALA_GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_ACTOR: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
run-e2e-tests:
Expand All @@ -35,19 +37,17 @@ jobs:
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ secrets.ATALA_GITHUB_ACTOR }}
password: ${{ secrets.ATALA_GITHUB_TOKEN }}
username: ${{ env.GITHUB_ACTOR }}
password: ${{ env.GITHUB_TOKEN }}

- uses: KengoTODA/actions-setup-docker-compose@v1
name: Install `docker-compose`
with:
version: '2.14.2'
version: "2.14.2"

- name: Build local version of PRISM Agent
env:
ENV_FILE: "infrastructure/local/.env"
GITHUB_ACTOR: ${{ secrets.ATALA_GITHUB_ACTOR }}
GITHUB_TOKEN: ${{ secrets.ATALA_GITHUB_TOKEN }}
run: |
sbt docker:publishLocal
PRISM_AGENT_VERSION=$(cut version.sbt -d '=' -f2 | tr -d '" ')
Expand Down Expand Up @@ -113,7 +113,7 @@ jobs:
with:
node-version: 16.x
registry-url: "https://npm.pkg.github.com"
scope: 'input-output-hk'
scope: "input-output-hk"

- name: Install dependencies
uses: borales/[email protected]
Expand Down
42 changes: 30 additions & 12 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ lazy val V = new {
val zioMetricsConnector = "2.1.0"
val zioMock = "1.0.0-RC11"
val mockito = "3.2.16.0"
val monocle = "3.1.0"
val monocle = "3.1.0"

// https://mvnrepository.com/artifact/io.circe/circe-core
val circe = "0.14.6"
Expand Down Expand Up @@ -107,6 +107,7 @@ lazy val D = new {
val tapirPrometheusMetrics: ModuleID = "com.softwaremill.sttp.tapir" %% "tapir-prometheus-metrics" % V.tapir
val micrometer: ModuleID = "io.micrometer" % "micrometer-registry-prometheus" % V.micrometer
val micrometerPrometheusRegistry = "io.micrometer" % "micrometer-core" % V.micrometer
val scalaUri = "io.lemonlabs" %% "scala-uri" % V.scalaUri

val zioConfig: ModuleID = "dev.zio" %% "zio-config" % V.zioConfig
val zioConfigMagnolia: ModuleID = "dev.zio" %% "zio-config-magnolia" % V.zioConfig
Expand All @@ -117,6 +118,8 @@ lazy val D = new {
val circeParser: ModuleID = "io.circe" %% "circe-parser" % V.circe

val jwtCirce = "com.github.jwt-scala" %% "jwt-circe" % V.jwtCirceVersion
val jsonCanonicalization: ModuleID = "io.github.erdtman" % "java-json-canonicalization" % "1.1"
val scodecBits: ModuleID = "org.scodec" %% "scodec-bits" % "1.1.38"

// https://mvnrepository.com/artifact/org.didcommx/didcomm/0.3.2
val didcommx: ModuleID = "org.didcommx" % "didcomm" % "0.3.1"
Expand Down Expand Up @@ -153,7 +156,7 @@ lazy val D = new {
val zioTestMagnolia: ModuleID = "dev.zio" %% "zio-test-magnolia" % V.zio % Test
val zioMock: ModuleID = "dev.zio" %% "zio-mock" % V.zioMock
val mockito: ModuleID = "org.scalatestplus" %% "mockito-4-11" % V.mockito % Test
val monocle: ModuleID = "dev.optics" %% "monocle-core" % V.monocle % Test
val monocle: ModuleID = "dev.optics" %% "monocle-core" % V.monocle % Test
val monocleMacro: ModuleID = "dev.optics" %% "monocle-macro" % V.monocle % Test

// LIST of Dependencies
Expand All @@ -167,10 +170,17 @@ lazy val D_Shared = new {
D.typesafeConfig,
D.scalaPbGrpc,
D.zio,
D.zioHttp,
D.scalaUri,
// FIXME: split shared DB stuff as subproject?
D.doobieHikari,
D.doobiePostgres,
D.zioCatsInterop
D.zioCatsInterop,
D.jsonCanonicalization,
D.scodecBits,
D.circeCore,
D.circeGeneric,
D.circeParser,
)
}

Expand Down Expand Up @@ -207,8 +217,6 @@ lazy val D_Connect = new {

lazy val D_Castor = new {

val scalaUri = "io.lemonlabs" %% "scala-uri" % V.scalaUri

// We have to exclude bouncycastle since for some reason bitcoinj depends on bouncycastle jdk15to18
// (i.e. JDK 1.5 to 1.8), but we are using JDK 11
val prismCrypto = "io.iohk.atala" % "prism-crypto-jvm" % V.prismSdk excludeAll
Expand All @@ -225,11 +233,7 @@ lazy val D_Castor = new {
D.zioMock,
D.zioTestSbt,
D.zioTestMagnolia,
D.circeCore,
D.circeGeneric,
D.circeParser,
prismIdentity,
scalaUri
)

// Project Dependencies
Expand Down Expand Up @@ -313,9 +317,8 @@ lazy val D_Pollux_VC_JWT = new {

// Dependency Modules
val zioDependencies: Seq[ModuleID] = Seq(zio, zioPrelude, zioTest, zioTestSbt, zioTestMagnolia)
val circeDependencies: Seq[ModuleID] = Seq(D.circeCore, D.circeGeneric, D.circeParser)
val baseDependencies: Seq[ModuleID] =
circeDependencies ++ zioDependencies :+ D.jwtCirce :+ circeJsonSchema :+ networkntJsonSchemaValidator :+ D.nimbusJwt :+ scalaTest
zioDependencies :+ D.jwtCirce :+ circeJsonSchema :+ networkntJsonSchemaValidator :+ D.nimbusJwt :+ scalaTest

// Project Dependencies
lazy val polluxVcJwtDependencies: Seq[ModuleID] = baseDependencies
Expand Down Expand Up @@ -409,7 +412,12 @@ lazy val D_PrismAgent = new {
lazy val iamDependencies: Seq[ModuleID] = Seq(keycloakAuthz, D.jwtCirce)

lazy val serverDependencies: Seq[ModuleID] =
baseDependencies ++ tapirDependencies ++ postgresDependencies ++ Seq(D.zioMock, D.mockito, D.monocle, D.monocleMacro)
baseDependencies ++ tapirDependencies ++ postgresDependencies ++ Seq(
D.zioMock,
D.mockito,
D.monocle,
D.monocleMacro
)
}

publish / skip := true
Expand Down Expand Up @@ -582,6 +590,14 @@ lazy val protocolIssueCredential = project
.settings(libraryDependencies += D.munitZio)
.dependsOn(models)

lazy val protocolRevocationNotification = project
.in(file("mercury/mercury-library/protocol-revocation-notification"))
.settings(name := "mercury-protocol-revocation-notification")
.settings(libraryDependencies += D.zio)
.settings(libraryDependencies ++= Seq(D.circeCore, D.circeGeneric, D.circeParser))
.settings(libraryDependencies += D.munitZio)
.dependsOn(models)

lazy val protocolPresentProof = project
.in(file("mercury/mercury-library/protocol-present-proof"))
.settings(name := "mercury-protocol-present-proof")
Expand Down Expand Up @@ -640,6 +656,7 @@ lazy val agent = project // maybe merge into models
protocolMercuryMailbox,
protocolLogin,
protocolIssueCredential,
protocolRevocationNotification,
protocolPresentProof,
vc,
protocolConnection,
Expand Down Expand Up @@ -872,6 +889,7 @@ lazy val aggregatedProjects: Seq[ProjectReference] = Seq(
protocolReportProblem,
protocolRouting,
protocolIssueCredential,
protocolRevocationNotification,
protocolPresentProof,
vc,
protocolTrustPing,
Expand Down
1 change: 1 addition & 0 deletions infrastructure/shared/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ services:
AGENT_DB_NAME: agent
AGENT_DB_USER: postgres
AGENT_DB_PASSWORD: postgres
POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://${DOCKERHOST}:${PORT}/prism-agent
DIDCOMM_SERVICE_URL: http://${DOCKERHOST}:${PORT}/didcomm
REST_SERVICE_URL: http://${DOCKERHOST}:${PORT}/prism-agent
PRISM_NODE_HOST: prism-node
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Revocation notification protocol

This Protocol for an Isuser to notify the revocation of a credential to the holder.



## PIURI

Version 1.0: <https://atalaprism.io/revocation_notification/1.0/revoke>

### Roles

- Issuer
- Will create the message and send it to the holder via previously established connection
- Holder
- Will process the message as they see fit, protocol does not require any actions from the holder


### Revocation notification DIDcomV2 message as JSON

```json

{
"from": "fromDID_value",
"to": "toDID_value",
"piuri":"https://atalaprism.io/revocation_notification/1.0/revoke",
"body": {
"issueCredentialProtocolThreadId": "issueCredentialProtocolThreadId_value",
"comment": "Thread Id used to issue this credential withing issue credential protocol"
}
}

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.iohk.atala.mercury.protocol.revocationnotificaiton

import io.circe._
import io.circe.generic.semiauto._
import io.circe.syntax._

import io.iohk.atala.mercury.model._

final case class RevocationNotification(
id: String = java.util.UUID.randomUUID.toString(),
`type`: PIURI = RevocationNotification.`type`,
body: RevocationNotification.Body,
thid: Option[String] = None,
from: DidId,
to: DidId,
) {
assert(`type` == RevocationNotification.`type`)

def makeMessage: Message = Message(
id = this.id,
`type` = this.`type`,
from = Some(this.from),
to = Seq(this.to),
thid = this.thid,
body = this.body.asJson.asObject.get,
)
}
object RevocationNotification {

given Encoder[RevocationNotification] = deriveEncoder[RevocationNotification]
given Decoder[RevocationNotification] = deriveDecoder[RevocationNotification]

def `type`: PIURI = "https://atalaprism.io/revocation_notification/1.0/revoke"

def build(
fromDID: DidId,
toDID: DidId,
thid: Option[String] = None,
issueCredentialProtocolThreadId: String
): RevocationNotification = {
RevocationNotification(
thid = thid,
from = fromDID,
to = toDID,
body = Body(
issueCredentialProtocolThreadId = issueCredentialProtocolThreadId,
comment = Some("Thread Id used to issue this credential withing issue credential protocol")
),
)
}

final case class Body(
issueCredentialProtocolThreadId: String,
comment: Option[String] = None,
)

object Body {
given Encoder[Body] = deriveEncoder[Body]
given Decoder[Body] = deriveDecoder[Body]
}

def readFromMessage(message: Message): RevocationNotification =
val body = message.body.asJson.as[RevocationNotification.Body].toOption.get

RevocationNotification(
id = message.id,
`type` = message.piuri,
body = body,
thid = message.thid,
from = message.from.get, // TODO get
to = {
assert(message.to.length == 1, "The recipient is ambiguous. Need to have only 1 recipient")
message.to.head
},
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.iohk.atala.pollux.core.model

import io.iohk.atala.castor.core.model.did.CanonicalPrismDID
import io.iohk.atala.pollux.vc.jwt.StatusPurpose
import io.iohk.atala.shared.models.WalletId
import java.time.Instant
import java.util.UUID

final case class CredentialStatusList(
id: UUID,
walletId: WalletId,
issuer: CanonicalPrismDID,
issued: Instant,
purpose: StatusPurpose,
statusListCredential: String,
size: Int,
lastUsedIndex: Int,
createdAt: Instant,
updatedAt: Option[Instant]
)

case class CredInStatusList(
id: UUID,
issueCredentialRecordId: DidCommID,
statusListIndex: Int,
isCanceled: Boolean,
isProcessed: Boolean,
)

case class CredentialStatusListWithCred(
credentialStatusListId: UUID,
issuer: CanonicalPrismDID,
issued: Instant,
purpose: StatusPurpose,
walletId: WalletId,
statusListCredential: String,
size: Int,
lastUsedIndex: Int,
credentialInStatusListId: UUID,
issueCredentialRecordId: DidCommID,
statusListIndex: Int,
isCanceled: Boolean,
isProcessed: Boolean,
)

case class CredentialStatusListWithCreds(
id: UUID,
walletId: WalletId,
issuer: CanonicalPrismDID,
issued: Instant,
purpose: StatusPurpose,
statusListCredential: String,
size: Int,
lastUsedIndex: Int,
credentials: Seq[CredInStatusList]
)
Loading

0 comments on commit 88b7fa5

Please sign in to comment.