4 * TLS support code for the CUPS scheduler on OS X.
6 * Copyright 2007-2012 by Apple Inc.
7 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
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/".
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
21 * make_certificate() - Make a self-signed SSL/TLS certificate.
29 static CFArrayRef
copy_cdsa_certificate(cupsd_client_t
*con
);
30 static int make_certificate(cupsd_client_t
*con
);
34 * 'cupsdEndTLS()' - Shutdown a secure session with the client.
37 int /* O - 1 on success, 0 on error */
38 cupsdEndTLS(cupsd_client_t
*con
) /* I - Client connection */
40 while (SSLClose(con
->http
.tls
) == errSSLWouldBlock
)
43 SSLDisposeContext(con
->http
.tls
);
46 if (con
->http
.tls_credentials
)
47 CFRelease(con
->http
.tls_credentials
);
54 * 'cupsdStartTLS()' - Start a secure session with the client.
57 int /* O - 1 on success, 0 on error */
58 cupsdStartTLS(cupsd_client_t
*con
) /* I - Client connection */
60 OSStatus error
= 0; /* Error code */
61 CFArrayRef peerCerts
; /* Peer certificates */
64 cupsdLogMessage(CUPSD_LOG_DEBUG
, "[Client %d] Encrypting connection.",
67 con
->http
.tls_credentials
= copy_cdsa_certificate(con
);
69 if (!con
->http
.tls_credentials
)
72 * No keychain (yet), make a self-signed certificate...
75 if (make_certificate(con
))
76 con
->http
.tls_credentials
= copy_cdsa_certificate(con
);
79 if (!con
->http
.tls_credentials
)
81 cupsdLogMessage(CUPSD_LOG_ERROR
,
82 "Could not find signing key in keychain \"%s\"",
84 error
= errSSLBadConfiguration
;
88 error
= SSLNewContext(true, &con
->http
.tls
);
91 error
= SSLSetIOFuncs(con
->http
.tls
, _httpReadCDSA
, _httpWriteCDSA
);
94 error
= SSLSetConnection(con
->http
.tls
, HTTP(con
));
97 error
= SSLSetAllowsExpiredCerts(con
->http
.tls
, true);
100 error
= SSLSetAllowsAnyRoot(con
->http
.tls
, true);
103 error
= SSLSetCertificate(con
->http
.tls
, con
->http
.tls_credentials
);
108 * Perform SSL/TLS handshake
111 while ((error
= SSLHandshake(con
->http
.tls
)) == errSSLWouldBlock
)
117 cupsdLogMessage(CUPSD_LOG_ERROR
,
118 "Unable to encrypt connection from %s - %s (%d)",
119 con
->http
.hostname
, cssmErrorString(error
), (int)error
);
121 con
->http
.error
= error
;
122 con
->http
.status
= HTTP_ERROR
;
126 SSLDisposeContext(con
->http
.tls
);
127 con
->http
.tls
= NULL
;
130 if (con
->http
.tls_credentials
)
132 CFRelease(con
->http
.tls_credentials
);
133 con
->http
.tls_credentials
= NULL
;
139 cupsdLogMessage(CUPSD_LOG_DEBUG
, "Connection from %s now encrypted.",
142 if (!SSLCopyPeerCertificates(con
->http
.tls
, &peerCerts
) && peerCerts
)
144 cupsdLogMessage(CUPSD_LOG_DEBUG
, "Received %d peer certificates!",
145 (int)CFArrayGetCount(peerCerts
));
146 CFRelease(peerCerts
);
149 cupsdLogMessage(CUPSD_LOG_DEBUG
, "Received NO peer certificates!");
156 * 'copy_cdsa_certificate()' - Copy a SSL/TLS certificate from the System
160 static CFArrayRef
/* O - Array of certificates */
161 copy_cdsa_certificate(
162 cupsd_client_t
*con
) /* I - Client connection */
164 OSStatus err
; /* Error info */
165 SecKeychainRef keychain
= NULL
;/* Keychain reference */
166 SecIdentitySearchRef search
= NULL
; /* Search reference */
167 SecIdentityRef identity
= NULL
;/* Identity */
168 CFArrayRef certificates
= NULL
;
169 /* Certificate array */
170 # if HAVE_SECPOLICYCREATESSL
171 SecPolicyRef policy
= NULL
; /* Policy ref */
172 CFStringRef servername
= NULL
;
174 CFMutableDictionaryRef query
= NULL
; /* Query qualifiers */
175 CFArrayRef list
= NULL
; /* Keychain list */
176 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
177 char localname
[1024];/* Local hostname */
178 # endif /* HAVE_DNSSD || HAVE_AVAHI */
179 # elif defined(HAVE_SECIDENTITYSEARCHCREATEWITHPOLICY)
180 SecPolicyRef policy
= NULL
; /* Policy ref */
181 SecPolicySearchRef policy_search
= NULL
;
182 /* Policy search ref */
183 CSSM_DATA options
; /* Policy options */
184 CSSM_APPLE_TP_SSL_OPTIONS
185 ssl_options
; /* SSL Option for hostname */
186 char localname
[1024];/* Local hostname */
187 # endif /* HAVE_SECPOLICYCREATESSL */
190 cupsdLogMessage(CUPSD_LOG_DEBUG
,
191 "copy_cdsa_certificate: Looking for certs for \"%s\"...",
194 if ((err
= SecKeychainOpen(ServerCertificate
, &keychain
)))
196 cupsdLogMessage(CUPSD_LOG_ERROR
, "Cannot open keychain \"%s\" - %s (%d)",
197 ServerCertificate
, cssmErrorString(err
), (int)err
);
201 # if HAVE_SECPOLICYCREATESSL
202 servername
= CFStringCreateWithCString(kCFAllocatorDefault
, con
->servername
,
203 kCFStringEncodingUTF8
);
205 policy
= SecPolicyCreateSSL(1, servername
);
208 CFRelease(servername
);
212 cupsdLogMessage(CUPSD_LOG_ERROR
, "Cannot create ssl policy reference");
216 if (!(query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
217 &kCFTypeDictionaryKeyCallBacks
,
218 &kCFTypeDictionaryValueCallBacks
)))
220 cupsdLogMessage(CUPSD_LOG_ERROR
, "Cannot create query dictionary");
224 list
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&keychain
, 1,
225 &kCFTypeArrayCallBacks
);
227 CFDictionaryAddValue(query
, kSecClass
, kSecClassIdentity
);
228 CFDictionaryAddValue(query
, kSecMatchPolicy
, policy
);
229 CFDictionaryAddValue(query
, kSecReturnRef
, kCFBooleanTrue
);
230 CFDictionaryAddValue(query
, kSecMatchLimit
, kSecMatchLimitOne
);
231 CFDictionaryAddValue(query
, kSecMatchSearchList
, list
);
235 err
= SecItemCopyMatching(query
, (CFTypeRef
*)&identity
);
237 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
238 if (err
&& DNSSDHostName
)
241 * Search for the connection server name failed; try the DNS-SD .local
242 * hostname instead...
245 snprintf(localname
, sizeof(localname
), "%s.local", DNSSDHostName
);
247 cupsdLogMessage(CUPSD_LOG_DEBUG
,
248 "copy_cdsa_certificate: Looking for certs for \"%s\"...",
251 servername
= CFStringCreateWithCString(kCFAllocatorDefault
, localname
,
252 kCFStringEncodingUTF8
);
256 policy
= SecPolicyCreateSSL(1, servername
);
259 CFRelease(servername
);
263 cupsdLogMessage(CUPSD_LOG_ERROR
, "Cannot create ssl policy reference");
267 CFDictionarySetValue(query
, kSecMatchPolicy
, policy
);
269 err
= SecItemCopyMatching(query
, (CFTypeRef
*)&identity
);
271 # endif /* HAVE_DNSSD || HAVE_AVAHI */
275 cupsdLogMessage(CUPSD_LOG_DEBUG
,
276 "Cannot find signing key in keychain \"%s\": %s (%d)",
277 ServerCertificate
, cssmErrorString(err
), (int)err
);
281 # elif defined(HAVE_SECIDENTITYSEARCHCREATEWITHPOLICY)
283 * Use a policy to search for valid certificates whose common name matches the
287 if (SecPolicySearchCreate(CSSM_CERT_X_509v3
, &CSSMOID_APPLE_TP_SSL
,
288 NULL
, &policy_search
))
290 cupsdLogMessage(CUPSD_LOG_ERROR
, "Cannot create a policy search reference");
294 if (SecPolicySearchCopyNext(policy_search
, &policy
))
296 cupsdLogMessage(CUPSD_LOG_ERROR
,
297 "Cannot find a policy to use for searching");
301 memset(&ssl_options
, 0, sizeof(ssl_options
));
302 ssl_options
.Version
= CSSM_APPLE_TP_SSL_OPTS_VERSION
;
303 ssl_options
.ServerName
= con
->servername
;
304 ssl_options
.ServerNameLen
= strlen(con
->servername
);
306 options
.Data
= (uint8
*)&ssl_options
;
307 options
.Length
= sizeof(ssl_options
);
309 if (SecPolicySetValue(policy
, &options
))
311 cupsdLogMessage(CUPSD_LOG_ERROR
,
312 "Cannot set policy value to use for searching");
316 if ((err
= SecIdentitySearchCreateWithPolicy(policy
, NULL
, CSSM_KEYUSE_SIGN
,
317 keychain
, FALSE
, &search
)))
319 cupsdLogMessage(CUPSD_LOG_ERROR
,
320 "Cannot create identity search reference: %s (%d)",
321 cssmErrorString(err
), (int)err
);
325 err
= SecIdentitySearchCopyNext(search
, &identity
);
327 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
328 if (err
&& DNSSDHostName
)
331 * Search for the connection server name failed; try the DNS-SD .local
332 * hostname instead...
335 snprintf(localname
, sizeof(localname
), "%s.local", DNSSDHostName
);
337 ssl_options
.ServerName
= localname
;
338 ssl_options
.ServerNameLen
= strlen(localname
);
340 cupsdLogMessage(CUPSD_LOG_DEBUG
,
341 "copy_cdsa_certificate: Looking for certs for \"%s\"...",
344 if (SecPolicySetValue(policy
, &options
))
346 cupsdLogMessage(CUPSD_LOG_ERROR
,
347 "Cannot set policy value to use for searching");
353 if ((err
= SecIdentitySearchCreateWithPolicy(policy
, NULL
, CSSM_KEYUSE_SIGN
,
354 keychain
, FALSE
, &search
)))
356 cupsdLogMessage(CUPSD_LOG_ERROR
,
357 "Cannot create identity search reference: %s (%d)",
358 cssmErrorString(err
), (int)err
);
362 err
= SecIdentitySearchCopyNext(search
, &identity
);
365 # endif /* HAVE_DNSSD || HAVE_AVAHI */
369 cupsdLogMessage(CUPSD_LOG_DEBUG
,
370 "Cannot find signing key in keychain \"%s\": %s (%d)",
371 ServerCertificate
, cssmErrorString(err
), (int)err
);
377 * Assume there is exactly one SecIdentity in the keychain...
380 if ((err
= SecIdentitySearchCreate(keychain
, CSSM_KEYUSE_SIGN
, &search
)))
382 cupsdLogMessage(CUPSD_LOG_DEBUG
,
383 "Cannot create identity search reference (%d)", (int)err
);
387 if ((err
= SecIdentitySearchCopyNext(search
, &identity
)))
389 cupsdLogMessage(CUPSD_LOG_DEBUG
,
390 "Cannot find signing key in keychain \"%s\": %s (%d)",
391 ServerCertificate
, cssmErrorString(err
), (int)err
);
394 # endif /* HAVE_SECPOLICYCREATESSL */
396 if (CFGetTypeID(identity
) != SecIdentityGetTypeID())
398 cupsdLogMessage(CUPSD_LOG_ERROR
, "SecIdentity CFTypeID failure!");
402 if ((certificates
= CFArrayCreate(NULL
, (const void **)&identity
,
403 1, &kCFTypeArrayCallBacks
)) == NULL
)
405 cupsdLogMessage(CUPSD_LOG_ERROR
, "Cannot create certificate array");
418 # if HAVE_SECPOLICYCREATESSL
423 # elif defined(HAVE_SECIDENTITYSEARCHCREATEWITHPOLICY)
427 CFRelease(policy_search
);
428 # endif /* HAVE_SECPOLICYCREATESSL */
430 return (certificates
);
435 * 'make_certificate()' - Make a self-signed SSL/TLS certificate.
438 static int /* O - 1 on success, 0 on failure */
439 make_certificate(cupsd_client_t
*con
) /* I - Client connection */
441 int pid
, /* Process ID of command */
442 status
; /* Status of command */
443 char command
[1024], /* Command */
444 *argv
[4], /* Command-line arguments */
445 *envp
[MAX_ENV
+ 1], /* Environment variables */
446 keychain
[1024], /* Keychain argument */
447 infofile
[1024], /* Type-in information for cert */
448 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
449 localname
[1024], /* Local hostname */
450 # endif /* HAVE_DNSSD || HAVE_AVAHI */
451 *servername
; /* Name of server in cert */
452 cups_file_t
*fp
; /* Seed/info file */
453 int infofd
; /* Info file descriptor */
456 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
457 if (con
->servername
&& isdigit(con
->servername
[0] & 255) && DNSSDHostName
)
459 snprintf(localname
, sizeof(localname
), "%s.local", DNSSDHostName
);
460 servername
= localname
;
463 # endif /* HAVE_DNSSD || HAVE_AVAHI */
464 servername
= con
->servername
;
467 * Run the "certtool" command to generate a self-signed certificate...
470 if (!cupsFileFind("certtool", getenv("PATH"), 1, command
, sizeof(command
)))
472 cupsdLogMessage(CUPSD_LOG_ERROR
,
473 "No SSL certificate and certtool command not found!");
478 * Create a file with the certificate information fields...
480 * Note: This assumes that the default questions are asked by the certtool
484 if ((fp
= cupsTempFile2(infofile
, sizeof(infofile
))) == NULL
)
486 cupsdLogMessage(CUPSD_LOG_ERROR
,
487 "Unable to create certificate information file %s - %s",
488 infofile
, strerror(errno
));
493 "%s\n" /* Enter key and certificate label */
494 "r\n" /* Generate RSA key pair */
495 "2048\n" /* Key size in bits */
496 "y\n" /* OK (y = yes) */
497 "b\n" /* Usage (b=signing/encryption) */
498 "s\n" /* Sign with SHA1 */
499 "y\n" /* OK (y = yes) */
500 "%s\n" /* Common name */
501 "\n" /* Country (default) */
502 "\n" /* Organization (default) */
503 "\n" /* Organizational unit (default) */
504 "\n" /* State/Province (default) */
505 "%s\n" /* Email address */
506 "y\n", /* OK (y = yes) */
507 servername
, servername
, ServerAdmin
);
510 cupsdLogMessage(CUPSD_LOG_INFO
,
511 "Generating SSL server key and certificate...");
513 snprintf(keychain
, sizeof(keychain
), "k=%s", ServerCertificate
);
515 argv
[0] = "certtool";
520 cupsdLoadEnv(envp
, MAX_ENV
);
522 infofd
= open(infofile
, O_RDONLY
);
524 if (!cupsdStartProcess(command
, argv
, envp
, infofd
, -1, -1, -1, -1, 1, NULL
,
535 while (waitpid(pid
, &status
, 0) < 0)
542 cupsdFinishProcess(pid
, command
, sizeof(command
), NULL
);
546 if (WIFEXITED(status
))
547 cupsdLogMessage(CUPSD_LOG_ERROR
,
548 "Unable to create SSL server key and certificate - "
549 "the certtool command stopped with status %d!",
550 WEXITSTATUS(status
));
552 cupsdLogMessage(CUPSD_LOG_ERROR
,
553 "Unable to create SSL server key and certificate - "
554 "the certtool command crashed on signal %d!",
559 cupsdLogMessage(CUPSD_LOG_INFO
,
560 "Created SSL server certificate file \"%s\"...",