max / makenotwork
9 files changed,
+55 insertions,
-27 deletions
| @@ -4343,7 +4343,7 @@ dependencies = [ | |||
| 4343 | 4343 | "goblin 0.10.7", | |
| 4344 | 4344 | "governor", | |
| 4345 | 4345 | "hex", | |
| 4346 | - | "hmac 0.12.1", | |
| 4346 | + | "hmac 0.13.0", | |
| 4347 | 4347 | "http-body-util", | |
| 4348 | 4348 | "infer", | |
| 4349 | 4349 | "jsonwebtoken", | |
| @@ -4364,8 +4364,8 @@ dependencies = [ | |||
| 4364 | 4364 | "semver", | |
| 4365 | 4365 | "serde", | |
| 4366 | 4366 | "serde_json", | |
| 4367 | - | "sha1 0.10.6", | |
| 4368 | - | "sha2 0.10.9", | |
| 4367 | + | "sha1 0.11.0", | |
| 4368 | + | "sha2 0.11.0", | |
| 4369 | 4369 | "sqlx", | |
| 4370 | 4370 | "subtle", | |
| 4371 | 4371 | "syntect", |
| @@ -71,9 +71,9 @@ openssl = { version = "0.10", features = ["vendored"] } | |||
| 71 | 71 | ||
| 72 | 72 | # Security | |
| 73 | 73 | rand = "0.10" | |
| 74 | - | hmac = "0.12.1" | |
| 75 | - | sha1 = "0.10.6" | |
| 76 | - | sha2 = "0.10.9" | |
| 74 | + | hmac = "0.13.0" | |
| 75 | + | sha1 = "0.11.0" | |
| 76 | + | sha2 = "0.11.0" | |
| 77 | 77 | subtle = "2.6" | |
| 78 | 78 | chacha20poly1305 = "0.10.1" | |
| 79 | 79 | hex = "0.4.3" |
| @@ -118,7 +118,7 @@ done | |||
| 118 | 118 | ||
| 119 | 119 | /// Compute a per-repo HMAC so the global token never touches disk. | |
| 120 | 120 | pub fn repo_hmac(token: &str, owner: &str, repo: &str) -> String { | |
| 121 | - | use hmac::{Hmac, Mac}; | |
| 121 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 122 | 122 | use sha2::Sha256; | |
| 123 | 123 | let mut mac = | |
| 124 | 124 | Hmac::<Sha256>::new_from_slice(token.as_bytes()).expect("HMAC accepts any key length"); |
| @@ -14,7 +14,7 @@ const TOTP_ENC_PREFIX: &str = "enc:v1:"; | |||
| 14 | 14 | /// backup-code HMAC, session tokens), so reuse in one context can't weaken | |
| 15 | 15 | /// another. | |
| 16 | 16 | fn totp_encryption_key(signing_secret: &str) -> [u8; 32] { | |
| 17 | - | use hmac::{Hmac, Mac}; | |
| 17 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 18 | 18 | use sha2::Sha256; | |
| 19 | 19 | ||
| 20 | 20 | let mut mac = Hmac::<Sha256>::new_from_slice(signing_secret.as_bytes()) | |
| @@ -191,7 +191,7 @@ pub fn mint_internal_actor_token( | |||
| 191 | 191 | expiry_unix: i64, | |
| 192 | 192 | signing_secret: &str, | |
| 193 | 193 | ) -> String { | |
| 194 | - | use hmac::{Hmac, Mac}; | |
| 194 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 195 | 195 | use sha2::Sha256; | |
| 196 | 196 | let message = format!("{INTERNAL_ACTOR_DOMAIN}:{user_id}:{expiry_unix}"); | |
| 197 | 197 | let mut mac = Hmac::<Sha256>::new_from_slice(signing_secret.as_bytes()) | |
| @@ -210,7 +210,7 @@ pub fn verify_internal_actor_token( | |||
| 210 | 210 | signing_secret: &str, | |
| 211 | 211 | now_unix: i64, | |
| 212 | 212 | ) -> Option<crate::db::UserId> { | |
| 213 | - | use hmac::{Hmac, Mac}; | |
| 213 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 214 | 214 | use sha2::Sha256; | |
| 215 | 215 | ||
| 216 | 216 | let (user_part, rest) = token.split_once('.')?; | |
| @@ -231,7 +231,7 @@ pub fn verify_internal_actor_token( | |||
| 231 | 231 | ||
| 232 | 232 | /// Compute the hex HMAC-SHA256 over `feed:{user_id}:{version}` with `secret`. | |
| 233 | 233 | fn feed_signature(user_id: crate::db::UserId, version: i32, secret: &str) -> String { | |
| 234 | - | use hmac::{Hmac, Mac}; | |
| 234 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 235 | 235 | use sha2::Sha256; | |
| 236 | 236 | ||
| 237 | 237 | let message = format!("feed:{user_id}:{version}"); |
| @@ -76,7 +76,7 @@ pub fn generate_verification_url( | |||
| 76 | 76 | email: &str, | |
| 77 | 77 | secret: &str, | |
| 78 | 78 | ) -> String { | |
| 79 | - | use hmac::{Hmac, Mac}; | |
| 79 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 80 | 80 | use sha2::Sha256; | |
| 81 | 81 | ||
| 82 | 82 | let expires = chrono::Utc::now().timestamp() + constants::EMAIL_VERIFICATION_EXPIRY_SECS; | |
| @@ -151,7 +151,7 @@ pub fn verify_email_signature( | |||
| 151 | 151 | signature: &str, | |
| 152 | 152 | secret: &str, | |
| 153 | 153 | ) -> bool { | |
| 154 | - | use hmac::{Hmac, Mac}; | |
| 154 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 155 | 155 | use sha2::Sha256; | |
| 156 | 156 | ||
| 157 | 157 | if expires < chrono::Utc::now().timestamp() { | |
| @@ -184,7 +184,7 @@ pub fn generate_unsubscribe_url( | |||
| 184 | 184 | target: &str, | |
| 185 | 185 | secret: &str, | |
| 186 | 186 | ) -> String { | |
| 187 | - | use hmac::{Hmac, Mac}; | |
| 187 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 188 | 188 | use sha2::Sha256; | |
| 189 | 189 | ||
| 190 | 190 | let message = format!("unsub:{}:{}:{}", user_id, action, target); | |
| @@ -207,7 +207,7 @@ pub fn verify_unsubscribe_signature( | |||
| 207 | 207 | signature: &str, | |
| 208 | 208 | secret: &str, | |
| 209 | 209 | ) -> bool { | |
| 210 | - | use hmac::{Hmac, Mac}; | |
| 210 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 211 | 211 | use sha2::Sha256; | |
| 212 | 212 | ||
| 213 | 213 | let message = format!("unsub:{}:{}:{}", user_id, action, target); | |
| @@ -234,7 +234,7 @@ pub fn generate_unsubscribe_url_for_email( | |||
| 234 | 234 | target: &str, | |
| 235 | 235 | secret: &str, | |
| 236 | 236 | ) -> String { | |
| 237 | - | use hmac::{Hmac, Mac}; | |
| 237 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 238 | 238 | use sha2::Sha256; | |
| 239 | 239 | ||
| 240 | 240 | let email = email.to_lowercase(); | |
| @@ -262,7 +262,7 @@ pub fn verify_email_unsubscribe_signature( | |||
| 262 | 262 | signature: &str, | |
| 263 | 263 | secret: &str, | |
| 264 | 264 | ) -> bool { | |
| 265 | - | use hmac::{Hmac, Mac}; | |
| 265 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 266 | 266 | use sha2::Sha256; | |
| 267 | 267 | ||
| 268 | 268 | let email = email.to_lowercase(); | |
| @@ -293,7 +293,7 @@ pub fn generate_deletion_signature( | |||
| 293 | 293 | expires: i64, | |
| 294 | 294 | email: &str, | |
| 295 | 295 | ) -> String { | |
| 296 | - | use hmac::{Hmac, Mac}; | |
| 296 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 297 | 297 | use sha2::Sha256; | |
| 298 | 298 | ||
| 299 | 299 | let message = format!("delete:{}:{}:{}", user_id, expires, email); | |
| @@ -319,7 +319,7 @@ pub fn generate_issue_reply_address( | |||
| 319 | 319 | secret: &str, | |
| 320 | 320 | ) -> String { | |
| 321 | 321 | use base64::engine::{Engine, general_purpose::URL_SAFE_NO_PAD}; | |
| 322 | - | use hmac::{Hmac, Mac}; | |
| 322 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 323 | 323 | use sha2::Sha256; | |
| 324 | 324 | ||
| 325 | 325 | let message = format!("issue-reply:{}:{}", issue_id, user_id); | |
| @@ -342,7 +342,7 @@ pub fn parse_issue_reply_token( | |||
| 342 | 342 | secret: &str, | |
| 343 | 343 | ) -> Option<(crate::db::IssueId, UserId)> { | |
| 344 | 344 | use base64::engine::{Engine, general_purpose::URL_SAFE_NO_PAD}; | |
| 345 | - | use hmac::{Hmac, Mac}; | |
| 345 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 346 | 346 | use sha2::Sha256; | |
| 347 | 347 | ||
| 348 | 348 | let payload = local_part.strip_prefix("issue+")?; | |
| @@ -880,7 +880,7 @@ mod tests { | |||
| 880 | 880 | let now = chrono::Utc::now().timestamp(); | |
| 881 | 881 | ||
| 882 | 882 | // Generate a sig for an EXPIRED timestamp. | |
| 883 | - | use hmac::{Hmac, Mac}; | |
| 883 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 884 | 884 | use sha2::Sha256; | |
| 885 | 885 | let expires_past = now - 60; | |
| 886 | 886 | let message = format!("verify:{}:{}:{}", user_id, expires_past, email); | |
| @@ -904,7 +904,7 @@ mod tests { | |||
| 904 | 904 | let secret = "secret"; | |
| 905 | 905 | let expires_future = chrono::Utc::now().timestamp() + 3600; | |
| 906 | 906 | ||
| 907 | - | use hmac::{Hmac, Mac}; | |
| 907 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 908 | 908 | use sha2::Sha256; | |
| 909 | 909 | let message = format!("verify:{}:{}:{}", user_id, expires_future, email); | |
| 910 | 910 | let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).unwrap(); |
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | //! | |
| 3 | 3 | //! Signs requests with HMAC-SHA256 and communicates with MT's `/internal/*` endpoints. | |
| 4 | 4 | ||
| 5 | - | use hmac::{Hmac, Mac}; | |
| 5 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 6 | 6 | use serde::{Deserialize, Serialize}; | |
| 7 | 7 | use sha2::Sha256; | |
| 8 | 8 | use uuid::Uuid; | |
| @@ -254,7 +254,7 @@ mod tests { | |||
| 254 | 254 | /// nonce are all bound (a mutation dropping any field would collide). | |
| 255 | 255 | #[test] | |
| 256 | 256 | fn signed_message_binds_method_path_nonce() { | |
| 257 | - | use hmac::{Hmac, Mac}; | |
| 257 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 258 | 258 | use sha2::Sha256; | |
| 259 | 259 | ||
| 260 | 260 | fn sig( | |
| @@ -274,6 +274,21 @@ mod tests { | |||
| 274 | 274 | hex::encode(mac.finalize().into_bytes()) | |
| 275 | 275 | } | |
| 276 | 276 | ||
| 277 | + | // The verifier is multithreaded's compute_internal_signature_v2, so the | |
| 278 | + | // exact bytes are a cross-repo contract that neither repo can catch by | |
| 279 | + | // agreeing with itself. Both ends pin this same independently computed | |
| 280 | + | // HMAC-SHA256: key "secret", message "100\nPOST\n/x\nn\nbody". | |
| 281 | + | assert_eq!( | |
| 282 | + | sig("secret", "100", "POST", "/x", "n", "body"), | |
| 283 | + | "0e97f90cb4e4ca7aaa5499e67a22fb5b7ad45ad3cc966f37d225f39da2728098" | |
| 284 | + | ); | |
| 285 | + | ||
| 286 | + | // Tie the production signer to that layout, so the pinned vector above | |
| 287 | + | // constrains sign_request and not just this local reimplementation. | |
| 288 | + | let client = MtClient::new("http://localhost".to_string(), "s".to_string()); | |
| 289 | + | let (ts, nonce, produced) = client.sign_request("POST", "/a", "body"); | |
| 290 | + | assert_eq!(produced, sig("s", &ts, "POST", "/a", &nonce, "body")); | |
| 291 | + | ||
| 277 | 292 | let base = sig("s", "100", "POST", "/a", "n1", "body"); | |
| 278 | 293 | assert_ne!( | |
| 279 | 294 | base, |
| @@ -4,7 +4,7 @@ | |||
| 4 | 4 | //! and a thin `UntypedEvent` envelope. The webhook dispatcher matches on | |
| 5 | 5 | //! `type_` and consumes `data_object` (no per-extractor clones). | |
| 6 | 6 | ||
| 7 | - | use hmac::{Hmac, Mac}; | |
| 7 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 8 | 8 | use sha2::Sha256; | |
| 9 | 9 | ||
| 10 | 10 | use super::StripeClient; | |
| @@ -724,6 +724,19 @@ mod tests { | |||
| 724 | 724 | } | |
| 725 | 725 | ||
| 726 | 726 | #[test] | |
| 727 | + | fn signature_matches_the_reference_hmac() { | |
| 728 | + | // Stripe is the counterparty and its HMAC is fixed, so these bytes are | |
| 729 | + | // an external contract no round-trip test can check — signing and | |
| 730 | + | // verifying with the same crate agrees with itself even if the crate | |
| 731 | + | // changed. Pinned against an independent HMAC-SHA256 over Stripe's | |
| 732 | + | // documented signed payload, "{timestamp}.{body}". | |
| 733 | + | assert_eq!( | |
| 734 | + | sign_at(r#"{"id":"evt_1"}"#, "whsec_test", 1_700_000_000), | |
| 735 | + | "t=1700000000,v1=c89214b5b5da833daed6f0b8c5bb6bd58cea9022bd80ccc78230f3942d632925" | |
| 736 | + | ); | |
| 737 | + | } | |
| 738 | + | ||
| 739 | + | #[test] | |
| 727 | 740 | fn verify_signature_valid_current() { | |
| 728 | 741 | let header = sign_at(r#"{"id":"evt_1"}"#, "whsec_test", now_secs()); | |
| 729 | 742 | assert!(verify_signature(r#"{"id":"evt_1"}"#, &header, "whsec_test").is_ok()); |
| @@ -343,7 +343,7 @@ pub(crate) fn hash_backup_code(code: &str, _secret: &str) -> String { | |||
| 343 | 343 | /// stored hash isn't an Argon2 PHC string. Do not call from new code paths — | |
| 344 | 344 | /// any newly issued backup code goes through `hash_backup_code` (Argon2). | |
| 345 | 345 | pub(crate) fn legacy_hmac_backup_code(code: &str, secret: &str) -> String { | |
| 346 | - | use hmac::{Hmac, Mac}; | |
| 346 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 347 | 347 | use sha2::Sha256; | |
| 348 | 348 | ||
| 349 | 349 | let mut mac = |
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | //! Stripe test helpers — webhook signature computation and mock payment provider. | |
| 2 | 2 | ||
| 3 | - | use hmac::{Hmac, Mac}; | |
| 3 | + | use hmac::{Hmac, KeyInit, Mac}; | |
| 4 | 4 | use sha2::Sha256; | |
| 5 | 5 | use std::sync::Mutex; | |
| 6 | 6 | use std::time::{SystemTime, UNIX_EPOCH}; |