Skip to content

Commit 9c6a184

Browse files
Merge pull request #12 from BitcoinErrorLog/improvements/audit-fixes-and-tests
feat: comprehensive audit fixes, new tests, and production improvements
2 parents e8a4545 + fee07dd commit 9c6a184

25 files changed

+1014
-60
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ hkdf = "0.12"
2929
zeroize = "1"
3030
secrecy = "0.8"
3131
serde = { version = "1", features = ["derive"] }
32+
serde_bytes = "0.11"
3233
serde_json = "1"
3334
thiserror = "1"
3435
cfg-if = "1"

THREAT_MODEL.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,82 @@ Signature = Ed25519.Sign(ed25519_secret, H)
327327

328328
---
329329

330+
### 5. FFI Boundary (Mobile Applications)
331+
**Entry Points**:
332+
- UniFFI-generated bindings for iOS (Swift) and Android (Kotlin/Java)
333+
- Cross-language function calls
334+
- Memory ownership transfers
335+
336+
**Threats**:
337+
- **Memory Safety**: Incorrect memory management across language boundaries
338+
- **Type Confusion**: Mismatched types between Rust and target language
339+
- **Resource Leaks**: Unclosed handles or sessions
340+
- **Panic Propagation**: Rust panics crossing FFI boundary
341+
342+
**Mitigations**:
343+
- ✅ UniFFI handles memory management automatically (Arc refcounting)
344+
- ✅ Structured error codes (`NoiseErrorCode` as `i32`) prevent type confusion
345+
- ✅ No raw pointers exposed to generated bindings
346+
- ✅ Thread-safe wrappers (`ThreadSafeSessionManager`) for concurrent access
347+
- ✅ Error codes mapped to platform-specific error types
348+
349+
**Risk**: LOW (UniFFI provides safe abstractions)
350+
351+
**Mobile-Specific Considerations**:
352+
- **App Suspension**: Sessions must be persisted before app backgrounding
353+
- **Memory Dumps**: Keys in memory vulnerable if device is compromised
354+
- **Jailbreak/Root**: Elevated privileges can access process memory
355+
- **Debugging**: Debuggers can inspect memory (mitigated by release builds)
356+
357+
**Recommendation**: Applications SHOULD:
358+
- Persist session state before app suspension (use `NoiseManager::save_state()`)
359+
- Use secure storage for master seeds (iOS Keychain, Android Keystore)
360+
- Enable `secure-mem` feature on servers (page locking)
361+
- Clear sensitive data on app termination
362+
363+
---
364+
365+
### 6. Mobile-Specific Threats
366+
367+
#### 6.1 App Lifecycle Attacks
368+
**Threat**: App suspension/resume can cause session state loss or corruption
369+
370+
**Mitigations**:
371+
-`NoiseManager` provides `save_state()` and `restore_state()` methods
372+
- ✅ Session state is serializable for persistence
373+
- ✅ Connection status tracking for reconnection logic
374+
375+
**Risk**: LOW (if state is properly persisted)
376+
377+
#### 6.2 Memory Dump Attacks
378+
**Threat**: Compromised device can dump process memory containing keys
379+
380+
**Mitigations**:
381+
-`Zeroizing` reduces key lifetime in memory
382+
- ✅ Closure-based key access (keys don't escape function scope)
383+
- ✅ Optional `secure-mem` feature (page locking on supported OSes)
384+
- ❌ Cannot fully protect against root/admin access
385+
386+
**Risk**: MEDIUM (requires device compromise)
387+
388+
**Recommendation**: Use secure hardware storage (HSM, TEE) for master seeds in high-security deployments
389+
390+
#### 6.3 Platform-Specific Threats
391+
392+
**iOS**:
393+
- **Jailbreak Detection**: Jailbroken devices have elevated attack surface
394+
- **Keychain Access**: Secure storage via iOS Keychain (recommended)
395+
- **App Sandbox**: Provides isolation but keys still in process memory
396+
397+
**Android**:
398+
- **Root Detection**: Rooted devices can access all app memory
399+
- **Keystore**: Hardware-backed key storage available on modern devices
400+
- **Debugging**: Release builds obfuscate but don't fully protect
401+
402+
**Risk**: MEDIUM (platform-dependent)
403+
404+
---
405+
330406
## Cryptographic Assumptions
331407

332408
### Standard Assumptions (Accepted)
@@ -347,6 +423,39 @@ Signature = Ed25519.Sign(ed25519_secret, H)
347423

348424
---
349425

426+
## FFI Boundary Security
427+
428+
### Trust Model
429+
The FFI layer (UniFFI) is considered **TRUSTED** for memory safety but **UNTRUSTED** for application logic:
430+
- ✅ UniFFI-generated code is safe (no manual memory management)
431+
- ⚠️ Application code calling FFI must handle errors correctly
432+
- ⚠️ Platform-specific code (Swift/Kotlin) must validate inputs
433+
434+
### Error Handling
435+
FFI errors are mapped to structured error codes:
436+
- `NoiseErrorCode` enum provides platform-agnostic error types
437+
- Errors are serialized as `i32` for cross-language compatibility
438+
- Platform bindings should map these to native error types
439+
440+
### Memory Safety
441+
- **Ownership**: UniFFI uses `Arc` for shared ownership (automatic refcounting)
442+
- **Lifetimes**: No manual lifetime management required
443+
- **Leaks**: Automatic cleanup when objects are dropped
444+
445+
### Thread Safety
446+
- `ThreadSafeSessionManager` uses `Arc<Mutex<>>` for concurrent access
447+
- FFI layer is thread-safe if internal types are thread-safe
448+
- Mobile apps should use thread-safe wrappers for background workers
449+
450+
### Security Best Practices for FFI
451+
1. **Input Validation**: Validate all inputs from platform code
452+
2. **Error Handling**: Never ignore FFI errors
453+
3. **Resource Management**: Ensure sessions are properly closed
454+
4. **State Persistence**: Save state before app suspension
455+
5. **Secure Storage**: Use platform secure storage for master seeds
456+
457+
---
458+
350459
## Known Limitations
351460

352461
### 1. No Post-Quantum Cryptography

build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
fn main() {
22
uniffi_build::generate_scaffolding("src/pubky_noise.udl").unwrap();
3+
println!("cargo::rustc-check-cfg=cfg(loom)");
34
}

fuzz/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ cargo-fuzz = true
1010
[dependencies]
1111
libfuzzer-sys = "0.4"
1212
arbitrary = { version = "1", features = ["derive"] }
13+
sha2 = "0.10"
14+
zeroize = "1"
1315

1416
[dependencies.pubky-noise]
1517
path = ".."

fuzz/fuzz_targets/fuzz_handshake.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,24 @@ fuzz_target!(|input: HandshakeInput| {
9393
);
9494

9595
// Should succeed with valid inputs
96-
if let Ok((hs_state, first_msg)) = handshake_result {
97-
// Test server processing valid message
98-
let server_result = pubky_noise::datalink_adapter::server_accept_ik(&server, &first_msg);
96+
if let Ok((_hs_state, first_msg)) = handshake_result {
97+
// Test server processing valid message using 3-step handshake
98+
let server_result = server.build_responder_read_ik(&first_msg);
9999
// Server should be able to process a properly formed message
100100
// (though verification may fail due to dummy signatures)
101-
let _ = server_result;
101+
if let Ok((mut hs, _identity)) = server_result {
102+
// Generate response
103+
let mut response = vec![0u8; 128];
104+
if let Ok(n) = hs.write_message(&[], &mut response) {
105+
response.truncate(n);
106+
let _ = pubky_noise::datalink_adapter::server_complete_ik(hs);
107+
}
108+
}
102109
}
103110

104111
// Test 2: Server handling malformed/arbitrary message
105112
// This should NOT panic, just return an error
106-
let malformed_result = pubky_noise::datalink_adapter::server_accept_ik(&server, &input.malformed_message);
113+
let malformed_result = server.build_responder_read_ik(&input.malformed_message);
107114

108115
// We expect an error for malformed messages, but no panic
109116
match malformed_result {

fuzz/fuzz_targets/fuzz_noise_link.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use arbitrary::Arbitrary;
44
use libfuzzer_sys::fuzz_target;
55
use pubky_noise::{NoiseClient, NoiseServer, RingKeyProvider, NoiseError};
66
use pubky_noise::datalink_adapter::{
7-
client_start_ik_direct, client_complete_ik, server_accept_ik, server_complete_ik,
7+
client_start_ik_direct, client_complete_ik, server_complete_ik,
88
};
99
use std::sync::Arc;
1010

@@ -74,10 +74,18 @@ fuzz_target!(|input: NoiseLinkInput| {
7474
Err(_) => return,
7575
};
7676

77-
let (s_hs, _identity, response) = match server_accept_ik(&server, &first_msg) {
77+
let (mut s_hs, _identity) = match server.build_responder_read_ik(&first_msg) {
7878
Ok(result) => result,
7979
Err(_) => return,
8080
};
81+
82+
// Generate response message
83+
let mut response = vec![0u8; 128];
84+
let n = match s_hs.write_message(&[], &mut response) {
85+
Ok(n) => n,
86+
Err(_) => return,
87+
};
88+
response.truncate(n);
8189

8290
let mut client_link = match client_complete_ik(c_hs, &response) {
8391
Ok(link) => link,

src/ffi/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl From<FfiConnectionStatus> for ConnectionStatus {
3333
}
3434

3535
/// FFI-safe mobile configuration wrapper
36-
#[derive(uniffi::Record)]
36+
#[derive(uniffi::Record, Clone)]
3737
pub struct FfiMobileConfig {
3838
pub auto_reconnect: bool,
3939
pub max_reconnect_attempts: u32,

src/identity_payload.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pub fn make_binding_message(params: &BindingMessageParams<'_>) -> [u8; 32] {
101101
if let Some(r) = params.remote_noise_pub {
102102
h.update(r);
103103
}
104-
h.update(&INTERNAL_EPOCH.to_le_bytes());
104+
h.update(INTERNAL_EPOCH.to_le_bytes());
105105
h.update(match params.role {
106106
Role::Client => b"client",
107107
Role::Server => b"server",

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
//! let server = NoiseServer::<_, ()>::new_direct("server_kid", b"server_device", server_ring);
2020
//! ```
2121
22+
#![allow(unpredictable_function_pointer_comparisons)]
23+
2224
pub mod client;
2325
pub mod datalink_adapter;
2426
pub mod errors;
@@ -60,5 +62,4 @@ pub use transport::NoiseTransport;
6062

6163
// UniFFI setup - must be at crate root for proc macros to work
6264
#[cfg(feature = "uniffi_macros")]
63-
#[allow(clippy::fn_address_comparisons)]
6465
uniffi::setup_scaffolding!();

0 commit comments

Comments
 (0)