/// <summary>
        /// Computes the derived signature for a chunk of data of given length in the input buffer,
        /// placing a formatted chunk with headers, signature and data into the output buffer
        /// ready for streaming back to the consumer.
        /// </summary>
        /// <param name="dataLen"></param>
        private void ConstructOutputBufferChunk(int dataLen)
        {
            // if the input wasn't sufficient to fill the buffer, size it
            // down to make the subseqent hashing/computations easier since
            // they don't take any length arguments
            if (dataLen > 0 && dataLen < _inputBuffer.Length)
            {
                var temp = new byte[dataLen];
                Buffer.BlockCopy(_inputBuffer, 0, temp, 0, dataLen);
                _inputBuffer = temp;
            }

            var chunkHeader = new StringBuilder();

            // variable-length size of the embedded chunk data in hex
            chunkHeader.Append(dataLen.ToString("X", CultureInfo.InvariantCulture));

            const string nonsigExtension = "";

            // signature-extension
            var chunkStringToSign =
                CHUNK_STRING_TO_SIGN_PREFIX + "\n" +
                HeaderSigningResult.ISO8601DateTime + "\n" +
                HeaderSigningResult.Scope + "\n" +
                PreviousChunkSignature + "\n" +
                AWSSDKUtils.ToHex(AWS4Signer.ComputeHash(nonsigExtension), true) + "\n" +
                (dataLen > 0
                    ? AWSSDKUtils.ToHex(AWS4Signer.ComputeHash(_inputBuffer), true)
                    : AWS4Signer.EmptyBodySha256);

            var chunkSignature = AWSSDKUtils.ToHex(AWS4Signer.SignBlob(HeaderSigningResult.SigningKey, chunkStringToSign), true);

            PreviousChunkSignature = chunkSignature;
            chunkHeader.Append(nonsigExtension + CHUNK_SIGNATURE_HEADER + chunkSignature);
            chunkHeader.Append(CLRF);

            try
            {
                var header  = Encoding.UTF8.GetBytes(chunkHeader.ToString());
                var trailer = Encoding.UTF8.GetBytes(CLRF);

                var writePos = 0;
                Buffer.BlockCopy(header, 0, _outputBuffer, writePos, header.Length);
                writePos += header.Length;
                if (dataLen > 0)
                {
                    Buffer.BlockCopy(_inputBuffer, 0, _outputBuffer, writePos, dataLen);
                    writePos += dataLen;
                }
                Buffer.BlockCopy(trailer, 0, _outputBuffer, writePos, trailer.Length);

                _outputBufferPos     = 0;
                _outputBufferDataLen = header.Length + dataLen + trailer.Length;
            }
            catch (Exception e)
            {
                throw new AmazonClientException("Unable to sign the chunked data. " + e.Message, e);
            }
        }
Beispiel #2
0
 private string BuildV4aTrailerChunkHelper(AWS4aSigningResult headerResult, string previousSignature, IDictionary <string, string> trailingHeaders)
 {
     return(string.Join('\n',
                        "AWS4-ECDSA-P256-SHA256-TRAILER",
                        headerResult.ISO8601DateTime,
                        headerResult.Scope,
                        previousSignature.TrimEnd('*'),
                        AWSSDKUtils.ToHex(AWS4Signer.ComputeHash("x-amz-foo:bar\n"), true)));
 }
Beispiel #3
0
 /// <summary>
 /// Builds the string to sign for a single V4/V4a chunk
 /// </summary>
 /// <param name="prefix">Algorithm being used</param>
 /// <param name="dateTime">ISO8601DateTime that we're signing the request for</param>
 /// <param name="scope">Signing scope (date/region/service/aws4_request)</param>
 /// <param name="previousSignature">Previous chunk's unpadded signature</param>
 /// <param name="dataLength">Length of the content for this chunk</param>
 /// <param name="inputBuffer">Content of this chunk</param>
 /// <returns>The string to sign for this chunk</returns>
 public static string BuildChunkedStringToSign(string prefix, string dateTime, string scope, string previousSignature, int dataLength, byte[] inputBuffer)
 {
     return(prefix + "\n" +
            dateTime + "\n" +
            scope + "\n" +
            previousSignature + "\n" +
            AWSSDKUtils.ToHex(AWS4Signer.ComputeHash(""), true) + "\n" +
            (dataLength > 0
                ? AWSSDKUtils.ToHex(AWS4Signer.ComputeHash(inputBuffer), true)
                : AWS4Signer.EmptyBodySha256));
 }
        public static async Task <string> CalculateAsync(HttpContent content)
        {
            // Use a hash (digest) function like SHA256 to create a hashed value from the payload
            // in the body of the HTTP or HTTPS request.
            //
            // If the payload is empty, use an empty string as the input to the hash function.
            if (content == null)
            {
                // Per performance reasons, use the pre-computed hash of an empty string from the
                // AWS SDK
                return(AWS4Signer.EmptyBodySha256);
            }

            var data = await content.ReadAsByteArrayAsync();

            var hash = AWS4Signer.ComputeHash(data);

            return(AWSSDKUtils.ToHex(hash, true));
        }
