diff options
author | Richard Levitte <levitte@openssl.org> | 2020-07-23 16:56:59 +0200 |
---|---|---|
committer | Richard Levitte <levitte@openssl.org> | 2020-09-03 17:48:32 +0200 |
commit | 16feca71544681cabf873fecd3f860f9853bdf07 (patch) | |
tree | a1dce6397911d95de73f10208b65dbba04526ac6 /engines | |
parent | OSSL_ENCODER / OSSL_DECODER post-rename cleanup (diff) | |
download | openssl-16feca71544681cabf873fecd3f860f9853bdf07.tar.xz openssl-16feca71544681cabf873fecd3f860f9853bdf07.zip |
STORE: Move the built-in 'file:' loader to become an engine module
From this point on, this engine must be specifically specified.
To replace the internal EMBEDDED hack with something unique for the
new module, functions to create application specific OSSL_STORE_INFO
types were added.
Furthermore, the following function had to be exported:
ossl_do_blob_header()
ossl_do_PVK_header()
asn1_d2i_read_bio()
Finally, evp_pkcs82pkey_int() has become public under a new name,
EVP_PKCS82PKEY_with_libctx()
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/12587)
Diffstat (limited to 'engines')
-rw-r--r-- | engines/build.info | 10 | ||||
-rw-r--r-- | engines/e_loader_attic.c | 1790 | ||||
-rw-r--r-- | engines/e_loader_attic.ec | 3 | ||||
-rw-r--r-- | engines/e_loader_attic.txt | 23 | ||||
-rw-r--r-- | engines/e_loader_attic_err.c | 73 | ||||
-rw-r--r-- | engines/e_loader_attic_err.h | 43 |
6 files changed, 1941 insertions, 1 deletions
diff --git a/engines/build.info b/engines/build.info index 3bfe1dc057..4e83dbf9bc 100644 --- a/engines/build.info +++ b/engines/build.info @@ -70,7 +70,7 @@ IF[{- !$disabled{"engine"} -}] ENDIF ENDIF - MODULES{noinst,engine}=ossltest dasync + MODULES{noinst,engine}=ossltest dasync loader_attic SOURCE[dasync]=e_dasync.c DEPEND[dasync]=../libcrypto INCLUDE[dasync]=../include @@ -86,6 +86,14 @@ IF[{- !$disabled{"engine"} -}] SOURCE[ossltest]=ossltest.ld GENERATE[ossltest.ld]=../util/engines.num ENDIF + + SOURCE[loader_attic]=e_loader_attic.c + DEPEND[loader_attic]=../libcrypto + INCLUDE[loader_attic]=../include + IF[{- defined $target{shared_defflag} -}] + SOURCE[loader_attic]=loader_attic.ld + GENERATE[loader_attic.ld]=../util/engines.num + ENDIF ENDIF GENERATE[e_padlock-x86.s]=asm/e_padlock-x86.pl GENERATE[e_padlock-x86_64.s]=asm/e_padlock-x86_64.pl diff --git a/engines/e_loader_attic.c b/engines/e_loader_attic.c new file mode 100644 index 0000000000..581bfb0285 --- /dev/null +++ b/engines/e_loader_attic.c @@ -0,0 +1,1790 @@ +/* + * Copyright 2016-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 + */ + +/* THIS ENGINE IS FOR TESTING PURPOSES ONLY. */ + +/* We need to use some engine deprecated APIs */ +#define OPENSSL_SUPPRESS_DEPRECATED + +/* #include "e_os.h" */ +#include <string.h> +#include <sys/stat.h> +#include <ctype.h> +#include <assert.h> + +#include <openssl/bio.h> +#include <openssl/dsa.h> /* For d2i_DSAPrivateKey */ +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/pkcs12.h> /* For the PKCS8 stuff o.O */ +#include <openssl/rsa.h> /* For d2i_RSAPrivateKey */ +#include <openssl/safestack.h> +#include <openssl/store.h> +#include <openssl/ui.h> +#include <openssl/engine.h> +#include <openssl/x509.h> /* For the PKCS8 stuff o.O */ +#include "internal/asn1.h" /* For asn1_d2i_read_bio */ +#include "internal/pem.h" /* For PVK and "blob" PEM headers */ +#include "internal/o_dir.h" +#include "internal/cryptlib.h" + +#include "e_loader_attic_err.c" + +DEFINE_STACK_OF(X509) +DEFINE_STACK_OF(OSSL_STORE_INFO) + +#ifdef _WIN32 +# define stat _stat +# define strncasecmp _strnicmp +#endif + +#ifndef S_ISDIR +# define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR) +#endif + +/*- + * Password prompting + * ------------------ + */ + +static char *file_get_pass(const UI_METHOD *ui_method, char *pass, + size_t maxsize, const char *desc, const char *info, + void *data) +{ + UI *ui = UI_new(); + char *prompt = NULL; + + if (ui == NULL) { + ATTICerr(0, ERR_R_MALLOC_FAILURE); + return NULL; + } + + if (ui_method != NULL) + UI_set_method(ui, ui_method); + UI_add_user_data(ui, data); + + if ((prompt = UI_construct_prompt(ui, desc, info)) == NULL) { + ATTICerr(0, ERR_R_MALLOC_FAILURE); + pass = NULL; + } else if (!UI_add_input_string(ui, prompt, UI_INPUT_FLAG_DEFAULT_PWD, + pass, 0, maxsize - 1)) { + ATTICerr(0, ERR_R_UI_LIB); + pass = NULL; + } else { + switch (UI_process(ui)) { + case -2: + ATTICerr(0, ATTIC_R_UI_PROCESS_INTERRUPTED_OR_CANCELLED); + pass = NULL; + break; + case -1: + ATTICerr(0, ERR_R_UI_LIB); + pass = NULL; + break; + default: + break; + } + } + + OPENSSL_free(prompt); + UI_free(ui); + return pass; +} + +struct pem_pass_data { + const UI_METHOD *ui_method; + void *data; + const char *prompt_desc; + const char *prompt_info; +}; + +static int file_fill_pem_pass_data(struct pem_pass_data *pass_data, + const char *desc, const char *info, + const UI_METHOD *ui_method, void *ui_data) +{ + if (pass_data == NULL) + return 0; + pass_data->ui_method = ui_method; + pass_data->data = ui_data; + pass_data->prompt_desc = desc; + pass_data->prompt_info = info; + return 1; +} + +/* This is used anywhere a pem_password_cb is needed */ +static int file_get_pem_pass(char *buf, int num, int w, void *data) +{ + struct pem_pass_data *pass_data = data; + char *pass = file_get_pass(pass_data->ui_method, buf, num, + pass_data->prompt_desc, pass_data->prompt_info, + pass_data->data); + + return pass == NULL ? 0 : strlen(pass); +} + +/* + * Check if |str| ends with |suffix| preceded by a space, and if it does, + * return the index of that space. If there is no such suffix in |str|, + * return -1. + * For |str| == "FOO BAR" and |suffix| == "BAR", the returned value is 3. + */ +static int check_suffix(const char *str, const char *suffix) +{ + int str_len = strlen(str); + int suffix_len = strlen(suffix) + 1; + const char *p = NULL; + + if (suffix_len >= str_len) + return -1; + p = str + str_len - suffix_len; + if (*p != ' ' + || strcmp(p + 1, suffix) != 0) + return -1; + return p - str; +} + +/* + * EMBEDDED is a special type of OSSL_STORE_INFO, specially for the file + * handlers, so we define it internally. This uses the possibility to + * create an OSSL_STORE_INFO with a generic data pointer and arbitrary + * type number. + * + * This is used by a FILE_HANDLER's try_decode function to signal that it + * has decoded the incoming blob into a new blob, and that the attempted + * decoding should be immediately restarted with the new blob, using the + * new PEM name. + */ +/* Negative numbers are never used for public OSSL_STORE_INFO types */ +#define STORE_INFO_EMBEDDED -1 + +/* This is the embedded data */ +struct embedded_st { + BUF_MEM *blob; + char *pem_name; +}; + +/* Helper functions */ +static struct embedded_st *get0_EMBEDDED(OSSL_STORE_INFO *info) +{ + return OSSL_STORE_INFO_get0_data(STORE_INFO_EMBEDDED, info); +} + +static void store_info_free(OSSL_STORE_INFO *info) +{ + struct embedded_st *data; + + if (info != NULL && (data = get0_EMBEDDED(info)) != NULL) { + BUF_MEM_free(data->blob); + OPENSSL_free(data->pem_name); + OPENSSL_free(data); + } + OSSL_STORE_INFO_free(info); +} + +static OSSL_STORE_INFO *new_EMBEDDED(const char *new_pem_name, + BUF_MEM *embedded) +{ + OSSL_STORE_INFO *info = NULL; + struct embedded_st *data = NULL; + + if ((data = OPENSSL_zalloc(sizeof(*data))) == NULL + || (info = OSSL_STORE_INFO_new(STORE_INFO_EMBEDDED, data)) == NULL) { + ATTICerr(0, ERR_R_MALLOC_FAILURE); + OPENSSL_free(data); + return NULL; + } + + data->pem_name = + new_pem_name == NULL ? NULL : OPENSSL_strdup(new_pem_name); + + if (new_pem_name != NULL && data->pem_name == NULL) { + ATTICerr(0, ERR_R_MALLOC_FAILURE); + store_info_free(info); + info = NULL; + } + data->blob = embedded; + + return info; +} + +/*- + * The file scheme decoders + * ------------------------ + * + * Each possible data type has its own decoder, which either operates + * through a given PEM name, or attempts to decode to see if the blob + * it's given is decodable for its data type. The assumption is that + * only the correct data type will match the content. + */ + +/*- + * The try_decode function is called to check if the blob of data can + * be used by this handler, and if it can, decodes it into a supported + * OpenSSL type and returns a OSSL_STORE_INFO with the decoded data. + * Input: + * pem_name: If this blob comes from a PEM file, this holds + * the PEM name. If it comes from another type of + * file, this is NULL. + * pem_header: If this blob comes from a PEM file, this holds + * the PEM headers. If it comes from another type of + * file, this is NULL. + * blob: The blob of data to match with what this handler + * can use. + * len: The length of the blob. + * handler_ctx: For a handler marked repeatable, this pointer can + * be used to create a context for the handler. IT IS + * THE HANDLER'S RESPONSIBILITY TO CREATE AND DESTROY + * THIS CONTEXT APPROPRIATELY, i.e. create on first call + * and destroy when about to return NULL. + * matchcount: A pointer to an int to count matches for this data. + * Usually becomes 0 (no match) or 1 (match!), but may + * be higher in the (unlikely) event that the data matches + * more than one possibility. The int will always be + * zero when the function is called. + * ui_method: Application UI method for getting a password, pin + * or any other interactive data. + * ui_data: Application data to be passed to ui_method when + * it's called. + * libctx: The library context to be used if applicable + * propq: The property query string for any algorithm fetches + * Output: + * a OSSL_STORE_INFO + */ +typedef OSSL_STORE_INFO *(*file_try_decode_fn)(const char *pem_name, + const char *pem_header, + const unsigned char *blob, + size_t len, void **handler_ctx, + int *matchcount, + const UI_METHOD *ui_method, + void *ui_data, const char *uri, + OPENSSL_CTX *libctx, + const char *propq); +/* + * The eof function should return 1 if there's no more data to be found + * with the handler_ctx, otherwise 0. This is only used when the handler is + * marked repeatable. + */ +typedef int (*file_eof_fn)(void *handler_ctx); +/* + * The destroy_ctx function is used to destroy the handler_ctx that was + * initiated by a repeatable try_decode function. This is only used when + * the handler is marked repeatable. + */ +typedef void (*file_destroy_ctx_fn)(void **handler_ctx); + +typedef struct file_handler_st { + const char *name; + file_try_decode_fn try_decode; + file_eof_fn eof; + file_destroy_ctx_fn destroy_ctx; + + /* flags */ + int repeatable; +} FILE_HANDLER; + +/* + * PKCS#12 decoder. It operates by decoding all of the blob content, + * extracting all the interesting data from it and storing them internally, + * then serving them one piece at a time. + */ +static OSSL_STORE_INFO *try_decode_PKCS12(const char *pem_name, + const char *pem_header, + const unsigned char *blob, + size_t len, void **pctx, + int *matchcount, + const UI_METHOD *ui_method, + void *ui_data, const char *uri, + OPENSSL_CTX *libctx, + const char *propq) +{ + OSSL_STORE_INFO *store_info = NULL; + STACK_OF(OSSL_STORE_INFO) *ctx = *pctx; + + if (ctx == NULL) { + /* Initial parsing */ + PKCS12 *p12; + + if (pem_name != NULL) + /* No match, there is no PEM PKCS12 tag */ + return NULL; + + if ((p12 = d2i_PKCS12(NULL, &blob, len)) != NULL) { + char *pass = NULL; + char tpass[PEM_BUFSIZE]; + EVP_PKEY *pkey = NULL; + X509 *cert = NULL; + STACK_OF(X509) *chain = NULL; + + *matchcount = 1; + + if (PKCS12_verify_mac(p12, "", 0) + || PKCS12_verify_mac(p12, NULL, 0)) { + pass = ""; + } else { + if ((pass = file_get_pass(ui_method, tpass, PEM_BUFSIZE, + "PKCS12 import pass phrase", uri, + ui_data)) == NULL) { + ATTICerr(0, ATTIC_R_PASSPHRASE_CALLBACK_ERROR); + goto p12_end; + } + if (!PKCS12_verify_mac(p12, pass, strlen(pass))) { + ATTICerr(0, ATTIC_R_ERROR_VERIFYING_PKCS12_MAC); + goto p12_end; + } + } + + if (PKCS12_parse(p12, pass, &pkey, &cert, &chain)) { + OSSL_STORE_INFO *osi_pkey = NULL; + OSSL_STORE_INFO *osi_cert = NULL; + OSSL_STORE_INFO *osi_ca = NULL; + int ok = 1; + + if ((ctx = sk_OSSL_STORE_INFO_new_null()) != NULL) { + if (pkey != NULL) { + if ((osi_pkey = OSSL_STORE_INFO_new_PKEY(pkey)) != NULL + /* clearing pkey here avoids case distinctions */ + && (pkey = NULL) == NULL + && sk_OSSL_STORE_INFO_push(ctx, osi_pkey) != 0) + osi_pkey = NULL; + else + ok = 0; + } + if (ok && cert != NULL) { + if ((osi_cert = OSSL_STORE_INFO_new_CERT(cert)) != NULL + /* clearing cert here avoids case distinctions */ + && (cert = NULL) == NULL + && sk_OSSL_STORE_INFO_push(ctx, osi_cert) != 0) + osi_cert = NULL; + else + ok = 0; + } + while (ok && sk_X509_num(chain) > 0) { + X509 *ca = sk_X509_value(chain, 0); + + if ((osi_ca = OSSL_STORE_INFO_new_CERT(ca)) != NULL + && sk_X509_shift(chain) != NULL + && sk_OSSL_STORE_INFO_push(ctx, osi_ca) != 0) + osi_ca = NULL; + else + ok = 0; + } + } + EVP_PKEY_free(pkey); + X509_free(cert); + sk_X509_pop_free(chain, X509_free); + store_info_free(osi_pkey); + store_info_free(osi_cert); + store_info_free(osi_ca); + if (!ok) { + sk_OSSL_STORE_INFO_pop_free(ctx, store_info_free); + ctx = NULL; + } + *pctx = ctx; + } + } + p12_end: + PKCS12_free(p12); + if (ctx == NULL) + return NULL; + } + + *matchcount = 1; + store_info = sk_OSSL_STORE_INFO_shift(ctx); + return store_info; +} + +static int eof_PKCS12(void *ctx_) +{ + STACK_OF(OSSL_STORE_INFO) *ctx = ctx_; + + return ctx == NULL || sk_OSSL_STORE_INFO_num(ctx) == 0; +} + +static void destroy_ctx_PKCS12(void **pctx) +{ + STACK_OF(OSSL_STORE_INFO) *ctx = *pctx; + + sk_OSSL_STORE_INFO_pop_free(ctx, store_info_free); + *pctx = NULL; +} + +static FILE_HANDLER PKCS12_handler = { + "PKCS12", + try_decode_PKCS12, + eof_PKCS12, + destroy_ctx_PKCS12, + 1 /* repeatable */ +}; + +/* + * Encrypted PKCS#8 decoder. It operates by just decrypting the given blob + * into a new blob, which is returned as an EMBEDDED STORE_INFO. The whole + * decoding process will then start over with the new blob. + */ +static OSSL_STORE_INFO *try_decode_PKCS8Encrypted(const char *pem_name, + const char *pem_header, + const unsigned char *blob, + size_t len, void **pctx, + int *matchcount, + const UI_METHOD *ui_method, + void *ui_data, + const char *uri, + OPENSSL_CTX *libctx, + const char *propq) +{ + X509_SIG *p8 = NULL; + char kbuf[PEM_BUFSIZE]; + char *pass = NULL; + const X509_ALGOR *dalg = NULL; + const ASN1_OCTET_STRING *doct = NULL; + OSSL_STORE_INFO *store_info = NULL; + BUF_MEM *mem = NULL; + unsigned char *new_data = NULL; + int new_data_len; + + if (pem_name != NULL) { + if (strcmp(pem_name, PEM_STRING_PKCS8) != 0) + return NULL; + *matchcount = 1; + } + + if ((p8 = d2i_X509_SIG(NULL, &blob, len)) == NULL) + return NULL; + + *matchcount = 1; + + if ((mem = BUF_MEM_new()) == NULL) { + ATTICerr(0, ERR_R_MALLOC_FAILURE); + goto nop8; + } + + if ((pass = file_get_pass(ui_method, kbuf, PEM_BUFSIZE, + "PKCS8 decrypt pass phrase", uri, + ui_data)) == NULL) { + ATTICerr(0, ATTIC_R_BAD_PASSWORD_READ); + goto nop8; + } + + X509_SIG_get0(p8, &dalg, &doct); + if (!PKCS12_pbe_crypt(dalg, pass, strlen(pass), doct->data, doct->length, + &new_data, &new_data_len, 0)) + goto nop8; + + mem->data = (char *)new_data; + mem->max = mem->length = (size_t)new_data_len; + X509_SIG_free(p8); + + store_info = new_EMBEDDED(PEM_STRING_PKCS8INF, mem); + if (store_info == NULL) { + ATTICerr(0, ERR_R_MALLOC_FAILURE); + goto nop8; + } + + return store_info; + nop8: + X509_SIG_free(p8); + BUF_MEM_free(mem); + return NULL; +} + +static FILE_HANDLER PKCS8Encrypted_handler = { + "PKCS8Encrypted", + try_decode_PKCS8Encrypted +}; + +/* + * Private key decoder. Decodes all sorts of private keys, both PKCS#8 + * encoded ones and old style PEM ones (with the key type is encoded into + * the PEM name). + */ +static OSSL_STORE_INFO *try_decode_PrivateKey(const char *pem_name, + const char *pem_header, + const unsigned char *blob, + size_t len, void **pctx, + int *matchcount, + const UI_METHOD *ui_method, + void *ui_data, const char *uri, + OPENSSL_CTX *libctx, + const char *propq) +{ + OSSL_STORE_INFO *store_info = NULL; + EVP_PKEY *pkey = NULL; + const EVP_PKEY_ASN1_METHOD *ameth = NULL; + + if (pem_name != NULL) { + if (strcmp(pem_name, PEM_STRING_PKCS8INF) == 0) { + PKCS8_PRIV_KEY_INFO *p8inf = + d2i_PKCS8_PRIV_KEY_INFO(NULL, &blob, len); + + *matchcount = 1; + if (p8inf != NULL) + pkey = EVP_PKCS82PKEY_with_libctx(p8inf, libctx, propq); + PKCS8_PRIV_KEY_INFO_free(p8inf); + } else { + int slen; + int pkey_id; + + if ((slen = check_suffix(pem_name, "PRIVATE KEY")) > 0 + && (ameth = EVP_PKEY_asn1_find_str(NULL, pem_name, + slen)) != NULL + && EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, + ameth)) { + *matchcount = 1; + pkey = d2i_PrivateKey_ex(pkey_id, NULL, &blob, len, + libctx, propq); + } + } + } else { + int i; +#ifndef OPENSSL_NO_ENGINE + ENGINE *curengine = ENGINE_get_first(); + + while (curengine != NULL) { + ENGINE_PKEY_ASN1_METHS_PTR asn1meths = + ENGINE_get_pkey_asn1_meths(curengine); + + if (asn1meths != NULL) { + const int *nids = NULL; + int nids_n = asn1meths(curengine, NULL, &nids, 0); + + for (i = 0; i < nids_n; i++) { + EVP_PKEY_ASN1_METHOD *ameth2 = NULL; + EVP_PKEY *tmp_pkey = NULL; + const unsigned char *tmp_blob = blob; + int pkey_id, pkey_flags; + + if (!asn1meths(curengine, &ameth2, NULL, nids[i]) + || !EVP_PKEY_asn1_get0_info(&pkey_id, NULL, + &pkey_flags, NULL, NULL, + ameth2) + || (pkey_flags & ASN1_PKEY_ALIAS) != 0) + continue; + + ERR_set_mark(); /* prevent flooding error queue */ + tmp_pkey = d2i_PrivateKey_ex(pkey_id, NULL, + &tmp_blob, len, + libctx, propq); + if (tmp_pkey != NULL) { + if (pkey != NULL) + EVP_PKEY_free(tmp_pkey); + else + pkey = tmp_pkey; + (*matchcount)++; + } + ERR_pop_to_mark(); + } + } + curengine = ENGINE_get_next(curengine); + } +#endif + + for (i = 0; i < EVP_PKEY_asn1_get_count(); i++) { + EVP_PKEY *tmp_pkey = NULL; + const unsigned char *tmp_blob = blob; + int pkey_id, pkey_flags; + + ameth = EVP_PKEY_asn1_get0(i); + if (!EVP_PKEY_asn1_get0_info(&pkey_id, NULL, &pkey_flags, NULL, + NULL, ameth) + || (pkey_flags & ASN1_PKEY_ALIAS) != 0) + continue; + + ERR_set_mark(); /* prevent flooding error queue */ + tmp_pkey = d2i_PrivateKey_ex(pkey_id, NULL, &tmp_blob, len, + libctx, propq); + if (tmp_pkey != NULL) { + if (pkey != NULL) + EVP_PKEY_free(tmp_pkey); + else + pkey = tmp_pkey; + (*matchcount)++; + } + ERR_pop_to_mark(); + } + + if (*matchcount > 1) { + EVP_PKEY_free(pkey); + pkey = NULL; + } + } + if (pkey == NULL) + /* No match */ + return NULL; + + store_info = OSSL_STORE_INFO_new_PKEY(pkey); + if (store_info == NULL) + EVP_PKEY_free(pkey); + + return store_info; +} + +static FILE_HANDLER PrivateKey_handler = { + "PrivateKey", + try_decode_PrivateKey +}; + +/* + * Public key decoder. Only supports SubjectPublicKeyInfo formatted keys. + */ +static OSSL_STORE_INFO *try_decode_PUBKEY(const char *pem_name, + const char *pem_header, + const unsigned char *blob, + size_t len, void **pctx, + int *matchcount, + const UI_METHOD *ui_method, + void *ui_data, const char *uri, + OPENSSL_CTX *libctx, + const char *propq) +{ + OSSL_STORE_INFO *store_info = NULL; + EVP_PKEY *pkey = NULL; + + if (pem_name != NULL) { + if (strcmp(pem_name, PEM_STRING_PUBLIC) != 0) + /* No match */ + return NULL; + *matchcount = 1; + } + + if ((pkey = d2i_PUBKEY(NULL, &blob, len)) != NULL) { + *matchcount = 1; + store_info = OSSL_STORE_INFO_new_PUBKEY(pkey); + } + + return store_info; +} + +static FILE_HANDLER PUBKEY_handler = { + "PUBKEY", + try_decode_PUBKEY +}; + +/* + * Key parameter decoder. + */ +static OSSL_STORE_INFO *try_decode_params(const char *pem_name, + const char *pem_header, + const unsigned char *blob, + size_t len, void **pctx, + int *matchcount, + const UI_METHOD *ui_method, + void *ui_data, const char *uri, + OPENSSL_CTX *libctx, + const char *propq) +{ + OSSL_STORE_INFO *store_info = NULL; + EVP_PKEY *pkey = NULL; + const EVP_PKEY_ASN1_METHOD *ameth = NULL; + + if (pem_name != NULL) { + int slen; + int pkey_id; + + if ((slen = check_suffix(pem_name, "PARAMETERS")) > 0 + && (ameth = EVP_PKEY_asn1_find_str(NULL, pem_name, slen)) != NULL + && EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, + ameth)) { + *matchcount = 1; + pkey = d2i_KeyParams(pkey_id, NULL, &blob, len); + } + } else { + int i; + + for (i = 0; i < EVP_PKEY_asn1_get_count(); i++) { + EVP_PKEY *tmp_pkey = NULL; + const unsigned char *tmp_blob = blob; + int pkey_id, pkey_flags; + + ameth = EVP_PKEY_asn1_get0(i); + if (!EVP_PKEY_asn1_get0_info(&pkey_id, NULL, &pkey_flags, NULL, + NULL, ameth) + || (pkey_flags & ASN1_PKEY_ALIAS) != 0) + continue; + + ERR_set_mark(); /* prevent flooding error queue */ + + tmp_pkey = d2i_KeyParams(pkey_id, NULL, &tmp_blob, len); + + if (tmp_pkey != NULL) { + if (pkey != NULL) + EVP_PKEY_free(tmp_pkey); + else + pkey = tmp_pkey; + (*matchcount)++; + } + ERR_pop_to_mark(); + } + + if (*matchcount > 1) { + EVP_PKEY_free(pkey); + pkey = NULL; + } + } + if (pkey == NULL) + /* No match */ + return NULL; + + store_info = OSSL_STORE_INFO_new_PARAMS(pkey); + if (store_info == NULL) + EVP_PKEY_free(pkey); + + return store_info; +} + +static FILE_HANDLER params_handler = { + "params", + try_decode_params +}; + +/* + * X.509 certificate decoder. + */ +static OSSL_STORE_INFO *try_decode_X509Certificate(const char *pem_name, + const char *pem_header, + const unsigned char *blob, + size_t len, void **pctx, + int *matchcount, + const UI_METHOD *ui_method, + void *ui_data, + const char *uri, + OPENSSL_CTX *libctx, + const char *propq) +{ + OSSL_STORE_INFO *store_info = NULL; + X509 *cert = NULL; + + /* + * In most cases, we can try to interpret the serialized data as a trusted + * cert (X509 + X509_AUX) and fall back to reading it as a normal cert + * (just X509), but if the PEM name specifically declares it as a trusted + * cert, then no fallback should be engaged. |ignore_trusted| tells if + * the fallback can be used (1) or not (0). + */ + int ignore_trusted = 1; + + if (pem_name != NULL) { + if (strcmp(pem_name, PEM_STRING_X509_TRUSTED) == 0) + ignore_trusted = 0; + else if (strcmp(pem_name, PEM_STRING_X509_OLD) != 0 + && strcmp(pem_name, PEM_STRING_X509) != 0) + /* No match */ + return NULL; + *matchcount = 1; + } + + cert = X509_new_with_libctx(libctx, propq); + if (cert == NULL) + return NULL; + + if ((d2i_X509_AUX(&cert, &blob, len)) != NULL + || (ignore_trusted && (d2i_X509(&cert, &blob, len)) != NULL)) { + *matchcount = 1; + store_info = OSSL_STORE_INFO_new_CERT(cert); + } + + if (store_info == NULL) + X509_free(cert); + + return store_info; +} + +static FILE_HANDLER X509Certificate_handler = { + "X509Certificate", + try_decode_X509Certificate +}; + +/* + * X.509 CRL decoder. + */ +static OSSL_STORE_INFO *try_decode_X509CRL(const char *pem_name, + const char *pem_header, + const unsigned char *blob, + size_t len, void **pctx, + int *matchcount, + const UI_METHOD *ui_method, + void *ui_data, const char *uri, + OPENSSL_CTX *libctx, + const char *propq) +{ + OSSL_STORE_INFO *store_info = NULL; + X509_CRL *crl = NULL; + + if (pem_name != NULL) { + if (strcmp(pem_name, PEM_STRING_X509_CRL) != 0) + /* No match */ + return NULL; + *matchcount = 1; + } + + if ((crl = d2i_X509_CRL(NULL, &blob, len)) != NULL) { + *matchcount = 1; + store_info = OSSL_STORE_INFO_new_CRL(crl); + } + + if (store_info == NULL) + X509_CRL_free(crl); + + return store_info; +} + +static FILE_HANDLER X509CRL_handler = { + "X509CRL", + try_decode_X509CRL +}; + +/* + * To finish it all off, we collect all the handlers. + */ +static const FILE_HANDLER *file_handlers[] = { + &PKCS12_handler, + &PKCS8Encrypted_handler, + &X509Certificate_handler, + &X509CRL_handler, + ¶ms_handler, + &PUBKEY_handler, + &PrivateKey_handler, +}; + + +/*- + * The loader itself + * ----------------- + */ + +struct ossl_store_loader_ctx_st { + char *uri; /* The URI we currently try to load */ + enum { + is_raw = 0, + is_pem, + is_dir + } type; + int errcnt; +#define FILE_FLAG_SECMEM (1<<0) +#define FILE_FLAG_ATTACHED (1<<1) + unsigned int flags; + union { + struct { /* Used with is_raw and is_pem */ + BIO *file; + + /* + * The following are used when the handler is marked as + * repeatable + */ + const FILE_HANDLER *last_handler; + void *last_handler_ctx; + } file; + struct { /* Used with is_dir */ + OPENSSL_DIR_CTX *ctx; + int end_reached; + + /* + * When a search expression is given, these are filled in. + * |search_name| contains the file basename to look for. + * The string is exactly 8 characters long. + */ + char search_name[9]; + + /* + * The directory reading utility we have combines opening with + * reading the first name. To make sure we can detect the end + * at the right time, we read early and cache the name. + */ + const char *last_entry; + int last_errno; + } dir; + } _; + + /* Expected object type. May be unspecified */ + int expected_type; + + OPENSSL_CTX *libctx; + char *propq; +}; + +static void OSSL_STORE_LOADER_CTX_free(OSSL_STORE_LOADER_CTX *ctx) +{ + if (ctx == NULL) + return; + + OPENSSL_free(ctx->propq); + OPENSSL_free(ctx->uri); + if (ctx->type != is_dir) { + if (ctx->_.file.last_handler != NULL) { + ctx->_.file.last_handler->destroy_ctx(&ctx->_.file.last_handler_ctx); + ctx->_.file.last_handler_ctx = NULL; + ctx->_.file.last_handler = NULL; + } + } + OPENSSL_free(ctx); +} + +static int file_find_type(OSSL_STORE_LOADER_CTX *ctx) +{ + BIO *buff = NULL; + char peekbuf[4096] = { 0, }; + + if ((buff = BIO_new(BIO_f_buffer())) == NULL) + return 0; + + ctx->_.file.file = BIO_push(buff, ctx->_.file.file); + if (BIO_buffer_peek(ctx->_.file.file, peekbuf, sizeof(peekbuf) - 1) > 0) { + peekbuf[sizeof(peekbuf) - 1] = '\0'; + if (strstr(peekbuf, "-----BEGIN ") != NULL) + ctx->type = is_pem; + } + return 1; +} + +static OSSL_STORE_LOADER_CTX *file_open_with_libctx + (const OSSL_STORE_LOADER *loader, const char *uri, + OPENSSL_CTX *libctx, const char *propq, + const UI_METHOD *ui_method, void *ui_data) +{ + OSSL_STORE_LOADER_CTX *ctx = NULL; + struct stat st; + struct { + const char *path; + unsigned int check_absolute:1; + } path_data[2]; + size_t path_data_n = 0, i; + const char *path; + + /* + * First step, just take the URI as is. + */ + path_data[path_data_n].check_absolute = 0; + path_data[path_data_n++].path = uri; + + /* + * Second step, if the URI appears to start with the 'file' scheme, + * extract the path and make that the second path to check. + * There's a special case if the URI also contains an authority, then + * the full URI shouldn't be used as a path anywhere. + */ + if (strncasecmp(uri, "file:", 5) == 0) { + const char *p = &uri[5]; + + if (strncmp(&uri[5], "//", 2) == 0) { + path_data_n--; /* Invalidate using the full URI */ + if (strncasecmp(&uri[7], "localhost/", 10) == 0) { + p = &uri[16]; + } else if (uri[7] == '/') { + p = &uri[7]; + } else { + ATTICerr(0, ATTIC_R_URI_AUTHORITY_UNSUPPORTED); + return NULL; + } + } + + path_data[path_data_n].check_absolute = 1; +#ifdef _WIN32 + /* Windows file: URIs with a drive letter start with a / */ + if (p[0] == '/' && p[2] == ':' && p[3] == '/') { + char c = tolower(p[1]); + + if (c >= 'a' && c <= 'z') { + p++; + /* We know it's absolute, so no need to check */ + path_data[path_data_n].check_absolute = 0; + } + } +#endif + path_data[path_data_n++].path = p; + } + + + for (i = 0, path = NULL; path == NULL && i < path_data_n; i++) { + /* + * If the scheme "file" was an explicit part of the URI, the path must + * be absolute. So says RFC 8089 + */ + if (path_data[i].check_absolute && path_data[i].path[0] != '/') { + ATTICerr(0, ATTIC_R_PATH_MUST_BE_ABSOLUTE); + ERR_add_error_data(1, path_data[i].path); + return NULL; + } + + if (stat(path_data[i].path, &st) < 0) { + ERR_raise_data(ERR_LIB_SYS, errno, + "calling stat(%s)", + path_data[i].path); + } else { + path = path_data[i].path; + } + } + if (path == NULL) { + return NULL; + } + + /* Successfully found a working path */ + + ctx = OPENSSL_zalloc(sizeof(*ctx)); + if (ctx == NULL) { + ATTICerr(0, ERR_R_MALLOC_FAILURE); + return NULL; + } + ctx->uri = OPENSSL_strdup(uri); + if (ctx->uri == NULL) { + ATTICerr(0, ERR_R_MALLOC_FAILURE); + goto err; + } + + if (S_ISDIR(st.st_mode)) { + ctx->type = is_dir; + ctx->_.dir.last_entry = OPENSSL_DIR_read(&ctx->_.dir.ctx, path); + ctx->_.dir.last_errno = errno; + if (ctx->_.dir.last_entry == NULL) { + if (ctx->_.dir.last_errno != 0) { + ERR_raise(ERR_LIB_SYS, ctx->_.dir.last_errno); + goto err; + } + ctx->_.dir.end_reached = 1; + } + } else if ((ctx->_.file.file = BIO_new_file(path, "rb")) == NULL + || !file_find_type(ctx)) { + BIO_free_all(ctx->_.file.file); + goto err; + } + if (propq != NULL) { + ctx->propq = OPENSSL_strdup(propq); + if (ctx->propq == NULL) { + ATTICerr(0, ERR_R_MALLOC_FAILURE); + goto err; + } + } + ctx->libctx = libctx; + + return ctx; + err: + OSSL_STORE_LOADER_CTX_free(ctx); + return NULL; +} + +static OSSL_STORE_LOADER_CTX *file_open + (const OSSL_STORE_LOADER *loader, const char *uri, + const UI_METHOD *ui_method, void *ui_data) +{ + return file_open_with_libctx(loader, uri, NULL, NULL, ui_method, ui_data); +} + +static OSSL_STORE_LOADER_CTX *file_attach + (const OSSL_STORE_LOADER *loader, BIO *bp, + OPENSSL_CTX *libctx, const char *propq, + const UI_METHOD *ui_method, void *ui_data) +{ + OSSL_STORE_LOADER_CTX *ctx = NULL; + + if ((ctx = OPENSSL_zalloc(sizeof(*ctx))) == NULL + || (propq != NULL && (ctx->propq = OPENSSL_strdup(propq)) == NULL)) { + ATTICerr(0, ERR_R_MALLOC_FAILURE); + OSSL_STORE_LOADER_CTX_free(ctx); + return NULL; + } + ctx->libctx = libctx; + ctx->flags |= FILE_FLAG_ATTACHED; + ctx->_.file.file = bp; + if (!file_find_type(ctx)) { + /* Safety measure */ + ctx->_.file.file = NULL; + goto err; + } + return ctx; +err: + OSSL_STORE_LOADER_CTX_free(ctx); + return NULL; +} + +static int file_ctrl(OSSL_STORE_LOADER_CTX *ctx, int cmd, va_list args) +{ + int ret = 1; + + switch (cmd) { + case OSSL_STORE_C_USE_SECMEM: + { + int on = *(va_arg(args, int *)); + + switch (on) { + case 0: + ctx->flags &= ~FILE_FLAG_SECMEM; + break; + case 1: + ctx->flags |= FILE_FLAG_SECMEM; + break; + default: + ATTICerr(0, ERR_R_PASSED_INVALID_ARGUMENT); + ret = 0; + break; + } + } + break; + default: + break; + } + + return ret; +} + +static int file_expect(OSSL_STORE_LOADER_CTX *ctx, int expected) +{ + ctx->expected_type = expected; + return 1; +} + +static int file_find(OSSL_STORE_LOADER_CTX *ctx, + const OSSL_STORE_SEARCH *search) +{ + /* + * If ctx == NULL, the library is looking to know if this loader supports + * the given search type. + */ + + if (OSSL_STORE_SEARCH_get_type(search) == OSSL_STORE_SEARCH_BY_NAME) { + unsigned long hash = 0; + + if (ctx == NULL) + return 1; + + if (ctx->type != is_dir) { + ATTICerr(0, ATTIC_R_SEARCH_ONLY_SUPPORTED_FOR_DIRECTORIES); + return 0; + } + + hash = X509_NAME_hash(OSSL_STORE_SEARCH_get0_name(search)); + BIO_snprintf(ctx->_.dir.search_name, sizeof(ctx->_.dir.search_name), + "%08lx", hash); + return 1; + } + + if (ctx != NULL) + ATTICerr(0, ATTIC_R_UNSUPPORTED_SEARCH_TYPE); + return 0; +} + +static OSSL_STORE_INFO *file_load_try_decode(OSSL_STORE_LOADER_CTX *ctx, + const char *pem_name, + const char *pem_header, + unsigned char *data, size_t len, + const UI_METHOD *ui_method, + void *ui_data, int *matchcount) +{ + OSSL_STORE_INFO *result = NULL; + BUF_MEM *new_mem = NULL; + char *new_pem_name = NULL; + int t = 0; + + again: + { + size_t i = 0; + void *handler_ctx = NULL; + const FILE_HANDLER **matching_handlers = + OPENSSL_zalloc(sizeof(*matching_handlers) + * OSSL_NELEM(file_handlers)); + + if (matching_handlers == NULL) { + ATTICerr(0, ERR_R_MALLOC_FAILURE); + goto err; + } + + *matchcount = 0; + for (i = 0; i < OSSL_NELEM(file_handlers); i++) { + const FILE_HANDLER *handler = file_handlers[i]; + int try_matchcount = 0; + void *tmp_handler_ctx = NULL; + OSSL_STORE_INFO *tmp_result; + unsigned long err; + + ERR_set_mark(); + tmp_result = + handler->try_decode(pem_name, pem_header, data, len, + &tmp_handler_ctx, &try_matchcount, + ui_method, ui_data, ctx->uri, + ctx->libctx, ctx->propq); + /* avoid flooding error queue with low-level ASN.1 parse errors */ + err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_ASN1 + && ERR_GET_REASON(err) == ERR_R_NESTED_ASN1_ERROR) + ERR_pop_to_mark(); + else + ERR_clear_last_mark(); + + if (try_matchcount > 0) { + + matching_handlers[*matchcount] = handler; + + if (handler_ctx) + handler->destroy_ctx(&handler_ctx); + handler_ctx = tmp_handler_ctx; + + if ((*matchcount += try_matchcount) > 1) { + /* more than one match => ambiguous, kill any result */ + store_info_free(result); + store_info_free(tmp_result); + if (handler->destroy_ctx != NULL) + handler->destroy_ctx(&handler_ctx); + handler_ctx = NULL; + tmp_result = NULL; + result = NULL; + } + if (result == NULL) + result = tmp_result; + } + } + + if (*matchcount == 1 && matching_handlers[0]->repeatable) { + ctx->_.file.last_handler = matching_handlers[0]; + ctx->_.file.last_handler_ctx = handler_ctx; + } + + OPENSSL_free(matching_handlers); + } + + err: + OPENSSL_free(new_pem_name); + BUF_MEM_free(new_mem); + + if (result != NULL + && (t = OSSL_STORE_INFO_get_type(result)) == STORE_INFO_EMBEDDED) { + struct embedded_st *embedded = get0_EMBEDDED(result); + + /* "steal" the embedded data */ + pem_name = new_pem_name = embedded->pem_name; + new_mem = embedded->blob; + data = (unsigned char *)new_mem->data; + len = new_mem->length; + embedded->pem_name = NULL; + embedded->blob = NULL; + + store_info_free(result); + result = NULL; + goto again; + } + + return result; +} + +static OSSL_STORE_INFO *file_load_try_repeat(OSSL_STORE_LOADER_CTX *ctx, + const UI_METHOD *ui_method, + void *ui_data) +{ + OSSL_STORE_INFO *result = NULL; + int try_matchcount = 0; + + if (ctx->_.file.last_handler != NULL) { + result = + ctx->_.file.last_handler->try_decode(NULL, NULL, NULL, 0, + &ctx->_.file.last_handler_ctx, + &try_matchcount, + ui_method, ui_data, ctx->uri, + ctx->libctx, ctx->propq); + + if (result == NULL) { + ctx->_.file.last_handler->destroy_ctx(&ctx->_.file.last_handler_ctx); + ctx->_.file.last_handler_ctx = NULL; + ctx->_.file.last_handler = NULL; + } + } + return result; +} + +static void pem_free_flag(void *pem_data, int secure, size_t num) +{ + if (secure) + OPENSSL_secure_clear_free(pem_data, num); + else + OPENSSL_free(pem_data); +} +static int file_read_pem(BIO *bp, char **pem_name, char **pem_header, + unsigned char **data, long *len, + const UI_METHOD *ui_method, void *ui_data, + const char *uri, int secure) +{ + int i = secure + ? PEM_read_bio_ex(bp, pem_name, pem_header, data, len, + PEM_FLAG_SECURE | PEM_FLAG_EAY_COMPATIBLE) + : PEM_read_bio(bp, pem_name, pem_header, data, len); + + if (i <= 0) + return 0; + + /* + * 10 is the number of characters in "Proc-Type:", which + * PEM_get_EVP_CIPHER_INFO() requires to be present. + * If the PEM header has less characters than that, it's + * not worth spending cycles on it. + */ + if (strlen(*pem_header) > 10) { + EVP_CIPHER_INFO cipher; + struct pem_pass_data pass_data; + + if (!PEM_get_EVP_CIPHER_INFO(*pem_header, &cipher) + || !file_fill_pem_pass_data(&pass_data, "PEM pass phrase", uri, + ui_method, ui_data) + || !PEM_do_header(&cipher, *data, len, file_get_pem_pass, + &pass_data)) { + return 0; + } + } + return 1; +} + +static OSSL_STORE_INFO *file_try_read_msblob(BIO *bp, int *matchcount) +{ +#ifdef OPENSSL_NO_DSA + return NULL; +#else + OSSL_STORE_INFO *result = NULL; + int ispub = -1; + + { + unsigned int magic = 0, bitlen = 0; + int isdss = 0; + unsigned char peekbuf[16] = { 0, }; + const unsigned char *p = peekbuf; + + if (BIO_buffer_peek(bp, peekbuf, sizeof(peekbuf)) <= 0) + return 0; + if (!ossl_do_blob_header(&p, sizeof(peekbuf), &magic, &bitlen, + &isdss, &ispub)) + return 0; + } + + (*matchcount)++; + + { + EVP_PKEY *tmp = ispub + ? b2i_PublicKey_bio(bp) + : b2i_PrivateKey_bio(bp); + + if (tmp == NULL + || (result = OSSL_STORE_INFO_new_PKEY(tmp)) == NULL) { + EVP_PKEY_free(tmp); + return 0; + } + } + + return result; +#endif +} + +static OSSL_STORE_INFO *file_try_read_PVK(BIO *bp, const UI_METHOD *ui_method, + void *ui_data, const char *uri, + int *matchcount) +{ +#if defined(OPENSSL_NO_DSA) || defined(OPENSSL_NO_RC4) + return NULL; +#else + OSSL_STORE_INFO *result = NULL; + + { + unsigned int saltlen = 0, keylen = 0; + unsigned char peekbuf[24] = { 0, }; + const unsigned char *p = peekbuf; + + if (BIO_buffer_peek(bp, peekbuf, sizeof(peekbuf)) <= 0) + return 0; + if (!ossl_do_PVK_header(&p, sizeof(peekbuf), 0, &saltlen, &keylen)) + return 0; + } + + (*matchcount)++; + + { + EVP_PKEY *tmp = NULL; + struct pem_pass_data pass_data; + + if (!file_fill_pem_pass_data(&pass_data, "PVK pass phrase", uri, + ui_method, ui_data) + || (tmp = b2i_PVK_bio(bp, file_get_pem_pass, &pass_data)) == NULL + || (result = OSSL_STORE_INFO_new_PKEY(tmp)) == NULL) { + EVP_PKEY_free(tmp); + return 0; + } + } + + return result; +#endif +} + +static int file_read_asn1(BIO *bp, unsigned char **data, long *len) +{ + BUF_MEM *mem = NULL; + + if (asn1_d2i_read_bio(bp, &mem) < 0) + return 0; + + *data = (unsigned char *)mem->data; + *len = (long)mem->length; + OPENSSL_free(mem); + + return 1; +} + +static int ends_with_dirsep(const char *uri) +{ + if (*uri != '\0') + uri += strlen(uri) - 1; +#if defined(__VMS) + if (*uri == ']' || *uri == '>' || *uri == ':') + return 1; +#elif defined(_WIN32) + if (*uri == '\\') + return 1; +#endif + return *uri == '/'; +} + +static int file_name_to_uri(OSSL_STORE_LOADER_CTX *ctx, const char *name, + char **data) +{ + assert(name != NULL); + assert(data != NULL); + { + const char *pathsep = ends_with_dirsep(ctx->uri) ? "" : "/"; + long calculated_length = strlen(ctx->uri) + strlen(pathsep) + + strlen(name) + 1 /* \0 */; + + *data = OPENSSL_zalloc(calculated_length); + if (*data == NULL) { + ATTICerr(0, ERR_R_MALLOC_FAILURE); + return 0; + } + + OPENSSL_strlcat(*data, ctx->uri, calculated_length); + OPENSSL_strlcat(*data, pathsep, calculated_length); + OPENSSL_strlcat(*data, name, calculated_length); + } + return 1; +} + +static int file_name_check(OSSL_STORE_LOADER_CTX *ctx, const char *name) +{ + const char *p = NULL; + + /* If there are no search criteria, all names are accepted */ + if (ctx->_.dir.search_name[0] == '\0') + return 1; + + /* If the expected type isn't supported, no name is accepted */ + if (ctx->expected_type != 0 + && ctx->expected_type != OSSL_STORE_INFO_CERT + && ctx->expected_type != OSSL_STORE_INFO_CRL) + return 0; + + /* + * First, check the basename + */ + if (strncasecmp(name, ctx->_.dir.search_name, + sizeof(ctx->_.dir.search_name) - 1) != 0 + || name[sizeof(ctx->_.dir.search_name) - 1] != '.') + return 0; + p = &name[sizeof(ctx->_.dir.search_name)]; + + /* + * Then, if the expected type is a CRL, check that the extension starts + * with 'r' + */ + if (*p == 'r') { + p++; + if (ctx->expected_type != 0 + && ctx->expected_type != OSSL_STORE_INFO_CRL) + return 0; + } else if (ctx->expected_type == OSSL_STORE_INFO_CRL) { + return 0; + } + + /* + * Last, check that the rest of the extension is a decimal number, at + * least one digit long. + */ + if (!isdigit(*p)) + return 0; + while (isdigit(*p)) + p++; + +#ifdef __VMS + /* + * One extra step here, check for a possible generation number. + */ + if (*p == ';') + for (p++; *p != '\0'; p++) + if (!ossl_isdigit(*p)) + break; +#endif + + /* + * If we've reached the end of the string at this point, we've successfully + * found a fitting file name. + */ + return *p == '\0'; +} + +static int file_eof(OSSL_STORE_LOADER_CTX *ctx); +static int file_error(OSSL_STORE_LOADER_CTX *ctx); +static OSSL_STORE_INFO *file_load(OSSL_STORE_LOADER_CTX *ctx, + const UI_METHOD *ui_method, + void *ui_data) +{ + OSSL_STORE_INFO *result = NULL; + + ctx->errcnt = 0; + + if (ctx->type == is_dir) { + do { + char *newname = NULL; + + if (ctx->_.dir.last_entry == NULL) { + if (!ctx->_.dir.end_reached) { + assert(ctx->_.dir.last_errno != 0); + ERR_raise(ERR_LIB_SYS, ctx->_.dir.last_errno); + ctx->errcnt++; + } + return NULL; + } + + if (ctx->_.dir.last_entry[0] != '.' + && file_name_check(ctx, ctx->_.dir.last_entry) + && !file_name_to_uri(ctx, ctx->_.dir.last_entry, &newname)) + return NULL; + + /* + * On the first call (with a NULL context), OPENSSL_DIR_read() + * cares about the second argument. On the following calls, it + * only cares that it isn't NULL. Therefore, we can safely give + * it our URI here. + */ + ctx->_.dir.last_entry = OPENSSL_DIR_read(&ctx->_.dir.ctx, ctx->uri); + ctx->_.dir.last_errno = errno; + if (ctx->_.dir.last_entry == NULL && ctx->_.dir.last_errno == 0) + ctx->_.dir.end_reached = 1; + + if (newname != NULL + && (result = OSSL_STORE_INFO_new_NAME(newname)) == NULL) { + OPENSSL_free(newname); + ATTICerr(0, ERR_R_OSSL_STORE_LIB); + return NULL; + } + } while (result == NULL && !file_eof(ctx)); + } else { + int matchcount = -1; + + again: + result = file_load_try_repeat(ctx, ui_method, ui_data); + if (result != NULL) + return result; + + if (file_eof(ctx)) + return NULL; + + do { + char *pem_name = NULL; /* PEM record name */ + char *pem_header = NULL; /* PEM record header */ + unsigned char *data = NULL; /* DER encoded data */ + long len = 0; /* DER encoded data length */ + + matchcount = -1; + if (ctx->type == is_pem) { + if (!file_read_pem(ctx->_.file.file, &pem_name, &pem_header, + &data, &len, ui_method, ui_data, ctx->uri, + (ctx->flags & FILE_FLAG_SECMEM) != 0)) { + ctx->errcnt++; + goto endloop; + } + } else { + if ((result = file_try_read_msblob(ctx->_.file.file, + &matchcount)) != NULL + || (result = file_try_read_PVK(ctx->_.file.file, + ui_method, ui_data, ctx->uri, + &matchcount)) != NULL) + goto endloop; + + if (!file_read_asn1(ctx->_.file.file, &data, &len)) { + ctx->errcnt++; + goto endloop; + } + } + + result = file_load_try_decode(ctx, pem_name, pem_header, data, len, + ui_method, ui_data, &matchcount); + + if (result != NULL) + goto endloop; + + /* + * If a PEM name matches more than one handler, the handlers are + * badly coded. + */ + if (!ossl_assert(pem_name == NULL || matchcount <= 1)) { + ctx->errcnt++; + goto endloop; + } + + if (matchcount > 1) { + ATTICerr(0, ATTIC_R_AMBIGUOUS_CONTENT_TYPE); + } else if (matchcount == 1) { + /* + * If there are other errors on the stack, they already show + * what the problem is. + */ + if (ERR_peek_error() == 0) { + ATTICerr(0, ATTIC_R_UNSUPPORTED_CONTENT_TYPE); + if (pem_name != NULL) + ERR_add_error_data(3, "PEM type is '", pem_name, "'"); + } + } + if (matchcount > 0) + ctx->errcnt++; + + endloop: + pem_free_flag(pem_name, (ctx->flags & FILE_FLAG_SECMEM) != 0, 0); + pem_free_flag(pem_header, (ctx->flags & FILE_FLAG_SECMEM) != 0, 0); + pem_free_flag(data, (ctx->flags & FILE_FLAG_SECMEM) != 0, len); + } while (matchcount == 0 && !file_eof(ctx) && !file_error(ctx)); + + /* We bail out on ambiguity */ + if (matchcount > 1) { + store_info_free(result); + return NULL; + } + + if (result != NULL + && ctx->expected_type != 0 + && ctx->expected_type != OSSL_STORE_INFO_get_type(result)) { + store_info_free(result); + goto again; + } + } + + return result; +} + +static int file_error(OSSL_STORE_LOADER_CTX *ctx) +{ + return ctx->errcnt > 0; +} + +static int file_eof(OSSL_STORE_LOADER_CTX *ctx) +{ + if (ctx->type == is_dir) + return ctx->_.dir.end_reached; + + if (ctx->_.file.last_handler != NULL + && !ctx->_.file.last_handler->eof(ctx->_.file.last_handler_ctx)) + return 0; + return BIO_eof(ctx->_.file.file); +} + +static int file_close(OSSL_STORE_LOADER_CTX *ctx) +{ + if ((ctx->flags & FILE_FLAG_ATTACHED) == 0) { + if (ctx->type == is_dir) + OPENSSL_DIR_end(&ctx->_.dir.ctx); + else + BIO_free_all(ctx->_.file.file); + } else { + /* + * Because file_attach() called file_find_type(), we know that a + * BIO_f_buffer() has been pushed on top of the regular BIO. + */ + BIO *buff = ctx->_.file.file; + + /* Detach buff */ + (void)BIO_pop(ctx->_.file.file); + /* Safety measure */ + ctx->_.file.file = NULL; + + BIO_free(buff); + } + OSSL_STORE_LOADER_CTX_free(ctx); + return 1; +} + +/*- + * ENGINE management + */ + +static const char *loader_attic_id = "loader_attic"; +static const char *loader_attic_name = "'file:' loader"; + +static OSSL_STORE_LOADER *loader_attic = NULL; + +static int loader_attic_init(ENGINE *e) +{ + return 1; +} + + +static int loader_attic_finish(ENGINE *e) +{ + return 1; +} + + +static int loader_attic_destroy(ENGINE *e) +{ + OSSL_STORE_LOADER *loader = OSSL_STORE_unregister_loader("file"); + + if (loader == NULL) + return 0; + + ERR_unload_ATTIC_strings(); + OSSL_STORE_LOADER_free(loader); + return 1; +} + +static int bind_loader_attic(ENGINE *e) +{ + + /* Ensure the ATTIC error handdling is set up on best effort basis */ + ERR_load_ATTIC_strings(); + + if (/* Create the OSSL_STORE_LOADER */ + (loader_attic = OSSL_STORE_LOADER_new(e, "file")) == NULL + || !OSSL_STORE_LOADER_set_open_with_libctx(loader_attic, + file_open_with_libctx) + || !OSSL_STORE_LOADER_set_open(loader_attic, file_open) + || !OSSL_STORE_LOADER_set_attach(loader_attic, file_attach) + || !OSSL_STORE_LOADER_set_ctrl(loader_attic, file_ctrl) + || !OSSL_STORE_LOADER_set_expect(loader_attic, file_expect) + || !OSSL_STORE_LOADER_set_find(loader_attic, file_find) + || !OSSL_STORE_LOADER_set_load(loader_attic, file_load) + || !OSSL_STORE_LOADER_set_eof(loader_attic, file_eof) + || !OSSL_STORE_LOADER_set_error(loader_attic, file_error) + || !OSSL_STORE_LOADER_set_close(loader_attic, file_close) + /* Init the engine itself */ + || !ENGINE_set_id(e, loader_attic_id) + || !ENGINE_set_name(e, loader_attic_name) + || !ENGINE_set_destroy_function(e, loader_attic_destroy) + || !ENGINE_set_init_function(e, loader_attic_init) + || !ENGINE_set_finish_function(e, loader_attic_finish) + /* Finally, register the method with libcrypto */ + || !OSSL_STORE_register_loader(loader_attic)) { + OSSL_STORE_LOADER_free(loader_attic); + loader_attic = NULL; + ATTICerr(0, ATTIC_R_INIT_FAILED); + return 0; + } + + return 1; +} + +#ifdef OPENSSL_NO_DYNAMIC_ENGINE +# error "Only allowed as dynamically shared object" +#endif + +static int bind_helper(ENGINE *e, const char *id) +{ + if (id && (strcmp(id, loader_attic_id) != 0)) + return 0; + if (!bind_loader_attic(e)) + return 0; + return 1; +} + +IMPLEMENT_DYNAMIC_CHECK_FN() + IMPLEMENT_DYNAMIC_BIND_FN(bind_helper) diff --git a/engines/e_loader_attic.ec b/engines/e_loader_attic.ec new file mode 100644 index 0000000000..525a689fe5 --- /dev/null +++ b/engines/e_loader_attic.ec @@ -0,0 +1,3 @@ +# The INPUT HEADER is scanned for declarations +# LIBNAME INPUT HEADER ERROR-TABLE FILE +L ATTIC e_loader_attic_err.h e_loader_attic_err.c diff --git a/engines/e_loader_attic.txt b/engines/e_loader_attic.txt new file mode 100644 index 0000000000..db1a996a33 --- /dev/null +++ b/engines/e_loader_attic.txt @@ -0,0 +1,23 @@ +# Copyright 1999-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 + +# Function codes + +#Reason codes +ATTIC_R_AMBIGUOUS_CONTENT_TYPE:100:ambiguous content type +ATTIC_R_BAD_PASSWORD_READ:101:bad password read +ATTIC_R_ERROR_VERIFYING_PKCS12_MAC:102:error verifying pkcs12 mac +ATTIC_R_INIT_FAILED:103:init failed +ATTIC_R_PASSPHRASE_CALLBACK_ERROR:104:passphrase callback error +ATTIC_R_PATH_MUST_BE_ABSOLUTE:105:path must be absolute +ATTIC_R_SEARCH_ONLY_SUPPORTED_FOR_DIRECTORIES:106:\ + search only supported for directories +ATTIC_R_UI_PROCESS_INTERRUPTED_OR_CANCELLED:107:\ + ui process interrupted or cancelled +ATTIC_R_UNSUPPORTED_CONTENT_TYPE:108:unsupported content type +ATTIC_R_UNSUPPORTED_SEARCH_TYPE:109:unsupported search type +ATTIC_R_URI_AUTHORITY_UNSUPPORTED:110:uri authority unsupported diff --git a/engines/e_loader_attic_err.c b/engines/e_loader_attic_err.c new file mode 100644 index 0000000000..2bc4e854c8 --- /dev/null +++ b/engines/e_loader_attic_err.c @@ -0,0 +1,73 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-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/err.h> +#include "e_loader_attic_err.h" + +#ifndef OPENSSL_NO_ERR + +static ERR_STRING_DATA ATTIC_str_reasons[] = { + {ERR_PACK(0, 0, ATTIC_R_AMBIGUOUS_CONTENT_TYPE), "ambiguous content type"}, + {ERR_PACK(0, 0, ATTIC_R_BAD_PASSWORD_READ), "bad password read"}, + {ERR_PACK(0, 0, ATTIC_R_ERROR_VERIFYING_PKCS12_MAC), + "error verifying pkcs12 mac"}, + {ERR_PACK(0, 0, ATTIC_R_INIT_FAILED), "init failed"}, + {ERR_PACK(0, 0, ATTIC_R_PASSPHRASE_CALLBACK_ERROR), + "passphrase callback error"}, + {ERR_PACK(0, 0, ATTIC_R_PATH_MUST_BE_ABSOLUTE), "path must be absolute"}, + {ERR_PACK(0, 0, ATTIC_R_SEARCH_ONLY_SUPPORTED_FOR_DIRECTORIES), + "search only supported for directories"}, + {ERR_PACK(0, 0, ATTIC_R_UI_PROCESS_INTERRUPTED_OR_CANCELLED), + "ui process interrupted or cancelled"}, + {ERR_PACK(0, 0, ATTIC_R_UNSUPPORTED_CONTENT_TYPE), + "unsupported content type"}, + {ERR_PACK(0, 0, ATTIC_R_UNSUPPORTED_SEARCH_TYPE), + "unsupported search type"}, + {ERR_PACK(0, 0, ATTIC_R_URI_AUTHORITY_UNSUPPORTED), + "uri authority unsupported"}, + {0, NULL} +}; + +#endif + +static int lib_code = 0; +static int error_loaded = 0; + +static int ERR_load_ATTIC_strings(void) +{ + if (lib_code == 0) + lib_code = ERR_get_next_error_library(); + + if (!error_loaded) { +#ifndef OPENSSL_NO_ERR + ERR_load_strings(lib_code, ATTIC_str_reasons); +#endif + error_loaded = 1; + } + return 1; +} + +static void ERR_unload_ATTIC_strings(void) +{ + if (error_loaded) { +#ifndef OPENSSL_NO_ERR + ERR_unload_strings(lib_code, ATTIC_str_reasons); +#endif + error_loaded = 0; + } +} + +static void ERR_ATTIC_error(int function, int reason, char *file, int line) +{ + if (lib_code == 0) + lib_code = ERR_get_next_error_library(); + ERR_raise(lib_code, reason); + ERR_set_debug(file, line, NULL); +} diff --git a/engines/e_loader_attic_err.h b/engines/e_loader_attic_err.h new file mode 100644 index 0000000000..115e0ea6f6 --- /dev/null +++ b/engines/e_loader_attic_err.h @@ -0,0 +1,43 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-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 + */ + +#ifndef OPENSSL_ATTICERR_H +# define OPENSSL_ATTICERR_H +# pragma once + +# include <openssl/opensslconf.h> +# include <openssl/symhacks.h> + + +# define ATTICerr(f, r) ERR_ATTIC_error(0, (r), OPENSSL_FILE, OPENSSL_LINE) + + +/* + * ATTIC function codes. + */ +# ifndef OPENSSL_NO_DEPRECATED_3_0 +# endif + +/* + * ATTIC reason codes. + */ +# define ATTIC_R_AMBIGUOUS_CONTENT_TYPE 100 +# define ATTIC_R_BAD_PASSWORD_READ 101 +# define ATTIC_R_ERROR_VERIFYING_PKCS12_MAC 102 +# define ATTIC_R_INIT_FAILED 103 +# define ATTIC_R_PASSPHRASE_CALLBACK_ERROR 104 +# define ATTIC_R_PATH_MUST_BE_ABSOLUTE 105 +# define ATTIC_R_SEARCH_ONLY_SUPPORTED_FOR_DIRECTORIES 106 +# define ATTIC_R_UI_PROCESS_INTERRUPTED_OR_CANCELLED 107 +# define ATTIC_R_UNSUPPORTED_CONTENT_TYPE 108 +# define ATTIC_R_UNSUPPORTED_SEARCH_TYPE 109 +# define ATTIC_R_URI_AUTHORITY_UNSUPPORTED 110 + +#endif |