How to perform TLS Handshake with NWConnection after connected TCP to Server

Currently, I have a client by using NWConnection for a socket connection to a server in local network. My server address is ***.***.***.***:YYYY

The client can connected to the server with the code below:

func connect() {
  let connection = NWConnection(host: .init("***.***.***.***"), port: .init(integerLiteral: YYYY), using: NWParameters(tls: nil, tcp: .init()))
   connection.stateUpdateHandler = { state in
           print(state)
           if state == .ready {
               receiveData()
           }
    }
    connection.start(queue: .global())
}

 private func receiveData() {
        self.connection?.receive(minimumIncompleteLength: 1, maximumLength: 8192) { [weak self] (data, context, isComplete, error) in
            guard let self = self else { return }
            if let error = error {
                self.socketConnectionStateCallBack(.onError(connection: self, error: error))
                return
            }
            if let connection = connection, connection.state == .ready && isComplete == false,
               let data = data, !data.isEmpty {
                self.socketConnectionStateCallBack(.onDataReceived(connection: self, data: data))
            }
        }
    }

The stateUpdateHandler callback with state == .ready and there is a receive method in that block also, so the client receive an encrypted String from the server.

At this time, the client should do TSL handshake with server. (I have a certificate file)

I already tried configuring TLS in NWParameters:

func createTLSParameters(allowInsecure: Bool, queue: DispatchQueue) -> NWParameters {
        let options = NWProtocolTLS.Options()
        sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in
            let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
            var error: CFError?
            if SecTrustEvaluateWithError(trust, &error) {
                sec_protocol_verify_complete(true)
            } else {
                if allowInsecure == true {
                    sec_protocol_verify_complete(true)
                } else {
                    sec_protocol_verify_complete(false)
                }
            }
        }, queue)
        return NWParameters(tls: options)
    }

but received the errors:

2023-06-26 13:44:52.793596+0700 TestNWConnection[8571:237696] [boringssl] boringssl_context_handle_fatal_alert(1991) [C1:4][0x7f9807c051f0] write alert, level: fatal, description: protocol version
2023-06-26 13:44:52.793784+0700 TestNWConnection[8571:237696] [boringssl] boringssl_context_error_print(1981) [C1:4][0x7f9807c051f0] Error: 140290895852456:error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER:/AppleInternal/Library/BuildRoots/9c39860a-c3e2-11ed-88f7-863efbbaf80d/Library/Caches/com.apple.xbs/Sources/boringssl/ssl/tls_record.cc:242:
2023-06-26 13:44:52.794547+0700 TestNWConnection[8571:237696] [boringssl] boringssl_session_handshake_incomplete(88) [C1:4][0x7f9807c051f0] SSL library error
2023-06-26 13:44:52.794617+0700 TestNWConnection[8571:237696] [boringssl] boringssl_session_handshake_error_print(43) [C1:4][0x7f9807c051f0] Error: 140290895852456:error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER:/AppleInternal/Library/BuildRoots/9c39860a-c3e2-11ed-88f7-863efbbaf80d/Library/Caches/com.apple.xbs/Sources/boringssl/ssl/tls_record.cc:242:
2023-06-26 13:44:52.794660+0700 TestNWConnection[8571:237696] [boringssl] nw_protocol_boringssl_handshake_negotiate_proceed(771) [C1:4][0x7f9807c051f0] handshake failed at state 12288: not completed
waiting(-9836: bad protocol version)
2023-06-26 13:44:52.833700+0700 TestNWConnection[8571:238121] [tcp] tcp_input [C1:5] flags=[R.] seq=764001948, ack=1321044260, win=506 state=CLOSED rcv_nxt=764000508, snd_una=1321044252

So can you help me to perform TLS Handshake with NWConnection after connected TCP to Server? Many thanks!

So can you help me to perform TLS Handshake with NWConnection after connected TCP to Server?

It sounds like you’re trying to implement some sort of STARTTLS mechanism, right?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks for your quick response, Quinn.

