Permalink
Browse files

Added signature validation

  • Loading branch information...
1 parent 5e18ef2 commit 9d479952d573029777b6910b3edc9875ff56d145 Pat Patterson committed Mar 1, 2012
Showing with 105 additions and 0 deletions.
  1. +49 −0 src/classes/TwilioRestClient.cls
  2. +56 −0 src/classes/Twilio_TestRest.cls
@@ -550,4 +550,53 @@ public class TwilioRestClient {
public String getAccountSid() {
return this.accountSid;
}
+
+ /**
+ * Validate the signature on an incoming call from Twilio
+ *
+ * @param expectedSignature
+ * The received signature. In an Apex REST method, this will be
+ * <code>RestContext.request.headers.get('X-Twilio-Signature')</code>.
+ *
+ * @param url
+ * The endpoint URL, for example
+ * <code>https://mysite.force.com/services/apexrest/twiliosms</code>.
+ * In an Apex REST method, this will be <code>'https://' +
+ * RestContext.request.headers.get('Host') + '/services/apexrest' +
+ * RestContext.request.requestURI</code>.
+ *
+ * @param params
+ * The params map from the incoming message. In an Apex REST method,
+ * this will be <code>RestContext.request.params</code>.
+ *
+ * @return true if the signature is valid, false otherwise
+ */
+ public boolean validateRequest(String expectedSignature, String url, Map<String,String> params) {
+ try {
+ // sort the params alphabetically, and append the key and value of each to the url
+ String data = url;
+ if(params!=null){
+ List<String> sortedKeys = new List<String>(params.keySet());
+ sortedKeys.sort();
+
+ for (String s: sortedKeys) {
+ data += s;
+ String v='';
+ if (params.get(s)!=null)
+ v=params.get(s);
+ data += v;
+ }
+ }
+
+ //compute the hmac on input data bytes, with AuthToken as key
+ Blob mac = Crypto.generateMac('hmacSHA1', Blob.valueOf(data), Blob.valueOf(authToken));
+
+ //base64-encode the hmac
+ String signature = EncodingUtil.base64Encode(mac);
+
+ return signature.equals(expectedSignature);
+ } catch (Exception e) {
+ return false;
+ }
+ }
}
@@ -156,4 +156,60 @@ private class Twilio_TestRest {
}
System.assert(e instanceof TwilioRestException);
}
+
+ static testmethod void testValidateRequest() {
+ //The Twilio authToken is the encryption key
+ String methodAuthToken = '1c892n40nd03kdnc0112slzkl3091j20';
+ TwilioRestClient client = new TwilioRestClient(accountSid,methodAuthToken);
+
+ // This is the signature we expect
+ String expected_sig = 'fF+xx6dTinOaCdZ0aIeNkHr/ZAA=';
+
+ //This is the url that twilio requested
+ String url = 'http://www.postbin.org/1ed898x';
+
+ //These are the post params twilio sent in its request
+ Map<String,String> params = new Map<String,String>{
+ 'AccountSid'=>'AC9a9f9392lad99kla0sklakjs90j092j3',
+ 'ApiVersion'=>'2010-04-01',
+ 'CallSid'=>'CAd800bb12c0426a7ea4230e492fef2a4f',
+ 'CallStatus'=>'ringing',
+ 'Called'=>'+15306384866',
+ 'CalledCity'=>'OAKLAND',
+ 'CalledCountry'=>'US',
+ 'CalledState'=>'CA',
+ 'CalledZip'=>'94612',
+ 'Caller'=>'+15306666666',
+ 'CallerCity'=>'SOUTH LAKE TAHOE',
+ 'CallerCountry'=>'US',
+ 'CallerName'=>'CA Wireless Call',
+ 'CallerState'=>'CA',
+ 'CallerZip'=>'89449',
+ 'Direction'=>'inbound',
+ 'From'=>'+15306666666',
+ 'FromCity'=>'SOUTH LAKE TAHOE',
+ 'FromCountry'=>'US',
+ 'FromState'=>'CA',
+ 'FromZip'=>'89449',
+ 'To'=>'+15306384866',
+ 'ToCity'=>'OAKLAND',
+ 'ToCountry'=>'US',
+ 'ToState'=>'CA',
+ 'ToZip'=>'94612'
+ };
+ boolean result = client.validateRequest(expected_sig, url, params);
+
+ System.assertEquals(true,result);
+
+ // Now tamper with something, and the result should be false
+ params.put('CalledState', 'NV');
+ result = client.validateRequest(expected_sig, url, params);
+
+ System.assertEquals(false,result);
+
+ // Pass nulls, and the result should still be false
+ result = client.validateRequest(null, null, null);
+
+ System.assertEquals(false,result);
+ }
}

0 comments on commit 9d47995

Please sign in to comment.