Search code examples
rustrust-axum

Rust HMAC can't get the right signature


I'm trying to emulate this Svix webhook: https://www.standardwebhooks.com/simulate/svix. I need to generate a signature by combining the svix-id header with the svix-timestamp header and the raw body of the request, and it shall equal the svix-signature header

let svix_id_header = &req
        .headers()
        .get("svix-id")
        .ok_or_else(|| (StatusCode::BAD_REQUEST, "Missing svix-id header".to_string()))?
        .to_str()
        .or_else(|_| Err((StatusCode::BAD_REQUEST, "Invalid svix-id header".to_string())))?
        .to_string();

    let svix_timestamp_header = &req
        .headers()
        .get("svix-timestamp")
        .ok_or_else(|| (StatusCode::BAD_REQUEST, "Missing svix-timestamp header".to_string()))?
        .to_str()
        .or_else(|_| Err((StatusCode::BAD_REQUEST, "Invalid svix-id header".to_string())))?
        .to_string();

    let svix_signature_header = &req
        .headers()
        .get("svix-signature")
        .ok_or_else(|| (StatusCode::BAD_REQUEST, "Missing svix-signature header".to_string()))?
        .to_str()
        .or_else(|_| Err((StatusCode::BAD_REQUEST, "Invalid svix-id header".to_string())))?
        .to_string();

    let body_bytes = to_bytes(req.into_body(), usize::MAX).await.unwrap();
    let body_string = String::from_utf8(body_bytes.to_vec()).unwrap();
    
    let secret_bytes = state.webhook_secret.as_bytes();
    let signed_content = format!("{}.{}.{}", svix_id_header, svix_timestamp_header, body_string);

    let mut signature = HmacSha256::new_from_slice(secret_bytes).or_else(|_| Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to create HMAC".to_string())))?;
    signature.update(signed_content.as_bytes());

    let result = signature.finalize().into_bytes();
    let result = base64::encode(&result.to_vec());
    
    if result != *svix_signature_header {
        return Err((StatusCode::UNAUTHORIZED, "Invalid signature".to_string()));
    }

result shall equal svix_signature_header but I'm getting the error of "Invalid signature". This means result does not equal svix_signature_header.


Solution

  • As mentioned on the Rust discord, you are not parsing the svix-signature header to compare the actual signatures. There is additional metadata in the header.

    Also please note that you should use constant-time comparison functions for verifying the hash to prevent timing attacks.