Skip to content

Commit

Permalink
Merge pull request #44 from sicpa-dlab/main
Browse files Browse the repository at this point in the history
release 0.2.0
  • Loading branch information
ashcherbakov authored Oct 18, 2021
2 parents f78b80b + 476fc3e commit e36a1a8
Show file tree
Hide file tree
Showing 54 changed files with 4,433 additions and 942 deletions.
28 changes: 15 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: release

on:
push:
branches:
- stable
workflow_dispatch:
#inputs:
# devN:
Expand Down Expand Up @@ -150,14 +152,13 @@ jobs:
steps:
- uses: actions/checkout@v2

# NOTE it's always in SNAPSHOT state for now
# - name: set SNAPSHOT version
# run: |
# # TODO decide
# # sed -i -r "s~^version=(.+)~version=\1-0.dev.${{ github.event.inputs.devN }}~" ./gradle.properties
# sed -i -r "s~^version=(.+)~version=\1-SNAPSHOT~" ./gradle.properties
# grep version ./gradle.properties
# shell: bash
- name: set SNAPSHOT version
run: |
# TODO decide
# sed -i -r "s~^version=(.+)~version=\1-0.dev.${{ github.event.inputs.devN }}~" ./gradle.properties
sed -i -r "s~^version=(.+)~version=\1-SNAPSHOT~" ./gradle.properties
grep version ./gradle.properties
shell: bash

# XXX do we need that here
- name: Set up JDK 8
Expand All @@ -168,10 +169,11 @@ jobs:

- name: Publish to Maven Central
env:
ORG_GRADLE_PROJECT_mavenOSSRHUsername: ${{ secrets.ORG_GRADLE_PROJECT_mavenOSSRHUsername }}
ORG_GRADLE_PROJECT_mavenOSSRHPassword: ${{ secrets.ORG_GRADLE_PROJECT_mavenOSSRHPassword }}
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.ORG_GRADLE_PROJECT_signingKeyId }}
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.ORG_GRADLE_PROJECT_signingKey }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.ORG_GRADLE_PROJECT_signingPassword }}
ORG_GRADLE_PROJECT_mavenOSSRHUsername: ${{ secrets.MAVEN_USERNAME }}
ORG_GRADLE_PROJECT_mavenOSSRHPassword: ${{ secrets.MAVEN_PASSWORD }}
# creds of for an ascii-armored GPG subkey to sign for Maven
# https://docs.gradle.org/current/userguide/signing_plugin.html#sec:in-memory-keys
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.MAVEN_GPG_ARMORED_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.MAVEN_GPG_PASSWORD }}
run: gradle publish
shell: bash
6 changes: 3 additions & 3 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ on: [ pull_request ]
jobs:

release-ready:
name: Check is ready for release
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'stable'
steps:
Expand Down Expand Up @@ -58,16 +57,17 @@ jobs:
strategy:
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
java: [ '8', '11', '13', '15', '17' ]

runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2

- name: Set up JDK 8
- name: Set up JDK
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: 8
java-version: ${{ matrix.java }}

- name: Execute tests
run: gradle test
Expand Down
49 changes: 41 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,43 @@
# didcomm-jvm
# DIDComm JVM

