Install TLS certificates on web server iOS app - swift

I'm trying to integrate Swish payment in one of the apps I develop.
In order to be able to connect to the swish api I have to "set up TLS certificates from Swish Certificate Management and install it on "my" web server" according to the documentation.Here is the full technical documentation https://developer.getswish.se/merchants-api-manual/4-merchant-setup-process/.
The problem I don't understand is that I don't use a web server and I can't install those certificates there.
My app just offers some services for the client and after pressing the pay button should open the Swish app to finish the transaction in short.
What I tried is to make a post request to get the request token with which I can open the swish app loaded with the payment details.
I'm sure the problems are the certificates but couldn't find a good source explaining how to import(integrate) them.
let strURL = "https://mss.cpc.getswish.net/swish-cpcapi/api/v1/paymentrequests/"
guard let postURL = URL(string: strURL ) else {
print("Can't create url")
return
}
var request = URLRequest(url: postURL)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
let data: [String: Any] = [
"callbackUrl": "https://example.com/api/swishcb/paymentrequests",
"payeeAlias": "123xxxxxxx", // The Swish number of the payee. It needs to match with Merchant Swish number.
"amount": "100",
"currency": "SEK",
"message": "Test request to get the token"
]
do {
let jsonParams = try JSONSerialization.data(withJSONObject: data, options: [])
request.httpBody = jsonParams
} catch {
print("Error serializing the parameters of the post request")
return
}
// response will contain a Token, unique for each payment request
let config = URLSessionConfiguration.default
config.timeoutIntervalForResource = 120
config.timeoutIntervalForRequest = 120
let session = URLSession(configuration: config)
session.dataTask(with: request) { (data, response, error) in
print("Data \(data)")
print("Response \(response)")
if error != nil {
print("Error post request \(error?.localizedDescription)")
}
}.resume()
The error I got is:
Error post request Optional("An SSL error has occurred and a secure connection to the server cannot be made.")
018-12-21 12:24:55.549759+0200 tolk-24-7[7230:111102] [BoringSSL] boringssl_context_alert_callback_handler(3718) [C6.1:2][0x7fce4a77bf00] Alert level: fatal, description: handshake failure
2018-12-21 12:24:55.550047+0200 tolk-24-7[7230:111102] [BoringSSL] boringssl_session_errorlog(224) [C6.1:2][0x7fce4a77bf00] [boringssl_session_handshake_incomplete] SSL_ERROR_SSL(1): operation failed within the library
2018-12-21 12:24:55.550332+0200 tolk-24-7[7230:111102] [BoringSSL] boringssl_session_handshake_error_print(205) [C6.1:2][0x7fce4a77bf00] 140523985879704:error:10000410:SSL routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE:/BuildRoot/Library/Caches/com.apple.xbs/Sources/boringssl_Sim/boringssl-109.220.4/ssl/tls_record.cc:586:SSL alert number 40
2018-12-21 12:24:55.550585+0200 tolk-24-7[7230:111102] [BoringSSL] boringssl_context_get_error_code(3539) [C6.1:2][0x7fce4a77bf00] SSL_AD_HANDSHAKE_FAILURE
2018-12-21 12:24:55.552299+0200 tolk-24-7[7230:111102] TIC TCP Conn Failed [6:0x600002dd6c40]: 3:-9824 Err(-9824)
2018-12-21 12:24:55.555924+0200 tolk-24-7[7230:111102] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
2018-12-21 12:24:55.556052+0200 tolk-24-7[7230:111102] Task <7888D080-D175-4DBF-8F66-4183F0D653E6>.<1> HTTP load failed (error code: -1200 [3:-9824])
2018-12-21 12:24:55.556234+0200 tolk-24-7[7230:111613] Task <7888D080-D175-4DBF-8F66-4183F0D653E6>.<1> finished with error - code: -1200

I feel your frustrations, I haven't worked with the Swish API per se, but it looks like URLSession is failing to perform the client certificate request. The handshake is failing on that step.
There is an option to add a URLSessionDelegate to URLSession in order to handle authentication challenges such as ServerTrust and ClientCertificate. They are discussing it here:
Swift 3 UrlSession with client authentication by certificate
If you're able to create a p12/pfx with the client certificate and private key, you can use SecPKCS12Import to import it and use it for the URLCredential trust in the NSURLAuthenticationMethodClientCertificate received in URLSessionDelegate. Here's an implementation I wrote:
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
let authenticationMethod = challenge.protectionSpace.authenticationMethod
if authenticationMethod == NSURLAuthenticationMethodServerTrust {
//Handle server trust if necessary
} else if authenticationMethod == NSURLAuthenticationMethodClientCertificate {
if let clientCredential = try? getClientUrlCredential() {
completionHandler(.useCredential, clientCredential)
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
}
getClientUrlCredential function:
private func getClientUrlCredential() throws -> URLCredential {
let p12Data = getP12Data() //Get the data from the bundle if it's bundled in the app
let p12Key = getP12Key() //you need the key set for creating the p12/pfx
let certOptions: NSDictionary = [
kSecImportExportPassphrase as NSString : p12Key as NSString
]
// import certificate to read its entries
var items: CFArray?
let status = SecPKCS12Import(p12Data, certOptions, &items)
if status == errSecSuccess,
let items = items,
let dict = (items as Array).first as? [String: AnyObject],
let certChain = dict[kSecImportItemCertChain as String] as? [SecTrust] {
// Check if SecIdentityGetTypeID is present
guard let cfIdentity = dict[kSecImportItemIdentity as String] as CFTypeRef?,
CFGetTypeID(cfIdentity) == SecIdentityGetTypeID() else {
throw URLSessionPinningDelegateError.localClientCertificateError
}
let identity = dict[kSecImportItemIdentity as String] as! SecIdentity
return URLCredential(
identity: identity,
certificates: certChain,
persistence: .forSession
)
}
//Failed to read local certificate, throw error
throw URLSessionPinningDelegateError.localClientCertificateError
}
With a valid client certificate you should be able to fulfill the client hello and set up the TLS towards the server since this seems to be where you are failing right now. The SSL alert number 40 you're getting by BoringSSL suggests so to me at least.
Hope this points you in the right direction at least, happy to support further if needed.

Related

Authenticating to a GCP HTTP Cloud Function with a Firebase ID Token doesn't work?

I know this is a duplicate question, but I haven't seen anyone present a swift and python implementation. In addition, i've tried everything listed in the other questions, and nothing seems to work
Environments and Conditions:
Calling the http Cloud Function from an iOS app after I get the ID
token (with the ID token)
Written in Swift
The Cloud Function is written in Python
I cannot use HTTP callables as they are not deployable via the current terraform infrastructure in place (at least not that I know of, but would be open to any ideas)
Problem:
So, I was under the assumption that including the Firebase ID token of a Firebase user inside of the Authorization header works ,but it hasn't been for me even with a force refresh. I get a 403 status response with message: The access token could not be verified. That being said, if I go into CLI and get the id token of my actual gcp user account via: gcloud auth print-identity-token then replace the header with said token, I am verified.
Swift Request Code (excuse the lack of convention, this is just POCing before I make a real implementation):
guard let user = Auth.auth(app: authAppToUse).currentUser else { fatalError("SearchMySQL -> user: \(String(describing: Auth.auth(app: authAppToUse).currentUser))") }
user.getIDTokenForcingRefresh(true) { (idToken, err) in
if err != nil{
fatalError("SearchMySQL -> user.getIDToken -> err: \(String(describing: err))")
}
guard let guardedIdToken = idToken else { fatalError("SearchMySQL -> guardedIdToken: \(String(describing: idToken))") }
let rawJSON = [
"data" : [
"table": table,
"search_by": searchBy,
"search_by_value": searchByValue
]
]
guard let guardedJSON = try? JSONSerialization.data(withJSONObject: rawJSON, options: .prettyPrinted) else {
return
}
guard let url = URL(string: "https://us-east1-fresh-customer-dev-ocean.cloudfunctions.net/mysql_search") else { return }
var request = URLRequest(url: url)
request.addValue("Bearer \(idToken)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = guardedJSON
let task = URLSession.shared.dataTask(with: request) { (data, response, err) in
if err != nil {
print(err?.localizedDescription)
fatalError("SearchMySQL -> URLSession.shared.dataTask -> err: \(String(describing: err))")
}
guard let guardedData = data else {
return
}
print(idToken)
print(response.debugDescription)
let json = try? JSONSerialization.jsonObject(with: guardedData, options: .allowFragments)
print(json)
completion(data)
}
task.resume()
}
Python Cloud Function:
def main(request):
"""Background Http Triggered Cloud Function for ********.
Validates authentication with Firebase ID token
Returns:
********.main(payload): http response code and data payload (or message)
"""
# validate request authorization
if not request.headers.get('Authorization'):
log.fatal('No Authorization Token provided')
return {'message': 'No Authorization Token provided'}, 400
try:
id_token = request.headers.get('Authorization')
auth.verify_id_token(id_token)
except (ValueError, InvalidIdTokenError, ExpiredIdTokenError, RevokedIdTokenError, CertificateFetchError) as e:
log.fatal(f'Authorization `id_token` error: {e}')
return {'message':f'Authorization `id_token` error: {e}'}, 400
data_payload = request.get_json(force=True)
if not data_payload.table:
log.fatal('Payload missing table field.')
return {'message': 'Payload missing table field'}, 422
if not data_payload.search_by:
log.fatal('Payload missing search_by field.')
return {'message': 'Payload missing search_by field'}, 422
return ********.main(data_payload)
Ideas/Questions:
Aren't Firebase ID tokens equivalent to Google ID Tokens?
Could there be an iAM permission issue for the (auto generated)
firebase service account?
Do I need to also add any of the plist values to the idtoken when
sending it over in the header?
Could it be something with the rules of my cloud function?
Am I missing something, or is this intended/expected behavior? With
http callables being regular http functions but with protocols
packaged to facilitate, I would think that this is a relatively easy
implementation....
I've thought of the route of using an admin function to send a
message to the mobile instance that needs a google id token during
login, but the overhead and latency would result in issues.

How can I only allow LAN requests that aren't secure in Swift iOS14

I've been searching the internet and still cannot find an answer.
My app talks to other smart home products within the home. For example, it can make requests to the Philips Hue Bridge to control the lights via POST requests. The IP of my bridge is 192.168.0.12. I am making a POST request to this endpoint, however it isn't allowed as the connection is unsecure.
I still want to keep the setting of where external connections to domains are secure since I connect to my own server via a domain, which is secure. So I only want to allow local connections via local IP addresses to be unsecure.
I have tried this:
Yet it doesn't work. I've even tried using Allow Arbitrary Loads just for testing to see if it would work, and it still wouldn't.
My API call:
func getPhilipsHueUsername(completion: #escaping (String?, Error?) -> Void){
var bridgeIP = UserDefaults.standard.string(forKey: "bridgeIP")
let url = "http://" + bridgeIP! + "/api"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = HTTPMethod.post.rawValue
let body = [
"devicetype": "test"
]
do {
let dataToS = try JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
request.httpBody = dataToS
}catch{
print("Error creating data object")
return
}
AF.request(request).responseJSON { (response) in
switch response.result {
case .success(let value):
print(value)
if(response != nil){
let json = JSON(value)
print(value)
}
return completion("",nil)
case .failure(let error):
return completion(nil, error)
}
}
}

Trouble connecting iOS App with AWS EC2 server

I am testing registration codes for an iOS app to a server hosted on AWS EC2 (tomcat, apache). I keep getting errors, like these:
TIC SSL Trust Error [1:0x283bc6280]: 3:0
NSURLSession/NSURLConnection HTTP load failed
(kCFStreamErrorDomainSSL, -9813)
Task <2F787366-D258-488D-AD2C-2FE696D08BF2>.<1> HTTP load failed
(error code: -1202 [3:-9813]).
I searched and changed the info.plist according to several suggestions:
// Here are my sample codes
// Send HTTP Request to Register user
let myUrl = URL(string: "https://ec2xxx.compute.amazonaws.com:8080/MyAPISever/api.json")
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"// Compose a query string
let postString = "username=\(userName)&password=\(userPassword)&first_name=\(firstName)&last_name=\(lastName)&email=\(userEmail)"
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
// parse response....
task.resume()
Still the problem persists. I am using Swift 4.0 and Xcode 10.2. Here is my info.plist additions:
Allow Arbitary Loads
Exception Domains : amazonaws.com
IncludeSubdomains : true
NSExceptionAllowsInsecureHTTPLoads : true
NSExceptionRequiresForwardSecrecy : false
NSExceptionMinimumTLSVersion : TLSv1.2
NSRequiresCertificateTransparency : false

Partially downloading data in Swift

I'm trying to develop a download accelerator in Swift. It should get the file's size and divide it to n parts. Then it should download them at once by running multiple threads, and then merge the parts.
I read C# - Creating a Download Accelerator, unfortunately it doesn't help me.
I can do the multiple thread part easily by
DispatchQueue.main.async {
// The new thread
}
but the other part is harder. I usually download a file like this:
try Data(contentsOf: URL(string: assetsUrl!)!)
or I can do the thing that is explained in this answer
class Downloader {
class func load(url: URL, to localUrl: URL, completion: #escaping () -> ()) {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = try! URLRequest(url: url, method: .get)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Success: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: localUrl)
completion()
} catch (let writeError) {
print("error writing file \(localUrl) : \(writeError)")
}
} else {
print("Failure: %#", error?.localizedDescription);
}
}
task.resume()
}
}
But this is not C - it's very simplistic and doesn't accept many arguments. How can I make it get "first 200_000 bytes" from the server?
First of all, the server needs to implement HTTP range requests. If it doesn't, and you don't control the server, then you will not be able to do this.
If the server supports HTTP range requests, then you need to specify the range with request headers, as explained here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests
The essentials are that you first send a HEAD request to figure out whether the server supports HTTP range requests. This is determined by whether the response includes the Accept-Ranges header, with a non-zero value.
If the server supports HTTP range requests, then you can make a request for the resource, with the Range header set for example to a value of bytes=0-1023 (depends which format the Accept-Ranges header specified, in this case bytes)

