1 /* $OpenBSD: by_dir.c,v 1.44 2023/02/16 08:38:17 tb Exp $ */ 2 /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) 3 * All rights reserved. 4 * 5 * This package is an SSL implementation written 6 * by Eric Young (eay@cryptsoft.com). 7 * The implementation was written so as to conform with Netscapes SSL. 8 * 9 * This library is free for commercial and non-commercial use as long as 10 * the following conditions are aheared to. The following conditions 11 * apply to all code found in this distribution, be it the RC4, RSA, 12 * lhash, DES, etc., code; not just the SSL code. The SSL documentation 13 * included with this distribution is covered by the same copyright terms 14 * except that the holder is Tim Hudson (tjh@cryptsoft.com). 15 * 16 * Copyright remains Eric Young's, and as such any Copyright notices in 17 * the code are not to be removed. 18 * If this package is used in a product, Eric Young should be given attribution 19 * as the author of the parts of the library used. 20 * This can be in the form of a textual message at program startup or 21 * in documentation (online or textual) provided with the package. 22 * 23 * Redistribution and use in source and binary forms, with or without 24 * modification, are permitted provided that the following conditions 25 * are met: 26 * 1. Redistributions of source code must retain the copyright 27 * notice, this list of conditions and the following disclaimer. 28 * 2. Redistributions in binary form must reproduce the above copyright 29 * notice, this list of conditions and the following disclaimer in the 30 * documentation and/or other materials provided with the distribution. 31 * 3. All advertising materials mentioning features or use of this software 32 * must display the following acknowledgement: 33 * "This product includes cryptographic software written by 34 * Eric Young (eay@cryptsoft.com)" 35 * The word 'cryptographic' can be left out if the rouines from the library 36 * being used are not cryptographic related :-). 37 * 4. If you include any Windows specific code (or a derivative thereof) from 38 * the apps directory (application code) you must include an acknowledgement: 39 * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" 40 * 41 * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND 42 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 43 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 44 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 45 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 46 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 47 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 48 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 49 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 50 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 51 * SUCH DAMAGE. 52 * 53 * The licence and distribution terms for any publically available version or 54 * derivative of this code cannot be changed. i.e. this code cannot simply be 55 * copied and put under another distribution licence 56 * [including the GNU Public Licence.] 57 */ 58 59 #include <sys/stat.h> 60 #include <sys/types.h> 61 62 #include <errno.h> 63 #include <stdio.h> 64 #include <string.h> 65 #include <time.h> 66 #include <unistd.h> 67 68 #include <openssl/opensslconf.h> 69 70 #include <openssl/err.h> 71 #include <openssl/x509.h> 72 73 #include "x509_local.h" 74 75 typedef struct lookup_dir_hashes_st { 76 unsigned long hash; 77 int suffix; 78 } BY_DIR_HASH; 79 80 typedef struct lookup_dir_entry_st { 81 char *dir; 82 int dir_type; 83 STACK_OF(BY_DIR_HASH) *hashes; 84 } BY_DIR_ENTRY; 85 86 typedef struct lookup_dir_st { 87 BUF_MEM *buffer; 88 STACK_OF(BY_DIR_ENTRY) *dirs; 89 } BY_DIR; 90 91 DECLARE_STACK_OF(BY_DIR_HASH) 92 DECLARE_STACK_OF(BY_DIR_ENTRY) 93 94 static int dir_ctrl(X509_LOOKUP *ctx, int cmd, const char *argp, long argl, 95 char **ret); 96 static int new_dir(X509_LOOKUP *lu); 97 static void free_dir(X509_LOOKUP *lu); 98 static int add_cert_dir(BY_DIR *ctx, const char *dir, int type); 99 static int get_cert_by_subject(X509_LOOKUP *xl, int type, X509_NAME *name, 100 X509_OBJECT *ret); 101 102 static X509_LOOKUP_METHOD x509_dir_lookup = { 103 .name = "Load certs from files in a directory", 104 .new_item = new_dir, 105 .free = free_dir, 106 .init = NULL, 107 .shutdown = NULL, 108 .ctrl = dir_ctrl, 109 .get_by_subject = get_cert_by_subject, 110 .get_by_issuer_serial = NULL, 111 .get_by_fingerprint = NULL, 112 .get_by_alias = NULL, 113 }; 114 115 X509_LOOKUP_METHOD * 116 X509_LOOKUP_hash_dir(void) 117 { 118 return &x509_dir_lookup; 119 } 120 LCRYPTO_ALIAS(X509_LOOKUP_hash_dir); 121 122 static int 123 dir_ctrl(X509_LOOKUP *ctx, int cmd, const char *argp, long argl, 124 char **retp) 125 { 126 int ret = 0; 127 BY_DIR *ld; 128 129 ld = (BY_DIR *)ctx->method_data; 130 131 switch (cmd) { 132 case X509_L_ADD_DIR: 133 if (argl == X509_FILETYPE_DEFAULT) { 134 ret = add_cert_dir(ld, X509_get_default_cert_dir(), 135 X509_FILETYPE_PEM); 136 if (!ret) { 137 X509error(X509_R_LOADING_CERT_DIR); 138 } 139 } else 140 ret = add_cert_dir(ld, argp, (int)argl); 141 break; 142 } 143 return ret; 144 } 145 146 static int 147 new_dir(X509_LOOKUP *lu) 148 { 149 BY_DIR *a; 150 151 if ((a = malloc(sizeof(*a))) == NULL) { 152 X509error(ERR_R_MALLOC_FAILURE); 153 return 0; 154 } 155 if ((a->buffer = BUF_MEM_new()) == NULL) { 156 X509error(ERR_R_MALLOC_FAILURE); 157 free(a); 158 return 0; 159 } 160 a->dirs = NULL; 161 lu->method_data = (char *)a; 162 return 1; 163 } 164 165 static void 166 by_dir_hash_free(BY_DIR_HASH *hash) 167 { 168 free(hash); 169 } 170 171 static int 172 by_dir_hash_cmp(const BY_DIR_HASH * const *a, 173 const BY_DIR_HASH * const *b) 174 { 175 if ((*a)->hash > (*b)->hash) 176 return 1; 177 if ((*a)->hash < (*b)->hash) 178 return -1; 179 return 0; 180 } 181 182 static void 183 by_dir_entry_free(BY_DIR_ENTRY *ent) 184 { 185 free(ent->dir); 186 sk_BY_DIR_HASH_pop_free(ent->hashes, by_dir_hash_free); 187 free(ent); 188 } 189 190 static void 191 free_dir(X509_LOOKUP *lu) 192 { 193 BY_DIR *a; 194 195 a = (BY_DIR *)lu->method_data; 196 sk_BY_DIR_ENTRY_pop_free(a->dirs, by_dir_entry_free); 197 BUF_MEM_free(a->buffer); 198 free(a); 199 } 200 201 static int 202 add_cert_dir(BY_DIR *ctx, const char *dir, int type) 203 { 204 int j; 205 const char *s, *ss, *p; 206 ptrdiff_t len; 207 208 if (dir == NULL || !*dir) { 209 X509error(X509_R_INVALID_DIRECTORY); 210 return 0; 211 } 212 213 s = dir; 214 p = s; 215 do { 216 if ((*p == ':') || (*p == '\0')) { 217 BY_DIR_ENTRY *ent; 218 219 ss = s; 220 s = p + 1; 221 len = p - ss; 222 if (len == 0) 223 continue; 224 for (j = 0; j < sk_BY_DIR_ENTRY_num(ctx->dirs); j++) { 225 ent = sk_BY_DIR_ENTRY_value(ctx->dirs, j); 226 if (strlen(ent->dir) == (size_t)len && 227 strncmp(ent->dir, ss, (size_t)len) == 0) 228 break; 229 } 230 if (j < sk_BY_DIR_ENTRY_num(ctx->dirs)) 231 continue; 232 if (ctx->dirs == NULL) { 233 ctx->dirs = sk_BY_DIR_ENTRY_new_null(); 234 if (ctx->dirs == NULL) { 235 X509error(ERR_R_MALLOC_FAILURE); 236 return 0; 237 } 238 } 239 ent = malloc(sizeof(*ent)); 240 if (ent == NULL) { 241 X509error(ERR_R_MALLOC_FAILURE); 242 return 0; 243 } 244 ent->dir_type = type; 245 ent->hashes = sk_BY_DIR_HASH_new(by_dir_hash_cmp); 246 ent->dir = strndup(ss, (size_t)len); 247 if (ent->dir == NULL || ent->hashes == NULL) { 248 X509error(ERR_R_MALLOC_FAILURE); 249 by_dir_entry_free(ent); 250 return 0; 251 } 252 if (!sk_BY_DIR_ENTRY_push(ctx->dirs, ent)) { 253 X509error(ERR_R_MALLOC_FAILURE); 254 by_dir_entry_free(ent); 255 return 0; 256 } 257 } 258 } while (*p++ != '\0'); 259 return 1; 260 } 261 262 static int 263 get_cert_by_subject(X509_LOOKUP *xl, int type, X509_NAME *name, 264 X509_OBJECT *ret) 265 { 266 BY_DIR *ctx; 267 union { 268 struct { 269 X509 st_x509; 270 X509_CINF st_x509_cinf; 271 } x509; 272 struct { 273 X509_CRL st_crl; 274 X509_CRL_INFO st_crl_info; 275 } crl; 276 } data; 277 int ok = 0; 278 int i, j, k; 279 unsigned long h; 280 BUF_MEM *b = NULL; 281 X509_OBJECT stmp, *tmp; 282 const char *postfix=""; 283 284 if (name == NULL) 285 return 0; 286 287 stmp.type = type; 288 if (type == X509_LU_X509) { 289 data.x509.st_x509.cert_info = &data.x509.st_x509_cinf; 290 data.x509.st_x509_cinf.subject = name; 291 stmp.data.x509 = &data.x509.st_x509; 292 postfix=""; 293 } else if (type == X509_LU_CRL) { 294 data.crl.st_crl.crl = &data.crl.st_crl_info; 295 data.crl.st_crl_info.issuer = name; 296 stmp.data.crl = &data.crl.st_crl; 297 postfix="r"; 298 } else { 299 X509error(X509_R_WRONG_LOOKUP_TYPE); 300 goto finish; 301 } 302 303 if ((b = BUF_MEM_new()) == NULL) { 304 X509error(ERR_R_BUF_LIB); 305 goto finish; 306 } 307 308 ctx = (BY_DIR *)xl->method_data; 309 310 h = X509_NAME_hash(name); 311 for (i = 0; i < sk_BY_DIR_ENTRY_num(ctx->dirs); i++) { 312 BY_DIR_ENTRY *ent; 313 int idx; 314 BY_DIR_HASH htmp, *hent; 315 316 ent = sk_BY_DIR_ENTRY_value(ctx->dirs, i); 317 j = strlen(ent->dir) + 1 + 8 + 6 + 1 + 1; 318 if (!BUF_MEM_grow(b, j)) { 319 X509error(ERR_R_MALLOC_FAILURE); 320 goto finish; 321 } 322 if (type == X509_LU_CRL) { 323 htmp.hash = h; 324 CRYPTO_r_lock(CRYPTO_LOCK_X509_STORE); 325 idx = sk_BY_DIR_HASH_find(ent->hashes, &htmp); 326 if (idx >= 0) { 327 hent = sk_BY_DIR_HASH_value(ent->hashes, idx); 328 k = hent->suffix; 329 } else { 330 hent = NULL; 331 k = 0; 332 } 333 CRYPTO_r_unlock(CRYPTO_LOCK_X509_STORE); 334 } else { 335 k = 0; 336 hent = NULL; 337 } 338 for (;;) { 339 (void) snprintf(b->data, b->max, "%s/%08lx.%s%d", 340 ent->dir, h, postfix, k); 341 342 { 343 struct stat st; 344 if (stat(b->data, &st) < 0) 345 break; 346 } 347 /* found one. */ 348 if (type == X509_LU_X509) { 349 if ((X509_load_cert_file(xl, b->data, 350 ent->dir_type)) == 0) 351 break; 352 } else if (type == X509_LU_CRL) { 353 if ((X509_load_crl_file(xl, b->data, 354 ent->dir_type)) == 0) 355 break; 356 } 357 /* else case will caught higher up */ 358 k++; 359 } 360 361 /* we have added it to the cache so now pull it out again */ 362 CRYPTO_w_lock(CRYPTO_LOCK_X509_STORE); 363 j = sk_X509_OBJECT_find(xl->store_ctx->objs, &stmp); 364 tmp = sk_X509_OBJECT_value(xl->store_ctx->objs, j); 365 CRYPTO_w_unlock(CRYPTO_LOCK_X509_STORE); 366 367 /* If a CRL, update the last file suffix added for this */ 368 if (type == X509_LU_CRL) { 369 CRYPTO_w_lock(CRYPTO_LOCK_X509_STORE); 370 /* 371 * Look for entry again in case another thread added 372 * an entry first. 373 */ 374 if (hent == NULL) { 375 htmp.hash = h; 376 idx = sk_BY_DIR_HASH_find(ent->hashes, &htmp); 377 hent = sk_BY_DIR_HASH_value(ent->hashes, idx); 378 } 379 if (hent == NULL) { 380 hent = malloc(sizeof(*hent)); 381 if (hent == NULL) { 382 X509error(ERR_R_MALLOC_FAILURE); 383 CRYPTO_w_unlock(CRYPTO_LOCK_X509_STORE); 384 ok = 0; 385 goto finish; 386 } 387 hent->hash = h; 388 hent->suffix = k; 389 if (!sk_BY_DIR_HASH_push(ent->hashes, hent)) { 390 X509error(ERR_R_MALLOC_FAILURE); 391 CRYPTO_w_unlock(CRYPTO_LOCK_X509_STORE); 392 free(hent); 393 ok = 0; 394 goto finish; 395 } 396 } else if (hent->suffix < k) 397 hent->suffix = k; 398 399 CRYPTO_w_unlock(CRYPTO_LOCK_X509_STORE); 400 401 } 402 403 if (tmp != NULL) { 404 ok = 1; 405 ret->type = tmp->type; 406 memcpy(&ret->data, &tmp->data, sizeof(ret->data)); 407 goto finish; 408 } 409 } 410 finish: 411 BUF_MEM_free(b); 412 return ok; 413 } 414