@derec-alliance/nodejs 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,284 @@
1
+ /*
2
+ * Copyright (c) DeRec Alliance and its Contributors.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ syntax = "proto3";
7
+
8
+ import "google/protobuf/timestamp.proto";
9
+ import "communicationinfo.proto";
10
+ import "parameterrange.proto";
11
+ import "result.proto";
12
+ import "transportprotocol.proto";
13
+
14
+ package org.derecalliance.derec.protobuf;
15
+
16
+ // SenderKind identifies the logical role of the sender in the pairing flow.
17
+ //
18
+ // The role determines how the receiver interprets the message and what
19
+ // role each party takes in the resulting channel. Pairs are formed by
20
+ // matching complementary kinds: Owner↔Helper, ReplicaSource↔ReplicaDestination.
21
+ enum SenderKind {
22
+ // Sender is an Owner.
23
+ //
24
+ // Used for all Owner-initiated pairings, whether establishing a new
25
+ // relationship or re-pairing during recovery. There is no protocol-level
26
+ // distinction between the two cases.
27
+ OWNER = 0;
28
+
29
+ // Sender is acting as a Helper.
30
+ //
31
+ // Helpers may initiate pairing in some application flows, though the
32
+ // Owner typically acts as the initiator.
33
+ HELPER = 1;
34
+
35
+ // Sender is the Source side of a replica pair — the device that owns
36
+ // the secrets and pushes them to a destination for backup. The matching
37
+ // peer kind is REPLICA_DESTINATION.
38
+ //
39
+ // Replica pairing follows the same cryptographic handshake as Helper
40
+ // pairing but adds a mandatory fingerprint-verify step before the
41
+ // channel transitions out of `Pending`. After verification, the Source
42
+ // calls ProtectSecret targeting the Destination channel; the library
43
+ // sends a `ReplicaSecretPayload` (full secret + share map) to the
44
+ // Destination via a normal channel-mode `StoreShareRequest`.
45
+ REPLICA_SOURCE = 3;
46
+
47
+ // Sender is the Destination side of a replica pair — the device that
48
+ // receives and stores full secret copies from a Source for backup or
49
+ // recovery purposes. The matching peer kind is REPLICA_SOURCE.
50
+ //
51
+ // The Destination never initiates ProtectSecret on this channel; it
52
+ // only receives `ReplicaSecretPayload` messages and surfaces them to
53
+ // the application via the `ReplicaSecretReceived` event.
54
+ REPLICA_DESTINATION = 4;
55
+ }
56
+
57
+ // PairRequestMessage is the first message in the pairing protocol,
58
+ // sent from the initiator to the responder after receiving a ContactMessage.
59
+ //
60
+ // # Preconditions
61
+ //
62
+ // Before sending this message:
63
+ //
64
+ // - The initiator MUST have received a valid ContactMessage from the responder
65
+ // - The initiator MUST use the responder's public encryption material from
66
+ // the ContactMessage to construct this request
67
+ //
68
+ // # Purpose
69
+ //
70
+ // This message establishes:
71
+ //
72
+ // - the initiator's identity and role (Owner/Helper, recovery mode or not)
73
+ // - fresh cryptographic material for the responder to use in subsequent messages
74
+ // - agreement negotiation inputs (parameter ranges)
75
+ // - binding between the out-of-band contact exchange and this pairing attempt
76
+ // - transport information for future communication
77
+ //
78
+ // # Security Properties
79
+ //
80
+ // - The nonce binds this request to the ContactMessage exchange
81
+ // - The ciphertext and public keys bootstrap a secure communication channel
82
+ // - The message is encrypted and authenticated at the DeRecMessage layer
83
+ //
84
+ // # Outcome
85
+ //
86
+ // Upon successful processing, the responder replies with PairResponseMessage.
87
+ // After this exchange:
88
+ //
89
+ // - both parties know each other's public keys
90
+ // - a secure communication channel is established for subsequent protocol flows
91
+ message PairRequestMessage {
92
+
93
+ // The role of the sender of this message.
94
+ //
95
+ // This informs the responder whether the initiator is:
96
+ //
97
+ // - an Owner (normal or recovery mode), or
98
+ // - a Helper
99
+ //
100
+ // Recovery mode has special semantics for how Helpers associate this
101
+ // pairing with existing stored secrets.
102
+ SenderKind senderKind = 1;
103
+
104
+ // ML-KEM-768 ciphertext.
105
+ //
106
+ // This ciphertext is generated using the responder's ML-KEM public key
107
+ // obtained from the ContactMessage. It is used to establish shared
108
+ // cryptographic material for securing subsequent communication.
109
+ bytes mlkemCiphertext = 2;
110
+
111
+ // Serialized ECIES public key of the sender.
112
+ //
113
+ // This key is provided to the responder so that future messages can be
114
+ // encrypted toward the initiator.
115
+ bytes eciesPublicKey = 3;
116
+
117
+ // Sender's application-level identifying information.
118
+ //
119
+ // This field is intended for user-facing identification and may include
120
+ // data such as name, phone number, or account identifiers.
121
+ //
122
+ // It is not used directly by the protocol for security decisions.
123
+ CommunicationInfo communicationInfo = 5;
124
+
125
+ // Nonce binding this request to the ContactMessage exchange.
126
+ //
127
+ // This value MUST match the nonce provided in the ContactMessage.
128
+ // It allows the responder to verify that the pairing request corresponds
129
+ // to the same out-of-band interaction, preventing mismatched or spoofed
130
+ // pairing attempts.
131
+ uint64 nonce = 6;
132
+
133
+ // Parameter range supported by the sender.
134
+ //
135
+ // This defines the acceptable configuration space (e.g., thresholds,
136
+ // limits) that the sender is willing to operate under.
137
+ //
138
+ // The responder combines this with its own parameter range to determine
139
+ // whether pairing is possible and which parameters to use.
140
+ ParameterRange parameterRange = 7;
141
+
142
+ // Transport information for reaching the initiator.
143
+ //
144
+ // This specifies:
145
+ //
146
+ // - the endpoint where the initiator can receive messages
147
+ // - the protocol required to deliver those messages
148
+ //
149
+ // After pairing, the responder uses this information to send responses
150
+ // and all subsequent protocol messages to the initiator.
151
+ // Deprecated: superseded by `supportedTransports`, which carries every
152
+ // endpoint rather than one.
153
+ //
154
+ // **Reading this field directly is now incorrect.** Its meaning narrowed
155
+ // from "the endpoint" to "one entry of a list, and possibly absent": an
156
+ // initiator that has moved past this field advertises only
157
+ // `supportedTransports`. A reader that was correct before this release is
158
+ // a bug now — it rejects, or fails to reach, a peer that is offering it a
159
+ // perfectly good endpoint. Resolve both spellings instead of reading
160
+ // either: `advertised_endpoints()` (Rust and TypeScript),
161
+ // `AdvertisedEndpoints()` (Go), `AdvertisedEndpoints()` (.NET).
162
+ //
163
+ // Senders still populate it: a sender that sets `supportedTransports`
164
+ // MUST also set this to a single best-compatibility choice, so
165
+ // implementations predating the list still pair.
166
+ //
167
+ // **Scheduled for removal in v0.0.5.**
168
+ TransportProtocol transportProtocol = 8 [deprecated = true];
169
+
170
+ // Timestamp indicating when this message was created.
171
+ //
172
+ // Used for observability and may assist in replay detection or timeout
173
+ // handling, depending on the implementation.
174
+ google.protobuf.Timestamp timestamp = 9;
175
+
176
+ // Every transport endpoint the initiator can be reached on, in the
177
+ // initiator's own preference order.
178
+ //
179
+ // Same semantics as `ContactMessage.supportedTransports`: the responder
180
+ // selects by its own preference, and absent means "only
181
+ // `transportProtocol` is offered".
182
+ repeated TransportProtocol supportedTransports = 10;
183
+ }
184
+
185
+ // PairResponseMessage is the response to a PairRequestMessage.
186
+ //
187
+ // # Purpose
188
+ //
189
+ // This message completes the pairing handshake. It informs the initiator:
190
+ //
191
+ // - whether the pairing was accepted or rejected
192
+ // - the responder's role and identity information
193
+ // - the responder's supported parameter range
194
+ //
195
+ // # Differences from PairRequestMessage
196
+ //
197
+ // - Does not include encryption public keys, as those were already exchanged
198
+ // via ContactMessage and PairRequestMessage
199
+ // - Includes a Result field indicating success or failure
200
+ //
201
+ // # Semantics
202
+ //
203
+ // - If `result` indicates success, a secure channel is considered established
204
+ // - If `result` indicates failure, the pairing MUST be treated as unsuccessful
205
+ // and no further protocol messages should be exchanged on this channel
206
+ //
207
+ // The nonce MUST match the value provided in the request, ensuring that the
208
+ // response corresponds to the same pairing session.
209
+ message PairResponseMessage {
210
+
211
+ // Result of processing the pairing request.
212
+ //
213
+ // Indicates whether the pairing was successful or failed.
214
+ // Failure may occur due to:
215
+ //
216
+ // - incompatible parameter ranges
217
+ // - failed authentication at the application layer
218
+ // - invalid or malformed request data
219
+ DeRecResult result = 1;
220
+
221
+ // Application-level identifying information of the responder.
222
+ //
223
+ // Intended for user-facing identification and display purposes.
224
+ CommunicationInfo communicationInfo = 4;
225
+
226
+ // Nonce identifying the pairing session.
227
+ //
228
+ // This value MUST exactly match the nonce received in the
229
+ // PairRequestMessage. A mismatch indicates an invalid or unrelated
230
+ // pairing attempt and SHOULD result in rejection.
231
+ uint64 nonce = 5;
232
+
233
+ // Parameter range supported by the responder.
234
+ //
235
+ // The initiator combines this with its own parameter range to determine
236
+ // the final agreed configuration for the channel.
237
+ ParameterRange parameterRange = 6;
238
+
239
+ // Timestamp indicating when this message was created.
240
+ //
241
+ // Used for observability and may assist in replay detection or timeout
242
+ // handling, depending on the implementation.
243
+ google.protobuf.Timestamp timestamp = 7;
244
+
245
+ // Derived channel identifier the two parties switch to immediately after
246
+ // the pairing handshake completes. This message is the only place this
247
+ // value travels on the wire, and it travels encrypted (inside the pairing
248
+ // envelope), so a passive observer who only sees the pre-rekey traffic
249
+ // cannot link the now-paired session to its long-running successor.
250
+ //
251
+ // # Derivation
252
+ //
253
+ // The responder computes:
254
+ //
255
+ // ```text
256
+ // channelId = u64::from_be_bytes(
257
+ // SHA-384(
258
+ // u64_be(originalChannelId) || sharedKeyBytes
259
+ // )[0..8]
260
+ // )
261
+ // ```
262
+ //
263
+ // where `originalChannelId` is the channelId from the contact / pairing
264
+ // envelopes and `sharedKeyBytes` is the freshly-derived pairing shared
265
+ // key.
266
+ //
267
+ // # Validation
268
+ //
269
+ // The requester MUST recompute the same hash with its own derivation of
270
+ // the shared key and reject the response (closing the would-be channel)
271
+ // if the value here does not match. A non-matching value indicates either
272
+ // a key-derivation mismatch (the channel would be unusable anyway) or
273
+ // tampering by a peer that somehow forged a valid envelope but cannot
274
+ // produce the SHA-384 preimage.
275
+ //
276
+ // # Effect
277
+ //
278
+ // Both parties atomically rekey their local channel record from
279
+ // `originalChannelId` to this value once the handshake is accepted. All
280
+ // subsequent envelopes on this channel — including the
281
+ // UpdateChannelInfo handover for the long-term transport — route on the
282
+ // new id.
283
+ uint64 channelId = 8;
284
+ }
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Copyright (c) DeRec Alliance and its Contributors.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ syntax = "proto3";
7
+
8
+ package org.derecalliance.derec.protobuf;
9
+
10
+ message ParameterRange {
11
+
12
+ // Minimum and maximum number of bytes the party is willing to store
13
+ // at any given time.
14
+ int64 minShareSize = 1;
15
+ int64 maxShareSize = 2;
16
+
17
+ // Minimum and maximum interval between verification requests that
18
+ // will be accepted.
19
+ int64 minTimeBetweenVerifications = 3;
20
+ int64 maxTimeBetweenVerifications = 4;
21
+
22
+ // Minimum and maximum interval between accepting new share updates.
23
+ int64 minTimeBetweenShareUpdates = 5;
24
+ int64 maxTimeBetweenShareUpdates = 6;
25
+
26
+ // Minimum and maximum timeout period (in seconds) after which the
27
+ // other party is considered unresponsive and the pairing may be
28
+ // removed along with all associated data.
29
+ int64 minUnresponsiveDeletionTimeout = 7;
30
+ int64 maxUnresponsiveDeletionTimeout = 8;
31
+
32
+ // Minimum and maximum timeout period (in seconds) after which the
33
+ // other party is considered inactive.
34
+ int64 minUnresponsiveDeactivationTimeout = 9;
35
+ int64 maxUnresponsiveDeactivationTimeout = 10;
36
+ }
@@ -0,0 +1,177 @@
1
+ /*
2
+ * Copyright (c) DeRec Alliance and its Contributors.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ syntax = "proto3";
7
+
8
+ import "google/protobuf/timestamp.proto";
9
+ import "result.proto";
10
+ import "transportprotocol.proto";
11
+
12
+ package org.derecalliance.derec.protobuf;
13
+
14
+ // PrePairRequestMessage is sent by the recipient of a ContactMessage whose
15
+ // `contactMode == CONTACT_MODE_HASHED_KEYS`. It asks the contact creator for
16
+ // the actual ML-KEM and ECIES public keys that the contact's
17
+ // `contactBindingHash` commits to.
18
+ //
19
+ // # When this is sent
20
+ //
21
+ // `CONTACT_MODE_HASHED_KEYS` contacts carry only a hash of the public keys
22
+ // so they fit in size-constrained out-of-band channels (e.g. QR codes,
23
+ // short URLs). The recipient must obtain the actual keys before it can
24
+ // construct the first encrypted `PairRequestMessage`.
25
+ //
26
+ // # Security
27
+ //
28
+ // This message is plaintext — no shared key has been established yet. The
29
+ // scanner-side integrity check happens after the `PrePairResponseMessage`
30
+ // arrives, by recomputing the hash and matching it against the original
31
+ // `contactBindingHash`. An attacker who intercepts the transport cannot
32
+ // substitute different keys without producing a hash collision.
33
+ //
34
+ // # Envelope
35
+ //
36
+ // The message is wrapped in a `DeRecMessage` envelope just like every
37
+ // other protocol message. The envelope's `channelId` carries the routing
38
+ // key; the envelope's `message` field carries the serialized
39
+ // `PrePairRequestMessage` bytes unencrypted (because no symmetric key
40
+ // exists yet).
41
+ //
42
+ // # Security: transport endpoint is observable
43
+ //
44
+ // Because the envelope is plaintext, the `transportProtocol` field in both
45
+ // `PrePairRequestMessage` and `PrePairResponseMessage` is visible to any
46
+ // passive observer on the network path. The same applies to the
47
+ // `transportProtocol` carried in the `HASHED_KEYS`-mode `ContactMessage`
48
+ // itself if its out-of-band channel can be observed.
49
+ //
50
+ // The protocol relies on this contract from the application to preserve
51
+ // the security guarantees of `HASHED_KEYS` mode:
52
+ //
53
+ // - The `transportProtocol` used during pairing (the one advertised in the
54
+ // `ContactMessage` and echoed in `PrePairRequestMessage`) MUST be
55
+ // ephemeral and short-lived — bound to the pairing attempt, not to the
56
+ // long-term identity of either peer.
57
+ // - Once pairing completes, the application MUST swap to a long-term
58
+ // endpoint via an `UpdateChannelInfoRequestMessage`. The ephemeral
59
+ // pairing endpoint is retired after the changeover (see the endpoint-
60
+ // changeover discipline on `UpdateChannelInfoRequestMessage`).
61
+ //
62
+ // `INLINE_KEYS`-mode contacts do not have this constraint because the
63
+ // `PairRequestMessage` is asymmetrically encrypted; only the
64
+ // `ContactMessage` itself crosses an unencrypted channel, which is by
65
+ // definition the trusted out-of-band one.
66
+ message PrePairRequestMessage {
67
+
68
+ // The same `nonce` the original `ContactMessage` advertised. Bound to
69
+ // the contact session; mismatches indicate a stale or spoofed request
70
+ // and the receiver MUST refuse to serve the keys.
71
+ uint64 nonce = 1;
72
+
73
+ // Transport endpoint at which the sender expects to receive the
74
+ // `PrePairResponseMessage`.
75
+ //
76
+ // Deprecated: superseded by `supportedTransports`, which carries every
77
+ // endpoint rather than one.
78
+ //
79
+ // **Reading this field directly is now incorrect.** Its meaning narrowed
80
+ // from "the endpoint" to "one entry of a list, and possibly absent": a
81
+ // sender that has moved past this field advertises only
82
+ // `supportedTransports`. A reader that was correct before this release is
83
+ // a bug now — it rejects, or fails to reply to, a peer that is offering it
84
+ // a perfectly good endpoint. Resolve both spellings instead of reading
85
+ // either: `advertised_endpoints()` (Rust and TypeScript),
86
+ // `AdvertisedEndpoints()` (Go), `AdvertisedEndpoints()` (.NET).
87
+ //
88
+ // Senders still populate it: a sender that sets `supportedTransports`
89
+ // MUST also set this to a single best-compatibility choice, so
90
+ // implementations predating the list still receive a reply.
91
+ //
92
+ // **Scheduled for removal in v0.0.5.**
93
+ TransportProtocol transportProtocol = 2 [deprecated = true];
94
+
95
+ // Timestamp indicating when this message was created.
96
+ //
97
+ // This value is expressed in UTC and is used for envelope-vs-body
98
+ // timestamp validation, replay detection, and observability.
99
+ google.protobuf.Timestamp timestamp = 3;
100
+
101
+ // Every transport endpoint the sender of this PrePair request can be
102
+ // reached on for the `PrePairResponseMessage`.
103
+ //
104
+ // Each entry is a complete endpoint: a URI plus the protocol that says
105
+ // how to interpret it. The list is written in the sender's own
106
+ // preference order.
107
+ //
108
+ // # Selection is the recipient's
109
+ //
110
+ // A recipient picks whichever entry suits it, ordered by its **own**
111
+ // preference rather than the sender's. The order here expresses
112
+ // availability, not a ranking the recipient must honor.
113
+ //
114
+ // # Compatibility
115
+ //
116
+ // Absent means "only `transportProtocol` is offered", which is how every
117
+ // implementation predating this field behaves. A sender populating this
118
+ // list MUST also set `transportProtocol` to a single best-compatibility
119
+ // choice so those implementations still reply.
120
+ //
121
+ // At least one of the two MUST be present: a request naming no endpoint
122
+ // gives the recipient nowhere to send the response.
123
+ //
124
+ // # Security
125
+ //
126
+ // PrePair traffic is plaintext — no shared key exists yet — so these
127
+ // endpoints are visible to a passive observer, exactly as
128
+ // `transportProtocol` already was. The recipient's transport policy still
129
+ // refuses plaintext entries unless plaintext has been opted into.
130
+ repeated TransportProtocol supportedTransports = 4;
131
+ }
132
+
133
+ // PrePairResponseMessage is the contact creator's reply to a
134
+ // `PrePairRequestMessage`. On success it carries the real public keys; on
135
+ // failure it carries only a non-`Ok` `DeRecResult`.
136
+ //
137
+ // # Validation by the receiver
138
+ //
139
+ // The recipient (the original scanner of the `ContactMessage`) MUST:
140
+ //
141
+ // - Confirm `result.status == OK`. On non-`Ok`, surface a typed error to
142
+ // the application and do not proceed.
143
+ // - Recompute
144
+ //
145
+ // ```text
146
+ // SHA-384(mlkemEncapsulationKey || eciesPublicKey
147
+ // || u64_be(nonce) || u64_be(channelId))
148
+ // ```
149
+ //
150
+ // and verify it matches the original `ContactMessage.contactBindingHash`.
151
+ // - Only on a match, proceed to construct and send a normal
152
+ // `PairRequestMessage`.
153
+ //
154
+ // # Envelope
155
+ //
156
+ // Plaintext-in-`DeRecMessage`-envelope, same as `PrePairRequestMessage`.
157
+ message PrePairResponseMessage {
158
+
159
+ // Result of processing the `PrePairRequestMessage`. On `OK`, the two
160
+ // key fields below are present. On any non-`Ok` status, the key fields
161
+ // MUST be absent.
162
+ DeRecResult result = 1;
163
+
164
+ // Serialized ML-KEM-768 encapsulation key. Present only when
165
+ // `result.status == OK`.
166
+ optional bytes mlkemEncapsulationKey = 2;
167
+
168
+ // Serialized ECIES public key. Present only when
169
+ // `result.status == OK`.
170
+ optional bytes eciesPublicKey = 3;
171
+
172
+ // Echoed from the request for session correlation.
173
+ uint64 nonce = 4;
174
+
175
+ // Timestamp indicating when this message was created.
176
+ google.protobuf.Timestamp timestamp = 5;
177
+ }