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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
extern crate aws_sig_verify;
extern crate chrono;
extern crate futures;
extern crate gotham;
extern crate hyper;

use std::collections::HashMap;
use std::io;

/// Re-export aws_sig_verify so users don't have to compute versions.
pub use aws_sig_verify::{
    AWSSigV4Algorithm, AWSSigV4, ErrorKind, IAMAssumedRoleDetails,
    IAMGroupDetails, IAMRoleDetails, IAMUserDetails, Principal, PrincipalType,
    Request, SignatureError, SigningKeyFn, SigningKeyKind,
    normalize_uri_path_component, canonicalize_uri_path,
    normalize_query_parameters,
};


use futures::future;
use futures::Async::{Ready, NotReady};
use futures::stream::Stream;
use chrono::Duration;
use gotham::handler::{HandlerFuture, IntoHandlerError};
use gotham::middleware::{Middleware, NewMiddleware};
use gotham::state::{FromState, State};
use hyper::{Body, HeaderMap, Method, Uri};
use hyper::header::HeaderValue;
use http::status::StatusCode;

/// AWSSigV4Verifier implements middleware for Gotham that implements the
/// AWS SigV4 signing protocol.
///
/// Verifying the signature requires reading (and thus consuming) the body.
/// Upon a successful signature verification, the `hyper::Body` object in the
/// state is replaced with a new body that contains all of the bytes read.
#[derive(Clone)]
pub struct AWSSigV4Verifier {
    pub signing_key_kind: SigningKeyKind,
    pub signing_key_fn: SigningKeyFn,
    pub allowed_mismatch: Option<Duration>,
    pub service: String,
    pub region: String,
}

impl AWSSigV4Verifier {
    /// The new method creates a AWSSigV4Verifier with preferred defaults
    /// for `signing_key_kind` (`KSigning`) and `allowed_mismatch` (5 minutes).
    pub fn new(
        signing_key_fn: SigningKeyFn, service: &str, region: &str)
    -> Self {
        AWSSigV4Verifier{
            signing_key_kind: SigningKeyKind::KSigning,
            signing_key_fn: signing_key_fn,
            allowed_mismatch: Some(Duration::minutes(5)),
            service: service.to_string(),
            region: region.to_string(),
        }
    }
}

impl NewMiddleware for AWSSigV4Verifier {
    type Instance = Self;

    fn new_middleware(&self) -> io::Result<Self::Instance> {
        Ok(self.clone())
    }
}

impl Middleware for AWSSigV4Verifier {
    fn call<Chain>(self, mut state: State, chain: Chain) -> Box<HandlerFuture>
    where
        Chain: FnOnce(State) -> Box<HandlerFuture> + Send + 'static,
    {
        let mut body: Vec<u8> = Vec::new();
        if let Some(mut hyper_body) = state.try_take::<Body>() {
            // Read the body, consuming all of the bytes from it.
            loop {
                match hyper_body.poll() {
                    Err(e) => return Box::new(future::err((
                        state,
                        e.into_handler_error().with_status(StatusCode::UNPROCESSABLE_ENTITY),
                    ))),
                    Ok(asyncopt) => match asyncopt {
                        NotReady => (),
                        Ready(opt) => match opt {
                            Some(chunk) => body.append(&mut chunk.as_ref().to_vec()),
                            None => break,
                        }
                    }
                }
            }

            // Replace the body with the bytes we read.
            state.put(Body::from(body.clone()));
        }

        // Read the other attributes of the request.
        let method = Method::borrow_from(&state);
        let uri = Uri::borrow_from(&state);
        let header_map = HeaderMap::<HeaderValue>::borrow_from(&state);
        let mut headers: HashMap<String, Vec<Vec<u8>>> = HashMap::new();

        // Push header key/values onto the HashMap for aws_sig_verify.
        for (hyper_key, hyper_value) in header_map.iter() {
            let key = hyper_key.as_str().to_string();
            let values = headers.entry(key).or_insert_with(|| {
                Vec::<Vec<u8>>::new()
            });
            values.push(hyper_value.as_bytes().to_vec());
        }

        let request = Request{
            request_method: method.to_string(),
            uri_path: uri.path().to_string(),
            query_string: match uri.query() {
                Some(s) => s.to_string(),
                None => "".to_string(),
            },
            headers: headers,
            body: body,
            region: self.region,
            service: self.service,
        };

        let sigv4 = AWSSigV4::new();
        if let Err(e) = sigv4.verify(
            &request, self.signing_key_kind, self.signing_key_fn,
            self.allowed_mismatch)
        {
            return Box::new(future::err((
                state,
                e.into_handler_error().with_status(StatusCode::UNAUTHORIZED),
            )));
        }

        chain(state)
    }
}

