/// <summary> /// Parse the signed request string. /// </summary> /// <param name="secret"> /// The secret. /// </param> /// <param name="signedRequestValue"> /// The signed request value. /// </param> /// <param name="maxAge"> /// The max age. /// </param> /// <param name="currentTime"> /// The current time. /// </param> /// <param name="throws"> /// The throws. /// </param> /// <returns> /// The FacebookSignedRequest. /// </returns> internal static IDictionary <string, object> TryParse(string secret, string signedRequestValue, int maxAge, double currentTime, bool throws) { Contract.Requires(!String.IsNullOrEmpty(signedRequestValue)); Contract.Requires(!String.IsNullOrEmpty(secret)); Contract.Requires(maxAge >= 0); Contract.Requires(currentTime >= 0); Contract.Requires(signedRequestValue.Contains("."), Properties.Resources.InvalidSignedRequest); try { // NOTE: currentTime added to parameters to make it unit testable. string[] split = signedRequestValue.Split('.'); if (split.Length != 2) { // need to have exactly 2 parts throw new InvalidOperationException(Properties.Resources.InvalidSignedRequest); } string encodedSignature = split[0]; string encodedEnvelope = split[1]; if (string.IsNullOrEmpty(encodedSignature)) { throw new InvalidOperationException(Properties.Resources.InvalidSignedRequest); } if (string.IsNullOrEmpty(encodedEnvelope)) { throw new InvalidOperationException(Properties.Resources.InvalidSignedRequest); } var envelope = (IDictionary <string, object>)JsonSerializer.Current.DeserializeObject(Encoding.UTF8.GetString(FacebookUtils.Base64UrlDecode(encodedEnvelope))); string algorithm = (string)envelope["algorithm"]; if (!algorithm.Equals("AES-256-CBC HMAC-SHA256") && !algorithm.Equals("HMAC-SHA256")) { // TODO: test throw new InvalidOperationException("Invalid signed request. (Unsupported algorithm)"); } byte[] key = Encoding.UTF8.GetBytes(secret); byte[] digest = FacebookUtils.ComputeHmacSha256Hash(Encoding.UTF8.GetBytes(encodedEnvelope), key); if (!digest.SequenceEqual(FacebookUtils.Base64UrlDecode(encodedSignature))) { throw new InvalidOperationException(Facebook.Web.Properties.Resources.InvalidSignedRequestSignature); } IDictionary <string, object> result; if (algorithm.Equals("HMAC-SHA256")) { // for requests that are signed, but not encrypted, we're done result = envelope; } else { result = new JsonObject(); result["algorithm"] = algorithm; long issuedAt = (long)envelope["issued_at"]; if (issuedAt < currentTime) { throw new InvalidOperationException(Web.Properties.Resources.OldSignedRequest); } result["issued_at"] = issuedAt; // otherwise, decrypt the payload byte[] iv = FacebookUtils.Base64UrlDecode((string)envelope["iv"]); byte[] rawCipherText = FacebookUtils.Base64UrlDecode((string)envelope["payload"]); var plainText = FacebookUtils.DecryptAes256CBCNoPadding(rawCipherText, key, iv); var payload = (IDictionary <string, object>)JsonSerializer.Current.DeserializeObject(plainText); result["payload"] = payload; } return(result); } catch { if (throws) { throw; } return(null); } }