Last active
January 3, 2024 08:32
-
-
Save lewisjkl/1e5b3fea12c8d54314c4cc8c99bef348 to your computer and use it in GitHub Desktop.
aws4 request signing iOS Swift 3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is free and unencumbered software released into the public domain. | |
// Anyone is free to copy, modify, publish, use, compile, sell, or | |
// distribute this software, either in source code form or as a compiled | |
// binary, for any purpose, commercial or non-commercial, and by any | |
// means. | |
// In jurisdictions that recognize copyright laws, the author or authors | |
// of this software dedicate any and all copyright interest in the | |
// software to the public domain. We make this dedication for the benefit | |
// of the public at large and to the detriment of our heirs and | |
// successors. We intend this dedication to be an overt act of | |
// relinquishment in perpetuity of all present and future rights to this | |
// software under copyright law. | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
// OTHER DEALINGS IN THE SOFTWARE. | |
// For more information, please refer to <https://unlicense.org> | |
import Foundation | |
import CryptoSwift | |
class URLRequestSigner: NSObject { | |
private let hmacShaTypeString = "AWS4-HMAC-SHA256" | |
private let awsRegion = "us-east-1" | |
private let serviceType = "execute-api" | |
private let aws4Request = "aws4_request" | |
private let iso8601Formatter: DateFormatter = { | |
let formatter = DateFormatter() | |
formatter.calendar = Calendar(identifier: .iso8601) | |
formatter.locale = Locale(identifier: "en_US_POSIX") | |
formatter.timeZone = TimeZone(secondsFromGMT: 0) | |
formatter.dateFormat = "yyyyMMdd'T'HHmmssXXXXX" | |
return formatter | |
}() | |
private func iso8601() -> (full: String, short: String) { | |
let date = iso8601Formatter.string(from: Date()) | |
let index = date.index(date.startIndex, offsetBy: 8) | |
let shortDate = date.substring(to: index) | |
return (full: date, short: shortDate) | |
} | |
func sign(request: URLRequest, secretSigningKey: String, accessKeyId: String) -> URLRequest? { | |
var signedRequest = request | |
let date = iso8601() | |
guard let bodyData = signedRequest.httpBody, let body = String(data: bodyData, encoding: .utf8), let url = signedRequest.url, let host = url.host | |
else { return .none } | |
signedRequest.addValue(host, forHTTPHeaderField: "Host") | |
signedRequest.addValue(date.full, forHTTPHeaderField: "X-Amz-Date") | |
guard let headers = signedRequest.allHTTPHeaderFields, let method = signedRequest.httpMethod | |
else { return .none } | |
let signedHeaders = headers.map{ $0.key.lowercased() }.sorted().joined(separator: ";") | |
let canonicalRequestHash = [ | |
method, | |
url.path, | |
url.query ?? "", | |
headers.map{ $0.key.lowercased() + ":" + $0.value }.sorted().joined(separator: "\n"), | |
"", | |
signedHeaders, | |
body.sha256() | |
].joined(separator: "\n").sha256() | |
let credential = [date.short, awsRegion, serviceType, aws4Request].joined(separator: "/") | |
let stringToSign = [ | |
hmacShaTypeString, | |
date.full, | |
credential, | |
canonicalRequestHash | |
].joined(separator: "\n") | |
guard let signature = hmacStringToSign(stringToSign: stringToSign, secretSigningKey: secretSigningKey, shortDateString: date.short) | |
else { return .none } | |
let authorization = hmacShaTypeString + " Credential=" + accessKeyId + "/" + credential + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature | |
signedRequest.addValue(authorization, forHTTPHeaderField: "Authorization") | |
return signedRequest | |
} | |
private func hmacStringToSign(stringToSign: String, secretSigningKey: String, shortDateString: String) -> String? { | |
let k1 = "AWS4" + secretSigningKey | |
guard let sk1 = try? HMAC(key: [UInt8](k1.utf8), variant: .sha256).authenticate([UInt8](shortDateString.utf8)), | |
let sk2 = try? HMAC(key: sk1, variant: .sha256).authenticate([UInt8](awsRegion.utf8)), | |
let sk3 = try? HMAC(key: sk2, variant: .sha256).authenticate([UInt8](serviceType.utf8)), | |
let sk4 = try? HMAC(key: sk3, variant: .sha256).authenticate([UInt8](aws4Request.utf8)), | |
let signature = try? HMAC(key: sk4, variant: .sha256).authenticate([UInt8](stringToSign.utf8)) else { return .none } | |
return signature.toHexString() | |
} | |
} | |
// Usage Example | |
class HttpUtils: NSObject { | |
private static let graphqlUrl = "https://somesite.com/api/graphql" | |
static func makeSignedRequestToGraphql(query: String, completion: @escaping (_ response: [String:AnyObject]?) -> Void) { | |
let sessionConfig = URLSessionConfiguration.default | |
let session = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil) | |
guard let URL = URL(string: graphqlUrl) else { return } | |
var request = URLRequest(url: URL) | |
request.httpMethod = "POST" | |
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") | |
request.addValue(user.sessionId, forHTTPHeaderField: "X-Amz-Security-Token") | |
request.httpBody = query.data(using: String.Encoding.utf8) | |
guard let signedRequest = URLRequestSigner().sign(request: request, secretSigningKey: secretAccessKey, accessKeyId: accessKeyId) else { return } | |
let task = session.dataTask(with: signedRequest, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in | |
if let d = data, let jsonOpt = try? JSONSerialization.jsonObject(with: d, options: []) as? [String:AnyObject], let json = jsonOpt { | |
completion(json) | |
} else { | |
completion(.none) | |
} | |
}) | |
task.resume() | |
session.finishTasksAndInvalidate() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that CryptoSwift is a required dependency for this algorithm