/// <summary> /// Returns true, if there is a WWW-Authenticate header containing ts and tsm but mac /// computed for ts does not match tsm, indicating possible tampering. Otherwise, returns false. /// This method also sets the compensation field so that the timestamp in the subsequent requests /// are adjusted to reduce the clock skew. /// </summary> private bool IsTimestampResponseTampered(ArtifactsContainer artifacts, IResponseMessage response) { var wwwHeader = response.WwwAuthenticate; if (wwwHeader != null) { string parameter = wwwHeader.Parameter; ArtifactsContainer timestampArtifacts; if (!String.IsNullOrWhiteSpace(parameter) && ArtifactsContainer.TryParse(parameter, out timestampArtifacts)) { var ts = new NormalizedTimestamp(timestampArtifacts.Timestamp, options.CredentialsCallback(), options.LocalTimeOffsetMillis); if (!ts.IsValid(timestampArtifacts.TimestampMac)) { return(true); } lock (myPrecious) HawkClient.CompensatorySeconds = (int)(timestampArtifacts.Timestamp - DateTime.UtcNow.ToUnixTime()); Tracing.Information("HawkClient.CompensatorySeconds set to " + HawkClient.CompensatorySeconds); } } return(false); }
/// <summary> /// Returns true, if there is a WWW-Authenticate header containing ts and tsm but mac /// computed for ts does not match tsm, indicating possible tampering. Otherwise, returns false. /// This method also sets the compensation field so that the timestamp in the subsequent requests /// are adjusted to reduce the clock skew. /// </summary> private bool IsTimestampResponseTampered(ArtifactsContainer artifacts, HttpResponseMessage response) { if (response.Headers.WwwAuthenticate != null) { var wwwHeader = response.Headers.WwwAuthenticate.FirstOrDefault(); if (wwwHeader != null && wwwHeader.Scheme.ToLower() == HawkConstants.Scheme) { string parameter = wwwHeader.Parameter; ArtifactsContainer timestampArtifacts; if (!String.IsNullOrWhiteSpace(parameter) && ArtifactsContainer.TryParse(parameter, out timestampArtifacts)) { var ts = new NormalizedTimestamp(timestampArtifacts.Timestamp, credentialFunc()); if (!ts.IsValid(timestampArtifacts.TimestampMac)) { return(true); } lock (myPrecious) HawkClient.CompensatorySeconds = (int)(timestampArtifacts.Timestamp - DateTime.UtcNow.ToUnixTime()); Tracing.Information("HawkClient.CompensatorySeconds set to " + HawkClient.CompensatorySeconds); } } } return(false); }
/// <summary> /// Returns true if the server response HMAC cannot be validated, indicating possible tampering. /// </summary> private async Task <bool> IsResponseTamperedAsync(ArtifactsContainer artifacts, Cryptographer crypto, HttpResponseMessage response) { if (response.Headers.Contains(HawkConstants.ServerAuthorizationHeaderName)) { string header = response.Headers.GetValues(HawkConstants.ServerAuthorizationHeaderName).FirstOrDefault(); if (!String.IsNullOrWhiteSpace(header) && header.Substring(0, HawkConstants.Scheme.Length).ToLower() == HawkConstants.Scheme) { ArtifactsContainer serverAuthorizationArtifacts; if (ArtifactsContainer.TryParse(header.Substring(HawkConstants.Scheme.Length + " ".Length), out serverAuthorizationArtifacts)) { // To validate response, ext, hash, and mac in the request artifacts must be // replaced with the ones from the server. artifacts.ApplicationSpecificData = serverAuthorizationArtifacts.ApplicationSpecificData; artifacts.PayloadHash = serverAuthorizationArtifacts.PayloadHash; artifacts.Mac = serverAuthorizationArtifacts.Mac; bool isValid = await crypto.IsSignatureValidAsync(response.Content); if (isValid) { this.WebApiSpecificData = serverAuthorizationArtifacts.ApplicationSpecificData; } return(!isValid); } } } return(true); // Missing header means possible tampered response (to err on the side of caution). }
public async Task TimestampInSubsequentRequestMustBeAdjustedBasedOnTimestampInWwwAuthenticateHeaderOfPrevious() { using (var invoker = new HttpMessageInvoker(server)) { using (var request = new HttpRequestMessage(HttpMethod.Get, URI)) { var client = ClientFactory.Create(); await client.CreateClientAuthorizationAsync(new WebApiRequestMessage(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(new WebApiResponseMessage(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(new WebApiRequestMessage(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); } } } } }
/// <summary> /// Returns true if the server response HMAC cannot be validated, indicating possible tampering. /// </summary> private async Task <bool> IsResponseTamperedAsync(ArtifactsContainer artifacts, Cryptographer crypto, IResponseMessage response) { if (response.Headers.ContainsKey(HawkConstants.ServerAuthorizationHeaderName)) { string header = response.Headers[HawkConstants.ServerAuthorizationHeaderName].FirstOrDefault(); if (!String.IsNullOrWhiteSpace(header) && header.Substring(0, HawkConstants.Scheme.Length).ToLower() == HawkConstants.Scheme) { ArtifactsContainer serverAuthorizationArtifacts; if (ArtifactsContainer.TryParse(header.Substring(HawkConstants.Scheme.Length + " ".Length), out serverAuthorizationArtifacts)) { // To validate response, ext, hash, and mac in the request artifacts must be // replaced with the ones from the server. artifacts.ApplicationSpecificData = serverAuthorizationArtifacts.ApplicationSpecificData; artifacts.PayloadHash = serverAuthorizationArtifacts.PayloadHash; artifacts.Mac = serverAuthorizationArtifacts.Mac; // Response body is needed only if payload hash is present in the server response. string body = null; if (artifacts.PayloadHash != null && artifacts.PayloadHash.Length > 0) { body = await response.ReadBodyAsStringAsync(); } bool isValid = crypto.IsSignatureValid(body, response.ContentType, isServerAuthorization: true); if (isValid) { string appSpecificData = serverAuthorizationArtifacts.ApplicationSpecificData; isValid = options.VerificationCallback == null || options.VerificationCallback(response, appSpecificData); } return(!isValid); } } } return(true); // Missing header means possible tampered response (to err on the side of caution). }