1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
|
package customizations
import (
"context"
"fmt"
"hash"
"hash/crc32"
"io"
"net/http"
"strconv"
smithy "github.com/aws/smithy-go"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
// AddValidateResponseChecksumOptions provides the options for the
// AddValidateResponseChecksum middleware setup.
type AddValidateResponseChecksumOptions struct {
Disable bool
}
// AddValidateResponseChecksum adds the Checksum to the middleware
// stack if checksum is not disabled.
func AddValidateResponseChecksum(stack *middleware.Stack, options AddValidateResponseChecksumOptions) error {
if options.Disable {
return nil
}
return stack.Deserialize.Add(&Checksum{}, middleware.After)
}
// Checksum provides a middleware to validate the DynamoDB response
// body's integrity by comparing the computed CRC32 checksum with the value
// provided in the HTTP response header.
type Checksum struct{}
// ID returns the middleware ID.
func (*Checksum) ID() string { return "DynamoDB:ResponseChecksumValidation" }
// HandleDeserialize implements the Deserialize middleware handle method.
func (m *Checksum) HandleDeserialize(
ctx context.Context, input middleware.DeserializeInput, next middleware.DeserializeHandler,
) (
output middleware.DeserializeOutput, metadata middleware.Metadata, err error,
) {
output, metadata, err = next.HandleDeserialize(ctx, input)
if err != nil {
return output, metadata, err
}
resp, ok := output.RawResponse.(*smithyhttp.Response)
if !ok {
return output, metadata, &smithy.DeserializationError{
Err: fmt.Errorf("unknown response type %T", output.RawResponse),
}
}
expectChecksum, ok, err := getCRC32Checksum(resp.Header)
if err != nil {
return output, metadata, &smithy.DeserializationError{Err: err}
}
resp.Body = wrapCRC32ChecksumValidate(expectChecksum, resp.Body)
return output, metadata, err
}
const crc32ChecksumHeader = "X-Amz-Crc32"
func getCRC32Checksum(header http.Header) (uint32, bool, error) {
v := header.Get(crc32ChecksumHeader)
if len(v) == 0 {
return 0, false, nil
}
c, err := strconv.ParseUint(v, 10, 32)
if err != nil {
return 0, false, fmt.Errorf("unable to parse checksum header %v, %w", v, err)
}
return uint32(c), true, nil
}
// crc32ChecksumValidate provides wrapping of an io.Reader to validate the CRC32
// checksum of the bytes read against the expected checksum.
type crc32ChecksumValidate struct {
io.Reader
closer io.Closer
expect uint32
hash hash.Hash32
}
// wrapCRC32ChecksumValidate constructs a new crc32ChecksumValidate that will
// compute a running CRC32 checksum of the bytes read.
func wrapCRC32ChecksumValidate(checksum uint32, reader io.ReadCloser) *crc32ChecksumValidate {
hash := crc32.NewIEEE()
return &crc32ChecksumValidate{
expect: checksum,
Reader: io.TeeReader(reader, hash),
closer: reader,
hash: hash,
}
}
// Close validates the wrapped reader's CRC32 checksum. Returns an error if
// the read checksum does not match the expected checksum.
//
// May return an error if the wrapped io.Reader's close returns an error, if it
// implements close.
func (c *crc32ChecksumValidate) Close() error {
if actual := c.hash.Sum32(); actual != c.expect {
c.closer.Close()
return fmt.Errorf("response did not match expected checksum, %d, %d", c.expect, actual)
}
return c.closer.Close()
}
|