summaryrefslogtreecommitdiffstats
path: root/crypto/x509/by_store.c
blob: 8a8c50b6531d2c5355fe22bf31f6b8f66257a988 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/*
 * Copyright 2018-2020 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the Apache License 2.0 (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */

#include <openssl/store.h>
#include "internal/cryptlib.h"
#include "crypto/x509.h"
#include "x509_local.h"

/* Generic object loader, given expected type and criterion */
static int cache_objects(X509_LOOKUP *lctx, const char *uri,
                         const OSSL_STORE_SEARCH *criterion,
                         int depth)
{
    int ok = 0;
    OSSL_STORE_CTX *ctx = NULL;
    X509_STORE *xstore = X509_LOOKUP_get_store(lctx);

    if ((ctx = OSSL_STORE_open(uri, NULL, NULL, NULL, NULL)) == NULL)
        return 0;

    /*
     * We try to set the criterion, but don't care if it was valid or not.
     * For a OSSL_STORE, it merely serves as an optimization, the expectation
     * being that if the criterion couldn't be used, we will get *everything*
     * from the container that the URI represents rather than the subset that
     * the criterion indicates, so the biggest harm is that we cache more
     * objects certs and CRLs than we may expect, but that's ok.
     *
     * Specifically for OpenSSL's own file: scheme, the only workable
     * criterion is the BY_NAME one, which it can only apply on directories,
     * but it's possible that the URI is a single file rather than a directory,
     * and in that case, the BY_NAME criterion is pointless.
     *
     * We could very simply not apply any criterion at all here, and just let
     * the code that selects certs and CRLs from the cached objects do its job,
     * but it's a nice optimization when it can be applied (such as on an
     * actual directory with a thousand CA certs).
     */
    if (criterion != NULL)
        OSSL_STORE_find(ctx, criterion);

    for (;;) {
        OSSL_STORE_INFO *info = OSSL_STORE_load(ctx);
        int infotype;

        /* NULL means error or "end of file".  Either way, we break. */
        if (info == NULL)
            break;

        infotype = OSSL_STORE_INFO_get_type(info);
        ok = 0;

        if (infotype == OSSL_STORE_INFO_NAME) {
            /*
             * This is an entry in the "directory" represented by the current
             * uri.  if |depth| allows, dive into it.
             */
            if (depth > 0)
                ok = cache_objects(lctx, OSSL_STORE_INFO_get0_NAME(info),
                                   criterion, depth - 1);
        } else {
            /*
             * We know that X509_STORE_add_{cert|crl} increments the object's
             * refcount, so we can safely use OSSL_STORE_INFO_get0_{cert,crl}
             * to get them.
             */
            switch (infotype) {
            case OSSL_STORE_INFO_CERT:
                ok = X509_STORE_add_cert(xstore,
                                         OSSL_STORE_INFO_get0_CERT(info));
                break;
            case OSSL_STORE_INFO_CRL:
                ok = X509_STORE_add_crl(xstore,
                                        OSSL_STORE_INFO_get0_CRL(info));
                break;
            }
        }

        OSSL_STORE_INFO_free(info);
        if (!ok)
            break;
    }
    OSSL_STORE_close(ctx);

    return ok;
}


/* Because OPENSSL_free is a macro and for C type match */
static void free_uri(OPENSSL_STRING data)
{
    OPENSSL_free(data);
}

static void by_store_free(X509_LOOKUP *ctx)
{
    STACK_OF(OPENSSL_STRING) *uris = X509_LOOKUP_get_method_data(ctx);
    sk_OPENSSL_STRING_pop_free(uris, free_uri);
}

