beaconcrypt 0.3.2__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.2 → beaconcrypt-0.3.5}/.github/workflows/python.yml +5 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/Cargo.lock +1 -1
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/Cargo.toml +1 -1
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/PKG-INFO +1 -1
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/README.md +15 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/beaconcrypt.go +24 -1
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/beaconcrypt_test.go +173 -15
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/bindings.h +8 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/pyproject.toml +4 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/src/cnsa2.rs +1 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/src/lib.rs +47 -2
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/src/pqxdh.rs +8 -8
- beaconcrypt-0.3.5/tests/protocol.rs +119 -0
- beaconcrypt-0.3.5/tests/test_encryption.py +168 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/tests/test_registration.py +17 -2
- beaconcrypt-0.3.2/tests/test_encryption.py +0 -92
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/.cargo/config.toml +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/.github/workflows/go.yml +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/.github/workflows/release.yml +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/.github/workflows/rust.yml +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/.gitignore +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/LICENSE +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/build.rs +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/clippy.toml +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/doc/protocol.md +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/doc/rationale.md +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/doc/threat_model.md +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/go.mod +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/rustfmt.toml +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/src/beacon.rs +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/src/error.rs +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/src/schema/cryptoframe.capnp +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/src/schema/phase1.capnp +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/src/schema/phase2.capnp +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/src/schema/protogram.capnp +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/src/server.rs +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/src/shared.rs +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/tests/beacon.rs +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/tests/server.rs +0 -0
- {beaconcrypt-0.3.2 → beaconcrypt-0.3.5}/uv.lock +0 -0
|
@@ -31,6 +31,11 @@ jobs:
|
|
|
31
31
|
run: |
|
|
32
32
|
sudo apt install capnproto
|
|
33
33
|
|
|
34
|
+
- name: Run Python tests
|
|
35
|
+
run: |
|
|
36
|
+
uv run --group dev maturin develop --bindings pyo3 --features pybinds
|
|
37
|
+
uv run --group dev pytest tests
|
|
38
|
+
|
|
34
39
|
- name: Build source distribution
|
|
35
40
|
run: uvx maturin sdist --out dist
|
|
36
41
|
|
|
@@ -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 {
|
|
@@ -58,17 +65,43 @@ func newBeacon(t *testing.T, serverPK []byte) *Beacon {
|
|
|
58
65
|
|
|
59
66
|
func TestRegister(t *testing.T) {
|
|
60
67
|
server := newServer(t)
|
|
61
|
-
|
|
68
|
+
serverPK, err := server.IdentityPK()
|
|
69
|
+
if err != nil {
|
|
70
|
+
t.Fatal(err)
|
|
71
|
+
}
|
|
72
|
+
beacon := newBeacon(t, serverPK)
|
|
62
73
|
|
|
63
74
|
if registerBeacon(t, server, beacon) == nil {
|
|
64
75
|
t.Fatal("expected registration to return initial message")
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
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
|
+
|
|
68
97
|
func TestEncryptToMultiple(t *testing.T) {
|
|
69
98
|
server := newServer(t)
|
|
70
|
-
|
|
71
|
-
|
|
99
|
+
serverPK, err := server.IdentityPK()
|
|
100
|
+
if err != nil {
|
|
101
|
+
t.Fatal(err)
|
|
102
|
+
}
|
|
103
|
+
b1 := newBeacon(t, serverPK)
|
|
104
|
+
b2 := newBeacon(t, serverPK)
|
|
72
105
|
message := bytes.Repeat([]byte{0x01}, 32)
|
|
73
106
|
|
|
74
107
|
b1Initial := registerBeacon(t, server, b1)
|
|
@@ -92,7 +125,11 @@ func TestEncryptToMultiple(t *testing.T) {
|
|
|
92
125
|
|
|
93
126
|
func TestEncryptMultiple(t *testing.T) {
|
|
94
127
|
server := newServer(t)
|
|
95
|
-
|
|
128
|
+
serverPK, err := server.IdentityPK()
|
|
129
|
+
if err != nil {
|
|
130
|
+
t.Fatal(err)
|
|
131
|
+
}
|
|
132
|
+
b1 := newBeacon(t, serverPK)
|
|
96
133
|
message := bytes.Repeat([]byte{0x01}, 32)
|
|
97
134
|
|
|
98
135
|
registerBeacon(t, server, b1)
|
|
@@ -112,7 +149,11 @@ func TestEncryptMultiple(t *testing.T) {
|
|
|
112
149
|
|
|
113
150
|
func TestDecryptMultiple(t *testing.T) {
|
|
114
151
|
server := newServer(t)
|
|
115
|
-
|
|
152
|
+
serverPK, err := server.IdentityPK()
|
|
153
|
+
if err != nil {
|
|
154
|
+
t.Fatal(err)
|
|
155
|
+
}
|
|
156
|
+
beacon := newBeacon(t, serverPK)
|
|
116
157
|
message := bytes.Repeat([]byte{0x01}, 32)
|
|
117
158
|
|
|
118
159
|
registerBeacon(t, server, beacon)
|
|
@@ -143,7 +184,11 @@ func TestDecryptMultiple(t *testing.T) {
|
|
|
143
184
|
|
|
144
185
|
func TestDecryptMultipleSigned(t *testing.T) {
|
|
145
186
|
server := newServer(t)
|
|
146
|
-
|
|
187
|
+
serverPK, err := server.IdentityPK()
|
|
188
|
+
if err != nil {
|
|
189
|
+
t.Fatal(err)
|
|
190
|
+
}
|
|
191
|
+
beacon := newBeacon(t, serverPK)
|
|
147
192
|
message := bytes.Repeat([]byte{0x01}, 32)
|
|
148
193
|
|
|
149
194
|
registerBeacon(t, server, beacon)
|
|
@@ -206,3 +251,116 @@ func TestDecryptCatchUp(t *testing.T) {
|
|
|
206
251
|
t.Fatalf("catch-up decrypt mismatch: got %x and %x want %x", plain1, plain2, message)
|
|
207
252
|
}
|
|
208
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,
|
|
@@ -475,9 +520,9 @@ pub mod beaconcrypt_py {
|
|
|
475
520
|
#[pymethods]
|
|
476
521
|
impl Beacon {
|
|
477
522
|
#[new]
|
|
478
|
-
fn new(server_kid: u64, server_id_pk:
|
|
523
|
+
fn new(server_kid: u64, server_id_pk: &[u8]) -> Self {
|
|
479
524
|
Self {
|
|
480
|
-
_0: BeaconCryptPqxdh::new(true, server_kid, server_id_pk, None),
|
|
525
|
+
_0: BeaconCryptPqxdh::new(true, server_kid, Some(server_id_pk), None),
|
|
481
526
|
}
|
|
482
527
|
}
|
|
483
528
|
|
|
@@ -143,7 +143,7 @@ impl CryptoProvider for BeaconCryptPqxdh {
|
|
|
143
143
|
let signed = crypto_sign::sign(data, self.identity_sk()).ok()?;
|
|
144
144
|
builder.set_data(&signed);
|
|
145
145
|
let mut buffer = vec![];
|
|
146
|
-
capnp::serialize_packed::write_message(&mut buffer, t_builder.borrow_inner()).
|
|
146
|
+
capnp::serialize_packed::write_message(&mut buffer, t_builder.borrow_inner()).ok()?;
|
|
147
147
|
Some(buffer)
|
|
148
148
|
}
|
|
149
149
|
|
|
@@ -315,7 +315,7 @@ impl ProviderBeacon for BeaconCryptPqxdh {
|
|
|
315
315
|
bundle.set_pq_key(&pq_sig);
|
|
316
316
|
|
|
317
317
|
let mut buffer = vec![];
|
|
318
|
-
capnp::serialize::write_message(&mut buffer, msg.borrow_inner()).
|
|
318
|
+
capnp::serialize::write_message(&mut buffer, msg.borrow_inner()).ok()?;
|
|
319
319
|
Some(buffer)
|
|
320
320
|
}
|
|
321
321
|
|
|
@@ -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
|
}
|
|
@@ -374,17 +375,16 @@ impl ProviderBeacon for BeaconCryptPqxdh {
|
|
|
374
375
|
#[cfg(feature = "server")]
|
|
375
376
|
impl ProviderServer for BeaconCryptPqxdh {
|
|
376
377
|
fn get_shared_secret(&mut self, buffer: &[u8]) -> Option<RegistrationOutput> {
|
|
377
|
-
let reader = capnp::serialize::read_message(buffer, ReaderOptions::new()).
|
|
378
|
+
let reader = capnp::serialize::read_message(buffer, ReaderOptions::new()).ok()?;
|
|
378
379
|
let typed_reader = TypedReader::<_, phase1_capnp::init_kex::Owned>::new(reader);
|
|
379
|
-
let registration = typed_reader.get().
|
|
380
|
+
let registration = typed_reader.get().ok()?;
|
|
380
381
|
|
|
381
382
|
let decoded_beacon_id = decode_sign(registration.get_identity_key().ok()?).ok()?;
|
|
382
383
|
let remote_id = crypto_sign::PublicKey::from_bytes(&decoded_beacon_id).ok()?;
|
|
383
|
-
let pq_verified = crypto_sign::verify(registration.get_pq_key().ok()?, &remote_id)
|
|
384
|
-
let prekey_verified =
|
|
385
|
-
crypto_sign::verify(registration.get_pre_key().ok()?, &remote_id).unwrap();
|
|
384
|
+
let pq_verified = crypto_sign::verify(registration.get_pq_key().ok()?, &remote_id)?;
|
|
385
|
+
let prekey_verified = crypto_sign::verify(registration.get_pre_key().ok()?, &remote_id)?;
|
|
386
386
|
let onetime_verified =
|
|
387
|
-
crypto_sign::verify(registration.get_one_time_key().ok()?, &remote_id)
|
|
387
|
+
crypto_sign::verify(registration.get_one_time_key().ok()?, &remote_id)?;
|
|
388
388
|
|
|
389
389
|
let beacon_prekey =
|
|
390
390
|
crypto_kx::PublicKey::from_bytes(&decode_kem(&prekey_verified).ok()?).ok()?;
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from beaconcrypt import BeaconCryptBeacon, BeaconCryptServer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def register_beacon(
|
|
5
|
+
server: BeaconCryptServer,
|
|
6
|
+
beacon: BeaconCryptBeacon,
|
|
7
|
+
) -> bytes | None:
|
|
8
|
+
message = bytes([0xFF]) * 32
|
|
9
|
+
phase_1 = beacon.generate_registration()
|
|
10
|
+
assert phase_1 is not None
|
|
11
|
+
reg_out = server.register_beacon(phase_1, message)
|
|
12
|
+
assert reg_out is not None
|
|
13
|
+
phase2 = beacon.process_initial_message(reg_out.serialized())
|
|
14
|
+
assert phase2 is not None
|
|
15
|
+
assert phase2 == message
|
|
16
|
+
return phase2
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_encrypt_to_multiple():
|
|
20
|
+
server = BeaconCryptServer(0, None)
|
|
21
|
+
server_pk = server.id_pk()
|
|
22
|
+
b1 = BeaconCryptBeacon(0, server_pk)
|
|
23
|
+
b2 = BeaconCryptBeacon(0, server_pk)
|
|
24
|
+
message = bytes([0x1]) * 32
|
|
25
|
+
|
|
26
|
+
b1_initial = register_beacon(server, b1)
|
|
27
|
+
b2_initial = register_beacon(server, b2)
|
|
28
|
+
assert b2_initial == b1_initial
|
|
29
|
+
|
|
30
|
+
b1_m1 = server.encrypt_to_beacon(message, 1)
|
|
31
|
+
b2_m1 = server.encrypt_to_beacon(message, 2)
|
|
32
|
+
assert b1_m1 is not None and b2_m1 is not None and b1_m1 != b2_m1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_encrypt_multiple():
|
|
36
|
+
server = BeaconCryptServer(0, None)
|
|
37
|
+
server_pk = server.id_pk()
|
|
38
|
+
b1 = BeaconCryptBeacon(0, server_pk)
|
|
39
|
+
message = bytes([0x1]) * 32
|
|
40
|
+
|
|
41
|
+
_ = register_beacon(server, b1)
|
|
42
|
+
|
|
43
|
+
b1_m1 = server.encrypt_to_beacon(message, 1)
|
|
44
|
+
b1_m2 = server.encrypt_to_beacon(message, 1)
|
|
45
|
+
assert b1_m1 is not None and b1_m2 is not None and b1_m1 != b1_m2
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_decrypt_multiple():
|
|
49
|
+
server = BeaconCryptServer(0, None)
|
|
50
|
+
server_pk = server.id_pk()
|
|
51
|
+
beacon = BeaconCryptBeacon(0, server_pk)
|
|
52
|
+
message = bytes([0x1]) * 32
|
|
53
|
+
|
|
54
|
+
_ = register_beacon(server, beacon)
|
|
55
|
+
m1 = server.encrypt_to_beacon(message, 1)
|
|
56
|
+
m2 = server.encrypt_to_beacon(message, 1)
|
|
57
|
+
assert m1 != m2
|
|
58
|
+
|
|
59
|
+
plain1 = beacon.decrypt_server_message(m1)
|
|
60
|
+
plain2 = beacon.decrypt_server_message(m2)
|
|
61
|
+
assert plain2 is not None and plain1 is not None
|
|
62
|
+
assert plain2 == plain1 == message
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_decrypt_multiple_signed():
|
|
66
|
+
server = BeaconCryptServer(0, None)
|
|
67
|
+
server_pk = server.id_pk()
|
|
68
|
+
beacon = BeaconCryptBeacon(0, server_pk)
|
|
69
|
+
message = bytes([0x1]) * 32
|
|
70
|
+
|
|
71
|
+
_ = register_beacon(server, beacon)
|
|
72
|
+
m1 = server.encrypt_to_beacon_signed(message, 1)
|
|
73
|
+
m2 = server.encrypt_to_beacon_signed(message, 1)
|
|
74
|
+
assert m1 != m2
|
|
75
|
+
|
|
76
|
+
plain1 = beacon.decrypt_server_message_signed(m1)
|
|
77
|
+
plain2 = beacon.decrypt_server_message_signed(m2)
|
|
78
|
+
assert plain2 is not None and plain1 is not None
|
|
79
|
+
assert plain2 == plain1 == message
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_decrypt_catch_up():
|
|
83
|
+
server = BeaconCryptServer(0, None)
|
|
84
|
+
server_pk = server.id_pk()
|
|
85
|
+
beacon = BeaconCryptBeacon(0, server_pk)
|
|
86
|
+
message = bytes([0x1]) * 32
|
|
87
|
+
|
|
88
|
+
_ = register_beacon(server, beacon)
|
|
89
|
+
m1 = server.encrypt_to_beacon(message, 1)
|
|
90
|
+
m2 = server.encrypt_to_beacon(message, 1)
|
|
91
|
+
assert m1 != m2
|
|
92
|
+
|
|
93
|
+
plain2 = beacon.decrypt_server_message(m2)
|
|
94
|
+
plain1 = beacon.decrypt_server_message(m1)
|
|
95
|
+
assert plain2 is not None and plain1 is not None
|
|
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)
|
|
@@ -18,6 +18,21 @@ def register_beacon(
|
|
|
18
18
|
|
|
19
19
|
def test_register():
|
|
20
20
|
server = BeaconCryptServer(0, None)
|
|
21
|
-
|
|
21
|
+
server_pk = server.id_pk()
|
|
22
|
+
beacon = BeaconCryptBeacon(0, server_pk)
|
|
22
23
|
|
|
23
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""
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
from beaconcrypt import BeaconCryptBeacon, BeaconCryptServer
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def register_beacon(
|
|
5
|
-
server: BeaconCryptServer,
|
|
6
|
-
beacon: BeaconCryptBeacon,
|
|
7
|
-
) -> bytes | None:
|
|
8
|
-
message = bytes(0xFF * 32)
|
|
9
|
-
phase_1 = beacon.generate_registration()
|
|
10
|
-
assert phase_1 is not None
|
|
11
|
-
reg_out = server.register_beacon(phase_1, message)
|
|
12
|
-
assert reg_out is not None
|
|
13
|
-
phase2 = beacon.process_initial_message(reg_out.serialized())
|
|
14
|
-
assert phase2 is not None
|
|
15
|
-
assert phase2 == message
|
|
16
|
-
return phase2
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def test_encrypt_to_multiple():
|
|
20
|
-
server = BeaconCryptServer(0, None)
|
|
21
|
-
b1 = BeaconCryptBeacon(0, None)
|
|
22
|
-
b2 = BeaconCryptBeacon(0, None)
|
|
23
|
-
message = bytes(0x1 * 32)
|
|
24
|
-
|
|
25
|
-
b1_initial = register_beacon(server, b1)
|
|
26
|
-
b2_initial = register_beacon(server, b2)
|
|
27
|
-
assert b2_initial == b1_initial
|
|
28
|
-
|
|
29
|
-
b1_m1 = server.encrypt_to_beacon(message, 1)
|
|
30
|
-
b2_m1 = server.encrypt_to_beacon(message, 2)
|
|
31
|
-
assert b1_m1 is not None and b2_m1 is not None and b1_m1 != b2_m1
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def test_encrypt_multiple():
|
|
35
|
-
server = BeaconCryptServer(0, None)
|
|
36
|
-
b1 = BeaconCryptBeacon(0, None)
|
|
37
|
-
message = bytes(0x1 * 32)
|
|
38
|
-
|
|
39
|
-
_ = register_beacon(server, b1)
|
|
40
|
-
|
|
41
|
-
b1_m1 = server.encrypt_to_beacon(message, 1)
|
|
42
|
-
b1_m2 = server.encrypt_to_beacon(message, 1)
|
|
43
|
-
assert b1_m1 is not None and b1_m2 is not None and b1_m1 != b1_m2
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def test_decrypt_multiple():
|
|
47
|
-
server = BeaconCryptServer(0, None)
|
|
48
|
-
beacon = BeaconCryptBeacon(0, None)
|
|
49
|
-
message = bytes(0x1 * 32)
|
|
50
|
-
|
|
51
|
-
_ = register_beacon(server, beacon)
|
|
52
|
-
m1 = server.encrypt_to_beacon(message, 1)
|
|
53
|
-
m2 = server.encrypt_to_beacon(message, 1)
|
|
54
|
-
assert m1 != m2
|
|
55
|
-
|
|
56
|
-
plain1 = beacon.decrypt_server_message(m1)
|
|
57
|
-
plain2 = beacon.decrypt_server_message(m2)
|
|
58
|
-
assert plain2 is not None and plain1 is not None
|
|
59
|
-
assert plain2 == plain1 == message
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def test_decrypt_multiple_signed():
|
|
63
|
-
server = BeaconCryptServer(0, None)
|
|
64
|
-
beacon = BeaconCryptBeacon(0, None)
|
|
65
|
-
message = bytes(0x1 * 32)
|
|
66
|
-
|
|
67
|
-
_ = register_beacon(server, beacon)
|
|
68
|
-
m1 = server.encrypt_to_beacon_signed(message, 1)
|
|
69
|
-
m2 = server.encrypt_to_beacon_signed(message, 1)
|
|
70
|
-
assert m1 != m2
|
|
71
|
-
|
|
72
|
-
plain1 = beacon.decrypt_server_message_signed(m1)
|
|
73
|
-
plain2 = beacon.decrypt_server_message_signed(m2)
|
|
74
|
-
assert plain2 is not None and plain1 is not None
|
|
75
|
-
assert plain2 == plain1 == message
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def test_decrypt_catch_up():
|
|
79
|
-
server = BeaconCryptServer(0, None)
|
|
80
|
-
server_pk = server.id_pk()
|
|
81
|
-
beacon = BeaconCryptBeacon(0, server_pk)
|
|
82
|
-
message = bytes(0x1 * 32)
|
|
83
|
-
|
|
84
|
-
_ = register_beacon(server, beacon)
|
|
85
|
-
m1 = server.encrypt_to_beacon(message, 1)
|
|
86
|
-
m2 = server.encrypt_to_beacon(message, 1)
|
|
87
|
-
assert m1 != m2
|
|
88
|
-
|
|
89
|
-
plain2 = beacon.decrypt_server_message(m2)
|
|
90
|
-
plain1 = beacon.decrypt_server_message(m1)
|
|
91
|
-
assert plain2 is not None and plain1 is not None
|
|
92
|
-
assert plain2 == plain1 == message
|
|
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
|