Basic DIDComm v2 support in Java/Kotlin and Android.
Available from Maven Central.
Gradle:
dependencies {
implementation 'org.didcommx:didcomm:0.3.0'
}
Maven:
<dependency>
<groupId>org.didcommx</groupId>
<artifactId>didcomm</artifactId>
<version>0.3.0</version>
</dependency>
See https://github.com/sicpa-dlab/didcomm-demo.
- Java 8+
- In order to use the library,
SecretResolver
andDIDDocResolver
interfaces must be implemented on the application level. Implementation of that interfaces is out of DIDComm library scope.- 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, that is
did#key-id
. - Verification methods referencing another DID Document are not supported (see Referring to Verification Methods).
- Verification materials are expected in JWK, Base58 and Multibase formats.
- The following curves and algorithms are supported:
- Encryption:
- Curves: X25519, P-384, P-256, P-521
- Content encryption algorithms:
- XC20P (to be used with ECDH-ES only, default for anoncrypt),
- A256GCM (to be used with ECDH-ES only),
- A256CBC-HS512 (default for authcrypt)
- Key wrapping algorithms: ECDH-ES+A256KW, ECDH-1PU+A256KW
- Signing:
- Curves: Ed25519, Secp256k1 (currently JDK < 15 only), P-256
- Algorithms: EdDSA (with crv=Ed25519), ES256, ES256K (currently JDK < 15 only)
- Encryption:
- DID rotation (
fromPrior
field) is supported. - Forward protocol is implemented and used by default.
- Limitations and known issues:
- Secp256k1 is supported on JDK < 15 only
- DIDComm has been implemented under the following Assumptions
See demo scripts for details:
A general usage of the API is the following:
- Sender Side:
- Build a
Message
(plaintext, payload). - Convert a message to a DIDComm Message for further transporting by calling one of the following:
packEncrypted
to build an Encrypted DIDComm messagepackSigned
to build a Signed DIDComm messagepackPlaintext
to build a Plaintext DIDComm message
- Build a
- Receiver side:
- Call
unpack
on receiver side that will decrypt the message, verify signature if needed and return aMessage
for further processing on the application level.
- Call
This is the most common DIDComm message to be used in most of the applications.
A DIDComm encrypted message is an encrypted JWM (JSON Web Messages) that
- hides its content from all but authorized recipients
- (optionally) discloses and proves the sender to only those recipients
- provides message integrity guarantees
It is important in privacy-preserving routing. It is what normally moves over network transports in DIDComm applications, and is the safest format for storing DIDComm data at rest.
See packEncrypted
documentation for more details.
Authentication encryption example (most common case):
val didComm = DIDComm(DIDDocResolverMock(), SecretResolverMock())
// ALICE
val message = Message.builder(
id = "1234567890",
body = mapOf("messagespecificattribute" to "and its value"),
type = "http://example.com/protocols/lets_do_lunch/1.0/proposal"
)
.from(ALICE_DID)
.to(listOf(BOB_DID))
.createdTime(1516269022)
.expiresTime(1516385931)
.build()
val packResult = didComm.packEncrypted(
PackEncryptedParams.builder(message, BOB_DID)
.from(JWM.ALICE_DID)
.build()
)
println("Sending ${packResult.packedMessage} to ${packResult.serviceMetadata?.serviceEndpoint ?: ""}")
// BOB
val unpackResult = didComm.unpack(
UnpackParams.Builder(packResult.packedMessage).build()
)
println("Got ${unpackResult.message} message")
Anonymous encryption example:
val didComm = DIDComm(DIDDocResolverMock(), SecretResolverMock())
val message = Message.builder(
id = "1234567890",
body = mapOf("messagespecificattribute" to "and its value"),
type = "http://example.com/protocols/lets_do_lunch/1.0/proposal"
)
.to(listOf(BOB_DID))
.createdTime(1516269022)
.expiresTime(1516385931)
.build()
val packResult = didComm.packEncrypted(
PackEncryptedParams.builder(message, BOB_DID).build()
)
)
Encryption with non-repudiation example:
val didComm = DIDComm(DIDDocResolverMock(), SecretResolverMock())
val message = Message.builder(
id = "1234567890",
body = mapOf("messagespecificattribute" to "and its value"),
type = "http://example.com/protocols/lets_do_lunch/1.0/proposal"
)
.from(ALICE_DID)
.to(listOf(BOB_DID))
.createdTime(1516269022)
.expiresTime(1516385931)
.build()
val packResult = didComm.packEncrypted(
PackEncryptedParams.builder(message, BOB_DID)
.signFrom(ALICE_DID)
.from(ALICE_DID)
.build()
)
Signed messages are only necessary when
- the origin of plaintext must be provable to third parties
- or the sender can’t be proven to the recipient by authenticated encryption because the recipient is not known in advance (e.g., in a broadcast scenario).
Adding a signature when one is not needed can degrade rather than enhance security because it relinquishes the sender’s ability to speak off the record.
See packSigned
documentation for more details.
val didComm = DIDComm(DIDDocResolverMock(), SecretResolverMock())
// ALICE
val message = Message.builder(
id = "1234567890",
body = mapOf("messagespecificattribute" to "and its value"),
type = "http://example.com/protocols/lets_do_lunch/1.0/proposal"
)
.from(ALICE_DID)
.to(listOf(BOB_DID))
.createdTime(1516269022)
.expiresTime(1516385931)
.build()
val packResult = didComm.packSigned(
PackSignedParams.builder(message, ALICE_DID).build()
)
println("Publishing ${packResult.packedMessage}")
// BOB
val unpackResult = didComm.unpack(
UnpackParams.Builder(packResult.packedMessage).build()
)
println("Got ${unpackResult.message} message")
A DIDComm message in its plaintext form that
- is not packaged into any protective envelope
- lacks confidentiality and integrity guarantees
- repudiable
They are therefore not normally transported across security boundaries.
val didComm = DIDComm(DIDDocResolverMock(), SecretResolverMock())
// ALICE
val message = Message.builder(
id = "1234567890",
body = mapOf("messagespecificattribute" to "and its value"),
type = "http://example.com/protocols/lets_do_lunch/1.0/proposal"
)
.from(ALICE_DID)
.to(listOf(BOB_DID))
.createdTime(1516269022)
.expiresTime(1516385931)
.build()
val packResult = didComm.packPlaintext(
PackPlaintextParams.builder(message)
.build()
)
println("Publishing ${packResult.packedMessage}")
// BOB
val unpackResult = didComm.unpack(
UnpackParams.Builder(packResult.packedMessage).build()
)
println("Got ${unpackResult.message} message")
PRs are welcome!
The following CI checks are run against every PR:
- all tests must pass
- code style is analyzed using ktlint.