Post

Replies

Boosts

Views

Activity

Reply to notarytool: No Keychain password item found for profile
Hi @eskimo! Thank you, for your response and suggestions. I ran an additional step in CD pipeline with two commands security find-generic-password -l '<profile-name>' and security find-generic-password -a ' com.apple.gke.notary.tool.com.saved-creds.<profile-name>'. The first one successfully found the entry in the keychain, however, the second one failed. On which result should I rely on?
Topic: Code Signing SubTopic: Notarization Tags:
Jul ’22
Reply to notarytool: No Keychain password item found for profile
We have changed a stored profile name, checked whether it is presented in the Keychain, and pushed CD pipelines several times with successful result. However, after some time it failed and continued delivering the same results with mentioned error. So, I make an assumption that notarytool has not enough rights to read something from the Keychain. Should we run it as a root process, or set an owner to root? The pipeline is running in the same user session as store-credentials executed.
Topic: Code Signing SubTopic: Notarization Tags:
Jul ’22
Reply to Steps to create, build and run Network System Extension on device
Thank you a lot for a clear explanation! I have managed to build and test my project following the steps you have provided! However, let's consider a situation when the Xcode can not generate a profile automatically, and a developer has to contact an Apple account admin to get a proper provisioning profile. Is there any way to test a system extension without getting the profile from the admin? For instance, would it work "normally" if the SIP is disabled?
Aug ’22
Reply to NETrasparentProxy::startProxy never gets called
Yep, I see logs from the main.swift That is what I have added func main() -> Never {     let logger = OSLog(subsystem: "proxy.main", category: "provider")     os_log(.debug, log: logger, "will start system extension mode")     autoreleasepool {         NEProvider.startSystemExtensionMode()     }     os_log(.debug, log: logger, "will dispatch main")     dispatchMain() } main()
Aug ’22
Reply to NETrasparentProxy::startProxy never gets called
So, I have managed to see the desired log about proxy start. However I don't see logs inside handleNewFlow. The method contains nothing complicated. All that I want to achieve for now is to "see" all outgoing traffic and allow it. override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {         os_log(.debug, log: self.log, "Handle new flow")         return false } inside startProxy I configured NETransparentProxySettings like this, to sniff all outgoing traffic: override func startProxy(options: [String : Any]?, completionHandler: @escaping (Error?) -> Void) {     os_log(.debug, log: self.log, "Proxy started")         let proxyNetworkSettings = NETransparentProxyNetworkSettings(tunnelRemoteAddress: "127.0.0.1")         let allOutgoingTCPTrafficRule = NENetworkRule(remoteNetwork: nil, remotePrefix: 0, localNetwork: nil, localPrefix: 0, protocol: .TCP, direction: .outbound)         let allOutgoingUDPTrafficRule = NENetworkRule(remoteNetwork: nil, remotePrefix: 0, localNetwork: nil, localPrefix: 0, protocol: .UDP, direction: .outbound)         proxyNetworkSettings.includedNetworkRules = [allOutgoingTCPTrafficRule, allOutgoingUDPTrafficRule]         setTunnelNetworkSettings(proxyNetworkSettings) { error in             if let error = error {                 os_log(.debug, log: self.log, "Error on saving tunnel network settings")                 return             }             os_log(.debug, log: self.log, "Tunnel network settings saved correctly")         }  } Also, I see in the Network Settings panel that the proxy's network profile hangs in "connecting" state forever. Is there a problem with tunnelRemoteAddress? What I have to configure to be able to track outgoing flows?
Aug ’22
Reply to Multiple network extension in a pipeline
Hi, Matt and Oskar! Can you explain how the traffic can be redirected from one extension to another? For instance, I would like to set up a pipeline with NETransparentProxyProvider and NEPacketTunnelProvider. As I understand from Matt's reply, if I process traffic inside the transparent proxy and redirect it somewhere, the tunnel provider will consider this traffic as originating from the proxy instead original application. However, for me, it is not clear how the proxy would redirect traffic to the packet tunnel. Should the extension with NEPacketTunnelProvider runs a sort of a server to sniff all traffic from the proxy?
Topic: App & System Services SubTopic: Core OS Tags:
Aug ’22
Reply to How to redirect TCP flow using Transparent Proxy to original destination
Finally, TCPConnectionManager class TCPConnectionManager {     public let flow: NEAppProxyTCPFlow     private let connection: NWConnection     private let tcpConnection = NWTCPConnection()     private let log = OSLog(subsystem: "com.ybelikov.transparent.proxy.network.extension", category: "provider")     private let connectionQueue = DispatchQueue(label: "com.ybelikov.transparent.proxy.network.extension.TCPQueue")     init(flow: NEAppProxyTCPFlow, endpoint: NWHostEndpoint) {         self.flow = flow         let host = Network.NWEndpoint.Host(endpoint.hostname)         let port = Network.NWEndpoint.Port(endpoint.port) ?? Network.NWEndpoint.Port.any         os_log(.debug, log: self.log, "On TCP init. host: %@, port: %@", String(describing: host), String(describing: port))         self.connection = NWConnection(host: host, port: port, using: .tcp)     }        public func startExchangingData() {     connection.stateUpdateHandler = self.stateChangedCallback(to:)         connection.start(queue: connectionQueue)     }     private func stateChangedCallback(to state: NWConnection.State) {             switch state {             case .ready:                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is ready")                 flow.open(withLocalEndpoint: tcpConnection.localAddress as? NWHostEndpoint) { error in                     if let error = error {                         os_log(.debug, log: self.log, "TCP flow opening failed %@", error.localizedDescription)                         return                     }                     self.handleOutgoingTCPData()                 }                 break             case .failed(let error):                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is failed %@", error.localizedDescription)                 self.connection.cancel()                 break             case .cancelled:                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is cancelled")                 break             case .preparing:                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is preparing")                 break             case .setup:                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is in setup state")                 break             case .waiting(let error):                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is in waiting state, %@", error.localizedDescription)                 self.connection.cancel()                 break                     default:                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. State is unknown")                 break             }     }          private func handleOutgoingTCPData() {         os_log(.debug, log: self.log, "handleOutgoingTCPData. handling TCP flow data")         flow.readData { data, error in             if let error = error {                 os_log(.debug, log: self.log, "handleOutgoingTCPData. Error on handling TCP flow data: %@", error.localizedDescription)                 return             }             if let data = data, !data.isEmpty {                 self.connection.send(content: data, completion: .contentProcessed({ error in                     if let error = error {                         os_log(.debug, log: self.log, "TCPConnectionManager::sendDataToEndpoint. TCP error: %@", error.localizedDescription)                         return                     }                     os_log(.debug, log: self.log, "TCP data sent successfully")                     self.handleOutgoingTCPData()                 }))             } else {                 self.handleIncomingTCPData()             }     }     }     private func handleIncomingTCPData() {         os_log(.debug, log: self.log, "On handleIncomingTCPData")         connection.receive(minimumIncompleteLength: 0,                                maximumLength: 2048) { (data, _, isComplete, error) in             switch (data, isComplete, error) {             case (let data?, _, _):                 os_log(.debug, log: self.log, "On handleIncomingTCPData. Received data, writing it to the flow")                 self.flow.write(data) { writeError in                     if writeError == nil {                         os_log(.debug, log: self.log, "On handleIncomingTCPData. Write error is nil")                         self.handleIncomingTCPData()                     }                 }             case (_, true, _):                 os_log(.debug, log: self.log, "On handleIncomingTCPData. Connection is completed")                 self.connection.stateUpdateHandler = nil                 self.connection.cancel()                 self.flow.closeReadWithError(error)                 self.flow.closeWriteWithError(error)             case (_, _, let error?):                 os_log(.debug, log: self.log, "On handleIncomingTCPData. Error: %@", error.localizedDescription)                 self.connection.cancel()                 self.flow.closeReadWithError(error)                 self.flow.closeWriteWithError(error)             default:                 os_log(.debug, log: self.log, "On handleIncomingTCPData. Unknown case")             }         }     } }
Aug ’22
Reply to How to redirect TCP flow using Transparent Proxy to original destination
Thank you for reply, Eskimo! So, I try to handle TCP flow like this override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {     os_log(.debug, log: self.log, "Handle new flow")         if let tcpFlow = flow as? NEAppProxyTCPFlow {         guard let targetEndpoint = tcpFlow.remoteEndpoint as? NWHostEndpoint else {             os_log(.debug, log: self.log, "On outboundCopierTCP. Unable to cast remote endpoint to NWHostEndpoint")                 return false             }             let tcpConnectionManager = TCPConnectionManager(flow: tcpFlow, endpoint: targetEndpoint)             tcpConnectionManager.startExchangingData() return true         }         return false     } Am I heading to the right direction? You can review a TCPConnectionManagerClass implementation in my reply above. It manages NWConnection with intended target of the flow and read\write operation between connection and flow
Aug ’22
Reply to How to redirect TCP flow using Transparent Proxy to original destination
I have read Handling flow copying article. So, it doesn't clear to me how I can switch between reading and writing on both sides of the connection. Here is the example of outboundCopier() from the article let flow: NEAppProxyTCPFlow let connection: NWConnection // Reads from the flow and writes to the remote connection. func outboundCopier() { flow.readData { (data, error) in if error == nil, let readData = data, !readData.isEmpty { connection.send(content: readData, completion: .contentProcessed( { connectionError in // Handle completion success or error. // Set up another read if there is no error. if connectionError == nil { self.outboundCopier() } })) } else { // Handle error case or the read that contains empty data. } } } I see that the method is called recursively until there is data that can be sent from the flow to the remote endpoint. However, I don't get where we should call inboundCopier() to read the response. Should it be called in the else branch from the outboundCopier(), when we detect that there is no flow data left unread? Or I can call inbound and outbound copiers synchronously inside the flow open() completion handler, when the state of the NWConnection transfers to .ready? For instance: case .ready:   flow.open(withLocalEndpoint: nil) { error in     if let error = error {     os_log(.debug, log: self.log, "TCP flow opening failed %@", error.localizedDescription)         return     } self.outboundCopier()     self.inboundCopier() } Let's assume that inboundCopier() and outboundCopier() have the same implementations as in the article Thank you in advance, for explanation
Aug ’22
Reply to notarytool: No Keychain password item found for profile
Hi @eskimo! Thank you, for your response and suggestions. I ran an additional step in CD pipeline with two commands security find-generic-password -l '<profile-name>' and security find-generic-password -a ' com.apple.gke.notary.tool.com.saved-creds.<profile-name>'. The first one successfully found the entry in the keychain, however, the second one failed. On which result should I rely on?
Topic: Code Signing SubTopic: Notarization Tags:
Replies
Boosts
Views
Activity
Jul ’22
Reply to notarytool: No Keychain password item found for profile
Also, can anyone suggest the way how to check via Terminal whether profile has been added to keychain successfully?
Topic: Code Signing SubTopic: Notarization Tags:
Replies
Boosts
Views
Activity
Jul ’22
Reply to notarytool: No Keychain password item found for profile
We have changed a stored profile name, checked whether it is presented in the Keychain, and pushed CD pipelines several times with successful result. However, after some time it failed and continued delivering the same results with mentioned error. So, I make an assumption that notarytool has not enough rights to read something from the Keychain. Should we run it as a root process, or set an owner to root? The pipeline is running in the same user session as store-credentials executed.
Topic: Code Signing SubTopic: Notarization Tags:
Replies
Boosts
Views
Activity
Jul ’22
Reply to notarytool: No Keychain password item found for profile
Thank you for your response, @eskimo. However, do you have any recommendations on where to store App Store Connect API private key?
Topic: Code Signing SubTopic: Notarization Tags:
Replies
Boosts
Views
Activity
Aug ’22
Reply to notarytool: No Keychain password item found for profile
So, App Store Connect keys helped us resolve an issue Thank you!
Topic: Code Signing SubTopic: Notarization Tags:
Replies
Boosts
Views
Activity
Aug ’22
Reply to Steps to create, build and run Network System Extension on device
Thank you a lot for a clear explanation! I have managed to build and test my project following the steps you have provided! However, let's consider a situation when the Xcode can not generate a profile automatically, and a developer has to contact an Apple account admin to get a proper provisioning profile. Is there any way to test a system extension without getting the profile from the admin? For instance, would it work "normally" if the SIP is disabled?
Replies
Boosts
Views
Activity
Aug ’22
Reply to Steps to create, build and run Network System Extension on device
Ok, thank you!
Replies
Boosts
Views
Activity
Aug ’22
Reply to NETrasparentProxy::startProxy never gets called
Yep, I see logs from the main.swift That is what I have added func main() -> Never {     let logger = OSLog(subsystem: "proxy.main", category: "provider")     os_log(.debug, log: logger, "will start system extension mode")     autoreleasepool {         NEProvider.startSystemExtensionMode()     }     os_log(.debug, log: logger, "will dispatch main")     dispatchMain() } main()
Replies
Boosts
Views
Activity
Aug ’22
Reply to NETrasparentProxy::startProxy never gets called
So, I have managed to see the desired log about proxy start. However I don't see logs inside handleNewFlow. The method contains nothing complicated. All that I want to achieve for now is to "see" all outgoing traffic and allow it. override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {         os_log(.debug, log: self.log, "Handle new flow")         return false } inside startProxy I configured NETransparentProxySettings like this, to sniff all outgoing traffic: override func startProxy(options: [String : Any]?, completionHandler: @escaping (Error?) -> Void) {     os_log(.debug, log: self.log, "Proxy started")         let proxyNetworkSettings = NETransparentProxyNetworkSettings(tunnelRemoteAddress: "127.0.0.1")         let allOutgoingTCPTrafficRule = NENetworkRule(remoteNetwork: nil, remotePrefix: 0, localNetwork: nil, localPrefix: 0, protocol: .TCP, direction: .outbound)         let allOutgoingUDPTrafficRule = NENetworkRule(remoteNetwork: nil, remotePrefix: 0, localNetwork: nil, localPrefix: 0, protocol: .UDP, direction: .outbound)         proxyNetworkSettings.includedNetworkRules = [allOutgoingTCPTrafficRule, allOutgoingUDPTrafficRule]         setTunnelNetworkSettings(proxyNetworkSettings) { error in             if let error = error {                 os_log(.debug, log: self.log, "Error on saving tunnel network settings")                 return             }             os_log(.debug, log: self.log, "Tunnel network settings saved correctly")         }  } Also, I see in the Network Settings panel that the proxy's network profile hangs in "connecting" state forever. Is there a problem with tunnelRemoteAddress? What I have to configure to be able to track outgoing flows?
Replies
Boosts
Views
Activity
Aug ’22
Reply to Multiple network extension in a pipeline
Hi, Matt and Oskar! Can you explain how the traffic can be redirected from one extension to another? For instance, I would like to set up a pipeline with NETransparentProxyProvider and NEPacketTunnelProvider. As I understand from Matt's reply, if I process traffic inside the transparent proxy and redirect it somewhere, the tunnel provider will consider this traffic as originating from the proxy instead original application. However, for me, it is not clear how the proxy would redirect traffic to the packet tunnel. Should the extension with NEPacketTunnelProvider runs a sort of a server to sniff all traffic from the proxy?
Topic: App & System Services SubTopic: Core OS Tags:
Replies
Boosts
Views
Activity
Aug ’22
Reply to NETrasparentProxy::startProxy never gets called
Ah, exactly! Thank you for pointing this out. It works fine now!
Replies
Boosts
Views
Activity
Aug ’22
Reply to How to redirect TCP flow using Transparent Proxy to original destination
Finally, TCPConnectionManager class TCPConnectionManager {     public let flow: NEAppProxyTCPFlow     private let connection: NWConnection     private let tcpConnection = NWTCPConnection()     private let log = OSLog(subsystem: "com.ybelikov.transparent.proxy.network.extension", category: "provider")     private let connectionQueue = DispatchQueue(label: "com.ybelikov.transparent.proxy.network.extension.TCPQueue")     init(flow: NEAppProxyTCPFlow, endpoint: NWHostEndpoint) {         self.flow = flow         let host = Network.NWEndpoint.Host(endpoint.hostname)         let port = Network.NWEndpoint.Port(endpoint.port) ?? Network.NWEndpoint.Port.any         os_log(.debug, log: self.log, "On TCP init. host: %@, port: %@", String(describing: host), String(describing: port))         self.connection = NWConnection(host: host, port: port, using: .tcp)     }        public func startExchangingData() {     connection.stateUpdateHandler = self.stateChangedCallback(to:)         connection.start(queue: connectionQueue)     }     private func stateChangedCallback(to state: NWConnection.State) {             switch state {             case .ready:                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is ready")                 flow.open(withLocalEndpoint: tcpConnection.localAddress as? NWHostEndpoint) { error in                     if let error = error {                         os_log(.debug, log: self.log, "TCP flow opening failed %@", error.localizedDescription)                         return                     }                     self.handleOutgoingTCPData()                 }                 break             case .failed(let error):                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is failed %@", error.localizedDescription)                 self.connection.cancel()                 break             case .cancelled:                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is cancelled")                 break             case .preparing:                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is preparing")                 break             case .setup:                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is in setup state")                 break             case .waiting(let error):                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is in waiting state, %@", error.localizedDescription)                 self.connection.cancel()                 break                     default:                 os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. State is unknown")                 break             }     }          private func handleOutgoingTCPData() {         os_log(.debug, log: self.log, "handleOutgoingTCPData. handling TCP flow data")         flow.readData { data, error in             if let error = error {                 os_log(.debug, log: self.log, "handleOutgoingTCPData. Error on handling TCP flow data: %@", error.localizedDescription)                 return             }             if let data = data, !data.isEmpty {                 self.connection.send(content: data, completion: .contentProcessed({ error in                     if let error = error {                         os_log(.debug, log: self.log, "TCPConnectionManager::sendDataToEndpoint. TCP error: %@", error.localizedDescription)                         return                     }                     os_log(.debug, log: self.log, "TCP data sent successfully")                     self.handleOutgoingTCPData()                 }))             } else {                 self.handleIncomingTCPData()             }     }     }     private func handleIncomingTCPData() {         os_log(.debug, log: self.log, "On handleIncomingTCPData")         connection.receive(minimumIncompleteLength: 0,                                maximumLength: 2048) { (data, _, isComplete, error) in             switch (data, isComplete, error) {             case (let data?, _, _):                 os_log(.debug, log: self.log, "On handleIncomingTCPData. Received data, writing it to the flow")                 self.flow.write(data) { writeError in                     if writeError == nil {                         os_log(.debug, log: self.log, "On handleIncomingTCPData. Write error is nil")                         self.handleIncomingTCPData()                     }                 }             case (_, true, _):                 os_log(.debug, log: self.log, "On handleIncomingTCPData. Connection is completed")                 self.connection.stateUpdateHandler = nil                 self.connection.cancel()                 self.flow.closeReadWithError(error)                 self.flow.closeWriteWithError(error)             case (_, _, let error?):                 os_log(.debug, log: self.log, "On handleIncomingTCPData. Error: %@", error.localizedDescription)                 self.connection.cancel()                 self.flow.closeReadWithError(error)                 self.flow.closeWriteWithError(error)             default:                 os_log(.debug, log: self.log, "On handleIncomingTCPData. Unknown case")             }         }     } }
Replies
Boosts
Views
Activity
Aug ’22
Reply to How to redirect TCP flow using Transparent Proxy to original destination
Thank you for reply, Eskimo! So, I try to handle TCP flow like this override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {     os_log(.debug, log: self.log, "Handle new flow")         if let tcpFlow = flow as? NEAppProxyTCPFlow {         guard let targetEndpoint = tcpFlow.remoteEndpoint as? NWHostEndpoint else {             os_log(.debug, log: self.log, "On outboundCopierTCP. Unable to cast remote endpoint to NWHostEndpoint")                 return false             }             let tcpConnectionManager = TCPConnectionManager(flow: tcpFlow, endpoint: targetEndpoint)             tcpConnectionManager.startExchangingData() return true         }         return false     } Am I heading to the right direction? You can review a TCPConnectionManagerClass implementation in my reply above. It manages NWConnection with intended target of the flow and read\write operation between connection and flow
Replies
Boosts
Views
Activity
Aug ’22
Reply to How to redirect TCP flow using Transparent Proxy to original destination
I have read Handling flow copying article. So, it doesn't clear to me how I can switch between reading and writing on both sides of the connection. Here is the example of outboundCopier() from the article let flow: NEAppProxyTCPFlow let connection: NWConnection // Reads from the flow and writes to the remote connection. func outboundCopier() { flow.readData { (data, error) in if error == nil, let readData = data, !readData.isEmpty { connection.send(content: readData, completion: .contentProcessed( { connectionError in // Handle completion success or error. // Set up another read if there is no error. if connectionError == nil { self.outboundCopier() } })) } else { // Handle error case or the read that contains empty data. } } } I see that the method is called recursively until there is data that can be sent from the flow to the remote endpoint. However, I don't get where we should call inboundCopier() to read the response. Should it be called in the else branch from the outboundCopier(), when we detect that there is no flow data left unread? Or I can call inbound and outbound copiers synchronously inside the flow open() completion handler, when the state of the NWConnection transfers to .ready? For instance: case .ready:   flow.open(withLocalEndpoint: nil) { error in     if let error = error {     os_log(.debug, log: self.log, "TCP flow opening failed %@", error.localizedDescription)         return     } self.outboundCopier()     self.inboundCopier() } Let's assume that inboundCopier() and outboundCopier() have the same implementations as in the article Thank you in advance, for explanation
Replies
Boosts
Views
Activity
Aug ’22
Reply to How to redirect TCP flow using Transparent Proxy to original destination
Seems like the code snippet from my last reply (with calls of inbound- and outbound- copiers inside open() completion handler) works, I am successful in controlling the traffic. On the other hand, is it supposed to work like that?
Replies
Boosts
Views
Activity
Aug ’22