Skip to content

Commit 819bed8

Browse files
committed
Sign auth HMAC over request bodies
Require authenticated gRPC requests to bind the HMAC to both the timestamp and the request body. This prevents a valid header from being replayed with different request contents during the allowed timestamp window. Update the client and docs so callers generate signatures that match the new server contract.
1 parent d1a0f9d commit 819bed8

4 files changed

Lines changed: 220 additions & 167 deletions

File tree

docs/api-guide.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ x-auth: HMAC <unix_timestamp>:<hmac_hex>
2424
Where:
2525

2626
- `unix_timestamp` is the current time in seconds since the Unix epoch
27-
- `hmac_hex` is the hex-encoded result of `HMAC-SHA256(api_key_bytes, timestamp_be_bytes)`
27+
- `hmac_hex` is the hex-encoded result of
28+
`HMAC-SHA256(api_key_bytes, timestamp_be_bytes || grpc_request_body_bytes)`
2829
- `api_key_bytes` is the API key string encoded as UTF-8 bytes
2930
- `timestamp_be_bytes` is the timestamp as a big-endian 8-byte unsigned integer
31+
- `grpc_request_body_bytes` is the raw gRPC request body sent over HTTP/2, including
32+
the 5-byte gRPC message frame
3033

3134
The server rejects requests where the timestamp differs from the server's clock by more than
3235
**60 seconds**.

ldk-server-client/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ println!("Node ID: {}", info.node_id);
3030

3131
The client handles HMAC-SHA256 authentication automatically. Pass the hex-encoded API key
3232
(found at `<storage_dir>/<network>/api_key`) and the server's TLS certificate (found at
33-
`<storage_dir>/tls.crt`).
33+
`<storage_dir>/tls.crt`). Each request signature covers both the timestamp and the raw gRPC
34+
request body bytes.
3435

3536
## Event Streaming
3637

ldk-server-client/src/client.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,16 @@ impl LdkServerClient {
106106

107107
/// Computes the HMAC-SHA256 authentication header value.
108108
/// Format: "HMAC <timestamp>:<hmac_hex>"
109-
/// Uses timestamp-only HMAC (no body) since TLS guarantees integrity.
110-
fn compute_auth_header(&self) -> String {
109+
/// The signature covers the timestamp and raw gRPC request body bytes.
110+
fn compute_auth_header(&self, body: &[u8]) -> String {
111111
let timestamp = SystemTime::now()
112112
.duration_since(UNIX_EPOCH)
113113
.expect("System time should be after Unix epoch")
114114
.as_secs();
115115

116-
// HMAC-SHA256(api_key, timestamp_bytes) — no body
117116
let mut hmac_engine: HmacEngine<sha256::Hash> = HmacEngine::new(self.api_key.as_bytes());
118117
hmac_engine.input(&timestamp.to_be_bytes());
118+
hmac_engine.input(body);
119119
let hmac_result = Hmac::<sha256::Hash>::from_engine(hmac_engine);
120120

121121
format!("HMAC {}:{}", timestamp, hmac_result)
@@ -428,7 +428,7 @@ impl LdkServerClient {
428428
let grpc_body = encode_grpc_frame(&request.encode_to_vec()).to_vec();
429429

430430
let url = format!("https://{}{}{}", self.base_url, GRPC_SERVICE_PREFIX, method);
431-
let auth_header = self.compute_auth_header();
431+
let auth_header = self.compute_auth_header(&grpc_body);
432432

433433
let response = self
434434
.client
@@ -471,7 +471,7 @@ impl LdkServerClient {
471471
let grpc_body = encode_grpc_frame(&request.encode_to_vec()).to_vec();
472472

473473
let url = format!("https://{}{}{}", self.base_url, GRPC_SERVICE_PREFIX, method);
474-
let auth_header = self.compute_auth_header();
474+
let auth_header = self.compute_auth_header(&grpc_body);
475475

476476
let response = self
477477
.streaming_client

0 commit comments

Comments
 (0)