Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add revocation for JWT credentials #934

Merged
merged 6 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading