• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dacut / scratchstack-aws-signature / 9966625752

17 Jul 2024 01:26AM UTC coverage: 89.623% (-0.3%) from 89.964%
9966625752

push

github

dacut
Add migration, start setting stability

This adds a migration guide (as a doc-only module, migration.rs).

Various parts of the library are not intended for typical use but
_could_ be useful for others doing AWS SigV4 hacking. These items
are gated behind the `unstable` feature, which is not enabled by
default.

40 of 47 new or added lines in 4 files covered. (85.11%)

7 existing lines in 1 file now uncovered.

3256 of 3633 relevant lines covered (89.62%)

109.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.87
/src/error.rs
1
use {
2
    http::status::StatusCode,
3
    scratchstack_errors::ServiceError,
4
    std::{
5
        error::Error,
6
        fmt::{Display, Formatter, Result as FmtResult},
7
        io::Error as IOError,
8
    },
9
};
10

11
/// Error code: ExpiredToken
12
const ERR_CODE_EXPIRED_TOKEN: &str = "ExpiredToken";
13

14
/// Error code: InternalFailure
15
const ERR_CODE_INTERNAL_FAILURE: &str = "InternalFailure";
16

17
/// Error code: InvalidContentType (non-AWS standard)
18
const ERR_CODE_INVALID_CONTENT_TYPE: &str = "InvalidContentType";
19

20
/// Error code: InvalidBodyEncoding
21
const ERR_CODE_INVALID_BODY_ENCODING: &str = "InvalidBodyEncoding";
22

23
/// Error code: InvalidClientTokenId
24
const ERR_CODE_INVALID_CLIENT_TOKEN_ID: &str = "InvalidClientTokenId";
25

26
/// Error code: IncompleteSignature
27
const ERR_CODE_INCOMPLETE_SIGNATURE: &str = "IncompleteSignature";
28

29
/// Error code: InvalidRequestMethod (non-AWS standard)
30
const ERR_CODE_INVALID_REQUEST_METHOD: &str = "InvalidRequestMethod";
31

32
/// Error code: InvalidURIPath
33
const ERR_CODE_INVALID_URI_PATH: &str = "InvalidURIPath";
34

35
/// Error code: MalformedQueryString
36
const ERR_CODE_MALFORMED_QUERY_STRING: &str = "MalformedQueryString";
37

38
/// Error code: MissingAuthenticationToken
39
const ERR_CODE_MISSING_AUTHENTICATION_TOKEN: &str = "MissingAuthenticationToken";
40

41
/// Error code: SignatureDoesNotMatch
42
const ERR_CODE_SIGNATURE_DOES_NOT_MATCH: &str = "SignatureDoesNotMatch";
43

44
/// Error returned when an attempt at validating an AWS SigV4 signature fails.
45
#[derive(Debug)]
3✔
46
#[non_exhaustive]
47
pub enum SignatureError {
48
    /// The security token included with the request is expired.
49
    ExpiredToken(/* message */ String),
×
50

51
    /// Validation failed due to an underlying I/O error.
52
    IO(IOError),
×
53

54
    /// Validation failed due to an internal service error.
55
    InternalServiceError(Box<dyn Error + Send + Sync>),
×
56

57
    /// The request body used an unsupported character set encoding. Currently only UTF-8 is supported.
58
    InvalidBodyEncoding(/* message */ String),
×
59

60
    /// The AWS access key provided does not exist in our records.
61
    InvalidClientTokenId(/* message */ String),
×
62

63
    /// The content-type of the request is unsupported.
64
    InvalidContentType(/* message */ String),
×
65

66
    /// Invalid request method.
67
    InvalidRequestMethod(/* message */ String),
×
68

69
    /// The request signature does not conform to AWS standards. Sample messages:  
70
    /// `Authorization header requires 'Credential' parameter. Authorization=...`  
71
    /// `Authorization header requires existence of either a 'X-Amz-Date' or a 'Date' header.`  
72
    /// `Date must be in ISO-8601 'basic format'. Got '...'. See http://en.wikipedia.org/wiki/ISO_8601`  
73
    /// `Unsupported AWS 'algorithm': 'AWS4-HMAC-SHA512'`
74
    IncompleteSignature(/* message */ String),
3✔
75

76
    /// The URI path includes invalid components. This can be a malformed hex encoding (e.g. `%0J`), a non-absolute
77
    /// URI path (`foo/bar`), or a URI path that attempts to navigate above the root (`/x/../../../y`).
78
    InvalidURIPath(/* message */ String),
×
79

80
    /// A query parameter was malformed -- the value could not be decoded as UTF-8, or the parameter was empty and
81
    /// this is not allowed (e.g. a signature parameter), or the parameter could not be parsed (e.g., the `X-Amz-Date`
82
    /// parameter is not a valid date).
83
    ///
84
    /// `Incomplete trailing escape % sequence`
85
    MalformedQueryString(/* message */ String),
×
86

87
    /// The request must contain either a valid (registered) AWS access key ID or X.509 certificate. Sample messages:  
88
    /// `Request is missing Authentication Token`  
89
    MissingAuthenticationToken(/* message */ String),
×
90

91
    /// Signature did not match the calculated signature value.
92
    /// Example messages:  
93
    /// `The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.`  
94
    /// `Signature expired: 20210502T144040Z is now earlier than 20210502T173143Z (20210502T174643Z - 15 min.)`  
95
    /// `Signature not yet current: 20210502T183640Z is still later than 20210502T175140Z (20210502T173640Z + 15 min.)`
96
    SignatureDoesNotMatch(Option</* message */ String>),
×
97
}
98

