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

dacut / scratchstack-aws-signature / 21892658230

11 Feb 2026 04:23AM UTC coverage: 96.352% (-0.6%) from 96.943%
21892658230

Pull #17

github

web-flow
Update src/signature.rs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Pull Request #17: Add S3-style streaming validation

132 of 151 new or added lines in 5 files covered. (87.42%)

1796 of 1864 relevant lines covered (96.35%)

184.53 hits per line

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

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

12
/// Error returned when an attempt at validating an AWS SigV4 signature fails.
13
#[derive(Debug)]
14
#[non_exhaustive]
15
pub enum SignatureError {
16
    /// The request contains a query parameter that duplicates a header value.
17
    DuplicateHeaderAndQueryParameter(/* message */ String),
18

19
    /// The security token included with the request is expired.
20
    ExpiredToken(/* message */ String),
21

22
    /// Validation failed due to an underlying I/O error.
23
    IO(IOError),
24

25
    /// Validation failed due to an internal service error.
26
    InternalServiceError(Box<dyn Error + Send + Sync>),
27

28
    /// The request body used an unsupported character set encoding. Currently only UTF-8 is supported.
29
    InvalidBodyEncoding(/* message */ String),
30

31
    /// The AWS access key provided does not exist in our records.
32
    InvalidClientTokenId(/* message */ String),
33

34
    /// The content-type of the request is unsupported.
35
    InvalidContentType(/* message */ String),
36

37
    /// Invalid request method.
38
    InvalidRequestMethod(/* message */ String),
39

40
    /// The request signature does not conform to AWS standards. Sample messages:  
41
    /// `Authorization header requires 'Credential' parameter. Authorization=...`  
42
    /// `Authorization header requires existence of either a 'X-Amz-Date' or a 'Date' header.`  
43
    /// `Date must be in ISO-8601 'basic format'. Got '...'. See http://en.wikipedia.org/wiki/ISO_8601`  
44
    /// `Unsupported AWS 'algorithm': 'AWS4-HMAC-SHA512'`
45
    IncompleteSignature(/* message */ String),
46

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

51
    /// A header was malformed -- the value could not be decoded as ASCII; the header was empty and this is not
52
    /// allowed (e.g. an `authorization` header); or the header could not be parsed (e.g., the `x-amz-date` header
53
    /// is not a valid date).
54
    MalformedHeader(/* message */ String),
55

56
    /// A query parameter was malformed -- the value could not be decoded as UTF-8; the parameter was empty and
57
    /// this is not allowed (e.g. a signature parameter); or the parameter could not be parsed (e.g., the `X-Amz-Date`
58
    /// parameter is not a valid date).
59
    ///
60
    /// `Incomplete trailing escape % sequence`
61
    MalformedQueryString(/* message */ String),
62

63
    /// The request must contain either a valid (registered) AWS access key ID or X.509 certificate. Sample messages:  
64
    /// `Request is missing Authentication Token`  
65
    MissingAuthenticationToken(/* message */ String),
66

67
    /// The request is missing a required header.
68
    MissingRequiredHeader(/* message */ String),
69

70
    /// Signature did not match the calculated signature value.
71
    /// Example messages:  
72
    /// `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.`  
73
    /// `Signature expired: 20210502T144040Z is now earlier than 20210502T173143Z (20210502T174643Z - 15 min.)`  
74
    /// `Signature not yet current: 20210502T183640Z is still later than 20210502T175140Z (20210502T173640Z + 15 min.)`
75
    SignatureDoesNotMatch(Option</* message */ String>),
76
}
77

78
impl SignatureError {
79
    fn error_code(&self) -> &'static str {
38✔
80
        match self {
38✔
NEW
81
            Self::DuplicateHeaderAndQueryParameter(_) => ERR_CODE_DUPLICATE_HEADER_AND_QUERY_PARAMETER,
×
82
            Self::ExpiredToken(_) => ERR_CODE_EXPIRED_TOKEN,
1✔
83
            Self::IO(_) | Self::InternalServiceError(_) => ERR_CODE_INTERNAL_FAILURE,
3✔
84
            Self::InvalidBodyEncoding(_) => ERR_CODE_INVALID_BODY_ENCODING,
1✔
85
            Self::InvalidClientTokenId(_) => ERR_CODE_INVALID_CLIENT_TOKEN_ID,
2✔
86
            Self::InvalidContentType(_) => ERR_CODE_INVALID_CONTENT_TYPE,
1✔
87
            Self::InvalidRequestMethod(_) => ERR_CODE_INVALID_REQUEST_METHOD,
1✔
88
            Self::IncompleteSignature(_) => ERR_CODE_INCOMPLETE_SIGNATURE,
9✔
89
            Self::InvalidURIPath(_) => ERR_CODE_INVALID_URI_PATH,
2✔
NEW
90
            Self::MalformedHeader(_) => ERR_CODE_MALFORMED_HEADER,
×
91
            Self::MalformedQueryString(_) => ERR_CODE_MALFORMED_QUERY_STRING,
2✔
92
            Self::MissingAuthenticationToken(_) => ERR_CODE_MISSING_AUTHENTICATION_TOKEN,
1✔
NEW
93
            Self::MissingRequiredHeader(_) => ERR_CODE_MISSING_REQUIRED_HEADER,
×
94
            Self::SignatureDoesNotMatch(_) => ERR_CODE_SIGNATURE_DOES_NOT_MATCH,
15✔
95
        }
96
    }
