jthomas opened a new issue #12: Swift SDK does not support self-signed server 
certificates.
URL: https://github.com/apache/incubator-openwhisk-runtime-swift/issues/12
 
 
   Local instances of the platform use a self-signed SSL certificate. The Swift 
SDK (`_Whisk.swift`) fails when invoked against platform endpoints without a 
valid SSL certificate.
   
   The JavaScript SDK supports a constructor argument to turn off certificat 
checking to resolve this issue. **Looking into implementing this behaviour for 
the Swift SDK, I have discovered a blocking issue due to the lack of support in 
the open-source Swift Foundation libraries.**
   
   In Swift, certificate checking can be turned off by creating a new 
`URLSessionDelegate` with always trusts the server.
   
   ```swift
   class SessionDelegate:NSObject, URLSessionDelegate
   {
       func urlSession(_ session: URLSession, didReceive challenge: 
URLAuthenticationChallenge, completionHandler: @escaping 
(URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
           let credential = URLCredential(trust: 
challenge.protectionSpace.serverTrust!)
           completionHandler(URLSession.AuthChallengeDisposition.useCredential, 
credential)
       }
   }
   ```
   
   This can be used when creating the `URLSession` to use based upon a method 
parameter.
   
   ```swift
   let session = ignoreCerts ? URLSession(configuration: .default, delegate: 
SessionDelegate(), delegateQueue: nil) : URLSession(configuration: 
URLSessionConfiguration.default)
   ```
   
   This works on OS X but compiling the code on Linux, I ran into the following 
issue.
   ```
   _Whisky.swift:25:39: error: incorrect argument label in call (have 'trust:', 
expected 'coder:')
           let credential = URLCredential(trust: 
challenge.protectionSpace.serverTrust!)
                                         ^~~~~~
                                          coder
   root@3a4fde570648:/source# swift -v
   Swift version 4.0 (swift-4.0-RELEASE)
   Target: x86_64-unknown-linux-gnu
   ```
   
   Looking into the source code for the Swift foundation core libraries, I 
discovered this method has not been implemented.
   
https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/URLCredential.swift#L155
   
   ```
   // TODO: We have no implementation for Security.framework primitive types 
SecIdentity and SecTrust yet
   ```
   
   Talking to the IBM@Swift team, support for this feature is being worked on 
but won't be available until Swift 5 at the earliest.
   
   Until then, we will have to document the behaviour and wait for the 
