public string Sign(RequestTemplate requestTemplate, Options options, IBlobSigner blobSigner, IClock clock) { var state = new SigningState(requestTemplate, options, blobSigner, clock); var signature = blobSigner.CreateSignature(state._blobToSign); return(state.GetResult(signature)); }
internal SigningState( string bucket, string objectName, DateTimeOffset expiration, HttpMethod requestMethod, Dictionary <string, IEnumerable <string> > requestHeaders, Dictionary <string, IEnumerable <string> > contentHeaders, IBlobSigner blobSigner) { StorageClientImpl.ValidateBucketName(bucket); bool isResumableUpload = false; if (requestMethod == null) { requestMethod = HttpMethod.Get; } else if (requestMethod == ResumableHttpMethod) { isResumableUpload = true; requestMethod = HttpMethod.Post; } string expiryUnixSeconds = ((int)(expiration - UnixEpoch).TotalSeconds).ToString(CultureInfo.InvariantCulture); resourcePath = $"/{bucket}"; if (objectName != null) { resourcePath += $"/{Uri.EscapeDataString(objectName)}"; } var extensionHeaders = GetExtensionHeaders(requestHeaders, contentHeaders); if (isResumableUpload) { extensionHeaders["x-goog-resumable"] = new StringBuilder("start"); } var contentMD5 = GetFirstHeaderValue(contentHeaders, "Content-MD5"); var contentType = GetFirstHeaderValue(contentHeaders, "Content-Type"); var signatureLines = new List <string> { requestMethod.ToString(), contentMD5, contentType, expiryUnixSeconds }; signatureLines.AddRange(extensionHeaders.Select( header => $"{header.Key}:{string.Join(", ", header.Value)}")); signatureLines.Add(resourcePath); blobToSign = Encoding.UTF8.GetBytes(string.Join("\n", signatureLines)); queryParameters = new List <string> { $"GoogleAccessId={blobSigner.Id}" }; if (expiryUnixSeconds != null) { queryParameters.Add($"Expires={expiryUnixSeconds}"); } }
public async Task <string> SignAsync( RequestTemplate requestTemplate, Options options, IBlobSigner blobSigner, IClock clock, CancellationToken cancellationToken) { var state = new SigningState(requestTemplate, options, blobSigner, clock); var signature = await blobSigner.CreateSignatureAsync(state._blobToSign, cancellationToken).ConfigureAwait(false); return(state.GetResult(signature)); }
// Note: It's irritating to have to convert from base64 to bytes and then to hex, but we can't change the IBlobSigner implementation // and ServiceAccountCredential.CreateSignature returns base64 anyway. public string Sign(RequestTemplate requestTemplate, Options options, IBlobSigner blobSigner, IClock clock) { var state = new UrlSigningState(requestTemplate, options, blobSigner, clock); var base64Signature = blobSigner.CreateSignature(state._blobToSign); var rawSignature = Convert.FromBase64String(base64Signature); var hexSignature = FormatHex(rawSignature); return(state.GetResult(hexSignature)); }
internal PostPolicySigningState(PostPolicy policy, Options options, IBlobSigner blobSigner, IClock clock) { string uri = options.UrlStyle switch { UrlStyle.PathStyle => policy.Bucket == null ? StorageHost : $"{StorageHost}/{policy.Bucket}", UrlStyle.VirtualHostedStyle => policy.Bucket == null ? throw new ArgumentNullException(nameof(PostPolicy.Bucket), $"When using {UrlStyle.VirtualHostedStyle} a bucket condition must be set in the policy.") : $"{policy.Bucket}.{StorageHost}", UrlStyle.BucketBoundHostname => options.BucketBoundHostname, _ => throw new ArgumentOutOfRangeException(nameof(options.UrlStyle)) }; uri = $"{options.Scheme}://{uri}/"; options = options.ToExpiration(clock); var now = clock.GetCurrentDateTimeUtc(); int expirySeconds = (int)(options.Expiration.Value - now).TotalSeconds; if (expirySeconds <= 0) { throw new ArgumentOutOfRangeException(nameof(options.Expiration), "Expiration must be at least 1 second."); } if (expirySeconds > MaxExpirySecondsInclusive) { throw new ArgumentOutOfRangeException(nameof(options.Expiration), "Expiration must not be greater than 7 days."); } var datestamp = now.ToString("yyyyMMdd", CultureInfo.InvariantCulture); string credentialScope = $"{datestamp}/{DefaultRegion}/{ScopeSuffix}"; policy.SetField(PolicyCreationDateTime.Element, new DateTimeOffset(now)); policy.SetField(PolicyAlgorithm.Element, Algorithm); policy.SetField(PolicyCredential.Element, $"{blobSigner.Id}/{credentialScope}"); _expiration = options.Expiration.Value; _policy = policy; _url = new Uri(uri); StringBuilder sb = new StringBuilder(); StringWriter sw = new StringWriter(sb); using (JsonWriter writer = new JsonTextWriter(sw)) { writer.StringEscapeHandling = StringEscapeHandling.EscapeNonAscii; writer.WriteStartObject(); policy.WriteTo(writer); writer.WritePropertyName("expiration"); writer.WriteValue(_expiration.UtcDateTime.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture)); writer.WriteEndObject(); } var decodedPolicy = sb.ToString(); _encodedPolicy = Convert.ToBase64String(Encoding.UTF8.GetBytes(decodedPolicy)); _blobToSign = Encoding.UTF8.GetBytes(_encodedPolicy); }
public SignedPostPolicy Sign(PostPolicy postPolicy, Options options, IBlobSigner blobSigner, IClock clock) { var state = new PostPolicySigningState(new PostPolicy(postPolicy), options, blobSigner, clock); var base64Signature = blobSigner.CreateSignature(state._blobToSign); var rawSignature = Convert.FromBase64String(base64Signature); var hexSignature = FormatHex(rawSignature); return(state.GetResult(hexSignature)); }
public async Task <string> SignAsync( RequestTemplate requestTemplate, Options options, IBlobSigner blobSigner, IClock clock, CancellationToken cancellationToken) { var state = new UrlSigningState(requestTemplate, options, blobSigner, clock); var base64Signature = await blobSigner.CreateSignatureAsync(state._blobToSign, cancellationToken).ConfigureAwait(false); var rawSignature = Convert.FromBase64String(base64Signature); var hexSignature = FormatHex(rawSignature); return(state.GetResult(hexSignature)); }
public string Sign( string bucket, string objectName, DateTimeOffset expiration, HttpMethod requestMethod, Dictionary <string, IEnumerable <string> > requestHeaders, Dictionary <string, IEnumerable <string> > contentHeaders, IBlobSigner blobSigner, IClock clock) { var state = new SigningState(bucket, objectName, expiration, requestMethod, requestHeaders, contentHeaders, blobSigner); var signature = blobSigner.CreateSignature(state.blobToSign); return(state.GetResult(signature)); }
public async Task <string> SignAsync( string bucket, string objectName, DateTimeOffset expiration, HttpMethod requestMethod, Dictionary <string, IEnumerable <string> > requestHeaders, Dictionary <string, IEnumerable <string> > contentHeaders, IBlobSigner blobSigner, IClock clock, CancellationToken cancellationToken) { var state = new SigningState(bucket, objectName, expiration, requestMethod, requestHeaders, contentHeaders, blobSigner); var signature = await blobSigner.CreateSignatureAsync(state.blobToSign, cancellationToken).ConfigureAwait(false); return(state.GetResult(signature)); }
// Note: It's irritating to have to convert from base64 to bytes and then to hex, but we can't change the IBlobSigner implementation // and ServiceAccountCredential.CreateSignature returns base64 anyway. public string Sign( string bucket, string objectName, DateTimeOffset expiration, HttpMethod requestMethod, Dictionary <string, IEnumerable <string> > requestHeaders, Dictionary <string, IEnumerable <string> > contentHeaders, IBlobSigner blobSigner, IClock clock) { var state = new SigningState(bucket, objectName, expiration, requestMethod, requestHeaders, contentHeaders, blobSigner, clock); var base64Signature = blobSigner.CreateSignature(state._blobToSign); var rawSignature = Convert.FromBase64String(base64Signature); var hexSignature = FormatHex(rawSignature); return(state.GetResult(hexSignature)); }
internal SigningState( string bucket, string objectName, DateTimeOffset expiration, HttpMethod requestMethod, Dictionary <string, IEnumerable <string> > requestHeaders, Dictionary <string, IEnumerable <string> > contentHeaders, IBlobSigner blobSigner, IClock clock) { StorageClientImpl.ValidateBucketName(bucket); var now = clock.GetCurrentDateTimeUtc(); var timestamp = now.ToString("yyyyMMdd'T'HHmmss'Z'", CultureInfo.InvariantCulture); var datestamp = now.ToString("yyyyMMdd", CultureInfo.InvariantCulture); int expirySeconds = (int)(expiration - now).TotalSeconds; if (expirySeconds <= 0) { throw new ArgumentOutOfRangeException(nameof(expiration), "Expiration must be at least 1 second"); } if (expirySeconds > MaxExpirySecondsInclusive) { throw new ArgumentOutOfRangeException(nameof(expiration), "Expiration must not be greater than 7 days."); } string expiryText = expirySeconds.ToString(CultureInfo.InvariantCulture); string credentialScope = $"{datestamp}/{DefaultRegion}/{ScopeSuffix}"; var headers = new SortedDictionary <string, string>(StringComparer.Ordinal); headers["host"] = HostHeaderValue; AddHeaders(headers, requestHeaders); AddHeaders(headers, contentHeaders); var canonicalHeaders = string.Join("", headers.Select(pair => $"{pair.Key}:{pair.Value}\n")); var signedHeaders = string.Join(";", headers.Keys.Select(k => k.ToLowerInvariant())); var queryParameters = new SortedDictionary <string, string>(StringComparer.Ordinal) { { "X-Goog-Algorithm", Algorithm }, { "X-Goog-Credential", $"{blobSigner.Id}/{credentialScope}" }, { "X-Goog-Date", timestamp }, { "X-Goog-Expires", expirySeconds.ToString(CultureInfo.InvariantCulture) }, { "X-Goog-SignedHeaders", signedHeaders } }; if (requestMethod == null) { requestMethod = HttpMethod.Get; } else if (requestMethod == ResumableHttpMethod) { requestMethod = HttpMethod.Post; queryParameters["X-Goog-Resumable"] = "Start"; } _canonicalQueryString = string.Join("&", queryParameters.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); _resourcePath = $"/{bucket}"; if (!string.IsNullOrEmpty(objectName)) { // EscapeDataString escapes slashes, which we *don't* want to escape here. The simplest option is to // split the path into segments by slashes, escape each segment, then join the escaped segments together again. var segments = objectName.Split('/'); var escaped = string.Join("/", segments.Select(Uri.EscapeDataString)); _resourcePath = _resourcePath + "/" + escaped; } var canonicalRequest = $"{requestMethod}\n{_resourcePath}\n{_canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\nUNSIGNED-PAYLOAD"; string hashHex; using (var sha256 = SHA256.Create()) { hashHex = FormatHex(sha256.ComputeHash(Encoding.UTF8.GetBytes(canonicalRequest))); } _blobToSign = Encoding.UTF8.GetBytes($"{Algorithm}\n{timestamp}\n{credentialScope}\n{hashHex}"); void AddHeaders(SortedDictionary <string, string> canonicalized, IDictionary <string, IEnumerable <string> > headersToAdd) { if (headersToAdd == null) { return; } foreach (var pair in headersToAdd) { if (pair.Value == null) { continue; } var headerName = pair.Key.ToLowerInvariant(); // Note: the comma-space separating here is because this is what HttpClient does. // Google Cloud Storage itself will just use commas if it receives multiple values for the same header name, // but HttpClient coalesces the values itself. This approach means that if the same request is made from .NET // with the signed URL, it will succeed - but it does mean that the signed URL won't be valid when used from // another platform that sends actual multiple values. var value = string.Join(", ", pair.Value.Select(PrepareHeaderValue)).Trim(); if (canonicalized.TryGetValue(headerName, out var existingValue)) { value = $"{existingValue}, {value}"; } canonicalized[headerName] = value; } } }
private UrlSigner(IBlobSigner blobSigner) => _blobSigner = blobSigner;
public Task <SignedPostPolicy> SignAsync(PostPolicy postPolicy, Options options, IBlobSigner blobSigner, IClock clock, CancellationToken cancellationToken) => throw new NotSupportedException($"Post policy signing is not supported by {nameof(SigningVersion)}.{SigningVersion.V2}.");
/// <summary> /// Creates a new <see cref="UrlSigner"/> instance for a custom blob signer. /// </summary> /// <remarks> /// This method is typically used when a service account credential file isn't available, either /// for testing or to use the IAM service's blob signing capabilities. /// </remarks> /// <param name="signer">The blob signer to use. Must not be null.</param> /// <returns>A new <see cref="UrlSigner"/> using the specified blob signer.</returns> public static UrlSigner FromBlobSigner(IBlobSigner signer) { GaxPreconditions.CheckNotNull(signer, nameof(signer)); return(new UrlSigner(signer, SystemClock.Instance)); }
internal SigningState(RequestTemplate template, Options options, IBlobSigner blobSigner, IClock clock) { GaxPreconditions.CheckArgument( template.QueryParameters.Count == 0, nameof(template.QueryParameters), $"When using {nameof(SigningVersion.V2)} custom query parematers are not included as part of the signature so none should be specified."); (_host, _urlResourcePath) = options.UrlStyle switch { UrlStyle.PathStyle => (StorageHost, $"/{template.Bucket}"), UrlStyle.VirtualHostedStyle => ($"{template.Bucket}.{StorageHost}", string.Empty), _ => throw new ArgumentOutOfRangeException( nameof(options.UrlStyle), $"When using {nameof(SigningVersion.V2)} only {nameof(UrlStyle.PathStyle)} or {nameof(UrlStyle.VirtualHostedStyle)} can be specified.") }; _scheme = options.Scheme; options = options.ToExpiration(clock); string expiryUnixSeconds = ((int)(options.Expiration.Value - UnixEpoch).TotalSeconds).ToString(CultureInfo.InvariantCulture); string signingResourcePath = $"/{template.Bucket}"; if (template.ObjectName != null) { string escaped = Uri.EscapeDataString(template.ObjectName); _urlResourcePath += $"/{escaped}"; signingResourcePath += $"/{escaped}"; } var extensionHeaders = GetExtensionHeaders(template.RequestHeaders, template.ContentHeaders); var effectiveHttpMethod = template.HttpMethod; if (effectiveHttpMethod == ResumableHttpMethod) { extensionHeaders["x-goog-resumable"] = new StringBuilder("start"); effectiveHttpMethod = HttpMethod.Post; } var contentMD5 = GetFirstHeaderValue(template.ContentHeaders, "Content-MD5"); var contentType = GetFirstHeaderValue(template.ContentHeaders, "Content-Type"); var signatureLines = new List <string> { effectiveHttpMethod.ToString(), contentMD5, contentType, expiryUnixSeconds }; signatureLines.AddRange(extensionHeaders.Select( header => $"{header.Key}:{string.Join(", ", header.Value)}")); signatureLines.Add(signingResourcePath); _blobToSign = Encoding.UTF8.GetBytes(string.Join("\n", signatureLines)); _queryParameters = new List <string> { $"GoogleAccessId={blobSigner.Id}" }; if (expiryUnixSeconds != null) { _queryParameters.Add($"Expires={expiryUnixSeconds}"); } }
internal SigningState( string bucket, string objectName, DateTimeOffset expiration, HttpMethod requestMethod, Dictionary <string, IEnumerable <string> > requestHeaders, Dictionary <string, IEnumerable <string> > contentHeaders, IBlobSigner blobSigner, IClock clock) { StorageClientImpl.ValidateBucketName(bucket); bool isResumableUpload = false; if (requestMethod == null) { requestMethod = HttpMethod.Get; } else if (requestMethod == ResumableHttpMethod) { isResumableUpload = true; requestMethod = HttpMethod.Post; } var now = clock.GetCurrentDateTimeUtc(); var timestamp = now.ToString("yyyyMMdd'T'HHmmss'Z'", CultureInfo.InvariantCulture); var datestamp = now.ToString("yyyyMMdd", CultureInfo.InvariantCulture); // TODO: Validate again maximum expirary duration int expirySeconds = (int)(expiration - now).TotalSeconds; string expiryText = expirySeconds.ToString(CultureInfo.InvariantCulture); string clientEmail = blobSigner.Id; string credentialScope = $"{datestamp}/auto/gcs/goog4_request"; string credential = WebUtility.UrlEncode($"{blobSigner.Id}/{credentialScope}"); // FIXME: Use requestHeaders and contentHeaders var headers = new SortedDictionary <string, string>(); headers["host"] = "storage.googleapis.com"; var canonicalHeaderBuilder = new StringBuilder(); foreach (var pair in headers) { canonicalHeaderBuilder.Append($"{pair.Key}:{pair.Value}\n"); } var canonicalHeaders = canonicalHeaderBuilder.ToString().ToLowerInvariant(); var signedHeaders = string.Join(";", headers.Keys.Select(k => k.ToLowerInvariant())); queryParameters = new List <string> { "X-Goog-Algorithm=GOOG4-RSA-SHA256", $"X-Goog-Credential={credential}", $"X-Goog-Date={timestamp}", $"X-Goog-Expires={expirySeconds}", $"X-Goog-SignedHeaders={signedHeaders}" }; if (isResumableUpload) { queryParameters.Insert(4, "X-Goog-Resumable=Start"); } var canonicalQueryString = string.Join("&", queryParameters); resourcePath = $"/{bucket}"; if (objectName != null) { resourcePath += $"/{Uri.EscapeDataString(objectName)}"; } var canonicalRequest = $"{requestMethod}\n{resourcePath}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\nUNSIGNED-PAYLOAD"; string hashHex; using (var sha256 = SHA256.Create()) { hashHex = FormatHex(sha256.ComputeHash(Encoding.UTF8.GetBytes(canonicalRequest))); } blobToSign = Encoding.UTF8.GetBytes($"GOOG4-RSA-SHA256\n{timestamp}\n{credentialScope}\n{hashHex}"); }
public async Task <SignedPostPolicy> SignAsync(PostPolicy postPolicy, Options options, IBlobSigner blobSigner, IClock clock, CancellationToken cancellationToken) { var state = new PostPolicySigningState(new PostPolicy(postPolicy), options, blobSigner, clock); var base64Signature = await blobSigner.CreateSignatureAsync(state._blobToSign, cancellationToken).ConfigureAwait(false); var rawSignature = Convert.FromBase64String(base64Signature); var hexSignature = FormatHex(rawSignature); return(state.GetResult(hexSignature)); }
internal SigningState( string bucket, string objectName, DateTimeOffset expiration, HttpMethod requestMethod, Dictionary <string, IEnumerable <string> > requestHeaders, Dictionary <string, IEnumerable <string> > contentHeaders, IBlobSigner blobSigner, IClock clock) { StorageClientImpl.ValidateBucketName(bucket); var now = clock.GetCurrentDateTimeUtc(); var timestamp = now.ToString("yyyyMMdd'T'HHmmss'Z'", CultureInfo.InvariantCulture); var datestamp = now.ToString("yyyyMMdd", CultureInfo.InvariantCulture); // TODO: Validate against maximum expiry duration int expirySeconds = (int)(expiration - now).TotalSeconds; string expiryText = expirySeconds.ToString(CultureInfo.InvariantCulture); string credentialScope = $"{datestamp}/{DefaultRegion}/{ScopeSuffix}"; var headers = new SortedDictionary <string, string>(StringComparer.Ordinal); headers["host"] = HostHeaderValue; AddHeaders(headers, requestHeaders); AddHeaders(headers, contentHeaders); var canonicalHeaders = string.Join("", headers.Select(pair => $"{pair.Key}:{pair.Value}\n")); var signedHeaders = string.Join(";", headers.Keys.Select(k => k.ToLowerInvariant())); var queryParameters = new SortedDictionary <string, string>(StringComparer.Ordinal) { { "X-Goog-Algorithm", Algorithm }, { "X-Goog-Credential", $"{blobSigner.Id}/{credentialScope}" }, { "X-Goog-Date", timestamp }, { "X-Goog-Expires", expirySeconds.ToString(CultureInfo.InvariantCulture) }, { "X-Goog-SignedHeaders", signedHeaders } }; if (requestMethod == null) { requestMethod = HttpMethod.Get; } else if (requestMethod == ResumableHttpMethod) { requestMethod = HttpMethod.Post; queryParameters["X-Goog-Resumable"] = "Start"; } _canonicalQueryString = string.Join("&", queryParameters.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); _resourcePath = $"/{bucket}"; if (objectName != null) { _resourcePath += $"/{Uri.EscapeDataString(objectName)}"; } var canonicalRequest = $"{requestMethod}\n{_resourcePath}\n{_canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\nUNSIGNED-PAYLOAD"; string hashHex; using (var sha256 = SHA256.Create()) { hashHex = FormatHex(sha256.ComputeHash(Encoding.UTF8.GetBytes(canonicalRequest))); } _blobToSign = Encoding.UTF8.GetBytes($"{Algorithm}\n{timestamp}\n{credentialScope}\n{hashHex}"); void AddHeaders(SortedDictionary <string, string> canonicalized, IDictionary <string, IEnumerable <string> > headersToAdd) { if (headersToAdd == null) { return; } foreach (var pair in headersToAdd) { if (pair.Value == null) { continue; } var headerName = pair.Key.ToLowerInvariant(); if (headerName == EncryptionKey.KeyHeader || headerName == EncryptionKey.KeyHashHeader || headerName == EncryptionKey.AlgorithmHeader) { continue; } var value = string.Join(", ", pair.Value.Select(PrepareHeaderValue)).Trim(); if (canonicalized.TryGetValue(headerName, out var existingValue)) { value = $"{existingValue}, {value}"; } canonicalized[headerName] = value; } } }
internal UrlSigningState(RequestTemplate template, Options options, IBlobSigner blobSigner, IClock clock) { (_host, _resourcePath) = options.UrlStyle switch { UrlStyle.PathStyle => (StorageHost, $"/{template.Bucket}"), UrlStyle.VirtualHostedStyle => ($"{template.Bucket}.{StorageHost}", string.Empty), UrlStyle.BucketBoundHostname => (options.BucketBoundHostname, string.Empty), _ => throw new ArgumentOutOfRangeException(nameof(options.UrlStyle)) }; _scheme = options.Scheme; options = options.ToExpiration(clock); var now = clock.GetCurrentDateTimeUtc(); var timestamp = now.ToString("yyyyMMdd'T'HHmmss'Z'", CultureInfo.InvariantCulture); var datestamp = now.ToString("yyyyMMdd", CultureInfo.InvariantCulture); int expirySeconds = (int)(options.Expiration.Value - now).TotalSeconds; if (expirySeconds <= 0) { throw new ArgumentOutOfRangeException(nameof(options.Expiration), "Expiration must be at least 1 second"); } if (expirySeconds > MaxExpirySecondsInclusive) { throw new ArgumentOutOfRangeException(nameof(options.Expiration), "Expiration must not be greater than 7 days."); } string expiryText = expirySeconds.ToString(CultureInfo.InvariantCulture); string credentialScope = $"{datestamp}/{DefaultRegion}/{ScopeSuffix}"; var headers = new SortedDictionary <string, string>(StringComparer.Ordinal); headers.AddHeader("host", _host); headers.AddHeaders(template.RequestHeaders); headers.AddHeaders(template.ContentHeaders); var canonicalHeaders = string.Join("", headers.Select(pair => $"{pair.Key}:{pair.Value}\n")); var signedHeaders = string.Join(";", headers.Keys.Select(k => k.ToLowerInvariant())); var queryParameters = new SortedSet <string>(StringComparer.Ordinal); queryParameters.AddQueryParameter("X-Goog-Algorithm", Algorithm); queryParameters.AddQueryParameter("X-Goog-Credential", $"{blobSigner.Id}/{credentialScope}"); queryParameters.AddQueryParameter("X-Goog-Date", timestamp); queryParameters.AddQueryParameter("X-Goog-Expires", expirySeconds.ToString(CultureInfo.InvariantCulture)); queryParameters.AddQueryParameter("X-Goog-SignedHeaders", signedHeaders); var effectiveRequestMethod = template.HttpMethod; if (effectiveRequestMethod == ResumableHttpMethod) { effectiveRequestMethod = HttpMethod.Post; queryParameters.AddQueryParameter("X-Goog-Resumable", "Start"); } queryParameters.AddQueryParameters(template.QueryParameters); _canonicalQueryString = string.Join("&", queryParameters); if (!string.IsNullOrEmpty(template.ObjectName)) { // EscapeDataString escapes slashes, which we *don't* want to escape here. The simplest option is to // split the path into segments by slashes, escape each segment, then join the escaped segments together again. var segments = template.ObjectName.Split('/'); var escaped = string.Join("/", segments.Select(Uri.EscapeDataString)); _resourcePath = _resourcePath + "/" + escaped; } string payloadHash = "UNSIGNED-PAYLOAD"; var payloadHashHeader = headers.Where( header => header.Key.Equals("X-Goog-Content-SHA256", StringComparison.OrdinalIgnoreCase)).ToList(); if (payloadHashHeader.Count == 1) { payloadHash = payloadHashHeader[0].Value; } var canonicalRequest = $"{effectiveRequestMethod}\n{_resourcePath}\n{_canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{payloadHash}"; string hashHex; using (var sha256 = SHA256.Create()) { hashHex = FormatHex(sha256.ComputeHash(Encoding.UTF8.GetBytes(canonicalRequest))); } _blobToSign = Encoding.UTF8.GetBytes($"{Algorithm}\n{timestamp}\n{credentialScope}\n{hashHex}"); }
private UrlSigner(IBlobSigner blobSigner, IClock clock) { _blobSigner = blobSigner; _clock = clock; }
public SignedPostPolicy Sign(PostPolicy postPolicy, Options options, IBlobSigner blobSigner, IClock clock) => throw new NotSupportedException($"Post policy signing is not supported by {nameof(SigningVersion)}.{SigningVersion.V2}.");