99
impl SignatureError {
100
    fn error_code(&self) -> &'static str {
38✔
101
        match self {
38✔
102
            Self::ExpiredToken(_) => ERR_CODE_EXPIRED_TOKEN,
1✔
103
            Self::IO(_) | Self::InternalServiceError(_) => ERR_CODE_INTERNAL_FAILURE,
3✔
104
            Self::InvalidBodyEncoding(_) => ERR_CODE_INVALID_BODY_ENCODING,
1✔
105
            Self::InvalidClientTokenId(_) => ERR_CODE_INVALID_CLIENT_TOKEN_ID,
2✔
106
            Self::InvalidContentType(_) => ERR_CODE_INVALID_CONTENT_TYPE,
1✔
107
            Self::InvalidRequestMethod(_) => ERR_CODE_INVALID_REQUEST_METHOD,
1✔
108
            Self::IncompleteSignature(_) => ERR_CODE_INCOMPLETE_SIGNATURE,
9✔
109
            Self::InvalidURIPath(_) => ERR_CODE_INVALID_URI_PATH,
2✔
110
            Self::MalformedQueryString(_) => ERR_CODE_MALFORMED_QUERY_STRING,
2✔
111
            Self::MissingAuthenticationToken(_) => ERR_CODE_MISSING_AUTHENTICATION_TOKEN,
1✔
112
            Self::SignatureDoesNotMatch(_) => ERR_CODE_SIGNATURE_DOES_NOT_MATCH,
15✔
113
        }
114
    }
38✔
115

116
    fn http_status(&self) -> StatusCode {
37✔
117
        match self {
37✔
118
            Self::IncompleteSignature(_)
119
            | Self::InvalidBodyEncoding(_)
120
            | Self::InvalidRequestMethod(_)
121
            | Self::InvalidURIPath(_)
122
            | Self::MalformedQueryString(_)
123
            | Self::MissingAuthenticationToken(_) => StatusCode::BAD_REQUEST,
15✔
124
            Self::IO(_) | Self::InternalServiceError(_) => StatusCode::INTERNAL_SERVER_ERROR,
3✔
125
            _ => StatusCode::FORBIDDEN,
19✔
126
        }
127
    }
37✔
128
}
129

130
impl ServiceError for SignatureError {
131
    fn error_code(&self) -> &'static str {
34✔
132
        SignatureError::error_code(self)
34✔
133
    }
34✔
134

135
    fn http_status(&self) -> StatusCode {
34✔
136
        SignatureError::http_status(self)
34✔
137
    }
34✔
138
}
139

