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}