tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

LibSecret.cpp (8232B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 *
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "LibSecret.h"
      8 
      9 #include <gio/gio.h>
     10 #include <gmodule.h>
     11 #include <memory>
     12 
     13 #include "mozilla/Base64.h"
     14 #include "mozilla/GUniquePtr.h"
     15 #include "mozilla/Logging.h"
     16 #include "MainThreadUtils.h"
     17 #include "prlink.h"
     18 
     19 // This is the implementation of LibSecret, an instantiation of OSKeyStore for
     20 // Linux.
     21 
     22 using namespace mozilla;
     23 
     24 LazyLogModule gLibSecretLog("libsecret");
     25 
     26 static PRLibrary* libsecret = nullptr;
     27 
     28 typedef enum {
     29  SECRET_SCHEMA_NONE = 0,
     30  SECRET_SCHEMA_DONT_MATCH_NAME = 1 << 1
     31 } SecretSchemaFlags;
     32 
     33 typedef enum {
     34  SECRET_SCHEMA_ATTRIBUTE_STRING = 0,
     35  SECRET_SCHEMA_ATTRIBUTE_INTEGER = 1,
     36  SECRET_SCHEMA_ATTRIBUTE_BOOLEAN = 2,
     37 } SecretSchemaAttributeType;
     38 
     39 typedef struct {
     40  const gchar* name;
     41  SecretSchemaAttributeType type;
     42 } SecretSchemaAttribute;
     43 
     44 typedef struct {
     45  const gchar* name;
     46  SecretSchemaFlags flags;
     47  SecretSchemaAttribute attributes[32];
     48 
     49  /* <private> */
     50  gint reserved;
     51  gpointer reserved1;
     52  gpointer reserved2;
     53  gpointer reserved3;
     54  gpointer reserved4;
     55  gpointer reserved5;
     56  gpointer reserved6;
     57  gpointer reserved7;
     58 } SecretSchema;
     59 
     60 typedef enum {
     61  SECRET_ERROR_PROTOCOL = 1,
     62  SECRET_ERROR_IS_LOCKED = 2,
     63  SECRET_ERROR_NO_SUCH_OBJECT = 3,
     64  SECRET_ERROR_ALREADY_EXISTS = 4,
     65 } SecretError;
     66 
     67 #define SECRET_COLLECTION_DEFAULT "default"
     68 
     69 typedef gboolean (*secret_password_clear_sync_fn)(const SecretSchema*,
     70                                                  GCancellable*, GError**, ...);
     71 typedef gchar* (*secret_password_lookup_sync_fn)(const SecretSchema*,
     72                                                 GCancellable*, GError**, ...);
     73 typedef gboolean (*secret_password_store_sync_fn)(const SecretSchema*,
     74                                                  const gchar*, const gchar*,
     75                                                  const gchar*, GCancellable*,
     76                                                  GError**, ...);
     77 typedef void (*secret_password_free_fn)(const gchar*);
     78 typedef GQuark (*secret_error_get_quark_fn)();
     79 
     80 static secret_password_clear_sync_fn secret_password_clear_sync = nullptr;
     81 static secret_password_lookup_sync_fn secret_password_lookup_sync = nullptr;
     82 static secret_password_store_sync_fn secret_password_store_sync = nullptr;
     83 static secret_password_free_fn secret_password_free = nullptr;
     84 static secret_error_get_quark_fn secret_error_get_quark = nullptr;
     85 
     86 nsresult MaybeLoadLibSecret() {
     87  MOZ_ASSERT(NS_IsMainThread());
     88  if (!NS_IsMainThread()) {
     89    return NS_ERROR_NOT_SAME_THREAD;
     90  }
     91 
     92  if (!libsecret) {
     93    libsecret = PR_LoadLibrary("libsecret-1.so.0");
     94    if (!libsecret) {
     95      return NS_ERROR_NOT_AVAILABLE;
     96    }
     97 
     98 // With TSan, we cannot unload libsecret once we have loaded it because
     99 // TSan does not support unloading libraries that are matched from its
    100 // suppression list. Hence we just keep the library loaded in TSan builds.
    101 #ifdef MOZ_TSAN
    102 #  define UNLOAD_LIBSECRET(x) \
    103    do {                      \
    104    } while (0)
    105 #else
    106 #  define UNLOAD_LIBSECRET(x) PR_UnloadLibrary(x)
    107 #endif
    108 
    109 #define FIND_FUNCTION_SYMBOL(function)                                   \
    110  function = (function##_fn)PR_FindFunctionSymbol(libsecret, #function); \
    111  if (!(function)) {                                                     \
    112    UNLOAD_LIBSECRET(libsecret);                                         \
    113    libsecret = nullptr;                                                 \
    114    return NS_ERROR_NOT_AVAILABLE;                                       \
    115  }
    116    FIND_FUNCTION_SYMBOL(secret_password_clear_sync);
    117    FIND_FUNCTION_SYMBOL(secret_password_lookup_sync);
    118    FIND_FUNCTION_SYMBOL(secret_password_store_sync);
    119    FIND_FUNCTION_SYMBOL(secret_password_free);
    120    FIND_FUNCTION_SYMBOL(secret_error_get_quark);
    121 #undef FIND_FUNCTION_SYMBOL
    122  }
    123 
    124  return NS_OK;
    125 }
    126 
    127 struct ScopedDelete {
    128  void operator()(char* val) {
    129    if (val) secret_password_free(val);
    130  }
    131 };
    132 
    133 template <class T>
    134 struct ScopedMaybeDelete {
    135  void operator()(T* ptr) {
    136    if (ptr) {
    137      ScopedDelete del;
    138      del(ptr);
    139    }
    140  }
    141 };
    142 
    143 typedef std::unique_ptr<char, ScopedMaybeDelete<char>> ScopedPassword;
    144 
    145 LibSecret::LibSecret() = default;
    146 
    147 LibSecret::~LibSecret() {
    148  MOZ_ASSERT(NS_IsMainThread());
    149  if (!NS_IsMainThread()) {
    150    return;
    151  }
    152  if (libsecret) {
    153    secret_password_clear_sync = nullptr;
    154    secret_password_lookup_sync = nullptr;
    155    secret_password_store_sync = nullptr;
    156    secret_password_free = nullptr;
    157    secret_error_get_quark = nullptr;
    158    UNLOAD_LIBSECRET(libsecret);
    159    libsecret = nullptr;
    160  }
    161 }
    162 
    163 static const SecretSchema kSchema = {
    164    "mozilla.firefox",
    165    SECRET_SCHEMA_NONE,
    166    {{"string", SECRET_SCHEMA_ATTRIBUTE_STRING}, /* the label */
    167     {"NULL", SECRET_SCHEMA_ATTRIBUTE_STRING}}};
    168 
    169 nsresult LibSecret::StoreSecret(const nsACString& aSecret,
    170                                const nsACString& aLabel) {
    171  MOZ_ASSERT(secret_password_store_sync);
    172  if (!secret_password_store_sync) {
    173    return NS_ERROR_FAILURE;
    174  }
    175  // libsecret expects a null-terminated string, so to be safe we store the
    176  // secret (which could be arbitrary bytes) base64-encoded.
    177  nsAutoCString base64;
    178  nsresult rv = Base64Encode(aSecret, base64);
    179  if (NS_FAILED(rv)) {
    180    MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error base64-encoding secret"));
    181    return rv;
    182  }
    183  GUniquePtr<GError> error;
    184  bool stored = secret_password_store_sync(
    185      &kSchema, SECRET_COLLECTION_DEFAULT, PromiseFlatCString(aLabel).get(),
    186      PromiseFlatCString(base64).get(),
    187      nullptr,  // GCancellable
    188      getter_Transfers(error), "string", PromiseFlatCString(aLabel).get(),
    189      nullptr);
    190  if (error) {
    191    MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error storing secret"));
    192    return NS_ERROR_FAILURE;
    193  }
    194 
    195  return stored ? NS_OK : NS_ERROR_FAILURE;
    196 }
    197 
    198 nsresult LibSecret::DeleteSecret(const nsACString& aLabel) {
    199  MOZ_ASSERT(secret_password_clear_sync && secret_error_get_quark);
    200  if (!secret_password_clear_sync || !secret_error_get_quark) {
    201    return NS_ERROR_FAILURE;
    202  }
    203  GUniquePtr<GError> error;
    204  (void)secret_password_clear_sync(&kSchema,
    205                                   nullptr,  // GCancellable
    206                                   getter_Transfers(error), "string",
    207                                   PromiseFlatCString(aLabel).get(), nullptr);
    208  if (error && !(error->domain == secret_error_get_quark() &&
    209                 error->code == SECRET_ERROR_NO_SUCH_OBJECT)) {
    210    MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error deleting secret"));
    211    return NS_ERROR_FAILURE;
    212  }
    213 
    214  return NS_OK;
    215 }
    216 
    217 nsresult LibSecret::RetrieveSecret(const nsACString& aLabel,
    218                                   /* out */ nsACString& aSecret) {
    219  MOZ_ASSERT(secret_password_lookup_sync && secret_password_free);
    220  if (!secret_password_lookup_sync || !secret_password_free) {
    221    return NS_ERROR_FAILURE;
    222  }
    223  GUniquePtr<GError> error;
    224  aSecret.Truncate();
    225  ScopedPassword s(
    226      secret_password_lookup_sync(&kSchema,
    227                                  nullptr,  // GCancellable
    228                                  getter_Transfers(error), "string",
    229                                  PromiseFlatCString(aLabel).get(), nullptr));
    230  if (error) {
    231    // TODO the API is broken. We may end up in this case if the key
    232    // ring is locked and then we will try to overwrite it and lose data!
    233    MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error retrieving secret"));
    234    return NS_ERROR_FAILURE;
    235  }
    236  if (!s) {
    237    MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Key not found in key store"));
    238    return NS_ERROR_NOT_AVAILABLE;
    239  }
    240 
    241  // libsecret expects a null-terminated string, so to be safe we store the
    242  // secret (which could be arbitrary bytes) base64-encoded, which means we have
    243  // to base64-decode it here.
    244  nsAutoCString base64Encoded(s.get());
    245  nsresult rv = Base64Decode(base64Encoded, aSecret);
    246  if (NS_FAILED(rv)) {
    247    MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error base64-decoding secret"));
    248    return rv;
    249  }
    250 
    251  return NS_OK;
    252 }