Skip to content

Commit 094e6ec

Browse files
author
Vyacheslav Gudkov
authored
Merge pull request #34 from sicpa-dlab/main
release-0.2.0
2 parents dc20e89 + 6c1371b commit 094e6ec

File tree

18 files changed

+2389
-183
lines changed

18 files changed

+2389
-183
lines changed

.github/workflows/release.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ name: release
22

33
on:
44
push:
5+
branches:
6+
- stable
57
workflow_dispatch:
68
inputs:
79
devN:

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = 'didcomm'
3-
version = '0.1.2'
3+
version = '0.2.0'
44
authors = ['Vyacheslav Gudkov <vyacheslav.gudkov@dsr-corporation.com>']
55
edition = '2018'
66
description = 'DIDComm for Rust'
@@ -49,3 +49,6 @@ harness = false
4949
name = "pack_encrypted"
5050
harness = false
5151

52+
[[example]]
53+
name = "basic"
54+

README.md

Lines changed: 258 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,259 @@
1-
# didcomm-rust
1+
# DIDComm Rust
22

3-
Basic DIDComm v2 support in Rust.
3+
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4+
[![Unit Tests](https://github.com/sicpa-dlab/didcomm-rust/workflows/verify/badge.svg)](https://github.com/sicpa-dlab/didcomm-rust/actions/workflows/verify.yml)
5+
[![Rust Package](https://img.shields.io/crates/v/didcomm)](https://crates.io/crates/actix/)
6+
7+
Basic [DIDComm v2](https://identity.foundation/didcomm-messaging/spec) support in Rust.
8+
9+
## Usage
10+
11+
To use `didcomm`, add this to your `Cargo.toml`:
12+
13+
```toml
14+
[dependencies]
15+
didcomm = "0.2"
16+
```
17+
18+
## Run examples
19+
20+
Use `cargo run --example {example-name}` for example `cargo run --example basic`.
21+
22+
## Assumptions and Limitations
23+
- Rust 2018 edition is required.
24+
- In order to use the library, `SecretsResolver` and `DIDResolver` traits must be implemented on the application level.
25+
Implementation of that traits is out of DIDComm library scope, but we provide 2 simple implementation `ExampleDIDResolver`
26+
and `ExampleSecretsResolver` that allows resolve locally known DID docs and secrets for tests/demo purposes.
27+
- Verification materials are expected in JWK.
28+
- Key IDs (kids) used in `SecretsResolver` must match the corresponding key IDs from DID Doc verification methods.
29+
- 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`.
30+
- 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)).
31+
- The following curves and algorithms are supported:
32+
- Encryption:
33+
- Curves: X25519, P-256
34+
- Content encryption algorithms:
35+
- XC20P (to be used with ECDH-ES only, default for anoncrypt),
36+
- A256GCM (to be used with ECDH-ES only),
37+
- A256CBC-HS512 (default for authcrypt)
38+
- Key wrapping algorithms: ECDH-ES+A256KW, ECDH-1PU+A256KW
39+
- Signing:
40+
- Curves: Ed25519, Secp256k1, P-256
41+
- Algorithms: EdDSA (with crv=Ed25519), ES256, ES256K
42+
- DIDComm has been implemented under the following [Assumptions](https://hackmd.io/i3gLqgHQR2ihVFV5euyhqg)
43+
44+
### **Features that will be supported in next versions**
45+
46+
- *Base58 and Multibase (internally Base58 only) formats for secrets and verification methods.*
47+
- *Forward protocol.*
48+
- *DID rotation (`fromPrior` field).*
49+
50+
51+
## Examples
52+
53+
See [examples](examples/) for details.
54+
55+
A general usage of the API is the following:
56+
- Sender Side:
57+
- Build a `Message` (plaintext, payload).
58+
- Convert a message to a DIDComm Message for further transporting by calling one of the following:
59+
- `Message::pack_encrypted` to build an Encrypted DIDComm message
60+
- `Message::pack_signed` to build a Signed DIDComm message
61+
- `Message::pack_plaintext` to build a Plaintext DIDComm message
62+
- Receiver side:
63+
- Call `Message::unpack` on receiver side that will decrypt the message, verify signature if needed
64+
and return a `Message` for further processing on the application level.
65+
66+
### 1. Build an Encrypted DIDComm message for the given recipient
67+
68+
This is the most common DIDComm message to be used in most of the applications.
69+
70+
A DIDComm encrypted message is an encrypted JWM (JSON Web Messages) that
71+
- hides its content from all but authorized recipients
72+
- (optionally) discloses and proves the sender to only those recipients
73+
- provides message integrity guarantees
74+
75+
It is important in privacy-preserving routing. It is what normally moves over network transports in DIDComm
76+
applications, and is the safest format for storing DIDComm data at rest.
77+
78+
See `Message::pack_encrypted` documentation for more details.
79+
80+
**Authentication encryption** example (most common case):
81+
82+
```rust
83+
// --- Build message from ALICE to BOB ---
84+
let msg = Message::build(
85+
"example-1".to_owned(),
86+
"example/v1".to_owned(),
87+
json!("example-body"),
88+
)
89+
.to(ALICE_DID.to_owned())
90+
.from(BOB_DID.to_owned())
91+
.finalize();
92+
93+
// --- Pack encrypted and authenticated message ---
94+
let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]);
95+
let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone());
96+
97+
let (msg, metadata) = msg
98+
.pack_encrypted(
99+
BOB_DID,
100+
Some(ALICE_DID),
101+
None,
102+
&did_resolver,
103+
&secrets_resolver,
104+
&PackEncryptedOptions {
105+
forward: false, // Forward wrapping is unsupported in current version
106+
..PackEncryptedOptions::default()
107+
},
108+
)
109+
.await
110+
.expect("Unable pack_encrypted");
111+
112+
println!("Encryption metadata is\n{:?}\n", metadata);
113+
114+
// --- Send message ---
115+
println!("Sending message \n{}\n", msg);
116+
117+
// --- Unpacking message ---
118+
let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]);
119+
let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone());
120+
121+
let (msg, metadata) = Message::unpack(
122+
&msg,
123+
&did_resolver,
124+
&secrets_resolver,
125+
&UnpackOptions {
126+
..UnpackOptions::default()
127+
},
128+
)
129+
.await
130+
.expect("Unable unpack");
131+
132+
println!("Receved message is \n{:?}\n", msg);
133+
println!("Receved message unpack metadata is \n{:?}\n", metadata);
134+
```
135+
136+
**Anonymous encryption** example:
137+
138+
```rust
139+
let (msg, metadata) = msg
140+
.pack_encrypted(
141+
BOB_DID,
142+
None, // Keep sender as None here
143+
None,
144+
&did_resolver,
145+
&secrets_resolver,
146+
&PackEncryptedOptions {
147+
forward: false, // Forward wrapping is unsupported in current version
148+
..PackEncryptedOptions::default()
149+
},
150+
)
151+
.await
152+
.expect("Unable pack_encrypted");
153+
```
154+
155+
**Encryption with non-repudiation** example:
156+
157+
```rust
158+
let (msg, metadata) = msg
159+
.pack_encrypted(
160+
BOB_DID,
161+
Some(ALICE_DID),
162+
None,
163+
&did_resolver,
164+
&secrets_resolver,
165+
&PackEncryptedOptions {
166+
sign_by: Some(ALICE_DID), // Provide information about signer here
167+
forward: false, // Forward wrapping is unsupported in current version
168+
..PackEncryptedOptions::default()
169+
},
170+
)
171+
.await
172+
.expect("Unable pack_encrypted");
173+
```
174+
175+
### 2. Build an unencrypted but Signed DIDComm message
176+
177+
Signed messages are only necessary when
178+
- the origin of plaintext must be provable to third parties
179+
- 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
180+
broadcast scenario).
181+
182+
Adding a signature when one is not needed can degrade rather than enhance security because it
183+
relinquishes the sender’s ability to speak off the record.
184+
185+
See `Message::pack_signed` documentation for more details.
186+
187+
```rust
188+
// ALICE
189+
let msg = Message::build(
190+
"example-1".to_owned(),
191+
"example/v1".to_owned(),
192+
json!("example-body"),
193+
)
194+
.to(ALICE_DID.to_owned())
195+
.from(BOB_DID.to_owned())
196+
.finalize();
197+
198+
let (msg, metadata) = msg
199+
.pack_signed(ALICE_DID, did_resolver, secrets_resolver)
200+
.await
201+
.expect("Unable pack_signed");
202+
203+
// BOB
204+
let (msg, metadata) = Message::unpack(
205+
&msg,
206+
&did_resolver,
207+
&secrets_resolver,
208+
&UnpackOptions {
209+
..UnpackOptions::default()
210+
},
211+
)
212+
.await
213+
.expect("Unable unpack");
214+
```
215+
216+
### 3. Build a Plaintext DIDComm message
217+
218+
A DIDComm message in its plaintext form that
219+
- is not packaged into any protective envelope
220+
- lacks confidentiality and integrity guarantees
221+
- repudiable
222+
223+
They are therefore not normally transported across security boundaries.
224+
225+
```rust
226+
// ALICE
227+
let msg = Message::build(
228+
"example-1".to_owned(),
229+
"example/v1".to_owned(),
230+
json!("example-body"),
231+
)
232+
.to(ALICE_DID.to_owned())
233+
.from(BOB_DID.to_owned())
234+
.finalize();
235+
236+
let msg = msg
237+
.pack_plaintext()
238+
.expect("Unable pack_plaintext");
239+
240+
// BOB
241+
let (msg, metadata) = Message::unpack(
242+
&msg,
243+
&did_resolver,
244+
&secrets_resolver,
245+
&UnpackOptions {
246+
..UnpackOptions::default()
247+
},
248+
)
249+
.await
250+
.expect("Unable unpack");
251+
```
252+
253+
## Contribution
254+
PRs are welcome!
255+
256+
The following CI checks are run against every PR:
257+
- No warnings from `cargo check --all-targets`
258+
- All tests must pass with `cargo tests`
259+
- Code must be formatted by `cargo fmt --all`

docs/release.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
## Release
2+
3+
Assumptions:
4+
5+
* `main` branch can wait until release PR is merged
6+
7+
The steps:
8+
9+
1. **release**:
10+
1. **review adjust if needed the release version in `main`** to match the changes from the latest release following the [SemVer rules](https://semver.org/#summary).
11+
2. [create](https://github.com/sicpa-dlab/didcomm-rust/compare/stable...main) a **PR from `main` to `stable`** (you may likely want to name it as `release-<version>`)
12+
3. once merged [release pipeline](https://github.com/sicpa-dlab/didcomm-rust/actions/workflows/release.yml) will publish the release to [crates.io](https://crates.io/crates/didcomm)
13+
2. **bump next release version in `main`**
14+
* **Note** decision about the next release version should be based on the same [SemVer](https://semver.org/) rules and the expected changes. Usually it would be either a MINOR or MAJOR (if incompatible changes are planned) release.

examples/basic.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#[allow(unused_imports, dead_code)]
2+
#[path = "../src/test_vectors/mod.rs"]
3+
mod test_vectors;
4+
5+
// TODO: look for better solution
6+
// Allows test vectors usage inside and outside crate
7+
pub(crate) use didcomm;
8+
9+
use didcomm::{
10+
did::resolvers::ExampleDIDResolver, secrets::resolvers::ExampleSecretsResolver, Message,
11+
PackEncryptedOptions, UnpackOptions,
12+
};
13+
use serde_json::json;
14+
use test_vectors::{ALICE_DID, ALICE_DID_DOC, ALICE_SECRETS, BOB_DID, BOB_DID_DOC, BOB_SECRETS};
15+
16+
#[tokio::main(flavor = "current_thread")]
17+
async fn main() {
18+
// --- Build message from ALICE to BOB ---
19+
20+
let msg = Message::build(
21+
"example-1".to_owned(),
22+
"example/v1".to_owned(),
23+
json!("example-body"),
24+
)
25+
.to(ALICE_DID.to_owned())
26+
.from(BOB_DID.to_owned())
27+
.finalize();
28+
29+
// --- Packing encrypted and authenticated message ---
30+
31+
let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]);
32+
let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone());
33+
34+
let (msg, metadata) = msg
35+
.pack_encrypted(
36+
BOB_DID,
37+
Some(ALICE_DID),
38+
None,
39+
&did_resolver,
40+
&secrets_resolver,
41+
&PackEncryptedOptions {
42+
forward: false, // Forward wrapping is unsupported in current version
43+
..PackEncryptedOptions::default()
44+
},
45+
)
46+
.await
47+
.expect("Unable pack_encrypted");
48+
49+
println!("Encryption metadata is\n{:?}\n", metadata);
50+
51+
// --- Send message ---
52+
53+
println!("Sending message \n{}\n", msg);
54+
55+
// // --- Unpacking message ---
56+
57+
let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]);
58+
let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone());
59+
60+
let (msg, metadata) = Message::unpack(
61+
&msg,
62+
&did_resolver,
63+
&secrets_resolver,
64+
&UnpackOptions {
65+
..UnpackOptions::default()
66+
},
67+
)
68+
.await
69+
.expect("Ubable unpack");
70+
71+
println!("Receved message is \n{:?}\n", msg);
72+
println!("Receved message unpack metadata is \n{:?}\n", metadata);
73+
}

src/algorithms.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// Algorithms for anonymous encryption
2-
#[derive(Debug, PartialEq, Eq)]
2+
#[derive(Debug, PartialEq, Eq, Clone)]
33
pub enum AnonCryptAlg {
44
/// AES256-CBC + HMAC-SHA512 with a 512 bit key content encryption,
55
/// ECDH-ES key agreement with A256KW key wrapping
@@ -14,13 +14,14 @@ pub enum AnonCryptAlg {
1414
A256gcmEcdhEsA256kw,
1515
}
1616

17-
#[derive(Debug, PartialEq, Eq)]
17+
#[derive(Debug, PartialEq, Eq, Clone)]
1818
pub enum AuthCryptAlg {
1919
/// AES256-CBC + HMAC-SHA512 with a 512 bit key content encryption,
2020
/// ECDH-1PU key agreement with A256KW key wrapping
2121
A256cbcHs512Ecdh1puA256kw,
2222
}
2323

24+
#[derive(Debug, PartialEq, Eq, Clone)]
2425
pub enum SignAlg {
2526
EdDSA,
2627
ES256,

0 commit comments

Comments
 (0)