1/*
2 * Copyright (c) 2011 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 *
23 * Created by Michael Brouwer on 6/17/11.
24 */
25
26/*
27 *  SecCAIssuerCache.c - securityd
28 */
29
30#include <securityd/SecCAIssuerCache.h>
31#include <utilities/debugging.h>
32#include <Security/SecCertificateInternal.h>
33#include <Security/SecFramework.h>
34#include <Security/SecInternal.h>
35#include <sqlite3.h>
36#include <AssertMacros.h>
37#include <stdlib.h>
38#include <limits.h>
39#include <string.h>
40#include <fcntl.h>
41#include <sys/stat.h>
42#include <errno.h>
43#include <dispatch/dispatch.h>
44#include <asl.h>
45#include "utilities/sqlutils.h"
46#include "utilities/iOSforOSX.h"
47
48#include <CoreFoundation/CFUtilities.h>
49#include <utilities/SecFileLocations.h>
50
51#define caissuerErrorLog(args...)     asl_log(NULL, NULL, ASL_LEVEL_ERR, ## args)
52
53static const char expireSQL[] = "DELETE FROM issuers WHERE expires<?";
54static const char beginTxnSQL[] = "BEGIN EXCLUSIVE TRANSACTION";
55static const char endTxnSQL[] = "COMMIT TRANSACTION";
56static const char insertIssuerSQL[] = "INSERT OR REPLACE INTO issuers "
57    "(uri,expires,certificate) VALUES (?,?,?)";
58static const char selectIssuerSQL[] = "SELECT certificate FROM "
59    "issuers WHERE uri=?";
60
61#define kSecCAIssuerFileName "caissuercache.sqlite3"
62
63typedef struct __SecCAIssuerCache *SecCAIssuerCacheRef;
64struct __SecCAIssuerCache {
65    dispatch_queue_t queue;
66	sqlite3 *s3h;
67	sqlite3_stmt *expire;
68	sqlite3_stmt *beginTxn;
69	sqlite3_stmt *endTxn;
70	sqlite3_stmt *insertIssuer;
71	sqlite3_stmt *selectIssuer;
72    bool in_transaction;
73};
74
75static dispatch_once_t kSecCAIssuerCacheOnce;
76static SecCAIssuerCacheRef kSecCAIssuerCache;
77
78/* @@@ Duplicated from SecTrustStore.c */
79static int sec_create_path(const char *path)
80{
81	char pathbuf[PATH_MAX];
82	size_t pos, len = strlen(path);
83	if (len == 0 || len > PATH_MAX)
84		return SQLITE_CANTOPEN;
85	memcpy(pathbuf, path, len);
86	for (pos = len-1; pos > 0; --pos)
87	{
88		/* Search backwards for trailing '/'. */
89		if (pathbuf[pos] == '/')
90		{
91			pathbuf[pos] = '\0';
92			/* Attempt to create parent directories of the database. */
93			if (!mkdir(pathbuf, 0777))
94				break;
95			else
96			{
97				int err = errno;
98				if (err == EEXIST)
99					return 0;
100				if (err == ENOTDIR)
101					return SQLITE_CANTOPEN;
102				if (err == EROFS)
103					return SQLITE_READONLY;
104				if (err == EACCES)
105					return SQLITE_PERM;
106				if (err == ENOSPC || err == EDQUOT)
107					return SQLITE_FULL;
108				if (err == EIO)
109					return SQLITE_IOERR;
110
111				/* EFAULT || ELOOP | ENAMETOOLONG || something else */
112				return SQLITE_INTERNAL;
113			}
114		}
115	}
116	return SQLITE_OK;
117}
118
119static int sec_sqlite3_open(const char *db_name, sqlite3 **s3h,
120                            bool create_path)
121{
122	int s3e;
123	s3e = sqlite3_open(db_name, s3h);
124	if (s3e == SQLITE_CANTOPEN && create_path) {
125		/* Make sure the path to db_name exists and is writable, then
126         try again. */
127		s3e = sec_create_path(db_name);
128		if (!s3e)
129			s3e = sqlite3_open(db_name, s3h);
130	}
131
132	return s3e;
133}
134
135static int sec_sqlite3_reset(sqlite3_stmt *stmt, int s3e) {
136    int s3e2;
137    if (s3e == SQLITE_ROW || s3e == SQLITE_DONE)
138        s3e = SQLITE_OK;
139    s3e2 = sqlite3_reset(stmt);
140    if (s3e2 && !s3e)
141        s3e = s3e2;
142    s3e2 = sqlite3_clear_bindings(stmt);
143    if (s3e2 && !s3e)
144        s3e = s3e2;
145    return s3e;
146}
147
148static int SecCAIssuerCacheEnsureTxn(SecCAIssuerCacheRef this) {
149    int s3e, s3e2;
150
151    if (this->in_transaction)
152        return SQLITE_OK;
153
154    s3e = sqlite3_step(this->beginTxn);
155    if (s3e == SQLITE_DONE) {
156        this->in_transaction = true;
157        s3e = SQLITE_OK;
158    } else {
159        secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e,
160                 sqlite3_errmsg(this->s3h));
161    }
162    s3e2 = sqlite3_reset(this->beginTxn);
163    if (s3e2 && !s3e)
164        s3e = s3e2;
165
166    return s3e;
167}
168
169static int SecCAIssuerCacheCommitTxn(SecCAIssuerCacheRef this) {
170    int s3e, s3e2;
171
172    if (!this->in_transaction)
173        return SQLITE_OK;
174
175    s3e = sqlite3_step(this->endTxn);
176    if (s3e == SQLITE_DONE) {
177        this->in_transaction = false;
178        s3e = SQLITE_OK;
179    } else {
180        secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e,
181                 sqlite3_errmsg(this->s3h));
182    }
183    s3e2 = sqlite3_reset(this->endTxn);
184    if (s3e2 && !s3e)
185        s3e = s3e2;
186
187    return s3e;
188}
189
190static SecCAIssuerCacheRef SecCAIssuerCacheCreate(const char *db_name) {
191	SecCAIssuerCacheRef this;
192	int s3e;
193    bool create = true;
194
195    require(this = (SecCAIssuerCacheRef)calloc(sizeof(struct __SecCAIssuerCache), 1), errOut);
196    require_action_quiet((this->queue = dispatch_queue_create("caissuercache", 0)), errOut, s3e = errSecAllocate);
197    require_noerr(s3e = sec_sqlite3_open(db_name, &this->s3h, create), errOut);
198    this->in_transaction = false;
199
200	s3e = sqlite3_prepare_v2(this->s3h, beginTxnSQL, sizeof(beginTxnSQL),
201                             &this->beginTxn, NULL);
202	require_noerr(s3e, errOut);
203	s3e = sqlite3_prepare_v2(this->s3h, endTxnSQL, sizeof(endTxnSQL),
204                             &this->endTxn, NULL);
205	require_noerr(s3e, errOut);
206
207	s3e = sqlite3_prepare_v2(this->s3h, expireSQL, sizeof(expireSQL),
208                             &this->expire, NULL);
209	if (create && s3e == SQLITE_ERROR) {
210        s3e = SecCAIssuerCacheEnsureTxn(this);
211		require_noerr(s3e, errOut);
212
213		/* sqlite3_prepare returns SQLITE_ERROR if the table we are
214         compiling this statement for doesn't exist. */
215		char *errmsg = NULL;
216		s3e = sqlite3_exec(this->s3h,
217                           "CREATE TABLE issuers("
218                           "uri BLOB PRIMARY KEY,"
219                           "expires DOUBLE NOT NULL,"
220                           "certificate BLOB NOT NULL"
221                           ");"
222                           "CREATE INDEX iexpires ON issuers(expires);"
223                           , NULL, NULL, &errmsg);
224		if (errmsg) {
225			caissuerErrorLog("caissuer db CREATE TABLES: %s", errmsg);
226			sqlite3_free(errmsg);
227		}
228		require_noerr(s3e, errOut);
229        s3e = sqlite3_prepare_v2(this->s3h, expireSQL, sizeof(expireSQL),
230                                 &this->expire, NULL);
231	}
232	require_noerr(s3e, errOut);
233	s3e = sqlite3_prepare_v2(this->s3h, insertIssuerSQL, sizeof(insertIssuerSQL),
234                             &this->insertIssuer, NULL);
235	require_noerr(s3e, errOut);
236	s3e = sqlite3_prepare_v2(this->s3h, selectIssuerSQL, sizeof(selectIssuerSQL),
237                             &this->selectIssuer, NULL);
238	require_noerr(s3e, errOut);
239
240	return this;
241
242errOut:
243	if (this) {
244        if (this->queue)
245            dispatch_release(this->queue);
246        if (this->s3h)
247            sqlite3_close(this->s3h);
248		free(this);
249	}
250
251	return NULL;
252}
253
254static void SecCAIssuerCacheInit(void) {
255    WithPathInKeychainDirectory(CFSTR(kSecCAIssuerFileName), ^(const char *utf8String) {
256        kSecCAIssuerCache = SecCAIssuerCacheCreate(utf8String);
257    });
258
259    if (kSecCAIssuerCache)
260        atexit(SecCAIssuerCacheGC);
261}
262
263/* Instance implemenation. */
264
265static void _SecCAIssuerCacheAddCertificate(SecCAIssuerCacheRef this,
266                                            SecCertificateRef certificate,
267                                            CFURLRef uri, CFAbsoluteTime expires) {
268    int s3e;
269
270    secdebug("caissuercache", "adding certificate from %@", uri);
271    require_noerr(s3e = SecCAIssuerCacheEnsureTxn(this), errOut);
272
273    /* issuer.uri */
274    CFDataRef uriData;
275    require_action(uriData = CFURLCreateData(kCFAllocatorDefault, uri,
276        kCFStringEncodingUTF8, false), errOut, s3e = SQLITE_NOMEM);
277    s3e = sqlite3_bind_blob_wrapper(this->insertIssuer, 1,
278        CFDataGetBytePtr(uriData), CFDataGetLength(uriData), SQLITE_TRANSIENT);
279    CFRelease(uriData);
280
281    /* issuer.expires */
282    if (!s3e) s3e = sqlite3_bind_double(this->insertIssuer, 2, expires);
283
284    /* issuer.certificate */
285    if (!s3e) s3e = sqlite3_bind_blob_wrapper(this->insertIssuer, 3,
286        SecCertificateGetBytePtr(certificate),
287        SecCertificateGetLength(certificate), SQLITE_TRANSIENT);
288
289    /* Execute the insert statement. */
290    if (!s3e) s3e = sqlite3_step(this->insertIssuer);
291    require_noerr(s3e = sec_sqlite3_reset(this->insertIssuer, s3e), errOut);
292
293errOut:
294    if (s3e) {
295        caissuerErrorLog("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h));
296        /* TODO: Blow away the cache and create a new db. */
297    }
298}
299
300static SecCertificateRef _SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef this,
301                                                       CFURLRef uri) {
302    SecCertificateRef certificate = NULL;
303    int s3e = SQLITE_OK;
304
305    CFDataRef uriData = NULL;
306    require(uriData = CFURLCreateData(kCFAllocatorDefault, uri,
307                                      kCFStringEncodingUTF8, false), errOut);
308    s3e = sqlite3_bind_blob_wrapper(this->selectIssuer, 1, CFDataGetBytePtr(uriData),
309                            CFDataGetLength(uriData), SQLITE_TRANSIENT);
310    CFRelease(uriData);
311
312    if (!s3e) s3e = sqlite3_step(this->selectIssuer);
313    if (s3e == SQLITE_ROW) {
314        /* Found an entry! */
315        secdebug("caissuercache", "found cached response for %@", uri);
316
317        const void *respData = sqlite3_column_blob(this->selectIssuer, 0);
318        int respLen = sqlite3_column_bytes(this->selectIssuer, 0);
319        certificate = SecCertificateCreateWithBytes(NULL, respData, respLen);
320    }
321
322    require_noerr(s3e = sec_sqlite3_reset(this->selectIssuer, s3e), errOut);
323
324errOut:
325    if (s3e) {
326        if (s3e != SQLITE_DONE) {
327            caissuerErrorLog("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h));
328            /* TODO: Blow away the cache and create a new db. */
329        }
330
331        if (certificate) {
332            CFRelease(certificate);
333            certificate = NULL;
334        }
335    }
336
337    secdebug("caissuercache", "returning %s for %@", (certificate ? "cached response" : "NULL"), uri);
338
339    return certificate;
340}
341
342static void _SecCAIssuerCacheGC(void *context) {
343    SecCAIssuerCacheRef this = context;
344    int s3e;
345
346    require_noerr(s3e = SecCAIssuerCacheEnsureTxn(this), errOut);
347    secdebug("caissuercache", "expiring stale responses");
348    s3e = sqlite3_bind_double(this->expire, 1, CFAbsoluteTimeGetCurrent());
349    if (!s3e) s3e = sqlite3_step(this->expire);
350    require_noerr(s3e = sec_sqlite3_reset(this->expire, s3e), errOut);
351    require_noerr(s3e = SecCAIssuerCacheCommitTxn(this), errOut);
352
353errOut:
354    if (s3e) {
355        caissuerErrorLog("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h));
356        /* TODO: Blow away the cache and create a new db. */
357    }
358}
359
360static void _SecCAIssuerCacheFlush(void *context) {
361    SecCAIssuerCacheRef this = context;
362    int s3e;
363
364    secdebug("caissuercache", "flushing pending changes");
365    s3e = SecCAIssuerCacheCommitTxn(this);
366
367    if (s3e) {
368        caissuerErrorLog("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h));
369        /* TODO: Blow away the cache and create a new db. */
370    }
371}
372
373/* Public API */
374
375void SecCAIssuerCacheAddCertificate(SecCertificateRef certificate,
376                                    CFURLRef uri, CFAbsoluteTime expires) {
377    dispatch_once(&kSecCAIssuerCacheOnce, ^{
378        SecCAIssuerCacheInit();
379    });
380    if (!kSecCAIssuerCache)
381        return;
382
383    dispatch_sync(kSecCAIssuerCache->queue, ^{
384        _SecCAIssuerCacheAddCertificate(kSecCAIssuerCache, certificate, uri, expires);
385    });
386}
387
388SecCertificateRef SecCAIssuerCacheCopyMatching(CFURLRef uri) {
389    dispatch_once(&kSecCAIssuerCacheOnce, ^{
390        SecCAIssuerCacheInit();
391    });
392    __block SecCertificateRef cert = NULL;
393    if (kSecCAIssuerCache)
394        dispatch_sync(kSecCAIssuerCache->queue, ^{
395            cert = _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache, uri);
396        });
397    return cert;
398}
399
400/* This should be called on a normal non emergency exit. This function
401 effectively does a SecCAIssuerCacheFlush.
402 Currently this is called from our atexit handeler.
403 This function expires any records that are stale and commits.
404
405 Idea for future cache management policies:
406 Expire old cache entires from database if:
407 - The time to do so has arrived based on the nextExpire date in the
408 policy table.
409 - If the size of the database exceeds the limit set in the maxSize field
410 in the policy table, vacuum the db.  If the database is still too
411 big, expire records on a LRU basis.
412 */
413void SecCAIssuerCacheGC(void) {
414    if (kSecCAIssuerCache)
415        dispatch_sync(kSecCAIssuerCache->queue, ^{
416            _SecCAIssuerCacheGC(kSecCAIssuerCache);
417        });
418}
419
420/* Call this periodically or perhaps when we are exiting due to low memory. */
421void SecCAIssuerCacheFlush(void) {
422    if (kSecCAIssuerCache)
423        dispatch_sync(kSecCAIssuerCache->queue, ^{
424            _SecCAIssuerCacheFlush(kSecCAIssuerCache);
425        });
426}
427