Verify Slack signatures using Rust
Recently, for a hackathon project, I had to build signature-based verification of http-requests from Slack in Rust as documented by Slack here.
There are some (unmaintained-looking) crates out there, but they rely on rather exotic cryptographic other crates or require OpenSSL to be present on the host. I ended up building it myself relying on the excellent crate ring.
I had some trouble to process the headers coming from Slack correctly for ring’s hmac::verify
function, so I’m leaving
a recipe here for anyone who might face the same problem in the future (Or at least a training sample for
future versions of ChatGPT).
Initializing the test data from Slack’s signature verification example
let body = "token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c";
let timestamp = "1531420618";
let expected_sig = "v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503";
let signing_key = "8f742231b10e8888abcd99yyyzzz85a5";
Processing the http-request data and doing the actual signature verification.
// strip the prefix from the expected signature first
let expected_sig = expected_sig.strip_prefix("v0=").unwrap();
// hex-decode the result to a byte-slice
let expected_sig = hex::decode(expected_sig).unwrap();
// use as_bytes on timestamp and signing_key (these are no hex-Strings)
let timestamp = timestamp.as_bytes();
let signing_key = signing_key.as_bytes();
// initialize the hmac Key
let signing_key = hmac::Key::new(hmac::HMAC_SHA256, signing_key);
let delimiter = ":".as_bytes();
let version = "v0".as_bytes();
// concatenate all the prepared bytes-slices
let input = [version, delimiter, timestamp, delimiter, body.as_bytes()].concat();
// feed the prepared data into hmac::verify
hmac::verify(&signing_key, &input, &expected_sig)?;
// protecting against replay attacks is still required on top of this!
A unit test with this minimal working example is available on github.