e94c5de87dc53e608d0e0e2c9e2b27b50500d408
[openssl.git] / crypto / property / property.c
1 /*
2  * Copyright 2019 The OpenSSL Project Authors. All Rights Reserved.
3  * Copyright (c) 2019, Oracle and/or its affiliates.  All rights reserved.
4  *
5  * Licensed under the Apache License 2.0 (the "License").  You may not use
6  * this file except in compliance with the License.  You can obtain a copy
7  * in the file LICENSE in the source distribution or at
8  * https://www.openssl.org/source/license.html
9  */
10
11 #include <string.h>
12 #include <stdio.h>
13 #include <stdarg.h>
14 #include <openssl/crypto.h>
15 #include "internal/property.h"
16 #include "internal/ctype.h"
17 #include <openssl/lhash.h>
18 #include <openssl/rand.h>
19 #include "internal/thread_once.h"
20 #include "internal/lhash.h"
21 #include "internal/sparse_array.h"
22 #include "property_lcl.h"
23
24 /* The number of elements in the query cache before we initiate a flush */
25 #define IMPL_CACHE_FLUSH_THRESHOLD  500
26
27 typedef struct {
28     const OSSL_PROVIDER *provider;
29     OSSL_PROPERTY_LIST *properties;
30     void *method;
31     void (*method_destruct)(void *);
32 } IMPLEMENTATION;
33
34 DEFINE_STACK_OF(IMPLEMENTATION)
35
36 typedef struct {
37     const char *query;
38     void *method;
39     char body[1];
40 } QUERY;
41
42 DEFINE_LHASH_OF(QUERY);
43
44 typedef struct {
45     int nid;
46     STACK_OF(IMPLEMENTATION) *impls;
47     LHASH_OF(QUERY) *cache;
48 } ALGORITHM;
49
50 struct ossl_method_store_st {
51     OPENSSL_CTX *ctx;
52     size_t nelem;
53     SPARSE_ARRAY_OF(ALGORITHM) *algs;
54     OSSL_PROPERTY_LIST *global_properties;
55     int need_flush;
56     unsigned int nbits;
57     unsigned char rand_bits[(IMPL_CACHE_FLUSH_THRESHOLD + 7) / 8];
58     CRYPTO_RWLOCK *lock;
59 };
60
61 typedef struct {
62     LHASH_OF(QUERY) *cache;
63     size_t nelem;
64     uint32_t seed;
65 } IMPL_CACHE_FLUSH;
66
67 DEFINE_SPARSE_ARRAY_OF(ALGORITHM);
68
69 static void ossl_method_cache_flush(OSSL_METHOD_STORE *store, int nid);
70 static void ossl_method_cache_flush_all(OSSL_METHOD_STORE *c);
71
72 int ossl_property_read_lock(OSSL_METHOD_STORE *p)
73 {
74     return p != NULL ? CRYPTO_THREAD_read_lock(p->lock) : 0;
75 }
76
77 int ossl_property_write_lock(OSSL_METHOD_STORE *p)
78 {
79     return p != NULL ? CRYPTO_THREAD_write_lock(p->lock) : 0;
80 }
81
82 int ossl_property_unlock(OSSL_METHOD_STORE *p)
83 {
84     return p != 0 ? CRYPTO_THREAD_unlock(p->lock) : 0;
85 }
86
87 static unsigned long query_hash(const QUERY *a)
88 {
89     return OPENSSL_LH_strhash(a->query);
90 }
91
92 static int query_cmp(const QUERY *a, const QUERY *b)
93 {
94     return strcmp(a->query, b->query);
95 }
96
97 static void impl_free(IMPLEMENTATION *impl)
98 {
99     if (impl != NULL) {
100         if (impl->method_destruct)
101             impl->method_destruct(impl->method);
102         OPENSSL_free(impl);
103     }
104 }
105
106 static void impl_cache_free(QUERY *elem)
107 {
108     OPENSSL_free(elem);
109 }
110
111 static void alg_cleanup(ossl_uintmax_t idx, ALGORITHM *a)
112 {
113     if (a != NULL) {
114         sk_IMPLEMENTATION_pop_free(a->impls, &impl_free);
115         lh_QUERY_doall(a->cache, &impl_cache_free);
116         lh_QUERY_free(a->cache);
117         OPENSSL_free(a);
118     }
119 }
120
121 /*
122  * The OPENSSL_CTX param here allows access to underlying property data needed
123  * for computation
124  */
125 OSSL_METHOD_STORE *ossl_method_store_new(OPENSSL_CTX *ctx)
126 {
127     OSSL_METHOD_STORE *res;
128
129     res = OPENSSL_zalloc(sizeof(*res));
130     if (res != NULL) {
131         res->ctx = ctx;
132         if ((res->algs = ossl_sa_ALGORITHM_new()) == NULL) {
133             OPENSSL_free(res);
134             return NULL;
135         }
136         if ((res->lock = CRYPTO_THREAD_lock_new()) == NULL) {
137             OPENSSL_free(res->algs);
138             OPENSSL_free(res);
139             return NULL;
140         }
141     }
142     return res;
143 }
144
145 void ossl_method_store_free(OSSL_METHOD_STORE *store)
146 {
147     if (store != NULL) {
148         ossl_sa_ALGORITHM_doall(store->algs, &alg_cleanup);
149         ossl_sa_ALGORITHM_free(store->algs);
150         ossl_property_free(store->global_properties);
151         CRYPTO_THREAD_lock_free(store->lock);
152         OPENSSL_free(store);
153     }
154 }
155
156 static ALGORITHM *ossl_method_store_retrieve(OSSL_METHOD_STORE *store, int nid)
157 {
158     return ossl_sa_ALGORITHM_get(store->algs, nid);
159 }
160
161 static int ossl_method_store_insert(OSSL_METHOD_STORE *store, ALGORITHM *alg)
162 {
163         return ossl_sa_ALGORITHM_set(store->algs, alg->nid, alg);
164 }
165
166 int ossl_method_store_add(OSSL_METHOD_STORE *store, const OSSL_PROVIDER *prov,
167                           int nid, const char *properties, void *method,
168                           int (*method_up_ref)(void *),
169                           void (*method_destruct)(void *))
170 {
171     ALGORITHM *alg = NULL;
172     IMPLEMENTATION *impl;
173     int ret = 0;
174     int i;
175
176     if (nid <= 0 || method == NULL || store == NULL)
177         return 0;
178     if (properties == NULL)
179         properties = "";
180
181     /* Create new entry */
182     impl = OPENSSL_malloc(sizeof(*impl));
183     if (impl == NULL)
184         return 0;
185     if (method_up_ref != NULL && !method_up_ref(method)) {
186         OPENSSL_free(impl);
187         return 0;
188     }
189     impl->provider = prov;
190     impl->method = method;
191     impl->method_destruct = method_destruct;
192
193     /*
194      * Insert into the hash table if required.
195      *
196      * A write lock is used unconditionally because we wend our way down to the
197      * property string code which isn't locking friendly.
198      */
199     ossl_property_write_lock(store);
200     ossl_method_cache_flush(store, nid);
201     if ((impl->properties = ossl_prop_defn_get(store->ctx, properties)) == NULL) {
202         impl->properties = ossl_parse_property(store->ctx, properties);
203         if (impl->properties == NULL)
204             goto err;
205         ossl_prop_defn_set(store->ctx, properties, impl->properties);
206     }
207
208     alg = ossl_method_store_retrieve(store, nid);
209     if (alg == NULL) {
210         if ((alg = OPENSSL_zalloc(sizeof(*alg))) == NULL
211                 || (alg->impls = sk_IMPLEMENTATION_new_null()) == NULL
212                 || (alg->cache = lh_QUERY_new(&query_hash, &query_cmp)) == NULL)
213             goto err;
214         alg->nid = nid;
215         if (!ossl_method_store_insert(store, alg))
216             goto err;
217     }
218
219     /* Push onto stack if there isn't one there already */
220     for (i = 0; i < sk_IMPLEMENTATION_num(alg->impls); i++) {
221         const IMPLEMENTATION *tmpimpl = sk_IMPLEMENTATION_value(alg->impls, i);
222
223         if (tmpimpl->provider == impl->provider
224             && tmpimpl->properties == impl->properties)
225             break;
226     }
227     if (i == sk_IMPLEMENTATION_num(alg->impls)
228         && sk_IMPLEMENTATION_push(alg->impls, impl))
229         ret = 1;
230     ossl_property_unlock(store);
231     if (ret == 0)
232         impl_free(impl);
233     return ret;
234
235 err:
236     ossl_property_unlock(store);
237     alg_cleanup(0, alg);
238     impl_free(impl);
239     return 0;
240 }
241
242 int ossl_method_store_remove(OSSL_METHOD_STORE *store, int nid,
243                              const void *method)
244 {
245     ALGORITHM *alg = NULL;
246     int i;
247
248     if (nid <= 0 || method == NULL || store == NULL)
249         return 0;
250
251     ossl_property_write_lock(store);
252     ossl_method_cache_flush(store, nid);
253     alg = ossl_method_store_retrieve(store, nid);
254     if (alg == NULL) {
255         ossl_property_unlock(store);
256         return 0;
257     }
258
259     /*
260      * A sorting find then a delete could be faster but these stacks should be
261      * relatively small, so we avoid the overhead.  Sorting could also surprise
262      * users when result orderings change (even though they are not guaranteed).
263      */
264     for (i = 0; i < sk_IMPLEMENTATION_num(alg->impls); i++) {
265         IMPLEMENTATION *impl = sk_IMPLEMENTATION_value(alg->impls, i);
266
267         if (impl->method == method) {
268             sk_IMPLEMENTATION_delete(alg->impls, i);
269             ossl_property_unlock(store);
270             impl_free(impl);
271             return 1;
272         }
273     }
274     ossl_property_unlock(store);
275     return 0;
276 }
277
278 int ossl_method_store_fetch(OSSL_METHOD_STORE *store, int nid,
279                             const char *prop_query, void **method)
280 {
281     ALGORITHM *alg;
282     IMPLEMENTATION *impl;
283     OSSL_PROPERTY_LIST *pq = NULL, *p2;
284     int ret = 0;
285     int j, best = -1, score, optional;
286
287 #ifndef FIPS_MODE
288     OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL);
289 #endif
290
291     if (nid <= 0 || method == NULL || store == NULL)
292         return 0;
293
294     /*
295      * This only needs to be a read lock, because queries never create property
296      * names or value and thus don't modify any of the property string layer.
297      */
298     ossl_property_read_lock(store);
299     alg = ossl_method_store_retrieve(store, nid);
300     if (alg == NULL) {
301         ossl_property_unlock(store);
302         return 0;
303     }
304
305     if (prop_query == NULL) {
306         if ((impl = sk_IMPLEMENTATION_value(alg->impls, 0)) != NULL) {
307             *method = impl->method;
308             ret = 1;
309         }
310         goto fin;
311     }
312     pq = ossl_parse_query(store->ctx, prop_query);
313     if (pq == NULL)
314         goto fin;
315     if (store->global_properties != NULL) {
316         p2 = ossl_property_merge(pq, store->global_properties);
317         if (p2 == NULL)
318             goto fin;
319         ossl_property_free(pq);
320         pq = p2;
321     }
322     optional = ossl_property_has_optional(pq);
323     for (j = 0; j < sk_IMPLEMENTATION_num(alg->impls); j++) {
324         impl = sk_IMPLEMENTATION_value(alg->impls, j);
325         score = ossl_property_match_count(pq, impl->properties);
326         if (score > best) {
327             *method = impl->method;
328             ret = 1;
329             if (!optional)
330                 goto fin;
331             best = score;
332         }
333     }
334 fin:
335     ossl_property_unlock(store);
336     ossl_property_free(pq);
337     return ret;
338 }
339
340 int ossl_method_store_set_global_properties(OSSL_METHOD_STORE *store,
341                                             const char *prop_query) {
342     int ret = 0;
343
344     if (store == NULL)
345         return 1;
346
347     ossl_property_write_lock(store);
348     ossl_method_cache_flush_all(store);
349     if (prop_query == NULL) {
350         ossl_property_free(store->global_properties);
351         store->global_properties = NULL;
352         ossl_property_unlock(store);
353         return 1;
354     }
355     store->global_properties = ossl_parse_query(store->ctx, prop_query);
356     ret = store->global_properties != NULL;
357     ossl_property_unlock(store);
358     return ret;
359 }
360
361 static void impl_cache_flush_alg(ossl_uintmax_t idx, ALGORITHM *alg)
362 {
363     lh_QUERY_doall(alg->cache, &impl_cache_free);
364     lh_QUERY_flush(alg->cache);
365 }
366
367 static void ossl_method_cache_flush(OSSL_METHOD_STORE *store, int nid)
368 {
369     ALGORITHM *alg = ossl_method_store_retrieve(store, nid);
370
371     if (alg != NULL) {
372         store->nelem -= lh_QUERY_num_items(alg->cache);
373         impl_cache_flush_alg(0, alg);
374     }
375 }
376
377 static void ossl_method_cache_flush_all(OSSL_METHOD_STORE *store)
378 {
379     ossl_sa_ALGORITHM_doall(store->algs, &impl_cache_flush_alg);
380     store->nelem = 0;
381 }
382
383 IMPLEMENT_LHASH_DOALL_ARG(QUERY, IMPL_CACHE_FLUSH);
384
385 /*
386  * Flush an element from the query cache (perhaps).
387  *
388  * In order to avoid taking a write lock or using atomic operations
389  * to keep accurate least recently used (LRU) or least frequently used
390  * (LFU) information, the procedure used here is to stochastically
391  * flush approximately half the cache.
392  *
393  * This procedure isn't ideal, LRU or LFU would be better.  However,
394  * in normal operation, reaching a full cache would be unexpected.
395  * It means that no steady state of algorithm queries has been reached.
396  * That is, it is most likely an attack of some form.  A suboptimal clearance
397  * strategy that doesn't degrade performance of the normal case is
398  * preferable to a more refined approach that imposes a performance
399  * impact.
400  */
401 static void impl_cache_flush_cache(QUERY *c, IMPL_CACHE_FLUSH *state)
402 {
403     uint32_t n;
404
405     /*
406      * Implement the 32 bit xorshift as suggested by George Marsaglia in:
407      *      https://doi.org/10.18637/jss.v008.i14
408      *
409      * This is a very fast PRNG so there is no need to extract bits one at a
410      * time and use the entire value each time.
411      */
412     n = state->seed;
413     n ^= n << 13;
414     n ^= n >> 17;
415     n ^= n << 5;
416     state->seed = n;
417
418     if ((n & 1) != 0)
419         OPENSSL_free(lh_QUERY_delete(state->cache, c));
420     else
421         state->nelem++;
422 }
423
424 static void impl_cache_flush_one_alg(ossl_uintmax_t idx, ALGORITHM *alg,
425                                      void *v)
426 {
427     IMPL_CACHE_FLUSH *state = (IMPL_CACHE_FLUSH *)v;
428
429     state->cache = alg->cache;
430     lh_QUERY_doall_IMPL_CACHE_FLUSH(state->cache, &impl_cache_flush_cache,
431                                     state);
432 }
433
434 static void ossl_method_cache_flush_some(OSSL_METHOD_STORE *store)
435 {
436     IMPL_CACHE_FLUSH state;
437
438     state.nelem = 0;
439     if ((state.seed = OPENSSL_rdtsc()) == 0)
440         state.seed = 1;
441     store->need_flush = 0;
442     ossl_sa_ALGORITHM_doall_arg(store->algs, &impl_cache_flush_one_alg, &state);
443     store->nelem = state.nelem;
444 }
445
446 int ossl_method_store_cache_get(OSSL_METHOD_STORE *store, int nid,
447                                 const char *prop_query, void **method)
448 {
449     ALGORITHM *alg;
450     QUERY elem, *r;
451
452     if (nid <= 0 || store == NULL)
453         return 0;
454
455     ossl_property_read_lock(store);
456     alg = ossl_method_store_retrieve(store, nid);
457     if (alg == NULL) {
458         ossl_property_unlock(store);
459         return 0;
460     }
461
462     elem.query = prop_query != NULL ? prop_query : "";
463     r = lh_QUERY_retrieve(alg->cache, &elem);
464     if (r == NULL) {
465         ossl_property_unlock(store);
466         return 0;
467     }
468     *method = r->method;
469     ossl_property_unlock(store);
470     return 1;
471 }
472
473 int ossl_method_store_cache_set(OSSL_METHOD_STORE *store, int nid,
474                                 const char *prop_query, void *method)
475 {
476     QUERY elem, *old, *p = NULL;
477     ALGORITHM *alg;
478     size_t len;
479
480     if (nid <= 0 || store == NULL)
481         return 0;
482     if (prop_query == NULL)
483         return 1;
484
485     ossl_property_write_lock(store);
486     if (store->need_flush)
487         ossl_method_cache_flush_some(store);
488     alg = ossl_method_store_retrieve(store, nid);
489     if (alg == NULL) {
490         ossl_property_unlock(store);
491         return 0;
492     }
493
494     if (method == NULL) {
495         elem.query = prop_query;
496         if (lh_QUERY_delete(alg->cache, &elem) != NULL)
497             store->nelem--;
498         ossl_property_unlock(store);
499         return 1;
500     }
501     p = OPENSSL_malloc(sizeof(*p) + (len = strlen(prop_query)));
502     if (p != NULL) {
503         p->query = p->body;
504         p->method = method;
505         memcpy((char *)p->query, prop_query, len + 1);
506         if ((old = lh_QUERY_insert(alg->cache, p)) != NULL) {
507             OPENSSL_free(old);
508             ossl_property_unlock(store);
509             return 1;
510         }
511         if (!lh_QUERY_error(alg->cache)) {
512             if (++store->nelem >= IMPL_CACHE_FLUSH_THRESHOLD)
513                 store->need_flush = 1;
514             ossl_property_unlock(store);
515             return 1;
516         }
517     }
518     ossl_property_unlock(store);
519     OPENSSL_free(p);
520     return 0;
521 }