When I access AWS gateway by calculating AWS signature by Alamofire. It responds error message "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."
Is there any way to access to AWS API gateway in swift 3. Could you please provide me some simple swift code if possible. Thanks in advance!
private var configuration:AWSServiceConfiguration?
private var awsCredential = AWSCredential()
private func apiGatewaySimple(){
let date = URLRequestSigner().iso8601()
let xAmzStamp = date.short
guard let URL = URL(string: "xxxxx") else { return }
var request = URLRequest(url: URL)
let url = request.url
let host = url?.host
let credentialProvider = AWSCognitoCredentialsProvider(regionType: .APNortheast1, identityPoolId: Constants.IDENTITY_POOL_ID)
let endpoint = AWSEndpoint.init(region: .APNortheast1, service: .APIGateway, url: url)
self.configuration = AWSServiceConfiguration.init(region: .APNortheast1, endpoint: endpoint, credentialsProvider: credentialProvider)
let signer : AWSSignatureV4Signer = AWSSignatureV4Signer(credentialsProvider: self.configuration?.credentialsProvider, endpoint: endpoint)
self.configuration?.requestInterceptors = [AWSNetworkingRequestInterceptor(),signer]
_ = self.configuration?.responseInterceptors
_ = self.configuration?.endpoint
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
let requestDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
_ = dateFormatter.string(from: requestDate)
let params:[String :Any]=["xxx" : "xxx" as AnyObject,
"xxxx" : "xxx" as AnyObject ]
let jsonData = try? JSONSerialization.data(withJSONObject: params, options: [])
let json = jsonData
request.httpMethod = HttpMethod.post.rawValue
request.httpBody = jsonData as! Data
request.setValue(date.full, forHTTPHeaderField: "X-Amz-Date")
request.setValue(self.awsCredential.sessionKey, forHTTPHeaderField: "X-Amz-Security-Token")
request.setValue(json?.count.description, forHTTPHeaderField: "Content-Length")
request.setValue(host, forHTTPHeaderField: "Host")
let contentLength = json?.count
let cfpath = request.url
let query = cfpath?.query
let hash = AWSSignatureSignerUtility.hash(request.httpBody)
let contentsha256 = AWSSignatureSignerUtility.hexEncode(NSString.init(data: hash!, encoding: String.Encoding.ascii.rawValue)! as String)
let canonicalRequest = AWSSignatureV4Signer.getCanonicalizedRequest(request.httpMethod, path: "xxxxx", query: query, headers: request.allHTTPHeaderFields, contentSha256: contentsha256)
let scope = String(format: "%@/%@/%@/%@", xAmzStamp, "ap-northeast-1","execute-api",AWSSignatureV4Terminator)
let signingCredential = String(format: "%@/%@", self.awsCredential.accessKey!,scope)
let awsSignatureSignerV4 = AWSSignatureV4Signer(credentialsProvider: configuration?.credentialsProvider,endpoint:endpoint)
_ = awsSignatureSignerV4?.interceptRequest(request as! NSMutableURLRequest)
let canonicalRequestHash = AWSSignatureSignerUtility.hashString(canonicalRequest)
let stringToSign = String(format: "%@/%@/%@/%@",AWSSignatureV4Algorithm,request.value(forHTTPHeaderField: "X-Amz-Date")!,scope,AWSSignatureSignerUtility.hexEncode(canonicalRequestHash))
let kSigning = AWSSignatureV4Signer.getV4DerivedKey(self.awsCredential.secretKey, date: xAmzStamp, region: "ap-northeast-1", service: "execute-api")
let signature = AWSSignatureSignerUtility.sha256HMac(with: stringToSign.data(using: .utf8), withKey: kSigning)
let credentialsAuthorizationHeader = String(format: "Credential=%@", signingCredential)
let signedHeadersAuthorizationHeader = String(format: "SignedHeaders=%@", AWSSignatureV4Signer.getSignedHeadersString(request.allHTTPHeaderFields))
let signatureAuthorizationHeader = String(format: "Signature=%@", AWSSignatureSignerUtility.hexEncode(NSString.init(data: signature!, encoding: String.Encoding.ascii.rawValue)! as String))
let authorization = String(format: "%@ %@, %@, %@", AWSSignatureV4Algorithm,credentialsAuthorizationHeader,signedHeadersAuthorizationHeader,signatureAuthorizationHeader)
let headers = [
"Content-Type": "application/json" ,
"x-amz-security-token" : self.awsCredential.sessionKey ?? "",
"x-amz-date" : date.full,
"Content-Length" : contentLength?.description ?? "0",
"Authorization" : authorization,
"Host" : host ?? ""
]
Alamofire.request(request).responseString{ (response: DataResponse<String>) in
print("\(response.result.value)")
}
}
The below code is working well for me.
func getUrlRequest(url:String,params:[String :Any]) -> URLRequest{
var headers = [String :String]()
var signedHeaders = [String:String]()
var bodyDigest = sha256("")
var urlForSigning = url
if urlForSigning.last == "/" {
urlForSigning = String(url.dropLast())
}
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = HttpMethod.post.rawValue
let body = try? JSONSerialization.data(withJSONObject: params, options: [])
let bodyString = NSString(data: body!, encoding: String.Encoding.utf8.rawValue)! as String
if (bodyString.trim().count > 0){
bodyDigest = sha256(bodyString)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = bodyString.data(using: String.Encoding.utf8)
}
signedHeaders = self.signedHeaders(url: URL(string: urlForSigning)!,
bodyDigest: bodyDigest, httpMethod: HttpMethod.post.rawValue)
headers["Authorization"] = signedHeaders["Authorization"]
headers["x-amz-date"] = signedHeaders["x-amz-date"]
headers["Host"] = signedHeaders["Host"]
headers["expiration"] = signedHeaders["expiration"]
headers["x-amz-security-token"] = signedHeaders["x-amz-security-token"]
headers["Content-Type"] = signedHeaders["Content-Type"]
for (k, v) in headers {
request.setValue(v, forHTTPHeaderField: k)
}
return request
}
func signedHeaders(url: URL, bodyDigest: String,
httpMethod: String, date: Date = Date()) -> [String: String] {
let datetime = timestamp(date)
let expirationTime = timestamp(self.expiration)
let port = ((url as NSURL).port != nil) ? ":" + String(describing: (url as NSURL).port!) : ""
var headers = ["x-amz-date": datetime, "Host": url.host! + port, "expiration": expirationTime, "x-amz-security-token" : self.sessionKey, "Content-Type": "application/json"]
headers["Authorization"] = authorization(accessKey, secretKey: secretKey, url: url, headers: headers,
datetime: datetime, httpMethod: httpMethod, bodyDigest: bodyDigest)
return headers
}
// MARK: Utilities
fileprivate func pathForURL(_ url: URL) -> String {
var path = url.path
if path.isEmpty {
path = "/"
} else {
// do this to preserve encoded path fragments, like those containing encoded '/' (%2F)
// NSURL.path decodes them and they are lost
var encodedPartsArray = [String]()
// get rid of 'http(s)://'
let fullURL = String(url.absoluteString[url.absoluteString.index(url.absoluteString.startIndex, offsetBy: 8)...])
var rawPath = String(fullURL[fullURL.range(of: "/")!.lowerBound...])
if rawPath.contains("?") {
rawPath = String(rawPath[..<rawPath.range(of: "?")!.lowerBound])
}
for part in rawPath.components(separatedBy: "/") {
if !part.isEmpty {
encodedPartsArray.append(Signer.encodeURIComponent(part))
}
}
path = "/" + encodedPartsArray.joined(separator: "/")
}
return path
}
func sha256(_ str: String) -> String {
let data = str.data(using: String.Encoding.utf8)!
return data.sha256().toHexString()
}
fileprivate func hmac(_ string: NSString, key: Data) -> Data {
let msg = string.data(using: String.Encoding.utf8.rawValue)!.bytes
let hmac:[UInt8] = try! HMAC(key: key.bytes, variant: .sha256).authenticate(msg)
return Data(bytes: hmac)
}
fileprivate func timestamp(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
formatter.timeZone = TimeZone(identifier: "UTC")
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter.string(from: date)
}
// MARK: Methods Ported from AWS SDK
fileprivate func authorization(_ accessKey: String, secretKey: String, url: URL, headers: Dictionary<String, String>,
datetime: String, httpMethod: String, bodyDigest: String) -> String {
let cred = credential(accessKey, datetime: datetime)
let shead = signedHeaders(headers)
let sig = signature(secretKey, url: url, headers: headers, datetime: datetime,
httpMethod: httpMethod, bodyDigest: bodyDigest)
return [
"AWS4-HMAC-SHA256 Credential=\(cred)",
"SignedHeaders=\(shead)",
"Signature=\(sig)",
].joined(separator: ", ")
}
fileprivate func credential(_ accessKey: String, datetime: String) -> String {
return "\(accessKey)/\(credentialScope(datetime))"
}
fileprivate func signedHeaders(_ headers: [String:String]) -> String {
var list = Array(headers.keys).map { $0.lowercased() }.sorted()
if let itemIndex = list.index(of: "authorization") {
list.remove(at: itemIndex)
}
return list.joined(separator: ";")
}
fileprivate func canonicalHeaders(_ headers: [String: String]) -> String {
var list = [String]()
let keys = Array(headers.keys).sorted {$0.localizedCompare($1) == ComparisonResult.orderedAscending}
for key in keys {
if key.caseInsensitiveCompare("authorization") != ComparisonResult.orderedSame {
// Note: This does not strip whitespace, but the spec says it should
list.append("\(key.lowercased()):\(headers[key]!)")
}
}
return list.joined(separator: "\n")
}
fileprivate func signature(_ secretKey: String, url: URL, headers: [String: String],
datetime: String, httpMethod: String, bodyDigest: String) -> String {
let secret = NSString(format: "AWS4%@", secretKey).data(using: String.Encoding.utf8.rawValue)!
let date = hmac(String(datetime[..<datetime.index(datetime.startIndex, offsetBy: 8)]) as NSString, key: secret)
let region = hmac(regionName as NSString, key: date)
let service = hmac(serviceName as NSString, key: region)
let credentials = hmac("aws4_request", key: service)
let string = stringToSign(datetime, url: url, headers: headers, httpMethod: httpMethod, bodyDigest: bodyDigest)
return hmac(string as NSString, key: credentials).toHexString()
}
fileprivate func credentialScope(_ datetime: String) -> String {
return [
String(datetime[..<datetime.index(datetime.startIndex, offsetBy: 8)]),
regionName,
serviceName,
"aws4_request"
].joined(separator: "/")
}
fileprivate func stringToSign(_ datetime: String, url: URL, headers: [String: String],
httpMethod: String, bodyDigest: String) -> String {
return [
"AWS4-HMAC-SHA256",
datetime,
credentialScope(datetime),
sha256(canonicalRequest(url, headers: headers, httpMethod: httpMethod, bodyDigest: bodyDigest)),
].joined(separator: "\n")
}
fileprivate func canonicalRequest(_ url: URL, headers: [String: String],
httpMethod: String, bodyDigest: String) -> String {
return [
httpMethod, // HTTP Method
pathForURL(url), // Resource Path
url.query ?? "", // Canonicalized Query String
"\(canonicalHeaders(headers))\n", // Canonicalized Header String (Plus a newline for some reason)
signedHeaders(headers), // Signed Headers String
bodyDigest, // Sha265 of Body
].joined(separator: "\n")
}
open static func encodeURIComponent(_ s: String) -> String {
let allowed = NSMutableCharacterSet.alphanumeric()
allowed.addCharacters(in: "-_.~")
//allowed.addCharactersInString("-_.!~*'()")
return s.addingPercentEncoding(withAllowedCharacters: allowed as CharacterSet) ?? ""
}