]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - cups/tls-darwin.c
Clean up some build issues on certain platforms.
[thirdparty/cups.git] / cups / tls-darwin.c
index c7b7363ac33d13b287909e4d7de3b33da0f685bf..cec08f53f25115ca73fc2eae9e456d93972ea798 100644 (file)
@@ -1,16 +1,10 @@
 /*
- * TLS support code for CUPS on OS X.
+ * TLS support code for CUPS on macOS.
  *
- * Copyright 2007-2016 by Apple Inc.
- * Copyright 1997-2007 by Easy Software Products, all rights reserved.
+ * Copyright © 2007-2018 by Apple Inc.
+ * Copyright © 1997-2007 by Easy Software Products, all rights reserved.
  *
- * These coded instructions, statements, and computer programs are the
- * property of Apple Inc. and are protected by Federal copyright
- * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
- * which should have been included with this file.  If this file is
- * file is missing or damaged, see the license at "http://www.cups.org/".
- *
- * This file is subject to the Apple OS-Developed Software exception.
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
  */
 
 /**** This file is included from tls.c ****/
 extern char **environ;
 
 
+#ifndef _SECURITY_VERSION_GREATER_THAN_57610_
+typedef CF_OPTIONS(uint32_t, SecKeyUsage) {
+    kSecKeyUsageAll              = 0x7FFFFFFF
+};
+#endif /* !_SECURITY_VERSION_GREATER_THAN_57610_ */
+extern const void * kSecCSRChallengePassword;
+extern const void * kSecSubjectAltName;
+extern const void * kSecCertificateKeyUsage;
+extern const void * kSecCSRBasicContraintsPathLen;
+extern const void * kSecCertificateExtensions;
+extern const void * kSecCertificateExtensionsEncoded;
+extern const void * kSecOidCommonName;
+extern const void * kSecOidCountryName;
+extern const void * kSecOidStateProvinceName;
+extern const void * kSecOidLocalityName;
+extern const void * kSecOidOrganization;
+extern const void * kSecOidOrganizationalUnit;
+extern bool SecCertificateIsValid(SecCertificateRef certificate, CFAbsoluteTime verifyTime);
+extern CFAbsoluteTime SecCertificateNotValidAfter(SecCertificateRef certificate);
+extern SecCertificateRef SecGenerateSelfSignedCertificate(CFArrayRef subject, CFDictionaryRef parameters, SecKeyRef publicKey, SecKeyRef privateKey);
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator, SecCertificateRef certificate, SecKeyRef privateKey);
+
+
+/*
+ * Constants, very secure stuff...
+ */
+
+#define _CUPS_CDSA_PASSWORD    "42"    /* CUPS keychain password */
+#define _CUPS_CDSA_PASSLEN     2       /* Length of keychain password */
+
+
 /*
  * Local globals...
  */
 
-#ifdef HAVE_SECKEYCHAINOPEN
 static int             tls_auto_create = 0;
                                        /* Auto-create self-signed certs? */
 static char            *tls_common_name = NULL;
                                        /* Default common name */
+#if TARGET_OS_OSX
+static int             tls_cups_keychain = 0;
+                                       /* Opened the CUPS keychain? */
 static SecKeychainRef  tls_keychain = NULL;
                                        /* Server cert keychain */
+#else
+static SecIdentityRef  tls_selfsigned = NULL;
+                                       /* Temporary self-signed cert */
+#endif /* TARGET_OS_OSX */
 static char            *tls_keypath = NULL;
                                        /* Server cert keychain path */
 static _cups_mutex_t   tls_mutex = _CUPS_MUTEX_INITIALIZER;
                                        /* Mutex for keychain/certs */
-#endif /* HAVE_SECKEYCHAINOPEN */
-static int             tls_options = -1;/* Options for TLS connections */
+static int             tls_options = -1,/* Options for TLS connections */
+                       tls_min_version = _HTTP_TLS_1_0,
+                       tls_max_version = _HTTP_TLS_MAX;
 
 
 /*
  * Local functions...
  */
 
-#ifdef HAVE_SECKEYCHAINOPEN
 static CFArrayRef      http_cdsa_copy_server(const char *common_name);
-#endif /* HAVE_SECKEYCHAINOPEN */
 static SecCertificateRef http_cdsa_create_credential(http_credential_t *credential);
+#if TARGET_OS_OSX
 static const char      *http_cdsa_default_path(char *buffer, size_t bufsize);
+static SecKeychainRef  http_cdsa_open_keychain(const char *path, char *filename, size_t filesize);
+static SecKeychainRef  http_cdsa_open_system_keychain(void);
+#endif /* TARGET_OS_OSX */
 static OSStatus                http_cdsa_read(SSLConnectionRef connection, void *data, size_t *dataLength);
 static int             http_cdsa_set_credentials(http_t *http);
 static OSStatus                http_cdsa_write(SSLConnectionRef connection, const void *data, size_t *dataLength);
@@ -71,115 +105,7 @@ cupsMakeServerCredentials(
     const char **alt_names,            /* I - Subject Alternate Names */
     time_t     expiration_date)                /* I - Expiration date */
 {
-#if defined(HAVE_SECGENERATESELFSIGNEDCERTIFICATE) && defined(HAVE_SECKEYCHAINOPEN)
-  char                 filename[1024]; /* Default keychain path */
-  int                  status = 0;     /* Return status */
-  OSStatus             err;            /* Error code (if any) */
-  CFStringRef          cfcommon_name = NULL;
-                                       /* CF string for server name */
-  SecIdentityRef       ident = NULL;   /* Identity */
-  SecKeyRef            publicKey = NULL,
-                                       /* Public key */
-                       privateKey = NULL;
-                                       /* Private key */
-  SecCertificateRef    cert = NULL;    /* Self-signed certificate */
-  CFMutableDictionaryRef keyParams = NULL;
-                                       /* Key generation parameters */
-
-
-  DEBUG_printf(("cupsMakeServerCredentials(path=\"%s\", common_name=\"%s\", num_alt_names=%d, alt_names=%p, expiration_date=%d)", path, common_name, num_alt_names, alt_names, (int)expiration_date));
-
-  (void)num_alt_names;
-  (void)alt_names;
-  (void)expiration_date;
-
-  if (!path)
-    path = http_cdsa_default_path(filename, sizeof(filename));
-
-  cfcommon_name = CFStringCreateWithCString(kCFAllocatorDefault, common_name, kCFStringEncodingUTF8);
-  if (!cfcommon_name)
-    goto cleanup;
-
- /*
-  * Create a public/private key pair...
-  */
-
-  keyParams = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
-  if (!keyParams)
-    goto cleanup;
-
-  CFDictionaryAddValue(keyParams, kSecAttrKeyType, kSecAttrKeyTypeRSA);
-  CFDictionaryAddValue(keyParams, kSecAttrKeySizeInBits, CFSTR("2048"));
-  CFDictionaryAddValue(keyParams, kSecAttrLabel, CFSTR("CUPS Self-Signed Certificate"));
-
-  err = SecKeyGeneratePair(keyParams, &publicKey, &privateKey);
-  if (err != noErr)
-    goto cleanup;
-
- /*
-  * Create a self-signed certificate using the public/private key pair...
-  */
-
-  CFIndex      usageInt = kSecKeyUsageAll;
-  CFNumberRef  usage = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &usageInt);
-  CFIndex      lenInt = 0;
-  CFNumberRef  len = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &lenInt);
-  CFMutableDictionaryRef certParams = CFDictionaryCreateMutable(kCFAllocatorDefault, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
-  CFDictionaryAddValue(certParams, kSecCSRBasicContraintsPathLen, len);
-  CFDictionaryAddValue(certParams, kSecSubjectAltName, cfcommon_name);
-  CFDictionaryAddValue(certParams, kSecCertificateKeyUsage, usage);
-  CFRelease(usage);
-  CFRelease(len);
-
-  const void   *ca_o[] = { kSecOidOrganization, CFSTR("") };
-  const void   *ca_cn[] = { kSecOidCommonName, cfcommon_name };
-  CFArrayRef   ca_o_dn = CFArrayCreate(kCFAllocatorDefault, ca_o, 2, NULL);
-  CFArrayRef   ca_cn_dn = CFArrayCreate(kCFAllocatorDefault, ca_cn, 2, NULL);
-  const void   *ca_dn_array[2];
-
-  ca_dn_array[0] = CFArrayCreate(kCFAllocatorDefault, (const void **)&ca_o_dn, 1, NULL);
-  ca_dn_array[1] = CFArrayCreate(kCFAllocatorDefault, (const void **)&ca_cn_dn, 1, NULL);
-
-  CFArrayRef   subject = CFArrayCreate(kCFAllocatorDefault, ca_dn_array, 2, NULL);
-  cert = SecGenerateSelfSignedCertificate(subject, certParams, publicKey, privateKey);
-  CFRelease(subject);
-  CFRelease(certParams);
-
-  if (!cert)
-    goto cleanup;
-
-  ident = SecIdentityCreate(kCFAllocatorDefault, cert, privateKey);
-
-  if (ident)
-    status = 1;
-
-  /*
-   * Cleanup and return...
-   */
-
-cleanup:
-
-  if (cfcommon_name)
-    CFRelease(cfcommon_name);
-
-  if (keyParams)
-    CFRelease(keyParams);
-
-  if (ident)
-    CFRelease(ident);
-
-  if (cert)
-    CFRelease(cert);
-
-  if (publicKey)
-    CFRelease(publicKey);
-
-  if (privateKey)
-    CFRelease(publicKey);
-
-  return (status);
-
-#else /* !(HAVE_SECGENERATESELFSIGNEDCERTIFICATE && HAVE_SECKEYCHAINOPEN) */
+#if TARGET_OS_OSX
   int          pid,                    /* Process ID of command */
                status,                 /* Status of command */
                i;                      /* Looping var */
@@ -278,7 +204,166 @@ cleanup:
     }
 
   return (!status);
