/* * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ using System; using System.Collections.Generic; using System.Text; using System.Linq; using Amazon.Util; using Amazon.Runtime.Internal.Util; #pragma warning disable 1591 namespace Amazon.Runtime.Internal.Auth { public class S3Signer : AbstractAWSSigner { public delegate void RegionDetectionUpdater(IRequest request); private readonly bool _useSigV4; private readonly RegionDetectionUpdater _regionDetector; /// /// S3 signer constructor /// public S3Signer() : this(true, null) { } /// /// S3 signer constructor /// public S3Signer(bool useSigV4, RegionDetectionUpdater regionDetector) { _useSigV4 = useSigV4; _regionDetector = regionDetector; } public override ClientProtocol Protocol { get { return ClientProtocol.RestProtocol; } } public override void Sign(IRequest request, IClientConfig clientConfig, RequestMetrics metrics, string awsAccessKeyId, string awsSecretAccessKey) { var signer = SelectSigner(this, _useSigV4, request, clientConfig); var aws4Signer = signer as AWS4Signer; var useV4 = aws4Signer != null; if (useV4) { _regionDetector?.Invoke(request); var signingResult = aws4Signer.SignRequest(request, clientConfig, metrics, awsAccessKeyId, awsSecretAccessKey); request.Headers[HeaderKeys.AuthorizationHeader] = signingResult.ForAuthorizationHeader; if (request.UseChunkEncoding) request.AWS4SignerResult = signingResult; } else { SignRequest(request, metrics, awsAccessKeyId, awsSecretAccessKey); } } public static void SignRequest(IRequest request, RequestMetrics metrics, string awsAccessKeyId, string awsSecretAccessKey) { request.Headers[HeaderKeys.XAmzDateHeader] = AWSSDKUtils.FormattedCurrentTimestampRFC822; var stringToSign = BuildStringToSign(request); metrics.AddProperty(Metric.StringToSign, stringToSign); var auth = CryptoUtilFactory.CryptoInstance.HMACSign(stringToSign, awsSecretAccessKey, SigningAlgorithm.HmacSHA1); var authorization = string.Concat("AWS ", awsAccessKeyId, ":", auth); request.Headers[HeaderKeys.AuthorizationHeader] = authorization; } static string BuildStringToSign(IRequest request) { var sb = new StringBuilder("", 256); sb.Append(request.HttpMethod); sb.Append("\n"); var headers = request.Headers; var parameters = request.Parameters; if (headers != null) { string value = null; if (headers.ContainsKey(HeaderKeys.ContentMD5Header) && !String.IsNullOrEmpty(value = headers[HeaderKeys.ContentMD5Header])) { sb.Append(value); } sb.Append("\n"); if (parameters.ContainsKey("ContentType")) { sb.Append(parameters["ContentType"]); } else if (headers.ContainsKey(HeaderKeys.ContentTypeHeader)) { sb.Append(headers[HeaderKeys.ContentTypeHeader]); } sb.Append("\n"); } else { // The headers are null, but we still need to append // the 2 newlines that are required by S3. // Without these, S3 rejects the signature. sb.Append("\n\n"); } if (parameters.ContainsKey("Expires")) { sb.Append(parameters["Expires"]); if (headers != null) headers.Remove(HeaderKeys.XAmzDateHeader); } IDictionary headersAndParameters = new Dictionary(headers); foreach (var pair in parameters) { // If there's a key that's both a header and a parameter then the header will take precedence. if (!headersAndParameters.ContainsKey(pair.Key)) headersAndParameters.Add(pair.Key, pair.Value); } sb.Append("\n"); sb.Append(BuildCanonicalizedHeaders(headersAndParameters)); var canonicalizedResource = BuildCanonicalizedResource(request); if (!string.IsNullOrEmpty(canonicalizedResource)) { sb.Append(canonicalizedResource); } return sb.ToString(); } static string BuildCanonicalizedHeaders(IDictionary headers) { var sb = new StringBuilder(256); foreach (var key in headers.Keys.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)) { var lowerKey = key.ToLowerInvariant(); if (!lowerKey.StartsWith("x-amz-", StringComparison.Ordinal)) continue; sb.Append(String.Concat(lowerKey, ":", headers[key], "\n")); } return sb.ToString(); } private static readonly HashSet SignableParameters = new HashSet ( new[] { "response-content-type", "response-content-language", "response-expires", "response-cache-control", "response-content-disposition", "response-content-encoding" }, StringComparer.OrdinalIgnoreCase ); //This is a list of sub resources that S3 does not expect to be signed //and thus have to be excluded from the signer. This is only applicable to S3SigV2 signer //id:- subresource belongs to analytics,inventory and metrics S3 APIs private static readonly HashSet SubResourcesSigningExclusion = new HashSet ( new[] { "id" }, StringComparer.OrdinalIgnoreCase ); static string BuildCanonicalizedResource(IRequest request) { // CanonicalResourcePrefix will hold the bucket name if we switched to virtual host addressing // during request preprocessing (where it would have been removed from ResourcePath) var sb = new StringBuilder(request.CanonicalResourcePrefix); sb.Append(!string.IsNullOrEmpty(request.ResourcePath) ? request.MarshallerVersion >= 2 ? AWSSDKUtils.ResolveResourcePath(request.ResourcePath, request.PathResources) : AWSSDKUtils.UrlEncode(request.ResourcePath, true) : "/"); // form up the set of all subresources and specific query parameters that must be // included in the canonical resource, then append them ordered by key to the // canonicalization var resourcesToSign = new Dictionary(StringComparer.OrdinalIgnoreCase); if (request.SubResources.Count > 0) { foreach (var subResource in request.SubResources) { if (!SubResourcesSigningExclusion.Contains(subResource.Key)) { resourcesToSign.Add(subResource.Key, subResource.Value); } } } if (request.Parameters.Count > 0) { var parameters = request.ParameterCollection.GetSortedParametersList(); foreach (var parameter in parameters) { if (parameter.Value != null && SignableParameters.Contains(parameter.Key)) { resourcesToSign.Add(parameter.Key, parameter.Value); } } } var delim = "?"; List> resources = new List>(); foreach (var kvp in resourcesToSign) { resources.Add(kvp); } resources.Sort((firstPair, nextPair) => { return string.CompareOrdinal(firstPair.Key, nextPair.Key); }); foreach (var resourceToSign in resources) { sb.AppendFormat("{0}{1}", delim, resourceToSign.Key); if (resourceToSign.Value != null) sb.AppendFormat("={0}", resourceToSign.Value); delim = "&"; } return sb.ToString(); } } }