Skip to content

Commit 56e5892

Browse files
committed
feat!: compile-time safety for handshake
1 parent 8137923 commit 56e5892

File tree

6 files changed

+870
-725
lines changed

6 files changed

+870
-725
lines changed

protocol/benches/cipher_session.rs

Lines changed: 52 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,69 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
13
#![feature(test)]
24

35
extern crate test;
46

5-
use bip324::{CipherSession, Handshake, InboundCipher, Network, OutboundCipher, PacketType, Role};
7+
use bip324::{
8+
CipherSession, Handshake, HandshakeAuthentication, InboundCipher, Initialized, Network,
9+
OutboundCipher, PacketType, ReceivedKey, Role, NUM_INITIAL_HANDSHAKE_BUFFER_BYTES,
10+
};
611
use test::{black_box, Bencher};
712

813
fn create_cipher_session_pair() -> (CipherSession, CipherSession) {
9-
// Create a proper handshake between Alice and Bob.
10-
let mut alice_init_buffer = vec![0u8; 64];
11-
let mut alice_handshake = Handshake::new(
12-
Network::Bitcoin,
13-
Role::Initiator,
14-
None,
15-
&mut alice_init_buffer,
16-
)
17-
.unwrap();
18-
19-
let mut bob_init_buffer = vec![0u8; 100];
20-
let mut bob_handshake = Handshake::new(
21-
Network::Bitcoin,
22-
Role::Responder,
23-
None,
24-
&mut bob_init_buffer,
25-
)
26-
.unwrap();
27-
28-
// Bob completes materials with Alice's key.
29-
bob_handshake
30-
.complete_materials(
31-
alice_init_buffer[..64].try_into().unwrap(),
32-
&mut bob_init_buffer[64..],
33-
None,
34-
)
14+
// Send Alice's key.
15+
let alice_handshake = Handshake::<Initialized>::new(Network::Bitcoin, Role::Initiator).unwrap();
16+
let mut alice_key_buffer = vec![0u8; Handshake::<Initialized>::send_key_len(None)];
17+
let alice_handshake = alice_handshake
18+
.send_key(None, &mut alice_key_buffer)
3519
.unwrap();
3620

37-
// Alice completes materials with Bob's key.
38-
let mut alice_response_buffer = vec![0u8; 36];
39-
alice_handshake
40-
.complete_materials(
41-
bob_init_buffer[..64].try_into().unwrap(),
42-
&mut alice_response_buffer,
43-
None,
44-
)
21+
// Send Bob's key
22+
let bob_handshake = Handshake::<Initialized>::new(Network::Bitcoin, Role::Responder).unwrap();
23+
let mut bob_key_buffer = vec![0u8; Handshake::<Initialized>::send_key_len(None)];
24+
let bob_handshake = bob_handshake.send_key(None, &mut bob_key_buffer).unwrap();
25+
26+
// Alice receives Bob's key.
27+
let alice_handshake = alice_handshake
28+
.receive_key(bob_key_buffer.try_into().unwrap())
4529
.unwrap();
4630

47-
// Authenticate.
48-
let mut packet_buffer = vec![0u8; 4096];
49-
alice_handshake
50-
.authenticate_garbage_and_version(&bob_init_buffer[64..], &mut packet_buffer)
31+
// Bob receives Alice's key.
32+
let bob_handshake = bob_handshake
33+
.receive_key(alice_key_buffer.try_into().unwrap())
5134
.unwrap();
52-
bob_handshake
53-
.authenticate_garbage_and_version(&alice_response_buffer, &mut packet_buffer)
35+
36+
// Alice sends version.
37+
let mut alice_version_buffer = vec![0u8; Handshake::<ReceivedKey>::send_version_len(None)];
38+
let alice_handshake = alice_handshake
39+
.send_version(&mut alice_version_buffer, None)
40+
.unwrap();
41+
42+
// Bob sends version.
43+
let mut bob_version_buffer = vec![0u8; Handshake::<ReceivedKey>::send_version_len(None)];
44+
let bob_handshake = bob_handshake
45+
.send_version(&mut bob_version_buffer, None)
5446
.unwrap();
5547

56-
let alice = alice_handshake.finalize().unwrap();
57-
let bob = bob_handshake.finalize().unwrap();
48+
let mut packet_buffer = vec![0u8; NUM_INITIAL_HANDSHAKE_BUFFER_BYTES];
49+
50+
// Alice receives Bob's version.
51+
let alice = match alice_handshake
52+
.receive_version(&bob_version_buffer, &mut packet_buffer)
53+
.unwrap()
54+
{
55+
HandshakeAuthentication::Complete { cipher, .. } => cipher,
56+
HandshakeAuthentication::NeedMoreData(_) => panic!("Should have completed"),
57+
};
58+
59+
// Bob receives Alice's version.
60+
let bob = match bob_handshake
61+
.receive_version(&alice_version_buffer, &mut packet_buffer)
62+
.unwrap()
63+
{
64+
HandshakeAuthentication::Complete { cipher, .. } => cipher,
65+
HandshakeAuthentication::NeedMoreData(_) => panic!("Should have completed"),
66+
};
5867

5968
(alice, bob)
6069
}
Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,60 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! ## Expected Outcomes
4+
//!
5+
//! * Most runs will fail with invalid EC points or handshake failures.
6+
//! * No panics, crashes, or memory safety issues should occur.
7+
//! * The implementation should handle all inputs gracefully.
8+
19
#![no_main]
2-
use bip324::{Handshake, Network, Role, NUM_INITIAL_HANDSHAKE_BUFFER_BYTES};
10+
use bip324::{
11+
Handshake, HandshakeAuthentication, Initialized, Network, ReceivedKey, Role,
12+
NUM_INITIAL_HANDSHAKE_BUFFER_BYTES,
13+
};
314
use libfuzzer_sys::fuzz_target;
415