-#endif /* HAVE_SECGENERATESELFSIGNEDCERTIFICATE && HAVE_SECKEYCHAINOPEN */
+
+#else
+  int                  status = 0;     /* Return status */
+  OSStatus             err;            /* Error code (if any) */
+  CFStringRef          cfcommon_name = NULL;
+                                       /* CF string for server name */
+  SecIdentityRef       ident = NULL;   /* Identity */
+  SecKeyRef            publicKey = NULL,
+                                       /* Public key */
+                       privateKey = NULL;
+                                       /* Private key */
+  SecCertificateRef    cert = NULL;    /* Self-signed certificate */
+  CFMutableDictionaryRef keyParams = NULL;
+                                       /* Key generation parameters */
+
+
+  DEBUG_printf(("cupsMakeServerCredentials(path=\"%s\", common_name=\"%s\", num_alt_names=%d, alt_names=%p, expiration_date=%d)", path, common_name, num_alt_names, alt_names, (int)expiration_date));
+
+  (void)path;
+  (void)num_alt_names;
+  (void)alt_names;
+  (void)expiration_date;
+
+  if (path)
+  {
+    DEBUG_puts("1cupsMakeServerCredentials: No keychain support compiled in, returning 0.");
+    return (0);
+  }
+
+  if (tls_selfsigned)
+  {
+    DEBUG_puts("1cupsMakeServerCredentials: Using existing self-signed cert.");
+    return (1);
+  }
+
+  cfcommon_name = CFStringCreateWithCString(kCFAllocatorDefault, common_name, kCFStringEncodingUTF8);
+  if (!cfcommon_name)
+  {
+    DEBUG_puts("1cupsMakeServerCredentials: Unable to create CF string of common name.");
+    goto cleanup;
+  }
+
+ /*
+  * Create a public/private key pair...
+  */
+
+  keyParams = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+  if (!keyParams)
+  {
+    DEBUG_puts("1cupsMakeServerCredentials: Unable to create key parameters dictionary.");
+    goto cleanup;
+  }
+
+  CFDictionaryAddValue(keyParams, kSecAttrKeyType, kSecAttrKeyTypeRSA);
+  CFDictionaryAddValue(keyParams, kSecAttrKeySizeInBits, CFSTR("2048"));
+  CFDictionaryAddValue(keyParams, kSecAttrLabel, cfcommon_name);
+
+  err = SecKeyGeneratePair(keyParams, &publicKey, &privateKey);
+  if (err != noErr)
+  {
+    DEBUG_printf(("1cupsMakeServerCredentials: Unable to generate key pair: %d.", (int)err));
+    goto cleanup;
+  }
+
+ /*
+  * Create a self-signed certificate using the public/private key pair...
+  */
+
+  CFIndex      usageInt = kSecKeyUsageAll;
+  CFNumberRef  usage = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &usageInt);
+  CFIndex      lenInt = 0;
+  CFNumberRef  len = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &lenInt);
+  CFTypeRef certKeys[] = { kSecCSRBasicContraintsPathLen, kSecSubjectAltName, kSecCertificateKeyUsage };
+  CFTypeRef certValues[] = { len, cfcommon_name, usage };
+  CFDictionaryRef certParams = CFDictionaryCreate(kCFAllocatorDefault, certKeys, certValues, sizeof(certKeys) / sizeof(certKeys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+  CFRelease(usage);
+  CFRelease(len);
+
+  const void   *ca_o[] = { kSecOidOrganization, CFSTR("") };
+  const void   *ca_cn[] = { kSecOidCommonName, cfcommon_name };
+  CFArrayRef   ca_o_dn = CFArrayCreate(kCFAllocatorDefault, ca_o, 2, NULL);
+  CFArrayRef   ca_cn_dn = CFArrayCreate(kCFAllocatorDefault, ca_cn, 2, NULL);
+  const void   *ca_dn_array[2];
+
+  ca_dn_array[0] = CFArrayCreate(kCFAllocatorDefault, (const void **)&ca_o_dn, 1, NULL);
+  ca_dn_array[1] = CFArrayCreate(kCFAllocatorDefault, (const void **)&ca_cn_dn, 1, NULL);
+
+  CFArrayRef   subject = CFArrayCreate(kCFAllocatorDefault, ca_dn_array, 2, NULL);
+
+  cert = SecGenerateSelfSignedCertificate(subject, certParams, publicKey, privateKey);
+
+  CFRelease(subject);
+  CFRelease(certParams);
+
+  if (!cert)
+  {
+    DEBUG_puts("1cupsMakeServerCredentials: Unable to create self-signed certificate.");
+    goto cleanup;
+  }
+
+  ident = SecIdentityCreate(kCFAllocatorDefault, cert, privateKey);
+
+  if (ident)
+  {
+    _cupsMutexLock(&tls_mutex);
+
+    if (tls_selfsigned)
+      CFRelease(ident);
+    else
+      tls_selfsigned = ident;
+
+    _cupsMutexLock(&tls_mutex);
+
+#  if 0 /* Someday perhaps SecItemCopyMatching will work for identities, at which point  */
+    CFTypeRef itemKeys[] = { kSecClass, kSecAttrLabel, kSecValueRef };
+    CFTypeRef itemValues[] = { kSecClassIdentity, cfcommon_name, ident };
+    CFDictionaryRef itemAttrs = CFDictionaryCreate(kCFAllocatorDefault, itemKeys, itemValues, sizeof(itemKeys) / sizeof(itemKeys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+
+    err = SecItemAdd(itemAttrs, NULL);
+    /* SecItemAdd consumes itemAttrs... */
+
+    CFRelease(ident);
+
+    if (err != noErr)
+    {
+      DEBUG_printf(("1cupsMakeServerCredentials: Unable to add identity to keychain: %d.", (int)err));
+      goto cleanup;
+    }
+#  endif /* 0 */
+
+    status = 1;
+  }
+  else
+    DEBUG_puts("1cupsMakeServerCredentials: Unable to create identity from cert and keys.");
+
+  /*
+   * Cleanup and return...
+   */
+
+cleanup:
+
+  if (cfcommon_name)
+    CFRelease(cfcommon_name);
+
+  if (keyParams)
+    CFRelease(keyParams);
+
+  if (cert)
+    CFRelease(cert);
+
+  if (publicKey)
+    CFRelease(publicKey);
+
+  if (privateKey)
+    CFRelease(privateKey);
+
+  DEBUG_printf(("1cupsMakeServerCredentials: Returning %d.", status));
+
+  return (status);
+#endif /* TARGET_OS_OSX */
 }
 
 
@@ -288,7 +373,7 @@ cleanup:
  * Note: The server credentials are used by all threads in the running process.
  * This function is threadsafe.
  *
- * @since CUPS 2.0/OS X 10.10@
+ * @since CUPS 2.0/macOS 10.10@
  */
 
 int                                    /* O - 1 on success, 0 on failure */
@@ -299,19 +384,13 @@ cupsSetServerCredentials(
 {
   DEBUG_printf(("cupsSetServerCredentials(path=\"%s\", common_name=\"%s\", auto_create=%d)", path, common_name, auto_create));
 
-#ifdef HAVE_SECKEYCHAINOPEN
-  char                 filename[1024]; /* Filename for keychain */
-  SecKeychainRef       keychain = NULL;/* Temporary keychain */
-  OSStatus             status;         /* Status code */
-
-
-  if (!path)
-    path = http_cdsa_default_path(filename, sizeof(filename));
+#if TARGET_OS_OSX
+  char         filename[1024];         /* Keychain filename */
+  SecKeychainRef keychain = http_cdsa_open_keychain(path, filename, sizeof(filename));
 
-  if ((status = SecKeychainOpen(path, &keychain)) != noErr)
+  if (!keychain)
   {
-    /* TODO: Set cups last error string */
-    DEBUG_printf(("1cupsSetServerCredentials: Unable to open keychain (%d), returning 0.", (int)status));
+    DEBUG_puts("1cupsSetServerCredentials: Unable to open keychain.");
     return (0);
   }
 
@@ -335,7 +414,7 @@ cupsSetServerCredentials(
   */
 
   tls_keychain    = keychain;
-  tls_keypath     = _cupsStrAlloc(path);
+  tls_keypath     = _cupsStrAlloc(filename);
   tls_auto_create = auto_create;
   tls_common_name = _cupsStrAlloc(common_name);
 
@@ -345,9 +424,17 @@ cupsSetServerCredentials(
   return (1);
 
 #else
-  DEBUG_puts("1cupsSetServerCredentials: No keychain support compiled in, returning 0.");
-  return (0);
-#endif /* HAVE_SECKEYCHAINOPEN */
+  if (path)
+  {
+    DEBUG_puts("1cupsSetServerCredentials: No keychain support compiled in, returning 0.");
+    return (0);
+  }
+
+  tls_auto_create = auto_create;
+  tls_common_name = _cupsStrAlloc(common_name);
+
+  return (1);
+#endif /* TARGET_OS_OSX */
 }
 
 
@@ -355,7 +442,7 @@ cupsSetServerCredentials(
  * 'httpCopyCredentials()' - Copy the credentials associated with the peer in
  *                           an encrypted connection.
  *
- * @since CUPS 1.5/OS X 10.7@
+ * @since CUPS 1.5/macOS 10.7@
  */
 
 int                                    /* O - Status of call (0 = success) */
@@ -458,7 +545,7 @@ _httpCreateCredentials(
 /*
  * 'httpCredentialsAreValidForName()' - Return whether the credentials are valid for the given name.
  *
- * @since CUPS 2.0/OS X 10.10@
+ * @since CUPS 2.0/macOS 10.10@
  */
 
 int                                    /* O - 1 if valid, 0 otherwise */
@@ -521,7 +608,7 @@ httpCredentialsAreValidForName(
 /*
  * 'httpCredentialsGetTrust()' - Return the trust of credentials.
  *
- * @since CUPS 2.0/OS X 10.10@
+ * @since CUPS 2.0/macOS 10.10@
  */
 
 http_trust_t                           /* O - Level of trust */
@@ -538,10 +625,16 @@ httpCredentialsGetTrust(
 
 
   if (!common_name)
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No common name specified."), 1);
     return (HTTP_TRUST_UNKNOWN);
+  }
 
   if ((secCert = http_cdsa_create_credential((http_credential_t *)cupsArrayFirst(credentials))) == NULL)
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create credentials from array."), 1);
     return (HTTP_TRUST_UNKNOWN);
+  }
 
   if (cg->any_root < 0)
     _cupsSetDefaults();
@@ -567,37 +660,110 @@ httpCredentialsGetTrust(
       * credentials and allow if the new ones have a later expiration...
       */
 
-      if (httpCredentialsGetExpiration(credentials) <= httpCredentialsGetExpiration(tcreds) ||
-          !httpCredentialsAreValidForName(credentials, common_name))
+      if (!cg->trust_first)
+      {
+       /*
+        * Do not trust certificates on first use...
+       */
+
+        _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Trust on first use is disabled."), 1);
+
+        trust = HTTP_TRUST_INVALID;
+      }
+      else if (httpCredentialsGetExpiration(credentials) <= httpCredentialsGetExpiration(tcreds))
+      {
+       /*
+        * The new credentials are not newly issued...
+       */
+
+        _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("New credentials are older than stored credentials."), 1);
+
+        trust = HTTP_TRUST_INVALID;
+      }
+      else if (!httpCredentialsAreValidForName(credentials, common_name))
+      {
+       /*
+        * The common name does not match the issued certificate...
+       */
+
+        _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("New credentials are not valid for name."), 1);
+
+        trust = HTTP_TRUST_INVALID;
+      }
+      else if (httpCredentialsGetExpiration(tcreds) < time(NULL))
+      {
+       /*
+        * Save the renewed credentials...
+       */
+
+       trust = HTTP_TRUST_RENEWED;
+
+        httpSaveCredentials(NULL, credentials, common_name);
+      }
+    }
+
+    httpFreeCredentials(tcreds);
+  }
+  else if (cg->validate_certs && !httpCredentialsAreValidForName(credentials, common_name))
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No stored credentials, not valid for name."), 1);
+    trust = HTTP_TRUST_INVALID;
+  }
+  else if (!cg->trust_first)
+  {
+   /*
+    * See if we have a site CA certificate we can compare...
+    */
+
+    if (!httpLoadCredentials(NULL, &tcreds, "site"))
+    {
+      if (cupsArrayCount(credentials) != (cupsArrayCount(tcreds) + 1))
       {
        /*
-        * Either the new credentials are not newly issued, or the common name
-       * does not match the issued certificate...
+        * Certificate isn't directly generated from the CA cert...
        */
 
         trust = HTTP_TRUST_INVALID;
       }
-      else if (httpCredentialsGetExpiration(tcreds) < time(NULL))
+      else
       {
        /*
-        * Save the renewed credentials...
+        * Do a tail comparison of the two certificates...
        */
 
-       trust = HTTP_TRUST_RENEWED;
+        http_credential_t      *a, *b;         /* Certificates */
 
-        httpSaveCredentials(NULL, credentials, common_name);
+        for (a = (http_credential_t *)cupsArrayFirst(tcreds), b = (http_credential_t *)cupsArrayIndex(credentials, 1);
+            a && b;
+            a = (http_credential_t *)cupsArrayNext(tcreds), b = (http_credential_t *)cupsArrayNext(credentials))
+         if (a->datalen != b->datalen || memcmp(a->data, b->data, a->datalen))
+           break;
+
+        if (a || b)
+         trust = HTTP_TRUST_INVALID;
       }
-    }
 
-    httpFreeCredentials(tcreds);
+      if (trust != HTTP_TRUST_OK)
+       _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Credentials do not validate against site CA certificate."), 1);
+    }
+    else
+    {
+      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Trust on first use is disabled."), 1);
+      trust = HTTP_TRUST_INVALID;
+    }
   }
-  else if (cg->validate_certs && !httpCredentialsAreValidForName(credentials, common_name))
-    trust = HTTP_TRUST_INVALID;
 
-  if (!cg->expired_certs && !SecCertificateIsValid(secCert, CFAbsoluteTimeGetCurrent()))
+  if (trust == HTTP_TRUST_OK && !cg->expired_certs && !SecCertificateIsValid(secCert, CFAbsoluteTimeGetCurrent()))
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Credentials have expired."), 1);
     trust = HTTP_TRUST_EXPIRED;
-  else if (!cg->any_root && cupsArrayCount(credentials) == 1)
+  }
+
+  if (trust == HTTP_TRUST_OK && !cg->any_root && cupsArrayCount(credentials) == 1)
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Self-signed credentials are blocked."), 1);
     trust = HTTP_TRUST_INVALID;
+  }
 
   CFRelease(secCert);
 
@@ -608,7 +774,7 @@ httpCredentialsGetTrust(
 /*
  * 'httpCredentialsGetExpiration()' - Return the expiration date of the credentials.
  *
- * @since CUPS 2.0/OS X 10.10@
+ * @since CUPS 2.0/macOS 10.10@
  */
 
 time_t                                 /* O - Expiration date of credentials */
@@ -633,7 +799,7 @@ httpCredentialsGetExpiration(
 /*
  * 'httpCredentialsString()' - Return a string representing the credentials.
  *
- * @since CUPS 2.0/OS X 10.10@
+ * @since CUPS 2.0/macOS 10.10@
  */
 
 size_t                                 /* O - Total size of credentials string */
@@ -657,27 +823,89 @@ httpCredentialsString(
   if ((first = (http_credential_t *)cupsArrayFirst(credentials)) != NULL &&
       (secCert = http_cdsa_create_credential(first)) != NULL)
   {
-    CFStringRef                cf_name;        /* CF common name string */
-    char               name[256];      /* Common name associated with cert */
+   /*
+    * Copy certificate (string) values from the SecCertificateRef and produce
+    * a one-line summary.  The API for accessing certificate values like the
+    * issuer name is, um, "interesting"...
+    */
+
+#  if TARGET_OS_OSX
+    CFDictionaryRef    cf_dict;        /* Dictionary for certificate */
+#  endif /* TARGET_OS_OSX */
+    CFStringRef                cf_string;      /* CF string */
+    char               commonName[256],/* Common name associated with cert */
+                       issuer[256],    /* Issuer name */
+                       sigalg[256];    /* Signature algorithm */
     time_t             expiration;     /* Expiration date of cert */
-    _cups_md5_state_t  md5_state;      /* MD5 state */
     unsigned char      md5_digest[16]; /* MD5 result */
 
-    if ((cf_name = SecCertificateCopySubjectSummary(secCert)) != NULL)
+    if (SecCertificateCopyCommonName(secCert, &cf_string) == noErr)
     {
-      CFStringGetCString(cf_name, name, (CFIndex)sizeof(name), kCFStringEncodingUTF8);
-      CFRelease(cf_name);
+      CFStringGetCString(cf_string, commonName, (CFIndex)sizeof(commonName), kCFStringEncodingUTF8);
+      CFRelease(cf_string);
     }
     else
-      strlcpy(name, "unknown", sizeof(name));
+    {
+      strlcpy(commonName, "unknown", sizeof(commonName));
+    }
+
+    strlcpy(issuer, "unknown", sizeof(issuer));
+    strlcpy(sigalg, "UnknownSignature", sizeof(sigalg));
+
+#  if TARGET_OS_OSX
+    if ((cf_dict = SecCertificateCopyValues(secCert, NULL, NULL)) != NULL)
+    {
+      CFDictionaryRef cf_issuer = CFDictionaryGetValue(cf_dict, kSecOIDX509V1IssuerName);
+      CFDictionaryRef cf_sigalg = CFDictionaryGetValue(cf_dict, kSecOIDX509V1SignatureAlgorithm);
+
+      if (cf_issuer)
+      {
+        CFArrayRef cf_values = CFDictionaryGetValue(cf_issuer, kSecPropertyKeyValue);
+        CFIndex i, count = CFArrayGetCount(cf_values);
+        CFDictionaryRef cf_value;
+
+        for (i = 0; i < count; i ++)
+        {
+          cf_value = CFArrayGetValueAtIndex(cf_values, i);
+
+          if (!CFStringCompare(CFDictionaryGetValue(cf_value, kSecPropertyKeyLabel), kSecOIDOrganizationName, kCFCompareCaseInsensitive))
+            CFStringGetCString(CFDictionaryGetValue(cf_value, kSecPropertyKeyValue), issuer, (CFIndex)sizeof(issuer), kCFStringEncodingUTF8);
+        }
+      }
+
+      if (cf_sigalg)
+      {
+        CFArrayRef cf_values = CFDictionaryGetValue(cf_sigalg, kSecPropertyKeyValue);
+        CFIndex i, count = CFArrayGetCount(cf_values);
+        CFDictionaryRef cf_value;
+
+        for (i = 0; i < count; i ++)
+        {
+          cf_value = CFArrayGetValueAtIndex(cf_values, i);
+
+          if (!CFStringCompare(CFDictionaryGetValue(cf_value, kSecPropertyKeyLabel), CFSTR("Algorithm"), kCFCompareCaseInsensitive))
+          {
+            CFStringRef cf_algorithm = CFDictionaryGetValue(cf_value, kSecPropertyKeyValue);
+
+            if (!CFStringCompare(cf_algorithm, CFSTR("1.2.840.113549.1.1.5"), kCFCompareCaseInsensitive))
+              strlcpy(sigalg, "SHA1WithRSAEncryption", sizeof(sigalg));
+           else if (!CFStringCompare(cf_algorithm, CFSTR("1.2.840.113549.1.1.11"), kCFCompareCaseInsensitive))
+              strlcpy(sigalg, "SHA256WithRSAEncryption", sizeof(sigalg));
+           else if (!CFStringCompare(cf_algorithm, CFSTR("1.2.840.113549.1.1.4"), kCFCompareCaseInsensitive))
+              strlcpy(sigalg, "MD5WithRSAEncryption", sizeof(sigalg));
+         }
+        }
+      }
+
+      CFRelease(cf_dict);
+    }
+#  endif /* TARGET_OS_OSX */
 
     expiration = (time_t)(SecCertificateNotValidAfter(secCert) + kCFAbsoluteTimeIntervalSince1970);
 
-    _cupsMD5Init(&md5_state);
-    _cupsMD5Append(&md5_state, first->data, (int)first->datalen);
-    _cupsMD5Finish(&md5_state, md5_digest);
+    cupsHashData("md5", first->data, first->datalen, md5_digest, sizeof(md5_digest));
 
-    snprintf(buffer, bufsize, "%s / %s / %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", name, httpGetDateString(expiration), md5_digest[0], md5_digest[1], md5_digest[2], md5_digest[3], md5_digest[4], md5_digest[5], md5_digest[6], md5_digest[7], md5_digest[8], md5_digest[9], md5_digest[10], md5_digest[11], md5_digest[12], md5_digest[13], md5_digest[14], md5_digest[15]);
+    snprintf(buffer, bufsize, "%s (issued by %s) / %s / %s / %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", commonName, issuer, httpGetDateString(expiration), sigalg, md5_digest[0], md5_digest[1], md5_digest[2], md5_digest[3], md5_digest[4], md5_digest[5], md5_digest[6], md5_digest[7], md5_digest[8], md5_digest[9], md5_digest[10], md5_digest[11], md5_digest[12], md5_digest[13], md5_digest[14], md5_digest[15]);
 
     CFRelease(secCert);
   }
@@ -715,18 +943,19 @@ httpLoadCredentials(
     cups_array_t **credentials,                /* IO - Credentials */
     const char   *common_name)         /* I  - Common name for credentials */
 {
-#ifdef HAVE_SECKEYCHAINOPEN
   OSStatus             err;            /* Error info */
+#if TARGET_OS_OSX
   char                 filename[1024]; /* Filename for keychain */
-  SecKeychainRef       keychain = NULL;/* Keychain reference */
-  SecIdentitySearchRef search = NULL;  /* Search reference */
+  SecKeychainRef       keychain = NULL,/* Keychain reference */
+                       syschain = NULL;/* System keychain */
+  CFArrayRef           list;           /* Keychain list */
+#endif /* TARGET_OS_OSX */
   SecCertificateRef    cert = NULL;    /* Certificate */
   CFDataRef            data;           /* Certificate data */
   SecPolicyRef         policy = NULL;  /* Policy ref */
   CFStringRef          cfcommon_name = NULL;
                                        /* Server name */
   CFMutableDictionaryRef query = NULL; /* Query qualifiers */
-  CFArrayRef           list = NULL;    /* Keychain list */
 
 
   DEBUG_printf(("httpLoadCredentials(path=\"%s\", credentials=%p, common_name=\"%s\")", path, (void *)credentials, common_name));
@@ -736,12 +965,19 @@ httpLoadCredentials(
 
   *credentials = NULL;
 
-  if (!path)
-    path = http_cdsa_default_path(filename, sizeof(filename));
+#if TARGET_OS_OSX
+  keychain = http_cdsa_open_keychain(path, filename, sizeof(filename));
 
-  if ((err = SecKeychainOpen(path, &keychain)) != noErr)
+  if (!keychain)
     goto cleanup;
 
+  syschain = http_cdsa_open_system_keychain();
+
+#else
+  if (path)
+    return (-1);
+#endif /* TARGET_OS_OSX */
+
   cfcommon_name = CFStringCreateWithCString(kCFAllocatorDefault, common_name, kCFStringEncodingUTF8);
 
   policy = SecPolicyCreateSSL(1, cfcommon_name);
@@ -755,15 +991,23 @@ httpLoadCredentials(
   if (!(query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)))
     goto cleanup;
 
-  list = CFArrayCreate(kCFAllocatorDefault, (const void **)&keychain, 1, &kCFTypeArrayCallBacks);
-
   CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
   CFDictionaryAddValue(query, kSecMatchPolicy, policy);
   CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
   CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitOne);
-  CFDictionaryAddValue(query, kSecMatchSearchList, list);
 
+#if TARGET_OS_OSX
+  if (syschain)
+  {
+    const void *values[2] = { syschain, keychain };
+
+    list = CFArrayCreate(kCFAllocatorDefault, (const void **)values, 2, &kCFTypeArrayCallBacks);
+  }
+  else
+    list = CFArrayCreate(kCFAllocatorDefault, (const void **)&keychain, 1, &kCFTypeArrayCallBacks);
+  CFDictionaryAddValue(query, kSecMatchSearchList, list);
   CFRelease(list);
+#endif /* TARGET_OS_OSX */
 
   err = SecItemCopyMatching(query, (CFTypeRef *)&cert);
 
@@ -784,10 +1028,13 @@ httpLoadCredentials(
 
   cleanup :
 
+#if TARGET_OS_OSX
   if (keychain)
     CFRelease(keychain);
-  if (search)
-    CFRelease(search);
+
+  if (syschain)
+    CFRelease(syschain);
+#endif /* TARGET_OS_OSX */
   if (cert)
     CFRelease(cert);
   if (policy)
@@ -798,14 +1045,6 @@ httpLoadCredentials(
   DEBUG_printf(("1httpLoadCredentials: Returning %d.", *credentials ? 0 : -1));
 
   return (*credentials ? 0 : -1);
-
-#else
-  (void)path;
-  (void)credentials;
-  (void)common_name;
-
-  return (-1);
-#endif /* HAVE_SECKEYCHAINOPEN */
 }
 
 
@@ -821,15 +1060,15 @@ httpSaveCredentials(
     cups_array_t *credentials,         /* I - Credentials */
     const char   *common_name)         /* I - Common name for credentials */
 {
-#ifdef HAVE_SECKEYCHAINOPEN
   int                  ret = -1;       /* Return value */
   OSStatus             err;            /* Error info */
+#if TARGET_OS_OSX
   char                 filename[1024]; /* Filename for keychain */
   SecKeychainRef       keychain = NULL;/* Keychain reference */
-  SecIdentitySearchRef search = NULL;  /* Search reference */
+  CFArrayRef           list;           /* Keychain list */
+#endif /* TARGET_OS_OSX */
   SecCertificateRef    cert = NULL;    /* Certificate */
   CFMutableDictionaryRef attrs = NULL; /* Attributes for add */
-  CFArrayRef           list = NULL;    /* Keychain list */
 
 
   DEBUG_printf(("httpSaveCredentials(path=\"%s\", credentials=%p, common_name=\"%s\")", path, (void *)credentials, common_name));
@@ -848,20 +1087,16 @@ httpSaveCredentials(
     goto cleanup;
   }
 
-  if (!path)
-    path = http_cdsa_default_path(filename, sizeof(filename));
+#if TARGET_OS_OSX
+  keychain = http_cdsa_open_keychain(path, filename, sizeof(filename));
 
-  if ((err = SecKeychainOpen(path, &keychain)) != noErr)
-  {
-    DEBUG_printf(("1httpSaveCredentials: SecKeychainOpen returned %d.", (int)err));
+  if (!keychain)
     goto cleanup;
-  }
 
-  if ((list = CFArrayCreate(kCFAllocatorDefault, (const void **)&keychain, 1, &kCFTypeArrayCallBacks)) == NULL)
-  {
-    DEBUG_puts("1httpSaveCredentials: Unable to create list of keychains.");
-    goto cleanup;
-  }
+#else
+  if (path)
+    return (-1);
+#endif /* TARGET_OS_OSX */
 
   if ((attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == NULL)
   {
@@ -871,7 +1106,16 @@ httpSaveCredentials(
 
   CFDictionaryAddValue(attrs, kSecClass, kSecClassCertificate);
   CFDictionaryAddValue(attrs, kSecValueRef, cert);
+
+#if TARGET_OS_OSX
+  if ((list = CFArrayCreate(kCFAllocatorDefault, (const void **)&keychain, 1, &kCFTypeArrayCallBacks)) == NULL)
+  {
+    DEBUG_puts("1httpSaveCredentials: Unable to create list of keychains.");
+    goto cleanup;
+  }
   CFDictionaryAddValue(attrs, kSecMatchSearchList, list);
+  CFRelease(list);
+#endif /* TARGET_OS_OSX */
 
   /* Note: SecItemAdd consumes "attrs"... */
   err = SecItemAdd(attrs, NULL);
@@ -879,26 +1123,16 @@ httpSaveCredentials(
 
   cleanup :
 
-  if (list)
-    CFRelease(list);
+#if TARGET_OS_OSX
   if (keychain)
     CFRelease(keychain);
-  if (search)
-    CFRelease(search);
+#endif /* TARGET_OS_OSX */
   if (cert)
     CFRelease(cert);
 
   DEBUG_printf(("1httpSaveCredentials: Returning %d.", ret));
 
   return (ret);
-
-#else
-  (void)path;
-  (void)credentials;
-  (void)common_name;
-
-  return (-1);
-#endif /* HAVE_SECKEYCHAINOPEN */
 }
 
 
@@ -986,9 +1220,16 @@ _httpTLSRead(http_t *http,                /* I - HTTP connection */
  */
 
 void
-_httpTLSSetOptions(int options)                /* I - Options */
+_httpTLSSetOptions(int options,                /* I - Options */
+                   int min_version,    /* I - Minimum TLS version */
+                   int max_version)    /* I - Maximum TLS version */
 {
-  tls_options = options;
+  if (!(options & _HTTP_TLS_SET_DEFAULT) || tls_options < 0)
+  {
+    tls_options     = options;
+    tls_min_version = min_version;
+    tls_max_version = max_version;
+  }
 }
 
 
@@ -1005,6 +1246,7 @@ _httpTLSStart(http_t *http)               /* I - HTTP connection */
                                        /* Pointer to library globals */
   OSStatus             error;          /* Error code */
   const char           *message = NULL;/* Error message */
+  char                 msgbuf[1024];   /* Error message buffer */
   cups_array_t         *credentials;   /* Credentials array */
   cups_array_t         *names;         /* CUPS distinguished names */
   CFArrayRef           dn_array;       /* CF distinguished names array */
@@ -1020,14 +1262,11 @@ _httpTLSStart(http_t *http)             /* I - HTTP connection */
   {
     DEBUG_puts("4_httpTLSStart: Setting defaults.");
     _cupsSetDefaults();
-    DEBUG_printf(("4_httpTLSStart: tls_options=%x", tls_options));
+    DEBUG_printf(("4_httpTLSStart: tls_options=%x, tls_min_version=%d, tls_max_version=%d", tls_options, tls_min_version, tls_max_version));
   }
 
-#ifdef HAVE_SECKEYCHAINOPEN
+#if TARGET_OS_OSX
   if (http->mode == _HTTP_MODE_SERVER && !tls_keychain)
-#else
-  if (http->mode == _HTTP_MODE_SERVER)
-#endif /* HAVE_SECKEYCHAINOPEN */
   {
     DEBUG_puts("4_httpTLSStart: cupsSetServerCredentials not called.");
     http->error  = errno = EINVAL;
@@ -1036,6 +1275,7 @@ _httpTLSStart(http_t *http)               /* I - HTTP connection */
 
     return (-1);
   }
+#endif /* TARGET_OS_OSX */
 
   if ((http->tls = SSLCreateContext(kCFAllocatorDefault, http->mode == _HTTP_MODE_CLIENT ? kSSLClientSide : kSSLServerSide, kSSLStreamType)) == NULL)
   {
@@ -1065,20 +1305,28 @@ _httpTLSStart(http_t *http)             /* I - HTTP connection */
 
   if (!error)
   {
-    SSLProtocol minProtocol;
-
-    if (tls_options & _HTTP_TLS_DENY_TLS10)
-      minProtocol = kTLSProtocol11;
-    else if (tls_options & _HTTP_TLS_ALLOW_SSL3)
-      minProtocol = kSSLProtocol3;
-    else
-      minProtocol = kTLSProtocol1;
+    static const SSLProtocol protocols[] =     /* Min/max protocol versions */
+    {
+      kSSLProtocol3,
+      kTLSProtocol1,
+      kTLSProtocol11,
+      kTLSProtocol12,
+      kTLSProtocol13
+    };
+
+    if (tls_min_version < _HTTP_TLS_MAX)
+    {
+      error = SSLSetProtocolVersionMin(http->tls, protocols[tls_min_version]);
+      DEBUG_printf(("4_httpTLSStart: SSLSetProtocolVersionMin(%d), error=%d", protocols[tls_min_version], (int)error));
+    }
 
-    error = SSLSetProtocolVersionMin(http->tls, minProtocol);
-    DEBUG_printf(("4_httpTLSStart: SSLSetProtocolVersionMin(%d), error=%d", minProtocol, (int)error));
+    if (!error && tls_max_version < _HTTP_TLS_MAX)
+    {
+      error = SSLSetProtocolVersionMax(http->tls, protocols[tls_max_version]);
+      DEBUG_printf(("4_httpTLSStart: SSLSetProtocolVersionMax(%d), error=%d", protocols[tls_max_version], (int)error));
+    }
   }
 
-#  if HAVE_SSLSETENABLEDCIPHERS
   if (!error)
   {
     SSLCipherSuite     supported[100]; /* Supported cipher suites */
@@ -1173,7 +1421,6 @@ _httpTLSStart(http_t *http)               /* I - HTTP connection */
           case TLS_DHE_RSA_WITH_AES_256_CBC_SHA :
           case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA :
           case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA :
-//          case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA :
           case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA :
           case TLS_DH_DSS_WITH_AES_128_CBC_SHA256 :
           case TLS_DH_RSA_WITH_AES_128_CBC_SHA256 :
@@ -1186,6 +1433,14 @@ _httpTLSStart(http_t *http)              /* I - HTTP connection */
           case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA :
           case TLS_DHE_PSK_WITH_AES_128_CBC_SHA :
           case TLS_DHE_PSK_WITH_AES_256_CBC_SHA :
+          case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 :
+          case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 :
+             if (tls_options & _HTTP_TLS_DENY_CBC)
+             {
+               DEBUG_printf(("4_httpTLSStart: Excluding CBC cipher suite %d", supported[i]));
+               break;
+             }
+
 //          case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 :
 //          case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 :
           case TLS_DH_RSA_WITH_AES_128_GCM_SHA256 :
@@ -1196,15 +1451,31 @@ _httpTLSStart(http_t *http)             /* I - HTTP connection */
           case TLS_DH_DSS_WITH_AES_256_GCM_SHA384 :
           case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 :
           case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 :
-          case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 :
-          case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 :
               if (tls_options & _HTTP_TLS_ALLOW_DH)
                enabled[num_enabled ++] = supported[i];
              else
                DEBUG_printf(("4_httpTLSStart: Excluding DH/DHE cipher suite %d", supported[i]));
               break;
 
-          /* Anything else we'll assume is secure */
+          case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA :
+          case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 :
+          case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 :
+          case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 :
+          case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 :
+          case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 :
+          case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 :
+          case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 :
+          case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 :
+          case TLS_RSA_WITH_3DES_EDE_CBC_SHA :
+          case TLS_RSA_WITH_AES_128_CBC_SHA :
+          case TLS_RSA_WITH_AES_256_CBC_SHA :
+              if (tls_options & _HTTP_TLS_DENY_CBC)
+             {
+               DEBUG_printf(("4_httpTLSStart: Excluding CBC cipher suite %d", supported[i]));
+               break;
+             }
+
+          /* Anything else we'll assume is "secure" */
           default :
              enabled[num_enabled ++] = supported[i];
              break;
@@ -1215,7 +1486,6 @@ _httpTLSStart(http_t *http)               /* I - HTTP connection */
       error = SSLSetEnabledCiphers(http->tls, enabled, num_enabled);
     }
   }
-#endif /* HAVE_SSLSETENABLEDCIPHERS */
 
   if (!error && http->mode == _HTTP_MODE_CLIENT)
   {
@@ -1243,7 +1513,7 @@ _httpTLSStart(http_t *http)               /* I - HTTP connection */
     * Server: find/create a certificate for TLS...
     */
 
-    if (http->fields[HTTP_FIELD_HOST][0])
+    if (http->fields[HTTP_FIELD_HOST])
     {
      /*
       * Use hostname for TLS upgrade...
@@ -1275,7 +1545,6 @@ _httpTLSStart(http_t *http)               /* I - HTTP connection */
       }
     }
 
-#ifdef HAVE_SECKEYCHAINOPEN
     if (isdigit(hostname[0] & 255) || hostname[0] == '[')
       hostname[0] = '\0';              /* Don't allow numeric addresses */
 
@@ -1300,7 +1569,6 @@ _httpTLSStart(http_t *http)               /* I - HTTP connection */
 
       http->tls_credentials = http_cdsa_copy_server(hostname[0] ? hostname : tls_common_name);
     }
-#endif /* HAVE_SECKEYCHAINOPEN */
 
     if (!http->tls_credentials)
     {
@@ -1353,7 +1621,28 @@ _httpTLSStart(http_t *http)              /* I - HTTP connection */
 
   if (!error)
   {
-    int done = 0;                      /* Are we done yet? */
+    int                        done = 0;       /* Are we done yet? */
+    double             old_timeout;    /* Old timeout value */
+    http_timeout_cb_t  old_cb;         /* Old timeout callback */
+    void               *old_data;      /* Old timeout data */
+
+   /*
+    * Enforce a minimum timeout of 10 seconds for the TLS handshake...
+    */
+
+    old_timeout  = http->timeout_value;
+    old_cb       = http->timeout_cb;
+    old_data     = http->timeout_data;
+
+    if (!old_cb || old_timeout < 10.0)
+    {
+      DEBUG_puts("4_httpTLSStart: Setting timeout to 10 seconds.");
+      httpSetTimeout(http, 10.0, NULL, NULL);
+    }
+
+   /*
+    * Do the TLS handshake...
+    */
 
     while (!error && !done)
     {
@@ -1474,6 +1763,12 @@ _httpTLSStart(http_t *http)              /* I - HTTP connection */
            break;
       }
     }
+
+   /*
+    * Restore the previous timeout settings...
+    */
+
+    httpSetTimeout(http, old_timeout, old_cb, old_data);
   }
 
   if (error)
@@ -1490,11 +1785,13 @@ _httpTLSStart(http_t *http)             /* I - HTTP connection */
     */
 
     if (!message)
-#ifdef HAVE_CSSMERRORSTRING
-      message = cssmErrorString(error);
-#else
-      message = _("Unable to establish a secure connection to host.");
-#endif /* HAVE_CSSMERRORSTRING */
+    {
+      if (!cg->lang_default)
+        cg->lang_default = cupsLangDefault();
+
+      snprintf(msgbuf, sizeof(msgbuf), _cupsLangString(cg->lang_default, _("Unable to establish a secure connection to host (%d).")), error);
+      message = msgbuf;
+    }
 
     _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, message, 1);
 
@@ -1581,7 +1878,6 @@ _httpTLSWrite(http_t     *http,           /* I - HTTP connection */
 }
 
 
-#ifdef HAVE_SECKEYCHAINOPEN
 /*
  * 'http_cdsa_copy_server()' - Find and copy server credentials from the keychain.
  */
@@ -1590,8 +1886,8 @@ static CFArrayRef                 /* O - Array of certificates or NULL */
 http_cdsa_copy_server(
     const char *common_name)           /* I - Server's hostname */
 {
+#if TARGET_OS_OSX
   OSStatus             err;            /* Error info */
-  SecIdentitySearchRef search = NULL;  /* Search reference */
   SecIdentityRef       identity = NULL;/* Identity */
   CFArrayRef           certificates = NULL;
                                        /* Certificate array */
@@ -1600,61 +1896,102 @@ http_cdsa_copy_server(
                                        /* Server name */
   CFMutableDictionaryRef query = NULL; /* Query qualifiers */
   CFArrayRef           list = NULL;    /* Keychain list */
+  SecKeychainRef       syschain = NULL;/* System keychain */
+  SecKeychainStatus    status = 0;     /* Keychain status */
 
 
+  DEBUG_printf(("3http_cdsa_copy_server(common_name=\"%s\")", common_name));
+
   cfcommon_name = CFStringCreateWithCString(kCFAllocatorDefault, common_name, kCFStringEncodingUTF8);
 
   policy = SecPolicyCreateSSL(1, cfcommon_name);
 
-  if (cfcommon_name)
-    CFRelease(cfcommon_name);
-
   if (!policy)
+  {
+    DEBUG_puts("4http_cdsa_copy_server: Unable to create SSL policy.");
     goto cleanup;
+  }
 
   if (!(query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)))
+  {
+    DEBUG_puts("4http_cdsa_copy_server: Unable to create query dictionary.");
     goto cleanup;
+  }
 
   _cupsMutexLock(&tls_mutex);
 
-  list = CFArrayCreate(kCFAllocatorDefault, (const void **)&tls_keychain, 1, &kCFTypeArrayCallBacks);
+  err = SecKeychainGetStatus(tls_keychain, &status);
+
+  if (err == noErr && !(status & kSecUnlockStateStatus) && tls_cups_keychain)
+    SecKeychainUnlock(tls_keychain, _CUPS_CDSA_PASSLEN, _CUPS_CDSA_PASSWORD, TRUE);
 
   CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
   CFDictionaryAddValue(query, kSecMatchPolicy, policy);
   CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
   CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitOne);
-  CFDictionaryAddValue(query, kSecMatchSearchList, list);
 
+  syschain = http_cdsa_open_system_keychain();
+
+  if (syschain)
+  {
+    const void *values[2] = { syschain, tls_keychain };
+
+    list = CFArrayCreate(kCFAllocatorDefault, (const void **)values, 2, &kCFTypeArrayCallBacks);
+  }
+  else
+    list = CFArrayCreate(kCFAllocatorDefault, (const void **)&tls_keychain, 1, &kCFTypeArrayCallBacks);
+
+  CFDictionaryAddValue(query, kSecMatchSearchList, list);
   CFRelease(list);
 
   err = SecItemCopyMatching(query, (CFTypeRef *)&identity);
 
   _cupsMutexUnlock(&tls_mutex);
 
-  if (err)
+  if (err != noErr)
+  {
+    DEBUG_printf(("4http_cdsa_copy_server: SecItemCopyMatching failed with status %d.", (int)err));
     goto cleanup;
+  }
 
   if (CFGetTypeID(identity) != SecIdentityGetTypeID())
+  {
+    DEBUG_puts("4http_cdsa_copy_server: Search returned something that is not an identity.");
     goto cleanup;
+  }
 
   if ((certificates = CFArrayCreate(NULL, (const void **)&identity, 1, &kCFTypeArrayCallBacks)) == NULL)
+  {
+    DEBUG_puts("4http_cdsa_copy_server: Unable to create array of certificates.");
     goto cleanup;
+  }
 
   cleanup :
 
-  if (search)
-    CFRelease(search);
+  if (syschain)
+    CFRelease(syschain);
   if (identity)
     CFRelease(identity);
-
   if (policy)
     CFRelease(policy);
+  if (cfcommon_name)
+    CFRelease(cfcommon_name);
   if (query)
     CFRelease(query);
 
+  DEBUG_printf(("4http_cdsa_copy_server: Returning %p.", (void *)certificates));
+
   return (certificates);
+#else
+
+  (void)common_name;
+
+  if (!tls_selfsigned)
+    return (NULL);
+
+  return (CFArrayCreate(NULL, (const void **)&tls_selfsigned, 1, &kCFTypeArrayCallBacks));
+#endif /* TARGET_OS_OSX */
 }
-#endif /* HAVE_SECKEYCHAINOPEN */
 
 
 /*
@@ -1665,13 +2002,22 @@ static SecCertificateRef                        /* O - Certificate */
 http_cdsa_create_credential(
     http_credential_t *credential)             /* I - Credential */
 {
+  SecCertificateRef    cert;                   /* Certificate */
+  CFDataRef            data;                   /* Data object */
+
+
   if (!credential)
     return (NULL);
 
-  return (SecCertificateCreateWithBytes(kCFAllocatorDefault, credential->data, (CFIndex)credential->datalen));
+  data = CFDataCreate(kCFAllocatorDefault, credential->data, (CFIndex)credential->datalen);
+  cert = SecCertificateCreateWithData(kCFAllocatorDefault, data);
+  CFRelease(data);
+
+  return (cert);
 }
 
 
+#if TARGET_OS_OSX
 /*
  * 'http_cdsa_default_path()' - Get the default keychain path.
  */
@@ -1683,10 +2029,16 @@ http_cdsa_default_path(char   *buffer,  /* I - Path buffer */
   const char *home = getenv("HOME");   /* HOME environment variable */
 
 
+ /*
+  * Determine the default keychain path.  Note that the login and system
+  * keychains are no longer accessible to user applications starting in macOS
+  * 10.11.4 (!), so we need to create our own keychain just for CUPS.
+  */
+
   if (getuid() && home)
-    snprintf(buffer, bufsize, "%s/Library/Keychains/login.keychain", home);
+    snprintf(buffer, bufsize, "%s/.cups/ssl.keychain", home);
   else
-    strlcpy(buffer, "/Library/Keychains/System.keychain", bufsize);
+    strlcpy(buffer, "/etc/cups/ssl.keychain", bufsize);
 
   DEBUG_printf(("1http_cdsa_default_path: Using default path \"%s\".", buffer));
 
@@ -1694,6 +2046,156 @@ http_cdsa_default_path(char   *buffer,  /* I - Path buffer */
 }
 
 
+/*
+ * 'http_cdsa_open_keychain()' - Open (or create) a keychain.
+ */
+
+static SecKeychainRef                  /* O - Keychain or NULL */
+http_cdsa_open_keychain(
+    const char *path,                  /* I - Path to keychain */
+    char       *filename,              /* I - Keychain filename */
+    size_t     filesize)               /* I - Size of filename buffer */
+{
+  SecKeychainRef       keychain = NULL;/* Temporary keychain */
+  OSStatus             err;            /* Error code */
+  Boolean              interaction;    /* Interaction allowed? */
+  SecKeychainStatus    status = 0;     /* Keychain status */
+
+
+ /*
+  * Get the keychain filename...
+  */
+
+  if (!path)
+  {
+    path = http_cdsa_default_path(filename, filesize);
+    tls_cups_keychain = 1;
+  }
+  else
+  {
+    strlcpy(filename, path, filesize);
+    tls_cups_keychain = 0;
+  }
+
+ /*
+  * Save the interaction setting and disable while we open the keychain...
+  */
+
+  SecKeychainGetUserInteractionAllowed(&interaction);
+  SecKeychainSetUserInteractionAllowed(FALSE);
+
+  if (access(path, R_OK) && tls_cups_keychain)
+  {
+   /*
+    * Create a new keychain at the given path...
+    */
+
+    err = SecKeychainCreate(path, _CUPS_CDSA_PASSLEN, _CUPS_CDSA_PASSWORD, FALSE, NULL, &keychain);
+  }
+  else
+  {
+   /*
+    * Open the existing keychain and unlock as needed...
+    */
+
+    err = SecKeychainOpen(path, &keychain);
+
+    if (err == noErr)
+      err = SecKeychainGetStatus(keychain, &status);
+
+    if (err == noErr && !(status & kSecUnlockStateStatus) && tls_cups_keychain)
+      err = SecKeychainUnlock(keychain, _CUPS_CDSA_PASSLEN, _CUPS_CDSA_PASSWORD, TRUE);
+  }
+
+ /*
+  * Restore interaction setting...
+  */
+
+  SecKeychainSetUserInteractionAllowed(interaction);
+
+ /*
+  * Release the keychain if we had any errors...
+  */
+
+  if (err != noErr)
+  {
+    /* TODO: Set cups last error string */
+    DEBUG_printf(("4http_cdsa_open_keychain: Unable to open keychain (%d), returning NULL.", (int)err));
+
+    if (keychain)
+    {
+      CFRelease(keychain);
+      keychain = NULL;
+    }
+  }
+
+ /*
+  * Return the keychain or NULL...
+  */
+
+  return (keychain);
+}
+
+
+/*
+ * 'http_cdsa_open_system_keychain()' - Open the System keychain.
+ */
+
+static SecKeychainRef
+http_cdsa_open_system_keychain(void)
+{
+  SecKeychainRef       keychain = NULL;/* Temporary keychain */
+  OSStatus             err;            /* Error code */
+  Boolean              interaction;    /* Interaction allowed? */
+  SecKeychainStatus    status = 0;     /* Keychain status */
+
+
+ /*
+  * Save the interaction setting and disable while we open the keychain...
+  */
+
+  SecKeychainGetUserInteractionAllowed(&interaction);
+  SecKeychainSetUserInteractionAllowed(TRUE);
+
+  err = SecKeychainOpen("/Library/Keychains/System.keychain", &keychain);
+
+  if (err == noErr)
+    err = SecKeychainGetStatus(keychain, &status);
+
+  if (err == noErr && !(status & kSecUnlockStateStatus))
+    err = errSecInteractionNotAllowed;
+
+ /*
+  * Restore interaction setting...
+  */
+
+  SecKeychainSetUserInteractionAllowed(interaction);
+
+ /*
+  * Release the keychain if we had any errors...
+  */
+
+  if (err != noErr)
+  {
+    /* TODO: Set cups last error string */
+    DEBUG_printf(("4http_cdsa_open_system_keychain: Unable to open keychain (%d), returning NULL.", (int)err));
+
+    if (keychain)
+    {
+      CFRelease(keychain);
+      keychain = NULL;
+    }
+  }
+
+ /*
+  * Return the keychain or NULL...
+  */
+
+  return (keychain);
+}
+#endif /* TARGET_OS_OSX */
+
+
 /*
  * 'http_cdsa_read()' - Read function for the CDSA library.
  */
@@ -1711,7 +2213,7 @@ http_cdsa_read(
 
   http = (http_t *)connection;
 
-  if (!http->blocking)
+  if (!http->blocking || http->timeout_value > 0.0)
   {
    /*
     * Make sure we have data before we read...