public async Task MustReturn200WhenBewitIsValid() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateBewitAsync(request, 10); 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.IsFalse(response.Headers.Contains(HawkConstants.ServerAuthorizationHeaderName)); } } } }
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 MustReturn401WhenMacCreatedUsingBadSymmetricKey() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var badCredential = ServerFactory.Credentials.First(); badCredential.Key = "werxhqb98"; // Some key other than werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn var client = new HawkClient(() => badCredential); await client.CreateClientAuthorizationAsync(request); 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); } } } }
static void Main(string[] args) { var credential = new Credential() { Id = "dh37fgj492je", Algorithm = SupportedAlgorithms.SHA256, User = "******", Key = "werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn" }; string uri = "http://localhost:12345/api/values"; var client = new HttpClient(); // GET using the Authorization header var request = new HttpRequestMessage(HttpMethod.Get, uri); request.Headers.Add("X-Request-Header-To-Protect", "Swoosh"); var hawkClient = new HawkClient(() => credential); hawkClient.ApplicationSpecificData = "X-Request-Header-To-Protect:Swoosh"; // Normalized header hawkClient.CreateClientAuthorizationAsync(request).Wait(); var response = client.SendAsync(request).Result; var isAuthentic = hawkClient.AuthenticateAsync(response).Result; Console.WriteLine(isAuthentic ? response.Content.ReadAsStringAsync().Result : "Response is Tampered"); // GET using Bewit hawkClient = new HawkClient(() => credential); string bewit = hawkClient.CreateBewitAsync(new HttpRequestMessage() { RequestUri = new Uri(uri) }, lifeSeconds:60).Result; // Bewit is handed off to a client needing temporary access to the resource. var clientNeedingTempAccess = new WebClient(); var resource = clientNeedingTempAccess.DownloadString(uri + "?bewit=" + bewit); Console.WriteLine(resource); Console.Read(); }
public async Task MustReturn401WhenBodyContentIsChangedAfterMacAndHashAreComputed() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Post, URI)) { request.Content = new ObjectContent<string>("Steve", new JsonMediaTypeFormatter()); var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateClientAuthorizationAsync(request); // Hash is now based on "Steve" // Changing body to "Stephen" request.Content = new ObjectContent<string>("Stephen", new JsonMediaTypeFormatter()); 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); } } } }
public async Task MustReturn401WhenQueryStringIsChangedAfterMacIsComputed() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI + "?firstname=Louis&&lastname=Phillipe")) { var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateClientAuthorizationAsync(request); // Hash is now based on ?firstname=Louis&&lastname=Phillipe // Changing query string to ?firstname=Luis&&lastname=Phillip request.RequestUri = new Uri(URI + "?firstname=Luis&&lastname=Phillip"); 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); } } } }
private static async Task MustReturn401WhenHawParameterIsTampered(string tamperedAttribute) { using (var invoker = new HttpMessageInvoker(ServerFactory.Create())) { using (var request = new HttpRequestMessage(HttpMethod.Post, URI)) { request.Content = new ObjectContent<string>("Steve", new JsonMediaTypeFormatter()); var client = new HawkClient(() => ServerFactory.DefaultCredential); client.ApplicationSpecificData = "world peace"; await client.CreateClientAuthorizationAsync(request); string goodParameter = request.Headers.Authorization.Parameter; // For example, ts="1370846381" becomes ts="bad1370846381" string tampered = goodParameter.Replace((tamperedAttribute + "=\""), (tamperedAttribute + "=\"bad")); request.Headers.Authorization = new AuthenticationHeaderValue("hawk", tampered); 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); } } } }
public async Task MustReturn401WhenHawkParameterHasDuplicateAttribute() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateClientAuthorizationAsync(request); string goodParameter = request.Headers.Authorization.Parameter; string duplicates = goodParameter + "," + goodParameter; request.Headers.Authorization = new AuthenticationHeaderValue("hawk", duplicates); 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.IsTrue(String.IsNullOrEmpty(response.Headers.WwwAuthenticate.FirstOrDefault().Parameter)); } } } }
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 MustReturn401WhenUriIsTampered() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); string bewit = await client.CreateBewitAsync(request, 10); var parts = bewit.ToUtf8StringFromBase64Url().Split('\\'); string id = parts[0]; string timestamp = parts[1]; string mac = parts[2]; string ext = parts[3]; string tamperedBewit = String.Format(@"{0}\{1}\{2}\{3}", "Id of my choice", timestamp, mac, ext); tamperedBewit = tamperedBewit.ToBytesFromUtf8().ToBase64UrlString(); using (var freshRequest = new HttpRequestMessage()) { string tamperedUri = URI + "/1"; freshRequest.RequestUri = new Uri(tamperedUri + "?bewit=" + tamperedBewit); using (var response = await invoker.SendAsync(freshRequest, CancellationToken.None)) { Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); } } } } }
public async Task MustThrowInvalidOperationExceptionWhenBewitUsedWithPost() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Post, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateBewitAsync(request, 10); await invoker.SendAsync(request, CancellationToken.None); } } }
public async Task MustReturn401WhenBewitHasExpired() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateBewitAsync(request, 0); // no life in it using (var response = await invoker.SendAsync(request, CancellationToken.None)) { Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); } } } }
private static async Task MustReturn401WhenHawParameterHasMissingAttribute(string missingAttribute) { using (var invoker = new HttpMessageInvoker(ServerFactory.Create())) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var client = new HawkClient(() => ServerFactory.DefaultCredential); await client.CreateClientAuthorizationAsync(request); string goodParameter = request.Headers.Authorization.Parameter; string incompleteParameter = goodParameter.Split(',') .Where(t => !t.Trim().StartsWith(missingAttribute)) .Aggregate((a, b) => a + "," + b).Trim(); request.Headers.Authorization = new AuthenticationHeaderValue("hawk", incompleteParameter); 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.IsTrue(String.IsNullOrEmpty(response.Headers.WwwAuthenticate.FirstOrDefault().Parameter)); } } } }
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 MustReturn401WhenHawkCredentialIdIsInvalid() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var badCredential = ServerFactory.Credentials.First(); badCredential.Id = "SomeIdOtherThan-dh37fgj492je"; // Some id other than dh37fgj492je var client = new HawkClient(() => badCredential); await client.CreateClientAuthorizationAsync(request); 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.IsTrue(String.IsNullOrEmpty(response.Headers.WwwAuthenticate.FirstOrDefault().Parameter)); } } } }
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)); } } } }
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 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 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 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 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)); } } } }
public async Task MustReturn401WhenProtectedCustomHeaderIsTampered() { Func<HttpRequestMessage, string, bool> verificationCallback = (request, appSpecificData) => { // Client and server decided that appSpecificData will be // header name:header value, like so: X-Request-Header: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(null, verificationCallback); using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { // Sensitive header to protect but simulate tampering by changing the value request.Headers.Add("X-Request-Header", "Tampered Swoosh"); var client = new HawkClient(() => ServerFactory.DefaultCredential); // Put the sensitive header in the earlier decided format in the ApplicationSpecificData property client.ApplicationSpecificData = "X-Request-Header:Swoosh"; await client.CreateClientAuthorizationAsync(request); 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); } } } }