/// <summary> /// Computes an AWS4 signature for a request, ready for inclusion as an /// 'Authorization' header. /// </summary> /// <param name="headers"> /// The request headers; 'Host' and 'X-Amz-Date' will be added to this set. /// </param> /// <param name="queryParameters"> /// Any query parameters that will be added to the endpoint. The parameters /// should be specified in canonical format. /// </param> /// <param name="bodyHash"> /// Precomputed SHA256 hash of the request body content; this value should also /// be set as the header 'X-Amz-Content-SHA256' for non-streaming uploads. /// </param> /// <param name="awsAccessKey"> /// The user's AWS Access Key. /// </param> /// <param name="awsSecretKey"> /// The user's AWS Secret Key. /// </param> /// <returns> /// The computed authorization string for the request. This value needs to be set as the /// header 'Authorization' on the subsequent HTTP request. /// </returns> public string ComputeSignature(IDictionary <string, string> headers, string queryParameters, string bodyHash, string awsAccessKey, string awsSecretKey) { // first get the date and time for the subsequent request, and convert to ISO 8601 format // for use in signature generation var requestDateTime = DateTime.UtcNow; var dateTimeStamp = requestDateTime.ToString(ISO8601BasicFormat, CultureInfo.InvariantCulture); // update the headers with required 'x-amz-date' and 'host' values headers.Add(X_Amz_Date, dateTimeStamp); var hostHeader = EndpointUri.Host; if (!EndpointUri.IsDefaultPort) { hostHeader += ":" + EndpointUri.Port; } headers.Add("Host", hostHeader); // canonicalize the headers; we need the set of header names as well as the // names and values to go into the signature process var canonicalizedHeaderNames = CanonicalizeHeaderNames(headers); var canonicalizedHeaders = CanonicalizeHeaders(headers); // if any query string parameters have been supplied, canonicalize them // (note this sample assumes any required url encoding has been done already) var canonicalizedQueryParameters = string.Empty; if (!string.IsNullOrEmpty(queryParameters)) { var paramDictionary = queryParameters.Split('&').Select(p => p.Split('=')) .ToDictionary(nameval => nameval[0], nameval => nameval.Length > 1 ? nameval[1] : ""); var sb = new StringBuilder(); var paramKeys = new List <string>(paramDictionary.Keys); paramKeys.Sort(StringComparer.Ordinal); foreach (var p in paramKeys) { if (sb.Length > 0) { sb.Append("&"); } sb.AppendFormat("{0}={1}", p, paramDictionary[p]); } canonicalizedQueryParameters = sb.ToString(); } // canonicalize the various components of the request var canonicalRequest = CanonicalizeRequest(EndpointUri, HttpMethod, canonicalizedQueryParameters, canonicalizedHeaderNames, canonicalizedHeaders, bodyHash); // generate a hash of the canonical request, to go into signature computation var canonicalRequestHashBytes = CanonicalRequestHashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(canonicalRequest)); // construct the string to be signed var stringToSign = new StringBuilder(); var dateStamp = requestDateTime.ToString(DateStringFormat, CultureInfo.InvariantCulture); var scope = string.Format("{0}/{1}/{2}/{3}", dateStamp, Region, Service, TERMINATOR); stringToSign.AppendFormat("{0}-{1}\n{2}\n{3}\n", SCHEME, ALGORITHM, dateTimeStamp, scope); stringToSign.Append(ToHexString(canonicalRequestHashBytes, true)); // compute the signing key var kha = new HMACSHA256(); kha.Key = DeriveSigningKey(HMACSHA256, awsSecretKey, Region, dateStamp, Service); // compute the AWS4 signature and return it var signature = kha.ComputeHash(Encoding.UTF8.GetBytes(stringToSign.ToString())); var signatureString = ToHexString(signature, true); var authString = new StringBuilder(); authString.AppendFormat("{0}-{1} ", SCHEME, ALGORITHM); authString.AppendFormat("Credential={0}/{1}, ", awsAccessKey, scope); authString.AppendFormat("SignedHeaders={0}, ", canonicalizedHeaderNames); authString.AppendFormat("Signature={0}", signatureString); var authorization = authString.ToString(); return(authorization); }
/// <summary> /// Returns a chunk for upload consisting of the signed 'header' or chunk /// prefix plus the user data. The signature of the chunk incorporates the /// signature of the previous chunk (or, if the first chunk, the signature /// of the headers portion of the request). /// </summary> /// <param name="userDataLen"> /// The length of the user data contained in userData /// </param> /// <param name="userData"> /// Contains the user data to be sent in the upload chunk /// </param> /// <returns> /// A new buffer of data for upload containing the chunk header plus user data /// </returns> public byte[] ConstructSignedChunk(long userDataLen, byte[] userData) { // to keep our computation routine signatures simple, if the userData // buffer contains less data than it could, shrink it. Note the special case // to handle the requirement that we send an empty chunk to complete // our chunked upload. byte[] dataToChunk; if (userDataLen == 0) { dataToChunk = FINAL_CHUNK; } else { if (userDataLen < userData.Length) { // shrink the chunkdata to fit dataToChunk = new byte[userDataLen]; Array.Copy(userData, 0, dataToChunk, 0, userDataLen); } else { dataToChunk = userData; } } var chunkHeader = new StringBuilder(); // start with size of user data chunkHeader.Append(dataToChunk.Length.ToString("X")); // nonsig-extension; we have none in these samples const string nonsigExtension = ""; // if this is the first chunk, we package it with the signing result // of the request headers, otherwise we use the cached signature // of the previous chunk // sig-extension var chunkStringToSign = CHUNK_STRING_TO_SIGN_PREFIX + "\n" + DateTimeStamp + "\n" + Scope + "\n" + LastComputedSignature + "\n" + ToHexString(CanonicalRequestHashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(nonsigExtension)), true) + "\n" + ToHexString(CanonicalRequestHashAlgorithm.ComputeHash(dataToChunk), true); Logger.LogDebug($"\nChunkStringToSign:\n{chunkStringToSign}"); // compute the V4 signature for the chunk var chunkSignature = ToHexString(ComputeKeyedHash("HMACSHA256", SigningKey, Encoding.UTF8.GetBytes(chunkStringToSign)), true); Logger.LogDebug($"\nChunkSignature:\n{chunkSignature}"); // cache the signature to include with the next chunk's signature computation this.LastComputedSignature = chunkSignature; // construct the actual chunk, comprised of the non-signed extensions, the // 'headers' we just signed and their signature, plus a newline then copy // that plus the user's data to a payload to be written to the request stream chunkHeader.Append(nonsigExtension + CHUNK_SIGNATURE_HEADER + chunkSignature); chunkHeader.Append(CLRF); Logger.LogDebug($"\nChunkHeader:\n{chunkHeader}"); try { var header = Encoding.UTF8.GetBytes(chunkHeader.ToString()); var trailer = Encoding.UTF8.GetBytes(CLRF); var signedChunk = new byte[header.Length + dataToChunk.Length + trailer.Length]; Array.Copy(header, 0, signedChunk, 0, header.Length); Array.Copy(dataToChunk, 0, signedChunk, header.Length, dataToChunk.Length); Array.Copy(trailer, 0, signedChunk, header.Length + dataToChunk.Length, trailer.Length); // this is the total data for the chunk that will be sent to the request stream return(signedChunk); } catch (Exception e) { throw new Exception("Unable to sign the chunked data. " + e.Message, e); } }
/// <summary> /// Computes an AWS4 signature for a request, ready for inclusion as an /// 'Authorization' header. /// </summary> /// <param name="headers"> /// The request headers; 'Host' and 'X-Amz-Date' will be added to this set. /// </param> /// <param name="queryParameters"> /// Any query parameters that will be added to the endpoint. The parameters /// should be specified in canonical format. /// </param> /// <param name="bodyHash"> /// Precomputed SHA256 hash of the request body content; this value should also /// be set as the header 'X-Amz-Content-SHA256' for non-streaming uploads. /// </param> /// <param name="awsAccessKey"> /// The user's AWS Access Key. /// </param> /// <param name="awsSecretKey"> /// The user's AWS Secret Key. /// </param> /// <returns> /// The computed authorization string for the request. This value needs to be set as the /// header 'Authorization' on the subsequent HTTP request. /// </returns> public string ComputeSignature(IHeaderDictionary headers, string dateTimeStamp, string dateStamp, Uri endpointUri, string httpMethod, string queryParameters, string awsSecretKey, string canonicalizedHeaderNames) { var canonicalizedHeaders = CanonicalizeHeaders(headers, canonicalizedHeaderNames); // if any query string parameters have been supplied, canonicalize them // (note this sample assumes any required url encoding has been done already) var canonicalizedQueryParameters = string.Empty; if (!string.IsNullOrEmpty(queryParameters)) { var paramDictionary = queryParameters.Split('&').Select(p => p.Split('=')) .ToDictionary(nameval => nameval[0], nameval => nameval.Length > 1 ? nameval[1] : ""); var sb = new StringBuilder(); var paramKeys = new List <string>(paramDictionary.Keys); paramKeys.Sort(StringComparer.Ordinal); foreach (var p in paramKeys) { if (sb.Length > 0) { sb.Append("&"); } sb.AppendFormat("{0}={1}", p, paramDictionary[p]); } canonicalizedQueryParameters = sb.ToString(); } // canonicalize the various components of the request var canonicalRequest = CanonicalizeRequest(endpointUri, httpMethod, canonicalizedQueryParameters, canonicalizedHeaderNames, canonicalizedHeaders, EMPTY_BODY_SHA256); // generate a hash of the canonical request, to go into signature computation var canonicalRequestHashBytes = CanonicalRequestHashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(canonicalRequest)); // construct the string to be signed var stringToSign = new StringBuilder(); //var dateStamp = requestDateTime.ToString(DateStringFormat, CultureInfo.InvariantCulture); var scope = string.Format("{0}/{1}/{2}/{3}", dateStamp, Region, Service, TERMINATOR); stringToSign.AppendFormat("{0}-{1}\n{2}\n{3}\n", SCHEME, ALGORITHM, dateTimeStamp, scope); stringToSign.Append(ToHexString(canonicalRequestHashBytes, true)); // compute the signing key var kha = KeyedHashAlgorithm.Create(HMACSHA256); kha.Key = DeriveSigningKey(HMACSHA256, awsSecretKey, Region, dateStamp, Service); // compute the AWS4 signature and return it var signature = kha.ComputeHash(Encoding.UTF8.GetBytes(stringToSign.ToString())); var signatureString = ToHexString(signature, true); return(signatureString); }
/// <summary> /// Computes an AWS4 authorization for a request, suitable for embedding /// in query parameters. /// </summary> /// <param name="headers"> /// The request headers; 'Host' and 'X-Amz-Date' will be added to this set. /// </param> /// <param name="queryParameters"> /// Any query parameters that will be added to the endpoint. The parameters /// should be specified in canonical format. /// </param> /// <param name="bodyHash"> /// Precomputed SHA256 hash of the request body content; this value should also /// be set as the header 'X-Amz-Content-SHA256' for non-streaming uploads. /// </param> /// <param name="awsAccessKey"> /// The user's AWS Access Key. /// </param> /// <param name="awsSecretKey"> /// The user's AWS Secret Key. /// </param> /// <returns> /// The string expressing the Signature V4 components to add to query parameters. /// </returns> public string ComputeSignature(IDictionary <string, string> headers, string queryParameters, string bodyHash, string awsAccessKey, string awsSecretKey) { // first get the date and time for the subsequent request, and convert to ISO 8601 format // for use in signature generation var requestDateTime = DateTime.UtcNow; var dateTimeStamp = requestDateTime.ToString(ISO8601BasicFormat, CultureInfo.InvariantCulture); // extract the host portion of the endpoint to include in the signature calculation, // unless already set if (!headers.ContainsKey("Host")) { var hostHeader = EndpointUri.Host; if (!EndpointUri.IsDefaultPort) { hostHeader += ":" + EndpointUri.Port; } headers.Add("Host", hostHeader); } var dateStamp = requestDateTime.ToString(DateStringFormat, CultureInfo.InvariantCulture); var scope = string.Format("{0}/{1}/{2}/{3}", dateStamp, Region, Service, TERMINATOR); // canonicalized headers need to be expressed in the query // parameters processed in the signature var canonicalizedHeaderNames = CanonicalizeHeaderNames(headers); var canonicalizedHeaders = CanonicalizeHeaders(headers); // reform the query parameters to (a) add the parameters required for // Signature V4 and (b) canonicalize the set before they go into the // signature calculation. Note that this assumes parameter names and // values added outside this routine are already url encoded var paramDictionary = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); if (!string.IsNullOrEmpty(queryParameters)) { paramDictionary = queryParameters.Split('&').Select(p => p.Split('=')) .ToDictionary(nameval => nameval[0], nameval => nameval.Length > 1 ? nameval[1] : ""); } // add the fixed authorization params required by Signature V4 paramDictionary.Add(X_Amz_Algorithm, HttpHelpers.UrlEncode(string.Format("{0}-{1}", SCHEME, ALGORITHM))); paramDictionary.Add(X_Amz_Credential, HttpHelpers.UrlEncode(string.Format("{0}/{1}", awsAccessKey, scope))); paramDictionary.Add(X_Amz_SignedHeaders, HttpHelpers.UrlEncode(canonicalizedHeaderNames)); // x-amz-date is now added as a query parameter, not a header, but still needs to be in ISO8601 basic form paramDictionary.Add(X_Amz_Date, HttpHelpers.UrlEncode(dateTimeStamp)); // build the expanded canonical query parameter string that will go into the // signature computation var sb = new StringBuilder(); var paramKeys = new List <string>(paramDictionary.Keys); paramKeys.Sort(StringComparer.Ordinal); foreach (var p in paramKeys) { if (sb.Length > 0) { sb.Append("&"); } sb.AppendFormat("{0}={1}", p, paramDictionary[p]); } var canonicalizedQueryParameters = sb.ToString(); // express all the header and query parameter data as a canonical request string var canonicalRequest = CanonicalizeRequest(EndpointUri, HttpMethod, canonicalizedQueryParameters, canonicalizedHeaderNames, canonicalizedHeaders, bodyHash); Logger.LogDebug($"\nCanonicalRequest:\n{canonicalRequest}"); byte[] canonicalRequestHashBytes = CanonicalRequestHashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(canonicalRequest)); // construct the string to be signed var stringToSign = new StringBuilder(); stringToSign.AppendFormat("{0}-{1}\n{2}\n{3}\n", SCHEME, ALGORITHM, dateTimeStamp, scope); stringToSign.Append(ToHexString(canonicalRequestHashBytes, true)); Logger.LogDebug($"\nStringToSign:\n{stringToSign}"); // compute the multi-stage signing key KeyedHashAlgorithm kha = KeyedHashAlgorithm.Create(HMACSHA256); kha.Key = DeriveSigningKey(HMACSHA256, awsSecretKey, Region, dateStamp, Service); // compute the final signature for the request, place into the result and return to the // user to be embedded in the request as needed var signature = kha.ComputeHash(Encoding.UTF8.GetBytes(stringToSign.ToString())); var signatureString = ToHexString(signature, true); Logger.LogDebug($"\nSignature:\n{signatureString}"); // form up the authorization parameters for the caller to place in the query string var authString = new StringBuilder(); var authParams = new string[] { X_Amz_Algorithm, X_Amz_Credential, X_Amz_Date, X_Amz_SignedHeaders }; foreach (var p in authParams) { if (authString.Length > 0) { authString.Append("&"); } authString.AppendFormat("{0}={1}", p, paramDictionary[p]); } authString.AppendFormat("&{0}={1}", X_Amz_Signature, signatureString); var authorization = authString.ToString(); Logger.LogDebug($"\nAuthorization:\n{authorization}"); return(authorization); }
/// <summary> /// Computes an AWS4 authorization for a request, suitable for embedding /// in query parameters. /// </summary> /// <param name="headers"> /// The request headers; 'Host' and 'X-Amz-Date' will be added to this set. /// </param> /// <param name="queryParameters"> /// Any query parameters that will be added to the endpoint. The parameters /// should be specified in canonical format. /// </param> /// <param name="bodyHash"> /// Precomputed SHA256 hash of the request body content; this value should also /// be set as the header 'X-Amz-Content-SHA256' for non-streaming uploads. /// </param> /// <param name="awsAccessKey"> /// The user's AWS Access Key. /// </param> /// <param name="awsSecretKey"> /// The user's AWS Secret Key. /// </param> /// <returns> /// The string expressing the Signature V4 components to add to query parameters. /// </returns> public string ComputeSignature(IDictionary <string, string> headers, string queryParameters, string bodyHash, string awsAccessKey, string awsSecretKey) { var requestDateTime = DateTime.UtcNow; var dateTimeStamp = requestDateTime.ToString(Iso8601BasicFormat, CultureInfo.InvariantCulture); if (!headers.ContainsKey("Host")) { var hostHeader = this.EndpointUri.Host; if (!this.EndpointUri.IsDefaultPort) { hostHeader += ":" + this.EndpointUri.Port; } headers.Add("Host", hostHeader); } var dateStamp = requestDateTime.ToString(DateStringFormat, CultureInfo.InvariantCulture); var scope = string.Format("{0}/{1}/{2}/{3}", dateStamp, this.Region, this.Service, Terminator); var canonicalizedHeaderNames = this.CanonicalizeHeaderNames(headers); var canonicalizedHeaders = this.CanonicalizeHeaders(headers); var paramDictionary = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); if (!string.IsNullOrEmpty(queryParameters)) { paramDictionary = queryParameters.Split('&').Select(p => p.Split('=')) .ToDictionary(nameval => nameval[0], nameval => nameval.Length > 1 ? nameval[1] : string.Empty); } paramDictionary.Add(XAmzAlgorithm, AmazonStorageHelpers.UrlEncode(string.Format("{0}-{1}", Scheme, Algorithm))); paramDictionary.Add(XAmzCredential, AmazonStorageHelpers.UrlEncode(string.Format("{0}/{1}", awsAccessKey, scope))); paramDictionary.Add(XAmzSignedHeaders, AmazonStorageHelpers.UrlEncode(canonicalizedHeaderNames)); paramDictionary.Add(XAmzDate, AmazonStorageHelpers.UrlEncode(dateTimeStamp)); var sb = new StringBuilder(); var paramKeys = new List <string>(paramDictionary.Keys); paramKeys.Sort(StringComparer.Ordinal); foreach (var p in paramKeys) { if (sb.Length > 0) { sb.Append("&"); } sb.AppendFormat("{0}={1}", p, paramDictionary[p]); } var canonicalizedQueryParameters = sb.ToString(); var canonicalRequest = this.CanonicalizeRequest(this.EndpointUri, this.HttpMethod, canonicalizedQueryParameters, canonicalizedHeaderNames, canonicalizedHeaders, bodyHash); Log.Application.InfoFormat("\nCanonicalRequest:\n{0}", canonicalRequest); var canonicalRequestHashBytes = CanonicalRequestHashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(canonicalRequest)); var stringToSign = new StringBuilder(); stringToSign.AppendFormat("{0}-{1}\n{2}\n{3}\n", Scheme, Algorithm, dateTimeStamp, scope); stringToSign.Append(ToHexString(canonicalRequestHashBytes, true)); Log.Application.InfoFormat("\nStringToSign:\n{0}", stringToSign); var kha = KeyedHashAlgorithm.Create(Hmacsha256); kha.Key = this.DeriveSigningKey(Hmacsha256, awsSecretKey, this.Region, dateStamp, this.Service); var signature = kha.ComputeHash(Encoding.UTF8.GetBytes(stringToSign.ToString())); var signatureString = ToHexString(signature, true); Log.Application.InfoFormat("\nSignature:\n{0}", signatureString); var authString = new StringBuilder(); var authParams = new[] { XAmzAlgorithm, XAmzCredential, XAmzDate, XAmzSignedHeaders }; foreach (var p in authParams) { if (authString.Length > 0) { authString.Append("&"); } authString.AppendFormat("{0}={1}", p, paramDictionary[p]); } authString.AppendFormat("&{0}={1}", XAmzSignature, signatureString); var authorization = authString.ToString(); Log.Application.InfoFormat("\nAuthorization:\n{0}", authorization); return(authorization); }