@@ -17,6 +17,7 @@ public enum AccessControlList: String {
17
17
18
18
public struct AWSSignatureV4 {
19
19
public enum Method : String {
20
+ case delete = " DELETE "
20
21
case get = " GET "
21
22
case post = " POST "
22
23
case put = " PUT "
@@ -28,7 +29,6 @@ public struct AWSSignatureV4 {
28
29
let accessKey : String
29
30
let secretKey : String
30
31
31
- //used for unit tests
32
32
var unitTestDate : Date ?
33
33
34
34
var amzDate : String {
@@ -38,17 +38,16 @@ public struct AWSSignatureV4 {
38
38
return dateFormatter. string ( from: unitTestDate ?? Date ( ) )
39
39
}
40
40
41
- //TODO(Brett): public init
42
- init (
41
+ public init (
43
42
service: String ,
44
43
host: String ,
45
- region: String ,
44
+ region: Region ,
46
45
accessKey: String ,
47
46
secretKey: String
48
47
) {
49
48
self . service = service
50
49
self . host = host
51
- self . region = region
50
+ self . region = region. rawValue
52
51
self . accessKey = accessKey
53
52
self . secretKey = secretKey
54
53
}
@@ -59,7 +58,12 @@ public struct AWSSignatureV4 {
59
58
scope: String ,
60
59
canonicalHash: String
61
60
) -> String {
62
- return " \( algorithm) \n \( date) \n \( scope) \n \( canonicalHash) "
61
+ return [
62
+ algorithm,
63
+ date,
64
+ scope,
65
+ canonicalHash
66
+ ] . joined ( separator: " \n " )
63
67
}
64
68
65
69
func getSignature( _ stringToSign: String ) throws -> String {
@@ -73,28 +77,41 @@ public struct AWSSignatureV4 {
73
77
}
74
78
75
79
func getCredentialScope( ) -> String {
76
- return " \( dateStamp ( ) ) / \( self . region) / \( self . service) /aws4_request "
80
+ return [
81
+ dateStamp ( ) ,
82
+ region,
83
+ service,
84
+ " aws4_request "
85
+ ] . joined ( separator: " / " )
77
86
}
78
87
79
- func getCanonicalRequest( method: Method , path: String , query: String = " " ) throws -> String {
88
+ func getCanonicalRequest(
89
+ payload: Payload ,
90
+ method: Method ,
91
+ path: String ,
92
+ query: String ,
93
+ headers: [ String : String ] = [ : ]
94
+ ) throws -> String {
80
95
let path = try path. percentEncode ( allowing: Byte . awsPathAllowed)
81
96
let query = try query. percentEncode ( allowing: Byte . awsQueryAllowed)
82
-
83
- var request : String = " "
97
+ let payloadHash = try payload. hashed ( )
84
98
85
- let headers = " host: \( host) \n x-amz-date: \( amzDate) \n "
86
- let signedHeaders = " host;x-amz-date "
87
- let payload : Payload = . none
88
- var payloadHash : String
89
-
90
- do {
91
- payloadHash = try payload. hashed ( )
92
- request = " \( method. rawValue) \n \( path) \n \( query) \n \( headers) \n \( signedHeaders) \n \( payloadHash) "
93
- } catch {
94
-
95
- }
96
-
97
- return request
99
+ var headers = headers
100
+ generateHeadersToSign ( headers: & headers, host: host, hash: payloadHash)
101
+
102
+ let sortedHeaders = alphabetize ( headers)
103
+ let canonicalHeaders = createCanonicalHeaders ( sortedHeaders)
104
+ let headersToSign = sortedHeaders. map { $0. key. lowercased ( ) } . joined ( separator: " ; " )
105
+
106
+ return [
107
+ method. rawValue,
108
+ path,
109
+ query,
110
+ canonicalHeaders,
111
+ " " ,
112
+ headersToSign,
113
+ payloadHash
114
+ ] . joined ( separator: " \n " )
98
115
}
99
116
100
117
func dateStamp( ) -> String {
@@ -105,6 +122,53 @@ public struct AWSSignatureV4 {
105
122
}
106
123
}
107
124
125
+ extension AWSSignatureV4 {
126
+ func generateHeadersToSign(
127
+ headers: inout [ String : String ] ,
128
+ host: String ,
129
+ hash: String
130
+ ) {
131
+ headers [ " host " ] = host
132
+ headers [ " X-Amz-Date " ] = amzDate
133
+
134
+ if hash != " UNSIGNED-PAYLOAD " {
135
+ headers [ " x-amz-content-sha256 " ] = hash
136
+ }
137
+ }
138
+
139
+ func alphabetize( _ dict: [ String : String ] ) -> [ ( key: String , value: String ) ] {
140
+ return dict. sorted ( by: { $0. 0 . lowercased ( ) < $1. 0 . lowercased ( ) } )
141
+ }
142
+
143
+ func createCanonicalHeaders( _ headers: [ ( key: String , value: String ) ] ) -> String {
144
+ return headers. map {
145
+ " \( $0. key. lowercased ( ) ) : \( $0. value) "
146
+ } . joined ( separator: " \n " )
147
+ }
148
+
149
+ func signPayload(
150
+ _ payload: Payload ,
151
+ mime: String ? ,
152
+ headers: inout [ HeaderKey : String ]
153
+ ) throws {
154
+ /*let contentLength: Int
155
+
156
+ switch payload {
157
+ case .bytes(let bytes):
158
+ contentLength = bytes.count
159
+ default:
160
+ contentLength = 0
161
+ }
162
+
163
+ headers["Content-Length"] = "\(contentLength)"
164
+ if let mime = mime {
165
+ headers["Content-Type"] = mime
166
+ }
167
+
168
+ headers["x-amz-content-sha256"] = try payload.hashed()*/
169
+ }
170
+ }
171
+
108
172
extension AWSSignatureV4 {
109
173
public func sign(
110
174
payload: Payload = . none,
@@ -117,11 +181,12 @@ extension AWSSignatureV4 {
117
181
let credentialScope = getCredentialScope ( )
118
182
119
183
let canonicalRequest = try getCanonicalRequest (
184
+ payload: payload,
120
185
method: method,
121
186
path: path,
122
187
query: query ?? " "
123
188
)
124
-
189
+
125
190
let canonicalHash = try Hash . make ( . sha256, canonicalRequest) . hexString
126
191
127
192
let stringToSign = getStringToSign (
@@ -133,17 +198,12 @@ extension AWSSignatureV4 {
133
198
134
199
let signature = try getSignature ( stringToSign)
135
200
136
- let authorizationHeader = " \( algorithm) Credential= \( accessKey) / \( credentialScope) , SignedHeaders=host;x-amz-date, Signature= \( signature) "
201
+ let authorizationHeader = " \( algorithm) Credential= \( accessKey) / \( credentialScope) , SignedHeaders=host;x-amz-content-sha256;x-amz- date, Signature= \( signature) "
137
202
138
203
return [
139
204
" X-Amz-Date " : amzDate,
205
+ " x-amz-content-sha256 " : try payload. hashed ( ) ,
140
206
" Authorization " : authorizationHeader
141
207
]
142
208
}
143
209
}
144
-
145
- extension Data {
146
- func hexEncodedString( ) -> String {
147
- return map { String ( format: " %02hhx " , $0) } . joined ( )
148
- }
149
- }
0 commit comments