From 9d1120db0973ea7741b13a6555b20ae61f6d037e Mon Sep 17 00:00:00 2001
From: Daniel Molkentin <danimo@owncloud.com>
Date: Wed, 25 Nov 2015 12:37:27 +0100
Subject: [PATCH] QSslSocket: evaluate CAs in all keychain categories

This will make sure that certs in the domainUser (login),
and domainAdmin (per machine) keychain are being picked up
in systemCaCertificates() in addition to the (usually immutable)
DomainSystem keychain.

Also consider the trust settings on OS X: If a certificate
is either fully trusted or trusted for the purpose of SSL,
it will be accepted.

[ChangeLog][Platform Specific Changes] OS X now accepts trusted
        certificates from the login and system keychains.

(Backport of fe3a84138e266c425f11353f7d8dc28a588af89e)

Task-number: QTBUG-32898
Change-Id: Ia23083d5af74388eeee31ba07239735cbbe64368
Reviewed-by: Markus Goetz (Woboq GmbH) <markus@woboq.com>
---
 src/network/ssl/qsslsocket.cpp            |   4 +
 src/network/ssl/qsslsocket_mac_shared.cpp | 148 ++++++++++++++++++++++++++++++
 src/network/ssl/qsslsocket_openssl.cpp    |  30 +-----
 src/network/ssl/ssl.pri                   |   4 +-
 4 files changed, 158 insertions(+), 28 deletions(-)
 create mode 100644 src/network/ssl/qsslsocket_mac_shared.cpp

diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
index 549906a..7b202b0 100644
--- a/src/network/ssl/qsslsocket.cpp
+++ b/src/network/ssl/qsslsocket.cpp
@@ -1508,6 +1508,10 @@ QList<QSslCertificate> QSslSocket::defaultCaCertificates()
     returned by defaultCaCertificates(). You can replace that database
     with your own with setDefaultCaCertificates().
 
+    \note: On OS X, only certificates that are either trusted for all
+    purposes or trusted for the purpose of SSL in the keychain will be
+    returned.
+
     \sa caCertificates(), defaultCaCertificates(), setDefaultCaCertificates()
 */
 QList<QSslCertificate> QSslSocket::systemCaCertificates()
diff --git a/src/network/ssl/qsslsocket_mac_shared.cpp b/src/network/ssl/qsslsocket_mac_shared.cpp
new file mode 100644
index 0000000..60fea4c
--- /dev/null
+++ b/src/network/ssl/qsslsocket_mac_shared.cpp
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Copyright (C) 2015 ownCloud Inc
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//#define QSSLSOCKET_DEBUG
+//#define QT_DECRYPT_SSL_TRAFFIC
+
+#include "qsslsocket.h"
+
+#ifndef QT_NO_OPENSSL
+#   include "qsslsocket_openssl_p.h"
+#   include "qsslsocket_openssl_symbols_p.h"
+#endif
+
+#include "qsslcertificate_p.h"
+
+#ifdef Q_OS_DARWIN
+#   include <private/qcore_mac_p.h>
+#endif
+
+#include <QtCore/qdebug.h>
+
+#ifdef Q_OS_OSX
+#   include <Security/Security.h>
+#endif
+
+
+QT_BEGIN_NAMESPACE
+
+#ifdef Q_OS_OSX
+namespace {
+
+bool hasTrustedSslServerPolicy(SecPolicyRef policy, CFDictionaryRef props) {
+    QCFType<CFDictionaryRef> policyProps = SecPolicyCopyProperties(policy);
+    // only accept certificates with policies for SSL server validation for now
+    if (CFEqual(CFDictionaryGetValue(policyProps, kSecPolicyOid), kSecPolicyAppleSSL)) {
+        CFBooleanRef policyClient;
+        if (CFDictionaryGetValueIfPresent(policyProps, kSecPolicyClient, reinterpret_cast<const void**>(&policyClient)) &&
+            CFEqual(policyClient, kCFBooleanTrue)) {
+            return false; // no client certs
+        }
+        if (!CFDictionaryContainsKey(props, kSecTrustSettingsResult)) {
+            // as per the docs, no trust settings result implies full trust
+            return true;
+        }
+        CFNumberRef number = static_cast<CFNumberRef>(CFDictionaryGetValue(props, kSecTrustSettingsResult));
+        SecTrustSettingsResult settingsResult;
+        CFNumberGetValue(number, kCFNumberSInt32Type, &settingsResult);
+        switch (settingsResult) {
+        case kSecTrustSettingsResultTrustRoot:
+        case kSecTrustSettingsResultTrustAsRoot:
+            return true;
+        default:
+            return false;
+        }
+    }
+    return false;
+}
+
+bool isCaCertificateTrusted(SecCertificateRef cfCert, int domain)
+{
+    QCFType<CFArrayRef> cfTrustSettings;
+    OSStatus status = SecTrustSettingsCopyTrustSettings(cfCert, domain, &cfTrustSettings);
+    if (status == noErr) {
+        CFIndex size = CFArrayGetCount(cfTrustSettings);
+        // if empty, trust for everything (as per the Security Framework documentation)
+        if (size == 0) {
+            return true;
+        } else {
+            for (CFIndex i = 0; i < size; ++i) {
+                CFDictionaryRef props = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(cfTrustSettings, i));
+                if (CFDictionaryContainsKey(props, kSecTrustSettingsPolicy)) {
+                    if (hasTrustedSslServerPolicy((SecPolicyRef)CFDictionaryGetValue(props, kSecTrustSettingsPolicy), props))
+                        return true;
+                }
+            }
+        }
+    } else {
+        qWarning("Error receiving trust for a CA certificate");
+    }
+    return false;
+}
+
+} // anon namespace
+#endif // Q_OS_OSX
+
+QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates()
+{
+    ensureInitialized();
+
+    QList<QSslCertificate> systemCerts;
+    // SecTrustSettingsCopyCertificates is not defined on iOS.
+#ifdef Q_OS_OSX
+    QCFType<CFArrayRef> cfCerts;
+    // iterate through all enum members, order:
+    // kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin, kSecTrustSettingsDomainSystem
+    for (int dom = kSecTrustSettingsDomainUser; dom <= kSecTrustSettingsDomainSystem; dom++) {
+        OSStatus status = SecTrustSettingsCopyCertificates(dom, &cfCerts);
+        if (status == noErr) {
+            const CFIndex size = CFArrayGetCount(cfCerts);
+            for (CFIndex i = 0; i < size; ++i) {
+                SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i);
+                QCFType<CFDataRef> derData = SecCertificateCopyData(cfCert);
+                if (::isCaCertificateTrusted(cfCert, dom)) {
+                    if (derData == NULL) {
+                        qWarning("Error retrieving a CA certificate from the system store");
+                    } else {
+                        systemCerts << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der);
+                    }
+                }
+            }
+        }
+    }
+#endif
+    return systemCerts;
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
index 415f147..7a3cb42 100644
--- a/src/network/ssl/qsslsocket_openssl.cpp
+++ b/src/network/ssl/qsslsocket_openssl.cpp
@@ -76,14 +76,6 @@
 
 #include <string.h>
 
