public void ShouldFailValidationDueToMissingDate() { // Arrange IHmacConfiguration configuration = CreateConfiguration(); IHmacSigner signer = new HmacSigner(configuration, _keyRepository); HmacValidator validator = new HmacValidator(configuration, signer); DateTimeOffset dateTimeOffset = DateTimeOffset.UtcNow.AddMinutes(-3); string dateString = dateTimeOffset.ToString(HmacConstants.DateHeaderFormat, _dateHeaderCulture); HttpRequestBase request = CreateRequest(dateString); HmacSignatureData signatureData = signer.GetSignatureDataFromHttpRequest(request); string signature = signer.CreateSignature(signatureData); request.Headers[HmacConstants.AuthorizationHeaderName] = string.Format( HmacConstants.AuthorizationHeaderFormat, configuration.AuthorizationScheme, signature); request.Headers.Remove(HmacConstants.DateHeaderName); // Act HmacValidationResult result = validator.ValidateHttpRequest(request); // Assert Assert.IsNotNull(result); Assert.IsNotNull(result.ErrorMessage); Assert.AreEqual(result.ResultCode, HmacValidationResultCode.DateMissing); }
public void ShouldAuthenticate() { // Arrange IHmacConfiguration configuration = CreateConfiguration(); IRestSharpHmacSigner signer = new RestSharpHmacSigner(configuration, _keyRepository); HmacAuthenticator authenticator = new HmacAuthenticator(configuration, signer); IRestClient client = CreateClient(); IRestRequest request = CreateRequest(configuration); // Act authenticator.Authenticate(client, request); Parameter contentMd5Param = request.Parameters.FirstOrDefault(p => p.Name == "Content-MD5"); Parameter authorizationParam = request.Parameters.FirstOrDefault(p => p.Name == "Authorization"); Parameter dateParam = request.Parameters.FirstOrDefault(p => p.Name == "Date"); string dateString = dateParam != null ? dateParam.Value as string ?? string.Empty : string.Empty; DateTimeOffset parsedDate; bool isValidDate = DateTimeOffset.TryParseExact(dateString, "ddd, dd MMM yyyy HH:mm:ss G\\MT", _dateHeaderCulture, DateTimeStyles.AssumeUniversal, out parsedDate); HmacSignatureData signatureData = signer.GetSignatureDataFromRestRequest(client, request); string signature = signer.CreateSignature(signatureData); // Assert Assert.IsNotNull(contentMd5Param); Assert.AreEqual(ParameterType.HttpHeader, contentMd5Param.Type); Assert.AreEqual(_base64Md5Hash, contentMd5Param.Value); Assert.IsNotNull(authorizationParam); Assert.AreEqual(ParameterType.HttpHeader, authorizationParam.Type); Assert.IsNotNull(authorizationParam.Value); Assert.IsInstanceOfType(authorizationParam.Value, typeof(string)); Assert.AreEqual((string)authorizationParam.Value, "HMAC " + signature); Assert.IsNotNull(dateParam); Assert.AreEqual(ParameterType.HttpHeader, dateParam.Type); Assert.IsNotNull(dateParam.Value); Assert.IsTrue(isValidDate); }
public void ShouldFailValidationDueToMissingKey() { // Arrange Mock <IHmacKeyRepository> mockKeyRepo = new Mock <IHmacKeyRepository>(); mockKeyRepo.Setup(r => r.GetHmacKeyForUsername(It.IsAny <string>())).Returns((string)null); IHmacConfiguration configuration = CreateConfiguration(); IHmacSigner signer = new HmacSigner(configuration, mockKeyRepo.Object); HmacValidator validator = new HmacValidator(configuration, signer); DateTimeOffset dateTimeOffset = DateTimeOffset.UtcNow.AddMinutes(-3); string dateString = dateTimeOffset.ToString(HmacConstants.DateHeaderFormat, _dateHeaderCulture); HttpRequestBase request = CreateRequest(dateString); HmacSignatureData signatureData = signer.GetSignatureDataFromHttpRequest(request); signatureData.Key = "TestKey"; string signature = signer.CreateSignature(signatureData); request.Headers[HmacConstants.AuthorizationHeaderName] = string.Format( HmacConstants.AuthorizationHeaderFormat, configuration.AuthorizationScheme, signature); // Act HmacValidationResult result = validator.ValidateHttpRequest(request); // Assert Assert.IsNotNull(result); Assert.IsNotNull(result.ErrorMessage); Assert.AreEqual(result.ResultCode, HmacValidationResultCode.KeyMissing); }
public void ShouldCreateSignature() { // Arrange IHmacConfiguration configuration = CreateConfiguration(); HmacSigner signer = new HmacSigner(configuration, _keyRepository); HmacSignatureData signatureData = new HmacSignatureData { Key = _keyRepository.Key, HttpMethod = "POST", ContentMd5 = _base64Md5Hash, ContentType = ContentType, Date = "Wed, 30 Dec 2015 12:30:45 GMT", Username = _keyRepository.Username, RequestUri = Url, Headers = new NameValueCollection { { "X-Custom-Test-Header-1", "Test1" }, { "X-Custom-Test-Header-2", "Test2" } } }; const string expectedSignature = "CSDWHwt5sOWkBKS5mSNWrgJaXREQB6CKywVyB/A4IDQ65h3gzR9/Uutug34ikpcW3JlnVyAL+xbu/eNaq99q/Q=="; HmacSignatureData signatureData2 = new HmacSignatureData { Key = _keyRepository.Key }; const string expectedSignature2 = "QwPLzI1RUBRyYerrolY3+4Mzuw8Z07YfxnpP9va2ckH/I7UvdPBMkcaomWOaC5pZym9x+t/BA/2VTP6pjje1DQ=="; // Act string signature = signer.CreateSignature(signatureData); string signature2 = signer.CreateSignature(signatureData2); // Assert Assert.IsNotNull(signature); Assert.AreEqual(expectedSignature, signature); Assert.IsNotNull(signature2); Assert.AreEqual(expectedSignature2, signature2); }
/// <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)); }
public void ShouldGetSignatureDataFromHttpRequest() { // Arrange IHmacConfiguration configuration = CreateConfiguration(); string dateString = CreateHttpDateString(); HttpRequestBase request = CreateRequest(dateString); HmacSigner signer = new HmacSigner(configuration, _keyRepository); // Act HmacSignatureData signatureData = signer.GetSignatureDataFromHttpRequest(request); // Assert Assert.IsNotNull(signatureData); Assert.AreEqual(_keyRepository.Key, signatureData.Key); Assert.AreEqual(request.HttpMethod, signatureData.HttpMethod); Assert.AreEqual(_base64Md5Hash, signatureData.ContentMd5); Assert.AreEqual(ContentType, signatureData.ContentType); Assert.AreEqual(dateString, signatureData.Date); Assert.AreEqual(_keyRepository.Username, signatureData.Username); Assert.AreEqual(Url, signatureData.RequestUri); Assert.IsNotNull(signatureData.Headers); Assert.IsTrue(signatureData.Headers.Count > 0); }
public void ShouldGetSignatureDataFromRestRequest() { // Arrange IHmacConfiguration configuration = CreateConfiguration(); string dateString = CreateHttpDateString(); IRestClient client = CreateClient(); IRestRequest request = CreateRequest(configuration, dateString); RestSharpHmacSigner signer = new RestSharpHmacSigner(configuration, _keyRepository); // Act HmacSignatureData signatureData = signer.GetSignatureDataFromRestRequest(client, request); // Assert Assert.IsNotNull(signatureData); Assert.AreEqual(_keyRepository.Key, signatureData.Key); Assert.AreEqual(request.Method.ToString().ToUpperInvariant(), signatureData.HttpMethod); Assert.AreEqual(_base64Md5Hash, signatureData.ContentMd5); Assert.AreEqual(ContentType, signatureData.ContentType); Assert.AreEqual(dateString, signatureData.Date); Assert.AreEqual(_keyRepository.Username, signatureData.Username); Assert.AreEqual(Url, signatureData.RequestUri); Assert.IsNotNull(signatureData.Headers); Assert.IsTrue(signatureData.Headers.Count > 0); }
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); }
/// <summary> /// Gets all required signature data, if found, from the RestSharp client or request. /// </summary> /// <param name="client">The client to get the data from.</param> /// <param name="request">The request to get the data from.</param> /// <returns>The extracted data as an <see cref="HmacSignatureData"/> object.</returns> /// <remarks> /// Note 1: /// The headers of the client are inspected before those of the request. /// Therefore, if a header is both in the client and request, the one from the client will be used. /// In case of the body, it's the other way around. /// /// Note 2: /// The Content-Type is extracted from the body parameter (from the <see cref="Parameter.Name"/> property), NOT from a header parameter. /// /// Note 3: /// Keep in mind that when signing additional canonicalized headers, some will possibly not be available for signing, which may cause validation to fail. /// This is because RestSharps itself adds some headers after authentication and immediately before sending the request (the 'User-Agent' header for example). /// </remarks> /// <exception cref="ArgumentNullException">The client or request is null.</exception> public virtual HmacSignatureData GetSignatureDataFromRestRequest(IRestClient client, IRestRequest request) { if (client == null) { throw new ArgumentNullException(nameof(client), "The client cannot be null."); } if (request == null) { throw new ArgumentNullException(nameof(request), "The request cannot be null."); } HmacSignatureData signatureData = new HmacSignatureData { HttpMethod = request.Method.ToString().ToUpperInvariant() }; // Get the request URI if configured if (HmacConfiguration.SignRequestUri) { signatureData.RequestUri = client.BuildUri(request).AbsoluteUri; } // Get date if a maximum request age is configured if (HmacConfiguration.MaxRequestAge.HasValue) { Parameter dateParameter = client.DefaultParameters.GetHeaderParameter(HmacConstants.DateHeaderName, request.Parameters); if (dateParameter?.Value != null) { signatureData.Date = dateParameter.Value.ToString(); } } // Get content type Parameter bodyParameter = request.Parameters.GetBodyParameter(client.DefaultParameters); if (bodyParameter != null) { signatureData.ContentType = bodyParameter.Name; // Get content MD5 if configured if (HmacConfiguration.ValidateContentMd5) { Parameter contentMd5Parameter = client.DefaultParameters.GetHeaderParameter(HmacConstants.ContentMd5HeaderName, request.Parameters); if (contentMd5Parameter?.Value != null) { signatureData.ContentMd5 = contentMd5Parameter.Value.ToString(); } } } // Get username Parameter usernameParameter = client.DefaultParameters.GetHeaderParameter(HmacConfiguration.UserHeaderName, request.Parameters); if (usernameParameter?.Value != null) { signatureData.Username = usernameParameter.Value.ToString(); } // Get the key try { signatureData.Key = HmacKeyRepository.GetHmacKeyForUsername(signatureData.Username); } catch (Exception ex) { throw new HmacKeyRepositoryException("Failed to retrieve the key.", ex); } // Add additional headers if (HmacConfiguration.Headers != null && HmacConfiguration.Headers.Count > 0) { signatureData.Headers = new NameValueCollection(); foreach (string headerName in HmacConfiguration.Headers.Distinct(StringComparer.OrdinalIgnoreCase)) { IEnumerable <string> headerValues; if (!client.DefaultParameters.TryGetHeaderValues(headerName, out headerValues, request.Parameters)) { continue; } foreach (string headerValue in headerValues) { signatureData.Headers.Add(headerName, headerValue); } } } return(signatureData); }
/// <summary> /// Creates a signature for the request. /// </summary> /// <param name="client">The client containing parameters to use for signing.</param> /// <param name="request">The request containing parameters to use for signing.</param> /// <returns>The signature as a <see cref="string"/>.</returns> protected virtual string CreateSignature(IRestClient client, IRestRequest request) { HmacSignatureData signatureData = Signer.GetSignatureDataFromRestRequest(client, request); return(Signer.CreateSignature(signatureData)); }