Пример #1
0
        /// <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);
        }
Пример #4
0
        /// <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);
        }
Пример #5
0
        /// <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);
        }