/// <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); } }
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))); }
/// <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)); }
/// <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); } }