137 lines
5.1 KiB
Swift
137 lines
5.1 KiB
Swift
//
|
|
// Copyright Amazon.com Inc. or its affiliates.
|
|
// All Rights Reserved.
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
import Foundation
|
|
import CryptoKit
|
|
|
|
struct CognitoUserPoolASF: AdvancedSecurityBehavior {
|
|
|
|
static let appNameKey = "ApplicationName"
|
|
static let targetSDKKey = "ApplicationTargetSdk"
|
|
static let appVersionKey = "ApplicationVersion"
|
|
static let deviceFingerPrintKey = "DeviceFingerprint"
|
|
static let deviceNameKey = "DeviceName"
|
|
static let buildTypeKey = "BuildType"
|
|
static let releaseVersionKey = "DeviceOsReleaseVersion"
|
|
static let deviceIdKey = "DeviceId"
|
|
static let thirdPartyDeviceIdKey = "ThirdPartyDeviceId"
|
|
static let platformKey = "Platform"
|
|
static let timezoneKey = "ClientTimezone"
|
|
static let deviceHeightKey = "ScreenHeightPixels"
|
|
static let deviceWidthKey = "ScreenWidthPixels"
|
|
static let deviceLanguageKey = "DeviceLanguage"
|
|
static let phoneTypeKey = "PhoneType"
|
|
static let asfVersion = "IOS20171114"
|
|
|
|
func userContextData(for username: String = "unknown",
|
|
deviceInfo: ASFDeviceBehavior,
|
|
appInfo: ASFAppInfoBehavior,
|
|
configuration: UserPoolConfigurationData) throws -> String {
|
|
|
|
let contextData = prepareUserContextData(deviceInfo: deviceInfo, appInfo: appInfo)
|
|
let payload = try prepareJsonPayload(username: username,
|
|
contextData: contextData,
|
|
userPoolId: configuration.poolId)
|
|
let signature = try calculateSecretHash(contextJson: payload,
|
|
clientId: configuration.clientId)
|
|
let result = try prepareJsonResult(payload: payload, signature: signature)
|
|
return result
|
|
}
|
|
|
|
func prepareUserContextData(deviceInfo: ASFDeviceBehavior,
|
|
appInfo: ASFAppInfoBehavior) -> [String: String] {
|
|
var build = "release"
|
|
#if DEBUG
|
|
build = "debug"
|
|
#endif
|
|
let fingerPrint = deviceInfo.deviceInfo()
|
|
var contextData: [String: String] = [
|
|
Self.targetSDKKey: appInfo.targetSDK,
|
|
Self.appVersionKey: appInfo.version,
|
|
Self.deviceNameKey: deviceInfo.name,
|
|
Self.phoneTypeKey: deviceInfo.type,
|
|
Self.deviceIdKey: deviceInfo.id,
|
|
Self.releaseVersionKey: deviceInfo.version,
|
|
Self.platformKey: deviceInfo.platform,
|
|
Self.buildTypeKey: build,
|
|
Self.timezoneKey: timeZoneOffet(),
|
|
Self.deviceHeightKey: deviceInfo.height,
|
|
Self.deviceWidthKey: deviceInfo.width,
|
|
Self.deviceLanguageKey: deviceInfo.locale,
|
|
Self.deviceFingerPrintKey: fingerPrint
|
|
]
|
|
if let appName = appInfo.name {
|
|
contextData[Self.appNameKey] = appName
|
|
}
|
|
if let thirdPartyDeviceIdKey = deviceInfo.thirdPartyId {
|
|
contextData[Self.thirdPartyDeviceIdKey] = thirdPartyDeviceIdKey
|
|
}
|
|
return contextData
|
|
}
|
|
|
|
func prepareJsonPayload(username: String,
|
|
contextData: [String: String],
|
|
userPoolId: String) throws -> String {
|
|
let timestamp = String(format: "%lli", floor(Date().timeIntervalSince1970 * 1000))
|
|
let payload = [
|
|
"contextData": contextData,
|
|
"username": username,
|
|
"userPoolId": userPoolId,
|
|
"timestamp": timestamp
|
|
] as [String: Any]
|
|
let jsonData = try JSONSerialization.data(withJSONObject: payload)
|
|
guard let jsonString = String(data: jsonData, encoding: .utf8) else {
|
|
throw ASFError.stringConversion
|
|
}
|
|
return jsonString
|
|
}
|
|
|
|
func timeZoneOffet(seconds: Int = TimeZone.current.secondsFromGMT()) -> String {
|
|
|
|
let hours = seconds/3600
|
|
let minutes = abs(seconds/60) % 60
|
|
return String(format: "%+.2d:%.2d", hours, minutes)
|
|
}
|
|
|
|
func calculateSecretHash(contextJson: String, clientId: String) throws -> String {
|
|
guard let keyData = clientId.data(using: .ascii) else {
|
|
throw ASFError.hashKey
|
|
}
|
|
let key = SymmetricKey(data: keyData)
|
|
let content = "\(Self.asfVersion)\(contextJson)"
|
|
guard let data = content.data(using: .utf8) else {
|
|
throw ASFError.hashData
|
|
}
|
|
let hmac = HMAC<SHA256>.authenticationCode(for: data, using: key)
|
|
let hmacData = Data(hmac)
|
|
return hmacData.base64EncodedString()
|
|
}
|
|
|
|
func prepareJsonResult(payload: String, signature: String) throws -> String {
|
|
let result = [
|
|
"payload": payload,
|
|
"version": Self.asfVersion,
|
|
"signature": signature
|
|
]
|
|
let jsonData = try JSONSerialization.data(withJSONObject: result)
|
|
guard let jsonString = String(data: jsonData, encoding: .utf8) else {
|
|
throw ASFError.stringConversion
|
|
}
|
|
guard let data = jsonString.data(using: .utf8) else {
|
|
throw ASFError.dataConversion
|
|
}
|
|
return data.base64EncodedString()
|
|
}
|
|
}
|
|
|
|
enum ASFError: Error {
|
|
case stringConversion
|
|
case dataConversion
|
|
case hashKey
|
|
case hashData
|
|
}
|