public async Task TamperedTimestampMacMustFailClientAuthentication() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateClientAuthorizationInternalAsync(request, DateTime.UtcNow.AddMinutes(-2)); using (var response = await invoker.SendAsync(request, CancellationToken.None)) { Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); var wwwheader = response.Headers.WwwAuthenticate.FirstOrDefault(); Assert.IsNotNull(wwwheader); Assert.AreEqual("hawk", wwwheader.Scheme); Assert.IsNotNull(wwwheader.Parameter); string tsParameter = wwwheader.Parameter; // ts and tsm must be present Assert.IsTrue(ParameterChecker.IsFieldPresent(tsParameter, "ts")); Assert.IsTrue(ParameterChecker.IsFieldPresent(tsParameter, "tsm")); Assert.IsTrue(await client.AuthenticateAsync(response)); string tamperedtsParameter = tsParameter.Replace("tsm=\"", "tsm=\"1234"); // tsm="abc" => tsm = "1234abc" response.Headers.WwwAuthenticate.Remove(wwwheader); response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("hawk", tamperedtsParameter)); Assert.IsFalse(await client.AuthenticateAsync(response)); } } } }
public async Task TamperedResponseMacOrHashMustFailClientAuthentication() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateClientAuthorizationAsync(request); using (var response = await invoker.SendAsync(request, CancellationToken.None)) { Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); string header = response.Headers.GetValues(HawkConstants.ServerAuthorizationHeaderName).FirstOrDefault(); Assert.IsTrue(await client.AuthenticateAsync(response)); string tamperedHeader = header.Replace("mac=\"", "mac=\"1234"); // mac="abc" => mac = "1234abc" response.Headers.Remove(HawkConstants.ServerAuthorizationHeaderName); response.Headers.Add(HawkConstants.ServerAuthorizationHeaderName, tamperedHeader); Assert.IsFalse(await client.AuthenticateAsync(response)); tamperedHeader = header.Replace("hash=\"", "hash=\"1234"); // hash="abc" => hash = "1234abc" response.Headers.Remove(HawkConstants.ServerAuthorizationHeaderName); response.Headers.Add(HawkConstants.ServerAuthorizationHeaderName, tamperedHeader); Assert.IsFalse(await client.AuthenticateAsync(response)); } } } }
public async Task MustReturn401WhenHawkParameterTimestampIsStale() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateClientAuthorizationInternalAsync(request, DateTime.UtcNow.AddMinutes(-2)); using (var response = await invoker.SendAsync(request, CancellationToken.None)) { Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); Assert.IsNotNull(response.Headers.WwwAuthenticate.FirstOrDefault()); Assert.AreEqual("hawk", response.Headers.WwwAuthenticate.FirstOrDefault().Scheme); Assert.IsFalse(String.IsNullOrEmpty(response.Headers.WwwAuthenticate.FirstOrDefault().Parameter)); string tsParameter = response.Headers.WwwAuthenticate.FirstOrDefault().Parameter; // ts and tsm must be present Assert.IsTrue(ParameterChecker.IsFieldPresent(tsParameter, "ts")); Assert.IsTrue(ParameterChecker.IsFieldPresent(tsParameter, "tsm")); Assert.IsTrue(await client.AuthenticateAsync(response)); } } } }
public async Task MustReturn204WhenValidHawkSchemeHeaderIsPresentInPut() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Put, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); request.Content = new ObjectContent<string>("Steve", new JsonMediaTypeFormatter()); await client.CreateClientAuthorizationAsync(request); string parameter = request.Headers.Authorization.Parameter; // hash must be present, since PUT contains a request body Assert.IsTrue(ParameterChecker.IsFieldPresent(parameter, "hash")); request.Headers.Authorization = new AuthenticationHeaderValue("hawk", parameter); using (var response = await invoker.SendAsync(request, CancellationToken.None)) { Assert.AreEqual(HttpStatusCode.NoContent, response.StatusCode); string authorization = response.Headers.GetValues("Server-Authorization").FirstOrDefault(); // mac must be present in Server-Authorization Assert.IsTrue(ParameterChecker.IsFieldPresent(authorization, "mac")); // Since there is no response body, no hash must be present in Server-Authorization Assert.IsFalse(ParameterChecker.IsFieldPresent(authorization, "hash")); Assert.IsTrue(await client.AuthenticateAsync(response)); } } } }
public async Task MustReturn200WhenValidHawkSchemeHeaderIsPresentInPost() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Post, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); request.Content = new ObjectContent<string>("Steve", new JsonMediaTypeFormatter()); await client.CreateClientAuthorizationAsync(request); string parameter = request.Headers.Authorization.Parameter; // hash must be present, since POST contains a request body Assert.IsTrue(ParameterChecker.IsFieldPresent(parameter, "hash")); request.Headers.Authorization = new AuthenticationHeaderValue("hawk", parameter); using (var response = await invoker.SendAsync(request, CancellationToken.None)) { Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); Assert.AreEqual("Hello, Steve. Thanks for flying Hawk", await response.Content.ReadAsAsync<string>()); Assert.IsTrue(await client.AuthenticateAsync(response)); } } } }
public async Task TimestampInSubsequentRequestMustBeAdjustedBasedOnTimestampInWwwAuthenticateHeaderOfPrevious() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateClientAuthorizationAsync(request); using (var response = await invoker.SendAsync(request, CancellationToken.None)) { // Simulate server clock running 5 minutes slower than client and // produce the parameter with ts and tsm var timestamp = new NormalizedTimestamp(DateTime.UtcNow.AddMinutes(-5), ServerFactory.DefaultCredential); string timestampHeader = timestamp.ToWwwAuthenticateHeaderParameter(); // Add that to the WWW-Authenticate before authenticating with the // client, to simulate clock skew response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("hawk", timestampHeader)); // Client must have now calculated an offset of -300 seconds approx Assert.IsTrue(await client.AuthenticateAsync(response)); Assert.IsTrue(HawkClient.CompensatorySeconds <= -299 && HawkClient.CompensatorySeconds >= -301); // Create a fresh request and see if this offset is applied to the timestamp using (var subsequentRequest = new HttpRequestMessage(HttpMethod.Get, URI)) { await client.CreateClientAuthorizationAsync(subsequentRequest); string header = subsequentRequest.Headers.Authorization.Parameter; ArtifactsContainer artifacts = null; Assert.IsTrue(ArtifactsContainer.TryParse(header, out artifacts)); var timestampInSubsequentRequest = artifacts.Timestamp; var now = DateTime.UtcNow.ToUnixTime(); // Since server clock is slow, the timestamp going out must be offset by the same 5 minutes // or 300 seconds. Give leeway of a second while asserting. ulong difference = now - timestampInSubsequentRequest; Assert.IsTrue(difference >= 299 && difference <= 301); } } } } }
public async Task MustReturn200WhenValidHawkSchemeHeaderIsPresentInGet() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateClientAuthorizationAsync(request); using (var response = await invoker.SendAsync(request, CancellationToken.None)) { Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); Assert.AreEqual("Thanks for flying Hawk", await response.Content.ReadAsAsync<string>()); Assert.IsTrue(await client.AuthenticateAsync(response)); } } } }
public async Task MustReturn200ForValidHawkSchemeWithXffRequestHeaderContainingIPV6AddressAndPort() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { request.Headers.Add("X-Forwarded-For", "[ipv6]:99"); var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateClientAuthorizationAsync(request); using (var response = await invoker.SendAsync(request, CancellationToken.None)) { Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); Assert.AreEqual("Thanks for flying Hawk", await response.Content.ReadAsAsync<string>()); Assert.IsTrue(await client.AuthenticateAsync(response)); } } } }
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var client = new HawkClient(this.credentialsCallback); if (this.normalizationCallback != null) client.ApplicationSpecificData = this.normalizationCallback(request); await client.CreateClientAuthorizationAsync(request); var response = await base.SendAsync(request, cancellationToken); if (!await client.AuthenticateAsync(response)) throw new SecurityException("Invalid Mac and/or hash. Response possibly tampered."); bool isValidAppSpecificData = this.verificationCallback == null || this.verificationCallback(response, client.WebApiSpecificData); if (!isValidAppSpecificData) throw new SecurityException("Invalid Application Specific Data"); return response; }
public async Task MustReturn200WhenValidHawkSchemeHeaderWithAppSpecifiDataIsPresentInGetAndServerSendsAppSpecificData() { Func<HttpResponseMessage, string> normalizationCallback = (response) => { // Simulate service adding a sensitive header response.Headers.Add("X-Header-To-Protect", "Swoop"); // Sensitive header to protect // return the status code and the sensitive header in the agreed format return (int)response.StatusCode + ":X-Header-To-Protect:Swoop"; }; Func<HttpRequestMessage, string, bool> verificationCallback = (request, appSpecificData) => { // Client and server decided that appSpecificData will be in the form of // header name and header value separated by a colon, like so: X-Header-To-Protect:Swoosh. if (String.IsNullOrEmpty(appSpecificData)) return true; // Nothing to check against var parts = appSpecificData.Split(':'); string headerName = parts[0]; string value = parts[1]; if (request.Headers.Contains(headerName) && request.Headers.GetValues(headerName).First().Equals(value)) return true; return false; }; server = ServerFactory.Create(normalizationCallback, verificationCallback); using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { request.Headers.Add("X-Header-To-Protect", "Swoosh"); // Sensitive header to protect var client = new HawkClient(() => ServerFactory.DefaultCredential); client.ApplicationSpecificData = "X-Header-To-Protect:Swoosh"; // Put the sensitive header in the format agreed await client.CreateClientAuthorizationAsync(request); using (var response = await invoker.SendAsync(request, CancellationToken.None)) { Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); Assert.AreEqual("Thanks for flying Hawk", await response.Content.ReadAsAsync<string>()); Assert.IsTrue(await client.AuthenticateAsync(response)); Assert.IsNotNull(client.WebApiSpecificData); Assert.IsFalse(String.IsNullOrWhiteSpace(client.WebApiSpecificData)); var parts = client.WebApiSpecificData.Split(':'); HttpStatusCode status = (HttpStatusCode)Enum.Parse(typeof(HttpStatusCode), parts[0]); string headerName = parts[1]; string value = parts[2]; Assert.AreEqual(response.StatusCode, status); Assert.IsTrue(response.Headers.Contains(headerName)); Assert.IsTrue(response.Headers.GetValues(headerName).First().Equals(value)); } } } }