/// <summary> /// Sends an HTTP request to the inner handler to send to the server as an asynchronous operation. /// </summary> /// <param name="request">The HTTP request message to send to the server.</param> /// <param name="cancellationToken">A cancellation token to cancel operation.</param> /// <returns> /// Returns <see cref="T:System.Threading.Tasks.Task`1" />. The task object representing the asynchronous operation. /// </returns> protected override async Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { if (request.Headers.Date == null) { request.Headers.Date = DateTime.UtcNow; } // Check if we have request content, if we do then we need to add a Content-MD5 header if it doesn't already exist. // However we can't do this if we're chunked. if (request.Headers.TransferEncodingChunked == null || !(bool)request.Headers.TransferEncodingChunked) { // We can't rely on the length header, as it's not set yet. if (request.Content != null && request.Content.Headers.ContentMD5 == null) { byte[] contentHash = SignatureValidator.CalculateBodyMd5(request).Result; if (contentHash != null) { request.Content.Headers.ContentMD5 = contentHash; } } } byte[] hash = SharedKeySignature.Calculate(request, this.accountName, this.sharedSecret); request.Headers.Authorization = new AuthenticationHeaderValue( SharedKeyAuthentication.Scheme, string.Format(CultureInfo.InvariantCulture, "{0}:{1}", this.accountName, Convert.ToBase64String(hash))); // And finally hand off the request to the InnerHandler. return(await base.SendAsync(request, cancellationToken)); }
/// <summary> /// Validates the specified request. /// </summary> /// <param name="request">The <see cref="HttpRequestMessage"/> to validate</param> /// <param name="resolver">An account name to shared secret resolver.</param> /// <param name="maxAge">The maximum age of a message that will be accepted</param> /// <returns>A ClaimsPrincipal for the the identity which owns the message.</returns> public static ClaimsPrincipal Validate(HttpRequestMessage request, Func <string, byte[]> resolver, TimeSpan maxAge) { AuthenticationHeaderValue authenticationHeader = null; if (request.Headers != null) { authenticationHeader = request.Headers.Authorization; } string accountName; byte[] sentHmac; if (request.Headers != null && DateTime.Now - request.Headers.Date > maxAge) { throw new ForbiddenException("Request expired."); } if (authenticationHeader == null || string.IsNullOrWhiteSpace(authenticationHeader.Parameter) || authenticationHeader.Scheme != SharedKeyAuthentication.Scheme || !SharedKeyAuthentication.TryParse(authenticationHeader.Parameter, out accountName, out sentHmac)) { return(AnonymousPrincipal); } byte[] sharedKey = resolver(accountName); if (sharedKey == null || sharedKey.Length == 0) { // Account not found throw new UnauthorizedException(); } if (request.Headers.TransferEncodingChunked == null || !(bool)request.Headers.TransferEncodingChunked) { if (request.Content.Headers.ContentLength != null && (long)request.Content.Headers.ContentLength > 0) { var sentHash = request.Content.Headers.ContentMD5; if (sentHash == null) { throw new ForbiddenException( "Content-MD5 header must be specified when a request body is included."); } if (!CompareHash(sentHash, SignatureValidator.CalculateBodyMd5(request).Result)) { throw new PreconditionFailedException("Content-MD5 does not match the request body."); } } } // Now check the actual auth signature. byte[] calculatedHmac = SharedKeySignature.Calculate(request, accountName, sharedKey); if (!CompareHash(sentHmac, calculatedHmac)) { throw new ForbiddenException("Token validation failed."); } var claims = new List <Claim> { new Claim(ClaimTypes.Name, accountName) }; var claimsIdentity = new ClaimsIdentity(claims, SharedKeyAuthentication.Scheme); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); return(claimsPrincipal); }