static int by_store_ctrl(X509_LOOKUP *ctx, int cmd,
                         const char *argp, long argl,
                         char **retp)
{
    switch (cmd) {
    case X509_L_ADD_STORE:
        /* If no URI is given, use the default cert dir as default URI */
        if (argp == NULL)
            argp = ossl_safe_getenv(X509_get_default_cert_dir_env());
        if (argp == NULL)
            argp = X509_get_default_cert_dir();

        {
            STACK_OF(OPENSSL_STRING) *uris = X509_LOOKUP_get_method_data(ctx);

            if (uris == NULL) {
                uris = sk_OPENSSL_STRING_new_null();
                X509_LOOKUP_set_method_data(ctx, uris);
            }
            return sk_OPENSSL_STRING_push(uris, OPENSSL_strdup(argp)) > 0;
        }
    case X509_L_LOAD_STORE:
        /* This is a shortcut for quick loading of specific containers */
        return cache_objects(ctx, argp, NULL, 0);
    }

    return 0;
}

static int by_store(X509_LOOKUP *ctx, X509_LOOKUP_TYPE type,
                    const OSSL_STORE_SEARCH *criterion, X509_OBJECT *ret)
{
    STACK_OF(OPENSSL_STRING) *uris = X509_LOOKUP_get_method_data(ctx);
    int i;
    int ok = 0;

    for (i = 0; i < sk_OPENSSL_STRING_num(uris); i++) {
        ok = cache_objects(ctx, sk_OPENSSL_STRING_value(uris, i), criterion,
                           1 /* depth */);

        if (ok)
            break;
    }
    return ok;
}

static int by_store_subject(X509_LOOKUP *ctx, X509_LOOKUP_TYPE type,
                            const X509_NAME *name, X509_OBJECT *ret)
{
    OSSL_STORE_SEARCH *criterion =
        OSSL_STORE_SEARCH_by_name((X509_NAME *)name); /* won't modify it */
    int ok = by_store(ctx, type, criterion, ret);
    STACK_OF(X509_OBJECT) *store_objects =
        X509_STORE_get0_objects(X509_LOOKUP_get_store(ctx));
    X509_OBJECT *tmp = NULL;

    OSSL_STORE_SEARCH_free(criterion);

    if (ok)
        tmp = X509_OBJECT_retrieve_by_subject(store_objects, type, name);

    ok = 0;
    if (tmp != NULL) {
        /*
         * This could also be done like this:
         *
         *     if (tmp != NULL) {
         *         *ret = *tmp;
         *         ok = 1;
         *     }
         *
         * However, we want to exercise the documented API to the max, so
         * we do it the hard way.
         *
         * To be noted is that X509_OBJECT_set1_* increment the refcount,
         * but so does X509_STORE_CTX_get_by_subject upon return of this
         * function, so we must ensure the the refcount is decremented
         * before we return, or we will get a refcount leak.  We cannot do
         * this with X509_OBJECT_free(), though, as that will free a bit
         * too much.
         */
        switch (type) {
        case X509_LU_X509:
            ok = X509_OBJECT_set1_X509(ret, tmp->data.x509);
            if (ok)
                X509_free(tmp->data.x509);
            break;
        case X509_LU_CRL:
            ok = X509_OBJECT_set1_X509_CRL(ret, tmp->data.crl);
            if (ok)
                X509_CRL_free(tmp->data.crl);
            break;
        case X509_LU_NONE:
            break;
        }
    }
    return ok;
}

/*
 * We lack the implementations for get_by_issuer_serial, get_by_fingerprint
 * and get_by_alias.  There's simply not enough support in the X509_LOOKUP
 * or X509_STORE APIs.
 */

static X509_LOOKUP_METHOD x509_store_lookup = {
    "Load certs from STORE URIs",
    NULL,                        /* new_item */
    by_store_free,               /* free */
    NULL,                        /* init */
    NULL,                        /* shutdown */
    by_store_ctrl,               /* ctrl */
    by_store_subject,            /* get_by_subject */
    NULL,                        /* get_by_issuer_serial */
    NULL,                        /* get_by_fingerprint */
    NULL,                        /* get_by_alias */
};

X509_LOOKUP_METHOD *X509_LOOKUP_store(void)
{
    return &x509_store_lookup;
}