-#ifdef Q_OS_DARWIN
-#  include <private/qcore_mac_p.h>
-#endif
-
-#ifdef Q_OS_OSX
-#  include <Security/Security.h>
-#endif
-
 QT_BEGIN_NAMESPACE
 
 #if defined(Q_OS_WIN)
@@ -672,6 +664,7 @@ void QSslSocketPrivate::resetDefaultEllipticCurves()
     setDefaultSupportedEllipticCurves(curves);
 }
 
+#ifndef Q_OS_DARWIN // Apple implementation in qsslsocket_mac_shared.cpp
 QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates()
 {
     ensureInitialized();
@@ -680,25 +673,7 @@ QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates()
     timer.start();
 #endif
     QList<QSslCertificate> systemCerts;
-    // note: also check implementation in openssl_mac.cpp
-#if defined(Q_OS_OSX)
-    // SecTrustSettingsCopyCertificates is not defined on iOS.
-    QCFType<CFArrayRef> cfCerts;
-
-    OSStatus status = SecTrustSettingsCopyCertificates(kSecTrustSettingsDomainSystem, &cfCerts);
-    if (status == noErr ) {
-        const CFIndex size = CFArrayGetCount(cfCerts);
-        for (CFIndex i = 0; i < size; ++i) {
-            SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i);
-            QCFType<CFDataRef> derData = SecCertificateCopyData(cfCert);
-            if (derData == NULL) {
-                qWarning("error retrieving a CA certificate from the system store");
-            } else {
-                systemCerts << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der);
-            }
-        }
-    }
-#elif defined(Q_OS_WIN)
+#if defined(Q_OS_WIN)
     if (ptrCertOpenSystemStoreW && ptrCertFindCertificateInStore && ptrCertCloseStore) {
         HCERTSTORE hSystemStore;
 #if defined(Q_OS_WINCE)
@@ -775,6 +750,7 @@ QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates()
 
     return systemCerts;
 }
+#endif // Q_OS_DARWIN
 
 void QSslSocketBackendPrivate::startClientEncryption()
 {
diff --git a/src/network/ssl/ssl.pri b/src/network/ssl/ssl.pri
index 29c47cd..8eb605b 100644
--- a/src/network/ssl/ssl.pri
+++ b/src/network/ssl/ssl.pri
@@ -62,7 +62,9 @@ contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) {
                ssl/qsslsocket_openssl.cpp \
                ssl/qsslsocket_openssl_symbols.cpp
 
-android:!android-no-sdk: SOURCES += ssl/qsslsocket_openssl_android.cpp
+    darwin:SOURCES += ssl/qsslsocket_mac_shared.cpp
+
+    android:!android-no-sdk: SOURCES += ssl/qsslsocket_openssl_android.cpp
 
     # Add optional SSL libs
     # Static linking of OpenSSL with msvc:
