/// <summary> /// Decode the given <paramref name="hexEncodedValue"/>. /// </summary> /// <param name="hexEncodedValue">The hex-encoded <see cref="string"/>.</param> /// <param name="signatureHeaderName"> /// The name of the HTTP header containing the <paramref name="hexEncodedValue"/>. /// </param> /// <param name="errorResult"> /// Set to <see langword="null"/> if decoding is successful. Otherwise, an <see cref="IActionResult"/> that /// when executed will produce a response containing details about the problem. /// </param> /// <returns> /// If successful, the <see cref="byte"/> array containing the decoded hash. <see langword="null"/> if any /// issues occur. /// </returns> protected virtual byte[] GetDecodedHash( string hexEncodedValue, string signatureHeaderName, out IActionResult errorResult) { try { var decodedHash = EncodingUtilities.FromHex(hexEncodedValue); errorResult = null; return(decodedHash); } catch (Exception ex) { Logger.LogError( 401, ex, "The '{HeaderName}' header value is invalid. It must be a valid hex-encoded string.", signatureHeaderName); } var message = string.Format( CultureInfo.CurrentCulture, Resources.Security_BadHeaderEncoding, signatureHeaderName); errorResult = WebHookResultUtilities.CreateErrorResult(message); return(null); }
public void ToHex_FromHex_Roundtrips_UriSafe(string input) { byte[] data = Encoding.UTF8.GetBytes(input); string encoded = EncodingUtilities.ToHex(data); byte[] output = EncodingUtilities.FromHex(encoded); string actual = Encoding.UTF8.GetString(output); Assert.Equal(input, actual); }
private async Task <HttpResponseMessage> VerifyContentDistribution(WebSubSubscription subscription, HttpRequestMessage request) { if (subscription.State != WebSubSubscriptionState.SubscribeValidated) { return(request.CreateResponse(HttpStatusCode.NotFound)); } if (String.IsNullOrWhiteSpace(subscription.Secret)) { return(null); } string signatureHeader; try { signatureHeader = GetRequestHeader(request, SIGNATURE_HEADER_NAME); } catch (HttpResponseException) { return(HandleInvalidSignatureHeader(request)); } string[] tokens = signatureHeader.SplitAndTrim('='); if (tokens.Length != 2) { return(HandleInvalidSignatureHeader(request)); } byte[] signatureHeaderExpectedHash; try { signatureHeaderExpectedHash = EncodingUtilities.FromHex(tokens[1]); } catch (Exception) { return(HandleBadRequest(request, $"The '{SIGNATURE_HEADER_NAME}' header value is invalid. The '{RECEIVER_NAME}' WebHook receiver requires a valid hex-encoded string.")); } byte[] payloadActualHash = await ComputeRequestBodyHashAsync(request, tokens[0], Encoding.UTF8.GetBytes(subscription.Secret)); if (payloadActualHash == null) { return(HandleInvalidSignatureHeader(request)); } if (!SecretEqual(signatureHeaderExpectedHash, payloadActualHash)) { return(HandleBadRequest(request, $"The signature provided by the '{SIGNATURE_HEADER_NAME}' header field does not match the value expected by the '{RECEIVER_NAME}' WebHook receiver. WebHook request is invalid.")); } return(null); }
/// <summary> /// Verifies that the signature header matches that of the actual body. /// </summary> protected virtual async Task <bool> VerifySignature(string name, HttpContext context) { string secretKey = _options.GetReceiverConfig(name); // Get the expected hash from the signature header string header = context.Request.Headers[SignatureHeaderName]; string[] values = header.Split('='); if (values.Length != 2 || !string.Equals(values[0], SignatureHeaderKey, StringComparison.OrdinalIgnoreCase)) { string msg = string.Format(CustomReceiverResource.Receiver_BadHeaderValue, SignatureHeaderName, SignatureHeaderKey, "<value>"); context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsync(msg); return(false); } byte[] expectedHash; try { expectedHash = EncodingUtilities.FromHex(values[1]); } catch (Exception) { string msg = string.Format(CustomReceiverResource.Receiver_BadHeaderEncoding, SignatureHeaderName); context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsync(msg); return(false); } // Compute the actual hash of the request body byte[] actualHash; byte[] secret = Encoding.UTF8.GetBytes(secretKey); using (var hasher = new HMACSHA256(secret)) { byte[] data = await ReadAsByteArrayAsync(context.Request); actualHash = hasher.ComputeHash(data); } // Now verify that the actual hash matches the expected hash. if (!WebHookReceiver.SecretEqual(expectedHash, actualHash)) { context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsync("Bad Signature"); return(false); } return(true); }
public void FromHex_ConvertsCorrectly(string input) { // Arrange byte[] expected = Encoding.UTF8.GetBytes(input); string data1 = ToExpectedHex(expected).ToUpperInvariant(); string data2 = ToExpectedHex(expected).ToLowerInvariant(); // Act byte[] actual1 = EncodingUtilities.FromHex(data1); byte[] actual2 = EncodingUtilities.FromHex(data2); // Assert Assert.Equal(expected, actual1); Assert.Equal(expected, actual2); }
/// <summary> /// Verifies that the signature header matches that of the actual body. /// </summary> protected virtual async Task VerifySignature(string id, HttpRequestMessage request) { string secretKey = await GetReceiverConfig(request, Name, id, SecretMinLength, SecretMaxLength); // Get the expected hash from the signature header string header = GetRequestHeader(request, SignatureHeaderName); string[] values = header.SplitAndTrim('='); if (values.Length != 2 || !string.Equals(values[0], SignatureHeaderKey, StringComparison.OrdinalIgnoreCase)) { string msg = string.Format(CultureInfo.CurrentCulture, FacebookReceiverResources.Receiver_BadHeaderValue, SignatureHeaderName, SignatureHeaderKey, "<value>"); request.GetConfiguration().DependencyResolver.GetLogger().Error(msg); HttpResponseMessage invalidHeader = request.CreateErrorResponse(HttpStatusCode.BadRequest, msg); throw new HttpResponseException(invalidHeader); } byte[] expectedHash; var headerHash = values[1]; try { expectedHash = EncodingUtilities.FromHex(headerHash); } catch (Exception ex) { string msg = string.Format(CultureInfo.CurrentCulture, FacebookReceiverResources.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 HMACSHA1(secret)) { var payload = EncodeNonAsciiCharacters(await request.Content.ReadAsStringAsync()); var data = Encoding.UTF8.GetBytes(payload); actualHash = hasher.ComputeHash(data); } // Now verify that the provided hash matches the expected hash. if (!string.Equals(headerHash, ByteArrayToString(actualHash), StringComparison.CurrentCultureIgnoreCase)) { var badSignature = CreateBadSignatureResponse(request, SignatureHeaderName); throw new HttpResponseException(badSignature); } }
public void FromHex_Throws_OnOddInput(string invalid) { InvalidOperationException ex = Assert.Throws <InvalidOperationException>(() => EncodingUtilities.FromHex(invalid)); Assert.Contains(string.Format(CultureInfo.CurrentCulture, "Input is not a valid hex-encoded string: '{0}'. Please provide a valid hex-encoded string.", invalid), ex.Message); }