Basic [DIDComm v2](https://identity.foundation/didcomm-messaging/spec) support in Java/Kotlin.
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Unit Tests](https://github.com/sicpa-dlab/didcomm-jvm/workflows/verify/badge.svg)](https://github.com/sicpa-dlab/didcomm-jvm/actions/workflows/verify.yml)


Basic [DIDComm v2](https://identity.foundation/didcomm-messaging/spec) support in Java/Kotlin and Android.


## Installation
Available from Maven Central.

Gradle:
```
dependencies {
implementation 'org.didcommx:didcomm:0.1.0'
}
```


Maven:
```
<dependency>
<groupId>org.didcommx</groupId>
<artifactId>didcomm</artifactId>
<version>0.1.0</version>
</dependency>
```

## DIDComm + peerdid Demo
See https://github.com/sicpa-dlab/didcomm-demo.

## Assumptions and Limitations
- Java 8+
- In order to use the library, `SecretResolver` and `DIDDocResolver` interfaces must be implemented on the application level.
Implementation of that interfaces is out of DIDComm library scope.
- Verification materials in DID Docs and secrets are expected in JWK format only.
- Verification materials are expected in JWK, Base58 and Multibase formats.
- In Base58 and Multibase formats, keys using only X25519 and Ed25519 curves are supported.
- For private keys in Base58 and Multibase formats, the verification material value contains both private and public parts (concatenated bytes).
- In Multibase format, bytes of the verification material value is prefixed with the corresponding Multicodec code.
- Key IDs (kids) used in `SecretResolver` must match the corresponding key IDs from DID Doc verification methods.
- Key IDs (kids) in DID Doc verification methods and secrets must be a full [DID Fragment](https://www.w3.org/TR/did-core/#fragment), that is `did#key-id`.
- Verification methods referencing another DID Document are not supported (see [Referring to Verification Methods](https://www.w3.org/TR/did-core/#referring-to-verification-methods)).
Expand All @@ -18,19 +50,20 @@ Basic [DIDComm v2](https://identity.foundation/didcomm-messaging/spec) support i
- A256CBC-HS512 (default for authcrypt)
- Key wrapping algorithms: ECDH-ES+A256KW, ECDH-1PU+A256KW
- Signing:
- Curves: Ed25519, Secp256k1 (JDK < 15), P-256
- Algorithms: EdDSA (with crv=Ed25519), ES256, ES256K
- Curves: Ed25519, Secp256k1 (currently JDK < 15 only), P-256
- Algorithms: EdDSA (with crv=Ed25519), ES256, ES256K (currently JDK < 15 only)
- DID rotation (`fromPrior` field) is supported.
- Limitations and known issues:
- Forward protocol is not implemented
- Secp256k1 is supported on JDK < 15 only
- DIDComm has been implemented under the following [Assumptions](https://hackmd.io/i3gLqgHQR2ihVFV5euyhqg)

## DIDComm + peerdid Demo
See https://github.com/sicpa-dlab/didcomm-demo.

## Examples

See [demo scripts](lib/src/test/kotlin/org/didcommx/didcomm/DIDCommDemoTest.kt) for details.
See demo scripts for details:
- [DIDComm examples](lib/src/test/kotlin/org/didcommx/didcomm/DIDCommDemoTest.kt)
- [Routing examples](lib/src/test/kotlin/org/didcommx/didcomm/protocols/routing/DIDCommRoutingTest.kt)

A general usage of the API is the following:
- Sender Side:
Expand Down
6 changes: 4 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official

version=0.1.0
version=0.2.0

kotlinJvm=1.5.21
jvmTarget=1.8
nimbusJoseJWTVersion=9.14-SNAPSHOT
nimbusJoseJWTVersion=9.16-preview.1
ktlintGradle=10.1.0
googleTinkVersion=1.6.1
zmanVarint=1.0.0
javaMultibase=v1.1.0
jacksonVersion=2.11.1
jacksonKotlinVersion=2.9.8
johnrengelmanShadow=7.0.0
Expand Down
10 changes: 10 additions & 0 deletions lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@ kotlin {

dependencies {
implementation "com.nimbusds:nimbus-jose-jwt:${nimbusJoseJWTVersion}"
implementation "com.github.multiformats:java-multibase:${javaMultibase}"
shadow "com.google.crypto.tink:tink:${googleTinkVersion}"
shadow "com.zmannotes:varint:${zmanVarint}"
// implementation 'org.bouncycastle:bcprov-jdk15on:1.69'

// TODO look for a better solution
// currently it's a workaround for shadow of the above deps
testImplementation "com.google.crypto.tink:tink:${googleTinkVersion}"
testImplementation "com.zmannotes:varint:${zmanVarint}"

testImplementation "org.jetbrains.kotlin:kotlin-test"
testImplementation "org.junit.jupiter:junit-jupiter:${jUnitJupiter}"
testImplementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
testImplementation "com.fasterxml.jackson.module:jackson-module-kotlin:${jacksonKotlinVersion}"
}
Expand Down Expand Up @@ -78,6 +83,7 @@ shadowJar {

dependencies {
include(dependency("com.nimbusds:nimbus-jose-jwt"))
include(dependency("com.github.multiformats:java-multibase"))
}

archiveClassifier.set("") // remove suffix `-all` as intellij can"t find the library otherwise
Expand Down Expand Up @@ -110,6 +116,10 @@ publishing {
project.shadow.component(publication)

artifactId = project.artifactId
// the following is required only for shadow
// https://github.com/johnrengelman/shadow/issues/544
artifact sourcesJar
artifact javadocJar

pom {
name = "DIDComm"
Expand Down
Binary file removed lib/libs/nimbus-jose-jwt-9.14-SNAPSHOT.jar
Binary file not shown.
55 changes: 49 additions & 6 deletions lib/src/main/kotlin/org/didcommx/didcomm/DIDComm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ import org.didcommx.didcomm.model.PackPlaintextParams
import org.didcommx.didcomm.model.PackPlaintextResult
import org.didcommx.didcomm.model.PackSignedParams
import org.didcommx.didcomm.model.PackSignedResult
import org.didcommx.didcomm.model.ServiceMetadata
import org.didcommx.didcomm.model.UnpackParams
import org.didcommx.didcomm.model.UnpackResult
import org.didcommx.didcomm.operations.encrypt
import org.didcommx.didcomm.operations.packFromPrior
import org.didcommx.didcomm.operations.protectSenderIfNeeded
import org.didcommx.didcomm.operations.signIfNeeded
import org.didcommx.didcomm.operations.unpack
import org.didcommx.didcomm.operations.wrapInForwardIfNeeded
import org.didcommx.didcomm.protocols.routing.resolveDIDCommServicesChain
import org.didcommx.didcomm.secret.SecretResolver

/**
Expand All @@ -39,6 +42,11 @@ class DIDComm(private val didDocResolver: DIDDocResolver, private val secretReso
* and it is the format used in the DIDComm spec to give examples of headers and other internals.
* Depending on ambient security, plaintext may or may not be an appropriate format for DIDComm data at rest.
*
* @throws DIDCommException if pack can not be done, in particular:
* - DIDDocException If a DID or DID URL (for example a key ID) can not be resolved to a DID Doc.
* - SecretNotFoundException If there is no secret for the given DID or DID URL (key ID)
* - DIDCommIllegalArgumentException If invalid input is provided.
*
* @param params Pack Plaintext Parameters.
* @return Result of Pack Plaintext Operation.
*/
Expand Down Expand Up @@ -73,6 +81,12 @@ class DIDComm(private val didDocResolver: DIDDocResolver, private val secretReso
* verification method identified by the given key ID is used.
*
* @param params Pack Signed Parameters.
*
* @throws DIDCommException if pack can not be done, in particular:
* - DIDDocException If a DID or DID URL (for example a key ID) can not be resolved to a DID Doc.
* - SecretNotFoundException If there is no secret for the given DID or DID URL (key ID)
* - DIDCommIllegalArgumentException If invalid input is provided.
*
* @return Result of Pack Signed Operation.
*/
fun packSigned(params: PackSignedParams): PackSignedResult {
Expand Down Expand Up @@ -137,6 +151,12 @@ class DIDComm(private val didDocResolver: DIDDocResolver, private val secretReso
* - If [PackEncryptedParams.signFrom] is a key ID, then the sender's [DIDDoc.authentications]
* verification method identified by the given key ID is used.
*
* @throws DIDCommException if pack can not be done, in particular:
* - DIDDocException If a DID or DID URL (for example a key ID) can not be resolved to a DID Doc.
* - SecretNotFoundException If there is no secret for the given DID or DID URL (key ID)
* - DIDCommIllegalArgumentException If invalid input is provided.
* - IncompatibleCryptoException If the sender and target crypto is not compatible (for example, there are no compatible keys for key agreement)
*
* @param params Pack Encrypted Parameters.
* @return Result of pack encrypted operation.
*/
Expand All @@ -148,23 +168,46 @@ class DIDComm(private val didDocResolver: DIDDocResolver, private val secretReso
val (message, fromPriorIssuerKid) = packFromPrior(params.message, params.fromPriorIssuerKid, senderKeySelector)
val (payload, signFromKid) = signIfNeeded(message.toString(), params, senderKeySelector)
val (encryptedResult, recipientKeys) = encrypt(params, payload, senderKeySelector)
val (packedMessage) = protectSenderIfNeeded(params, encryptedResult, recipientKeys)
var (packedMessage) = protectSenderIfNeeded(params, encryptedResult, recipientKeys)

// TODO make that (along with service metadata) as
// an internal part of routing routine
val didServicesChain = resolveDIDCommServicesChain(
didDocResolver, params.to, params.forwardServiceId
)

val wrapInForwardResult = wrapInForwardIfNeeded(
packedMessage, params, didServicesChain, didDocResolver, secretResolver
)

if (wrapInForwardResult != null)
packedMessage = wrapInForwardResult.msgEncrypted.packedMessage

val serviceMetadata = if (didServicesChain.isEmpty()) null else ServiceMetadata(
didServicesChain.last().id,
didServicesChain.first().serviceEndpoint
)

return PackEncryptedResult(
packedMessage,
encryptedResult.toKids,
encryptedResult.fromKid,
signFromKid,
fromPriorIssuerKid
fromPriorIssuerKid,
serviceMetadata
)
}

/**
* Unpacks the packed DIDComm message by doing decryption and verifying the signatures.
* If unpack config expects the message to be packed in a particular way (for example that a message is encrypted)
* and the packed message doesn't meet the criteria (it's not encrypted), then `UnsatisfiedConstraintError` will be raised.
* Unpacks the packed DIDComm message by doing decryption and verifying the signatures.
*
* @param params Unpack Parameters.
*
* @throws DIDCommException if unpack can not be done, in particular:
* - MalformedMessageException if the message is invalid (can not be decrypted, signature is invalid, the plaintext is invalid, etc.)
* - DIDDocException If a DID or DID URL (for example a key ID) can not be resolved to a DID Doc.
* - SecretNotFoundException If there is no secret for the given DID or DID URL (key ID)
*
* @param params Unpack Parameters.
* @return Result of Unpack Operation.
*/
fun unpack(params: UnpackParams): UnpackResult {
Expand Down
25 changes: 18 additions & 7 deletions lib/src/main/kotlin/org/didcommx/didcomm/common/Types.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ enum class Typ(val typ: String) {

enum class VerificationMethodType {
JSON_WEB_KEY_2020,
// X25519_KEY_AGREEMENT_KEY_2019, - not supported now
// X25519_KEY_AGREEMENT_KEY_2020, - not supported now
// ED25519_VERIFICATION_KEY_2018, - not supported now
// ED25519_VERIFICATION_KEY_2020, - not supported now
X25519_KEY_AGREEMENT_KEY_2019,
ED25519_VERIFICATION_KEY_2018,
X25519_KEY_AGREEMENT_KEY_2020,
ED25519_VERIFICATION_KEY_2020,
// ECDSA_SECP_256K1_VERIFICATION_KEY_2019, - not supported now
OTHER
}
Expand All @@ -32,7 +32,18 @@ data class VerificationMaterial(

enum class VerificationMaterialFormat {
JWK,
// BASE58, - not supported now
// MULTIBASE, - not supported now
// OTHER - not supported now
BASE58,
MULTIBASE,
OTHER
}

enum class DIDCommMessageProtocolTypes(val typ: String) {
Forward("https://didcomm.org/routing/2.0/forward");

companion object {
fun parse(str: String): DIDCommMessageProtocolTypes = when (str) {
Forward.typ -> Forward
else -> throw IllegalArgumentException("Unsupported protocol typ")
}
}
}
8 changes: 4 additions & 4 deletions lib/src/main/kotlin/org/didcommx/didcomm/crypto/JWE.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fun authEncrypt(payload: String, auth: AuthCryptAlg, from: Key, to: List<Key>):
.build()

val sender = from.jwk
val recipients = to.map { Pair.of(UnprotectedHeader.Builder(it.id).build(), it.jwk) }
val recipients = to.map { Pair.of(UnprotectedHeader.Builder().keyID(it.id).build(), it.jwk) }

val encryptor = try {
when (sender) {
Expand Down Expand Up @@ -91,7 +91,7 @@ fun anonEncrypt(payload: String, anon: AnonCryptAlg, to: List<Key>): EncryptResu
.agreementPartyVInfo(apv)
.build()

val recipients = to.map { Pair.of(UnprotectedHeader.Builder(it.id).build(), it.jwk) }
val recipients = to.map { Pair.of(UnprotectedHeader.Builder().keyID(it.id).build(), it.jwk) }

val encryptor = try {
when (val recipient = recipients.first().right) {
Expand Down Expand Up @@ -148,7 +148,7 @@ private fun anonDecryptForOneKey(jwe: JWEObjectJSON, to: Sequence<Key>) = to.map

private fun authDecryptForAllKeys(jwe: JWEObjectJSON, from: Key, to: List<Key>): DecryptResult {
val sender = from.jwk
val recipients = to.map { Pair.of(UnprotectedHeader.Builder(it.id).build(), it.jwk) }
val recipients = to.map { Pair.of(UnprotectedHeader.Builder().keyID(it.id).build(), it.jwk) }

val decrypter =
when (sender) {
Expand All @@ -175,7 +175,7 @@ private fun authDecryptForAllKeys(jwe: JWEObjectJSON, from: Key, to: List<Key>):
}

private fun anonDecryptForAllKeys(jwe: JWEObjectJSON, to: List<Key>): DecryptResult {
val recipients = to.map { Pair.of(UnprotectedHeader.Builder(it.id).build(), it.jwk) }
val recipients = to.map { Pair.of(UnprotectedHeader.Builder().keyID(it.id).build(), it.jwk) }

val decrypter =
when (val recipient = recipients.first().right) {
Expand Down
Loading

0 comments on commit e36a1a8

Please sign in to comment.