38✔
97

98
    fn http_status(&self) -> StatusCode {
37✔
99
        match self {
37✔
100
            Self::DuplicateHeaderAndQueryParameter(_)
101
            | Self::IncompleteSignature(_)
102
            | Self::InvalidBodyEncoding(_)
103
            | Self::InvalidRequestMethod(_)
104
            | Self::InvalidURIPath(_)
105
            | Self::MalformedHeader(_)
106
            | Self::MalformedQueryString(_)
107
            | Self::MissingAuthenticationToken(_)
108
            | Self::MissingRequiredHeader(_) => StatusCode::BAD_REQUEST,
15✔
109
            Self::IO(_) | Self::InternalServiceError(_) => StatusCode::INTERNAL_SERVER_ERROR,
3✔
110
            _ => StatusCode::FORBIDDEN,
19✔
111
        }
112
    }
37✔
113
}
114

115
impl ServiceError for SignatureError {
116
    fn error_code(&self) -> &'static str {
34✔
117
        SignatureError::error_code(self)
34✔
118
    }
34✔
119

120
    fn http_status(&self) -> StatusCode {
34✔
121
        SignatureError::http_status(self)
34✔
122
    }
34✔
123
}
124

125
impl Display for SignatureError {
126
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
69✔
127
        match self {
69✔
NEW
128
            Self::DuplicateHeaderAndQueryParameter(msg) => f.write_str(msg),
×
129
            Self::ExpiredToken(msg) => f.write_str(msg),
2✔
130
            Self::IO(ref e) => Display::fmt(e, f),
2✔
131
            Self::InternalServiceError(ref e) => Display::fmt(e, f),
1✔
132
            Self::InvalidBodyEncoding(msg) => f.write_str(msg),
1✔
133
            Self::InvalidClientTokenId(msg) => f.write_str(msg),
4✔
134
            Self::InvalidContentType(msg) => f.write_str(msg),
1✔
135
            Self::InvalidRequestMethod(msg) => f.write_str(msg),
1✔
136
            Self::IncompleteSignature(msg) => f.write_str(msg),
20✔
137
            Self::InvalidURIPath(msg) => f.write_str(msg),
7✔
NEW
138
            Self::MalformedHeader(msg) => f.write_str(msg),
×
139
            Self::MalformedQueryString(msg) => f.write_str(msg),
3✔
140
            Self::MissingAuthenticationToken(msg) => f.write_str(msg),
2✔
NEW
141
            Self::MissingRequiredHeader(msg) => f.write_str(msg),
×
142
            Self::SignatureDoesNotMatch(msg) => {
25✔
143
                if let Some(msg) = msg {
25✔
144
                    f.write_str(msg)
24✔
145
                } else {
146
                    Ok(())
1✔
147
                }
148
            }
149
        }
150
    }
69✔
151
}
152

153
impl Error for SignatureError {
154
    fn source(&self) -> Option<&(dyn Error + 'static)> {
43✔
155
        match self {
43✔
156
            Self::IO(ref e) => Some(e),
1✔
157
            _ => None,
42✔
158
        }
159
    }
43✔
160
}
161

162
impl From<IOError> for SignatureError {
163
    fn from(e: IOError) -> SignatureError {
1✔
164
        SignatureError::IO(e)
1✔
165
    }
1✔
166
}
167

168
impl From<Box<dyn Error + Send + Sync>> for SignatureError {
169
    fn from(e: Box<dyn Error + Send + Sync>) -> SignatureError {
2✔
170
        match e.downcast::<SignatureError>() {
2✔
171
            Ok(sig_err) => *sig_err,
1✔
172
            Err(e) => SignatureError::InternalServiceError(e),
1✔
173
        }
174
    }
2✔
175
}
176

177
/// Error returned by `KSecretKey::from_str` when the secret key cannot fit in the expected size.
178
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
179
pub enum KeyLengthError {
180
    /// The key is too long.
181
    TooLong,
182
    /// The key is too short.
183
    TooShort,
184
}
185

186
impl Display for KeyLengthError {
187
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
×
188
        match self {
×
189
            KeyLengthError::TooLong => f.write_str(ERR_MSG_KEY_TOO_LONG),
×
190
            KeyLengthError::TooShort => f.write_str(ERR_MSG_KEY_TOO_SHORT),
×
191
        }
192
    }
×
193
}
194

195
impl Error for KeyLengthError {}
196

197
#[cfg(test)]
198
mod tests {
199
    use {crate::SignatureError, std::error::Error};
200

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

209
        let e = SignatureError::MalformedQueryString("foo".to_string());
210
        let e2 = SignatureError::from(Box::new(e) as Box<dyn Error + Send + Sync + 'static>);
211
        assert_eq!(e2.to_string(), "foo");
212
        assert_eq!(e2.error_code(), "MalformedQueryString");
213

214
        let e = SignatureError::InvalidContentType("Invalid content type: image/jpeg".to_string());
215
        assert_eq!(e.error_code(), "InvalidContentType");
216
        assert_eq!(e.http_status(), 403); // Should be 400, but AWS returns 403.
217
        assert_eq!(format!("{}", e), "Invalid content type: image/jpeg");
218

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

© 2026 Coveralls, Inc