/*
* Copyright 2010-2013 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 Amazon.Util;
using System.Globalization;
using Amazon.Runtime.Internal.Util;
namespace Amazon.Runtime.Internal.Auth
{
public class AWS3Signer : AbstractAWSSigner
{
private const string HTTP_SCHEME = "AWS3";
private const string HTTPS_SCHEME = "AWS3-HTTPS";
private const string Slash = "/";
private bool UseAws3Https { get; set; }
public AWS3Signer(bool useAws3Https)
{
UseAws3Https = useAws3Https;
}
public AWS3Signer()
: this(false)
{
}
public override ClientProtocol Protocol
{
get { return ClientProtocol.RestProtocol; }
}
///
/// Signs the specified request with the AWS3 signing protocol by using the
/// AWS account credentials given in the method parameters.
///
/// The AWS public key
/// The AWS secret key used to sign the request in clear text
/// Request metrics
/// The configuration that specifies which hashing algorithm to use
/// The request to have the signature compute for
/// If any problems are encountered while signing the request
public override void Sign(IRequest request, IClientConfig clientConfig, RequestMetrics metrics, string awsAccessKeyId, string awsSecretAccessKey)
{
var signer = SelectSigner(request, clientConfig);
var useV4 = signer is AWS4Signer;
if (useV4)
signer.Sign(request, clientConfig, metrics, awsAccessKeyId, awsSecretAccessKey);
else
{
if (UseAws3Https)
{
SignHttps(request, clientConfig, metrics, awsAccessKeyId, awsSecretAccessKey);
}
else
{
SignHttp(request, metrics, awsAccessKeyId, awsSecretAccessKey);
}
}
}
private static void SignHttps(IRequest request, IClientConfig clientConfig, RequestMetrics metrics, string awsAccessKeyId, string awsSecretAccessKey)
{
string nonce = Guid.NewGuid().ToString();
string date = AWSSDKUtils.FormattedCurrentTimestampRFC822;
string stringToSign;
stringToSign = date + nonce;
metrics.AddProperty(Metric.StringToSign, stringToSign);
string signature = ComputeHash(stringToSign, awsSecretAccessKey, clientConfig.SignatureMethod);
StringBuilder builder = new StringBuilder();
builder.Append(HTTPS_SCHEME).Append(" ");
builder.Append("AWSAccessKeyId=" + awsAccessKeyId + ",");
builder.Append("Algorithm=" + clientConfig.SignatureMethod.ToString() + ",");
builder.Append("SignedHeaders=x-amz-date;x-amz-nonce,");
builder.Append("Signature=" + signature);
request.Headers[HeaderKeys.XAmzAuthorizationHeader] = builder.ToString();
request.Headers[HeaderKeys.XAmzNonceHeader] = nonce;
request.Headers[HeaderKeys.XAmzDateHeader] = date;
}
private static void SignHttp(IRequest request, RequestMetrics metrics, string awsAccessKeyId, string awsSecretAccessKey)
{
SigningAlgorithm algorithm = SigningAlgorithm.HmacSHA256;
string nonce = Guid.NewGuid().ToString();
string date = AWSSDKUtils.FormattedCurrentTimestampRFC822;
bool isHttps = IsHttpsRequest(request);
// Temporarily disabling the AWS3 HTTPS signing scheme and only using AWS3 HTTP
isHttps = false;
request.Headers[HeaderKeys.DateHeader] = date;
request.Headers[HeaderKeys.XAmzDateHeader] = date;
// Clear out existing auth header (can be there if retry)
request.Headers.Remove(HeaderKeys.XAmzAuthorizationHeader);
// AWS3 HTTP requires that we sign the Host header
// so we have to have it in the request by the time we sign.
string hostHeader = request.Endpoint.Host;
if (!request.Endpoint.IsDefaultPort)
hostHeader += ":" + request.Endpoint.Port;
request.Headers[HeaderKeys.HostHeader] = hostHeader;
byte[] bytesToSign = null;
string stringToSign;
if (isHttps)
{
request.Headers[HeaderKeys.XAmzNonceHeader] = nonce;
stringToSign = date + nonce;
bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
}
else
{
stringToSign = request.HttpMethod + "\n"
+ GetCanonicalizedResourcePath(request) + "\n"
+ GetCanonicalizedQueryString(request.Parameters) + "\n"
+ GetCanonicalizedHeadersForStringToSign(request) + "\n"
+ GetRequestPayload(request);
bytesToSign = CryptoUtilFactory.CryptoInstance.ComputeSHA256Hash(Encoding.UTF8.GetBytes(stringToSign));
}
metrics.AddProperty(Metric.StringToSign, stringToSign);
string signature = ComputeHash(bytesToSign, awsSecretAccessKey, algorithm);
StringBuilder builder = new StringBuilder();
builder.Append(isHttps ? HTTPS_SCHEME : HTTP_SCHEME);
builder.Append(" ");
builder.Append("AWSAccessKeyId=" + awsAccessKeyId + ",");
builder.Append("Algorithm=" + algorithm.ToString() + ",");
if (!isHttps)
{
builder.Append(GetSignedHeadersComponent(request) + ",");
}
builder.Append("Signature=" + signature);
string authorizationHeader = builder.ToString();
request.Headers[HeaderKeys.XAmzAuthorizationHeader] = authorizationHeader;
}
#region Http signing helpers
private static string GetCanonicalizedResourcePath(IRequest request)
{
var resourcePath = request.ResourcePath;
if (request.Endpoint != null)
{
var path = request.Endpoint.AbsolutePath;
if (string.IsNullOrEmpty(path) || string.Equals(path, Slash, StringComparison.Ordinal))
{
path = string.Empty;
}
if (resourcePath?.StartsWith(Slash, StringComparison.Ordinal) == true)
{
resourcePath = resourcePath.Substring(1);
}
if (!string.IsNullOrEmpty(resourcePath))
{
path = path + Slash + resourcePath;
}
resourcePath = path;
}
if (string.IsNullOrEmpty(resourcePath))
{
return Slash;
}
if(request.MarshallerVersion >= 2)
{
return AWSSDKUtils.ResolveResourcePath(resourcePath, request.PathResources);
}
return AWSSDKUtils.UrlEncode(resourcePath, true);
}
private static bool IsHttpsRequest(IRequest request)
{
string protocol = request.Endpoint.Scheme;
if (protocol.Equals("http", StringComparison.OrdinalIgnoreCase))
{
return false;
}
else if (protocol.Equals("https", StringComparison.OrdinalIgnoreCase))
{
return true;
}
else
{
throw new AmazonServiceException(
"Unknown request endpoint protocol encountered while signing request: " + protocol);
}
}
private static string GetCanonicalizedQueryString(IDictionary parameters)
{
IDictionary sorted =
new SortedDictionary(parameters, StringComparer.Ordinal);
StringBuilder builder = new StringBuilder();
foreach (var pair in sorted)
{
if (pair.Value != null)
{
string key = pair.Key;
string value = pair.Value;
builder.Append(AWSSDKUtils.UrlEncode(key, false));
builder.Append("=");
builder.Append(AWSSDKUtils.UrlEncode(value, false));
builder.Append("&");
}
}
string result = builder.ToString();
return (string.IsNullOrEmpty(result) ? string.Empty : result.Substring(0, result.Length - 1));
}
private static string GetRequestPayload(IRequest request)
{
if (request.Content == null)
return string.Empty;
return Encoding.UTF8.GetString(request.Content, 0, request.Content.Length);
}
private static string GetSignedHeadersComponent(IRequest request)
{
StringBuilder builder = new StringBuilder();
builder.Append("SignedHeaders=");
bool first = true;
foreach (string header in GetHeadersForStringToSign(request))
{
if (!first) builder.Append(";");
builder.Append(header);
first = false;
}
return builder.ToString();
}
private static List GetHeadersForStringToSign(IRequest request)
{
List headersToSign = new List();
foreach (var entry in request.Headers) {
string key = entry.Key;
if (key.StartsWith("x-amz", StringComparison.OrdinalIgnoreCase)
|| key.Equals("content-encoding", StringComparison.OrdinalIgnoreCase)
|| key.Equals("host", StringComparison.OrdinalIgnoreCase))
{
headersToSign.Add(key);
}
}
headersToSign.Sort(StringComparer.OrdinalIgnoreCase);
return headersToSign;
}
private static string GetCanonicalizedHeadersForStringToSign(IRequest request)
{
List headersToSign = GetHeadersForStringToSign(request);
for (int i = 0; i < headersToSign.Count; i++)
{
headersToSign[i] = headersToSign[i].ToLowerInvariant();
}
SortedDictionary sortedHeaderMap = new SortedDictionary();
foreach (var entry in request.Headers)
{
if (headersToSign.Contains(entry.Key.ToLowerInvariant()))
{
sortedHeaderMap[entry.Key] = entry.Value;
}
}
StringBuilder builder = new StringBuilder();
foreach (var entry in sortedHeaderMap)
{
builder.Append(entry.Key.ToLowerInvariant());
builder.Append(":");
builder.Append(entry.Value);
builder.Append("\n");
}
return builder.ToString();
}
#endregion
}
internal class AWS3HTTPSigner : AWS3Signer
{
public AWS3HTTPSigner()
: base(false)
{
}
}
}