public void ShouldWrapHttpRequestMessage() { // Arrange HttpMethod method = HttpMethod.Post; Uri uri = new Uri("http://www.example.website/resource.json"); string contentMd5 = Convert.ToBase64String(_md5Hash); const string contentType = "application/json"; NameValueCollection headers = new NameValueCollection { { "X-Test-Header", "TestValue" }, { "Date", "Tue, 15 Nov 1994 08:12:31 GMT" } }; HttpRequestMessage request = new HttpRequestMessage(method, uri); request.Headers.Date = new DateTimeOffset(1994, 11, 15, 8, 12, 31, TimeSpan.Zero); request.Headers.Add(headers.Keys[0], headers[0]); request.Content = _httpContent; request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType); request.Content.Headers.ContentMD5 = _md5Hash; // Act HmacRequestWrapper wrapper = new HmacRequestWrapper(request); // Assert AssertWrapper(wrapper, new DateTimeOffset(1994, 11, 15, 8, 12, 31, TimeSpan.Zero), _httpContentStream, headers, method.Method, uri, contentMd5, contentType); }
/// <summary> /// Gets all required signature data, if found, from an HTTP request message. /// </summary> /// <param name="request">The request message to get the data from.</param> /// <returns>The extracted data as an <see cref="HmacSignatureData"/> object.</returns> /// <exception cref="ArgumentNullException">The request is null.</exception> /// <exception cref="HmacKeyRepositoryException">A problem occured when trying to retrieve a key based on the request.</exception> public virtual HmacSignatureData GetSignatureDataFromHttpRequest(HttpRequestMessage request) { if (request == null) { throw new ArgumentNullException(nameof(request), "The request cannot be null."); } HmacRequestWrapper requestWrapper = new HmacRequestWrapper(request); return(GetSignatureDataFromHttpRequest(requestWrapper)); }
/// <summary> /// Validates an entire HTTP request message. /// </summary> /// <param name="request">The HTTP request to validate.</param> /// <returns>The result of the validation as a <see cref="HmacValidationResult"/> object.</returns> /// <remarks> /// The following validation logic is used: /// - The Date header must be present if a maximum request age is configured, but cannot be older than the configured value; /// - The username header must be present when the user header name has been configured; /// - The key must be found for the request; /// - The Authorization header must be present, must have the correct authorization scheme and must contain a signature; /// - The signature created from the extracted signature data must match the one on the Authorization header. /// /// In case the request contains a body: /// - The Content-MD5 header value must match an MD5 hash of the body, if Content-MD5 validation was enabled in the configuration. /// </remarks> /// <exception cref="ArgumentNullException">The request is null.</exception> /// <exception cref="HmacConfigurationException">One or more of the configuration parameters are invalid.</exception> public virtual HmacValidationResult ValidateHttpRequest(HttpRequestMessage request) { if (request == null) { throw new ArgumentNullException(nameof(request), "The request cannot be null."); } HmacRequestWrapper requestWrapper = new HmacRequestWrapper(request); HmacSignatureData signatureData = HmacSigner.GetSignatureDataFromHttpRequest(request); return(ValidateHttpRequest(requestWrapper, signatureData)); }
private void AssertWrapper(HmacRequestWrapper wrapper, DateTimeOffset?date, Stream content, NameValueCollection headers, string method, Uri requestUri, string contentMd5, string contentType) { Assert.IsNotNull(wrapper.Date); Assert.AreEqual(date, wrapper.Date); Assert.IsNotNull(wrapper.Content); Assert.IsTrue(ReferenceEquals(content, wrapper.Content)); Assert.IsNotNull(wrapper.Headers); Assert.AreEqual(headers.Count, wrapper.Headers.Count); Assert.IsTrue(headers.AllKeys.OrderBy(k => k).SequenceEqual(wrapper.Headers.AllKeys.OrderBy(k => k))); Assert.IsNotNull(wrapper.Method); Assert.AreEqual(method, wrapper.Method); Assert.IsNotNull(wrapper.RequestUri); Assert.AreEqual(requestUri.ToString(), wrapper.RequestUri.ToString()); Assert.IsNotNull(wrapper.ContentMd5); Assert.AreEqual(contentMd5, wrapper.ContentMd5); Assert.IsNotNull(wrapper.ContentType); Assert.AreEqual(contentType, wrapper.ContentType); }
public void ShouldWrapHttpRequestBase() { // Arrange string contentMd5 = Convert.ToBase64String(_md5Hash); const string contentType = "application/json"; NameValueCollection headers = new NameValueCollection { { "X-Test-Header", "TestValue" }, { "Content-MD5", contentMd5 }, { "Date", "Tue, 15 Nov 1994 08:12:31 GMT" } }; const string method = "POST"; Uri uri = new Uri("http://www.example.website/resource.json"); Mock <HttpRequestBase> mockRequest = new Mock <HttpRequestBase>(); mockRequest.Setup(r => r.InputStream).Returns(_bodyStream); mockRequest.Setup(r => r.Headers).Returns(headers); mockRequest.Setup(r => r.HttpMethod).Returns(method); mockRequest.Setup(r => r.Url).Returns(uri); mockRequest.Setup(r => r.ContentType).Returns(contentType); // Act HmacRequestWrapper wrapper = new HmacRequestWrapper(mockRequest.Object); // Assert AssertWrapper(wrapper, new DateTimeOffset(1994, 11, 15, 8, 12, 31, TimeSpan.Zero), _bodyStream, headers, method, uri, contentMd5, contentType); }
private HmacValidationResult ValidateHttpRequest(HmacRequestWrapper request, HmacSignatureData signatureData) { if (string.IsNullOrEmpty(HmacConfiguration.AuthorizationScheme)) { throw new HmacConfigurationException("The AuthorizationScheme cannot be null or empty."); } // Note: the Content-MD5 and Content-Type headers are only required if the request contains a body // If configured, the request date is validated to prevent replay attacks if (HmacConfiguration.MaxRequestAge.HasValue) { if (!request.Date.HasValue) { return(new HmacValidationResult(HmacValidationResultCode.DateMissing, "The request date was not found.")); } if (!IsValidRequestDate(request.Date.Value)) { return(new HmacValidationResult(HmacValidationResultCode.DateInvalid, "The request date is invalid.")); } } // The username is always required when the header has been configured if (!string.IsNullOrEmpty(HmacConfiguration.UserHeaderName) && string.IsNullOrEmpty(signatureData.Username)) { return(new HmacValidationResult(HmacValidationResultCode.UsernameMissing, "The username is required but was not found.")); } // The key must be found if (string.IsNullOrEmpty(signatureData.Key)) { return(new HmacValidationResult(HmacValidationResultCode.KeyMissing, "The key was not found.")); } // If configured, an MD5 hash of the body is generated and compared with the Content-MD5 header value to check if the body hasn't been altered if (HmacConfiguration.ValidateContentMd5 && !IsValidContentMd5(signatureData.ContentMd5, request.Content)) { if (string.IsNullOrEmpty(signatureData.ContentMd5)) { return(new HmacValidationResult(HmacValidationResultCode.BodyHashMissing, "The MD5 body hash was not found.")); } return(new HmacValidationResult(HmacValidationResultCode.BodyHashMismatch, "The body content differs.")); } // The Authorization header is always required and should contain the scheme and signature IList <string> authorizations = request.Headers.GetValues(HmacConstants.AuthorizationHeaderName); string authorization; if (authorizations == null || string.IsNullOrEmpty(authorization = authorizations.FirstOrDefault())) { return(new HmacValidationResult(HmacValidationResultCode.AuthorizationMissing, "The signature was not found.")); } string[] authorizationParts = authorization.Split(' '); if (authorizationParts.Length < 2 || authorizationParts[0] != HmacConfiguration.AuthorizationScheme) { return(new HmacValidationResult(HmacValidationResultCode.AuthorizationInvalid, "The signature was not correctly specified.")); } // Finally, the signature from the Authorization header should match the newly created signature string signature = authorizationParts[1]; string newSignature = HmacSigner.CreateSignature(signatureData); if (!IsValidSignature(signature, newSignature)) { return(new HmacValidationResult(HmacValidationResultCode.SignatureMismatch, "The signature does not match.")); } return(HmacValidationResult.Ok); }
private HmacSignatureData GetSignatureDataFromHttpRequest(HmacRequestWrapper request) { HmacSignatureData signatureData = new HmacSignatureData { HttpMethod = request.Method.ToUpperInvariant() }; // Get the request URI if configured if (HmacConfiguration.SignRequestUri) { signatureData.RequestUri = request.RequestUri.AbsoluteUri; } // Get the request date if a maximum request age is configured if (HmacConfiguration.MaxRequestAge.HasValue && request.Date.HasValue) { DateTime date = request.Date.Value.UtcDateTime; signatureData.Date = date.ToString(HmacConstants.DateHeaderFormat, DateHeaderCulture); } // Get the content type and, if configured, the MD5 body hash signatureData.ContentType = request.ContentType; if (HmacConfiguration.ValidateContentMd5) { signatureData.ContentMd5 = request.ContentMd5; } // Get the username if (!string.IsNullOrEmpty(HmacConfiguration.UserHeaderName)) { signatureData.Username = request.Headers[HmacConfiguration.UserHeaderName]; } // Get the key try { signatureData.Key = HmacKeyRepository.GetHmacKeyForUsername(signatureData.Username); } catch (Exception ex) { throw new HmacKeyRepositoryException("Failed to retrieve the key.", ex); } // Add full additional headers if (HmacConfiguration.Headers != null && HmacConfiguration.Headers.Count > 0) { signatureData.Headers = new NameValueCollection(); foreach (string headerName in HmacConfiguration.Headers.Distinct(StringComparer.OrdinalIgnoreCase)) { if (string.IsNullOrEmpty(headerName)) { continue; } IList <string> headerValues = request.Headers.GetValues(headerName); if (headerValues == null || headerValues.Count == 0) { continue; } foreach (string headerValue in headerValues) { signatureData.Headers.Add(headerName, headerValue); } } } return(signatureData); }