/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; using System.Text; #if AWS_ASYNC_API using System.Threading.Tasks; #endif using Amazon.Internal; using Amazon.Runtime; using Amazon.Runtime.Internal; using Amazon.Runtime.Internal.Auth; using Amazon.Runtime.Internal.Util; using Amazon.Runtime.SharedInterfaces; using Amazon.Util; using Amazon.S3.Internal; using Amazon.S3.Model; using Amazon.S3.Model.Internal.MarshallTransformations; using Amazon.S3.Util; using Amazon.Util.Internal; namespace Amazon.S3 { public partial class AmazonS3Client : AmazonServiceClient, IAmazonS3 { /// /// Specialize the initialize of the client. /// protected override void Initialize() { var clientConfig = this.Config as ClientConfig; if (clientConfig != null) { clientConfig.ResignRetries = true; } base.Initialize(); } /// /// Create a signed URL allowing access to a resource that would /// usually require authentication. /// /// /// /// When using query string authentication you create a query, /// specify an expiration time for the query, sign it with your /// signature, place the data in an HTTP request, and distribute /// the request to a user or embed the request in a web page. /// /// /// A PreSigned URL can be generated for GET, PUT, DELETE and HEAD /// operations on your bucketName, keys, and versions. /// /// /// The GetPreSignedUrlRequest that defines the /// parameters of the operation. /// determines if signing will fall back to SigV2 if the /// signing region is us-east-1 /// A string that is the signed http request. /// /// internal string GetPreSignedURLInternal(GetPreSignedUrlRequest request, bool useSigV2Fallback = true) { if (Credentials == null) throw new AmazonS3Exception("Credentials must be specified, cannot call method anonymously"); if (request == null) throw new ArgumentNullException("request", "The PreSignedUrlRequest specified is null!"); if (!request.IsSetExpires()) throw new InvalidOperationException("The Expires specified is null!"); var signatureVersionToUse = AWSConfigsS3.UseSignatureVersion4 ? SignatureVersion.SigV4 : SignatureVersion.SigV2; Arn arn; string accessPoint; if (Arn.TryParse(request.BucketName, out arn) && (arn.TryParseAccessPoint(out accessPoint) || arn.IsOutpostArn())) { signatureVersionToUse = SignatureVersion.SigV4; if (arn.IsMRAPArn()) { signatureVersionToUse = SignatureVersion.SigV4a; } } else { var region = AWS4Signer.DetermineSigningRegion(Config, "s3", alternateEndpoint: null, request: null); if (signatureVersionToUse == SignatureVersion.SigV4 && string.IsNullOrEmpty(region)) throw new InvalidOperationException("To use AWS4 signing, a region must be specified in the client configuration using the AuthenticationRegion or Region properties, or be determinable from the service URL."); RegionEndpoint endpoint = RegionEndpoint.GetBySystemName(region); var s3SignatureVersionOverride = endpoint.GetEndpointForService("s3", Config.ToGetEndpointForServiceOptions()).SignatureVersionOverride; if (s3SignatureVersionOverride == "4" || s3SignatureVersionOverride == null) { signatureVersionToUse = SignatureVersion.SigV4; } var fallbackToSigV2 = useSigV2Fallback && !AWSConfigsS3.UseSigV4SetExplicitly; if (endpoint?.SystemName == RegionEndpoint.USEast1.SystemName && fallbackToSigV2) { signatureVersionToUse = SignatureVersion.SigV2; } // If the expiration is longer than SigV4 will allow then automatically use SigV2 instead. // But only if the region we're signing for allows SigV2. if (signatureVersionToUse == SignatureVersion.SigV4) { var secondsUntilExpiration = GetSecondsUntilExpiration(this.Config, request, signatureVersionToUse); if (secondsUntilExpiration > AWS4PreSignedUrlSigner.MaxAWS4PreSignedUrlExpiry && s3SignatureVersionOverride == "2") { signatureVersionToUse = SignatureVersion.SigV2; } } } var immutableCredentials = Credentials.GetCredentials(); var irequest = Marshall(this.Config, request, immutableCredentials.AccessKey, immutableCredentials.Token, signatureVersionToUse); var context = new Amazon.Runtime.Internal.ExecutionContext(new Amazon.Runtime.Internal.RequestContext(true, new NullSigner()) { Request = irequest, ClientConfig = this.Config }, null); new AmazonS3EndpointResolver().ProcessRequestHandlers(context); var metrics = new RequestMetrics(); string result; string authorization; switch (signatureVersionToUse) { case SignatureVersion.SigV4a: var aws4aSigner = new AWS4aSignerCRTWrapper(); var signingResult4a = aws4aSigner.Presign4a(irequest, Config, metrics, immutableCredentials, "s3", arn.IsMRAPArn() ? "*" : ""); result = signingResult4a.PresignedUri; break; case SignatureVersion.SigV4: var aws4Signer = new AWS4PreSignedUrlSigner(); var signingResult4 = aws4Signer.SignRequest(irequest, Config, metrics, immutableCredentials.AccessKey, immutableCredentials.SecretKey); authorization = "&" + signingResult4.ForQueryParameters; result = ComposeUrl(irequest).AbsoluteUri + authorization; break; default: // SigV2 Amazon.S3.Internal.S3Signer.SignRequest(irequest, metrics, immutableCredentials.AccessKey, immutableCredentials.SecretKey); authorization = irequest.Headers[HeaderKeys.AuthorizationHeader]; authorization = authorization.Substring(authorization.IndexOf(":", StringComparison.Ordinal) + 1); authorization = "&Signature=" + AmazonS3Util.UrlEncode(authorization, false); result = ComposeUrl(irequest).AbsoluteUri + authorization; break; } Protocol protocol = DetermineProtocol(); if (request.Protocol != protocol) { switch (protocol) { case Protocol.HTTP: result = result.Replace("http://", "https://"); break; case Protocol.HTTPS: result = result.Replace("https://", "http://"); break; } } return result; } /// /// Marshalls the parameters for a presigned url for a preferred signing protocol. /// /// service client configuration /// /// /// /// Signature version to use. /// If AWS4 signing will be used and if the expiry period in the request exceeds the /// maximum allowed for AWS4 (one week), an ArgumentException is thrown. /// /// Internal request private static IRequest Marshall(IClientConfig config, GetPreSignedUrlRequest getPreSignedUrlRequest, string accessKey, string token, SignatureVersion signatureVersion) { IRequest request = new DefaultRequest(getPreSignedUrlRequest, "AmazonS3"); request.HttpMethod = getPreSignedUrlRequest.Verb.ToString(); var headers = getPreSignedUrlRequest.Headers; foreach (var key in headers.Keys) request.Headers[key] = headers[key]; AmazonS3Util.SetMetadataHeaders(request, getPreSignedUrlRequest.Metadata); if (getPreSignedUrlRequest.ServerSideEncryptionMethod != null && getPreSignedUrlRequest.ServerSideEncryptionMethod != ServerSideEncryptionMethod.None) request.Headers.Add(HeaderKeys.XAmzServerSideEncryptionHeader, S3Transforms.ToStringValue(getPreSignedUrlRequest.ServerSideEncryptionMethod)); if (getPreSignedUrlRequest.IsSetServerSideEncryptionCustomerMethod()) request.Headers.Add(HeaderKeys.XAmzSSECustomerAlgorithmHeader, getPreSignedUrlRequest.ServerSideEncryptionCustomerMethod); if (getPreSignedUrlRequest.IsSetServerSideEncryptionKeyManagementServiceKeyId()) request.Headers.Add(HeaderKeys.XAmzServerSideEncryptionAwsKmsKeyIdHeader, getPreSignedUrlRequest.ServerSideEncryptionKeyManagementServiceKeyId); if (getPreSignedUrlRequest.IsSetRequestPayer() && getPreSignedUrlRequest.RequestPayer == RequestPayer.Requester) request.Parameters.Add("x-amz-request-payer", RequestPayer.Requester.Value); var queryParameters = request.Parameters; var uriResourcePath = new StringBuilder("/"); if (!string.IsNullOrEmpty(getPreSignedUrlRequest.Key)) { uriResourcePath.Append(S3Transforms.ToStringValue(getPreSignedUrlRequest.Key)); } var expires = GetSecondsUntilExpiration(config, getPreSignedUrlRequest, signatureVersion); if ((signatureVersion == SignatureVersion.SigV4 || signatureVersion == SignatureVersion.SigV4a) && expires > AWS4PreSignedUrlSigner.MaxAWS4PreSignedUrlExpiry) { var msg = string.Format(CultureInfo.InvariantCulture, "The maximum expiry period for a presigned url using AWS4 signing is {0} seconds", AWS4PreSignedUrlSigner.MaxAWS4PreSignedUrlExpiry); throw new ArgumentException(msg); } if (signatureVersion == SignatureVersion.SigV2) { queryParameters.Add("Expires", expires.ToString(CultureInfo.InvariantCulture)); queryParameters.Add("AWSAccessKeyId", accessKey); if (!string.IsNullOrEmpty(token)) queryParameters.Add("x-amz-security-token", token); } else // SigV4 or SigV4a { queryParameters.Add(HeaderKeys.XAmzExpires, expires.ToString(CultureInfo.InvariantCulture)); if (!string.IsNullOrEmpty(token)) queryParameters.Add("X-Amz-Security-Token", token); } if (getPreSignedUrlRequest.IsSetVersionId()) request.AddSubResource("versionId", S3Transforms.ToStringValue(getPreSignedUrlRequest.VersionId)); if (getPreSignedUrlRequest.IsSetUploadId()) request.AddSubResource("uploadId", S3Transforms.ToStringValue(getPreSignedUrlRequest.UploadId)); if (getPreSignedUrlRequest.IsSetPartNumber()) request.AddSubResource("partNumber", S3Transforms.ToStringValue(getPreSignedUrlRequest.PartNumber)); var responseHeaderOverrides = getPreSignedUrlRequest.ResponseHeaderOverrides; if (!string.IsNullOrEmpty(responseHeaderOverrides.CacheControl)) queryParameters.Add("response-cache-control", responseHeaderOverrides.CacheControl); if (!string.IsNullOrEmpty(responseHeaderOverrides.ContentType)) queryParameters.Add("response-content-type", responseHeaderOverrides.ContentType); if (!string.IsNullOrEmpty(responseHeaderOverrides.ContentLanguage)) queryParameters.Add("response-content-language", responseHeaderOverrides.ContentLanguage); if (!string.IsNullOrEmpty(responseHeaderOverrides.Expires)) queryParameters.Add("response-expires", responseHeaderOverrides.Expires); if (!string.IsNullOrEmpty(responseHeaderOverrides.ContentDisposition)) queryParameters.Add("response-content-disposition", responseHeaderOverrides.ContentDisposition); if (!string.IsNullOrEmpty(responseHeaderOverrides.ContentEncoding)) queryParameters.Add("response-content-encoding", responseHeaderOverrides.ContentEncoding); // Add custom parameters to be included and signed foreach (string k in getPreSignedUrlRequest.Parameters.Keys) queryParameters.Add(k, getPreSignedUrlRequest.Parameters[k]); request.ResourcePath = uriResourcePath.ToString(); request.UseQueryString = true; return request; } private static long GetSecondsUntilExpiration(IClientConfig config, GetPreSignedUrlRequest request, SignatureVersion signatureVersion) { DateTime baselineTime; if (signatureVersion == SignatureVersion.SigV2) { baselineTime = new DateTime(1970, 1, 1); } else // SigV4 or SigV4a { baselineTime = config.CorrectedUtcNow; } return Convert.ToInt64((request.Expires.ToUniversalTime() - baselineTime).TotalSeconds); } private Protocol DetermineProtocol() { string serviceUrl = Config.DetermineServiceURL(); Protocol protocol = serviceUrl.StartsWith("https", StringComparison.OrdinalIgnoreCase) ? Protocol.HTTPS : Protocol.HTTP; return protocol; } internal static void CleanupRequest(AmazonWebServiceRequest request) { var putObjectRequest = request as PutObjectRequest; if (putObjectRequest != null) { if (putObjectRequest.InputStream != null && (!string.IsNullOrEmpty(putObjectRequest.FilePath) || putObjectRequest.AutoCloseStream)) { putObjectRequest.InputStream.Dispose(); } // Set the input stream to null since it was created during the request to represent the filepath or content body if (!string.IsNullOrEmpty(putObjectRequest.FilePath) || !string.IsNullOrEmpty(putObjectRequest.ContentBody)) { putObjectRequest.InputStream = null; } } var uploadPartRequest = request as UploadPartRequest; if (uploadPartRequest != null) { // FilePath was set, so we created the underlying stream, so we must close it if (uploadPartRequest.IsSetFilePath() && uploadPartRequest.InputStream != null) { uploadPartRequest.InputStream.Dispose(); } if (uploadPartRequest.IsSetFilePath()) uploadPartRequest.InputStream = null; } } internal void ConfigureProxy(HttpWebRequest httpRequest) { #if BCL if (!string.IsNullOrEmpty(Config.ProxyHost) && Config.ProxyPort != -1) { WebProxy proxy = new WebProxy(Config.ProxyHost, Config.ProxyPort); httpRequest.Proxy = proxy; } #elif NETSTANDARD httpRequest.Proxy = Config.GetWebProxy(); #endif if (httpRequest.Proxy != null && Config.ProxyCredentials != null) { httpRequest.Proxy.Credentials = Config.ProxyCredentials; } } #region GetPreSignedURL /// /// Create a signed URL allowing access to a resource that would /// usually require authentication. /// /// /// /// When using query string authentication you create a query, /// specify an expiration time for the query, sign it with your /// signature, place the data in an HTTP request, and distribute /// the request to a user or embed the request in a web page. /// /// /// A PreSigned URL can be generated for GET, PUT, DELETE and HEAD /// operations on your bucketName, keys, and versions. /// /// /// The GetPreSignedUrlRequest that defines the /// parameters of the operation. /// A string that is the signed http request. /// /// public string GetPreSignedURL(GetPreSignedUrlRequest request) { return GetPreSignedURLInternal(request); } #endregion #region ICoreAmazonS3 Implementation string ICoreAmazonS3.GeneratePreSignedURL(string bucketName, string objectKey, DateTime expiration, IDictionary additionalProperties) { var request = new GetPreSignedUrlRequest { BucketName = bucketName, Key = objectKey, Expires = expiration }; InternalSDKUtils.ApplyValues(request, additionalProperties); return this.GetPreSignedURL(request); } #if AWS_APM_API IAsyncResult ICoreAmazonS3.BeginDelete(string bucketName, string objectKey, IDictionary additionalProperties, AsyncCallback callback, object state) { var request = new DeleteObjectRequest { BucketName = bucketName, Key = objectKey }; InternalSDKUtils.ApplyValues(request, additionalProperties); return this.BeginDeleteObject(request, callback, state); } void ICoreAmazonS3.EndDelete(IAsyncResult result) { this.EndDeleteObject(result); } IAsyncResult ICoreAmazonS3.BeginUploadObjectFromStream(string bucketName, string objectKey, Stream stream, IDictionary additionalProperties, AsyncCallback callback, object state) { var transfer = new Amazon.S3.Transfer.TransferUtility(this); var request = new Amazon.S3.Transfer.TransferUtilityUploadRequest { BucketName = bucketName, Key = objectKey, InputStream = stream }; InternalSDKUtils.ApplyValues(request, additionalProperties); return transfer.BeginUpload(request, callback, state); } void ICoreAmazonS3.EndUploadObjectFromStream(IAsyncResult result) { var transfer = new Amazon.S3.Transfer.TransferUtility(this); transfer.EndUpload(result); } IAsyncResult ICoreAmazonS3.BeginUploadObjectFromFilePath(string bucketName, string objectKey, string filepath, IDictionary additionalProperties, AsyncCallback callback, object state) { var transfer = new Amazon.S3.Transfer.TransferUtility(this); var request = new Amazon.S3.Transfer.TransferUtilityUploadRequest { BucketName = bucketName, Key = objectKey, FilePath = filepath }; InternalSDKUtils.ApplyValues(request, additionalProperties); return transfer.BeginUpload(request, callback, state); } void ICoreAmazonS3.EndUploadObjectFromFilePath(IAsyncResult result) { var transfer = new Amazon.S3.Transfer.TransferUtility(this); transfer.EndUpload(result); } IAsyncResult ICoreAmazonS3.BeginDownloadToFilePath(string bucketName, string objectKey, string filepath, IDictionary additionalProperties, AsyncCallback callback, object state) { var transfer = new Amazon.S3.Transfer.TransferUtility(this); var request = new Amazon.S3.Transfer.TransferUtilityDownloadRequest { BucketName = bucketName, Key = objectKey, FilePath = filepath }; InternalSDKUtils.ApplyValues(request, additionalProperties); return transfer.BeginDownload(request, callback, state); } void ICoreAmazonS3.EndDownloadToFilePath(IAsyncResult result) { var transfer = new Amazon.S3.Transfer.TransferUtility(this); transfer.EndDownload(result); } IAsyncResult ICoreAmazonS3.BeginGetObjectStream(string bucketName, string objectKey, IDictionary additionalProperties, AsyncCallback callback, object state) { var request = new GetObjectRequest() { BucketName = bucketName, Key = objectKey }; InternalSDKUtils.ApplyValues(request, additionalProperties); return this.BeginGetObject(request, callback, state); } Stream ICoreAmazonS3.EndGetObjectStream(IAsyncResult result) { return this.EndGetObject(result).ResponseStream; } #endif #endregion } }