OSSL_STORE: Treat URIs as files first (with exceptions), then as full URIs
authorRichard Levitte <levitte@openssl.org>
Tue, 11 Jul 2017 09:54:00 +0000 (11:54 +0200)
committerRichard Levitte <levitte@openssl.org>
Sat, 15 Jul 2017 16:53:07 +0000 (18:53 +0200)
To handle paths that contain devices (for example, C:/foo/bar.pem on
Windows), try to "open" the URI using the file scheme loader first,
and failing that, check if the device is really a scheme we know.

The "file" scheme does the same kind of thing to pick out the path
part of the URI.

An exception to this special treatment is if the URI has an authority
part (something that starts with "//" directly after what looks like a
scheme).  Such URIs will never be treated as plain file paths.

Reviewed-by: Andy Polyakov <appro@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/3907)

crypto/store/loader_file.c
crypto/store/store_lib.c

index 85ab2b4..0a9191d 100644 (file)
@@ -744,26 +744,41 @@ 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;
+    const char *paths[2], *path;
+    size_t paths_n = 0, i;
 
+    /*
+     * First step, just take the URI as is.
+     */
+    paths[paths_n++] = 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_UNSUPPORTED);
-            return NULL;
+        const char *p = &uri[5];
+
+        if (strncmp(&uri[5], "//", 2) == 0) {
+            paths_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;
+            }
         }
 
         /*
          * If the scheme "file" was an explicit part of the URI, the path must
          * be absolute.  So says RFC 8089
          */
-        if (path[0] != '/') {
+        if (p[0] != '/') {
             OSSL_STOREerr(OSSL_STORE_F_FILE_OPEN,
                           OSSL_STORE_R_PATH_MUST_BE_ABSOLUTE);
             return NULL;
@@ -771,20 +786,28 @@ static OSSL_STORE_LOADER_CTX *file_open(const OSSL_STORE_LOADER *loader,
 
 #ifdef _WIN32
         /* Windows file: URIs with a drive letter start with a / */
-        if (path[0] == '/' && path[2] == ':' && path[3] == '/')
-            path++;
+        if (p[0] == '/' && p[2] == ':' && p[3] == '/')
+            p++;
 #endif
-    } else {
-        path = uri;
+        paths[paths_n++] = p;
     }
 
 
-    if (stat(path, &st) < 0) {
-        SYSerr(SYS_F_STAT, errno);
-        ERR_add_error_data(1, path);
+    for (i = 0, path = NULL; path == NULL && i < paths_n; i++) {
+        if (stat(paths[i], &st) < 0) {
+            SYSerr(SYS_F_STAT, errno);
+            ERR_add_error_data(1, paths[i]);
+        } else {
+            path = paths[i];
+        }
+    }
+    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);
index 91faae2..9dc3a70 100644 (file)
@@ -10,6 +10,8 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "e_os.h"
+
 #include <openssl/crypto.h>
 #include <openssl/err.h>
 #include <openssl/store.h>
@@ -31,22 +33,45 @@ OSSL_STORE_CTX *OSSL_STORE_open(const char *uri, const UI_METHOD *ui_method,
                                 OSSL_STORE_post_process_info_fn post_process,
                                 void *post_process_data)
 {
-    const OSSL_STORE_LOADER *loader;
+    const OSSL_STORE_LOADER *loader = NULL;
     OSSL_STORE_LOADER_CTX *loader_ctx = NULL;
     OSSL_STORE_CTX *ctx = NULL;
-    char scheme_copy[256], *p;
-
+    char scheme_copy[256], *p, *schemes[2];
+    size_t schemes_n = 0;
+    size_t i;
+
+    /*
+     * Put the file scheme first.  If the uri does represent an existing file,
+     * possible device name and all, then it should be loaded.  Only a failed
+     * attempt at loading a local file should have us try something else.
+     */
+    schemes[schemes_n++] = "file";
+
+    /*
+     * Now, check if we have something that looks like a scheme, and add it
+     * as a second scheme.  However, also check if there's an authority start
+     * (://), because that will invalidate the previous file scheme.  Also,
+     * check that this isn't actually the file scheme, as there's no point
+     * going through that one twice!
+     */
     OPENSSL_strlcpy(scheme_copy, uri, sizeof(scheme_copy));
     if ((p = strchr(scheme_copy, ':')) != NULL) {
-        *p = '\0';
-        p = scheme_copy;
-    } else {
-        p = "file";
+        *p++ = '\0';
+        if (strcasecmp(scheme_copy, "file") != 0) {
+            if (strncmp(p, "//", 2) == 0)
+                schemes_n--;         /* Invalidate the file scheme */
+            schemes[schemes_n++] = scheme_copy;
+        }
     }
 
-    if ((loader = ossl_store_get0_loader_int(p)) == NULL
-        || (loader_ctx = loader->open(loader, uri, ui_method, ui_data)) == NULL)
+    /* Try each scheme until we find one that could open the URI */
+    for (i = 0; loader_ctx == NULL && i < schemes_n; i++) {
+        if ((loader = ossl_store_get0_loader_int(schemes[i])) != NULL)
+            loader_ctx = loader->open(loader, uri, ui_method, ui_data);
+    }
+    if (loader_ctx == NULL)
         goto done;
+
     if ((ctx = OPENSSL_zalloc(sizeof(*ctx))) == NULL) {
         OSSL_STOREerr(OSSL_STORE_F_OSSL_STORE_OPEN, ERR_R_MALLOC_FAILURE);
         goto done;