/// <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);
        }
예제 #2
0
        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);
        }
예제 #3
0
        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);
        }
예제 #4
0
        /// <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);
        }
예제 #5
0
        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);
            }
        }
예제 #7
0
        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);
        }