1/*
2 * "$Id: tls-darwin.c 4057 2012-12-07 21:34:43Z msweet $"
3 *
4 *   TLS support code for the CUPS scheduler on OS X.
5 *
6 *   Copyright 2007-2012 by Apple Inc.
7 *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
8 *
9 *   These coded instructions, statements, and computer programs are the
10 *   property of Apple Inc. and are protected by Federal copyright
11 *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
12 *   which should have been included with this file.  If this file is
13 *   file is missing or damaged, see the license at "http://www.cups.org/".
14 *
15 * Contents:
16 *
17 *   cupsdEndTLS()	     - Shutdown a secure session with the client.
18 *   cupsdStartTLS()	     - Start a secure session with the client.
19 *   copy_cdsa_certificate() - Copy a SSL/TLS certificate from the System
20 *			       keychain.
21 *   make_certificate()      - Make a self-signed SSL/TLS certificate.
22 */
23
24
25/*
26 * Local functions...
27 */
28
29static CFArrayRef	copy_cdsa_certificate(cupsd_client_t *con);
30static int		make_certificate(cupsd_client_t *con);
31
32
33/*
34 * 'cupsdEndTLS()' - Shutdown a secure session with the client.
35 */
36
37int					/* O - 1 on success, 0 on error */
38cupsdEndTLS(cupsd_client_t *con)	/* I - Client connection */
39{
40  while (SSLClose(con->http.tls) == errSSLWouldBlock)
41    usleep(1000);
42
43  CFRelease(con->http.tls);
44  con->http.tls = NULL;
45
46  if (con->http.tls_credentials)
47    CFRelease(con->http.tls_credentials);
48
49  return (1);
50}
51
52
53/*
54 * 'cupsdStartTLS()' - Start a secure session with the client.
55 */
56
57int					/* O - 1 on success, 0 on error */
58cupsdStartTLS(cupsd_client_t *con)	/* I - Client connection */
59{
60  OSStatus	error = 0;		/* Error code */
61  SecTrustRef	peerTrust;		/* Peer certificates */
62
63
64  cupsdLogMessage(CUPSD_LOG_DEBUG, "[Client %d] Encrypting connection.",
65                  con->http.fd);
66
67  con->http.tls_credentials = copy_cdsa_certificate(con);
68
69  if (!con->http.tls_credentials)
70  {
71   /*
72    * No keychain (yet), make a self-signed certificate...
73    */
74
75    if (make_certificate(con))
76      con->http.tls_credentials = copy_cdsa_certificate(con);
77  }
78
79  if (!con->http.tls_credentials)
80  {
81    cupsdLogMessage(CUPSD_LOG_ERROR,
82        	    "Could not find signing key in keychain \"%s\"",
83		    ServerCertificate);
84    error = errSSLBadConfiguration;
85  }
86
87  if (!error)
88    con->http.tls = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide,
89                                     kSSLStreamType);
90
91  if (!error)
92    error = SSLSetIOFuncs(con->http.tls, _httpReadCDSA, _httpWriteCDSA);
93
94  if (!error)
95    error = SSLSetConnection(con->http.tls, HTTP(con));
96
97  if (!error)
98    error = SSLSetCertificate(con->http.tls, con->http.tls_credentials);
99
100  if (!error)
101  {
102   /*
103    * Perform SSL/TLS handshake
104    */
105
106    while ((error = SSLHandshake(con->http.tls)) == errSSLWouldBlock)
107      usleep(1000);
108  }
109
110  if (error)
111  {
112    cupsdLogMessage(CUPSD_LOG_ERROR,
113                    "Unable to encrypt connection from %s - %s (%d)",
114                    con->http.hostname, cssmErrorString(error), (int)error);
115
116    con->http.error  = error;
117    con->http.status = HTTP_ERROR;
118
119    if (con->http.tls)
120    {
121      CFRelease(con->http.tls);
122      con->http.tls = NULL;
123    }
124
125    if (con->http.tls_credentials)
126    {
127      CFRelease(con->http.tls_credentials);
128      con->http.tls_credentials = NULL;
129    }
130
131    return (0);
132  }
133
134  cupsdLogMessage(CUPSD_LOG_DEBUG, "Connection from %s now encrypted.",
135                  con->http.hostname);
136
137  if (!SSLCopyPeerTrust(con->http.tls, &peerTrust) && peerTrust)
138  {
139    cupsdLogMessage(CUPSD_LOG_DEBUG, "Received %d peer certificates!",
140		    (int)SecTrustGetCertificateCount(peerTrust));
141    CFRelease(peerTrust);
142  }
143  else
144    cupsdLogMessage(CUPSD_LOG_DEBUG, "Received NO peer certificates!");
145
146  return (1);
147}
148
149
150/*
151 * 'copy_cdsa_certificate()' - Copy a SSL/TLS certificate from the System
152 *                             keychain.
153 */
154
155static CFArrayRef				/* O - Array of certificates */
156copy_cdsa_certificate(
157    cupsd_client_t *con)			/* I - Client connection */
158{
159  OSStatus		err;		/* Error info */
160  SecKeychainRef	keychain = NULL;/* Keychain reference */
161  SecIdentitySearchRef	search = NULL;	/* Search reference */
162  SecIdentityRef	identity = NULL;/* Identity */
163  CFArrayRef		certificates = NULL;
164					/* Certificate array */
165  SecPolicyRef		policy = NULL;	/* Policy ref */
166  CFStringRef		servername = NULL;
167					/* Server name */
168  CFMutableDictionaryRef query = NULL;	/* Query qualifiers */
169  CFArrayRef		list = NULL;	/* Keychain list */
170#    if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
171  char			localname[1024];/* Local hostname */
172#    endif /* HAVE_DNSSD || HAVE_AVAHI */
173
174
175  cupsdLogMessage(CUPSD_LOG_DEBUG,
176                  "copy_cdsa_certificate: Looking for certs for \"%s\"...",
177		  con->servername);
178
179  if ((err = SecKeychainOpen(ServerCertificate, &keychain)))
180  {
181    cupsdLogMessage(CUPSD_LOG_ERROR, "Cannot open keychain \"%s\" - %s (%d)",
182	            ServerCertificate, cssmErrorString(err), (int)err);
183    goto cleanup;
184  }
185
186  servername = CFStringCreateWithCString(kCFAllocatorDefault, con->servername,
187					 kCFStringEncodingUTF8);
188
189  policy = SecPolicyCreateSSL(1, servername);
190
191  if (servername)
192    CFRelease(servername);
193
194  if (!policy)
195  {
196    cupsdLogMessage(CUPSD_LOG_ERROR, "Cannot create ssl policy reference");
197    goto cleanup;
198  }
199
200  if (!(query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
201					  &kCFTypeDictionaryKeyCallBacks,
202					  &kCFTypeDictionaryValueCallBacks)))
203  {
204    cupsdLogMessage(CUPSD_LOG_ERROR, "Cannot create query dictionary");
205    goto cleanup;
206  }
207
208  list = CFArrayCreate(kCFAllocatorDefault, (const void **)&keychain, 1,
209                       &kCFTypeArrayCallBacks);
210
211  CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
212  CFDictionaryAddValue(query, kSecMatchPolicy, policy);
213  CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
214  CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitOne);
215  CFDictionaryAddValue(query, kSecMatchSearchList, list);
216
217  CFRelease(list);
218
219  err = SecItemCopyMatching(query, (CFTypeRef *)&identity);
220
221#    if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
222  if (err && DNSSDHostName)
223  {
224   /*
225    * Search for the connection server name failed; try the DNS-SD .local
226    * hostname instead...
227    */
228
229    snprintf(localname, sizeof(localname), "%s.local", DNSSDHostName);
230
231    cupsdLogMessage(CUPSD_LOG_DEBUG,
232		    "copy_cdsa_certificate: Looking for certs for \"%s\"...",
233		    localname);
234
235    servername = CFStringCreateWithCString(kCFAllocatorDefault, localname,
236					   kCFStringEncodingUTF8);
237
238    CFRelease(policy);
239
240    policy = SecPolicyCreateSSL(1, servername);
241
242    if (servername)
243      CFRelease(servername);
244
245    if (!policy)
246    {
247      cupsdLogMessage(CUPSD_LOG_ERROR, "Cannot create ssl policy reference");
248      goto cleanup;
249    }
250
251    CFDictionarySetValue(query, kSecMatchPolicy, policy);
252
253    err = SecItemCopyMatching(query, (CFTypeRef *)&identity);
254  }
255#    endif /* HAVE_DNSSD || HAVE_AVAHI */
256
257  if (err)
258  {
259    cupsdLogMessage(CUPSD_LOG_DEBUG,
260		    "Cannot find signing key in keychain \"%s\": %s (%d)",
261		    ServerCertificate, cssmErrorString(err), (int)err);
262    goto cleanup;
263  }
264
265  if (CFGetTypeID(identity) != SecIdentityGetTypeID())
266  {
267    cupsdLogMessage(CUPSD_LOG_ERROR, "SecIdentity CFTypeID failure!");
268    goto cleanup;
269  }
270
271  if ((certificates = CFArrayCreate(NULL, (const void **)&identity,
272				  1, &kCFTypeArrayCallBacks)) == NULL)
273  {
274    cupsdLogMessage(CUPSD_LOG_ERROR, "Cannot create certificate array");
275    goto cleanup;
276  }
277
278  cleanup :
279
280  if (keychain)
281    CFRelease(keychain);
282  if (search)
283    CFRelease(search);
284  if (identity)
285    CFRelease(identity);
286
287  if (policy)
288    CFRelease(policy);
289  if (query)
290    CFRelease(query);
291
292  return (certificates);
293}
294
295
296/*
297 * 'make_certificate()' - Make a self-signed SSL/TLS certificate.
298 */
299
300static int				/* O - 1 on success, 0 on failure */
301make_certificate(cupsd_client_t *con)	/* I - Client connection */
302{
303  int		pid,			/* Process ID of command */
304		status;			/* Status of command */
305  char		command[1024],		/* Command */
306		*argv[4],		/* Command-line arguments */
307		*envp[MAX_ENV + 1],	/* Environment variables */
308		keychain[1024],		/* Keychain argument */
309		infofile[1024],		/* Type-in information for cert */
310#  if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
311		localname[1024],	/* Local hostname */
312#  endif /* HAVE_DNSSD || HAVE_AVAHI */
313		*servername;		/* Name of server in cert */
314  cups_file_t	*fp;			/* Seed/info file */
315  int		infofd;			/* Info file descriptor */
316
317
318#  if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
319  if (con->servername && isdigit(con->servername[0] & 255) && DNSSDHostName)
320  {
321    snprintf(localname, sizeof(localname), "%s.local", DNSSDHostName);
322    servername = localname;
323  }
324  else
325#  endif /* HAVE_DNSSD || HAVE_AVAHI */
326    servername = con->servername;
327
328 /*
329  * Run the "certtool" command to generate a self-signed certificate...
330  */
331
332  if (!cupsFileFind("certtool", getenv("PATH"), 1, command, sizeof(command)))
333  {
334    cupsdLogMessage(CUPSD_LOG_ERROR,
335                    "No SSL certificate and certtool command not found!");
336    return (0);
337  }
338
339 /*
340  * Create a file with the certificate information fields...
341  *
342  * Note: This assumes that the default questions are asked by the certtool
343  * command...
344  */
345
346  if ((fp = cupsTempFile2(infofile, sizeof(infofile))) == NULL)
347  {
348    cupsdLogMessage(CUPSD_LOG_ERROR,
349                    "Unable to create certificate information file %s - %s",
350                    infofile, strerror(errno));
351    return (0);
352  }
353
354  cupsFilePrintf(fp,
355                 "%s\n"			/* Enter key and certificate label */
356                 "r\n"			/* Generate RSA key pair */
357                 "2048\n"		/* Key size in bits */
358                 "y\n"			/* OK (y = yes) */
359                 "b\n"			/* Usage (b=signing/encryption) */
360                 "s\n"			/* Sign with SHA1 */
361                 "y\n"			/* OK (y = yes) */
362                 "%s\n"			/* Common name */
363                 "\n"			/* Country (default) */
364                 "\n"			/* Organization (default) */
365                 "\n"			/* Organizational unit (default) */
366                 "\n"			/* State/Province (default) */
367                 "%s\n"			/* Email address */
368                 "y\n",			/* OK (y = yes) */
369        	 servername, servername, ServerAdmin);
370  cupsFileClose(fp);
371
372  cupsdLogMessage(CUPSD_LOG_INFO,
373                  "Generating SSL server key and certificate...");
374
375  snprintf(keychain, sizeof(keychain), "k=%s", ServerCertificate);
376
377  argv[0] = "certtool";
378  argv[1] = "c";
379  argv[2] = keychain;
380  argv[3] = NULL;
381
382  cupsdLoadEnv(envp, MAX_ENV);
383
384  infofd = open(infofile, O_RDONLY);
385
386  if (!cupsdStartProcess(command, argv, envp, infofd, -1, -1, -1, -1, 1, NULL,
387                         NULL, &pid))
388  {
389    close(infofd);
390    unlink(infofile);
391    return (0);
392  }
393
394  close(infofd);
395  unlink(infofile);
396
397  while (waitpid(pid, &status, 0) < 0)
398    if (errno != EINTR)
399    {
400      status = 1;
401      break;
402    }
403
404  cupsdFinishProcess(pid, command, sizeof(command), NULL);
405
406  if (status)
407  {
408    if (WIFEXITED(status))
409      cupsdLogMessage(CUPSD_LOG_ERROR,
410                      "Unable to create SSL server key and certificate - "
411		      "the certtool command stopped with status %d!",
412	              WEXITSTATUS(status));
413    else
414      cupsdLogMessage(CUPSD_LOG_ERROR,
415                      "Unable to create SSL server key and certificate - "
416		      "the certtool command crashed on signal %d!",
417	              WTERMSIG(status));
418  }
419  else
420  {
421    cupsdLogMessage(CUPSD_LOG_INFO,
422                    "Created SSL server certificate file \"%s\"...",
423		    ServerCertificate);
424  }
425
426  return (!status);
427}
428
429
430/*
431 * End of "$Id: tls-darwin.c 4057 2012-12-07 21:34:43Z msweet $".
432 */
433