コード例 #1
0
ファイル: HmacValidatorTests.cs プロジェクト: denedios/hmac
        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);
        }
コード例 #2
0
        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);
        }
コード例 #3
0
ファイル: HmacValidatorTests.cs プロジェクト: denedios/hmac
        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);
        }
コード例 #4
0
ファイル: HmacSignerTests.cs プロジェクト: denedios/hmac
        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);
        }
コード例 #5
0
        /// <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));
        }
コード例 #6
0
ファイル: HmacSignerTests.cs プロジェクト: denedios/hmac
        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);
        }
コード例 #7
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);
        }
コード例 #8
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);
        }
コード例 #9
0
ファイル: RestSharpHmacSigner.cs プロジェクト: denedios/hmac
        /// <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);
        }
コード例 #10
0
ファイル: HmacAuthenticator.cs プロジェクト: denedios/hmac
        /// <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));
        }