Configure TLS Options For NWListener On macOS

For the past three years, I have been writing and maintaining a small but active REST service. It started life as a classic BSD sockets service, then moved to Swift NIO, and all that entails. The latest incarnation is nothing but native Network Framework, which seems to be the most bullet-proof of all. I have yet to crash it with the ApacheBench (ab) tool. Slow it down, maybe, but yet to crash it. Kudos to Apple on the NW Framework!

Now, I require adding a TLS-based listener. I have searched these forums and many other Internet sites for sample code, but to no avail. There is much about configuring an iOS client app, but almost nothing on the macOS listener side.

Below is a snippet of code in the init?() of my HTTPService class. Any suggestions on how to code in the correct TLS options would be greatly appreciated.

init?( port: UInt16, tls: Bool = false ) {
   .
   .
   .
   let TLS_opts = NWProtocolTLS.Options()

   let TCP_opts = NWProtocolTCP.Options()
       TCP_opts.disableECN         = true    // Explicit Congestion Notification
       TCP_opts.enableKeepalive    = false   // Send Keep-Alive packets
       TCP_opts.connectionTimeout  = 5       // Connection handshake timeout (seconds)
       TCP_opts.connectionDropTime = 5       // Seconds TCP will do packet retransmission

   if (tls) {
      let sec_opts = TLS_opts.securityProtocolOptions

      // I’m completely stuck in the sparse documentation at this point!
      //
      // For testing purposes, I have a self-signed certificate in the System keychain with
      // the identifier: “Server.local”
      //
      // How do I enable TLS using this (or a real) certificate?

      }

   let Parameters = NWParameters(tls: TLS_opts, tcp: TCP_opts)
       Parameters.allowLocalEndpointReuse = true

   guard let L = try? NWListener( using: Parameters, on: NWEndpoint.Port(rawValue: port) )
      else { return nil }

   self.Listener = L
   self.Listener.newConnectionHandler = NewConnection(_:)
   .
   .
   .
}

I am working with this question in a different context.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

UPDATE: With Matt Eaton's invaluable input, I've been able to get the following code to work. This listener is now running in my dev environment (i.e. localhost with a self-signed CA cert in the Login Keychain). Safari requests to "https://" (on port 443) now work as expected, whereas before only "http://" (on port 80) worked.

// Function used by the HTTPService class below
//
func getSecIdentity() -> SecIdentity? {

   let certName = "LocalNetworkTLSLeaf"
   let getquery = [kSecClass    : kSecClassCertificate,
                   kSecAttrLabel: certName,
                   kSecReturnRef: true] as NSDictionary

   var item    : CFTypeRef?
   var identity: SecIdentity?

   let status = SecItemCopyMatching(getquery as CFDictionary, &item)

   guard status == errSecSuccess else {
      print("Failed to get cetificate: \(status)")
      return nil
      }

   let certificate    = item as! SecCertificate
   let identityStatus = SecIdentityCreateWithCertificate(nil, certificate, &identity)

   guard identityStatus == errSecSuccess else {
      print("Failed to get sec identity: \(identityStatus)")
      return nil
      }

   return identity
   }




final class HTTPService {
   .
   .
   .
   private let Listener  : NWListener

   init?( port: UInt16, root: String, tls: Bool = false ) {
      .
      .
      .
      // Configure the main service Listener
      //
      guard let port = NWEndpoint.Port(rawValue: port)
         else {
            LogMessage("Not able to configure \(port) for listening")
            return nil
            }

      let TCP_opts = NWProtocolTCP.Options()
          TCP_opts.disableECN         = true    // Explicit Congestion Notification
          TCP_opts.enableKeepalive    = false   // Send Keep-Alive packets
          TCP_opts.connectionTimeout  = 5       // Connection handshake timeout (seconds)
          TCP_opts.connectionDropTime = 5       // Seconds TCP will do packet retransmission

      let Parameters: NWParameters

      if (tls) {
         let TLS_opts  = NWProtocolTLS.Options()

         Parameters = NWParameters(tls: TLS_opts, tcp: TCP_opts)
         Parameters.allowLocalEndpointReuse = true

         // Get SecIdentity from the Keychain
         //
         if let secIdentity = getSecIdentity(), let identity = sec_identity_create(secIdentity) {
            sec_protocol_options_set_min_tls_protocol_version(TLS_opts.securityProtocolOptions, .TLSv13)
            sec_protocol_options_set_local_identity(TLS_opts.securityProtocolOptions, identity)
            sec_protocol_options_append_tls_ciphersuite( TLS_opts.securityProtocolOptions, tls_ciphersuite_t(rawValue: UInt16(TLS_AES_128_GCM_SHA256))! )
            }
         }

      else {
         Parameters = NWParameters(tls: nil, tcp: TCP_opts)
         Parameters.allowLocalEndpointReuse = true
         }

      guard let L = try? NWListener( using: Parameters, on: port )
         else {
            LogMessage("Not able to configure TCP listener on port \(port)")
            return nil
            }

      self.Listener = L
      self.Listener.newConnectionHandler = self.NewConnection(_:)
      .
      .
      .
      }
   }
Configure TLS Options For NWListener On macOS
 
 
Q