Beispiel #5
0
        /// <summary>
        /// Constructs the signed trailing headers, optionally including
        /// the selected checksum for this stream's data. For example:
        /// trailing-header-A:value CRLF
        /// trailing-header-B:value CRLF
        /// x-amz-trailer-signature:signature_value CRLF
        /// CRLF
        /// </summary>
        /// <returns>Stream chunk containing the trailing headers and their signature</returns>
        private string ConstructSignedTrailersChunk()
        {
            // If the trailing headers included a trailing checksum, set the hash value
            if (_hashAlgorithm != null)
            {
                _hashAlgorithm.TransformFinalBlock(new byte[0], 0, 0);
                _trailingHeaders[ChecksumUtils.GetChecksumHeaderKey(_trailingChecksum)] = Convert.ToBase64String(_hashAlgorithm.Hash);
            }

            string chunkSignature;

            if (HeaderSigningResult is AWS4SigningResult)
            {
                var sortedTrailingHeaders        = AWS4Signer.SortAndPruneHeaders(_trailingHeaders);
                var canonicalizedTrailingHeaders = AWS4Signer.CanonicalizeHeaders(sortedTrailingHeaders);

                var chunkStringToSign =
                    TRAILING_HEADER_STRING_TO_SIGN_PREFIX + "\n" +
                    HeaderSigningResult.ISO8601DateTime + "\n" +
                    HeaderSigningResult.Scope + "\n" +
                    PreviousChunkSignature + "\n" +
                    AWSSDKUtils.ToHex(AWS4Signer.ComputeHash(canonicalizedTrailingHeaders), true);

                chunkSignature = AWSSDKUtils.ToHex(AWS4Signer.SignBlob(((AWS4SigningResult)HeaderSigningResult).GetSigningKey(), chunkStringToSign), true);
            }
            else // SigV4a
            {
                chunkSignature = Sigv4aSigner.SignTrailingHeaderChunk(_trailingHeaders, PreviousChunkSignature, (AWS4aSigningResult)HeaderSigningResult).PadRight(V4A_SIGNATURE_LENGTH, '*');
            }

            var chunk = new StringBuilder();

            // The order here must match the order of keys sent already in the X-Amz-Trailer header.
            foreach (var kvp in _trailingHeaders.OrderBy(kvp => kvp.Key))
            {
                chunk.Append($"{kvp.Key}:{kvp.Value}{STREAM_NEWLINE}");
            }
            chunk.Append($"{TRAILING_HEADER_SIGNATURE_KEY}:{chunkSignature}{STREAM_NEWLINE}");
            chunk.Append(STREAM_NEWLINE);
            return(chunk.ToString());
        }
        /// <returns>
        /// The first value is the string to sign, the second value is the credentials scope.
        /// </returns>
        public static (string, string) Build(DateTime now, string regionName, string serviceName, string canonicalRequest)
        {
            var builder = new StringBuilder();

            // Start with the algorithm designation, followed by a newline character. This value is
            // the hashing algorithm that you use to calculate the digests in the canonical
            // request. For SHA256, AWS4-HMAC-SHA256 is the algorithm.
            builder.Append($"{AWS4Signer.AWS4AlgorithmTag}\n");

            // Append the request date value, followed by a newline character. The date is
            // specified with ISO8601 basic format in the x-amz-date header in the format
            // YYYYMMDD'T'HHMMSS'Z'. This value must match the value you used in any previous
            // steps.
            builder.Append($"{now.ToIso8601BasicDateTime()}\n");

            // Append the credential scope value, followed by a newline character. This value is a
            // string that includes the date, the region you are targeting, the service you are
            // requesting, and a termination string ("aws4_request") in lowercase characters. The
            // region and service name strings must be UTF-8 encoded.
            //
            // - The date must be in the YYYYMMDD format. Note that the date does not include a
            //   time value.
            // - Verify that the region you specify is the region that you are sending the request
            //   to. See <see href="https://docs.aws.amazon.com/general/latest/gr/rande.html">AWS
            //   Regions and Endpoints</see>.
            var credentialScope = $"{now.ToIso8601BasicDate()}/{regionName}/{serviceName}/{AWS4Signer.Terminator}";

            builder.Append($"{credentialScope}\n");

            // Append the hash of the canonical request. This value is not followed by a newline
            // character. The hashed canonical request must be lowercase base-16 encoded, as
            // defined by <see href="https://tools.ietf.org/html/rfc4648#section-8">Section 8 of
            // RFC 4648</see>.
            var hash = AWS4Signer.ComputeHash(canonicalRequest);
            var hex  = AWSSDKUtils.ToHex(hash, true);

            builder.Append(hex);

            return(builder.ToString(), credentialScope);
        }
        /// <returns>
        /// The first value is the canonical request, the second value is the signed headers.
        /// </returns>
        public static async Task <(string, string)> BuildAsync(
            HttpRequestMessage request,
            HttpRequestHeaders defaultHeaders)
        {
            var builder = new StringBuilder();

            // The HTTP request method (GET, PUT, POST, etc.), followed by a newline character
            builder.Append($"{request.Method}\n");

            // Add the canonical URI parameter, followed by a newline character. The canonical URI
            // is the URI-encoded version of the absolute path component of the URI, which is
            // everything in the URI from the HTTP host to the question mark character ("?") that
            // begins the query string parameters (if any).
            //
            // Normalize URI paths according to <see href="https://tools.ietf.org/html/rfc3986">RFC
            // 3986</see>. Remove redundant and relative path components. Each path segment must be
            // URI-encoded twice (
            // <see href="https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html">
            // except for Amazon S3 which only gets URI-encoded once</see>).
            var pathSegments = request.RequestUri.AbsolutePath
                               .Replace("//", "/")
                               .Split('/')
                               .Select(pathSegment => AWSSDKUtils.UrlEncode(pathSegment, false));

            builder.Append($"{string.Join("/", pathSegments)}\n");

            // Add the canonical query string, followed by a newline character. If the request does
            // not include a query string, use an empty string (essentially, a blank line).
            //
            // To construct the canonical query string, complete the following steps:
            //
            // a. Sort the parameter names by character code point in ascending order. Parameters
            //    with duplicate names should be sorted by value. For example, a parameter name
            //    that begins with the uppercase letter F precedes a parameter name that begins
            //    with a lowercase letter b.
            // b. URI-encode each parameter name and value according to the following rules:
            //    - Do not URI-encode any of the unreserved characters that RFC 3986 defines: A-Z,
            //      a-z, 0-9, hyphen ( - ), underscore ( _ ), period ( . ), and tilde ( ~ ).
            //    - Percent-encode all other characters with %XY, where X and Y are hexadecimal
            //      characters (0-9 and uppercase A-F). For example, the space character must be
            //      encoded as %20 (not using '+', as some encoding schemes do) and extended UTF-8
            //      characters must be in the form %XY%ZA%BC.
            // c. Build the canonical query string by starting with the first parameter name in the
            //    sorted list.
            // d. For each parameter, append the URI-encoded parameter name, followed by the equals
            //    sign character (=), followed by the URI-encoded parameter value. Use an empty
            //    string for parameters that have no value.
            // e. Append the ampersand character (&) after each parameter value, except for the
            //    last value in the list.
            var parameters = SortQueryParameters(request.RequestUri.Query)
                             .SelectMany(
                parameter => parameter.Value.Select(
                    parameterValue => $"{AWSSDKUtils.UrlEncode(parameter.Key, false)}={AWSSDKUtils.UrlEncode(parameterValue, false)}"));

            builder.Append($"{string.Join("&", parameters)}\n");

            // Add the canonical headers, followed by a newline character. The canonical headers
            // consist of a list of all the HTTP headers that you are including with the signed
            // request.
            //
            // To create the canonical headers list, convert all header names to lowercase and
            // remove leading spaces and trailing spaces. Convert sequential spaces in the header
            // value to a single space.
            //
            // Build the canonical headers list by sorting the (lowercase) headers by character
            // code and then iterating through the header names. Construct each header according to
            // the following rules:
            //
            // - Append the lowercase header name followed by a colon.
            // - Append a comma-separated list of values for that header. Do not sort the values in
            //   headers that have multiple values.
            //   PLEASE NOTE: Microsoft has chosen to separate the header values with ", ", not ","
            //   as defined by the Canonical Request algorithm.
            // - Append a new line ('\n').
            var sortedHeaders = SortHeaders(request.Headers, defaultHeaders);

            foreach (var header in sortedHeaders)
            {
                builder.Append($"{header.Key}:{string.Join(HeaderValueSeparator, header.Value)}\n");
            }

            builder.Append('\n');

            // Add the signed headers, followed by a newline character. This value is the list of
            // headers that you included in the canonical headers. By adding this list of headers,
            // you tell AWS which headers in the request are part of the signing process and which
            // ones AWS can ignore (for example, any additional headers added by a proxy) for
            // purposes of validating the request.
            //
            // To create the signed headers list, convert all header names to lowercase, sort them
            // by character code, and use a semicolon to separate the header names.
            //
            // Build the signed headers list by iterating through the collection of header names,
            // sorted by lowercase character code. For each header name except the last, append a
            // semicolon (';') to the header name to separate it from the following header name.
            var signedHeaders = string.Join(";", sortedHeaders.Keys);

            builder.Append($"{signedHeaders}\n");

            // Use a hash (digest) function like SHA256 to create a hashed value from the payload
            // in the body of the HTTP or HTTPS request.
            //
            // If the payload is empty, use an empty string as the input to the hash function.
            var requestPayload = request.Content != null
                ? await request.Content.ReadAsByteArrayAsync()
                : new byte[0];

            var hash = AWS4Signer.ComputeHash(requestPayload);
            var hex  = AWSSDKUtils.ToHex(hash, true);

            builder.Append(hex);

            return(builder.ToString(), signedHeaders);
        }
        private void ConstructOutputBufferChunk(int dataLen)
        {
            if (dataLen > 0 && dataLen < _inputBuffer.Length)
            {
                byte[] array = new byte[dataLen];
                Buffer.BlockCopy(_inputBuffer, 0, array, 0, dataLen);
                _inputBuffer = array;
            }
            StringBuilder stringBuilder = new StringBuilder();

            stringBuilder.Append(dataLen.ToString("X", CultureInfo.InvariantCulture));
            string data = "AWS4-HMAC-SHA256-PAYLOAD\n" + HeaderSigningResult.ISO8601DateTime + "\n" + HeaderSigningResult.Scope + "\n" + PreviousChunkSignature + "\n" + AWSSDKUtils.ToHex(AWS4Signer.ComputeHash(""), lowercase: true) + "\n" + ((dataLen > 0) ? AWSSDKUtils.ToHex(AWS4Signer.ComputeHash(_inputBuffer), lowercase: true) : "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
            string str  = PreviousChunkSignature = AWSSDKUtils.ToHex(AWS4Signer.SignBlob(HeaderSigningResult.SigningKey, data), lowercase: true);

            stringBuilder.Append(";chunk-signature=" + str);
            stringBuilder.Append("\r\n");
            try
            {
                byte[] bytes  = Encoding.UTF8.GetBytes(stringBuilder.ToString());
                byte[] bytes2 = Encoding.UTF8.GetBytes("\r\n");
                int    num    = 0;
                Buffer.BlockCopy(bytes, 0, _outputBuffer, num, bytes.Length);
                num += bytes.Length;
                if (dataLen > 0)
                {
                    Buffer.BlockCopy(_inputBuffer, 0, _outputBuffer, num, dataLen);
                    num += dataLen;
                }
                Buffer.BlockCopy(bytes2, 0, _outputBuffer, num, bytes2.Length);
                _outputBufferPos     = 0;
                _outputBufferDataLen = bytes.Length + dataLen + bytes2.Length;
            }
            catch (Exception ex)
            {
                throw new AmazonClientException("Unable to sign the chunked data. " + ex.Message, ex);
            }
        }