static CFArrayRef copy_cdsa_certificate(cupsd_client_t *con);
static int make_certificate(cupsd_client_t *con);
int
cupsdEndTLS(cupsd_client_t *con)
{
while (SSLClose(con->http.tls) == errSSLWouldBlock)
usleep(1000);
SSLDisposeContext(con->http.tls);
con->http.tls = NULL;
if (con->http.tls_credentials)
CFRelease(con->http.tls_credentials);
return (1);
}
int
cupsdStartTLS(cupsd_client_t *con)
{
OSStatus error = 0;
CFArrayRef peerCerts;
cupsdLogMessage(CUPSD_LOG_DEBUG, "[Client %d] Encrypting connection.",
con->http.fd);
con->http.tls_credentials = copy_cdsa_certificate(con);
if (!con->http.tls_credentials)
{
if (make_certificate(con))
con->http.tls_credentials = copy_cdsa_certificate(con);
}
if (!con->http.tls_credentials)
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Could not find signing key in keychain \"%s\"",
ServerCertificate);
error = errSSLBadConfiguration;
}
if (!error)
error = SSLNewContext(true, &con->http.tls);
if (!error)
error = SSLSetIOFuncs(con->http.tls, _httpReadCDSA, _httpWriteCDSA);
if (!error)
error = SSLSetConnection(con->http.tls, HTTP(con));
if (!error)
error = SSLSetAllowsExpiredCerts(con->http.tls, true);
if (!error)
error = SSLSetAllowsAnyRoot(con->http.tls, true);
if (!error)
error = SSLSetCertificate(con->http.tls, con->http.tls_credentials);
if (!error)
{
while ((error = SSLHandshake(con->http.tls)) == errSSLWouldBlock)
usleep(1000);
}
if (error)
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Unable to encrypt connection from %s - %s (%d)",
con->http.hostname, cssmErrorString(error), (int)error);
con->http.error = error;
con->http.status = HTTP_ERROR;
if (con->http.tls)
{
SSLDisposeContext(con->http.tls);
con->http.tls = NULL;
}
if (con->http.tls_credentials)
{
CFRelease(con->http.tls_credentials);
con->http.tls_credentials = NULL;
}
return (0);
}
cupsdLogMessage(CUPSD_LOG_DEBUG, "Connection from %s now encrypted.",
con->http.hostname);
if (!SSLCopyPeerCertificates(con->http.tls, &peerCerts) && peerCerts)
{
cupsdLogMessage(CUPSD_LOG_DEBUG, "Received %d peer certificates!",
(int)CFArrayGetCount(peerCerts));
CFRelease(peerCerts);
}
else
cupsdLogMessage(CUPSD_LOG_DEBUG, "Received NO peer certificates!");
return (1);
}
static CFArrayRef
copy_cdsa_certificate(
cupsd_client_t *con)
{
OSStatus err;
SecKeychainRef keychain = NULL;
SecIdentitySearchRef search = NULL;
SecIdentityRef identity = NULL;
CFArrayRef certificates = NULL;
# if HAVE_SECPOLICYCREATESSL
SecPolicyRef policy = NULL;
CFStringRef servername = NULL;
CFMutableDictionaryRef query = NULL;
CFArrayRef list = NULL;
# if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
char localname[1024];
# endif
# elif defined(HAVE_SECIDENTITYSEARCHCREATEWITHPOLICY)
SecPolicyRef policy = NULL;
SecPolicySearchRef policy_search = NULL;
CSSM_DATA options;
CSSM_APPLE_TP_SSL_OPTIONS
ssl_options;
char localname[1024];
# endif
cupsdLogMessage(CUPSD_LOG_DEBUG,
"copy_cdsa_certificate: Looking for certs for \"%s\"...",
con->servername);
if ((err = SecKeychainOpen(ServerCertificate, &keychain)))
{
cupsdLogMessage(CUPSD_LOG_ERROR, "Cannot open keychain \"%s\" - %s (%d)",
ServerCertificate, cssmErrorString(err), (int)err);
goto cleanup;
}
# if HAVE_SECPOLICYCREATESSL
servername = CFStringCreateWithCString(kCFAllocatorDefault, con->servername,
kCFStringEncodingUTF8);
policy = SecPolicyCreateSSL(1, servername);
if (servername)
CFRelease(servername);
if (!policy)
{
cupsdLogMessage(CUPSD_LOG_ERROR, "Cannot create ssl policy reference");
goto cleanup;
}
if (!(query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks)))
{
cupsdLogMessage(CUPSD_LOG_ERROR, "Cannot create query dictionary");
goto cleanup;
}
list = CFArrayCreate(kCFAllocatorDefault, (const void **)&keychain, 1,
&kCFTypeArrayCallBacks);
CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
CFDictionaryAddValue(query, kSecMatchPolicy, policy);
CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitOne);
CFDictionaryAddValue(query, kSecMatchSearchList, list);
CFRelease(list);
err = SecItemCopyMatching(query, (CFTypeRef *)&identity);
# if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
if (err && DNSSDHostName)
{
snprintf(localname, sizeof(localname), "%s.local", DNSSDHostName);
cupsdLogMessage(CUPSD_LOG_DEBUG,
"copy_cdsa_certificate: Looking for certs for \"%s\"...",
localname);
servername = CFStringCreateWithCString(kCFAllocatorDefault, localname,
kCFStringEncodingUTF8);
CFRelease(policy);
policy = SecPolicyCreateSSL(1, servername);
if (servername)
CFRelease(servername);
if (!policy)
{
cupsdLogMessage(CUPSD_LOG_ERROR, "Cannot create ssl policy reference");
goto cleanup;
}
CFDictionarySetValue(query, kSecMatchPolicy, policy);
err = SecItemCopyMatching(query, (CFTypeRef *)&identity);
}
# endif
if (err)
{
cupsdLogMessage(CUPSD_LOG_DEBUG,
"Cannot find signing key in keychain \"%s\": %s (%d)",
ServerCertificate, cssmErrorString(err), (int)err);
goto cleanup;
}
# elif defined(HAVE_SECIDENTITYSEARCHCREATEWITHPOLICY)
if (SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_SSL,
NULL, &policy_search))
{
cupsdLogMessage(CUPSD_LOG_ERROR, "Cannot create a policy search reference");
goto cleanup;
}
if (SecPolicySearchCopyNext(policy_search, &policy))
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Cannot find a policy to use for searching");
goto cleanup;
}
memset(&ssl_options, 0, sizeof(ssl_options));
ssl_options.Version = CSSM_APPLE_TP_SSL_OPTS_VERSION;
ssl_options.ServerName = con->servername;
ssl_options.ServerNameLen = strlen(con->servername);
options.Data = (uint8 *)&ssl_options;
options.Length = sizeof(ssl_options);
if (SecPolicySetValue(policy, &options))
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Cannot set policy value to use for searching");
goto cleanup;
}
if ((err = SecIdentitySearchCreateWithPolicy(policy, NULL, CSSM_KEYUSE_SIGN,
keychain, FALSE, &search)))
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Cannot create identity search reference: %s (%d)",
cssmErrorString(err), (int)err);
goto cleanup;
}
err = SecIdentitySearchCopyNext(search, &identity);
# if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
if (err && DNSSDHostName)
{
snprintf(localname, sizeof(localname), "%s.local", DNSSDHostName);
ssl_options.ServerName = localname;
ssl_options.ServerNameLen = strlen(localname);
cupsdLogMessage(CUPSD_LOG_DEBUG,
"copy_cdsa_certificate: Looking for certs for \"%s\"...",
localname);
if (SecPolicySetValue(policy, &options))
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Cannot set policy value to use for searching");
goto cleanup;
}
CFRelease(search);
search = NULL;
if ((err = SecIdentitySearchCreateWithPolicy(policy, NULL, CSSM_KEYUSE_SIGN,
keychain, FALSE, &search)))
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Cannot create identity search reference: %s (%d)",
cssmErrorString(err), (int)err);
goto cleanup;
}
err = SecIdentitySearchCopyNext(search, &identity);
}
# endif
if (err)
{
cupsdLogMessage(CUPSD_LOG_DEBUG,
"Cannot find signing key in keychain \"%s\": %s (%d)",
ServerCertificate, cssmErrorString(err), (int)err);
goto cleanup;
}
# else
if ((err = SecIdentitySearchCreate(keychain, CSSM_KEYUSE_SIGN, &search)))
{
cupsdLogMessage(CUPSD_LOG_DEBUG,
"Cannot create identity search reference (%d)", (int)err);
goto cleanup;
}
if ((err = SecIdentitySearchCopyNext(search, &identity)))
{
cupsdLogMessage(CUPSD_LOG_DEBUG,
"Cannot find signing key in keychain \"%s\": %s (%d)",
ServerCertificate, cssmErrorString(err), (int)err);
goto cleanup;
}
# endif
if (CFGetTypeID(identity) != SecIdentityGetTypeID())
{
cupsdLogMessage(CUPSD_LOG_ERROR, "SecIdentity CFTypeID failure!");
goto cleanup;
}
if ((certificates = CFArrayCreate(NULL, (const void **)&identity,
1, &kCFTypeArrayCallBacks)) == NULL)
{
cupsdLogMessage(CUPSD_LOG_ERROR, "Cannot create certificate array");
goto cleanup;
}
cleanup :
if (keychain)
CFRelease(keychain);
if (search)
CFRelease(search);
if (identity)
CFRelease(identity);
# if HAVE_SECPOLICYCREATESSL
if (policy)
CFRelease(policy);
if (query)
CFRelease(query);
# elif defined(HAVE_SECIDENTITYSEARCHCREATEWITHPOLICY)
if (policy)
CFRelease(policy);
if (policy_search)
CFRelease(policy_search);
# endif
return (certificates);
}
static int
make_certificate(cupsd_client_t *con)
{
int pid,
status;
char command[1024],
*argv[4],
*envp[MAX_ENV + 1],
keychain[1024],
infofile[1024],
# if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
localname[1024],
# endif
*servername;
cups_file_t *fp;
int infofd;
# if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
if (con->servername && isdigit(con->servername[0] & 255) && DNSSDHostName)
{
snprintf(localname, sizeof(localname), "%s.local", DNSSDHostName);
servername = localname;
}
else
# endif
servername = con->servername;
if (!cupsFileFind("certtool", getenv("PATH"), 1, command, sizeof(command)))
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"No SSL certificate and certtool command not found!");
return (0);
}
if ((fp = cupsTempFile2(infofile, sizeof(infofile))) == NULL)
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Unable to create certificate information file %s - %s",
infofile, strerror(errno));
return (0);
}
cupsFilePrintf(fp,
"%s\n"
"r\n"
"2048\n"
"y\n"
"b\n"
"s\n"
"y\n"
"%s\n"
"\n"
"\n"
"\n"
"\n"
"%s\n"
"y\n",
servername, servername, ServerAdmin);
cupsFileClose(fp);
cupsdLogMessage(CUPSD_LOG_INFO,
"Generating SSL server key and certificate...");
snprintf(keychain, sizeof(keychain), "k=%s", ServerCertificate);
argv[0] = "certtool";
argv[1] = "c";
argv[2] = keychain;
argv[3] = NULL;
cupsdLoadEnv(envp, MAX_ENV);
infofd = open(infofile, O_RDONLY);
if (!cupsdStartProcess(command, argv, envp, infofd, -1, -1, -1, -1, 1, NULL,
NULL, &pid))
{
close(infofd);
unlink(infofile);
return (0);
}
close(infofd);
unlink(infofile);
while (waitpid(pid, &status, 0) < 0)
if (errno != EINTR)
{
status = 1;
break;
}
cupsdFinishProcess(pid, command, sizeof(command), NULL);
if (status)
{
if (WIFEXITED(status))
cupsdLogMessage(CUPSD_LOG_ERROR,
"Unable to create SSL server key and certificate - "
"the certtool command stopped with status %d!",
WEXITSTATUS(status));
else
cupsdLogMessage(CUPSD_LOG_ERROR,
"Unable to create SSL server key and certificate - "
"the certtool command crashed on signal %d!",
WTERMSIG(status));
}
else
{
cupsdLogMessage(CUPSD_LOG_INFO,
"Created SSL server certificate file \"%s\"...",
ServerCertificate);
}
return (!status);
}