File: checksum.go

package info (click to toggle)
golang-github-aws-aws-sdk-go-v2 1.24.1-2~bpo12%2B1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-backports
  • size: 554,032 kB
  • sloc: java: 15,941; makefile: 419; sh: 175
file content (119 lines) | stat: -rw-r--r-- 3,374 bytes parent folder | download | duplicates (5)
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()
}