#[cfg(test)]
mod tests {
    use aws_sig_verify::{ErrorKind, Principal, SignatureError, SigningKeyKind};
    use gotham::pipeline::new_pipeline;
    use gotham::pipeline::single::single_pipeline;
    use gotham::plain::test::TestServer;
    use gotham::router::builder::{build_router, DefineSingleRoute, DrawRoutes};
    use gotham::router::Router;
    use gotham::state::State;
    use http::status::StatusCode;
    use hyper::{Body, Response};
    use hyper::header::HeaderValue;
    use ring::digest::SHA256;
    use ring::hmac;
    use super::AWSSigV4Verifier;

    fn generic_handler(state: State) -> (State, Response<Body>) {
        let response: Response<Body> = Response::builder()
            .header("Content-Type", "text/plain; charset=utf-8")
            .status(StatusCode::OK)
            .body(Body::from("OK"))
            .unwrap();

        (state, response)
    }

    fn get_signing_key(
        kind: SigningKeyKind,
        _access_key_id: &str,
        _session_token: Option<&str>,
        req_date_opt: Option<&str>,
        region_opt: Option<&str>,
        service_opt: Option<&str>
    ) -> Result<(Principal, Vec<u8>), SignatureError> {
        let k_secret = "AWS4wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".as_bytes();
        let principal = Principal::create_user(
            "aws".to_string(), "123456789012".to_string(), "/".to_string(),
            "test".to_string(), "AIDAIAAAAAAAAAAAAAAAA".to_string());    
        let signing_key = match kind {
            SigningKeyKind::KSecret => k_secret.to_vec(),
            _ => get_signing_key_kdate(kind, k_secret, req_date_opt, region_opt, service_opt)?
        };

        Ok((principal, signing_key))
    }

    fn get_signing_key_kdate(
        kind: SigningKeyKind,
        k_secret: &[u8],
        req_date_opt: Option<&str>,
        region_opt: Option<&str>,
        service_opt: Option<&str>
    ) -> Result<Vec<u8>, SignatureError> {
        if let Some(req_date) = req_date_opt {
            let k_date = hmac::sign(
                &hmac::SigningKey::new(&SHA256, k_secret.as_ref()),
                req_date.as_bytes());
            match kind {
                SigningKeyKind::KDate => Ok(k_date.as_ref().to_vec()),
                _ => get_signing_key_kregion(kind, k_date.as_ref(), region_opt, service_opt)
            }
        } else {
            Err(SignatureError::new(ErrorKind::InvalidCredential, "Missing request date parameter"))
        }
    }

    fn get_signing_key_kregion(
        kind: SigningKeyKind,
        k_date: &[u8],
        region_opt: Option<&str>,
        service_opt: Option<&str>
    ) -> Result<Vec<u8>, SignatureError> {
        if let Some(region) = region_opt {
            let k_region = hmac::sign(
                &hmac::SigningKey::new(&SHA256, k_date.as_ref()),
                region.as_bytes());
            match kind {
                SigningKeyKind::KRegion => Ok(k_region.as_ref().to_vec()),
                _ => get_signing_key_kservice(kind, k_region.as_ref(), service_opt)
            }
        } else {
            Err(SignatureError::new(ErrorKind::InvalidCredential, "Missing request region parameter"))
        }
    }

    fn get_signing_key_kservice(
        kind: SigningKeyKind,
        k_region: &[u8],
        service_opt: Option<&str>
    ) -> Result<Vec<u8>, SignatureError> {
        if let Some(service) = service_opt {
            let k_service = hmac::sign(
                &hmac::SigningKey::new(&SHA256, k_region.as_ref()),
                service.as_bytes());
            match kind {
                SigningKeyKind::KService => Ok(k_service.as_ref().to_vec()),
                _ => {
                    let k_signing = hmac::sign(
                        &hmac::SigningKey::new(&SHA256, k_service.as_ref()),
                        "aws4_request".as_bytes());
                    Ok(k_signing.as_ref().to_vec())
                }
            }
        } else {
            Err(SignatureError::new(ErrorKind::InvalidCredential, "Missing service parameter"))
        }
    }

    fn router() -> Router {
        let verifier = AWSSigV4Verifier{
            signing_key_kind: SigningKeyKind::KSigning,
            signing_key_fn: get_signing_key,
            allowed_mismatch: None,
            service: "service".to_string(),
            region: "us-east-1".to_string(),
        };
        let (chain, pipelines) = single_pipeline(new_pipeline().add(verifier).build());

        build_router(chain, pipelines, |route| {
            route.get("/").to(generic_handler);
        })
    }
    #[test]
    fn check_verify() {
        let test_server = TestServer::new(router()).unwrap();
        let test_client = test_server.client();

        // This is the get-vanilla AWS testcase.
        let response = test_client.get("http://localhost/")
            .with_header("Host", HeaderValue::from_static("example.amazonaws.com"))
            .with_header("X-Amz-Date", HeaderValue::from_static("20150830T123600Z"))
            .with_header("Authorization", HeaderValue::from_static("AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31"))
            .perform().unwrap();
        assert_eq!(response.status(), StatusCode::OK);
    }
}