Академический Документы
Профессиональный Документы
Культура Документы
Contents
Introduction 4
How to Use This Document 4 Prerequisites 5
Listings
Introduction
Important: This is a preliminary document. Although it has been reviewed for technical accuracy, it is not final. Apple is supplying this information to help you adopt the technologies and programming interfaces described herein. This information is subject to change, and software implemented according to this document should be vetted against final documentation. For information about updates to this document, go to the Apple Developer website. In the relevant reference library, enter the document's title in the Documents text field that appears. This document is a collection of highly specialized, task-based articles related to specific areas of networking. As with other programming topics documents in this developer library, this document assumes that you already have a deep familiarity with networking concepts. Important: Most developers do not need to read this document, and most networking software does not need to do any of the things described in this document. Before you read this document, you should read Networking Overview to learn more about when you should perform the tasks described in these articles.
Using Sockets and Socket Streams (page 6)Describes how to use sockets and streams for low-level networking, from the POSIX layer up through the Foundation layer. This article explains how to write both client and server code using current best practices. Resolving DNS Hostnames (page 22)Explains how to resolve DNS hostnames in ways that avoid some of the common pitfalls associated with doing so. Overriding TLS Chain Validation Correctly (page 25)Tells how to safely alter the behavior of Transport Layer Security (TLS) chain validation without exposing your software to serious security risks. This article covers both TCP streams (using the CFStream or NSStream API) and URL requests (using the NSURLConnection API).
Each article is intended to be read by developers who need to write code that performs the specified task.
Introduction Prerequisites
Prerequisites
This document assumes you have already read or otherwise understand the subjects described in the following documents:
Networking Overview Provides a basic understanding of how networking software works, and how to avoid common mistakes. Networking Concepts Provides a basic explanation of socket-based networking at a conceptual level.
This article explains how to work with sockets and socket streams at various levels, from POSIX through Foundation. Important: This article describes ways to make socket connections that are completely under the control of your program. Most programs would be better served by higher-level APIs such as NSURLConnection. To learn more about these higher-level APIs, read Networking Overview . The APIs described in this article should be used only if you need to support some protocol other than the protocols supported by built-in Cocoa or Core Foundation functionality. At almost every level of networking, software can be divided into two categories: clients (programs that connect to other apps) and services (programs that other apps connect to). At a high level, these lines are clear. Most programs written using high-level APIs are purely clients. At a lower level, however, the lines are often blurry. Socket and stream programming generally falls into one of the following broad categories:
Packet-based communicationPrograms that operate on one packet at a time, listening for incoming packets, then sending packets in reply. With packet-based communication, the only differences between clients and servers are the contents of the packets that each program sends and receives, and (presumably) what each program does with the data. The networking code itself is identical.
Stream-based clientsPrograms that use TCP to send and receive data as two continuous streams of bytes, one in each direction. With stream-based communication, clients and servers are somewhat more distinct. The actual data handling part of clients and servers is similar, but the way that the program initially constructs the communication channel is very different.
Choosing an API Family (page 7)Describes how to decide which API family to use when writing networking code. Writing a TCP-Based Client (page 8)Describes how to make outgoing TCP connections to existing servers and services.
Writing a TCP-Based Server (page 11)Describes how to listen for incoming TCP connections when writing servers and services. Working with Packet-Based Sockets (page 18)Describes how to work with non-TCP protocols, such as UDP.
In OS X, if you already have networking code that is shared with non-Apple platforms, you can use POSIX C networking APIs and continue to use your networking code as-is (on a separate thread). If your program is based on a Core Foundation or Cocoa (Foundation) run loop, you can also use the Core Foundation CFStream API to integrate the POSIX networking code into your overall architecture on the main thread. Alternatively, if you are using Grand Central Dispatch (GCD), you can add a socket as a dispatch source. In iOS, POSIX networking is discouraged because it does not activate the cellular radio or on-demand VPN. Thus, as a general rule, you should separate the networking code from any common data processing functionality and rewrite the networking code using higher-level APIs. Note: If you use POSIX networking code, you should be aware that the POSIX networking API is not protocol-agnostic (you must handle some of the differences between IPv4 and IPv6 yourself ). It is a connect-by-IP API rather than a connect-by-name API, which means that you must do a lot of extra work if you want to achieve the same initial-connection performance and robustness that higher-level APIs give you for free. Before you decide to reuse existing POSIX networking code, be sure to read Avoid Resolving DNS Names Before Connecting to a Host in Networking Overview .
For daemons and services that listen on a port, or for non-TCP connections, use POSIX or Core Foundation (CFSocket) C networking APIs. For client code in Objective-C, use Foundation Objective-C networking APIs. Foundation defines high-level classes for managing URL connections, socket streams, network services, and other networking tasks. It is also the primary non-UI Objective-C framework in OS X and iOS, providing routines for run loops, string handling, collection objects, file access, and so on. For client code in C, use Core Foundation C networking APIs. The Core Foundation framework and the CFNetwork framework are two of the primary C-language frameworks in OS X and iOS. Together they define the functions and structures upon which the Foundation networking classes are built.
Note: In OS X, CFNetwork is a subframework of the Core Services framework; in iOS, CFNetwork is a top-level framework.
Use NSStream for outgoing connections in Objective-C. If you are connecting to a specific host, create a CFHost object (not NSHostthey are not toll-free bridged), then use CFStreamCreatePairWithSocketToHost or CFStreamCreatePairWithSocketToCFHost to open a socket connected to that host and port and associate a pair of CFStream objects with it. You can then cast these to an NSStream object. You can also use the CFStreamCreatePairWithSocketToNetService function with a CFNetServiceRef object to connect to a Bonjour service. Read Discovering and Advertising Network Services in Networking Overview for more information. Note: The getStreamsToHost:port:inputStream:outputStream: of NSNetService is nod available on iOS, and is discouraged on OS X for performance reasons. Specifically, NSNetService requires you to create an instance of NSHost. When you create the object, the lookup is performed synchronously. Thus, it is unsafe to construct an NSHost object on your main application thread. See NSNetService and Automatic Reference Counting (ARC) for details.
Use CFStream for outgoing connections in C. If you are writing code that cannot include Objective-C, use the CFStream API. It integrates more easily with other Core Foundation APIs than CFSocket, and enables the cellular hardware on iOS (where applicable), unlike lower-level APIs. You can use CFStreamCreatePairWithSocketToHost or CFStreamCreatePairWithSocketToCFHost to open a socket connected to a given host and port and associate a pair of CFStream objects with it. You can also use the CFStreamCreatePairWithSocketToNetService function to connect to a Bonjour service. Read Discovering and Advertising Network Services in Networking Overview for more information.
If you are writing networking code that runs exclusively in OS X and iOS, you should generally avoid POSIX networking calls, because they are harder to work with than higher-level APIs. However, if you are writing networking code that must be shared with other platforms, you can use the POSIX networking APIs so that you can use the same code everywhere. Never use synchronous POSIX networking APIs on the main thread of a GUI application. If you use synchronous networking calls in a GUI application, you must do so on a separate thread. Note: POSIX networking does not activate the cellular radio on iOS. For this reason, the POSIX networking API is generally discouraged in iOS.
The subsections below describe the use of NSStream. Except where noted, the CFStream API has functions with similar names, and behaves similarly. To learn more about the POSIX socket API, read the UNIX Socket FAQ at http://developerweb.net/.
Establishing a Connection
As a rule, the recommended way to establish a TCP connection to a remote host is with streams. Streams automatically handle many of the challenges that TCP connections present. For example, streams provide the ability to connect by hostname, and in iOS, they automatically activate a devices cellular modem or on-demand VPN when needed (unlike CFSocket or BSD sockets). Streams are also a more Cocoa-like networking interface than lower-level protocols, behaving in a way that is largely compatible with the Cocoa file stream APIs. The way you obtain input and output streams for a host depends on whether you used service discovery to discover the host:
If you already know the DNS name or IP address of the remote host, obtain Core Foundation read (input) and write (output) streams with the CFStreamCreatePairWithSocketToHost function. You can then take advantage of the toll-free bridge between CFStream and NSStream to cast your CFReadStreamRef and CFWriteStreamRef objects to NSInputStream and NSOutputStream objects. If you discovered the host by browsing for network services with a CFNetServiceBrowser object, you obtain input and output streams for the service with the CFStreamCreatePairWithSocketToNetService function. Read Discovering and Advertising Network Services in Networking Overview for more information.
After you have obtained your input and output streams, you should retain them immediately if you are not using automatic reference counting. Then cast them to NSInputStream and NSOutputStream objects, set their delegate objects (which should conform to the NSStreamDelegate protocol), schedule them on the current run loop, and call their open methods.
Note: If you are working with more than one connection at a time, you must also keep track of which input stream is associated with a given output stream and vice versa. The most straightforward way to do this is to create your own connection object that holds references to both streams, and then set that object as the delegate for each stream.
Handling Events
When the stream:handleEvent: method is called on the NSOutputStream objects delegate and the streamEvent parameters value is NSStreamEventHasSpaceAvailable, call write:maxLength: to send data. This method returns the number of bytes written or a negative number on error. If fewer bytes were written than you tried to send, you must queue up the remaining data and send it after the delegate method gets called again with an NSStreamEventHasSpaceAvailable event. If an error occurs, you should call streamError to find out what went wrong. When the stream:handleEvent: method is called on your NSInputStream objects delegate and the streamEvent parameters value is NSStreamEventHasBytesAvailable, your input stream has received data that you can read with the read:maxLength: method. This method returns the number of bytes read, or a negative number on error. If fewer bytes were read than you need, you must queue the data and wait until you receive another stream event with additional data. If an error occurs, you should call streamError to find out what went wrong. If the other end of the connection closes the connection:
Your connection delegates stream:handleEvent: method is called with streamEvent set to NSStreamEventHasBytesAvailable. When you read from that stream, you get a length of zero (0). Your connection delegates stream:handleEvent: method is called with streamEvent set to NSStreamEventEndEncountered.
When either of these two events occurs, the delegate method is responsible for detecting the end-of-file condition and cleaning up.
If you previously set the kCFStreamPropertyShouldCloseNativeSocket to kCFBooleanFalse by calling setProperty:forKey: on the stream.
10
If you created the streams based on an existing BSD socket by calling CFStreamCreatePairWithSocket. By default, streams created from an existing native socket do not close their underlying socket. However, you can enable automatic closing by setting the kCFStreamPropertyShouldCloseNativeSocket to kCFBooleanTrue with the setProperty:forKey: method.
If you are writing code for OS X and iOS exclusively, use POSIX networking calls to set up your network sockets. Then, use GCD or CFSocket to integrate the sockets into your run loop. Use pure POSIX networking code with a POSIX-based run loop (select) if cross-platform portability with non-Apple platforms is required. If you are writing networking code that runs exclusively in OS X and iOS, you should generally avoid POSIX networking calls because they are harder to work with than higher level APIs. However, if you are writing networking code that must be shared with other platforms, you can use the POSIX networking APIs so that you can use the same code everywhere.
Never use NSSocketPort or NSFileHandle for general socket communication. For details, see Do Not Use NSSocketPort (OS X) or NSFileHandle for General Socket Communication in Networking Overview .
The following sections describe how to use these APIs to listen for incoming connections.
11
1.
2.
Create socket objects (returned as a CFSocketRef object) with the CFSocketCreate or CFSocketCreateWithNative function. Specify kCFSocketAcceptCallBack as the callBackTypes parameter value. Provide a pointer to a CFSocketCallBack callback function as the callout parameter value.
CFSocketRef myipv4cfsock = CFSocketCreate( kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, handleConnect, NULL); CFSocketRef myipv6cfsock = CFSocketCreate( kCFAllocatorDefault, PF_INET6, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, handleConnect, NULL);
3.
Bind a socket with the CFSocketSetAddress function. Provide a CFData object containing a sockaddr struct that specifies information about the desired port and family.
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; /* Address family */ sin.sin_port = htons(0); /* Or a specific port */ sin.sin_addr.s_addr= INADDR_ANY;
12
memset(&sin6, 0, sizeof(sin6)); sin6.sin6_len = sizeof(sin6); sin6.sin6_family = AF_INET6; /* Address family */ sin6.sin6_port = htons(0); /* Or a specific port */ sin6.sin6_addr = in6addr_any;
4.
Begin listening on a socket by adding the socket to a run loop. Create a run-loop source for a socket with the CFSocketCreateRunLoopSource function. Then, add the socket to a run loop by providing its run-loop source to the CFRunLoopAddSource function.
CFRunLoopSourceRef socketsource = CFSocketCreateRunLoopSource( kCFAllocatorDefault, myipv4cfsock, 0);
CFRunLoopAddSource( CFRunLoopGetCurrent(),
13
socketsource, kCFRunLoopDefaultMode);
After this, you can access the underlying BSD socket descriptor with the CFSocketGetNative function. When you are through with the socket, you must close it by calling CFSocketInvalidate. In your listening sockets callback function (handleConnect in this case), you should check to make sure the value of the callbackType parameter is kCFSocketAcceptCallBack, which means that a new connection has been accepted. In this case, the data parameter of the callback is a pointer to a CFSocketNativeHandle value (an integer socket number) representing the socket. To handle the new incoming connections, you can use the CFStream, NSStream, or CFSocket APIs. The stream-based APIs are strongly recommended. To do this:
1. 2.
Create read and write streams for the socket with the CFStreamCreatePairWithSocket function. Cast the streams to an NSInputStream object and an NSOutputStream object if you are working in Cocoa. Use the streams as described in Writing a TCP-Based Client (page 8).
3.
For more information, see CFSocket Reference . For sample code, see the RemoteCurrency and WiTap sample code projects.
14
2.
Bind it to a port.
If you have a specific port in mind, use that. If you dont have a specific port in mind, pass zero for the port number, and the operating system will assign you an ephemeral port. (If you are going to advertise your service with Bonjour, you should almost always use an ephemeral port.)
For example:
struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; // or AF_INET6 (address family) sin.sin_port = htons(0); sin.sin_addr.s_addr= INADDR_ANY;
3.
If you are using an ephemeral port, call getsockname to find out what port you are using. You can then register this port with Bonjour. For example:
socklen_t len = sizeof(sin);
15
if (getsockname(listen_sock, (struct sockaddr *)&sin, &len) < 0) { // Handle error here } // You can now get the port number with ntohs(sin.sin_port).
4.
The next steps depend on whether you intend to use pure POSIX socket code or a higher level abstraction.
Call dispatch_source_create to create a dispatch source for the listening socket, specifying DISPATCH_SOURCE_TYPE_READ as the source type. Call dispatch_source_set_event_handler (or dispatch_source_set_event_handler_f and dispatch_set_context) to set a handler that gets called whenever a new connection arrives on the socket. When the listen socket handler is called (upon a new connection), it should:
2.
3.
Call accept. This function fills a new sockaddr structure with information about the connection and returns a new socket for that connection. If desired, call ntohl(my_sockaddr_obj.sin_addr.s_addr) to determine the clients IP address.
Call dispatch_source_create to create a dispatch source for the client socket, specifying DISPATCH_SOURCE_TYPE_READ as the source type. Call setsockopt to set the SO_NOSIGPIPE flag on the socket. Call dispatch_source_set_event_handler (or dispatch_source_set_event_handler_f and dispatch_set_context) to set a handler that gets called whenever the state of the connection changes.
4.
In the client socket handler, call dispatch_async or dispatch_async_f and pass a block that calls read on the socket to grab any new data, then handle that data appropriately. This block can also send responses by calling write on the socket.
16
Create a file descriptor set and add new sockets to that set as new connections come in.
fd_set incoming_connections; memset(&incoming_connections, 0, sizeof(incoming_connections));
2.
If you need to perform actions periodically on your networking thread, construct a timeval structure for the select timeout.
struct timeval tv; tv.tv_sec = 1; /* 1 second timeout */ tv.tv_usec = 0; /* no microseconds. */
It is important to choose a timeout that is reasonable. Short timeout values bog down the system by causing your process to run more frequently than is necessary. Unless you are doing something very unusual, your select loop should not wake more than a few times per second, at most, and on iOS, you should try to avoid doing this at all. For alternatives, read Common Networking Mistakes in Networking Overview . If you do not need to perform periodic actions, pass NULL.
3.
Call select in a loop, passing two separate copies of that file descriptor set (created by calling FD_COPY) for the read and write descriptor sets. The select system call modifies these descriptor sets, clearing any descriptors that are not ready for reading or writing. For the timeout parameter, pass the timeval structure you created earlier. Although OS X and iOS do not modify this structure, some other operating systems replace this value with the amount of time remaining. Thus, for cross-platform compatibility, you must reset this value each time you call select. For the nfds parameter, pass a number that is one higher than the highest-numbered file descriptor that is actually in use.
4.
Read data from sockets, calling FD_ISSET to determine if a given socket has pending data. Write data to calling FD_ISSET to determine if a given socket has room for new data. Maintain appropriate queues for incoming and outgoing data.
As an alternative to the POSIX select function, the BSD-specific kqueue API can also be used to handle socket events.
17
Create a socket by calling socket. Bind the socket by calling bind. Provide a sockaddr struct that specifies information about the desired port and family. Connect the socket by calling connect (optional). Note that a connected UDP socket is not a connection in the purest sense of the word. However, it provides two advantages over an unconnected socket. First, it removes the need to specify the destination address every time you send a new message. Second, your app may receive errors when a packet cannot be delivered. This error delivery is not guaranteed with UDP, however; it is dependent on network conditions that are beyond your apps control.
3.
From there, you can work with the connection in three ways:
If you are using GCD for run loop integration (recommended), create a dispatch source by calling dispatch_source_create. Assign an event handler to the dispatch source. Optionally assign a cancellation handler. Finally, pass the dispatch source to the dispatch_resume function to begin handling events. If you are using CFSocket for integration, this technique is somewhat more complicated, but makes it easier to interface your code with some Cocoa APIs. However, CFSocket objects use a single object to represent a connection (much like sockets at the POSIX layer), whereas most Cocoa APIs are designed to interface with stream-based APIs that use separate objects for sending and receiving. As a result, some Cocoa APIs that expect read or write streams may be difficult to use in conjunction with CFSocketRef objects. To use CFSocket:
1.
Create an object to use for managing the connection. If you are writing Objective-C code, this can be a class. If you are writing pure C code, this should be a Core Foundation object, such as a mutable dictionary. Create a context object to describe that object.
CFSocketContext ctxt;
2.
18
3.
Create a CFSocket object (CFSocketRef) for the CFSocketNativeHandle object by calling CFSocketCreateWithNative. Be sure to set (at minimum) the kCFSocketDataCallBack flag in your callBackTypes parameter value. Do not set the kCFSocketAcceptCallBack flag. Youll also need to provide a pointer to a CFSocketCallBack callback function as the callout parameter value. For example:
CFSocketRef connection = CFSocketCreateWithNative(kCFAllocatorDefault, sock, kCFSocketDataCallBack, handleNetworkData, &ctxt);
4.
Tell Core Foundation that it is allowed to close the socket when the underlying Core Foundation object is invalidated.
CFOptionFlags sockopt = CFSocketGetSocketFlags(connection);
5.
Create an event source for the socket and schedule it on your run loop.
CFRunLoopSourceRef socketsource = CFSocketCreateRunLoopSource( kCFAllocatorDefault, connection, 0);
19
Using Sockets and Socket Streams Obtaining the Native Socket Handle for a Socket Stream
Whenever new data becomes available, the data handler callback gets called. In your callback, if the value of the callbackType parameter is kCFSocketConnectCallBack, check the data parameter passed into the callback. If it is NULL, you have connected to the host. You can then send data using the CFSocketSendData function. When you are finished with the socket, close and invalidate it by calling the CFSocketInvalidate function. At any point, you can also access the underlying BSD socket by calling the CFSocketGetNative function. For more information, see CFSocket Reference . For sample code, see the UDPEcho sample code project.
If you are using pure POSIX sockets, use the select system call to wait for data, then use the read and write system calls to perform I/O. To learn more about sending and receiving UDP packets with the POSIX socket API, read the UNIX Socket FAQ at http://developerweb.net/.
20
Using Sockets and Socket Streams Obtaining the Native Socket Handle for a Socket Stream
You can do the same thing with an output stream, but you only need to do this with one or the other because the input and output streams for a given connection always share the same underlying native socket. Note: If you are working with a Core Foundation stream, you can do the same thing with CFReadStreamCopyProperty, CFDataGetLength, and CFDataGetBytes.
21
This article explains how to resolve a DNS hostname in a way that offers you maximum flexibility. Important: Most high-level OS X and iOS APIs allow you to connect to a host by its DNS name, and, as a rule, you should do so. A hostname can map onto multiple IP addresses simultaneously. If you resolve the hostname yourself, you must choose which IP address to use when connecting to the remote host. By contrast, when you connect by name, you allow the operating system to choose the best way of connecting on your behalf. This is particularly true for Bonjour services because the IP address can change at any time. However, connecting by name is not always possible. If you must use APIs that require an IP address, OS X and iOS provide a number of ways to obtain one or more addresses for a DNS hostname. This appendix describes those techniques. Before you read this appendix, read Avoid Resolving DNS Names Before Connecting to a Host in Networking Overview to learn why you probably should not do the things described below. There are three primary APIs in OS X and iOS for resolving hostnames: NSHost (only in OS X), CFHost, and the POSIX resolver API.
NSHostAlthough passing NSHost is a common way to pass hostnames to other APIs, using NSHost to resolve addresses yourself is generally discouraged because it is a synchronous API. Thus, using it on the main thread can cause serious performance degradation. Because this API is discouraged, its use is not described here. CFHostThe CFHost API allows you to perform resolution asynchronously, and is the preferred way to resolve hostnames if you must resolve them yourself. POSIXThe POSIX layer provides several functions for resolving hostnames. These functions should be used only if you are writing portable code that must be shared with non-Apple platforms or if you are integrating your code with existing POSIX networking code.
22
2.
Call CFHostSetClient and provide the context object of your choice and a callback function that will be called when resolution completes. Call CFHostScheduleWithRunLoop to schedule the resolver on your run loop. Call CFHostStartInfoResolution to tell the resolver to start resolving, passing kCFHostAddresses as the second parameter to indicate that you want it to return IP addresses. Wait for the resolver to call your callback. Within your callback, obtain the results by calling CFHostGetAddressing. This function returns an array of CFDataRef objects, each of which contains a POSIX sockaddr structure.
3. 4.
5.
The process for reverse name resolution (translating an IP address into a hostname) is similar, except that you call CFHostCreateWithAddress to create the object, pass kCFHostNames to CFHostStartInfoResolution, and call CFHostGetNames to retrieve the results.
Returns all the resolved addresses for a given hostname. This function is the preferred way to obtain address information at the POSIX level. You can find sample code in its man page (linked above).
Important: Some older DNS servers do not reply to IPv6 lookup requests with an error. The POSIX getaddrinfo function attempts to hide this misbehavior by canceling outstanding IPv6 queries shortly after receiving a successful IPv4 reply. If your app would benefit from continuing to receive IPv6 addresses until you connect successfully (rather than stopping as soon as you have an IPv4 address that might or might not work), then you should use an asynchronous API such as CFHost. gethostbyname
Returns a single IPv4 address for a given hostname. This function is discouraged for new development because it is limited to IPv4 addresses.
23
gethostbyname2
Returns a single address for a given hostname in the specified address family (AF_INET, AF_INET6, and so on). Although this function allows you to get around the IPv4 limitations of gethostbyname, it still limits your ability to try multiple addresses at once and choose the fastest one. Thus, this function is primarily intended as a nearly drop-in replacement for gethostbyname in existing code. The getaddrinfo is preferred for use in new code. For reverse name resolution (translating an IP address into a hostname), POSIX provides getnameinfo and gethostbyaddr. The getnameinfo function is preferred because it is more flexible. To learn more about these functions, read their respective man pages, linked above.
24
This article describes how to override the chain validation behavior of network connections secured with Transport Layer Security (TLS). Important: This article assumes you have already read Cryptographic Services in Security Overview or are otherwise familiar with the concepts of certificates, signatures, and chains of trust. Most production software should not override chain validation. However, it is common to do so during development. By using the techniques described here to override chain validation safely, your users will not be left unprotected if you inadvertently ship a version of your software without disabling that debugging code. When a TLS certificate is verified, the operating system verifies its chain of trust. If that chain of trust contains only valid certificates and ends at a known (trusted) anchor certificate, then the certificate is considered valid. If it does not, it is considered invalid. If you are using a commercially signed certificate from a major vendor, the certificate should just work . However, if you are doing something that falls outside the normcreating client certificates for your users, providing service for multiple domains with a single certificate that is not trusted for those domains, using a self-signed certificate, connecting to a host by IP address (where the networking stack cannot determine the servers hostname), and so onyou must take additional steps to convince the operating system to accept the certificate. At a high level, TLS chain validation is performed by a trust object (SecTrustRef). This object contains a number of flags that control what types of validation are performed. As a rule, you should not touch these flags, but you should be aware of their existence. In addition, the trust object contains a policy (SecPolicyRef) that allows you to provide the hostname that should be used when evaluating a TLS certificate. Finally, the trust object contains a list of trusted anchor certificates that your application can modify. This article is split into multiple parts. The first part, Manipulating Trust Objects (page 26), describes common ways to manipulate the trust object to change validation behavior. The remaining sections, Trust Objects and NSURLConnection (page 28) and Trust Objects and NSStream (page 31), show how to integrate those changes with various networking technologies.
25
if (SecTrustCopyAnchorCertificates(&oldAnchorArray) != errSecSuccess) { /* Something went wrong. */ return NULL; } CFMutableArrayRef newAnchorArray = CFArrayCreateMutableCopy( kCFAllocatorDefault, 0, oldAnchorArray); CFRelease(oldAnchorArray);
CFArrayAppendValue(newAnchorArray, trustedCert);
SecTrustSetAnchorCertificates(trust, newAnchorArray);
return trust;
26
Note: To construct a SecCertificateRef object for trustedCert, first load a DER-encoded certificate into a CFData object, then call SecCertificateCreateWithData. For more flexibility, including loading certificates in other encodings, you can also use SecItemImport function in OS X v10.7 and later.
To override the hostname (to allow a certificate for one specific site to work for another specific site, or to allow a certificate to work when you connected to a host by its IP address), you must replace the policy object that the trust policy uses to determine how to interpret the certificate. To do this, first create a new TLS policy object for the desired hostname. Then create an array containing that policy. Finally, tell the trust object to use that array for future evaluation of trust. Listing 2 shows a function that does this.
Listing 2 Changing the remote hostname for a SecTrustRef object
CFArrayAppendValue(newTrustPolicies, sslPolicy);
return trust; #else /* This technique works in iOS 2 and later, or OS X v10.7 and later */
27
/* Copy the certificates from the original trust object */ CFIndex count = SecTrustGetCertificateCount(trust); CFIndex i=0; for (i = 0; i < count; i++) { SecCertificateRef item = SecTrustGetCertificateAtIndex(trust, i); CFArrayAppendValue(certificates, item); }
/* Create a new trust object */ SecTrustRef newtrust = NULL; if (SecTrustCreateWithCertificates(certificates, newTrustPolicies, &newtrust) != errSecSuccess) { /* Probably a good spot to log something. */
return NULL; }
connection:canAuthenticateAgainstProtectionSpace:
This method tells NSURLConnection that it knows how to handle authentication of a particular type. When your application decides whether to trust a server certificate, it is considered a form of authenticationyour application authenticating the server.
connection:didReceiveAuthenticationChallenge:
In this method, your code needs to modify the trust policies, keys, or hostnames provided by the server or the client so that the trust policy evaluates successfully. Listing 3 shows an example of these two methods.
28
Listing 3
// If you are building for OS X 10.7 and later or iOS 5 and later, // leave out the first method and use the second method as the // connection:willSendRequestForAuthenticationChallenge: method. // For earlier operating systems, include the first method, and // use the second method as the connection:didReceiveAuthenticationChallenge: // method.
NSString *method = [protectionSpace authenticationMethod]; if (method == NSURLAuthenticationMethodServerTrust) { return YES; } return NO; }
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge #else -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge #endif { NSURLProtectionSpace *protectionSpace = [challenge protectionSpace]; if ([protectionSpace authenticationMethod] == NSURLAuthenticationMethodServerTrust) { SecTrustRef trust = [protectionSpace serverTrust];
29
[connection cancel];
switch (secresult) { case kSecTrustResultUnspecified: // The OS trusts this certificate implicitly. case kSecTrustResultProceed: // The user explicitly told the OS to trust it. { NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; return; } default: /* It's somebody else's key. Fall through. */ } /* The server sent a key other than the trusted key. */ [connection cancel];
30
This changes the streams notion of its hostname so that when the stream object later creates a trust object, it provides the new name. If you need to actually alter the list of trusted anchors, the process is somewhat more complex. As soon as the stream object creates a trust object, it evaluates it. If that trust evaluation fails, the stream is closed before your code has the opportunity to modify the trust object. Thus, to override trust evaluation, you must:
Disable TLS chain validation. Because the stream never evaluates the TLS chain, the evaluation does not fail, and the stream does not close. Perform the chain validation yourself in the streams delegate (after modifying the trust object appropriately).
By the time your stream delegates event handler gets called to indicate that there is space available on the socket, the operating system has already constructed a TLS channel, obtained a certificate chain from the other end of the connection, and created a trust object to evaluate it. At this point, you have an open TLS stream, but you have no idea whether you can trust the host at the other end. By disabling chain validation, it becomes your responsibility to verify that the host at the other end can be trusted. Among other things, this means:
Do not disable hostname checking by creating a non-TLS policy or passing in a NULL pointer for the hostname. If you are intentionally connecting to a host using a hostname other than one of the names listed on its certificate, you should allow the operation only if the certificate you receive from that host is valid for some other domain that you control . Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors). Instead, add your own (self-signed) CA certificate to the list of trusted anchors.
31
Do not arbitrarily disable other security options, such as checking for expired certificates or roots. There are certain situations where doing so might make sense (such as verifying that a document signed back in 2001 was signed by a certificate that was valid back in 2001), but for networking purposes, the default options should generally be left alone.
With those rules in mind, Listing 5 shows how to use custom TLS anchors with NSStream. This listing also uses the function addAnchorToTrust from Listing 1 (page 26).
Listing 5 Using custom TLS anchors with NSStream
...
32
NSArray *certs = [theStream propertyForKey: (__bridge NSString *)kCFStreamPropertySSLPeerCertificates]; SecTrustRef trust = (SecTrustRef)[theStream propertyForKey: (__bridge NSString *)kCFStreamPropertySSLPeerTrust];
/* Because you don't want the array of certificates to keep growing, you should add the anchor to the trust list only upon the initial receipt of data (rather than every time). */ NSNumber *alreadyAdded = [theStream propertyForKey: kAnchorAlreadyAdded]; if (!alreadyAdded || ![alreadyAdded boolValue]) { trust = addAnchorToTrust(trust, self.trustedCert); // defined earlier. [theStream setProperty: [NSNumber numberWithBool: YES] forKey: kAnchorAlreadyAdded]; } SecTrustResultType res = kSecTrustResultInvalid;
if (SecTrustEvaluate(trust, &res)) { /* The trust evaluation failed for some reason. This probably means your certificate was broken in some way or your code is otherwise wrong. */
/* Tear down the input stream. */ [theStream removeFromRunLoop: ... forMode: ...]; [theStream setDelegate: nil]; [theStream close];
return;
33
/* Tear down the input stream. */ [theStream removeFromRunLoop: ... forMode: ...]; [theStream setDelegate: nil]; [theStream close];
} } }
34
Date 2012-12-13
Notes Corrected the data type used for the length provided to getsockname, fixed missing ampersands in code snippets, and corrected sockaddr_in6 initialization snippet. New document that explains how to perform certain networking tasks that although somewhat common, are beyond the scope of the overview documentation.
2012-07-19
35
Apple Inc. 2012 Apple Inc. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by any means, mechanical, electronic, photocopying, recording, or otherwise, without prior written permission of Apple Inc., with the following exceptions: Any person is hereby authorized to store documentation on a single computer for personal use only and to print copies of documentation for personal use provided that the documentation contains Apples copyright notice. No licenses, express or implied, are granted with respect to any of the technology described in this document. Apple retains all intellectual property rights associated with the technology described in this document. This document is intended to assist application developers to develop applications only for Apple-labeled computers. Apple Inc. 1 Infinite Loop Cupertino, CA 95014 408-996-1010 Apple, the Apple logo, Bonjour, Cocoa, Mac, Objective-C, and OS X are trademarks of Apple Inc., registered in the U.S. and other countries. UNIX is a registered trademark of The Open Group. iOS is a trademark or registered trademark of Cisco in the U.S. and other countries and is used under license.
Even though Apple has reviewed this document, APPLE MAKES NO WARRANTY OR REPRESENTATION, EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS DOCUMENT, ITS QUALITY, ACCURACY, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. AS A RESULT, THIS DOCUMENT IS PROVIDED AS IS, AND YOU, THE READER, ARE ASSUMING THE ENTIRE RISK AS TO ITS QUALITY AND ACCURACY. IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES RESULTING FROM ANY DEFECT OR INACCURACY IN THIS DOCUMENT, even if advised of the possibility of such damages. THE WARRANTY AND REMEDIES SET FORTH ABOVE ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer, agent, or employee is authorized to make any modification, extension, or addition to this warranty. Some states do not allow the exclusion or limitation of implied warranties or liability for incidental or consequential damages, so the above limitation or exclusion may not apply to you. This warranty gives you specific legal rights, and you may also have other rights which vary from state to state.