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 }