516
fuzz_target!(|data: &[u8]| {
6-
// Skip if data is too small for an interesting test.
7-
if data.len() < 64 {
17+
// Skip if data is too small for a meaningful test.
18+
// We need at least 64 bytes for the public key and at
19+
// least 30 more for some interesting garbage, decoy, version bytes.
20+
if data.len() < 100 {
821
return;
922
}
1023

11-
let mut initiator_pubkey = [0u8; 64];
12-
let mut handshake = Handshake::new(
13-
Network::Bitcoin,
14-
Role::Initiator,
15-
None,
16-
&mut initiator_pubkey,
17-
)
18-
.unwrap();
19-
20-
let mut responder_pubkey = [0u8; 64];
21-
let _responder_handshake = Handshake::new(
22-
Network::Bitcoin,
23-
Role::Responder,
24-
None,
25-
&mut responder_pubkey,
26-
)
27-
.unwrap();
28-
29-
// Create a mutation of the responder's bytes.
30-
let mut garbage_and_version = [0u8; 36];
31-
let copy_len = std::cmp::min(data.len(), garbage_and_version.len());
32-
garbage_and_version[..copy_len].copy_from_slice(&data[..copy_len]);
33-
34-
// Create mutation of the responder's public key.
35-
// The key is either completely random or slightly tweaked.
24+
// Initiator side of the handshake.
25+
let handshake = Handshake::<Initialized>::new(Network::Bitcoin, Role::Initiator).unwrap();
26+
let mut initiator_pubkey = vec![0u8; Handshake::<Initialized>::send_key_len(None)];
27+
let handshake = handshake.send_key(None, &mut initiator_pubkey).unwrap();
28+
29+
// Use the first 64 bytes of fuzz data as the responder's public key.
3630
let mut fuzzed_responder_pubkey = [0u8; 64];
37-
if data.len() >= 128 {
38-
fuzzed_responder_pubkey.copy_from_slice(&data[64..128]);
39-
} else {
40-
fuzzed_responder_pubkey.copy_from_slice(&responder_pubkey);
41-
for (i, b) in data
42-
.iter()
43-
.enumerate()
44-
.take(fuzzed_responder_pubkey.len())
45-
.skip(copy_len)
46-
{
47-
fuzzed_responder_pubkey[i % 64] ^= b; // XOR to make controlled changes.
31+
fuzzed_responder_pubkey.copy_from_slice(&data[..64]);
32+
33+
// Attempt to receive the fuzzed key.
34+
let handshake = match handshake.receive_key(fuzzed_responder_pubkey) {
35+
Ok(h) => h,
36+
Err(_) => return, // Invalid key rejected successfully.
37+
};
38+
39+
// Send version message just to move the state of the handshake along.
40+
let mut version_buffer = vec![0u8; Handshake::<ReceivedKey>::send_version_len(None)];
41+
let handshake = handshake.send_version(&mut version_buffer, None).unwrap();
42+
43+
// Try to receive and authenticate the fuzzed garbage and version data.
44+
let garbage_and_version = Vec::from(&data[64..]);
45+
let mut packet_buffer = vec![0u8; NUM_INITIAL_HANDSHAKE_BUFFER_BYTES];
46+
match handshake.receive_version(&garbage_and_version, &mut packet_buffer) {
47+
Ok(HandshakeAuthentication::Complete { .. }) => {
48+
// Handshake completed successfully.
49+
// This should only happen with some very lucky random bytes.
50+
}
51+
Ok(HandshakeAuthentication::NeedMoreData(_)) => {
52+
// Handshake needs more ciphertext.
53+
// This is an expected outcome for fuzzed inputs.
54+
}
55+
Err(_) => {
56+
// Authentication or parsing failed.
57+
// This is an expected outcome for fuzzed inputs.
4858
}
4959
}
50-
51-
// Try to complete the materials and authenticate with the fuzzed key and data.
52-
// Exercising malformed public key handling.
53-
let _ = handshake.complete_materials(fuzzed_responder_pubkey, &mut garbage_and_version, None);
54-
// Check how a broken handshake is handled.
55-
let mut packet_buffer = vec![0u8; NUM_INITIAL_HANDSHAKE_BUFFER_BYTES]; // Initial buffer for decoy and version packets
56-
let _ = handshake.authenticate_garbage_and_version(&garbage_and_version, &mut packet_buffer);
57-
let _ = handshake.finalize();
5860
});

0 commit comments

Comments
 (0)