/// <summary> /// Verifies that the signature header matches that of the actual body. /// </summary> protected virtual async Task <bool> VerifySignature(string id, HttpRequestMessage request) { string secretKey = await GetReceiverConfig(request, Name, id, SecretMinLength, SecretMaxLength); // Get the expected hash from the signature header string signatureHeaderValue = GetRequestHeader(request, SignatureHeaderName); byte[] expectedHash; try { expectedHash = EncodingUtilities.FromHex(signatureHeaderValue); } catch (Exception ex) { string msg = string.Format(CultureInfo.CurrentCulture, InstagramReceiverResources.Receiver_BadHeaderEncoding, SignatureHeaderName); request.GetConfiguration().DependencyResolver.GetLogger().Error(msg, ex); HttpResponseMessage invalidEncoding = request.CreateErrorResponse(HttpStatusCode.BadRequest, msg); throw new HttpResponseException(invalidEncoding); } // Get the actual hash of the request body concatenated with the request URI byte[] actualHash; byte[] secret = Encoding.UTF8.GetBytes(secretKey); using (var hasher = new HMACSHA1(secret)) { byte[] data = await request.Content.ReadAsByteArrayAsync(); actualHash = hasher.ComputeHash(data); } // Now verify that the provided hash matches the expected hash. return(WebHookReceiver.SecretEqual(expectedHash, actualHash)); }
/// <summary> /// Verifies that the signature header matches that of the actual body. /// </summary> protected virtual async Task VerifySignature(string id, HttpRequestMessage request) { var secretKey = await GetReceiverConfig(request, Name, id, SecretMinLength, SecretMaxLength); // Get the expected hash from the signature header var header = GetRequestHeader(request, SignatureHeaderName); var values = header.SplitAndTrim('='); if (values.Length != 2 || !string.Equals(values[0], SignatureHeaderKey, StringComparison.OrdinalIgnoreCase)) { var message = string.Format(CultureInfo.CurrentCulture, GitHubReceiverResources.Receiver_BadHeaderValue, SignatureHeaderName, SignatureHeaderKey, "<value>"); request.GetConfiguration().DependencyResolver.GetLogger().Error(message); var invalidHeader = request.CreateErrorResponse(HttpStatusCode.BadRequest, message); throw new HttpResponseException(invalidHeader); } byte[] expectedHash; try { expectedHash = EncodingUtilities.FromHex(values[1]); } catch (Exception ex) { var message = string.Format(CultureInfo.CurrentCulture, GitHubReceiverResources.Receiver_BadHeaderEncoding, SignatureHeaderName); request.GetConfiguration().DependencyResolver.GetLogger().Error(message, ex); var invalidEncoding = request.CreateErrorResponse(HttpStatusCode.BadRequest, message); throw new HttpResponseException(invalidEncoding); } // Get the actual hash of the request body byte[] actualHash; var secret = Encoding.UTF8.GetBytes(secretKey); using (var hasher = new HMACSHA1(secret)) { var data = await request.Content.ReadAsByteArrayAsync(); actualHash = hasher.ComputeHash(data); } // Now verify that the provided hash matches the expected hash. if (!WebHookReceiver.SecretEqual(expectedHash, actualHash)) { var badSignature = CreateBadSignatureResponse(request, SignatureHeaderName); throw new HttpResponseException(badSignature); } }
/// <summary> /// Verifies that the signature header matches that of the actual body. /// </summary> protected virtual async Task <bool> VerifySignature(string id, HttpRequestMessage request) { // Get the expected hash from the signature and app key headers string signatureHeaderValue = GetRequestHeader(request, SignatureHeaderName); string keyHeaderValue = GetRequestHeader(request, KeyHeaderName); // Lookup which secret to use based on key header value IDictionary <string, string> lookupTable = await GetSecretLookupTable(id, request); string secretKey; if (!lookupTable.TryGetValue(keyHeaderValue, out secretKey)) { string msg = string.Format(CultureInfo.CurrentCulture, PusherReceiverResources.Receiver_SecretNotFound, KeyHeaderName, keyHeaderValue); request.GetConfiguration().DependencyResolver.GetLogger().Error(msg); HttpResponseMessage invalidEncoding = request.CreateErrorResponse(HttpStatusCode.BadRequest, msg); throw new HttpResponseException(invalidEncoding); } byte[] expectedHash; try { expectedHash = EncodingUtilities.FromHex(signatureHeaderValue); } catch (Exception ex) { string msg = string.Format(CultureInfo.CurrentCulture, PusherReceiverResources.Receiver_BadHeaderEncoding, SignatureHeaderName); request.GetConfiguration().DependencyResolver.GetLogger().Error(msg, ex); HttpResponseMessage invalidEncoding = request.CreateErrorResponse(HttpStatusCode.BadRequest, msg); throw new HttpResponseException(invalidEncoding); } // Get the actual hash of the request body byte[] actualHash; byte[] secret = Encoding.UTF8.GetBytes(secretKey); using (var hasher = new HMACSHA256(secret)) { byte[] data = await request.Content.ReadAsByteArrayAsync(); actualHash = hasher.ComputeHash(data); } // Now verify that the provided hash matches the expected hash. return(WebHookReceiver.SecretEqual(expectedHash, actualHash)); }
/// <summary> /// Verifies that the signature header matches that of the actual body. /// </summary> protected virtual async Task <bool> VerifySignature(HttpRequestMessage request, string id) { // 1. Ensure configuration for this receiver and id exists. var secretKey = await GetReceiverConfig(request, Name, id, SecretMinLength, SecretMaxLength); // 2. Get the timestamp and expected signatures from the signature header. Header is a comma-separated set // of key=value pairs. var signatureHeaderValue = GetRequestHeader(request, SignatureHeaderName); var pairs = signatureHeaderValue.SplitAndTrim(','); string timestamp = null; var expectedSignatures = new List <string>(); foreach (var pair in pairs) { var keyValuePair = pair.SplitAndTrim('='); if (keyValuePair.Length != 2) { var message = string.Format( CultureInfo.CurrentCulture, StripeReceiverResources.Receiver_InvalidHeaderFormat, SignatureHeaderName); request.GetConfiguration().DependencyResolver.GetLogger().Error(message); var invalidHeader = request.CreateErrorResponse(HttpStatusCode.BadRequest, message); throw new HttpResponseException(invalidHeader); } if (string.Equals(keyValuePair[0], SignatureKey, StringComparison.OrdinalIgnoreCase)) { expectedSignatures.Add(keyValuePair[1]); } else if (string.IsNullOrEmpty(timestamp) && string.Equals(keyValuePair[0], TimestampKey, StringComparison.OrdinalIgnoreCase)) { timestamp = keyValuePair[1]; } } if (string.IsNullOrEmpty(timestamp) || expectedSignatures.Count == 0) { var message = string.Format( CultureInfo.CurrentCulture, StripeReceiverResources.Receiver_HeaderMissingValue, SignatureHeaderName, TimestampKey, SignatureKey); request.GetConfiguration().DependencyResolver.GetLogger().Error(message); var invalidHeader = request.CreateErrorResponse(HttpStatusCode.BadRequest, message); throw new HttpResponseException(invalidHeader); } // 3. Get the actual hash of the timestamp and request body. var secret = Encoding.UTF8.GetBytes(secretKey); var prefix = Encoding.UTF8.GetBytes(timestamp + "."); byte[] actualHash; using (var hasher = new HMACSHA256(secret)) { hasher.TransformBlock( inputBuffer: prefix, inputOffset: 0, inputCount: prefix.Length, outputBuffer: null, outputOffset: 0); var data = await request.Content.ReadAsByteArrayAsync(); actualHash = hasher.ComputeHash(data); } // 4. Verify that the actual hash matches one of the expected hashes. foreach (var expectedSignature in expectedSignatures) { // While this looks repetitious compared to hex-encoding actualHash (once), a single v1 entry in the // header is the normal case. Expect multiple signatures only when rolling secret keys. byte[] expectedHash; try { expectedHash = EncodingUtilities.FromHex(expectedSignature); } catch (Exception ex) { var message = string.Format( CultureInfo.CurrentCulture, StripeReceiverResources.Receiver_BadSignatureEncoding, SignatureHeaderName, SignatureKey); request.GetConfiguration().DependencyResolver.GetLogger().Error(message, ex); var invalidEncoding = request.CreateErrorResponse(HttpStatusCode.BadRequest, message); throw new HttpResponseException(invalidEncoding); } if (SecretEqual(expectedHash, actualHash)) { return(true); } } return(false); }