/// <summary> /// Instantiate the object without supplying a stream. Useful for HEAD responses. /// </summary> /// <param name="ctx">S3 context.</param> public S3Response(S3Context ctx) { if (ctx == null) { throw new ArgumentNullException(nameof(ctx)); } _Http = ctx.Http; _Request = ctx.Request; }
/// <summary> /// Instantiate the object without supplying a stream. Useful for HEAD responses. /// </summary> /// <param name="ctx">S3 context.</param> /// <param name="statusCode">HTTP status code.</param> /// <param name="contentType">Content-type.</param> /// <param name="headers">HTTP headers.</param> /// <param name="contentLength">Content length.</param> public S3Response(S3Context ctx, int statusCode = 200, string contentType = "text/plain", Dictionary <string, string> headers = null, long contentLength = 0) { if (ctx == null) { throw new ArgumentNullException(nameof(ctx)); } _Http = ctx.Http; _Request = ctx.Request; StatusCode = statusCode; ContentType = contentType; ContentLength = contentLength; Chunked = _Request.Chunked; }
/// <summary> /// Instantiates the object. /// </summary> /// <param name="ctx">S3 context.</param> /// <param name="baseDomains">List of base domains against which the hostname should be evaluated to identify the bucket. For instance, to resolve buckets for *.localhost, specify '.localhost'.</param> /// <param name="logger">Method to invoke to send log messages.</param> public S3Request(S3Context ctx, List <string> baseDomains = null, Action <string> logger = null) { if (ctx == null) { throw new ArgumentNullException(nameof(ctx)); } if (ctx.Http == null) { throw new ArgumentNullException(nameof(ctx.Http)); } if (baseDomains != null) { _BaseDomains = baseDomains; } _Context = ctx; _Logger = logger; _BaseDomains = baseDomains; ParseHttpContext(); }
private async Task RequestHandler(HttpContext ctx) { if (ctx == null) { throw new ArgumentNullException(nameof(ctx)); } DateTime startTime = DateTime.Now; S3Context s3ctx = null; try { s3ctx = new S3Context(ctx, _BaseDomains, null, (Logging.S3Requests ? Logger : null)); } catch (Exception e) { if (Logging.Exceptions) { Logger?.Invoke(_Header + "Exception:" + Environment.NewLine + Common.SerializeJson(e, true)); } return; } bool success = false; try { if (Logging.HttpRequests) { Logger?.Invoke(_Header + "HTTP request: " + Environment.NewLine + s3ctx.Http.ToJson(true)); } if (Logging.S3Requests) { Logger?.Invoke(_Header + "S3 request: " + Environment.NewLine + s3ctx.Request.ToJson(true)); } if (PreRequestHandler != null) { success = await PreRequestHandler(s3ctx).ConfigureAwait(false); if (success) { await s3ctx.Response.Send().ConfigureAwait(false); return; } } if (AuthenticateSignatures && GetSecretKey != null) { if (!String.IsNullOrEmpty(s3ctx.Request.AccessKey)) { byte[] secretKey = GetSecretKey(s3ctx); if (secretKey == null || secretKey.Length < 1) { await s3ctx.Response.Send(S3Objects.ErrorCode.InvalidRequest).ConfigureAwait(false); return; } if (!s3ctx.Request.IsValidSignature(secretKey)) { await s3ctx.Response.Send(S3Objects.ErrorCode.SignatureDoesNotMatch).ConfigureAwait(false); return; } } } switch (s3ctx.Request.RequestType) { case S3RequestType.ListBuckets: if (Service.ListBuckets != null) { await Service.ListBuckets(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketDelete: if (Bucket.Delete != null) { await Bucket.Delete(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketDeleteTags: if (Bucket.DeleteTags != null) { await Bucket.DeleteTags(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketDeleteWebsite: if (Bucket.DeleteWebsite != null) { await Bucket.DeleteWebsite(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketExists: if (Bucket.Exists != null) { await Bucket.Exists(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketRead: if (Bucket.Read != null) { await Bucket.Read(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketReadAcl: if (Bucket.ReadAcl != null) { await Bucket.ReadAcl(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketReadLocation: if (Bucket.ReadLocation != null) { await Bucket.ReadLocation(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketReadLogging: if (Bucket.ReadLogging != null) { await Bucket.ReadLogging(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketReadTags: if (Bucket.ReadTags != null) { await Bucket.ReadTags(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketReadVersioning: if (Bucket.ReadVersioning != null) { await Bucket.ReadVersioning(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketReadVersions: if (Bucket.ReadVersions != null) { await Bucket.ReadVersions(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketReadWebsite: if (Bucket.ReadWebsite != null) { await Bucket.ReadWebsite(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketWrite: if (Bucket.Write != null) { await Bucket.Write(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketWriteAcl: if (Bucket.WriteAcl != null) { await Bucket.WriteAcl(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketWriteLogging: if (Bucket.WriteLogging != null) { await Bucket.WriteLogging(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketWriteTags: if (Bucket.WriteTags != null) { await Bucket.WriteTags(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketWriteVersioning: if (Bucket.WriteVersioning != null) { await Bucket.WriteVersioning(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.BucketWriteWebsite: if (Bucket.WriteWebsite != null) { await Bucket.WriteWebsite(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectDelete: if (Object.Delete != null) { await Object.Delete(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectDeleteMultiple: if (Object.DeleteMultiple != null) { await Object.DeleteMultiple(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectDeleteTags: if (Object.DeleteTags != null) { await Object.DeleteTags(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectExists: if (Object.Exists != null) { await Object.Exists(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectRead: if (Object.Read != null) { await Object.Read(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectReadAcl: if (Object.ReadAcl != null) { await Object.ReadAcl(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectReadLegalHold: if (Object.ReadLegalHold != null) { await Object.ReadLegalHold(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectReadRange: if (Object.ReadRange != null) { await Object.ReadRange(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectReadRetention: if (Object.ReadRetention != null) { await Object.ReadRetention(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectReadTags: if (Object.ReadTags != null) { await Object.ReadTags(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectWrite: if (Object.Write != null) { await Object.Write(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectWriteAcl: if (Object.WriteAcl != null) { await Object.WriteAcl(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectWriteLegalHold: if (Object.WriteLegalHold != null) { await Object.WriteLegalHold(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectWriteRetention: if (Object.WriteRetention != null) { await Object.WriteRetention(s3ctx).ConfigureAwait(false); return; } break; case S3RequestType.ObjectWriteTags: if (Object.WriteTags != null) { await Object.WriteTags(s3ctx).ConfigureAwait(false); return; } break; } if (DefaultRequestHandler != null) { await DefaultRequestHandler(s3ctx).ConfigureAwait(false); return; } await s3ctx.Response.Send(S3Objects.ErrorCode.InvalidRequest).ConfigureAwait(false); return; } catch (Exception e) { if (Logging.Exceptions) { Logger?.Invoke(_Header + "Exception:" + Environment.NewLine + Common.SerializeJson(e, true)); } await s3ctx.Response.Send(S3Objects.ErrorCode.InternalError).ConfigureAwait(false); return; } finally { if (Logging.HttpRequests) { Logger?.Invoke( _Header + "[" + ctx.Request.Source.IpAddress + ":" + ctx.Request.Source.Port + "] " + ctx.Request.Method.ToString() + " " + ctx.Request.Url.RawWithoutQuery + " " + s3ctx.Response.StatusCode + " [" + Common.TotalMsFrom(startTime) + "ms]"); } if (PostRequestHandler != null) { await PostRequestHandler(s3ctx).ConfigureAwait(false); } } }
// For V2, see https://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html // For V4, see https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html internal static bool IsValidSignature(S3Context ctx, byte[] secretKey, Action <string> logger) { if (ctx == null) { throw new ArgumentNullException(nameof(ctx)); } if (secretKey == null || secretKey.Length < 1) { throw new ArgumentNullException(nameof(secretKey)); } _S3Context = ctx; _SecretKey = secretKey; _Logger = logger; byte[] signingKey = null; string hmacSha1Signature = null; string hmacSha256Signature = null; string logMessage = null; if (_S3Context.Request.SignatureVersion == S3SignatureVersion.Version2) { #region V2 hmacSha1Signature = Common.BytesToBase64( Common.HmacSha1(Encoding.UTF8.GetBytes(_V2StringToSign), secretKey) ); hmacSha256Signature = Common.BytesToBase64( Common.HmacSha256(Encoding.UTF8.GetBytes(_V2StringToSign), secretKey) ); logMessage = _Header + "Signature V2 validation:" + Environment.NewLine + " String to sign" + Environment.NewLine + " --------------" + Environment.NewLine + _V2StringToSign + Environment.NewLine + Environment.NewLine + " Client-supplied signature : " + _S3Context.Request.Signature + Environment.NewLine + " Generated HMAC-SHA1 : " + hmacSha1Signature + Environment.NewLine + " Generated HMAC-SHA256 : " + hmacSha256Signature + Environment.NewLine + " Success : "; if (hmacSha256Signature.Equals(_S3Context.Request.Signature) || hmacSha1Signature.Equals(_S3Context.Request.Signature)) { _Logger?.Invoke(logMessage + "true"); return(true); } else { _Logger?.Invoke(logMessage + "false"); return(false); } #endregion } else if (_S3Context.Request.SignatureVersion == S3SignatureVersion.Version4) { #region V4 signingKey = V4GenerateSigningKey(secretKey); hmacSha1Signature = Common.BytesToBase64( Common.HmacSha1(Encoding.UTF8.GetBytes(_V4StringToSign), secretKey) ); hmacSha256Signature = Common.BytesToHex( Common.HmacSha256(Encoding.UTF8.GetBytes(_V4StringToSign), signingKey) ) .ToLower(); logMessage = _Header + "Signature V4 validation:" + Environment.NewLine + " Canonical request" + Environment.NewLine + " -----------------" + Environment.NewLine + _V4CanonicalRequest + Environment.NewLine + Environment.NewLine + " String to sign" + Environment.NewLine + " --------------" + Environment.NewLine + _V4StringToSign + Environment.NewLine + Environment.NewLine + " Client-supplied signature : " + _S3Context.Request.Signature + Environment.NewLine + " Generated HMAC-SHA1 : " + hmacSha1Signature + Environment.NewLine + " Generated HMAC-SHA256 : " + hmacSha256Signature + Environment.NewLine + " Success : "; if (hmacSha256Signature.Equals(_S3Context.Request.Signature) || hmacSha1Signature.Equals(_S3Context.Request.Signature)) { _Logger?.Invoke(logMessage + "true"); return(true); } else { _Logger?.Invoke(logMessage + "false"); return(false); } #endregion } else { throw new InvalidOperationException("Unable to validate signature unless signature version is V2 or V4."); } }