I really try hard to solve this problem on my project. But I can't.
I try to sign URLRequest headers and body by Amazon AWS Signature Version 4. And send it to server with Alamofire SDK.
But only headers without body work correctly.
I dunno why I get the response "403 forbidden" from server when I put httpBody into URLRequest.
Here is my source.
...
var request = URLRequest(url: convUrl)
if let jsonString = String(data: jsonData, encoding: .utf8) {
AWS4Signer().aws4Sign(request: &request, method: httpMethodType, payload: jsonString, query: [:], path: url.replacingOccurrences(of: self.url.host, with: ""))
}
AF.request(request).responseData { response in
}
func aws4Sign(request: inout URLRequest, method: HttpMethodType, payload: String?, query: [String:String]?, path: String) {
var headers = [String:String]()
let requested_date_time = self.getAmzDate(date: Date())
headers["x-amz-date"] = requested_date_time
// x-amz-content-sha256
var payload_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // empty
if let payload {
let utf8Data = payload.data(using: .utf8) ?? Data()
payload_hash = SHA256.hash(data: utf8Data).hexDigest()
if !payload.isEmpty {
headers["x-amz-content-sha256"] = payload_hash
request.httpBody = utf8Data
}
}
let canonical_querystring = ""
headers["host"] = "hosthost.com"
let sortedHeaders = headers.filter { ["host", "x-amz-date", "x-amz-content-sha256"].contains($0.key) }
.sorted { $0.key < $1.key }
let canonical_headers = sortedHeaders.map { "\($0.key.lowercased()):\($0.value)" }.joined(separator: "\n") + "\n"
let signed_headers = sortedHeaders.map { $0.key.lowercased() }.joined(separator: ";")
let canonical_uri = path
let canonical_request = "\(method.rawValue)\n\(canonical_uri)\n\(canonical_querystring)\n\(canonical_headers)\n\(signed_headers)\n\(payload_hash)"
let hashed_canonical_request = SHA256.hash(data: canonical_request.data(using: .utf8)!).hexDigest()
let algorithm = "AWS4-HMAC-SHA256"
let nowDate = Date().getString(format: "YYYYMMdd", timeZone: TimeZone(identifier: "UTC")!)
let credential_scope = "\(nowDate)/\(Const.key.awsRegion)/\(Const.key.awsServiceType)/aws4_request"
let string_to_sign = "\(algorithm)\n\(requested_date_time)\n\(credential_scope)\n\(hashed_canonical_request)"
let signing_key = self.getSignatureKey(key: Const.key.awsSecretAccessKey, dateStamp: nowDate, regionName: Const.key.awsRegion, serviceName: Const.key.awsServiceType)
let signature = self.sign(key: signing_key, msg: string_to_sign).hexDigest()
let authorization_header = "\(algorithm) Credential=\(Const.key.awsAccessKeyID)/\(credential_scope), SignedHeaders=\(signed_headers), Signature=\(signature)"
request.addValue(requested_date_time, forHTTPHeaderField: "X-amz-date")
request.addValue(authorization_header, forHTTPHeaderField: "Authorization")
}
func getSignatureKey(key: String, dateStamp: String, regionName: String, serviceName: String) -> Data {
let keyData = Data("AWS4\(key)".utf8)
let dateStampData = Data(dateStamp.utf8)
let regionNameData = Data(regionName.utf8)
let serviceNameData = Data(serviceName.utf8)
let signingData = Data("aws4_request".utf8)
var symmetricKey = SymmetricKey(data: keyData)
let dateSHA256 = HMAC<SHA256>.authenticationCode(for: dateStampData, using: symmetricKey)
symmetricKey = SymmetricKey(data: Data(dateSHA256))
let regionSHA256 = HMAC<SHA256>.authenticationCode(for: regionNameData, using: symmetricKey)
symmetricKey = SymmetricKey(data: Data(regionSHA256))
let serviceNameSHA256 = HMAC<SHA256>.authenticationCode(for: serviceNameData, using: symmetricKey)
symmetricKey = SymmetricKey(data: Data(serviceNameSHA256))
let signingSHA256 = HMAC<SHA256>.authenticationCode(for: signingData, using: symmetricKey)
let skeyString = keyData.map { String(format: "%02hhx", $0) }.joined()
let kDateString = Data(dateSHA256).map { String(format: "%02hhx", $0) }.joined()
let kRegionString = Data(regionSHA256).map { String(format: "%02hhx", $0) }.joined()
let kServiceString = Data(serviceNameSHA256).map { String(format: "%02hhx", $0) }.joined()
let kSigningString = Data(signingSHA256).map { String(format: "%02hhx", $0) }.joined()
return Data(signingSHA256)
}
func sign(key: Data, msg: String) -> Data {
let hmac = HMAC<SHA256>.authenticationCode(for: msg.data(using: .utf8)!, using: SymmetricKey(data: key))
return Data(hmac)
}
func getAmzDate(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
formatter.timeZone = TimeZone(identifier: "UTC")
return formatter.string(from: date)
}
func getCanonicalQuerystring(query: [String: String]) -> String {
let sortedParameters = query.sorted { $0.0 < $1.0 }
let encodedQueryParameters = sortedParameters.map { (key, value) in
return "\(key.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")=\(value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")"
}
let canonicalQuerystring = encodedQueryParameters.joined(separator: "&")
return canonicalQuerystring
}
The "Authorization" data has it.
"AWS4-HMAC-SHA256 Credential=ACCESS_KEY_ID/20230213/ap-northeast-2/execute-api/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=cfa667aa18472d3d5a419f67aa2c321977428abac391a33f9f3e31839bcb4665"
ACCESS_KEY_ID is my access_key_id of AWS.
So far, only the POST method is tested.