Skip to content

Instantly share code, notes, and snippets.

@lewisjkl
Last active January 3, 2024 08:32
Show Gist options
  • Save lewisjkl/1e5b3fea12c8d54314c4cc8c99bef348 to your computer and use it in GitHub Desktop.
Save lewisjkl/1e5b3fea12c8d54314c4cc8c99bef348 to your computer and use it in GitHub Desktop.
aws4 request signing iOS Swift 3
// 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()
}
}
@lewisjkl
Copy link
Author

Note that CryptoSwift is a required dependency for this algorithm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment