OSSL_STORE: Better information when prompting for pass phrases
[openssl.git] / crypto / store / loader_file.c
index ebcad03ead8bbe03c079bdf844653939fdbfb285..05938a27c8b6df6721833d644dd2d08ff21539e7 100644 (file)
@@ -1,14 +1,16 @@
 /*
- * Copyright 2016-2017 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
  *
- * Licensed under the OpenSSL license (the "License").  You may not use
+ * 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 "e_os.h"
 #include <string.h>
 #include <sys/stat.h>
+#include <ctype.h>
 #include <assert.h>
 
 #include <openssl/bio.h>
 #include <openssl/store.h>
 #include <openssl/ui.h>
 #include <openssl/x509.h>        /* For the PKCS8 stuff o.O */
-#include "internal/asn1_int.h"
+#include "crypto/asn1.h"
+#include "crypto/ctype.h"
 #include "internal/o_dir.h"
 #include "internal/cryptlib.h"
-#include "internal/store_int.h"
-#include "store_locl.h"
+#include "crypto/store.h"
+#include "crypto/evp.h"
+#include "store_local.h"
 
-#include "e_os.h"
+DEFINE_STACK_OF(X509)
 
-/*
+#ifdef _WIN32
+# define stat    _stat
+#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 *prompt_info, void *data)
+                           size_t maxsize, const char *desc, const char *info,
+                           void *data)
 {
     UI *ui = UI_new();
     char *prompt = NULL;
@@ -49,8 +63,7 @@ static char *file_get_pass(const UI_METHOD *ui_method, char *pass,
         UI_set_method(ui, ui_method);
     UI_add_user_data(ui, data);
 
-    if ((prompt = UI_construct_prompt(ui, "pass phrase",
-                                      prompt_info)) == NULL) {
+    if ((prompt = UI_construct_prompt(ui, desc, info)) == NULL) {
         OSSL_STOREerr(OSSL_STORE_F_FILE_GET_PASS, ERR_R_MALLOC_FAILURE);
         pass = NULL;
     } else if (!UI_add_input_string(ui, prompt, UI_INPUT_FLAG_DEFAULT_PWD,
@@ -81,30 +94,42 @@ static char *file_get_pass(const UI_METHOD *ui_method, char *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 *prompt_info,
+                                   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_info = prompt_info;
+    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_info, pass_data->data);
+                               pass_data->prompt_desc, pass_data->prompt_info,
+                               pass_data->data);
 
     return pass == NULL ? 0 : strlen(pass);
 }
 
-/*
- *  The file scheme handlers
+/*-
+ *  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.
  */
 
 /*-
@@ -135,6 +160,8 @@ static int file_get_pem_pass(char *buf, int num, int w, void *data)
  *                  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
  */
@@ -144,7 +171,9 @@ typedef OSSL_STORE_INFO *(*file_try_decode_fn)(const char *pem_name,
                                                size_t len, void **handler_ctx,
                                                int *matchcount,
                                                const UI_METHOD *ui_method,
-                                               void *ui_data);
+                                               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
@@ -153,7 +182,7 @@ typedef OSSL_STORE_INFO *(*file_try_decode_fn)(const char *pem_name,
 typedef int (*file_eof_fn)(void *handler_ctx);
 /*
  * The destroy_ctx function is used to destroy the handler_ctx that was
- * intiated by a repeatable try_decode fuction.  This is only used when
+ * 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);
@@ -168,13 +197,20 @@ typedef struct file_handler_st {
     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)
+                                          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;
@@ -202,7 +238,7 @@ static OSSL_STORE_INFO *try_decode_PKCS12(const char *pem_name,
                 pass = "";
             } else {
                 if ((pass = file_get_pass(ui_method, tpass, PEM_BUFSIZE,
-                                          "PKCS12 import password",
+                                          "PKCS12 import pass phrase", uri,
                                           ui_data)) == NULL) {
                     OSSL_STOREerr(OSSL_STORE_F_TRY_DECODE_PKCS12,
                                   OSSL_STORE_R_PASSPHRASE_CALLBACK_ERROR);
@@ -216,35 +252,35 @@ static OSSL_STORE_INFO *try_decode_PKCS12(const char *pem_name,
             }
 
             if (PKCS12_parse(p12, pass, &pkey, &cert, &chain)) {
-                OSSL_STORE_INFO *si_pkey = NULL;
-                OSSL_STORE_INFO *si_cert = NULL;
-                OSSL_STORE_INFO *si_ca = NULL;
+                OSSL_STORE_INFO *osi_pkey = NULL;
+                OSSL_STORE_INFO *osi_cert = NULL;
+                OSSL_STORE_INFO *osi_ca = NULL;
 
                 if ((ctx = sk_OSSL_STORE_INFO_new_null()) != NULL
-                    && (si_pkey = OSSL_STORE_INFO_new_PKEY(pkey)) != NULL
-                    && sk_OSSL_STORE_INFO_push(ctx, si_pkey) != 0
-                    && (si_cert = OSSL_STORE_INFO_new_CERT(cert)) != NULL
-                    && sk_OSSL_STORE_INFO_push(ctx, si_cert) != 0) {
+                    && (osi_pkey = OSSL_STORE_INFO_new_PKEY(pkey)) != NULL
+                    && sk_OSSL_STORE_INFO_push(ctx, osi_pkey) != 0
+                    && (osi_cert = OSSL_STORE_INFO_new_CERT(cert)) != NULL
+                    && sk_OSSL_STORE_INFO_push(ctx, osi_cert) != 0) {
                     ok = 1;
-                    si_pkey = NULL;
-                    si_cert = NULL;
+                    osi_pkey = NULL;
+                    osi_cert = NULL;
 
                     while(sk_X509_num(chain) > 0) {
                         X509 *ca = sk_X509_value(chain, 0);
 
-                        if ((si_ca = OSSL_STORE_INFO_new_CERT(ca)) == NULL
-                            || sk_OSSL_STORE_INFO_push(ctx, si_ca) == 0) {
+                        if ((osi_ca = OSSL_STORE_INFO_new_CERT(ca)) == NULL
+                            || sk_OSSL_STORE_INFO_push(ctx, osi_ca) == 0) {
                             ok = 0;
                             break;
                         }
-                        si_ca = NULL;
+                        osi_ca = NULL;
                         (void)sk_X509_shift(chain);
                     }
                 }
                 if (!ok) {
-                    OSSL_STORE_INFO_free(si_ca);
-                    OSSL_STORE_INFO_free(si_cert);
-                    OSSL_STORE_INFO_free(si_pkey);
+                    OSSL_STORE_INFO_free(osi_ca);
+                    OSSL_STORE_INFO_free(osi_cert);
+                    OSSL_STORE_INFO_free(osi_pkey);
                     sk_OSSL_STORE_INFO_pop_free(ctx, OSSL_STORE_INFO_free);
                     EVP_PKEY_free(pkey);
                     X509_free(cert);
@@ -267,12 +303,14 @@ static OSSL_STORE_INFO *try_decode_PKCS12(const char *pem_name,
 
     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;
@@ -280,6 +318,7 @@ static void destroy_ctx_PKCS12(void **pctx)
     sk_OSSL_STORE_INFO_pop_free(ctx, OSSL_STORE_INFO_free);
     *pctx = NULL;
 }
+
 static FILE_HANDLER PKCS12_handler = {
     "PKCS12",
     try_decode_PKCS12,
@@ -288,13 +327,21 @@ static FILE_HANDLER PKCS12_handler = {
     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)
+                                                  void *ui_data,
+                                                  const char *uri,
+                                                  OPENSSL_CTX *libctx,
+                                                  const char *propq)
 {
     X509_SIG *p8 = NULL;
     char kbuf[PEM_BUFSIZE];
@@ -324,7 +371,8 @@ static OSSL_STORE_INFO *try_decode_PKCS8Encrypted(const char *pem_name,
     }
 
     if ((pass = file_get_pass(ui_method, kbuf, PEM_BUFSIZE,
-                              "PKCS8 decrypt password", ui_data)) == NULL) {
+                              "PKCS8 decrypt pass phrase", uri,
+                              ui_data)) == NULL) {
         OSSL_STOREerr(OSSL_STORE_F_TRY_DECODE_PKCS8ENCRYPTED,
                       OSSL_STORE_R_BAD_PASSWORD_READ);
         goto nop8;
@@ -352,11 +400,17 @@ static OSSL_STORE_INFO *try_decode_PKCS8Encrypted(const char *pem_name,
     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).
+ */
 int pem_check_suffix(const char *pem_str, const char *suffix);
 static OSSL_STORE_INFO *try_decode_PrivateKey(const char *pem_name,
                                               const char *pem_header,
@@ -364,7 +418,9 @@ static OSSL_STORE_INFO *try_decode_PrivateKey(const char *pem_name,
                                               size_t len, void **pctx,
                                               int *matchcount,
                                               const UI_METHOD *ui_method,
-                                              void *ui_data)
+                                              void *ui_data, const char *uri,
+                                              OPENSSL_CTX *libctx,
+                                              const char *propq)
 {
     OSSL_STORE_INFO *store_info = NULL;
     EVP_PKEY *pkey = NULL;
@@ -377,7 +433,7 @@ static OSSL_STORE_INFO *try_decode_PrivateKey(const char *pem_name,
 
             *matchcount = 1;
             if (p8inf != NULL)
-                pkey = EVP_PKCS82PKEY(p8inf);
+                pkey = evp_pkcs82pkey_int(p8inf, libctx, propq);
             PKCS8_PRIV_KEY_INFO_free(p8inf);
         } else {
             int slen;
@@ -386,7 +442,8 @@ static OSSL_STORE_INFO *try_decode_PrivateKey(const char *pem_name,
                 && (ameth = EVP_PKEY_asn1_find_str(NULL, pem_name,
                                                    slen)) != NULL) {
                 *matchcount = 1;
-                pkey = d2i_PrivateKey(ameth->pkey_id, NULL, &blob, len);
+                pkey = d2i_PrivateKey_ex(ameth->pkey_id, NULL, &blob, len,
+                                         libctx, propq);
             }
         }
     } else {
@@ -400,7 +457,8 @@ static OSSL_STORE_INFO *try_decode_PrivateKey(const char *pem_name,
             if (ameth->pkey_flags & ASN1_PKEY_ALIAS)
                 continue;
 
-            tmp_pkey = d2i_PrivateKey(ameth->pkey_id, NULL, &tmp_blob, len);
+            tmp_pkey = d2i_PrivateKey_ex(ameth->pkey_id, NULL, &tmp_blob, len,
+                                         libctx, propq);
             if (tmp_pkey != NULL) {
                 if (pkey != NULL)
                     EVP_PKEY_free(tmp_pkey);
@@ -425,18 +483,24 @@ static OSSL_STORE_INFO *try_decode_PrivateKey(const char *pem_name,
 
     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)
+                                          void *ui_data, const char *uri,
+                                          OPENSSL_CTX *libctx,
+                                          const char *propq)
 {
     OSSL_STORE_INFO *store_info = NULL;
     EVP_PKEY *pkey = NULL;
@@ -455,18 +519,24 @@ static OSSL_STORE_INFO *try_decode_PUBKEY(const char *pem_name,
 
     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)
+                                          void *ui_data, const char *uri,
+                                          OPENSSL_CTX *libctx,
+                                          const char *propq)
 {
     OSSL_STORE_INFO *store_info = NULL;
     int slen = 0;
@@ -480,12 +550,13 @@ static OSSL_STORE_INFO *try_decode_params(const char *pem_name,
         *matchcount = 1;
     }
 
-    if ((pkey = EVP_PKEY_new()) == NULL) {
-        OSSL_STOREerr(OSSL_STORE_F_TRY_DECODE_PARAMS, ERR_R_EVP_LIB);
-        return NULL;
-    }
-
     if (slen > 0) {
+        if ((pkey = EVP_PKEY_new()) == NULL) {
+            OSSL_STOREerr(OSSL_STORE_F_TRY_DECODE_PARAMS, ERR_R_EVP_LIB);
+            return NULL;
+        }
+
+
         if (EVP_PKEY_set_type_str(pkey, pem_name, slen)
             && (ameth = EVP_PKEY_get0_asn1(pkey)) != NULL
             && ameth->param_decode != NULL
@@ -493,22 +564,37 @@ static OSSL_STORE_INFO *try_decode_params(const char *pem_name,
             ok = 1;
     } else {
         int i;
+        EVP_PKEY *tmp_pkey = NULL;
 
         for (i = 0; i < EVP_PKEY_asn1_get_count(); i++) {
             const unsigned char *tmp_blob = blob;
 
+            if (tmp_pkey == NULL && (tmp_pkey = EVP_PKEY_new()) == NULL) {
+                OSSL_STOREerr(OSSL_STORE_F_TRY_DECODE_PARAMS, ERR_R_EVP_LIB);
+                break;
+            }
+
             ameth = EVP_PKEY_asn1_get0(i);
             if (ameth->pkey_flags & ASN1_PKEY_ALIAS)
                 continue;
-            if (EVP_PKEY_set_type(pkey, ameth->pkey_id)
-                && (ameth = EVP_PKEY_get0_asn1(pkey)) != NULL
+
+            if (EVP_PKEY_set_type(tmp_pkey, ameth->pkey_id)
+                && (ameth = EVP_PKEY_get0_asn1(tmp_pkey)) != NULL
                 && ameth->param_decode != NULL
-                && ameth->param_decode(pkey, &tmp_blob, len)) {
+                && ameth->param_decode(tmp_pkey, &tmp_blob, len)) {
+                if (pkey != NULL)
+                    EVP_PKEY_free(tmp_pkey);
+                else
+                    pkey = tmp_pkey;
+                tmp_pkey = NULL;
                 (*matchcount)++;
-                ok = 1;
-                break;
             }
         }
+
+        EVP_PKEY_free(tmp_pkey);
+        if (*matchcount == 1) {
+            ok = 1;
+        }
     }
 
     if (ok)
@@ -518,18 +604,25 @@ static OSSL_STORE_INFO *try_decode_params(const char *pem_name,
 
     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)
+                                                   void *ui_data,
+                                                   const char *uri,
+                                                   OPENSSL_CTX *libctx,
+                                                   const char *propq)
 {
     OSSL_STORE_INFO *store_info = NULL;
     X509 *cert = NULL;
@@ -564,18 +657,24 @@ static OSSL_STORE_INFO *try_decode_X509Certificate(const char *pem_name,
 
     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)
+                                           void *ui_data, const char *uri,
+                                           OPENSSL_CTX *libctx,
+                                           const char *propq)
 {
     OSSL_STORE_INFO *store_info = NULL;
     X509_CRL *crl = NULL;
@@ -597,11 +696,15 @@ static OSSL_STORE_INFO *try_decode_X509CRL(const char *pem_name,
 
     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,
@@ -613,11 +716,13 @@ static const FILE_HANDLER *file_handlers[] = {
 };
 
 
-/*
+/*-
  *  The loader itself
+ *  -----------------
  */
 
 struct ossl_store_loader_ctx_st {
+    char *uri;                   /* The URI we currently try to load */
     enum {
         is_raw = 0,
         is_pem,
@@ -625,6 +730,7 @@ struct ossl_store_loader_ctx_st {
     } 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 */
@@ -640,7 +746,13 @@ struct ossl_store_loader_ctx_st {
         struct {                 /* Used with is_dir */
             OPENSSL_DIR_CTX *ctx;
             int end_reached;
-            char *uri;
+
+            /*
+             * 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
@@ -651,22 +763,48 @@ struct ossl_store_loader_ctx_st {
             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->type == is_dir) {
-        OPENSSL_free(ctx->_.dir.uri);
-    } else {
+    if (ctx == NULL)
+        return;
+
+    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->propq);
     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(const OSSL_STORE_LOADER *loader,
                                         const char *uri,
                                         const UI_METHOD *ui_method,
@@ -674,94 +812,115 @@ static OSSL_STORE_LOADER_CTX *file_open(const OSSL_STORE_LOADER *loader,
 {
     OSSL_STORE_LOADER_CTX *ctx = NULL;
     struct stat st;
-    const char *path = NULL;
+    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) {
-        if (strncasecmp(&uri[5], "//localhost/", 12) == 0) {
-            path = &uri[16];
-        } else if (strncmp(&uri[5], "///", 3) == 0) {
-            path = &uri[7];
-        } else if (strncmp(&uri[5], "//", 2) != 0) {
-            path = &uri[5];
-        } else {
-            OSSL_STOREerr(OSSL_STORE_F_FILE_OPEN,
-                          OSSL_STORE_R_URI_AUTHORITY_UNSUPPORED);
-            return NULL;
+        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 {
+                OSSL_STOREerr(OSSL_STORE_F_FILE_OPEN,
+                              OSSL_STORE_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 = ossl_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[0] != '/') {
+        if (path_data[i].check_absolute && path_data[i].path[0] != '/') {
             OSSL_STOREerr(OSSL_STORE_F_FILE_OPEN,
                           OSSL_STORE_R_PATH_MUST_BE_ABSOLUTE);
+            ERR_add_error_data(1, path_data[i].path);
             return NULL;
         }
 
-#ifdef _WIN32
-        /* Windows file: URIs with a drive letter start with a / */
-        if (path[0] == '/' && path[2] == ':' && path[3] == '/')
-            path++;
-#endif
-    } else {
-        path = uri;
+        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 (stat(path, &st) < 0) {
-        SYSerr(SYS_F_STAT, errno);
-        ERR_add_error_data(1, path);
+    if (path == NULL) {
         return NULL;
     }
 
+    /* Successfully found a working path, clear possible collected errors */
+    ERR_clear_error();
+
     ctx = OPENSSL_zalloc(sizeof(*ctx));
     if (ctx == NULL) {
         OSSL_STOREerr(OSSL_STORE_F_FILE_OPEN, ERR_R_MALLOC_FAILURE);
         return NULL;
     }
+    ctx->uri = OPENSSL_strdup(uri);
+    if (ctx->uri == NULL) {
+        OSSL_STOREerr(OSSL_STORE_F_FILE_OPEN, ERR_R_MALLOC_FAILURE);
+        goto err;
+    }
 
-    if ((st.st_mode & S_IFDIR) == S_IFDIR) {
-        /*
-         * Try to copy everything, even if we know that some of them must be
-         * NULL for the moment.  This prevents errors in the future, when more
-         * components may be used.
-         */
-        ctx->_.dir.uri = OPENSSL_strdup(uri);
+    if (S_ISDIR(st.st_mode)) {
         ctx->type = is_dir;
-
-        if (ctx->_.dir.uri == NULL)
-            goto err;
-
         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) {
                 char errbuf[256];
-                errno = ctx->_.dir.last_errno;
-                openssl_strerror_r(errno, errbuf, sizeof(errbuf));
                 OSSL_STOREerr(OSSL_STORE_F_FILE_OPEN, ERR_R_SYS_LIB);
-                ERR_add_error_data(1, errbuf);
+                errno = ctx->_.dir.last_errno;
+                if (openssl_strerror_r(errno, errbuf, sizeof(errbuf)))
+                    ERR_add_error_data(1, errbuf);
                 goto err;
             }
             ctx->_.dir.end_reached = 1;
         }
-    } else {
-        BIO *buff = NULL;
-        char peekbuf[4096];
-
-        if ((buff = BIO_new(BIO_f_buffer())) == NULL
-            || (ctx->_.file.file = BIO_new_file(path, "rb")) == NULL) {
-            BIO_free_all(buff);
-            goto err;
-        }
-
-        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;
-        }
+    } else if ((ctx->_.file.file = BIO_new_file(path, "rb")) == NULL
+               || !file_find_type(ctx)) {
+        BIO_free_all(ctx->_.file.file);
+        goto err;
     }
 
     return ctx;
@@ -770,6 +929,34 @@ static OSSL_STORE_LOADER_CTX *file_open(const OSSL_STORE_LOADER *loader,
     return NULL;
 }
 
+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;
+
+    if ((ctx = OPENSSL_zalloc(sizeof(*ctx))) == NULL
+        || (propq != NULL && (ctx->propq = OPENSSL_strdup(propq)) == NULL)) {
+        OSSL_STOREerr(OSSL_STORE_F_FILE_ATTACH, 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;
+        OSSL_STORE_LOADER_CTX_free(ctx);
+        ctx = NULL;
+    }
+
+    return ctx;
+}
+
 static int file_ctrl(OSSL_STORE_LOADER_CTX *ctx, int cmd, va_list args)
 {
     int ret = 1;
@@ -801,21 +988,42 @@ static int file_ctrl(OSSL_STORE_LOADER_CTX *ctx, int cmd, va_list args)
     return ret;
 }
 
-/* Internal function to decode an already opened PEM file */
-OSSL_STORE_LOADER_CTX *ossl_store_file_attach_pem_bio_int(BIO *bp)
+static int file_expect(OSSL_STORE_LOADER_CTX *ctx, int expected)
 {
-    OSSL_STORE_LOADER_CTX *ctx = OPENSSL_zalloc(sizeof(*ctx));
+    ctx->expected_type = expected;
+    return 1;
+}
 
-    if (ctx == NULL) {
-        OSSL_STOREerr(OSSL_STORE_F_OSSL_STORE_FILE_ATTACH_PEM_BIO_INT,
-                      ERR_R_MALLOC_FAILURE);
-        return NULL;
-    }
+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.
+     */
 
-    ctx->_.file.file = bp;
-    ctx->type = is_pem;
+    if (OSSL_STORE_SEARCH_get_type(search) == OSSL_STORE_SEARCH_BY_NAME) {
+        unsigned long hash = 0;
 
-    return ctx;
+        if (ctx == NULL)
+            return 1;
+
+        if (ctx->type != is_dir) {
+            OSSL_STOREerr(OSSL_STORE_F_FILE_FIND,
+                          OSSL_STORE_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)
+        OSSL_STOREerr(OSSL_STORE_F_FILE_FIND,
+                      OSSL_STORE_R_UNSUPPORTED_SEARCH_TYPE);
+    return 0;
 }
 
 static OSSL_STORE_INFO *file_load_try_decode(OSSL_STORE_LOADER_CTX *ctx,
@@ -852,11 +1060,12 @@ static OSSL_STORE_INFO *file_load_try_decode(OSSL_STORE_LOADER_CTX *ctx,
             OSSL_STORE_INFO *tmp_result =
                 handler->try_decode(pem_name, pem_header, data, len,
                                     &tmp_handler_ctx, &try_matchcount,
-                                    ui_method, ui_data);
+                                    ui_method, ui_data, ctx->uri,
+                                    ctx->libctx, ctx->propq);
 
             if (try_matchcount > 0) {
-                if (matching_handlers)
-                    matching_handlers[*matchcount] = handler;
+
+                matching_handlers[*matchcount] = handler;
 
                 if (handler_ctx)
                     handler->destroy_ctx(&handler_ctx);
@@ -919,7 +1128,8 @@ static OSSL_STORE_INFO *file_load_try_repeat(OSSL_STORE_LOADER_CTX *ctx,
             ctx->_.file.last_handler->try_decode(NULL, NULL, NULL, 0,
                                                  &ctx->_.file.last_handler_ctx,
                                                  &try_matchcount,
-                                                 ui_method, ui_data);
+                                                 ui_method, ui_data, ctx->uri,
+                                                 ctx->libctx, ctx->propq);
 
         if (result == NULL) {
             ctx->_.file.last_handler->destroy_ctx(&ctx->_.file.last_handler_ctx);
@@ -930,17 +1140,17 @@ static OSSL_STORE_INFO *file_load_try_repeat(OSSL_STORE_LOADER_CTX *ctx,
     return result;
 }
 
-static void pem_free_flag(void *pem_data, int secure)
+static void pem_free_flag(void *pem_data, int secure, size_t num)
 {
     if (secure)
-        OPENSSL_secure_free(pem_data);
+        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, int secure)
+                         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,
@@ -961,7 +1171,8 @@ static int file_read_pem(BIO *bp, char **pem_name, char **pem_header,
         struct pem_pass_data pass_data;
 
         if (!PEM_get_EVP_CIPHER_INFO(*pem_header, &cipher)
-            || !file_fill_pem_pass_data(&pass_data, "PEM", ui_method, ui_data)
+            || !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;
@@ -1004,8 +1215,8 @@ static int file_name_to_uri(OSSL_STORE_LOADER_CTX *ctx, const char *name,
     assert(name != NULL);
     assert(data != NULL);
     {
-        const char *pathsep = ends_with_dirsep(ctx->_.dir.uri) ? "" : "/";
-        long calculated_length = strlen(ctx->_.dir.uri) + strlen(pathsep)
+        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);
@@ -1014,13 +1225,75 @@ static int file_name_to_uri(OSSL_STORE_LOADER_CTX *ctx, const char *name,
             return 0;
         }
 
-        OPENSSL_strlcat(*data, ctx->_.dir.uri, calculated_length);
+        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 (!ossl_isdigit(*p))
+        return 0;
+    while (ossl_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,
@@ -1039,16 +1312,17 @@ static OSSL_STORE_INFO *file_load(OSSL_STORE_LOADER_CTX *ctx,
                 if (!ctx->_.dir.end_reached) {
                     char errbuf[256];
                     assert(ctx->_.dir.last_errno != 0);
+                    OSSL_STOREerr(OSSL_STORE_F_FILE_LOAD, ERR_R_SYS_LIB);
                     errno = ctx->_.dir.last_errno;
                     ctx->errcnt++;
-                    openssl_strerror_r(errno, errbuf, sizeof(errbuf));
-                    OSSL_STOREerr(OSSL_STORE_F_FILE_LOAD, ERR_R_SYS_LIB);
-                    ERR_add_error_data(1, errbuf);
+                    if (openssl_strerror_r(errno, errbuf, sizeof(errbuf)))
+                        ERR_add_error_data(1, errbuf);
                 }
                 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;
 
@@ -1058,8 +1332,7 @@ static OSSL_STORE_INFO *file_load(OSSL_STORE_LOADER_CTX *ctx,
              * 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->_.dir.uri);
+            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;
@@ -1074,6 +1347,7 @@ static OSSL_STORE_INFO *file_load(OSSL_STORE_LOADER_CTX *ctx,
     } else {
         int matchcount = -1;
 
+     again:
         result = file_load_try_repeat(ctx, ui_method, ui_data);
         if (result != NULL)
             return result;
@@ -1090,7 +1364,7 @@ static OSSL_STORE_INFO *file_load(OSSL_STORE_LOADER_CTX *ctx,
             matchcount = -1;
             if (ctx->type == is_pem) {
                 if (!file_read_pem(ctx->_.file.file, &pem_name, &pem_header,
-                                   &data, &len, ui_method, ui_data,
+                                   &data, &len, ui_method, ui_data, ctx->uri,
                                    (ctx->flags & FILE_FLAG_SECMEM) != 0)) {
                     ctx->errcnt++;
                     goto endloop;
@@ -1136,14 +1410,21 @@ static OSSL_STORE_INFO *file_load(OSSL_STORE_LOADER_CTX *ctx,
                 ctx->errcnt++;
 
          endloop:
-            pem_free_flag(pem_name, (ctx->flags & FILE_FLAG_SECMEM) != 0);
-            pem_free_flag(pem_header, (ctx->flags & FILE_FLAG_SECMEM) != 0);
-            pem_free_flag(data, (ctx->flags & FILE_FLAG_SECMEM) != 0);
+            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)
             return NULL;
+
+        if (result != NULL
+            && ctx->expected_type != 0
+            && ctx->expected_type != OSSL_STORE_INFO_get_type(result)) {
+            OSSL_STORE_INFO_free(result);
+            goto again;
+        }
     }
 
     return result;
@@ -1167,17 +1448,25 @@ static int file_eof(OSSL_STORE_LOADER_CTX *ctx)
 
 static int file_close(OSSL_STORE_LOADER_CTX *ctx)
 {
-    if (ctx->type == is_dir) {
-        OPENSSL_DIR_end(&ctx->_.dir.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 {
-        BIO_free_all(ctx->_.file.file);
-    }
-    OSSL_STORE_LOADER_CTX_free(ctx);
-    return 1;
-}
+        /*
+         * 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;
 
-int ossl_store_file_detach_pem_bio_int(OSSL_STORE_LOADER_CTX *ctx)
-{
+        /* 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;
 }
@@ -1187,7 +1476,10 @@ static OSSL_STORE_LOADER file_loader =
         "file",
         NULL,
         file_open,
+        file_attach,
         file_ctrl,
+        file_expect,
+        file_find,
         file_load,
         file_eof,
         file_error,