|
Packit |
63bb0d |
package s3
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
import (
|
|
Packit |
63bb0d |
"bytes"
|
|
Packit |
63bb0d |
"crypto/md5"
|
|
Packit |
63bb0d |
"crypto/sha256"
|
|
Packit |
63bb0d |
"encoding/base64"
|
|
Packit |
63bb0d |
"encoding/hex"
|
|
Packit |
63bb0d |
"fmt"
|
|
Packit |
63bb0d |
"hash"
|
|
Packit |
63bb0d |
"io"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
"github.com/aws/aws-sdk-go/aws"
|
|
Packit |
63bb0d |
"github.com/aws/aws-sdk-go/aws/awserr"
|
|
Packit |
63bb0d |
"github.com/aws/aws-sdk-go/aws/request"
|
|
Packit |
63bb0d |
"github.com/aws/aws-sdk-go/internal/sdkio"
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
const (
|
|
Packit |
63bb0d |
contentMD5Header = "Content-Md5"
|
|
Packit |
63bb0d |
contentSha256Header = "X-Amz-Content-Sha256"
|
|
Packit |
63bb0d |
amzTeHeader = "X-Amz-Te"
|
|
Packit |
63bb0d |
amzTxEncodingHeader = "X-Amz-Transfer-Encoding"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
appendMD5TxEncoding = "append-md5"
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// contentMD5 computes and sets the HTTP Content-MD5 header for requests that
|
|
Packit |
63bb0d |
// require it.
|
|
Packit |
63bb0d |
func contentMD5(r *request.Request) {
|
|
Packit |
63bb0d |
h := md5.New()
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if !aws.IsReaderSeekable(r.Body) {
|
|
Packit |
63bb0d |
if r.Config.Logger != nil {
|
|
Packit |
63bb0d |
r.Config.Logger.Log(fmt.Sprintf(
|
|
Packit |
63bb0d |
"Unable to compute Content-MD5 for unseekable body, S3.%s",
|
|
Packit |
63bb0d |
r.Operation.Name))
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if _, err := copySeekableBody(h, r.Body); err != nil {
|
|
Packit |
63bb0d |
r.Error = awserr.New("ContentMD5", "failed to compute body MD5", err)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// encode the md5 checksum in base64 and set the request header.
|
|
Packit |
63bb0d |
v := base64.StdEncoding.EncodeToString(h.Sum(nil))
|
|
Packit |
63bb0d |
r.HTTPRequest.Header.Set(contentMD5Header, v)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// computeBodyHashes will add Content MD5 and Content Sha256 hashes to the
|
|
Packit |
63bb0d |
// request. If the body is not seekable or S3DisableContentMD5Validation set
|
|
Packit |
63bb0d |
// this handler will be ignored.
|
|
Packit |
63bb0d |
func computeBodyHashes(r *request.Request) {
|
|
Packit |
63bb0d |
if aws.BoolValue(r.Config.S3DisableContentMD5Validation) {
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
if r.IsPresigned() {
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
if r.Error != nil || !aws.IsReaderSeekable(r.Body) {
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
var md5Hash, sha256Hash hash.Hash
|
|
Packit |
63bb0d |
hashers := make([]io.Writer, 0, 2)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Determine upfront which hashes can be set without overriding user
|
|
Packit |
63bb0d |
// provide header data.
|
|
Packit |
63bb0d |
if v := r.HTTPRequest.Header.Get(contentMD5Header); len(v) == 0 {
|
|
Packit |
63bb0d |
md5Hash = md5.New()
|
|
Packit |
63bb0d |
hashers = append(hashers, md5Hash)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if v := r.HTTPRequest.Header.Get(contentSha256Header); len(v) == 0 {
|
|
Packit |
63bb0d |
sha256Hash = sha256.New()
|
|
Packit |
63bb0d |
hashers = append(hashers, sha256Hash)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Create the destination writer based on the hashes that are not already
|
|
Packit |
63bb0d |
// provided by the user.
|
|
Packit |
63bb0d |
var dst io.Writer
|
|
Packit |
63bb0d |
switch len(hashers) {
|
|
Packit |
63bb0d |
case 0:
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
case 1:
|
|
Packit |
63bb0d |
dst = hashers[0]
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
dst = io.MultiWriter(hashers...)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if _, err := copySeekableBody(dst, r.Body); err != nil {
|
|
Packit |
63bb0d |
r.Error = awserr.New("BodyHashError", "failed to compute body hashes", err)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// For the hashes created, set the associated headers that the user did not
|
|
Packit |
63bb0d |
// already provide.
|
|
Packit |
63bb0d |
if md5Hash != nil {
|
|
Packit |
63bb0d |
sum := make([]byte, md5.Size)
|
|
Packit |
63bb0d |
encoded := make([]byte, md5Base64EncLen)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
base64.StdEncoding.Encode(encoded, md5Hash.Sum(sum[0:0]))
|
|
Packit |
63bb0d |
r.HTTPRequest.Header[contentMD5Header] = []string{string(encoded)}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if sha256Hash != nil {
|
|
Packit |
63bb0d |
encoded := make([]byte, sha256HexEncLen)
|
|
Packit |
63bb0d |
sum := make([]byte, sha256.Size)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
hex.Encode(encoded, sha256Hash.Sum(sum[0:0]))
|
|
Packit |
63bb0d |
r.HTTPRequest.Header[contentSha256Header] = []string{string(encoded)}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
const (
|
|
Packit |
63bb0d |
md5Base64EncLen = (md5.Size + 2) / 3 * 4 // base64.StdEncoding.EncodedLen
|
|
Packit |
63bb0d |
sha256HexEncLen = sha256.Size * 2 // hex.EncodedLen
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func copySeekableBody(dst io.Writer, src io.ReadSeeker) (int64, error) {
|
|
Packit |
63bb0d |
curPos, err := src.Seek(0, sdkio.SeekCurrent)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return 0, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// hash the body. seek back to the first position after reading to reset
|
|
Packit |
63bb0d |
// the body for transmission. copy errors may be assumed to be from the
|
|
Packit |
63bb0d |
// body.
|
|
Packit |
63bb0d |
n, err := io.Copy(dst, src)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return n, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
_, err = src.Seek(curPos, sdkio.SeekStart)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return n, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return n, nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Adds the x-amz-te: append_md5 header to the request. This requests the service
|
|
Packit |
63bb0d |
// responds with a trailing MD5 checksum.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// Will not ask for append MD5 if disabled, the request is presigned or,
|
|
Packit |
63bb0d |
// or the API operation does not support content MD5 validation.
|
|
Packit |
63bb0d |
func askForTxEncodingAppendMD5(r *request.Request) {
|
|
Packit |
63bb0d |
if aws.BoolValue(r.Config.S3DisableContentMD5Validation) {
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
if r.IsPresigned() {
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
r.HTTPRequest.Header.Set(amzTeHeader, appendMD5TxEncoding)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func useMD5ValidationReader(r *request.Request) {
|
|
Packit |
63bb0d |
if r.Error != nil {
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if v := r.HTTPResponse.Header.Get(amzTxEncodingHeader); v != appendMD5TxEncoding {
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
var bodyReader *io.ReadCloser
|
|
Packit |
63bb0d |
var contentLen int64
|
|
Packit |
63bb0d |
switch tv := r.Data.(type) {
|
|
Packit |
63bb0d |
case *GetObjectOutput:
|
|
Packit |
63bb0d |
bodyReader = &tv.Body
|
|
Packit |
63bb0d |
contentLen = aws.Int64Value(tv.ContentLength)
|
|
Packit |
63bb0d |
// Update ContentLength hiden the trailing MD5 checksum.
|
|
Packit |
63bb0d |
tv.ContentLength = aws.Int64(contentLen - md5.Size)
|
|
Packit |
63bb0d |
tv.ContentRange = aws.String(r.HTTPResponse.Header.Get("X-Amz-Content-Range"))
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
r.Error = awserr.New("ChecksumValidationError",
|
|
Packit |
63bb0d |
fmt.Sprintf("%s: %s header received on unsupported API, %s",
|
|
Packit |
63bb0d |
amzTxEncodingHeader, appendMD5TxEncoding, r.Operation.Name,
|
|
Packit |
63bb0d |
), nil)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if contentLen < md5.Size {
|
|
Packit |
63bb0d |
r.Error = awserr.New("ChecksumValidationError",
|
|
Packit |
63bb0d |
fmt.Sprintf("invalid Content-Length %d for %s %s",
|
|
Packit |
63bb0d |
contentLen, appendMD5TxEncoding, amzTxEncodingHeader,
|
|
Packit |
63bb0d |
), nil)
|
|
Packit |
63bb0d |
return
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Wrap and swap the response body reader with the validation reader.
|
|
Packit |
63bb0d |
*bodyReader = newMD5ValidationReader(*bodyReader, contentLen-md5.Size)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
type md5ValidationReader struct {
|
|
Packit |
63bb0d |
rawReader io.ReadCloser
|
|
Packit |
63bb0d |
payload io.Reader
|
|
Packit |
63bb0d |
hash hash.Hash
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
payloadLen int64
|
|
Packit |
63bb0d |
read int64
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func newMD5ValidationReader(reader io.ReadCloser, payloadLen int64) *md5ValidationReader {
|
|
Packit |
63bb0d |
h := md5.New()
|
|
Packit |
63bb0d |
return &md5ValidationReader{
|
|
Packit |
63bb0d |
rawReader: reader,
|
|
Packit |
63bb0d |
payload: io.TeeReader(&io.LimitedReader{R: reader, N: payloadLen}, h),
|
|
Packit |
63bb0d |
hash: h,
|
|
Packit |
63bb0d |
payloadLen: payloadLen,
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func (v *md5ValidationReader) Read(p []byte) (n int, err error) {
|
|
Packit |
63bb0d |
n, err = v.payload.Read(p)
|
|
Packit |
63bb0d |
if err != nil && err != io.EOF {
|
|
Packit |
63bb0d |
return n, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
v.read += int64(n)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if err == io.EOF {
|
|
Packit |
63bb0d |
if v.read != v.payloadLen {
|
|
Packit |
63bb0d |
return n, io.ErrUnexpectedEOF
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
expectSum := make([]byte, md5.Size)
|
|
Packit |
63bb0d |
actualSum := make([]byte, md5.Size)
|
|
Packit |
63bb0d |
if _, sumReadErr := io.ReadFull(v.rawReader, expectSum); sumReadErr != nil {
|
|
Packit |
63bb0d |
return n, sumReadErr
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
actualSum = v.hash.Sum(actualSum[0:0])
|
|
Packit |
63bb0d |
if !bytes.Equal(expectSum, actualSum) {
|
|
Packit |
63bb0d |
return n, awserr.New("InvalidChecksum",
|
|
Packit |
63bb0d |
fmt.Sprintf("expected MD5 checksum %s, got %s",
|
|
Packit |
63bb0d |
hex.EncodeToString(expectSum),
|
|
Packit |
63bb0d |
hex.EncodeToString(actualSum),
|
|
Packit |
63bb0d |
),
|
|
Packit |
63bb0d |
nil)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return n, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func (v *md5ValidationReader) Close() error {
|
|
Packit |
63bb0d |
return v.rawReader.Close()
|
|
Packit |
63bb0d |
}
|