]> git.ipfire.org Git - thirdparty/cups.git/blob - scheduler/tls-darwin.c
Save work.
[thirdparty/cups.git] / scheduler / tls-darwin.c
1 /*
2 * "$Id$"
3 *
4 * TLS support code for the CUPS scheduler on OS X.
5 *
6 * Copyright 2007-2013 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
29 static CFArrayRef copy_cdsa_certificate(cupsd_client_t *con);
30 static int make_certificate(cupsd_client_t *con);
31
32
33 /*
34 * 'cupsdEndTLS()' - Shutdown a secure session with the client.
35 */
36
37 int /* O - 1 on success, 0 on error */
38 cupsdEndTLS(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
57 int /* O - 1 on success, 0 on error */
58 cupsdStartTLS(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->number);
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
155 static CFArrayRef /* O - Array of certificates */
156 copy_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
300 static int /* O - 1 on success, 0 on failure */
301 make_certificate(cupsd_client_t *con) /* I - Client connection */
302 {
303 # ifdef HAVE_SECGENERATESELFSIGNEDCERTIFICATE
304 int status = 0; /* Return status */
305 OSStatus err; /* Error code (if any) */
306 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
307 char localname[1024];/* Local hostname */
308 # endif /* HAVE_DNSSD || HAVE_AVAHI */
309 const char *servername; /* Name of server in cert */
310 CFStringRef cfservername = NULL;
311 /* CF string for server name */
312 SecIdentityRef ident = NULL; /* Identity */
313 SecKeyRef publicKey = NULL,
314 /* Public key */
315 privateKey = NULL;
316 /* Private key */
317 CFMutableDictionaryRef keyParams = NULL;
318 /* Key generation parameters */
319
320
321 cupsdLogMessage(CUPSD_LOG_INFO,
322 "Generating SSL server key and certificate.");
323
324 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
325 if (con->servername && isdigit(con->servername[0] & 255) && DNSSDHostName)
326 {
327 snprintf(localname, sizeof(localname), "%s.local", DNSSDHostName);
328 servername = localname;
329 }
330 else
331 # endif /* HAVE_DNSSD || HAVE_AVAHI */
332 servername = con->servername;
333
334 cfservername = CFStringCreateWithCString(kCFAllocatorDefault, servername,
335 kCFStringEncodingUTF8);
336 if (!cfservername)
337 goto cleanup;
338
339 /*
340 * Create a public/private key pair...
341 */
342
343 keyParams = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
344 &kCFTypeDictionaryKeyCallBacks,
345 &kCFTypeDictionaryValueCallBacks);
346 if (!keyParams)
347 goto cleanup;
348
349 CFDictionaryAddValue(keyParams, kSecAttrKeyType, kSecAttrKeyTypeRSA);
350 CFDictionaryAddValue(keyParams, kSecAttrKeySizeInBits, CFSTR("2048"));
351 CFDictionaryAddValue(keyParams, kSecAttrLabel,
352 CFSTR("CUPS Self-Signed Certificate"));
353
354 err = SecKeyGeneratePair(keyParams, &publicKey, &privateKey);
355 if (err != noErr)
356 {
357 cupsdLogMessage(CUPSD_LOG_DEBUG, "SecKeyGeneratePair returned %ld.",
358 (long)err);
359 goto cleanup;
360 }
361
362 /*
363 * Create a self-signed certificate using the public/private key pair...
364 */
365
366 CFIndex usageInt = kSecKeyUsageAll;
367 CFNumberRef usage = CFNumberCreate(alloc, kCFNumberCFIndexType, &usageInt);
368 CFDictionaryRef certParams = CFDictionaryCreateMutable(kCFAllocatorDefault,
369 kSecCSRBasicContraintsPathLen, CFINT(0),
370 kSecSubjectAltName, cfservername,
371 kSecCertificateKeyUsage, usage,
372 NULL, NULL);
373 CFRelease(usage);
374
375 const void *ca_o[] = { kSecOidOrganization, CFSTR("") };
376 const void *ca_cn[] = { kSecOidCommonName, cfservername };
377 CFArrayRef ca_o_dn = CFArrayCreate(kCFAllocatorDefault, ca_o, 2, NULL);
378 CFArrayRef ca_cn_dn = CFArrayCreate(kCFAllocatorDefault, ca_cn, 2, NULL);
379 const void *ca_dn_array[2];
380
381 ca_dn_array[0] = CFArrayCreate(kCFAllocatorDefault, (const void **)&ca_o_dn,
382 1, NULL);
383 ca_dn_array[1] = CFArrayCreate(kCFAllocatorDefault, (const void **)&ca_cn_dn,
384 1, NULL);
385
386 CFArrayRef subject = CFArrayCreate(kCFAllocatorDefault, ca_dn_array, 2,
387 NULL);
388 SecCertificateRef cert = SecGenerateSelfSignedCertificate(subject, certParams,
389 publicKey,
390 privateKey);
391 CFRelease(subject);
392 CFRelease(certParams);
393
394 if (!cert)
395 {
396 cupsdLogMessage(CUPSD_LOG_DEBUG, "SecGenerateSelfSignedCertificate failed.");
397 goto cleanup;
398 }
399
400 ident = SecIdentityCreate(kCFAllocatorDefault, cert, privateKey);
401
402 if (ident)
403 cupsdLogMessage(CUPSD_LOG_INFO,
404 "Created SSL server certificate file \"%s\".",
405 ServerCertificate);
406
407 /*
408 * Cleanup and return...
409 */
410
411 cleanup:
412
413 if (cfservername)
414 CFRelease(cfservername);
415
416 if (keyParams)
417 CFRelease(keyParams);
418
419 if (ident)
420 CFRelease(ident);
421
422 if (cert)
423 CFRelease(cert);
424
425 if (publicKey)
426 CFRelease(publicKey);
427
428 if (privateKey)
429 CFRelease(publicKey);
430
431 if (!status)
432 cupsdLogMessage(CUPSD_LOG_ERROR,
433 "Unable to create SSL server key and certificate.");
434
435 return (status);
436
437 # else /* !HAVE_SECGENERATESELFSIGNEDCERTIFICATE */
438 int pid, /* Process ID of command */
439 status; /* Status of command */
440 char command[1024], /* Command */
441 *argv[4], /* Command-line arguments */
442 *envp[MAX_ENV + 1], /* Environment variables */
443 keychain[1024], /* Keychain argument */
444 infofile[1024], /* Type-in information for cert */
445 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
446 localname[1024], /* Local hostname */
447 # endif /* HAVE_DNSSD || HAVE_AVAHI */
448 *servername; /* Name of server in cert */
449 cups_file_t *fp; /* Seed/info file */
450 int infofd; /* Info file descriptor */
451
452
453 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
454 if (con->servername && isdigit(con->servername[0] & 255) && DNSSDHostName)
455 {
456 snprintf(localname, sizeof(localname), "%s.local", DNSSDHostName);
457 servername = localname;
458 }
459 else
460 # endif /* HAVE_DNSSD || HAVE_AVAHI */
461 servername = con->servername;
462
463 /*
464 * Run the "certtool" command to generate a self-signed certificate...
465 */
466
467 if (!cupsFileFind("certtool", getenv("PATH"), 1, command, sizeof(command)))
468 {
469 cupsdLogMessage(CUPSD_LOG_ERROR,
470 "No SSL certificate and certtool command not found.");
471 return (0);
472 }
473
474 /*
475 * Create a file with the certificate information fields...
476 *
477 * Note: This assumes that the default questions are asked by the certtool
478 * command...
479 */
480
481 if ((fp = cupsTempFile2(infofile, sizeof(infofile))) == NULL)
482 {
483 cupsdLogMessage(CUPSD_LOG_ERROR,
484 "Unable to create certificate information file %s - %s",
485 infofile, strerror(errno));
486 return (0);
487 }
488
489 cupsFilePrintf(fp,
490 "%s\n" /* Enter key and certificate label */
491 "r\n" /* Generate RSA key pair */
492 "2048\n" /* Key size in bits */
493 "y\n" /* OK (y = yes) */
494 "b\n" /* Usage (b=signing/encryption) */
495 "s\n" /* Sign with SHA1 */
496 "y\n" /* OK (y = yes) */
497 "%s\n" /* Common name */
498 "\n" /* Country (default) */
499 "\n" /* Organization (default) */
500 "\n" /* Organizational unit (default) */
501 "\n" /* State/Province (default) */
502 "%s\n" /* Email address */
503 "y\n", /* OK (y = yes) */
504 servername, servername, ServerAdmin);
505 cupsFileClose(fp);
506
507 cupsdLogMessage(CUPSD_LOG_INFO,
508 "Generating SSL server key and certificate.");
509
510 snprintf(keychain, sizeof(keychain), "k=%s", ServerCertificate);
511
512 argv[0] = "certtool";
513 argv[1] = "c";
514 argv[2] = keychain;
515 argv[3] = NULL;
516
517 cupsdLoadEnv(envp, MAX_ENV);
518
519 infofd = open(infofile, O_RDONLY);
520
521 if (!cupsdStartProcess(command, argv, envp, infofd, -1, -1, -1, -1, 1, NULL,
522 NULL, &pid))
523 {
524 close(infofd);
525 unlink(infofile);
526 return (0);
527 }
528
529 close(infofd);
530 unlink(infofile);
531
532 while (waitpid(pid, &status, 0) < 0)
533 if (errno != EINTR)
534 {
535 status = 1;
536 break;
537 }
538
539 cupsdFinishProcess(pid, command, sizeof(command), NULL);
540
541 if (status)
542 {
543 if (WIFEXITED(status))
544 cupsdLogMessage(CUPSD_LOG_ERROR,
545 "Unable to create SSL server key and certificate - "
546 "the certtool command stopped with status %d.",
547 WEXITSTATUS(status));
548 else
549 cupsdLogMessage(CUPSD_LOG_ERROR,
550 "Unable to create SSL server key and certificate - "
551 "the certtool command crashed on signal %d.",
552 WTERMSIG(status));
553 }
554 else
555 {
556 cupsdLogMessage(CUPSD_LOG_INFO,
557 "Created SSL server certificate file \"%s\".",
558 ServerCertificate);
559 }
560
561 return (!status);
562 # endif /* HAVE_SECGENERATESELFSIGNEDCERTIFICATE */
563 }
564
565
566 /*
567 * End of "$Id$".
568 */