]> git.saurik.com Git - apple/security.git/blobdiff - Security/sec/securityd/SecTrustStoreServer.c
Security-57031.1.35.tar.gz
[apple/security.git] / Security / sec / securityd / SecTrustStoreServer.c
diff --git a/Security/sec/securityd/SecTrustStoreServer.c b/Security/sec/securityd/SecTrustStoreServer.c
new file mode 100644 (file)
index 0000000..fe5be3d
--- /dev/null
@@ -0,0 +1,455 @@
+/*
+ * Copyright (c) 2007-2010,2012-2014 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+/*
+ * SecTrustStoreServer.c - CertificateSource API to a system root certificate store
+ */
+#include "SecTrustStoreServer.h"
+
+#include <Security/SecCertificateInternal.h>
+#include <Security/SecFramework.h>
+#include <errno.h>
+#include <limits.h>
+#include <dispatch/dispatch.h>
+#include <sqlite3.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <CoreFoundation/CFData.h>
+#include <CoreFoundation/CFPropertyList.h>
+#include <CoreFoundation/CFURL.h>
+#include <AssertMacros.h>
+#include <utilities/debugging.h>
+#include "SecBasePriv.h"
+#include <Security/SecInternal.h>
+#include <ipc/securityd_client.h>
+#include <securityd/SecTrustStoreServer.h>
+#include "utilities/sqlutils.h"
+#include "utilities/SecDb.h"
+#include <utilities/SecCFError.h>
+#include "utilities/SecFileLocations.h"
+#include <utilities/SecDispatchRelease.h>
+
+/* uid of the _securityd user. */
+#define SECURTYD_UID 64
+
+static dispatch_once_t kSecTrustStoreUserOnce;
+static SecTrustStoreRef kSecTrustStoreUser = NULL;
+
+static const char copyParentsSQL[] = "SELECT data FROM tsettings WHERE subj=?";
+static const char containsSQL[] = "SELECT tset FROM tsettings WHERE sha1=?";
+static const char insertSQL[] = "INSERT INTO tsettings(sha1,subj,tset,data)VALUES(?,?,?,?)";
+static const char updateSQL[] = "UPDATE tsettings SET tset=? WHERE sha1=?";
+static const char deleteSQL[] = "DELETE FROM tsettings WHERE sha1=?";
+static const char deleteAllSQL[] = "BEGIN EXCLUSIVE TRANSACTION; DELETE from tsettings; COMMIT TRANSACTION; VACUUM;";
+
+#define kSecTrustStoreName CFSTR("TrustStore")
+#define kSecTrustStoreDbExtension CFSTR("sqlite3")
+
+#define kTrustStoreFileName CFSTR("TrustStore.sqlite3")
+
+
+struct __SecTrustStore {
+    dispatch_queue_t queue;
+       sqlite3 *s3h;
+       sqlite3_stmt *copyParents;
+       sqlite3_stmt *contains;
+       bool readOnly;
+};
+
+static int sec_create_path(const char *path)
+{
+       char pathbuf[PATH_MAX];
+       size_t pos, len = strlen(path);
+       if (len == 0 || len > PATH_MAX)
+               return SQLITE_CANTOPEN;
+       memcpy(pathbuf, path, len);
+       for (pos = len-1; pos > 0; --pos)
+       {
+               /* Search backwards for trailing '/'. */
+               if (pathbuf[pos] == '/')
+               {
+                       pathbuf[pos] = '\0';
+                       /* Attempt to create parent directories of the database. */
+                       if (!mkdir(pathbuf, 0777))
+                               break;
+                       else
+                       {
+                               int err = errno;
+                               if (err == EEXIST)
+                                       return 0;
+                               if (err == ENOTDIR)
+                                       return SQLITE_CANTOPEN;
+                               if (err == EROFS)
+                                       return SQLITE_READONLY;
+                               if (err == EACCES)
+                                       return SQLITE_PERM;
+                               if (err == ENOSPC || err == EDQUOT)
+                                       return SQLITE_FULL;
+                               if (err == EIO)
+                                       return SQLITE_IOERR;
+
+                               /* EFAULT || ELOOP | ENAMETOOLONG || something else */
+                               return SQLITE_INTERNAL;
+                       }
+               }
+       }
+       return SQLITE_OK;
+}
+
+static int sec_sqlite3_open(const char *db_name, sqlite3 **s3h,
+       bool create_path)
+{
+       int s3e;
+       s3e = sqlite3_open(db_name, s3h);
+       if (s3e == SQLITE_CANTOPEN && create_path) {
+               /* Make sure the path to db_name exists and is writable, then
+                  try again. */
+               s3e = sec_create_path(db_name);
+               if (!s3e)
+                       s3e = sqlite3_open(db_name, s3h);
+       }
+
+       return s3e;
+}
+
+static SecTrustStoreRef SecTrustStoreCreate(const char *db_name,
+       bool create) {
+       SecTrustStoreRef ts;
+       int s3e;
+
+       require(ts = (SecTrustStoreRef)malloc(sizeof(struct __SecTrustStore)), errOut);
+    ts->queue = dispatch_queue_create("truststore", DISPATCH_QUEUE_SERIAL);
+       require_noerr(s3e = sec_sqlite3_open(db_name, &ts->s3h, create), errOut);
+
+       s3e = sqlite3_prepare(ts->s3h, copyParentsSQL, sizeof(copyParentsSQL),
+               &ts->copyParents, NULL);
+       if (create && s3e == SQLITE_ERROR) {
+               /* sqlite3_prepare returns SQLITE_ERROR if the table we are
+                  compiling this statement for doesn't exist. */
+               char *errmsg = NULL;
+               s3e = sqlite3_exec(ts->s3h,
+                       "CREATE TABLE tsettings("
+                       "sha1 BLOB NOT NULL DEFAULT '',"
+                       "subj BLOB NOT NULL DEFAULT '',"
+                       "tset BLOB,"
+                       "data BLOB,"
+                       "PRIMARY KEY(sha1)"
+                       ");"
+                       "CREATE INDEX isubj ON tsettings(subj);"
+                       , NULL, NULL, &errmsg);
+               if (errmsg) {
+                       secwarning("CREATE TABLE cert: %s", errmsg);
+                       sqlite3_free(errmsg);
+               }
+               require_noerr(s3e, errOut);
+               s3e = sqlite3_prepare(ts->s3h, copyParentsSQL, sizeof(copyParentsSQL),
+                       &ts->copyParents, NULL);
+       }
+       require_noerr(s3e, errOut);
+       require_noerr(s3e = sqlite3_prepare(ts->s3h, containsSQL, sizeof(containsSQL),
+               &ts->contains, NULL), errOut);
+
+       return ts;
+
+errOut:
+       if (ts) {
+               sqlite3_close(ts->s3h);
+        dispatch_release_safe(ts->queue);
+               free(ts);
+       }
+
+       return NULL;
+}
+
+static bool SecExtractFilesystemPathForKeychainFile(CFStringRef file, UInt8 *buffer, CFIndex maxBufLen)
+{
+    bool translated = false;
+    CFURLRef fileURL = SecCopyURLForFileInKeychainDirectory(file);
+    
+    if (fileURL && CFURLGetFileSystemRepresentation(fileURL, false, buffer, maxBufLen))
+        translated = true;
+    CFReleaseSafe(fileURL);
+    
+    return translated;
+}
+
+static void SecTrustStoreInitUser(void) {
+       const char path[MAXPATHLEN];
+    
+    if (SecExtractFilesystemPathForKeychainFile(kTrustStoreFileName, (UInt8*) path, (CFIndex) sizeof(path)))
+    {
+        kSecTrustStoreUser = SecTrustStoreCreate(path, true);
+        if (kSecTrustStoreUser)
+            kSecTrustStoreUser->readOnly = false;
+    }    
+}
+
+/* AUDIT[securityd](done):
+   domainName (ok) is a caller provided string of any length (might be 0), only
+       its cf type has been checked.
+ */
+SecTrustStoreRef SecTrustStoreForDomainName(CFStringRef domainName, CFErrorRef *error) {
+       if (CFEqual(CFSTR("user"), domainName)) {
+               dispatch_once(&kSecTrustStoreUserOnce, ^{ SecTrustStoreInitUser(); });
+               return kSecTrustStoreUser;
+       } else {
+        SecError(errSecParam, error, CFSTR("unknown domain: %@"), domainName);
+               return NULL;
+       }
+}
+
+/* AUDIT[securityd](done):
+   ts (ok) might be NULL.
+   certificate (ok) is a valid SecCertificateRef.
+   trustSettingsDictOrArray (checked by CFPropertyListCreateXMLData) is either
+   NULL, a dictionary or an array, but its contents have not been checked.
+ */
+bool _SecTrustStoreSetTrustSettings(SecTrustStoreRef ts,
+       SecCertificateRef certificate,
+    CFTypeRef tsdoa, CFErrorRef *error) {
+    __block bool ok;
+       require_action_quiet(ts, errOutNotLocked, ok = SecError(errSecParam, error, CFSTR("truststore is NULL")));
+    require_action_quiet(!ts->readOnly, errOutNotLocked, ok = SecError(errSecReadOnly, error, CFSTR("truststore is readOnly")));
+    dispatch_sync(ts->queue, ^{
+        CFTypeRef trustSettingsDictOrArray = tsdoa;
+        sqlite3_stmt *insert = NULL, *update = NULL;
+        CFDataRef xmlData = NULL;
+        CFArrayRef array = NULL;
+
+        CFDataRef subject;
+        require_action_quiet(subject = SecCertificateGetNormalizedSubjectContent(certificate),
+                             errOut, ok = SecError(errSecParam, error, CFSTR("get normalized subject failed")));
+        CFDataRef digest;
+        require_action_quiet(digest = SecCertificateGetSHA1Digest(certificate), errOut, ok = SecError(errSecParam, error, CFSTR("get sha1 digest failed")));
+
+        /* Do some basic checks on the trust settings passed in. */
+        if (trustSettingsDictOrArray == NULL) {
+            require_action_quiet(array = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks), errOut, ok = SecError(errSecAllocate, error, CFSTR("CFArrayCreate failed")));
+            trustSettingsDictOrArray = array;
+        }
+        else if(CFGetTypeID(trustSettingsDictOrArray) == CFDictionaryGetTypeID()) {
+            /* array-ize it */
+            array = CFArrayCreate(NULL, &trustSettingsDictOrArray, 1,
+                                  &kCFTypeArrayCallBacks);
+            trustSettingsDictOrArray = array;
+        }
+        else {
+            require_action_quiet(CFGetTypeID(trustSettingsDictOrArray) == CFArrayGetTypeID(), errOut, ok = SecError(errSecParam, error, CFSTR("trustSettingsDictOrArray neither dict nor array")));
+        }
+
+        require_action_quiet(xmlData = CFPropertyListCreateXMLData(kCFAllocatorDefault,
+                                                                   trustSettingsDictOrArray), errOut, ok = SecError(errSecParam, error, CFSTR("xml encode failed")));
+
+        int s3e = sqlite3_exec(ts->s3h, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL);
+        require_action_quiet(s3e == SQLITE_OK, errOut, ok = SecError(errSecInternal, error, CFSTR("sqlite3 error: %d"), s3e));
+
+        /* Parameter order is sha1,subj,tset,data. */
+        require_noerr_action_quiet(sqlite3_prepare(ts->s3h, insertSQL, sizeof(insertSQL),
+                                                   &insert, NULL), errOutSql, ok = SecError(errSecInternal, error, CFSTR("sqlite3 error: %d"), s3e));
+        require_noerr_action_quiet(sqlite3_bind_blob_wrapper(insert, 1,
+                                                             CFDataGetBytePtr(digest), CFDataGetLength(digest), SQLITE_STATIC),
+                                   errOutSql, ok = SecError(errSecInternal, error, CFSTR("sqlite3 error: %d"), s3e));
+        require_noerr_action_quiet(sqlite3_bind_blob_wrapper(insert, 2,
+                                                             CFDataGetBytePtr(subject), CFDataGetLength(subject),
+                                                             SQLITE_STATIC), errOutSql, ok = SecError(errSecInternal, error, CFSTR("sqlite3 error: %d"), s3e));
+        require_noerr_action_quiet(sqlite3_bind_blob_wrapper(insert, 3,
+                                                             CFDataGetBytePtr(xmlData), CFDataGetLength(xmlData),
+                                                             SQLITE_STATIC), errOutSql, ok = SecError(errSecInternal, error, CFSTR("sqlite3 error: %d"), s3e));
+        require_noerr_action_quiet(sqlite3_bind_blob_wrapper(insert, 4,
+                                                             SecCertificateGetBytePtr(certificate),
+                                                             SecCertificateGetLength(certificate), SQLITE_STATIC), errOutSql, ok = SecError(errSecInternal, error, CFSTR("sqlite3 error: %d"), s3e));
+        s3e = sqlite3_step(insert);
+        if (s3e == SQLITE_DONE) {
+            /* Great the insert worked. */
+            ok = true;
+        } else if (s3e == SQLITE_ERROR) {
+            /* Try update. */
+            require_noerr_action_quiet(s3e = sqlite3_prepare(ts->s3h, updateSQL, sizeof(updateSQL),
+                                                             &update, NULL), errOutSql, ok = SecError(errSecInternal, error, CFSTR("sqlite3 error: %d"), s3e));
+            require_noerr_action_quiet(s3e = sqlite3_bind_blob_wrapper(update, 1,
+                                                                       CFDataGetBytePtr(xmlData), CFDataGetLength(xmlData),
+                                                                       SQLITE_STATIC), errOutSql, ok = SecError(errSecInternal, error, CFSTR("sqlite3 error: %d"), s3e));
+            require_noerr_action_quiet(s3e = sqlite3_bind_blob_wrapper(update, 2,
+                                                                       CFDataGetBytePtr(digest), CFDataGetLength(digest), SQLITE_STATIC),
+                                       errOutSql, ok = SecError(errSecInternal, error, CFSTR("sqlite3 error: %d"), s3e));
+            s3e = sqlite3_step(update);
+            require_action_quiet(s3e == SQLITE_DONE, errOutSql, ok = SecError(errSecInternal, error, CFSTR("sqlite3 error: %d"), s3e));
+            s3e = SQLITE_OK;
+            ok = true;
+        } else {
+            require_noerr_action_quiet(s3e, errOutSql, ok = SecError(errSecInternal, error, CFSTR("sqlite3 error: %d"), s3e));
+            ok = true;
+        }
+
+    errOutSql:
+        if (insert)
+            s3e = sqlite3_finalize(insert);
+        if (update)
+            s3e = sqlite3_finalize(update);
+
+        if (ok && s3e == SQLITE_OK)
+            s3e = sqlite3_exec(ts->s3h, "COMMIT TRANSACTION", NULL, NULL, NULL);
+
+        if (!ok || s3e != SQLITE_OK) {
+            sqlite3_exec(ts->s3h, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
+            if (ok) {
+                ok = SecError(errSecInternal, error, CFSTR("sqlite3 error: %d"), s3e);
+            }
+        }
+
+    errOut:
+        CFReleaseSafe(xmlData);
+        CFReleaseSafe(array);
+    });
+errOutNotLocked:
+       return ok;
+}
+
+/* AUDIT[securityd](done):
+   ts (ok) might be NULL.
+   digest (ok) is a data of any length (might be 0).
+ */
+bool SecTrustStoreRemoveCertificateWithDigest(SecTrustStoreRef ts,
+    CFDataRef digest, CFErrorRef *error) {
+       require_quiet(ts, errOutNotLocked);
+       require(!ts->readOnly, errOutNotLocked);
+    dispatch_sync(ts->queue, ^{
+        sqlite3_stmt *deleteStmt = NULL;
+        require_noerr(sqlite3_prepare(ts->s3h, deleteSQL, sizeof(deleteSQL),
+                                      &deleteStmt, NULL), errOut);
+        require_noerr(sqlite3_bind_blob_wrapper(deleteStmt, 1,
+                                                CFDataGetBytePtr(digest), CFDataGetLength(digest), SQLITE_STATIC),
+                      errOut);
+        sqlite3_step(deleteStmt);
+
+    errOut:
+        if (deleteStmt) {
+            verify_noerr(sqlite3_finalize(deleteStmt));
+        }
+    });
+errOutNotLocked:
+       return true;
+}
+
+bool _SecTrustStoreRemoveAll(SecTrustStoreRef ts, CFErrorRef *error)
+{
+    __block bool removed_all = false;
+       require(ts, errOutNotLocked);
+       require(!ts->readOnly, errOutNotLocked);
+    dispatch_sync(ts->queue, ^{
+        if (SQLITE_OK == sqlite3_exec(ts->s3h, deleteAllSQL, NULL, NULL, NULL))
+            removed_all = true;
+
+        /* prepared statements become unusable after deleteAllSQL, reset them */
+        if (ts->copyParents)
+            sqlite3_finalize(ts->copyParents);
+        sqlite3_prepare(ts->s3h, copyParentsSQL, sizeof(copyParentsSQL),
+                        &ts->copyParents, NULL);
+        if (ts->contains)
+            sqlite3_finalize(ts->contains);
+        sqlite3_prepare(ts->s3h, containsSQL, sizeof(containsSQL),
+                        &ts->contains, NULL);
+    });
+errOutNotLocked:
+       return removed_all;
+}
+
+CFArrayRef SecTrustStoreCopyParents(SecTrustStoreRef ts,
+       SecCertificateRef certificate, CFErrorRef *error) {
+       __block CFMutableArrayRef parents = NULL;
+       require(ts, errOutNotLocked);
+    dispatch_sync(ts->queue, ^{
+        CFDataRef issuer;
+        require(issuer = SecCertificateGetNormalizedIssuerContent(certificate),
+            errOut);
+        /* @@@ Might have to use SQLITE_TRANSIENT */
+        require_noerr(sqlite3_bind_blob_wrapper(ts->copyParents, 1,
+            CFDataGetBytePtr(issuer), CFDataGetLength(issuer),
+            SQLITE_STATIC), errOut);
+
+        require(parents = CFArrayCreateMutable(kCFAllocatorDefault, 0,
+            &kCFTypeArrayCallBacks), errOut);
+        for (;;) {
+            int s3e = sqlite3_step(ts->copyParents);
+            if (s3e == SQLITE_ROW) {
+                SecCertificateRef cert;
+                require(cert = SecCertificateCreateWithBytes(kCFAllocatorDefault,
+                    sqlite3_column_blob(ts->copyParents, 0),
+                    sqlite3_column_bytes(ts->copyParents, 0)), errOut);
+                CFArrayAppendValue(parents, cert);
+                CFRelease(cert);
+            } else {
+                require(s3e == SQLITE_DONE, errOut);
+                break;
+            }
+        }
+
+        goto ok;
+    errOut:
+        if (parents) {
+            CFRelease(parents);
+            parents = NULL;
+        }
+    ok:
+        verify_noerr(sqlite3_reset(ts->copyParents));
+        verify_noerr(sqlite3_clear_bindings(ts->copyParents));
+    });
+errOutNotLocked:
+       return parents;
+}
+
+/* AUDIT[securityd](done):
+   ts (ok) might be NULL.
+   digest (ok) is a data of any length (might be 0), only its cf type has
+   been checked.
+*/
+bool SecTrustStoreContainsCertificateWithDigest(SecTrustStoreRef ts,
+       CFDataRef digest, bool *contains, CFErrorRef *error) {
+    if (contains)
+        *contains = false;
+    __block bool ok = true;
+       require_action_quiet(ts, errOutNotLocked, ok = SecError(errSecParam, error, CFSTR("ts is NULL")));
+    dispatch_sync(ts->queue, ^{
+        int s3e;
+        require_noerr_action(s3e = sqlite3_bind_blob_wrapper(ts->contains, 1,
+            CFDataGetBytePtr(digest), CFDataGetLength(digest), SQLITE_STATIC),
+            errOut, ok = SecDbErrorWithStmt(s3e, ts->contains, error, CFSTR("sqlite3_bind_blob failed")));
+        s3e = sqlite3_step(ts->contains);
+        if (s3e == SQLITE_ROW) {
+            if (contains)
+                *contains = true;
+        } else {
+            require_action(s3e == SQLITE_DONE, errOut, ok = SecDbErrorWithStmt(s3e, ts->contains, error, CFSTR("sqlite3_step failed")));
+        }
+
+    errOut:
+        verify_noerr(sqlite3_reset(ts->contains));
+        verify_noerr(sqlite3_clear_bindings(ts->contains));
+    });
+errOutNotLocked:
+       return ok;
+}