Send Firebase Swift Push using NSURLSession HTTP Post

I set up my app so that users when they login subscribe to a topic with their user UID. Whenever a user sends a message to another user I will be calling the function below that I am hoping to trigger the push.
func sendPushNotification(toUser: String, message: String) {
let urlString = "https://fcm.googleapis.com/fcm/send"
let topic = "\topic\\(toUser)"
let url = NSURL(string: urlString)!
let paramString = "to=\(topic)"
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = paramString.dataUsingEncoding(NSUTF8StringEncoding)!
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in
do {
if let jsonData = data {
if let jsonDataDict = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.AllowFragments) as? [String: AnyObject] {
NSLog("Received data:\n\(jsonDataDict))")
}
}
} catch let err as NSError {
print(err.debugDescription)
}
}
task.resume()
}
Based on Firebase docs I am supposed to do this HTTP request:
Send to a single topic:
https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA
{
"to": "/topics/foo-bar",
"data": {
"message": "This is a Firebase Cloud Messaging Topic Message!",
}
}
I am struggling to see what I pass in the paramString for the message to be sent based off of their example. Also, where do I define the Authorization:key=
Sending a message with Firebase Cloud Messaging requires that you provide your server key in the Authorization header. As the name suggests, this key should only be used in code that runs on an app server (or other infrastructure that you control). Putting the server key into the app that you ship to your users, means malicious users can use it to send messages on your behalf.
From the Firebase documentation on sending messages to topics (emphasis mine):
From the server side, sending messages to a Firebase Cloud Messaging topic is very similar to sending messages to an individual device or to a user group. The app server sets the to key with a value like /topics/yourTopic.