1use crate::date_time::{format_date, format_date_time, truncate_subsecs};
50use crate::http_request::SigningError;
51use crate::sign::v4::{calculate_signature, generate_signing_key, sha256_hex_string};
52use crate::SigningOutput;
53use aws_credential_types::Credentials;
54use aws_smithy_eventstream::frame::{write_headers_to, write_message_to};
55use aws_smithy_types::event_stream::{Header, HeaderValue, Message};
56use bytes::Bytes;
57use std::io::Write;
58use std::time::SystemTime;
59
60pub type SigningParams<'a> = crate::sign::v4::SigningParams<'a, ()>;
62
63fn calculate_string_to_sign(
65 message_payload: &[u8],
66 last_signature: &str,
67 time: SystemTime,
68 params: &SigningParams<'_>,
69) -> Vec<u8> {
70 let date_time_str = format_date_time(time);
73 let date_str = format_date(time);
74
75 let mut sts: Vec<u8> = Vec::new();
76 writeln!(sts, "AWS4-HMAC-SHA256-PAYLOAD").unwrap();
77 writeln!(sts, "{}", date_time_str).unwrap();
78 writeln!(
79 sts,
80 "{}/{}/{}/aws4_request",
81 date_str, params.region, params.name
82 )
83 .unwrap();
84 writeln!(sts, "{}", last_signature).unwrap();
85
86 let date_header = Header::new(":date", HeaderValue::Timestamp(time.into()));
87 let mut date_buffer = Vec::new();
88 write_headers_to(&[date_header], &mut date_buffer).unwrap();
89 writeln!(sts, "{}", sha256_hex_string(&date_buffer)).unwrap();
90 write!(sts, "{}", sha256_hex_string(message_payload)).unwrap();
91 sts
92}
93
94pub fn sign_message<'a>(
100 message: &'a Message,
101 last_signature: &'a str,
102 params: &'a SigningParams<'a>,
103) -> Result<SigningOutput<Message>, SigningError> {
104 let message_payload = {
105 let mut payload = Vec::new();
106 write_message_to(message, &mut payload).unwrap();
107 payload
108 };
109 sign_payload(Some(message_payload), last_signature, params)
110}
111
112pub fn sign_empty_message<'a>(
118 last_signature: &'a str,
119 params: &'a SigningParams<'a>,
120) -> Result<SigningOutput<Message>, SigningError> {
121 sign_payload(None, last_signature, params)
122}
123
124fn sign_payload<'a>(
125 message_payload: Option<Vec<u8>>,
126 last_signature: &'a str,
127 params: &'a SigningParams<'a>,
128) -> Result<SigningOutput<Message>, SigningError> {
129 let time = truncate_subsecs(params.time);
132 let creds = params
133 .identity
134 .data::<Credentials>()
135 .ok_or_else(SigningError::unsupported_identity_type)?;
136
137 let signing_key =
138 generate_signing_key(creds.secret_access_key(), time, params.region, params.name);
139 let string_to_sign = calculate_string_to_sign(
140 message_payload.as_ref().map(|v| &v[..]).unwrap_or(&[]),
141 last_signature,
142 time,
143 params,
144 );
145 let signature = calculate_signature(signing_key, &string_to_sign);
146 tracing::trace!(canonical_request = ?message_payload, string_to_sign = ?string_to_sign, "calculated signing parameters");
147
148 Ok(SigningOutput::new(
150 Message::new(message_payload.map(Bytes::from).unwrap_or_default())
151 .add_header(Header::new(
152 ":chunk-signature",
153 HeaderValue::ByteArray(hex::decode(&signature).unwrap().into()),
154 ))
155 .add_header(Header::new(":date", HeaderValue::Timestamp(time.into()))),
156 signature,
157 ))
158}
159
160#[cfg(test)]
161mod tests {
162 use crate::event_stream::{calculate_string_to_sign, sign_message, SigningParams};
163 use crate::sign::v4::sha256_hex_string;
164 use aws_credential_types::Credentials;
165 use aws_smithy_eventstream::frame::write_message_to;
166 use aws_smithy_types::event_stream::{Header, HeaderValue, Message};
167 use std::time::{Duration, UNIX_EPOCH};
168
169 #[test]
170 fn string_to_sign() {
171 let message_to_sign = Message::new(&b"test payload"[..]).add_header(Header::new(
172 "some-header",
173 HeaderValue::String("value".into()),
174 ));
175 let mut message_payload = Vec::new();
176 write_message_to(&message_to_sign, &mut message_payload).unwrap();
177
178 let params = SigningParams {
179 identity: &Credentials::for_tests().into(),
180 region: "us-east-1",
181 name: "testservice",
182 time: (UNIX_EPOCH + Duration::new(123_456_789_u64, 1234u32)),
183 settings: (),
184 };
185
186 let expected = "\
187 AWS4-HMAC-SHA256-PAYLOAD\n\
188 19731129T213309Z\n\
189 19731129/us-east-1/testservice/aws4_request\n\
190 be1f8c7d79ef8e1abc5254a2c70e4da3bfaf4f07328f527444e1fc6ea67273e2\n\
191 0c0e3b3bf66b59b976181bd7d401927bbd624107303c713fd1e5f3d3c8dd1b1e\n\
192 f2eba0f2e95967ee9fbc6db5e678d2fd599229c0d04b11e4fc8e0f2a02a806c6\
193 ";
194
195 let last_signature = sha256_hex_string(b"last message sts");
196 assert_eq!(
197 expected,
198 std::str::from_utf8(&calculate_string_to_sign(
199 &message_payload,
200 &last_signature,
201 params.time,
202 ¶ms
203 ))
204 .unwrap()
205 );
206 }
207
208 #[test]
209 fn sign() {
210 let message_to_sign = Message::new(&b"test payload"[..]).add_header(Header::new(
211 "some-header",
212 HeaderValue::String("value".into()),
213 ));
214 let params = SigningParams {
215 identity: &Credentials::for_tests().into(),
216 region: "us-east-1",
217 name: "testservice",
218 time: (UNIX_EPOCH + Duration::new(123_456_789_u64, 1234u32)),
219 settings: (),
220 };
221
222 let last_signature = sha256_hex_string(b"last message sts");
223 let (signed, signature) = sign_message(&message_to_sign, &last_signature, ¶ms)
224 .unwrap()
225 .into_parts();
226 assert_eq!(":chunk-signature", signed.headers()[0].name().as_str());
227 if let HeaderValue::ByteArray(bytes) = signed.headers()[0].value() {
228 assert_eq!(signature, hex::encode(bytes));
229 } else {
230 panic!("expected byte array for :chunk-signature header");
231 }
232 assert_eq!(":date", signed.headers()[1].name().as_str());
233 if let HeaderValue::Timestamp(value) = signed.headers()[1].value() {
234 assert_eq!(123_456_789_i64, value.secs());
235 assert_eq!(0, value.subsec_nanos());
237 } else {
238 panic!("expected timestamp for :date header");
239 }
240 }
241}