Skip to content

Commit d41d1d9

Browse files
committed
Improve webhook request validation and test coverage
1 parent 03f18c0 commit d41d1d9

File tree

3 files changed

+42
-32
lines changed

3 files changed

+42
-32
lines changed

lightspark/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,13 @@ client = ["base", "objects", "dep:reqwest"]
2121

2222
[dependencies]
2323
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls", "zstd"], optional = true }
24-
futures = "0.3"
2524
base64 = "0.21.0"
2625
serde_json = "1.0.94"
2726
serde = { version = "1.0.155", features = ["derive"] }
2827
regex = "1.7.1"
2928
chrono = "0.4.35"
3029
aes-gcm = "0.10.1"
3130
rand = "0.8.5"
32-
block-modes = "0.9.1"
3331
os-version = "0.2.0"
3432
version_check = "0.9.4"
3533
hex = "0.4.3"

lightspark/src/objects/webhook_event_type.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use serde_json::Value;
44
use std::fmt;
55

66
/// This is an enum of the potential event types that can be associated with your Lightspark wallets.
7-
#[derive(Debug, Clone, Deserialize, Serialize)]
7+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
88
pub enum WebhookEventType {
99
#[serde(rename = "PAYMENT_FINISHED")]
1010
PaymentFinished,

lightspark/src/webhooks.rs

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use chrono::{DateTime, Utc};
99

1010
pub const SIGNATURE_HEADER: &str = "lightspark-signature";
1111

12-
#[derive(Clone, Serialize, Deserialize)]
12+
#[derive(Debug, Clone, Serialize, Deserialize)]
1313
pub struct WebhookEvent {
1414
pub event_type: WebhookEventType,
1515
pub event_id: String,
@@ -24,46 +24,37 @@ pub struct WebhookEvent {
2424
impl WebhookEvent {
2525
pub fn verify_and_parse(
2626
data: &[u8],
27-
hexdigest: &str,
27+
hex_digest: &str,
2828
webhook_secret: &str,
2929
) -> Result<WebhookEvent, Error> {
30-
let mut hmac: Hmac<Sha256> =
31-
Hmac::new_from_slice(webhook_secret.as_bytes()).expect("HMAC can take key of any size");
32-
hmac.update(data);
33-
let result = hmac.finalize();
34-
let code_bytes = result.into_bytes();
35-
let hex_string = hex::encode(code_bytes);
36-
if !hex_string
37-
.to_ascii_lowercase()
38-
.eq(&hexdigest.to_ascii_lowercase())
39-
{
40-
return Err(Error::WebhookSignatureError);
41-
}
42-
Self::parse(data)
43-
}
30+
if let Ok(digest_bytes) = hex::decode(hex_digest) {
31+
let hmac: Hmac<Sha256> = Hmac::new_from_slice(webhook_secret.as_bytes())
32+
.expect("HMAC can take key of any size")
33+
.chain_update(data);
4434

45-
fn parse(data: &[u8]) -> Result<WebhookEvent, Error> {
46-
let event: WebhookEvent = serde_json::from_slice(data).map_err(Error::JsonError)?;
47-
Ok(event)
35+
if hmac.verify_slice(digest_bytes.as_slice()).is_ok() {
36+
return serde_json::from_slice(data).map_err(Error::JsonError);
37+
}
38+
}
39+
Err(Error::WebhookSignatureError)
4840
}
4941
}
5042

5143
#[cfg(test)]
5244
mod tests {
45+
use crate::error::Error;
46+
5347
#[test]
5448
fn test_verify_and_parse() {
5549
let data = "{\"event_type\": \"NODE_STATUS\", \"event_id\": \"1615c8be5aa44e429eba700db2ed8ca5\", \"timestamp\": \"2023-05-17T23:56:47.874449+00:00\", \"entity_id\": \"lightning_node:01882c25-157a-f96b-0000-362d42b64397\"}";
56-
let hexdigest = "62a8829aeb48b4142533520b1f7f86cdb1ee7d718bf3ea15bc1c662d4c453b74";
50+
let hex_digest = "62a8829aeb48b4142533520b1f7f86cdb1ee7d718bf3ea15bc1c662d4c453b74";
5751
let webhook_secret = "3gZ5oQQUASYmqQNuEk0KambNMVkOADDItIJjzUlAWjX";
5852

5953
let result =
60-
super::WebhookEvent::verify_and_parse(data.as_bytes(), hexdigest, webhook_secret)
54+
super::WebhookEvent::verify_and_parse(data.as_bytes(), hex_digest, webhook_secret)
6155
.expect("Success case");
6256

63-
assert_eq!(
64-
result.event_type.to_string(),
65-
super::WebhookEventType::NodeStatus.to_string()
66-
);
57+
assert_eq!(result.event_type, super::WebhookEventType::NodeStatus);
6758
assert_eq!(result.event_id, "1615c8be5aa44e429eba700db2ed8ca5");
6859
assert_eq!(
6960
result.entity_id,
@@ -75,6 +66,30 @@ mod tests {
7566
);
7667
}
7768

69+
#[test]
70+
fn test_invalid_signature() {
71+
let data = "{\"event_type\": \"NODE_STATUS\", \"event_id\": \"1615c8be5aa44e429eba700db2ed8ca5\", \"timestamp\": \"2023-05-17T23:56:47.874449+00:00\", \"entity_id\": \"lightning_node:01882c25-157a-f96b-0000-362d42b64397\"}";
72+
let hex_digest = "deadbeef";
73+
let webhook_secret = "3gZ5oQQUASYmqQNuEk0KambNMVkOADDItIJjzUlAWjX";
74+
75+
let result =
76+
super::WebhookEvent::verify_and_parse(data.as_bytes(), hex_digest, webhook_secret);
77+
78+
result.expect_err(Error::WebhookSignatureError.to_string().as_str());
79+
}
80+
81+
#[test]
82+
fn test_invalid_digest_bytes() {
83+
let data = "{\"event_type\": \"NODE_STATUS\", \"event_id\": \"1615c8be5aa44e429eba700db2ed8ca5\", \"timestamp\": \"2023-05-17T23:56:47.874449+00:00\", \"entity_id\": \"lightning_node:01882c25-157a-f96b-0000-362d42b64397\"}";
84+
let hex_digest = "NotAHexDigest";
85+
let webhook_secret = "3gZ5oQQUASYmqQNuEk0KambNMVkOADDItIJjzUlAWjX";
86+
87+
let result =
88+
super::WebhookEvent::verify_and_parse(data.as_bytes(), hex_digest, webhook_secret);
89+
90+
result.expect_err(Error::WebhookSignatureError.to_string().as_str());
91+
}
92+
7893
#[test]
7994
fn test_remote_signing_webhook() {
8095
let data = "{\"event_type\": \"REMOTE_SIGNING\", \"event_id\": \"1615c8be5aa44e429eba700db2ed8ca5\", \"timestamp\": \"2023-05-17T23:56:47.874449+00:00\", \"entity_id\": \"lightning_node:01882c25-157a-f96b-0000-362d42b64397\", \"data\": {\"sub_event_type\": \"ECDH\", \"public_key\": \"027c4b09ffb985c298afe7e5813266cbfcb7780b480ac294b0b43dc21f2be3d13c\"}}";
@@ -85,10 +100,7 @@ mod tests {
85100
super::WebhookEvent::verify_and_parse(data.as_bytes(), hexdigest, webhook_secret)
86101
.expect("Success case");
87102

88-
assert_eq!(
89-
result.event_type.to_string(),
90-
super::WebhookEventType::RemoteSigning.to_string()
91-
);
103+
assert_eq!(result.event_type, super::WebhookEventType::RemoteSigning);
92104

93105
let data = result.data.expect("Data is missing");
94106
let sub_event_type = data["sub_event_type"]

0 commit comments

Comments
 (0)