It sounds like you’re trying to implement some sort of STARTTLS mechanism, right?

I think its correct. At first, we connect via TCP protocol, after that we will use TLS to make it secured.

I have tried to use Security to handle get the SSL context by using: SSLCreateContext but It is deprecated in macOS 13.0. Did you have any clue to apply this protocol in NWConnection?

Did you have any clue to apply this protocol in NWConnection?

SSLCreateContext is part of Secure Transport, which is now thoroughly deprecated. Don’t go down that path.

It should be possible to make this work with Network framework’s built-in TLS stack. The trick is to use a custom framer. I don’t have any experience with this myself, or any pointers to other examples. If you really need to do this, I encourage you to open a DTS tech support incident, which will allow me to allocate the time to dig into this.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hmmm, reading through this again I want to be absolutely certain you need STARTTLS. That involves:

  1. Start a TCP connection.

  2. Exchange some plaintext data over that TCP connection.

  3. Then start TLS over the same TCP connection.

Is that what you want?

Step 2 is critical. If you don’t need step 2 then this is just a straightforward TLS-over-TCP connection, and NWConnection supports that in a very straightforward fashion. Rather than pass .tcp to the using parameter of the NWConnection initialise, pass .tls.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi Quinn, sorry for my late reply and this is my update.

I tried with the code below and received the same error in the question.

I am afraid I can't pass .tls to NWConnection for connection because the host address does not have https or wss at the beginning and the sec_protocol_options_set_verify_block callback wasn't triggered.

I tried putting https or wss at the beginning of the host but unfortunately, it couldn't connect.

class ViewModel {
    var connection: NWConnection?
    
    func connect() {
        let connection = NWConnection(host: "XX.X.***.XX", port: 1515, using: createTLSParameters(allowInsecure: true, queue: .main))
        self.connection = connection
        connection.stateUpdateHandler = { newState in
            print("newState \(newState)")
        }
        connection.start(queue: .main)
    }
    
    func createTLSParameters(allowInsecure: Bool, queue: DispatchQueue) -> NWParameters {
        let tlsOptions = NWProtocolTLS.Options()
        sec_protocol_options_set_verify_block(tlsOptions.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in
            let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
            var error: CFError?
            if SecTrustEvaluateWithError(trust, &error) {
                sec_protocol_verify_complete(true)
            } else {
                if allowInsecure == true {
                    sec_protocol_verify_complete(true)
                } else {
                    sec_protocol_verify_complete(false)
                }
            }
        }, queue)
        return NWParameters(tls: tlsOptions)
    }
}
let viewModel = ViewModel()
viewModel.connect()

I did some research and I think my issue might be similar to this question in this link. Because that question was posted 4 years ago so do you have any updates on that issue and can we switch from TCP to TLS now?

I did some research and I think my issue might be similar to this question in this link.

That thread is about STARTTLS, which is where we started this discussion. So, just to confirm, you need to do this:

  1. Open a TCP connection to a host.

  2. Exchange plaintext data with the server.

  3. Sometimes later, start the TLS protocol on top of that same TCP connection.

Is that right?

Is the protocol in step 2 something custom? Or something defined by an Internet standard?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks for your response!

Is that right?

Yes, that's right.

Is the protocol in step 2 something custom? Or something defined by an Internet standard?

In step 2, the client just waits for a response from the Server and checks that response contains "START<<TLS>>" to start the TLS protocol.

Could we get sec_trust from the server for verification by using this function sec_protocol_options_set_verify_block(tlsOptions.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) ?

Yes, that's right.

OK. That’s definitely STARTTLS in action.

I asked about this internally and it’s definitely possible to implement STARTTLS with NWConnection — in fact, the Mail app does it! — but it’s not easy. This is more complex than I have time for here on DevForums. I recommend that you open a DTS tech support incident, and we’ll can pick things up in that context.

Please reference this thread in your TSI submission for context.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

How to perform TLS Handshake with NWConnection after connected TCP to Server
 
 
Q