4 * TLS support code for the CUPS scheduler on OS X.
6 * Copyright 2007-2013 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 CFRelease(con
->http
->tls
);
44 con
->http
->tls
= NULL
;
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 SecTrustRef peerTrust
; /* Peer certificates */
64 cupsdLogMessage(CUPSD_LOG_DEBUG
, "[Client %d] Encrypting connection.",
67 con
->http
->encryption
= HTTP_ENCRYPTION_ALWAYS
;
69 con
->http
->tls_credentials
= copy_cdsa_certificate(con
);
71 if (!con
->http
->tls_credentials
)
74 * No keychain (yet), make a self-signed certificate...
77 if (make_certificate(con
))
78 con
->http
->tls_credentials
= copy_cdsa_certificate(con
);
81 if (!con
->http
->tls_credentials
)
83 cupsdLogMessage(CUPSD_LOG_ERROR
,
84 "Could not find signing key in keychain \"%s\"",
86 error
= errSSLBadConfiguration
;
90 con
->http
->tls
= SSLCreateContext(kCFAllocatorDefault
, kSSLServerSide
,
94 error
= SSLSetIOFuncs(con
->http
->tls
, _httpReadCDSA
, _httpWriteCDSA
);
97 error
= SSLSetConnection(con
->http
->tls
, HTTP(con
));
100 error
= SSLSetCertificate(con
->http
->tls
, con
->http
->tls_credentials
);
105 * Perform SSL/TLS handshake
108 while ((error
= SSLHandshake(con
->http
->tls
)) == errSSLWouldBlock
)
114 cupsdLogMessage(CUPSD_LOG_ERROR
,
115 "Unable to encrypt connection from %s - %s (%d)",
116 con
->http
->hostname
, cssmErrorString(error
), (int)error
);
118 con
->http
->error
= error
;
119 con
->http
->status
= HTTP_ERROR
;
123 CFRelease(con
->http
->tls
);
124 con
->http
->tls
= NULL
;
127 if (con
->http
->tls_credentials
)
129 CFRelease(con
->http
->tls_credentials
);
130 con
->http
->tls_credentials
= NULL
;
136 cupsdLogMessage(CUPSD_LOG_DEBUG
, "Connection from %s now encrypted.",
137 con
->http
->hostname
);
139 if (!SSLCopyPeerTrust(con
->http
->tls
, &peerTrust
) && peerTrust
)
141 cupsdLogMessage(CUPSD_LOG_DEBUG
, "Received %d peer certificates.",
142 (int)SecTrustGetCertificateCount(peerTrust
));
143 CFRelease(peerTrust
);
146 cupsdLogMessage(CUPSD_LOG_DEBUG
, "Received NO peer certificates.");
153 * 'copy_cdsa_certificate()' - Copy a SSL/TLS certificate from the System
157 static CFArrayRef
/* O - Array of certificates */
158 copy_cdsa_certificate(
159 cupsd_client_t
*con
) /* I - Client connection */
161 OSStatus err
; /* Error info */
162 SecKeychainRef keychain
= NULL
;/* Keychain reference */
163 SecIdentitySearchRef search
= NULL
; /* Search reference */
164 SecIdentityRef identity
= NULL
;/* Identity */
165 CFArrayRef certificates
= NULL
;
166 /* Certificate array */
167 SecPolicyRef policy
= NULL
; /* Policy ref */
168 CFStringRef servername
= NULL
;
170 CFMutableDictionaryRef query
= NULL
; /* Query qualifiers */
171 CFArrayRef list
= NULL
; /* Keychain list */
172 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
173 char localname
[1024];/* Local hostname */
174 # endif /* HAVE_DNSSD || HAVE_AVAHI */
177 cupsdLogMessage(CUPSD_LOG_DEBUG
,
178 "copy_cdsa_certificate: Looking for certs for \"%s\".",
181 if ((err
= SecKeychainOpen(ServerCertificate
, &keychain
)))
183 cupsdLogMessage(CUPSD_LOG_ERROR
, "Cannot open keychain \"%s\" - %s (%d)",
184 ServerCertificate
, cssmErrorString(err
), (int)err
);
188 servername
= CFStringCreateWithCString(kCFAllocatorDefault
, con
->servername
,
189 kCFStringEncodingUTF8
);
191 policy
= SecPolicyCreateSSL(1, servername
);
194 CFRelease(servername
);
198 cupsdLogMessage(CUPSD_LOG_ERROR
, "Cannot create ssl policy reference");
202 if (!(query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
203 &kCFTypeDictionaryKeyCallBacks
,
204 &kCFTypeDictionaryValueCallBacks
)))
206 cupsdLogMessage(CUPSD_LOG_ERROR
, "Cannot create query dictionary");
210 list
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&keychain
, 1,
211 &kCFTypeArrayCallBacks
);
213 CFDictionaryAddValue(query
, kSecClass
, kSecClassIdentity
);
214 CFDictionaryAddValue(query
, kSecMatchPolicy
, policy
);
215 CFDictionaryAddValue(query
, kSecReturnRef
, kCFBooleanTrue
);
216 CFDictionaryAddValue(query
, kSecMatchLimit
, kSecMatchLimitOne
);
217 CFDictionaryAddValue(query
, kSecMatchSearchList
, list
);
221 err
= SecItemCopyMatching(query
, (CFTypeRef
*)&identity
);
223 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
224 if (err
&& DNSSDHostName
)
227 * Search for the connection server name failed; try the DNS-SD .local
228 * hostname instead...
231 snprintf(localname
, sizeof(localname
), "%s.local", DNSSDHostName
);
233 cupsdLogMessage(CUPSD_LOG_DEBUG
,
234 "copy_cdsa_certificate: Looking for certs for \"%s\".",
237 servername
= CFStringCreateWithCString(kCFAllocatorDefault
, localname
,
238 kCFStringEncodingUTF8
);
242 policy
= SecPolicyCreateSSL(1, servername
);
245 CFRelease(servername
);
249 cupsdLogMessage(CUPSD_LOG_ERROR
, "Cannot create ssl policy reference");
253 CFDictionarySetValue(query
, kSecMatchPolicy
, policy
);
255 err
= SecItemCopyMatching(query
, (CFTypeRef
*)&identity
);
257 # endif /* HAVE_DNSSD || HAVE_AVAHI */
261 cupsdLogMessage(CUPSD_LOG_DEBUG
,
262 "Cannot find signing key in keychain \"%s\": %s (%d)",
263 ServerCertificate
, cssmErrorString(err
), (int)err
);
267 if (CFGetTypeID(identity
) != SecIdentityGetTypeID())
269 cupsdLogMessage(CUPSD_LOG_ERROR
, "SecIdentity CFTypeID failure.");
273 if ((certificates
= CFArrayCreate(NULL
, (const void **)&identity
,
274 1, &kCFTypeArrayCallBacks
)) == NULL
)
276 cupsdLogMessage(CUPSD_LOG_ERROR
, "Cannot create certificate array");
294 return (certificates
);
299 * 'make_certificate()' - Make a self-signed SSL/TLS certificate.
302 static int /* O - 1 on success, 0 on failure */
303 make_certificate(cupsd_client_t
*con
) /* I - Client connection */
305 # ifdef HAVE_SECGENERATESELFSIGNEDCERTIFICATE
306 int status
= 0; /* Return status */
307 OSStatus err
; /* Error code (if any) */
308 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
309 char localname
[1024];/* Local hostname */
310 # endif /* HAVE_DNSSD || HAVE_AVAHI */
311 const char *servername
; /* Name of server in cert */
312 CFStringRef cfservername
= NULL
;
313 /* CF string for server name */
314 SecIdentityRef ident
= NULL
; /* Identity */
315 SecKeyRef publicKey
= NULL
,
319 CFMutableDictionaryRef keyParams
= NULL
;
320 /* Key generation parameters */
323 cupsdLogMessage(CUPSD_LOG_INFO
,
324 "Generating SSL server key and certificate.");
326 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
327 if (con
->servername
&& isdigit(con
->servername
[0] & 255) && DNSSDHostName
)
329 snprintf(localname
, sizeof(localname
), "%s.local", DNSSDHostName
);
330 servername
= localname
;
333 # endif /* HAVE_DNSSD || HAVE_AVAHI */
334 servername
= con
->servername
;
336 cfservername
= CFStringCreateWithCString(kCFAllocatorDefault
, servername
,
337 kCFStringEncodingUTF8
);
342 * Create a public/private key pair...
345 keyParams
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
346 &kCFTypeDictionaryKeyCallBacks
,
347 &kCFTypeDictionaryValueCallBacks
);
351 CFDictionaryAddValue(keyParams
, kSecAttrKeyType
, kSecAttrKeyTypeRSA
);
352 CFDictionaryAddValue(keyParams
, kSecAttrKeySizeInBits
, CFSTR("2048"));
353 CFDictionaryAddValue(keyParams
, kSecAttrLabel
,
354 CFSTR("CUPS Self-Signed Certificate"));
356 err
= SecKeyGeneratePair(keyParams
, &publicKey
, &privateKey
);
359 cupsdLogMessage(CUPSD_LOG_DEBUG
, "SecKeyGeneratePair returned %ld.",
365 * Create a self-signed certificate using the public/private key pair...
368 CFIndex usageInt
= kSecKeyUsageAll
;
369 CFNumberRef usage
= CFNumberCreate(alloc
, kCFNumberCFIndexType
, &usageInt
);
370 CFDictionaryRef certParams
= CFDictionaryCreateMutable(kCFAllocatorDefault
,
371 kSecCSRBasicContraintsPathLen
, CFINT(0),
372 kSecSubjectAltName
, cfservername
,
373 kSecCertificateKeyUsage
, usage
,
377 const void *ca_o
[] = { kSecOidOrganization
, CFSTR("") };
378 const void *ca_cn
[] = { kSecOidCommonName
, cfservername
};
379 CFArrayRef ca_o_dn
= CFArrayCreate(kCFAllocatorDefault
, ca_o
, 2, NULL
);
380 CFArrayRef ca_cn_dn
= CFArrayCreate(kCFAllocatorDefault
, ca_cn
, 2, NULL
);
381 const void *ca_dn_array
[2];
383 ca_dn_array
[0] = CFArrayCreate(kCFAllocatorDefault
, (const void **)&ca_o_dn
,
385 ca_dn_array
[1] = CFArrayCreate(kCFAllocatorDefault
, (const void **)&ca_cn_dn
,
388 CFArrayRef subject
= CFArrayCreate(kCFAllocatorDefault
, ca_dn_array
, 2,
390 SecCertificateRef cert
= SecGenerateSelfSignedCertificate(subject
, certParams
,
394 CFRelease(certParams
);
398 cupsdLogMessage(CUPSD_LOG_DEBUG
, "SecGenerateSelfSignedCertificate failed.");
402 ident
= SecIdentityCreate(kCFAllocatorDefault
, cert
, privateKey
);
405 cupsdLogMessage(CUPSD_LOG_INFO
,
406 "Created SSL server certificate file \"%s\".",
410 * Cleanup and return...
416 CFRelease(cfservername
);
419 CFRelease(keyParams
);
428 CFRelease(publicKey
);
431 CFRelease(publicKey
);
434 cupsdLogMessage(CUPSD_LOG_ERROR
,
435 "Unable to create SSL server key and certificate.");
439 # else /* !HAVE_SECGENERATESELFSIGNEDCERTIFICATE */
440 int pid
, /* Process ID of command */
441 status
; /* Status of command */
442 char command
[1024], /* Command */
443 *argv
[4], /* Command-line arguments */
444 *envp
[MAX_ENV
+ 1], /* Environment variables */
445 keychain
[1024], /* Keychain argument */
446 infofile
[1024], /* Type-in information for cert */
447 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
448 localname
[1024], /* Local hostname */
449 # endif /* HAVE_DNSSD || HAVE_AVAHI */
450 *servername
; /* Name of server in cert */
451 cups_file_t
*fp
; /* Seed/info file */
452 int infofd
; /* Info file descriptor */
455 # if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
456 if (con
->servername
&& isdigit(con
->servername
[0] & 255) && DNSSDHostName
)
458 snprintf(localname
, sizeof(localname
), "%s.local", DNSSDHostName
);
459 servername
= localname
;
462 # endif /* HAVE_DNSSD || HAVE_AVAHI */
463 servername
= con
->servername
;
466 * Run the "certtool" command to generate a self-signed certificate...
469 if (!cupsFileFind("certtool", getenv("PATH"), 1, command
, sizeof(command
)))
471 cupsdLogMessage(CUPSD_LOG_ERROR
,
472 "No SSL certificate and certtool command not found.");
477 * Create a file with the certificate information fields...
479 * Note: This assumes that the default questions are asked by the certtool
483 if ((fp
= cupsTempFile2(infofile
, sizeof(infofile
))) == NULL
)
485 cupsdLogMessage(CUPSD_LOG_ERROR
,
486 "Unable to create certificate information file %s - %s",
487 infofile
, strerror(errno
));
492 "%s\n" /* Enter key and certificate label */
493 "r\n" /* Generate RSA key pair */
494 "2048\n" /* Key size in bits */
495 "y\n" /* OK (y = yes) */
496 "b\n" /* Usage (b=signing/encryption) */
497 "s\n" /* Sign with SHA1 */
498 "y\n" /* OK (y = yes) */
499 "%s\n" /* Common name */
500 "\n" /* Country (default) */
501 "\n" /* Organization (default) */
502 "\n" /* Organizational unit (default) */
503 "\n" /* State/Province (default) */
504 "%s\n" /* Email address */
505 "y\n", /* OK (y = yes) */
506 servername
, servername
, ServerAdmin
);
509 cupsdLogMessage(CUPSD_LOG_INFO
,
510 "Generating SSL server key and certificate.");
512 snprintf(keychain
, sizeof(keychain
), "k=%s", ServerCertificate
);
514 argv
[0] = "certtool";
519 cupsdLoadEnv(envp
, MAX_ENV
);
521 infofd
= open(infofile
, O_RDONLY
);
523 if (!cupsdStartProcess(command
, argv
, envp
, infofd
, -1, -1, -1, -1, 1, NULL
,
534 while (waitpid(pid
, &status
, 0) < 0)
541 cupsdFinishProcess(pid
, command
, sizeof(command
), NULL
);
545 if (WIFEXITED(status
))
546 cupsdLogMessage(CUPSD_LOG_ERROR
,
547 "Unable to create SSL server key and certificate - "
548 "the certtool command stopped with status %d.",
549 WEXITSTATUS(status
));
551 cupsdLogMessage(CUPSD_LOG_ERROR
,
552 "Unable to create SSL server key and certificate - "
553 "the certtool command crashed on signal %d.",
558 cupsdLogMessage(CUPSD_LOG_INFO
,
559 "Created SSL server certificate file \"%s\".",
564 # endif /* HAVE_SECGENERATESELFSIGNEDCERTIFICATE */