foundation libraries to catch up. I've attached the completed `_Whisk.swift` 
demonstrating the bug.
   
   ```swift
   /*
    * Licensed to the Apache Software Foundation (ASF) under one or more
    * contributor license agreements.  See the NOTICE file distributed with
    * this work for additional information regarding copyright ownership.
    * The ASF licenses this file to You under the Apache License, Version 2.0
    * (the "License"); you may not use this file except in compliance with
    * the License.  You may obtain a copy of the License at
    *
    *     http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */
   
   import Foundation
   import Dispatch
   
   
   class SessionDelegate:NSObject, URLSessionDelegate
   {
       func urlSession(_ session: URLSession, didReceive challenge: 
URLAuthenticationChallenge, completionHandler: @escaping 
(URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
           let credential = URLCredential(trust: 
challenge.protectionSpace.serverTrust!)
           completionHandler(URLSession.AuthChallengeDisposition.useCredential, 
credential)
       }
   }
   
   class Whisk {
       
       static var baseUrl = ProcessInfo.processInfo.environment["__OW_API_HOST"]
       static var apiKey = ProcessInfo.processInfo.environment["__OW_API_KEY"]
       
       class func invoke(actionNamed action : String, withParameters params : 
[String:Any], ignoreCerts: Bool = false, blocking: Bool = true) -> [String:Any] 
{
           let parsedAction = parseQualifiedName(name: action)
           let strBlocking = blocking ? "true" : "false"
           let path = 
"/api/v1/namespaces/\(parsedAction.namespace)/actions/\(parsedAction.name)?blocking=\(strBlocking)"
           
           return sendWhiskRequestSyncronish(uriPath: path, params: params, 
method: "POST", ignoreCerts: ignoreCerts)
       }
       
       class func trigger(eventNamed event : String, ignoreCerts: Bool = false, 
withParameters params : [String:Any]) -> [String:Any] {
           let parsedEvent = parseQualifiedName(name: event)
           let path = 
"/api/v1/namespaces/\(parsedEvent.namespace)/triggers/\(parsedEvent.name)?blocking=true"
           
           return sendWhiskRequestSyncronish(uriPath: path, params: params, 
method: "POST", ignoreCerts: ignoreCerts)
       }
       
       class func createTrigger(triggerNamed trigger: String, ignoreCerts: Bool 
= false, withParameters params : [String:Any]) -> [String:Any] {
           let parsedTrigger = parseQualifiedName(name: trigger)
           let path = 
"/api/v1/namespaces/\(parsedTrigger.namespace)/triggers/\(parsedTrigger.name)"
           return sendWhiskRequestSyncronish(uriPath: path, params: params, 
method: "PUT", ignoreCerts: ignoreCerts)
       }
       
       class func createRule(ruleNamed ruleName: String, withTrigger 
triggerName: String, andAction actionName: String, ignoreCerts: Bool = false) 
-> [String:Any] {
           let parsedRule = parseQualifiedName(name: ruleName)
           let path = 
"/api/v1/namespaces/\(parsedRule.namespace)/rules/\(parsedRule.name)"
           let params = ["trigger":triggerName, "action":actionName]
           return sendWhiskRequestSyncronish(uriPath: path, params: params, 
method: "PUT", ignoreCerts: ignoreCerts)
       }
       
       // handle the GCD dance to make the post async, but then obtain/return
       // the result from this function sync
       private class func sendWhiskRequestSyncronish(uriPath path: String, 
params : [String:Any], method: String, ignoreCerts: Bool) -> [String:Any] {
           var response : [String:Any]!
           
           let queue = DispatchQueue.global()
           let invokeGroup = DispatchGroup()
           
           invokeGroup.enter()
           queue.async {
               postUrlSession(uriPath: path, ignoreCerts: ignoreCerts, params: 
params, method: method, group: invokeGroup) { result in
                   response = result
               }
           }
           
           // On one hand, FOREVER seems like an awfully long time...
           // But on the other hand, I think we can rely on the system to kill 
this
           // if it exceeds a reasonable execution time.
           switch invokeGroup.wait(timeout: DispatchTime.distantFuture) {
           case DispatchTimeoutResult.success:
               break
           case DispatchTimeoutResult.timedOut:
               break
           }
           
           return response
       }
       
       
       /**
        * Using new UrlSession
        */
       private class func postUrlSession(uriPath: String, ignoreCerts: Bool, 
params : [String:Any], method: String,group: DispatchGroup, callback : 
@escaping([String:Any]) -> Void) {
           
           guard let encodedPath = 
uriPath.addingPercentEncoding(withAllowedCharacters: 
CharacterSet.urlQueryAllowed) else {
               callback(["error": "Error encoding uri path to make openwhisk 
REST call."])
               return
           }
           
           let urlStr = "\(baseUrl!)\(encodedPath)"
           if let url = URL(string: urlStr) {
               var request = URLRequest(url: url)
               request.httpMethod = method
               
               do {
                   request.addValue("application/json", forHTTPHeaderField: 
"Content-Type")
                   request.httpBody = try 
JSONSerialization.data(withJSONObject: params)
                   
                   let loginData: Data = apiKey!.data(using: 
String.Encoding.utf8, allowLossyConversion: false)!
                   let base64EncodedAuthKey  = 
loginData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 
0))
                   request.addValue("Basic \(base64EncodedAuthKey)", 
forHTTPHeaderField: "Authorization")
                   let session = ignoreCerts ? URLSession(configuration: 
.default, delegate: SessionDelegate(), delegateQueue: nil) : 
URLSession(configuration: URLSessionConfiguration.default)
                   
                   let task = session.dataTask(with: request, 
completionHandler: {data, response, error -> Void in
                       
                       // exit group after we are done
                       defer {
                           group.leave()
                       }
                       
                       if let error = error {
                           callback(["error":error.localizedDescription])
                       } else {
                           
                           if let data = data {
                               do {
                                   //let outputStr  = String(data: data, 
encoding: String.Encoding.utf8) as String!
                                   //print(outputStr)
                                   let respJson = try 
JSONSerialization.jsonObject(with: data)
                                   if respJson is [String:Any] {
                                       callback(respJson as! [String:Any])
                                   } else {
                                       callback(["error":" response from server 
is not a dictionary"])
                                   }
                               } catch {
                                   callback(["error":"Error creating json from 
response: \(error)"])
                               }
                           }
                       }
                   })
                   
                   task.resume()
               } catch {
                   callback(["error":"Got error creating params body: 
\(error)"])
               }
           }
       }
       
       // separate an OpenWhisk qualified name (e.g. 
"/whisk.system/samples/date")
       // into namespace and name components
       private class func parseQualifiedName(name qualifiedName : String) -> 
(namespace : String, name : String) {
           let defaultNamespace = "_"
           let delimiter = "/"
           
           let segments :[String] = qualifiedName.components(separatedBy: 
delimiter)
           
           if segments.count > 2 {
               return (segments[1], 
Array(segments[2..<segments.count]).joined(separator: delimiter))
           } else if segments.count == 2 {
               // case "/action" or "package/action"
               let name = qualifiedName.hasPrefix(delimiter) ? segments[1] : 
segments.joined(separator: delimiter)
               return (defaultNamespace, name)
           } else {
               return (defaultNamespace, segments[0])
           }
       }
   }
   ```
   

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


With regards,
Apache Git Services

Reply via email to