aws_smithy_checksums/
http.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Checksum support for HTTP requests and responses.
7
8use aws_smithy_types::base64;
9use http::header::{HeaderMap, HeaderValue};
10
11use crate::{
12    Checksum, Crc32, Crc32c, Md5, Sha1, Sha256, CRC_32_C_NAME, CRC_32_NAME, SHA_1_NAME,
13    SHA_256_NAME,
14};
15
16pub static CRC_32_HEADER_NAME: &str = "x-amz-checksum-crc32";
17pub static CRC_32_C_HEADER_NAME: &str = "x-amz-checksum-crc32c";
18pub static SHA_1_HEADER_NAME: &str = "x-amz-checksum-sha1";
19pub static SHA_256_HEADER_NAME: &str = "x-amz-checksum-sha256";
20
21// Preserved for compatibility purposes. This should never be used by users, only within smithy-rs
22pub(crate) static MD5_HEADER_NAME: &str = "content-md5";
23
24/// When a response has to be checksum-verified, we have to check possible headers until we find the
25/// header with the precalculated checksum. Because a service may send back multiple headers, we have
26/// to check them in order based on how fast each checksum is to calculate.
27pub const CHECKSUM_ALGORITHMS_IN_PRIORITY_ORDER: [&str; 4] =
28    [CRC_32_C_NAME, CRC_32_NAME, SHA_1_NAME, SHA_256_NAME];
29
30/// Checksum algorithms are use to validate the integrity of data. Structs that implement this trait
31/// can be used as checksum calculators. This trait requires Send + Sync because these checksums are
32/// often used in a threaded context.
33pub trait HttpChecksum: Checksum + Send + Sync {
34    /// Either return this checksum as a `HeaderMap` containing one HTTP header, or return an error
35    /// describing why checksum calculation failed.
36    fn headers(self: Box<Self>) -> HeaderMap<HeaderValue> {
37        let mut header_map = HeaderMap::new();
38        header_map.insert(self.header_name(), self.header_value());
39
40        header_map
41    }
42
43    /// Return the `HeaderName` used to represent this checksum algorithm
44    fn header_name(&self) -> &'static str;
45
46    /// Return the calculated checksum as a base64-encoded `HeaderValue`
47    fn header_value(self: Box<Self>) -> HeaderValue {
48        let hash = self.finalize();
49        HeaderValue::from_str(&base64::encode(&hash[..]))
50            .expect("base64 encoded bytes are always valid header values")
51    }
52
53    /// Return the total size of
54    /// - The `HeaderName`
55    /// - The header name/value separator
56    /// - The base64-encoded `HeaderValue`
57    fn size(&self) -> u64 {
58        let trailer_name_size_in_bytes = self.header_name().len();
59        let base64_encoded_checksum_size_in_bytes =
60            base64::encoded_length(Checksum::size(self) as usize);
61
62        let size = trailer_name_size_in_bytes
63            // HTTP trailer names and values may be separated by either a single colon or a single
64            // colon and a whitespace. In the AWS Rust SDK, we use a single colon.
65            + ":".len()
66            + base64_encoded_checksum_size_in_bytes;
67
68        size as u64
69    }
70}
71
72impl HttpChecksum for Crc32 {
73    fn header_name(&self) -> &'static str {
74        CRC_32_HEADER_NAME
75    }
76}
77
78impl HttpChecksum for Crc32c {
79    fn header_name(&self) -> &'static str {
80        CRC_32_C_HEADER_NAME
81    }
82}
83
84impl HttpChecksum for Sha1 {
85    fn header_name(&self) -> &'static str {
86        SHA_1_HEADER_NAME
87    }
88}
89
90impl HttpChecksum for Sha256 {
91    fn header_name(&self) -> &'static str {
92        SHA_256_HEADER_NAME
93    }
94}
95
96impl HttpChecksum for Md5 {
97    fn header_name(&self) -> &'static str {
98        MD5_HEADER_NAME
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use aws_smithy_types::base64;
105    use bytes::Bytes;
106
107    use crate::{ChecksumAlgorithm, CRC_32_C_NAME, CRC_32_NAME, SHA_1_NAME, SHA_256_NAME};
108
109    use super::HttpChecksum;
110
111    #[test]
112    fn test_trailer_length_of_crc32_checksum_body() {
113        let checksum = CRC_32_NAME
114            .parse::<ChecksumAlgorithm>()
115            .unwrap()
116            .into_impl();
117        let expected_size = 29;
118        let actual_size = HttpChecksum::size(&*checksum);
119        assert_eq!(expected_size, actual_size)
120    }
121
122    #[test]
123    fn test_trailer_value_of_crc32_checksum_body() {
124        let checksum = CRC_32_NAME
125            .parse::<ChecksumAlgorithm>()
126            .unwrap()
127            .into_impl();
128        // The CRC32 of an empty string is all zeroes
129        let expected_value = Bytes::from_static(b"");
130        let expected_value = base64::encode(&expected_value);
131        let actual_value = checksum.header_value();
132        assert_eq!(expected_value, actual_value)
133    }
134
135    #[test]
136    fn test_trailer_length_of_crc32c_checksum_body() {
137        let checksum = CRC_32_C_NAME
138            .parse::<ChecksumAlgorithm>()
139            .unwrap()
140            .into_impl();
141        let expected_size = 30;
142        let actual_size = HttpChecksum::size(&*checksum);
143        assert_eq!(expected_size, actual_size)
144    }
145
146    #[test]
147    fn test_trailer_value_of_crc32c_checksum_body() {
148        let checksum = CRC_32_C_NAME
149            .parse::<ChecksumAlgorithm>()
150            .unwrap()
151            .into_impl();
152        // The CRC32C of an empty string is all zeroes
153        let expected_value = Bytes::from_static(b"");
154        let expected_value = base64::encode(&expected_value);
155        let actual_value = checksum.header_value();
156        assert_eq!(expected_value, actual_value)
157    }
158
159    #[test]
160    fn test_trailer_length_of_sha1_checksum_body() {
161        let checksum = SHA_1_NAME.parse::<ChecksumAlgorithm>().unwrap().into_impl();
162        let expected_size = 48;
163        let actual_size = HttpChecksum::size(&*checksum);
164        assert_eq!(expected_size, actual_size)
165    }
166
167    #[test]
168    fn test_trailer_value_of_sha1_checksum_body() {
169        let checksum = SHA_1_NAME.parse::<ChecksumAlgorithm>().unwrap().into_impl();
170        // The SHA1 of an empty string is da39a3ee5e6b4b0d3255bfef95601890afd80709
171        let expected_value = Bytes::from_static(&[
172            0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60,
173            0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
174        ]);
175        let expected_value = base64::encode(&expected_value);
176        let actual_value = checksum.header_value();
177        assert_eq!(expected_value, actual_value)
178    }
179
180    #[test]
181    fn test_trailer_length_of_sha256_checksum_body() {
182        let checksum = SHA_256_NAME
183            .parse::<ChecksumAlgorithm>()
184            .unwrap()
185            .into_impl();
186        let expected_size = 66;
187        let actual_size = HttpChecksum::size(&*checksum);
188        assert_eq!(expected_size, actual_size)
189    }
190
191    #[test]
192    fn test_trailer_value_of_sha256_checksum_body() {
193        let checksum = SHA_256_NAME
194            .parse::<ChecksumAlgorithm>()
195            .unwrap()
196            .into_impl();
197        // The SHA256 of an empty string is e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
198        let expected_value = Bytes::from_static(&[
199            0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f,
200            0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b,
201            0x78, 0x52, 0xb8, 0x55,
202        ]);
203        let expected_value = base64::encode(&expected_value);
204        let actual_value = checksum.header_value();
205        assert_eq!(expected_value, actual_value)
206    }
207}