140
impl Display for SignatureError {
141
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
69✔
142
        match self {
69✔
143
            Self::ExpiredToken(msg) => f.write_str(msg),
2✔
144
            Self::IO(ref e) => Display::fmt(e, f),
2✔
145
            Self::InternalServiceError(ref e) => Display::fmt(e, f),
1✔
146
            Self::InvalidBodyEncoding(msg) => f.write_str(msg),
1✔
147
            Self::InvalidClientTokenId(msg) => f.write_str(msg),
4✔
148
            Self::InvalidContentType(msg) => f.write_str(msg),
1✔
149
            Self::InvalidRequestMethod(msg) => f.write_str(msg),
1✔
150
            Self::IncompleteSignature(msg) => f.write_str(msg),
20✔
151
            Self::InvalidURIPath(msg) => f.write_str(msg),
7✔
152
            Self::MalformedQueryString(msg) => f.write_str(msg),
3✔
153
            Self::MissingAuthenticationToken(msg) => f.write_str(msg),
2✔
154
            Self::SignatureDoesNotMatch(msg) => {
25✔
155
                if let Some(msg) = msg {
25✔
156
                    f.write_str(msg)
24✔
157
                } else {
158
                    Ok(())
1✔
159
                }
160
            }
161
        }
162
    }
69✔
163
}
164

165
impl Error for SignatureError {
166
    fn source(&self) -> Option<&(dyn Error + 'static)> {
43✔
167
        match self {
43✔
168
            Self::IO(ref e) => Some(e),
1✔
169
            _ => None,
42✔
170
        }
171
    }
43✔
172
}
173

174
impl From<IOError> for SignatureError {
175
    fn from(e: IOError) -> SignatureError {
1✔
176
        SignatureError::IO(e)
1✔
177
    }
1✔
178
}
179

180
impl From<Box<dyn Error + Send + Sync>> for SignatureError {
181
    fn from(e: Box<dyn Error + Send + Sync>) -> SignatureError {
2✔
182
        match e.downcast::<SignatureError>() {
2✔
183
            Ok(sig_err) => *sig_err,
1✔
184
            Err(e) => SignatureError::InternalServiceError(e),
1✔
185
        }
186
    }
2✔
187
}
188

189
/// Error returned by `KSecretKey::from_str` when the secret key cannot fit in the expected size.
NEW
190
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
×
191
pub struct KeyTooLongError;
192

193
impl Display for KeyTooLongError {
NEW
194
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
×
NEW
195
        f.write_str("Key too long")
×
NEW
196
    }
×
197
}
198

199
impl Error for KeyTooLongError {}
200

201
#[cfg(test)]
202
mod tests {
203
    use {crate::SignatureError, std::error::Error};
204

205
    #[test_log::test]
5✔
206
    fn test_from() {
1✔
207
        // This just exercises a few codepaths that aren't usually exercised.
208
        let utf8_error = Box::new(String::from_utf8(b"\x80".to_vec()).unwrap_err());
1✔
209
        let e: SignatureError = (utf8_error as Box<dyn Error + Send + Sync + 'static>).into();
1✔
210
        assert_eq!(e.error_code(), "InternalFailure");
1✔
211
        assert_eq!(e.http_status(), 500);
1✔
212

213
        let e = SignatureError::MalformedQueryString("foo".to_string());
1✔
214
        let e2 = SignatureError::from(Box::new(e) as Box<dyn Error + Send + Sync + 'static>);
1✔
215
        assert_eq!(e2.to_string(), "foo");
1✔
216
        assert_eq!(e2.error_code(), "MalformedQueryString");
1✔
217

218
        let e = SignatureError::InvalidContentType("Invalid content type: image/jpeg".to_string());
1✔
219
        assert_eq!(e.error_code(), "InvalidContentType");
1✔
220
        assert_eq!(e.http_status(), 403); // Should be 400, but AWS returns 403.
1✔
221
        assert_eq!(format!("{}", e), "Invalid content type: image/jpeg");
1✔
222

223
        let e = SignatureError::InvalidRequestMethod("Invalid request method: DELETE".to_string());
1✔
224
        assert_eq!(e.error_code(), "InvalidRequestMethod");
1✔
225
        assert_eq!(e.http_status(), 400);
1✔
226
        assert_eq!(format!("{}", e), "Invalid request method: DELETE");
1✔
227
    }
1✔
228
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc