diff --git a/AcceptSDKSampleApp/ViewController.swift b/AcceptSDKSampleApp/ViewController.swift index d5afa8c..316fdf1 100644 --- a/AcceptSDKSampleApp/ViewController.swift +++ b/AcceptSDKSampleApp/ViewController.swift @@ -168,23 +168,78 @@ class ViewController: UIViewController, UITextFieldDelegate { handler!.getTokenWithRequest(request, successHandler: { (inResponse:AcceptSDKTokenResponse) -> () in DispatchQueue.main.async(execute: { + // SECURITY (AISAST-c0e043bb): App-owned defensive parsing. The + // SDK's response model uses implicitly-unwrapped-optionals (IUOs) + // for server-controlled fields — invoking the getters on a + // MitM-injected response that omits 'opaqueData', + // 'messages.message', etc. would trap. This block lives in + // APP-OWNED code (not Pods/), so it survives `pod install` and + // shields the host app even if the SDK regenerates from upstream. self.updateTokenButton(true) - self.activityIndicatorAcceptSDKDemo.stopAnimating() - print("Token--->%@", inResponse.getOpaqueData().getDataValue()) - var output = String(format: "Response: %@\nData Value: %@ \nDescription: %@", inResponse.getMessages().getResultCode(), inResponse.getOpaqueData().getDataValue(), inResponse.getOpaqueData().getDataDescriptor()) - output = output + String(format: "\nMessage Code: %@\nMessage Text: %@", inResponse.getMessages().getMessages()[0].getCode(), inResponse.getMessages().getMessages()[0].getText()) + + let messages = inResponse.getMessages() + let resultCode = messages?.getResultCode() + let opaqueData = inResponse.getOpaqueData() + let dataValue = opaqueData?.getDataValue() + let dataDescriptor = opaqueData?.getDataDescriptor() + let messageArr = messages?.getMessages() ?? [] + let firstMsg: Message? = messageArr.first + let messageCode = firstMsg?.getCode() + let messageText = firstMsg?.getText() + + guard let _ = resultCode, + let _ = opaqueData, + let _ = dataValue, + let _ = dataDescriptor else { + // MitM or malformed response — refuse to render token output. + // SECURITY: Do not log the opaque payment token; even on + // failure, avoid echoing partial server data to logs. + print("Tokenization success: malformed payload (missing opaqueData/resultCode)") + self.textViewShowResults.text = "Response received but payload was malformed (missing opaqueData). Tokenization aborted." + self.textViewShowResults.textColor = UIColor.red + return + } + + // SECURITY: Do not log the opaque payment token — it is a + // single-use payment nonce that can be used to charge the + // customer's card. + print("Tokenization success: resultCode=\(resultCode ?? "")") + var output = String(format: "Response: %@\nData Value: %@ \nDescription: %@", + resultCode ?? "", + dataValue ?? "", + dataDescriptor ?? "") + output = output + String(format: "\nMessage Code: %@\nMessage Text: %@", + messageCode ?? "", + messageText ?? "") self.textViewShowResults.text = output self.textViewShowResults.textColor = UIColor.green }) }) { (inError:AcceptSDKErrorResponse) -> () in + // SECURITY (AISAST-c0e043bb): App-owned defensive parsing for + // failure path. The SDK's failure response can carry an empty + // messages array under MitM preconditions; indexing [0] on an + // empty array raises a Swift runtime trap. Bounds-check before + // subscript. self.activityIndicatorAcceptSDKDemo.stopAnimating() self.updateTokenButton(true) - let output = String(format: "Response: %@\nError code: %@\nError text: %@", inError.getMessages().getResultCode(), inError.getMessages().getMessages()[0].getCode(), inError.getMessages().getMessages()[0].getText()) + let messagesObj = inError.getMessages() + let messageArr = messagesObj?.getMessages() ?? [] + let resultCode = messagesObj?.getResultCode() + let firstMsg: Message? = messageArr.first + let errorCode = firstMsg?.getCode() + let errorText = firstMsg?.getText() + + // SECURITY: Do not log raw server error text — error messages may + // contain sensitive context. Log only the structured error code. + print("Tokenization failed: errorCode=\(errorCode ?? "")") + let output = String(format: "Response: %@\nError code: %@\nError text: %@", + resultCode ?? "", + errorCode ?? "", + errorText ?? "") self.textViewShowResults.text = output self.textViewShowResults.textColor = UIColor.red - print(output) } } diff --git a/Pods/AuthorizeNetAccept/AcceptSDK/Interface/AcceptSDKTokenInterface.swift b/Pods/AuthorizeNetAccept/AcceptSDK/Interface/AcceptSDKTokenInterface.swift index b7fa692..b925456 100644 --- a/Pods/AuthorizeNetAccept/AcceptSDK/Interface/AcceptSDKTokenInterface.swift +++ b/Pods/AuthorizeNetAccept/AcceptSDK/Interface/AcceptSDKTokenInterface.swift @@ -34,8 +34,14 @@ class AcceptSDKTokenInterface: AcceptSDKBaseInterface { } fileprivate func handleResponse(_ response:Dictionary,successHandler:(_ isSuccess:Bool)->(),failureHandler:(_ isSuccess:Bool)->()) { - let messagesDict = response[AcceptSDKResponse.kMessagesKey] - let statusCode = messagesDict![AcceptSDKResponse.kResultCodeKey] as? String + // SECURITY (AISAST-c0e043bb): Use optional binding instead of force-unwrap + // to prevent app crash when a (potentially MitM-injected) response omits + // the 'messages' key. Force-unwrap of nil is a Swift runtime trap. + guard let messagesDict = response[AcceptSDKResponse.kMessagesKey] as? Dictionary else { + failureHandler(false) + return + } + let statusCode = messagesDict[AcceptSDKResponse.kResultCodeKey] as? String if statusCode == AcceptSDKResponse.kResultCodeOkValueKey { successHandler(true) } else if statusCode == AcceptSDKResponse.kResultCodeErrorValueKey { diff --git a/Pods/AuthorizeNetAccept/AcceptSDK/Network/AccepSDKtHttp.swift b/Pods/AuthorizeNetAccept/AcceptSDK/Network/AccepSDKtHttp.swift index 5baa4a4..b970cae 100644 --- a/Pods/AuthorizeNetAccept/AcceptSDK/Network/AccepSDKtHttp.swift +++ b/Pods/AuthorizeNetAccept/AcceptSDK/Network/AccepSDKtHttp.swift @@ -100,15 +100,33 @@ class HTTP: NSObject, URLSessionDelegate { httpResponse.error = error as NSError? } else if let castedResponse = response as? HTTPURLResponse { - let bodyDict = self.deserializeData(taskData!) + // SECURITY (AISAST-c0e043bb): Guard server-controlled inputs to + // prevent app crash on MitM-injected or malformed responses. + // taskData can be nil on legitimate empty responses; treat that + // as a transport failure rather than crashing the host app. + guard let safeData = taskData else { + httpResponse.error = NSError(domain: "EmptyResponseBody", + code: castedResponse.statusCode, userInfo: nil) + semaphore.signal() + return + } + let bodyDict = self.deserializeData(safeData) if HTTPStatusCode.kHTTPSuccessCode == castedResponse.statusCode || HTTPStatusCode.kHTTPCreationSuccessCode == castedResponse.statusCode { httpResponse.body = bodyDict } else { - let (errorMessage) = self.getErrorResponse(bodyDict!) - if let message = errorMessage { - httpResponse.error = NSError(domain: message, code: castedResponse.statusCode, userInfo:[NSLocalizedDescriptionKey:message,HTTPErrorResponseCode.kErrorDictionaryKey:bodyDict!]) - }else { + // SECURITY (AISAST-c0e043bb): Use optional binding instead of + // force-unwrap. deserializeData() can legitimately return nil + // on malformed/non-dictionary JSON; force-unwrapping that + // would crash the host app under MitM preconditions. + if let safeBodyDict = bodyDict { + let (errorMessage) = self.getErrorResponse(safeBodyDict) + if let message = errorMessage { + httpResponse.error = NSError(domain: message, code: castedResponse.statusCode, userInfo:[NSLocalizedDescriptionKey:message,HTTPErrorResponseCode.kErrorDictionaryKey:safeBodyDict]) + } else { + httpResponse.error = NSError(domain: "BadResponse", code: castedResponse.statusCode, userInfo:nil) + } + } else { httpResponse.error = NSError(domain: "BadResponse", code: castedResponse.statusCode, userInfo:nil) } } @@ -138,15 +156,20 @@ class HTTP: NSObject, URLSessionDelegate { } fileprivate func deserializeData (_ data : Data) -> Dictionary? { - var jsonDict:Dictionary = [:] - do{ - jsonDict = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as! Dictionary - } - catch _ as NSError{ - //todo handle error + // SECURITY (AISAST-c0e043bb): Use conditional cast (as?) instead of forced + // downcast (as!). A forced downcast is a Swift runtime trap (NOT a + // throw), so the surrounding catch block CANNOT catch it — a + // MitM-injected response containing a valid top-level JSON array, + // scalar, or null would crash the host app before any other handler + // runs. Returning nil on type mismatch lets callers handle the + // malformed-response case gracefully. + do { + let parsed = try JSONSerialization.jsonObject(with: data, + options: JSONSerialization.ReadingOptions.mutableContainers) + return parsed as? Dictionary + } catch _ as NSError { + return nil } - return jsonDict - } } diff --git a/Pods/AuthorizeNetAccept/AcceptSDK/Network/AcceptSDKHttpConnection.swift b/Pods/AuthorizeNetAccept/AcceptSDK/Network/AcceptSDKHttpConnection.swift index 33a59c2..84f8500 100644 --- a/Pods/AuthorizeNetAccept/AcceptSDK/Network/AcceptSDKHttpConnection.swift +++ b/Pods/AuthorizeNetAccept/AcceptSDK/Network/AcceptSDKHttpConnection.swift @@ -33,11 +33,26 @@ class HttpConnection{ let response : HTTPResponse = self.http!.request(request) self.responseQueue.async(execute: { - if response.error != nil { - failure(response.error!) - } - else { - success(response.body!) + // SECURITY (AISAST-c0e043bb): Use optional binding for both + // response.error and response.body. Both are server-controlled + // (error is constructed from server HTTP status; body is the + // parsed response dictionary). Force-unwrapping either would + // crash the host app under MitM preconditions: + // - response.error! traps if the success path forgot to set + // error before invoking failure + // - response.body! traps when deserializeData() returned nil + // (e.g. MitM-injected top-level JSON array, scalar, or + // non-dictionary response) + if let err = response.error { + failure(err) + } else if let body = response.body { + success(body) + } else { + failure(NSError( + domain: "MalformedResponseBody", + code: HTTPErrorResponseCode.apiErrorResponseCode, + userInfo: [NSLocalizedDescriptionKey: + "Response body was nil or not a JSON object."])) } }) }) diff --git a/Pods/AuthorizeNetAccept/AcceptSDK/Response/AcceptSDKErrorResponse.swift b/Pods/AuthorizeNetAccept/AcceptSDK/Response/AcceptSDKErrorResponse.swift index 444491c..39968b8 100644 --- a/Pods/AuthorizeNetAccept/AcceptSDK/Response/AcceptSDKErrorResponse.swift +++ b/Pods/AuthorizeNetAccept/AcceptSDK/Response/AcceptSDKErrorResponse.swift @@ -9,7 +9,10 @@ import Foundation open class AcceptSDKErrorResponse: NSObject { - fileprivate var messages:Messages! + // SECURITY (AISAST-c0e043bb): Storage stays nilable so absent keys in a + // MitM-injected error response do not produce a non-nil-but-uninitialized + // IUO. The getter returns optional so callers must nil-check. + fileprivate var messages: Messages? @objc convenience init(inDict:Dictionary) { self.init() @@ -39,7 +42,7 @@ open class AcceptSDKErrorResponse: NSObject { self.messages = Messages(withMessage: withMessage) } - @objc open func getMessages() -> Messages { + @objc open func getMessages() -> Messages? { return self.messages } } diff --git a/Pods/AuthorizeNetAccept/AcceptSDK/Response/AcceptSDKTokenResponse.swift b/Pods/AuthorizeNetAccept/AcceptSDK/Response/AcceptSDKTokenResponse.swift index 0686db0..7e33efd 100644 --- a/Pods/AuthorizeNetAccept/AcceptSDK/Response/AcceptSDKTokenResponse.swift +++ b/Pods/AuthorizeNetAccept/AcceptSDK/Response/AcceptSDKTokenResponse.swift @@ -20,8 +20,10 @@ struct AcceptSDKTokenResponseKeys { } open class AcceptSDKTokenResponse: NSObject { - fileprivate var opaqueData:OpaqueData! - fileprivate var messages:Messages! + // SECURITY (AISAST-c0e043bb): Storage stays nilable so absent keys in a + // MitM-injected response do not produce a non-nil-but-uninitialized IUO. + fileprivate var opaqueData: OpaqueData? + fileprivate var messages: Messages? @objc convenience init(inDict:Dictionary) { self.init() @@ -35,11 +37,13 @@ open class AcceptSDKTokenResponse: NSObject { } } - @objc open func getOpaqueData() -> OpaqueData { + // SECURITY (AISAST-c0e043bb): Return optionals so callers must nil-check. + // The previous IUO return type silently trapped on absent server fields. + @objc open func getOpaqueData() -> OpaqueData? { return self.opaqueData } - @objc open func getMessages() -> Messages { + @objc open func getMessages() -> Messages? { return self.messages } } @@ -59,17 +63,20 @@ open class OpaqueData: NSObject { } } - @objc open func getDataDescriptor()->String { - return self.dataDescriptor! + // SECURITY (AISAST-c0e043bb): Return optionals. Storage already used '?' + // but the getters force-unwrapped, which trapped on absent server keys. + @objc open func getDataDescriptor() -> String? { + return self.dataDescriptor } - @objc open func getDataValue() -> String { - return self.dataValue! + @objc open func getDataValue() -> String? { + return self.dataValue } } open class Messages: NSObject { - fileprivate var resultCode: String! + // SECURITY (AISAST-c0e043bb): Storage stays nilable; getter returns optional. + fileprivate var resultCode: String? fileprivate var messages: Array = [] @objc convenience init (inDict:Dictionary) { @@ -126,7 +133,9 @@ open class Messages: NSObject { self.messages.append(withMessage) } - @objc open func getResultCode() -> String { + // SECURITY (AISAST-c0e043bb): Return optional. A MitM-injected response + // omitting 'resultCode' would otherwise trap via the IUO read. + @objc open func getResultCode() -> String? { return self.resultCode } @@ -136,8 +145,9 @@ open class Messages: NSObject { } open class Message: NSObject { - fileprivate var code:String! - fileprivate var text:String! + // SECURITY (AISAST-c0e043bb): Storage stays nilable; getters return optionals. + fileprivate var code: String? + fileprivate var text: String? @objc convenience init (inDict:Dictionary) { self.init() @@ -179,11 +189,14 @@ open class Message: NSObject { self.text = inErrorMessage } - @objc open func getCode() -> String { + // SECURITY (AISAST-c0e043bb): Return optionals. A MitM-injected error + // response omitting 'code' or 'text' would otherwise trap via the + // IUO read. + @objc open func getCode() -> String? { return self.code } - @objc open func getText() -> String { + @objc open func getText() -> String? { return self.text } }