beaconcrypt 0.3.4__tar.gz → 0.3.5__tar.gz
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.
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/Cargo.lock +1 -1
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/Cargo.toml +1 -1
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/PKG-INFO +1 -1
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/README.md +15 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/beaconcrypt.go +24 -1
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/beaconcrypt_test.go +147 -9
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/bindings.h +8 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/src/cnsa2.rs +1 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/src/lib.rs +45 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/src/pqxdh.rs +1 -0
- beaconcrypt-0.3.5/tests/protocol.rs +119 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/tests/test_encryption.py +78 -6
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/tests/test_registration.py +15 -1
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/.cargo/config.toml +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/.github/workflows/go.yml +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/.github/workflows/python.yml +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/.github/workflows/release.yml +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/.github/workflows/rust.yml +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/.gitignore +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/LICENSE +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/build.rs +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/clippy.toml +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/doc/protocol.md +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/doc/rationale.md +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/doc/threat_model.md +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/go.mod +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/pyproject.toml +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/rustfmt.toml +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/src/beacon.rs +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/src/error.rs +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/src/schema/cryptoframe.capnp +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/src/schema/phase1.capnp +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/src/schema/phase2.capnp +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/src/schema/protogram.capnp +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/src/server.rs +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/src/shared.rs +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/tests/beacon.rs +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/tests/server.rs +0 -0
- {beaconcrypt-0.3.4 → beaconcrypt-0.3.5}/uv.lock +0 -0
|
@@ -46,14 +46,29 @@ Test the C interface
|
|
|
46
46
|
# Reference implementation
|
|
47
47
|
I don't use rust a lot, so the code is probably fairly naive. It provides both a beacon and server implementation with C bindings through `cbindgen`. Ideally more bindings would be built on top of that so it can be used in the mythic server-side.
|
|
48
48
|
|
|
49
|
+
The reference implementation expects that all beacons are compiled with the server's public key, and that beaconcrypt is initialized with it.
|
|
50
|
+
|
|
51
|
+
The server is currently not very usable as it doesn't support saving the state of any individual beacon. This means that if your server goes down, you will not be able to communicate with any previously-registered beacons anymore. The server doesn't support being initialized with an Ed25519 seed (32 random bytes). Users wishing to use the server in practical cases should use this interface to ensure their server keeps its identity across reboots.
|
|
52
|
+
|
|
49
53
|
## Building
|
|
50
54
|
You will need [Capn'Proto](https://capnproto.org/install.html) (just the binaries) and a recent version of rust for every build.
|
|
51
55
|
|
|
52
56
|
For windows, I prefer building with stable-gnu for normal usage, and nightly-gnu for release builds. You can find the exact arguments I use to the the static library as small as possible in [release.yml](/.github\workflows\release.yml). The MSVC toolchain is expected to work just as well, I just like mingw.
|
|
53
57
|
|
|
58
|
+
Build and run all tests:
|
|
59
|
+
```bash
|
|
60
|
+
cargo test
|
|
61
|
+
cargo build --target x86_64-pc-windows-gnu
|
|
62
|
+
go test ./...
|
|
63
|
+
uv run maturin build --features pybinds --uv
|
|
64
|
+
uv run --no-sync pytest tests
|
|
65
|
+
```
|
|
66
|
+
|
|
54
67
|
## Profiles
|
|
55
68
|
The reference implementation has two profiles: `PQXDH` and `CNSA2`. Profiles are controlled by cargo features. The CNSA2 profile only exists as a test for now. It uses a simple ML-KEM encapsulation for key exchange and the underlying libraries are not FIPS-approved. It is experimental and is likely broken. PQXDH is the intended target and the default.
|
|
56
69
|
|
|
70
|
+
**CNSA2 is currently completely broken**
|
|
71
|
+
|
|
57
72
|
## Usage
|
|
58
73
|
The reference implementation is a library that can currently be used either from rust or through C FFI. The C interface is currently not tested.
|
|
59
74
|
|
|
@@ -33,7 +33,9 @@ beaconcrypt_go_buffer beaconcrypt_go_process_initial_message(void *handle, const
|
|
|
33
33
|
beaconcrypt_go_buffer beaconcrypt_go_encrypt_to_beacon(void *handle, uint64_t key_id, const uint8_t *ptr, uintptr_t len);
|
|
34
34
|
beaconcrypt_go_buffer beaconcrypt_go_encrypt_to_beacon_signed(void *handle, uint64_t key_id, const uint8_t *ptr, uintptr_t len);
|
|
35
35
|
beaconcrypt_go_buffer beaconcrypt_go_decrypt_beacon_message(void *handle, uint64_t key_id, const uint8_t *ptr, uintptr_t len);
|
|
36
|
+
beaconcrypt_go_buffer beaconcrypt_go_decrypt_beacon_message_signed(void *handle, const uint8_t *ptr, uintptr_t len);
|
|
36
37
|
beaconcrypt_go_buffer beaconcrypt_go_encrypt_to_server(void *handle, const uint8_t *ptr, uintptr_t len);
|
|
38
|
+
beaconcrypt_go_buffer beaconcrypt_go_encrypt_to_server_signed(void *handle, const uint8_t *ptr, uintptr_t len);
|
|
37
39
|
beaconcrypt_go_buffer beaconcrypt_go_decrypt_server_message(void *handle, const uint8_t *ptr, uintptr_t len);
|
|
38
40
|
beaconcrypt_go_buffer beaconcrypt_go_decrypt_server_message_signed(void *handle, const uint8_t *ptr, uintptr_t len);
|
|
39
41
|
*/
|
|
@@ -203,6 +205,15 @@ func (s *Server) DecryptBeaconMessage(keyID uint64, ciphertext []byte) ([]byte,
|
|
|
203
205
|
})
|
|
204
206
|
}
|
|
205
207
|
|
|
208
|
+
func (s *Server) DecryptBeaconMessageSigned(ciphertext []byte) ([]byte, error) {
|
|
209
|
+
if s == nil || s.handle == nil {
|
|
210
|
+
return nil, ErrClosed
|
|
211
|
+
}
|
|
212
|
+
return callUnary(ciphertext, func(ptr *C.uint8_t, len C.uintptr_t) C.beaconcrypt_go_buffer {
|
|
213
|
+
return C.beaconcrypt_go_decrypt_beacon_message_signed(s.handle, ptr, len)
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
206
217
|
func (b *Beacon) EncryptToServer(plaintext []byte) ([]byte, error) {
|
|
207
218
|
if b == nil || b.handle == nil {
|
|
208
219
|
return nil, ErrClosed
|
|
@@ -212,6 +223,15 @@ func (b *Beacon) EncryptToServer(plaintext []byte) ([]byte, error) {
|
|
|
212
223
|
})
|
|
213
224
|
}
|
|
214
225
|
|
|
226
|
+
func (b *Beacon) EncryptToServerSigned(plaintext []byte) ([]byte, error) {
|
|
227
|
+
if b == nil || b.handle == nil {
|
|
228
|
+
return nil, ErrClosed
|
|
229
|
+
}
|
|
230
|
+
return callUnary(plaintext, func(ptr *C.uint8_t, len C.uintptr_t) C.beaconcrypt_go_buffer {
|
|
231
|
+
return C.beaconcrypt_go_encrypt_to_server_signed(b.handle, ptr, len)
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
215
235
|
func (b *Beacon) DecryptServerMessage(ciphertext []byte) ([]byte, error) {
|
|
216
236
|
if b == nil || b.handle == nil {
|
|
217
237
|
return nil, ErrClosed
|
|
@@ -240,10 +260,13 @@ func callUnary(data []byte, call func(*C.uint8_t, C.uintptr_t) C.beaconcrypt_go_
|
|
|
240
260
|
}
|
|
241
261
|
|
|
242
262
|
func copyBuffer(buffer C.beaconcrypt_go_buffer) ([]byte, error) {
|
|
243
|
-
if buffer.ptr == nil
|
|
263
|
+
if buffer.ptr == nil {
|
|
244
264
|
return nil, ErrCrypto
|
|
245
265
|
}
|
|
246
266
|
defer C.beaconcrypt_go_free_buffer(buffer)
|
|
267
|
+
if buffer.len == 0 {
|
|
268
|
+
return []byte{}, nil
|
|
269
|
+
}
|
|
247
270
|
return C.GoBytes(unsafe.Pointer(buffer.ptr), C.int(buffer.len)), nil
|
|
248
271
|
}
|
|
249
272
|
|
|
@@ -11,27 +11,34 @@ func registerBeacon(t *testing.T, server *Server, beacon *Beacon) []byte {
|
|
|
11
11
|
t.Helper()
|
|
12
12
|
|
|
13
13
|
message := bytes.Repeat([]byte{0xff}, 32)
|
|
14
|
-
|
|
14
|
+
regOut := registerBeaconWithInitial(t, server, beacon, message)
|
|
15
|
+
|
|
16
|
+
phase2, err := beacon.ProcessInitialMessage(regOut.Serialized)
|
|
15
17
|
if err != nil {
|
|
16
18
|
t.Fatal(err)
|
|
17
19
|
}
|
|
20
|
+
if !bytes.Equal(phase2, message) {
|
|
21
|
+
t.Fatalf("initial message mismatch: got %x want %x", phase2, message)
|
|
22
|
+
}
|
|
23
|
+
return phase2
|
|
24
|
+
}
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
func registerBeaconWithInitial(t *testing.T, server *Server, beacon *Beacon, message []byte) *RegistrationResponse {
|
|
27
|
+
t.Helper()
|
|
28
|
+
|
|
29
|
+
phase1, err := beacon.GenerateRegistration()
|
|
20
30
|
if err != nil {
|
|
21
31
|
t.Fatal(err)
|
|
22
32
|
}
|
|
23
|
-
if regOut.KeyID == 0 {
|
|
24
|
-
t.Fatal("expected non-zero beacon key id")
|
|
25
|
-
}
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
regOut, err := server.RegisterBeacon(phase1, message)
|
|
28
35
|
if err != nil {
|
|
29
36
|
t.Fatal(err)
|
|
30
37
|
}
|
|
31
|
-
if
|
|
32
|
-
t.
|
|
38
|
+
if regOut.KeyID == 0 {
|
|
39
|
+
t.Fatal("expected non-zero beacon key id")
|
|
33
40
|
}
|
|
34
|
-
return
|
|
41
|
+
return regOut
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
func newServer(t *testing.T) *Server {
|
|
@@ -69,6 +76,24 @@ func TestRegister(t *testing.T) {
|
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
78
|
|
|
79
|
+
func TestRegisterWithoutInitialMessage(t *testing.T) {
|
|
80
|
+
server := newServer(t)
|
|
81
|
+
serverPK, err := server.IdentityPK()
|
|
82
|
+
if err != nil {
|
|
83
|
+
t.Fatal(err)
|
|
84
|
+
}
|
|
85
|
+
beacon := newBeacon(t, serverPK)
|
|
86
|
+
|
|
87
|
+
regOut := registerBeaconWithInitial(t, server, beacon, nil)
|
|
88
|
+
phase2, err := beacon.ProcessInitialMessage(regOut.Serialized)
|
|
89
|
+
if err != nil {
|
|
90
|
+
t.Fatal(err)
|
|
91
|
+
}
|
|
92
|
+
if len(phase2) != 0 {
|
|
93
|
+
t.Fatalf("expected empty initial message, got %x", phase2)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
72
97
|
func TestEncryptToMultiple(t *testing.T) {
|
|
73
98
|
server := newServer(t)
|
|
74
99
|
serverPK, err := server.IdentityPK()
|
|
@@ -226,3 +251,116 @@ func TestDecryptCatchUp(t *testing.T) {
|
|
|
226
251
|
t.Fatalf("catch-up decrypt mismatch: got %x and %x want %x", plain1, plain2, message)
|
|
227
252
|
}
|
|
228
253
|
}
|
|
254
|
+
|
|
255
|
+
func TestBeaconEncryptsToServer(t *testing.T) {
|
|
256
|
+
server := newServer(t)
|
|
257
|
+
serverPK, err := server.IdentityPK()
|
|
258
|
+
if err != nil {
|
|
259
|
+
t.Fatal(err)
|
|
260
|
+
}
|
|
261
|
+
beacon := newBeacon(t, serverPK)
|
|
262
|
+
message := []byte("beacon to server")
|
|
263
|
+
|
|
264
|
+
registerBeacon(t, server, beacon)
|
|
265
|
+
ciphertext, err := beacon.EncryptToServer(message)
|
|
266
|
+
if err != nil {
|
|
267
|
+
t.Fatal(err)
|
|
268
|
+
}
|
|
269
|
+
plaintext, err := server.DecryptBeaconMessage(1, ciphertext)
|
|
270
|
+
if err != nil {
|
|
271
|
+
t.Fatal(err)
|
|
272
|
+
}
|
|
273
|
+
if !bytes.Equal(plaintext, message) {
|
|
274
|
+
t.Fatalf("beacon-to-server decrypt mismatch: got %x want %x", plaintext, message)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
func TestBeaconEncryptsToServerSigned(t *testing.T) {
|
|
279
|
+
server := newServer(t)
|
|
280
|
+
serverPK, err := server.IdentityPK()
|
|
281
|
+
if err != nil {
|
|
282
|
+
t.Fatal(err)
|
|
283
|
+
}
|
|
284
|
+
beacon := newBeacon(t, serverPK)
|
|
285
|
+
message := []byte("signed beacon to server")
|
|
286
|
+
|
|
287
|
+
registerBeacon(t, server, beacon)
|
|
288
|
+
signed, err := beacon.EncryptToServerSigned(message)
|
|
289
|
+
if err != nil {
|
|
290
|
+
t.Fatal(err)
|
|
291
|
+
}
|
|
292
|
+
plaintext, err := server.DecryptBeaconMessageSigned(signed)
|
|
293
|
+
if err != nil {
|
|
294
|
+
t.Fatal(err)
|
|
295
|
+
}
|
|
296
|
+
if !bytes.Equal(plaintext, message) {
|
|
297
|
+
t.Fatalf("signed beacon-to-server decrypt mismatch: got %x want %x", plaintext, message)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
func TestSignedServerMessageRejectsTampering(t *testing.T) {
|
|
302
|
+
server := newServer(t)
|
|
303
|
+
serverPK, err := server.IdentityPK()
|
|
304
|
+
if err != nil {
|
|
305
|
+
t.Fatal(err)
|
|
306
|
+
}
|
|
307
|
+
beacon := newBeacon(t, serverPK)
|
|
308
|
+
|
|
309
|
+
registerBeacon(t, server, beacon)
|
|
310
|
+
signed, err := server.EncryptToBeaconSigned(1, []byte("server to beacon"))
|
|
311
|
+
if err != nil {
|
|
312
|
+
t.Fatal(err)
|
|
313
|
+
}
|
|
314
|
+
signed[len(signed)-1] ^= 0x01
|
|
315
|
+
|
|
316
|
+
if _, err := beacon.DecryptServerMessageSigned(signed); err == nil {
|
|
317
|
+
t.Fatal("expected tampered signed message to be rejected")
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
func TestBeaconCannotDecryptMessageForDifferentBeacon(t *testing.T) {
|
|
322
|
+
server := newServer(t)
|
|
323
|
+
serverPK, err := server.IdentityPK()
|
|
324
|
+
if err != nil {
|
|
325
|
+
t.Fatal(err)
|
|
326
|
+
}
|
|
327
|
+
b1 := newBeacon(t, serverPK)
|
|
328
|
+
b2 := newBeacon(t, serverPK)
|
|
329
|
+
|
|
330
|
+
registerBeacon(t, server, b1)
|
|
331
|
+
registerBeacon(t, server, b2)
|
|
332
|
+
ciphertext, err := server.EncryptToBeacon(1, []byte("for b1 only"))
|
|
333
|
+
if err != nil {
|
|
334
|
+
t.Fatal(err)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if _, err := b2.DecryptServerMessage(ciphertext); err == nil {
|
|
338
|
+
t.Fatal("expected message for another beacon to be rejected")
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
func TestCiphertextCannotBeReplayed(t *testing.T) {
|
|
343
|
+
server := newServer(t)
|
|
344
|
+
serverPK, err := server.IdentityPK()
|
|
345
|
+
if err != nil {
|
|
346
|
+
t.Fatal(err)
|
|
347
|
+
}
|
|
348
|
+
beacon := newBeacon(t, serverPK)
|
|
349
|
+
message := []byte("one shot")
|
|
350
|
+
|
|
351
|
+
registerBeacon(t, server, beacon)
|
|
352
|
+
ciphertext, err := server.EncryptToBeacon(1, message)
|
|
353
|
+
if err != nil {
|
|
354
|
+
t.Fatal(err)
|
|
355
|
+
}
|
|
356
|
+
plaintext, err := beacon.DecryptServerMessage(ciphertext)
|
|
357
|
+
if err != nil {
|
|
358
|
+
t.Fatal(err)
|
|
359
|
+
}
|
|
360
|
+
if !bytes.Equal(plaintext, message) {
|
|
361
|
+
t.Fatalf("replay setup decrypt mismatch: got %x want %x", plaintext, message)
|
|
362
|
+
}
|
|
363
|
+
if _, err := beacon.DecryptServerMessage(ciphertext); err == nil {
|
|
364
|
+
t.Fatal("expected replayed ciphertext to be rejected")
|
|
365
|
+
}
|
|
366
|
+
}
|
|
@@ -455,10 +455,18 @@ struct beaconcrypt_GoBuffer beaconcrypt_go_decrypt_beacon_message(struct beaconc
|
|
|
455
455
|
const uint8_t *ptr,
|
|
456
456
|
uintptr_t len);
|
|
457
457
|
|
|
458
|
+
struct beaconcrypt_GoBuffer beaconcrypt_go_decrypt_beacon_message_signed(struct beaconcrypt_BeaconCryptPqxdh *handle,
|
|
459
|
+
const uint8_t *ptr,
|
|
460
|
+
uintptr_t len);
|
|
461
|
+
|
|
458
462
|
struct beaconcrypt_GoBuffer beaconcrypt_go_encrypt_to_server(struct beaconcrypt_BeaconCryptPqxdh *handle,
|
|
459
463
|
const uint8_t *ptr,
|
|
460
464
|
uintptr_t len);
|
|
461
465
|
|
|
466
|
+
struct beaconcrypt_GoBuffer beaconcrypt_go_encrypt_to_server_signed(struct beaconcrypt_BeaconCryptPqxdh *handle,
|
|
467
|
+
const uint8_t *ptr,
|
|
468
|
+
uintptr_t len);
|
|
469
|
+
|
|
462
470
|
struct beaconcrypt_GoBuffer beaconcrypt_go_decrypt_server_message(struct beaconcrypt_BeaconCryptPqxdh *handle,
|
|
463
471
|
const uint8_t *ptr,
|
|
464
472
|
uintptr_t len);
|
|
@@ -320,6 +320,7 @@ impl ProviderBeacon for BeaconCryptCnsa2 {
|
|
|
320
320
|
self.init_ratchets(&derived_secret, &info_str, true, srv_key_id);
|
|
321
321
|
|
|
322
322
|
match response.get_app_cipher_text() {
|
|
323
|
+
Ok(ciphertext) if ciphertext.is_empty() => Some(vec![]),
|
|
323
324
|
Ok(ciphertext) => match self.decrypt_message(ciphertext, srv_key_id, true) {
|
|
324
325
|
Some(plaintext) => Some(plaintext),
|
|
325
326
|
None => None,
|
|
@@ -296,6 +296,28 @@ mod gobinds {
|
|
|
296
296
|
decrypt(handle, ptr, len, key_id, false)
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
#[unsafe(no_mangle)]
|
|
300
|
+
pub extern "C" fn beaconcrypt_go_decrypt_beacon_message_signed(
|
|
301
|
+
handle: *mut BeaconCryptPqxdh,
|
|
302
|
+
ptr: *const u8,
|
|
303
|
+
len: usize,
|
|
304
|
+
) -> GoBuffer {
|
|
305
|
+
if handle.is_null() {
|
|
306
|
+
return empty_buffer();
|
|
307
|
+
}
|
|
308
|
+
let Some(data) = (unsafe { input(ptr, len) }) else {
|
|
309
|
+
return empty_buffer();
|
|
310
|
+
};
|
|
311
|
+
let provider = unsafe { &mut *handle };
|
|
312
|
+
match provider.verify_signature(data) {
|
|
313
|
+
Some(verified) => provider
|
|
314
|
+
.decrypt_message(&verified.data, verified.key_id, false)
|
|
315
|
+
.map(into_buffer)
|
|
316
|
+
.unwrap_or_else(empty_buffer),
|
|
317
|
+
None => empty_buffer(),
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
299
321
|
#[unsafe(no_mangle)]
|
|
300
322
|
pub extern "C" fn beaconcrypt_go_encrypt_to_server(
|
|
301
323
|
handle: *mut BeaconCryptPqxdh,
|
|
@@ -309,6 +331,29 @@ mod gobinds {
|
|
|
309
331
|
encrypt(handle, ptr, len, false, provider.server_kid())
|
|
310
332
|
}
|
|
311
333
|
|
|
334
|
+
#[unsafe(no_mangle)]
|
|
335
|
+
pub extern "C" fn beaconcrypt_go_encrypt_to_server_signed(
|
|
336
|
+
handle: *mut BeaconCryptPqxdh,
|
|
337
|
+
ptr: *const u8,
|
|
338
|
+
len: usize,
|
|
339
|
+
) -> GoBuffer {
|
|
340
|
+
if handle.is_null() {
|
|
341
|
+
return empty_buffer();
|
|
342
|
+
}
|
|
343
|
+
let Some(data) = (unsafe { input(ptr, len) }) else {
|
|
344
|
+
return empty_buffer();
|
|
345
|
+
};
|
|
346
|
+
let provider = unsafe { &mut *handle };
|
|
347
|
+
let srv_kid = provider.server_kid();
|
|
348
|
+
match provider.encrypt_message(data, false, srv_kid) {
|
|
349
|
+
Some(ciphertext) => provider
|
|
350
|
+
.sign_message(ciphertext.as_slice())
|
|
351
|
+
.map(into_buffer)
|
|
352
|
+
.unwrap_or_else(empty_buffer),
|
|
353
|
+
None => empty_buffer(),
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
312
357
|
#[unsafe(no_mangle)]
|
|
313
358
|
pub extern "C" fn beaconcrypt_go_decrypt_server_message(
|
|
314
359
|
handle: *mut BeaconCryptPqxdh,
|
|
@@ -365,6 +365,7 @@ impl ProviderBeacon for BeaconCryptPqxdh {
|
|
|
365
365
|
self.init_ratchets(&derived_secret, &info_str, true, srv_key_id);
|
|
366
366
|
|
|
367
367
|
match response.get_app_cipher_text() {
|
|
368
|
+
Ok(ciphertext) if ciphertext.is_empty() => Some(vec![]),
|
|
368
369
|
Ok(ciphertext) => self.decrypt_message(ciphertext, srv_key_id, true),
|
|
369
370
|
Err(_) => Some(vec![0u8; 0]),
|
|
370
371
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
use beaconcrypt::*;
|
|
2
|
+
|
|
3
|
+
const SERVER_KID: u64 = 0;
|
|
4
|
+
|
|
5
|
+
fn new_pair() -> (BeaconCryptPqxdh, BeaconCryptPqxdh) {
|
|
6
|
+
let server = BeaconCryptPqxdh::new(false, SERVER_KID, None, None);
|
|
7
|
+
let server_id = server.identity_pk().to_owned();
|
|
8
|
+
let beacon = BeaconCryptPqxdh::new(true, SERVER_KID, Some(server_id.as_bytes()), None);
|
|
9
|
+
(server, beacon)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
fn register_beacon(
|
|
13
|
+
server: &mut BeaconCryptPqxdh,
|
|
14
|
+
beacon: &mut BeaconCryptPqxdh,
|
|
15
|
+
initial_message: Option<&[u8]>,
|
|
16
|
+
) -> RegResponse {
|
|
17
|
+
let phase_1 = beacon.get_registration_bundle().unwrap();
|
|
18
|
+
let reg_out = server.get_shared_secret(&phase_1).unwrap();
|
|
19
|
+
let phase_2 = server
|
|
20
|
+
.build_registration_response(reg_out, initial_message)
|
|
21
|
+
.unwrap();
|
|
22
|
+
let plaintext = beacon.finish_registration(&phase_2.serialized).unwrap();
|
|
23
|
+
assert_eq!(plaintext, initial_message.unwrap_or(&[]));
|
|
24
|
+
phase_2
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[test]
|
|
28
|
+
fn registration_can_omit_initial_message() {
|
|
29
|
+
let (mut server, mut beacon) = new_pair();
|
|
30
|
+
|
|
31
|
+
let response = register_beacon(&mut server, &mut beacon, None);
|
|
32
|
+
|
|
33
|
+
assert_eq!(response.kid, 1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#[test]
|
|
37
|
+
fn beacon_can_encrypt_to_server() {
|
|
38
|
+
let (mut server, mut beacon) = new_pair();
|
|
39
|
+
let response = register_beacon(&mut server, &mut beacon, Some(&[0xFF; 32]));
|
|
40
|
+
let message = b"beacon to server";
|
|
41
|
+
|
|
42
|
+
let ciphertext = beacon
|
|
43
|
+
.encrypt_message(message.as_slice(), false, SERVER_KID)
|
|
44
|
+
.unwrap();
|
|
45
|
+
let plaintext = server
|
|
46
|
+
.decrypt_message(&ciphertext, response.kid, false)
|
|
47
|
+
.unwrap();
|
|
48
|
+
|
|
49
|
+
assert_eq!(plaintext, message);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#[test]
|
|
53
|
+
fn beacon_can_encrypt_to_server_signed() {
|
|
54
|
+
let (mut server, mut beacon) = new_pair();
|
|
55
|
+
let response = register_beacon(&mut server, &mut beacon, Some(&[0xFF; 32]));
|
|
56
|
+
let message = b"signed beacon to server";
|
|
57
|
+
|
|
58
|
+
let ciphertext = beacon
|
|
59
|
+
.encrypt_message(message.as_slice(), false, SERVER_KID)
|
|
60
|
+
.unwrap();
|
|
61
|
+
let signed = beacon.sign_message(&ciphertext).unwrap();
|
|
62
|
+
let verified = server.verify_signature(&signed).unwrap();
|
|
63
|
+
let plaintext = server
|
|
64
|
+
.decrypt_message(&verified.data, verified.key_id, false)
|
|
65
|
+
.unwrap();
|
|
66
|
+
|
|
67
|
+
assert_eq!(verified.key_id, response.kid);
|
|
68
|
+
assert_eq!(plaintext, message);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[test]
|
|
72
|
+
fn signed_server_message_rejects_tampering() {
|
|
73
|
+
let (mut server, mut beacon) = new_pair();
|
|
74
|
+
let response = register_beacon(&mut server, &mut beacon, Some(&[0xFF; 32]));
|
|
75
|
+
|
|
76
|
+
let ciphertext = server
|
|
77
|
+
.encrypt_message(b"server to beacon", true, response.kid)
|
|
78
|
+
.unwrap();
|
|
79
|
+
let mut signed = server.sign_message(&ciphertext).unwrap();
|
|
80
|
+
let last = signed.len() - 1;
|
|
81
|
+
signed[last] ^= 0x01;
|
|
82
|
+
|
|
83
|
+
assert!(beacon.verify_signature(&signed).is_none());
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#[test]
|
|
87
|
+
fn beacon_cannot_decrypt_message_for_different_beacon() {
|
|
88
|
+
let mut server = BeaconCryptPqxdh::new(false, SERVER_KID, None, None);
|
|
89
|
+
let server_id = server.identity_pk().to_owned();
|
|
90
|
+
let mut b1 = BeaconCryptPqxdh::new(true, SERVER_KID, Some(server_id.as_bytes()), None);
|
|
91
|
+
let mut b2 = BeaconCryptPqxdh::new(true, SERVER_KID, Some(server_id.as_bytes()), None);
|
|
92
|
+
let b1_response = register_beacon(&mut server, &mut b1, Some(&[0xFF; 32]));
|
|
93
|
+
let _ = register_beacon(&mut server, &mut b2, Some(&[0xFF; 32]));
|
|
94
|
+
|
|
95
|
+
let ciphertext = server
|
|
96
|
+
.encrypt_message(b"for b1 only", true, b1_response.kid)
|
|
97
|
+
.unwrap();
|
|
98
|
+
|
|
99
|
+
assert!(b2.decrypt_message(&ciphertext, SERVER_KID, true).is_none());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#[test]
|
|
103
|
+
fn ciphertext_cannot_be_replayed() {
|
|
104
|
+
let (mut server, mut beacon) = new_pair();
|
|
105
|
+
let response = register_beacon(&mut server, &mut beacon, Some(&[0xFF; 32]));
|
|
106
|
+
let message = b"one shot";
|
|
107
|
+
|
|
108
|
+
let ciphertext = server.encrypt_message(message, true, response.kid).unwrap();
|
|
109
|
+
let first = beacon
|
|
110
|
+
.decrypt_message(&ciphertext, SERVER_KID, true)
|
|
111
|
+
.unwrap();
|
|
112
|
+
|
|
113
|
+
assert_eq!(first, message);
|
|
114
|
+
assert!(
|
|
115
|
+
beacon
|
|
116
|
+
.decrypt_message(&ciphertext, SERVER_KID, true)
|
|
117
|
+
.is_none()
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -5,7 +5,7 @@ def register_beacon(
|
|
|
5
5
|
server: BeaconCryptServer,
|
|
6
6
|
beacon: BeaconCryptBeacon,
|
|
7
7
|
) -> bytes | None:
|
|
8
|
-
message = bytes(0xFF * 32
|
|
8
|
+
message = bytes([0xFF]) * 32
|
|
9
9
|
phase_1 = beacon.generate_registration()
|
|
10
10
|
assert phase_1 is not None
|
|
11
11
|
reg_out = server.register_beacon(phase_1, message)
|
|
@@ -21,7 +21,7 @@ def test_encrypt_to_multiple():
|
|
|
21
21
|
server_pk = server.id_pk()
|
|
22
22
|
b1 = BeaconCryptBeacon(0, server_pk)
|
|
23
23
|
b2 = BeaconCryptBeacon(0, server_pk)
|
|
24
|
-
message = bytes(0x1 * 32
|
|
24
|
+
message = bytes([0x1]) * 32
|
|
25
25
|
|
|
26
26
|
b1_initial = register_beacon(server, b1)
|
|
27
27
|
b2_initial = register_beacon(server, b2)
|
|
@@ -36,7 +36,7 @@ def test_encrypt_multiple():
|
|
|
36
36
|
server = BeaconCryptServer(0, None)
|
|
37
37
|
server_pk = server.id_pk()
|
|
38
38
|
b1 = BeaconCryptBeacon(0, server_pk)
|
|
39
|
-
message = bytes(0x1 * 32
|
|
39
|
+
message = bytes([0x1]) * 32
|
|
40
40
|
|
|
41
41
|
_ = register_beacon(server, b1)
|
|
42
42
|
|
|
@@ -49,7 +49,7 @@ def test_decrypt_multiple():
|
|
|
49
49
|
server = BeaconCryptServer(0, None)
|
|
50
50
|
server_pk = server.id_pk()
|
|
51
51
|
beacon = BeaconCryptBeacon(0, server_pk)
|
|
52
|
-
message = bytes(0x1 * 32
|
|
52
|
+
message = bytes([0x1]) * 32
|
|
53
53
|
|
|
54
54
|
_ = register_beacon(server, beacon)
|
|
55
55
|
m1 = server.encrypt_to_beacon(message, 1)
|
|
@@ -66,7 +66,7 @@ def test_decrypt_multiple_signed():
|
|
|
66
66
|
server = BeaconCryptServer(0, None)
|
|
67
67
|
server_pk = server.id_pk()
|
|
68
68
|
beacon = BeaconCryptBeacon(0, server_pk)
|
|
69
|
-
message = bytes(0x1 * 32
|
|
69
|
+
message = bytes([0x1]) * 32
|
|
70
70
|
|
|
71
71
|
_ = register_beacon(server, beacon)
|
|
72
72
|
m1 = server.encrypt_to_beacon_signed(message, 1)
|
|
@@ -83,7 +83,7 @@ def test_decrypt_catch_up():
|
|
|
83
83
|
server = BeaconCryptServer(0, None)
|
|
84
84
|
server_pk = server.id_pk()
|
|
85
85
|
beacon = BeaconCryptBeacon(0, server_pk)
|
|
86
|
-
message = bytes(0x1 * 32
|
|
86
|
+
message = bytes([0x1]) * 32
|
|
87
87
|
|
|
88
88
|
_ = register_beacon(server, beacon)
|
|
89
89
|
m1 = server.encrypt_to_beacon(message, 1)
|
|
@@ -94,3 +94,75 @@ def test_decrypt_catch_up():
|
|
|
94
94
|
plain1 = beacon.decrypt_server_message(m1)
|
|
95
95
|
assert plain2 is not None and plain1 is not None
|
|
96
96
|
assert plain2 == plain1 == message
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_beacon_encrypts_to_server():
|
|
100
|
+
server = BeaconCryptServer(0, None)
|
|
101
|
+
server_pk = server.id_pk()
|
|
102
|
+
beacon = BeaconCryptBeacon(0, server_pk)
|
|
103
|
+
message = b"beacon to server"
|
|
104
|
+
|
|
105
|
+
_ = register_beacon(server, beacon)
|
|
106
|
+
ciphertext = beacon.encrypt_message_to_server(message)
|
|
107
|
+
assert ciphertext is not None
|
|
108
|
+
|
|
109
|
+
plaintext = server.decrypt_beacon_message(ciphertext, 1)
|
|
110
|
+
assert plaintext == message
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_beacon_encrypts_to_server_signed():
|
|
114
|
+
server = BeaconCryptServer(0, None)
|
|
115
|
+
server_pk = server.id_pk()
|
|
116
|
+
beacon = BeaconCryptBeacon(0, server_pk)
|
|
117
|
+
message = b"signed beacon to server"
|
|
118
|
+
|
|
119
|
+
_ = register_beacon(server, beacon)
|
|
120
|
+
signed = beacon.encrypt_to_server_signed(message)
|
|
121
|
+
assert signed is not None
|
|
122
|
+
|
|
123
|
+
plaintext = server.decrypt_beacon_message_signed(signed)
|
|
124
|
+
assert plaintext == message
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_signed_server_message_rejects_tampering():
|
|
128
|
+
server = BeaconCryptServer(0, None)
|
|
129
|
+
server_pk = server.id_pk()
|
|
130
|
+
beacon = BeaconCryptBeacon(0, server_pk)
|
|
131
|
+
message = b"server to beacon"
|
|
132
|
+
|
|
133
|
+
_ = register_beacon(server, beacon)
|
|
134
|
+
signed = server.encrypt_to_beacon_signed(message, 1)
|
|
135
|
+
assert signed is not None
|
|
136
|
+
tampered = bytearray(signed)
|
|
137
|
+
tampered[-1] ^= 0x01
|
|
138
|
+
|
|
139
|
+
assert beacon.decrypt_server_message_signed(bytes(tampered)) is None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_beacon_cannot_decrypt_message_for_different_beacon():
|
|
143
|
+
server = BeaconCryptServer(0, None)
|
|
144
|
+
server_pk = server.id_pk()
|
|
145
|
+
b1 = BeaconCryptBeacon(0, server_pk)
|
|
146
|
+
b2 = BeaconCryptBeacon(0, server_pk)
|
|
147
|
+
message = b"for b1 only"
|
|
148
|
+
|
|
149
|
+
_ = register_beacon(server, b1)
|
|
150
|
+
_ = register_beacon(server, b2)
|
|
151
|
+
ciphertext = server.encrypt_to_beacon(message, 1)
|
|
152
|
+
assert ciphertext is not None
|
|
153
|
+
|
|
154
|
+
assert b2.decrypt_server_message(ciphertext) is None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_ciphertext_cannot_be_replayed():
|
|
158
|
+
server = BeaconCryptServer(0, None)
|
|
159
|
+
server_pk = server.id_pk()
|
|
160
|
+
beacon = BeaconCryptBeacon(0, server_pk)
|
|
161
|
+
message = b"one shot"
|
|
162
|
+
|
|
163
|
+
_ = register_beacon(server, beacon)
|
|
164
|
+
ciphertext = server.encrypt_to_beacon(message, 1)
|
|
165
|
+
assert ciphertext is not None
|
|
166
|
+
|
|
167
|
+
assert beacon.decrypt_server_message(ciphertext) == message
|
|
168
|
+
assert beacon.decrypt_server_message(ciphertext) is None
|
|
@@ -5,7 +5,7 @@ def register_beacon(
|
|
|
5
5
|
server: BeaconCryptServer,
|
|
6
6
|
beacon: BeaconCryptBeacon,
|
|
7
7
|
) -> bytes | None:
|
|
8
|
-
message = bytes(0xFF * 32
|
|
8
|
+
message = bytes([0xFF]) * 32
|
|
9
9
|
phase_1 = beacon.generate_registration()
|
|
10
10
|
assert phase_1 is not None
|
|
11
11
|
reg_out = server.register_beacon(phase_1, message)
|
|
@@ -22,3 +22,17 @@ def test_register():
|
|
|
22
22
|
beacon = BeaconCryptBeacon(0, server_pk)
|
|
23
23
|
|
|
24
24
|
assert register_beacon(server, beacon) is not None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_register_without_initial_message():
|
|
28
|
+
server = BeaconCryptServer(0, None)
|
|
29
|
+
server_pk = server.id_pk()
|
|
30
|
+
beacon = BeaconCryptBeacon(0, server_pk)
|
|
31
|
+
|
|
32
|
+
phase_1 = beacon.generate_registration()
|
|
33
|
+
assert phase_1 is not None
|
|
34
|
+
reg_out = server.register_beacon(phase_1, None)
|
|
35
|
+
assert reg_out is not None
|
|
36
|
+
|
|
37
|
+
phase_2 = beacon.process_initial_message(reg_out.serialized())
|
|
38
|
+
assert phase_2 == b""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|