1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/password_manager/native_backend_kwallet_x.h"
6
7 #include <sstream>
8
9 #include "base/logging.h"
10 #include "base/pickle.h"
11 #include "base/stl_util-inl.h"
12 #include "base/string_util.h"
13 #include "content/browser/browser_thread.h"
14
15 using std::string;
16 using std::vector;
17 using webkit_glue::PasswordForm;
18
19 // We could localize these strings, but then changing your locale would cause
20 // you to lose access to all your stored passwords. Maybe best not to do that.
21 const char* NativeBackendKWallet::kAppId = "Chrome";
22 const char* NativeBackendKWallet::kKWalletFolder = "Chrome Form Data";
23
24 const char* NativeBackendKWallet::kKWalletServiceName = "org.kde.kwalletd";
25 const char* NativeBackendKWallet::kKWalletPath = "/modules/kwalletd";
26 const char* NativeBackendKWallet::kKWalletInterface = "org.kde.KWallet";
27 const char* NativeBackendKWallet::kKLauncherServiceName = "org.kde.klauncher";
28 const char* NativeBackendKWallet::kKLauncherPath = "/KLauncher";
29 const char* NativeBackendKWallet::kKLauncherInterface = "org.kde.KLauncher";
30
NativeBackendKWallet()31 NativeBackendKWallet::NativeBackendKWallet()
32 : error_(NULL),
33 connection_(NULL),
34 proxy_(NULL) {
35 }
36
~NativeBackendKWallet()37 NativeBackendKWallet::~NativeBackendKWallet() {
38 if (proxy_)
39 g_object_unref(proxy_);
40 }
41
Init()42 bool NativeBackendKWallet::Init() {
43 // Get a connection to the session bus.
44 connection_ = dbus_g_bus_get(DBUS_BUS_SESSION, &error_);
45 if (CheckError())
46 return false;
47
48 if (!InitWallet()) {
49 // kwalletd may not be running. Try to start it and try again.
50 if (!StartKWalletd() || !InitWallet())
51 return false;
52 }
53
54 return true;
55 }
56
StartKWalletd()57 bool NativeBackendKWallet::StartKWalletd() {
58 // Sadly kwalletd doesn't use DBUS activation, so we have to make a call to
59 // klauncher to start it.
60 DBusGProxy* klauncher_proxy =
61 dbus_g_proxy_new_for_name(connection_, kKLauncherServiceName,
62 kKLauncherPath, kKLauncherInterface);
63
64 char* empty_string_list = NULL;
65 int ret = 1;
66 char* error = NULL;
67 dbus_g_proxy_call(klauncher_proxy, "start_service_by_desktop_name", &error_,
68 G_TYPE_STRING, "kwalletd", // serviceName
69 G_TYPE_STRV, &empty_string_list, // urls
70 G_TYPE_STRV, &empty_string_list, // envs
71 G_TYPE_STRING, "", // startup_id
72 G_TYPE_BOOLEAN, (gboolean) false, // blind
73 G_TYPE_INVALID,
74 G_TYPE_INT, &ret, // result
75 G_TYPE_STRING, NULL, // dubsName
76 G_TYPE_STRING, &error, // error
77 G_TYPE_INT, NULL, // pid
78 G_TYPE_INVALID);
79
80 if (error && *error) {
81 LOG(ERROR) << "Error launching kwalletd: " << error;
82 ret = 1; // Make sure we return false after freeing.
83 }
84
85 g_free(error);
86 g_object_unref(klauncher_proxy);
87
88 if (CheckError() || ret != 0)
89 return false;
90 return true;
91 }
92
InitWallet()93 bool NativeBackendKWallet::InitWallet() {
94 // Make a proxy to KWallet.
95 proxy_ = dbus_g_proxy_new_for_name(connection_, kKWalletServiceName,
96 kKWalletPath, kKWalletInterface);
97
98 // Check KWallet is enabled.
99 gboolean is_enabled = false;
100 dbus_g_proxy_call(proxy_, "isEnabled", &error_,
101 G_TYPE_INVALID,
102 G_TYPE_BOOLEAN, &is_enabled,
103 G_TYPE_INVALID);
104 if (CheckError() || !is_enabled)
105 return false;
106
107 // Get the wallet name.
108 char* wallet_name = NULL;
109 dbus_g_proxy_call(proxy_, "networkWallet", &error_,
110 G_TYPE_INVALID,
111 G_TYPE_STRING, &wallet_name,
112 G_TYPE_INVALID);
113 if (CheckError() || !wallet_name)
114 return false;
115
116 wallet_name_.assign(wallet_name);
117 g_free(wallet_name);
118
119 return true;
120 }
121
AddLogin(const PasswordForm & form)122 bool NativeBackendKWallet::AddLogin(const PasswordForm& form) {
123 int wallet_handle = WalletHandle();
124 if (wallet_handle == kInvalidKWalletHandle)
125 return false;
126
127 PasswordFormList forms;
128 GetLoginsList(&forms, form.signon_realm, wallet_handle);
129
130 forms.push_back(new PasswordForm(form));
131 bool ok = SetLoginsList(forms, form.signon_realm, wallet_handle);
132
133 STLDeleteElements(&forms);
134 return ok;
135 }
136
UpdateLogin(const PasswordForm & form)137 bool NativeBackendKWallet::UpdateLogin(const PasswordForm& form) {
138 int wallet_handle = WalletHandle();
139 if (wallet_handle == kInvalidKWalletHandle)
140 return false;
141
142 PasswordFormList forms;
143 GetLoginsList(&forms, form.signon_realm, wallet_handle);
144
145 for (size_t i = 0; i < forms.size(); ++i) {
146 if (CompareForms(form, *forms[i], true))
147 *forms[i] = form;
148 }
149
150 bool ok = SetLoginsList(forms, form.signon_realm, wallet_handle);
151
152 STLDeleteElements(&forms);
153 return ok;
154 }
155
RemoveLogin(const PasswordForm & form)156 bool NativeBackendKWallet::RemoveLogin(const PasswordForm& form) {
157 int wallet_handle = WalletHandle();
158 if (wallet_handle == kInvalidKWalletHandle)
159 return false;
160
161 PasswordFormList all_forms;
162 GetLoginsList(&all_forms, form.signon_realm, wallet_handle);
163
164 PasswordFormList kept_forms;
165 kept_forms.reserve(all_forms.size());
166 for (size_t i = 0; i < all_forms.size(); ++i) {
167 if (CompareForms(form, *all_forms[i], false))
168 delete all_forms[i];
169 else
170 kept_forms.push_back(all_forms[i]);
171 }
172
173 // Update the entry in the wallet, possibly deleting it.
174 bool ok = SetLoginsList(kept_forms, form.signon_realm, wallet_handle);
175
176 STLDeleteElements(&kept_forms);
177 return ok;
178 }
179
RemoveLoginsCreatedBetween(const base::Time & delete_begin,const base::Time & delete_end)180 bool NativeBackendKWallet::RemoveLoginsCreatedBetween(
181 const base::Time& delete_begin,
182 const base::Time& delete_end) {
183 int wallet_handle = WalletHandle();
184 if (wallet_handle == kInvalidKWalletHandle)
185 return false;
186
187 // We could probably also use readEntryList here.
188 char** realm_list = NULL;
189 dbus_g_proxy_call(proxy_, "entryList", &error_,
190 G_TYPE_INT, wallet_handle, // handle
191 G_TYPE_STRING, kKWalletFolder, // folder
192 G_TYPE_STRING, kAppId, // appid
193 G_TYPE_INVALID,
194 G_TYPE_STRV, &realm_list,
195 G_TYPE_INVALID);
196 if (CheckError())
197 return false;
198
199 bool ok = true;
200 for (char** realm = realm_list; *realm; ++realm) {
201 GArray* byte_array = NULL;
202 dbus_g_proxy_call(proxy_, "readEntry", &error_,
203 G_TYPE_INT, wallet_handle, // handle
204 G_TYPE_STRING, kKWalletFolder, // folder
205 G_TYPE_STRING, *realm, // key
206 G_TYPE_STRING, kAppId, // appid
207 G_TYPE_INVALID,
208 DBUS_TYPE_G_UCHAR_ARRAY, &byte_array,
209 G_TYPE_INVALID);
210
211 if (CheckError() || !byte_array ||
212 !CheckSerializedValue(byte_array, *realm)) {
213 continue;
214 }
215
216 string signon_realm(*realm);
217 Pickle pickle(byte_array->data, byte_array->len);
218 PasswordFormList all_forms;
219 DeserializeValue(signon_realm, pickle, &all_forms);
220 g_array_free(byte_array, true);
221
222 PasswordFormList kept_forms;
223 kept_forms.reserve(all_forms.size());
224 for (size_t i = 0; i < all_forms.size(); ++i) {
225 if (delete_begin <= all_forms[i]->date_created &&
226 (delete_end.is_null() || all_forms[i]->date_created < delete_end)) {
227 delete all_forms[i];
228 } else {
229 kept_forms.push_back(all_forms[i]);
230 }
231 }
232
233 if (!SetLoginsList(kept_forms, signon_realm, wallet_handle))
234 ok = false;
235 STLDeleteElements(&kept_forms);
236 }
237 g_strfreev(realm_list);
238 return ok;
239 }
240
GetLogins(const PasswordForm & form,PasswordFormList * forms)241 bool NativeBackendKWallet::GetLogins(const PasswordForm& form,
242 PasswordFormList* forms) {
243 int wallet_handle = WalletHandle();
244 if (wallet_handle == kInvalidKWalletHandle)
245 return false;
246 return GetLoginsList(forms, form.signon_realm, wallet_handle);
247 }
248
GetLoginsCreatedBetween(const base::Time & get_begin,const base::Time & get_end,PasswordFormList * forms)249 bool NativeBackendKWallet::GetLoginsCreatedBetween(const base::Time& get_begin,
250 const base::Time& get_end,
251 PasswordFormList* forms) {
252 int wallet_handle = WalletHandle();
253 if (wallet_handle == kInvalidKWalletHandle)
254 return false;
255 return GetLoginsList(forms, get_begin, get_end, wallet_handle);
256 }
257
GetAutofillableLogins(PasswordFormList * forms)258 bool NativeBackendKWallet::GetAutofillableLogins(PasswordFormList* forms) {
259 int wallet_handle = WalletHandle();
260 if (wallet_handle == kInvalidKWalletHandle)
261 return false;
262 return GetLoginsList(forms, true, wallet_handle);
263 }
264
GetBlacklistLogins(PasswordFormList * forms)265 bool NativeBackendKWallet::GetBlacklistLogins(PasswordFormList* forms) {
266 int wallet_handle = WalletHandle();
267 if (wallet_handle == kInvalidKWalletHandle)
268 return false;
269 return GetLoginsList(forms, false, wallet_handle);
270 }
271
GetLoginsList(PasswordFormList * forms,const string & signon_realm,int wallet_handle)272 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
273 const string& signon_realm,
274 int wallet_handle) {
275 // Is there an entry in the wallet?
276 gboolean has_entry = false;
277 dbus_g_proxy_call(proxy_, "hasEntry", &error_,
278 G_TYPE_INT, wallet_handle, // handle
279 G_TYPE_STRING, kKWalletFolder, // folder
280 G_TYPE_STRING, signon_realm.c_str(), // key
281 G_TYPE_STRING, kAppId, // appid
282 G_TYPE_INVALID,
283 G_TYPE_BOOLEAN, &has_entry,
284 G_TYPE_INVALID);
285
286 if (CheckError())
287 return false;
288 if (!has_entry) {
289 // This is not an error. There just isn't a matching entry.
290 return true;
291 }
292
293 GArray* byte_array = NULL;
294 dbus_g_proxy_call(proxy_, "readEntry", &error_,
295 G_TYPE_INT, wallet_handle, // handle
296 G_TYPE_STRING, kKWalletFolder, // folder
297 G_TYPE_STRING, signon_realm.c_str(), // key
298 G_TYPE_STRING, kAppId, // appid
299 G_TYPE_INVALID,
300 DBUS_TYPE_G_UCHAR_ARRAY, &byte_array,
301 G_TYPE_INVALID);
302
303 if (CheckError() || !byte_array)
304 return false;
305 if (!CheckSerializedValue(byte_array, signon_realm.c_str())) {
306 // This is weird, but we choose not to call it an error. There's an invalid
307 // entry somehow, but by pretending it just doesn't exist, we make it easier
308 // to repair without having to delete it using kwalletmanager (that is, by
309 // just saving a new password within this realm to overwrite it).
310 g_array_free(byte_array, true);
311 return true;
312 }
313
314 Pickle pickle(byte_array->data, byte_array->len);
315 DeserializeValue(signon_realm, pickle, forms);
316 g_array_free(byte_array, true);
317
318 return true;
319 }
320
GetLoginsList(PasswordFormList * forms,bool autofillable,int wallet_handle)321 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
322 bool autofillable,
323 int wallet_handle) {
324 PasswordFormList all_forms;
325 if (!GetAllLogins(&all_forms, wallet_handle))
326 return false;
327
328 // We have to read all the entries, and then filter them here.
329 forms->reserve(forms->size() + all_forms.size());
330 for (size_t i = 0; i < all_forms.size(); ++i) {
331 if (all_forms[i]->blacklisted_by_user == !autofillable)
332 forms->push_back(all_forms[i]);
333 else
334 delete all_forms[i];
335 }
336
337 return true;
338 }
339
GetLoginsList(PasswordFormList * forms,const base::Time & begin,const base::Time & end,int wallet_handle)340 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
341 const base::Time& begin,
342 const base::Time& end,
343 int wallet_handle) {
344 PasswordFormList all_forms;
345 if (!GetAllLogins(&all_forms, wallet_handle))
346 return false;
347
348 // We have to read all the entries, and then filter them here.
349 forms->reserve(forms->size() + all_forms.size());
350 for (size_t i = 0; i < all_forms.size(); ++i) {
351 if (begin <= all_forms[i]->date_created &&
352 (end.is_null() || all_forms[i]->date_created < end)) {
353 forms->push_back(all_forms[i]);
354 } else {
355 delete all_forms[i];
356 }
357 }
358
359 return true;
360 }
361
GetAllLogins(PasswordFormList * forms,int wallet_handle)362 bool NativeBackendKWallet::GetAllLogins(PasswordFormList* forms,
363 int wallet_handle) {
364 // We could probably also use readEntryList here.
365 char** realm_list = NULL;
366 dbus_g_proxy_call(proxy_, "entryList", &error_,
367 G_TYPE_INT, wallet_handle, // handle
368 G_TYPE_STRING, kKWalletFolder, // folder
369 G_TYPE_STRING, kAppId, // appid
370 G_TYPE_INVALID,
371 G_TYPE_STRV, &realm_list,
372 G_TYPE_INVALID);
373 if (CheckError())
374 return false;
375
376 for (char** realm = realm_list; *realm; ++realm) {
377 GArray* byte_array = NULL;
378 dbus_g_proxy_call(proxy_, "readEntry", &error_,
379 G_TYPE_INT, wallet_handle, // handle
380 G_TYPE_STRING, kKWalletFolder, // folder
381 G_TYPE_STRING, *realm, // key
382 G_TYPE_STRING, kAppId, // appid
383 G_TYPE_INVALID,
384 DBUS_TYPE_G_UCHAR_ARRAY, &byte_array,
385 G_TYPE_INVALID);
386
387 if (CheckError() || !byte_array ||
388 !CheckSerializedValue(byte_array, *realm)) {
389 continue;
390 }
391
392 Pickle pickle(byte_array->data, byte_array->len);
393 DeserializeValue(*realm, pickle, forms);
394 g_array_free(byte_array, true);
395 }
396 g_strfreev(realm_list);
397 return true;
398 }
399
SetLoginsList(const PasswordFormList & forms,const string & signon_realm,int wallet_handle)400 bool NativeBackendKWallet::SetLoginsList(const PasswordFormList& forms,
401 const string& signon_realm,
402 int wallet_handle) {
403 if (forms.empty()) {
404 // No items left? Remove the entry from the wallet.
405 int ret = 0;
406 dbus_g_proxy_call(proxy_, "removeEntry", &error_,
407 G_TYPE_INT, wallet_handle, // handle
408 G_TYPE_STRING, kKWalletFolder, // folder
409 G_TYPE_STRING, signon_realm.c_str(), // key
410 G_TYPE_STRING, kAppId, // appid
411 G_TYPE_INVALID,
412 G_TYPE_INT, &ret,
413 G_TYPE_INVALID);
414 CheckError();
415 if (ret != 0)
416 LOG(ERROR) << "Bad return code " << ret << " from KWallet removeEntry";
417 return ret == 0;
418 }
419
420 Pickle value;
421 SerializeValue(forms, &value);
422
423 // Convert the pickled bytes to a GByteArray.
424 GArray* byte_array = g_array_sized_new(false, false, sizeof(char),
425 value.size());
426 g_array_append_vals(byte_array, value.data(), value.size());
427
428 // Make the call.
429 int ret = 0;
430 dbus_g_proxy_call(proxy_, "writeEntry", &error_,
431 G_TYPE_INT, wallet_handle, // handle
432 G_TYPE_STRING, kKWalletFolder, // folder
433 G_TYPE_STRING, signon_realm.c_str(), // key
434 DBUS_TYPE_G_UCHAR_ARRAY, byte_array, // value
435 G_TYPE_STRING, kAppId, // appid
436 G_TYPE_INVALID,
437 G_TYPE_INT, &ret,
438 G_TYPE_INVALID);
439 g_array_free(byte_array, true);
440
441 CheckError();
442 if (ret != 0)
443 LOG(ERROR) << "Bad return code " << ret << " from KWallet writeEntry";
444 return ret == 0;
445 }
446
CompareForms(const PasswordForm & a,const PasswordForm & b,bool update_check)447 bool NativeBackendKWallet::CompareForms(const PasswordForm& a,
448 const PasswordForm& b,
449 bool update_check) {
450 // An update check doesn't care about the submit element.
451 if (!update_check && a.submit_element != b.submit_element)
452 return false;
453 return a.origin == b.origin &&
454 a.password_element == b.password_element &&
455 a.signon_realm == b.signon_realm &&
456 a.username_element == b.username_element &&
457 a.username_value == b.username_value;
458 }
459
SerializeValue(const PasswordFormList & forms,Pickle * pickle)460 void NativeBackendKWallet::SerializeValue(const PasswordFormList& forms,
461 Pickle* pickle) {
462 pickle->WriteInt(kPickleVersion);
463 pickle->WriteSize(forms.size());
464 for (PasswordFormList::const_iterator it = forms.begin() ;
465 it != forms.end() ; ++it) {
466 const PasswordForm* form = *it;
467 pickle->WriteInt(form->scheme);
468 pickle->WriteString(form->origin.spec());
469 pickle->WriteString(form->action.spec());
470 pickle->WriteString16(form->username_element);
471 pickle->WriteString16(form->username_value);
472 pickle->WriteString16(form->password_element);
473 pickle->WriteString16(form->password_value);
474 pickle->WriteString16(form->submit_element);
475 pickle->WriteBool(form->ssl_valid);
476 pickle->WriteBool(form->preferred);
477 pickle->WriteBool(form->blacklisted_by_user);
478 pickle->WriteInt64(form->date_created.ToTimeT());
479 }
480 }
481
CheckSerializedValue(const GArray * byte_array,const char * realm)482 bool NativeBackendKWallet::CheckSerializedValue(const GArray* byte_array,
483 const char* realm) {
484 const Pickle::Header* header =
485 reinterpret_cast<const Pickle::Header*>(byte_array->data);
486 if (byte_array->len < sizeof(*header) ||
487 header->payload_size > byte_array->len - sizeof(*header)) {
488 LOG(WARNING) << "Invalid KWallet entry detected (realm: " << realm << ")";
489 return false;
490 }
491 return true;
492 }
493
DeserializeValue(const string & signon_realm,const Pickle & pickle,PasswordFormList * forms)494 void NativeBackendKWallet::DeserializeValue(const string& signon_realm,
495 const Pickle& pickle,
496 PasswordFormList* forms) {
497 void* iter = NULL;
498
499 int version = -1;
500 if (!pickle.ReadInt(&iter, &version) || version != kPickleVersion) {
501 // This is the only version so far, so anything else is an error.
502 LOG(ERROR) << "Failed to deserialize KWallet entry "
503 << "(realm: " << signon_realm << ")";
504 return;
505 }
506
507 size_t count = 0;
508 if (!pickle.ReadSize(&iter, &count)) {
509 LOG(ERROR) << "Failed to deserialize KWallet entry "
510 << "(realm: " << signon_realm << ")";
511 return;
512 }
513
514 forms->reserve(forms->size() + count);
515 for (size_t i = 0; i < count; ++i) {
516 scoped_ptr<PasswordForm> form(new PasswordForm());
517 form->signon_realm.assign(signon_realm);
518
519 int scheme = 0;
520 int64 date_created = 0;
521 // Note that these will be read back in the order listed due to
522 // short-circuit evaluation. This is important.
523 if (!pickle.ReadInt(&iter, &scheme) ||
524 !ReadGURL(pickle, &iter, &form->origin) ||
525 !ReadGURL(pickle, &iter, &form->action) ||
526 !pickle.ReadString16(&iter, &form->username_element) ||
527 !pickle.ReadString16(&iter, &form->username_value) ||
528 !pickle.ReadString16(&iter, &form->password_element) ||
529 !pickle.ReadString16(&iter, &form->password_value) ||
530 !pickle.ReadString16(&iter, &form->submit_element) ||
531 !pickle.ReadBool(&iter, &form->ssl_valid) ||
532 !pickle.ReadBool(&iter, &form->preferred) ||
533 !pickle.ReadBool(&iter, &form->blacklisted_by_user) ||
534 !pickle.ReadInt64(&iter, &date_created)) {
535 LOG(ERROR) << "Failed to deserialize KWallet entry "
536 << "(realm: " << signon_realm << ")";
537 break;
538 }
539 form->scheme = static_cast<PasswordForm::Scheme>(scheme);
540 form->date_created = base::Time::FromTimeT(date_created);
541 forms->push_back(form.release());
542 }
543 }
544
ReadGURL(const Pickle & pickle,void ** iter,GURL * url)545 bool NativeBackendKWallet::ReadGURL(const Pickle& pickle, void** iter,
546 GURL* url) {
547 string url_string;
548 if (!pickle.ReadString(iter, &url_string)) {
549 LOG(ERROR) << "Failed to deserialize URL";
550 *url = GURL();
551 return false;
552 }
553 *url = GURL(url_string);
554 return true;
555 }
556
CheckError()557 bool NativeBackendKWallet::CheckError() {
558 if (error_) {
559 LOG(ERROR) << "Failed to complete KWallet call: " << error_->message;
560 g_error_free(error_);
561 error_ = NULL;
562 return true;
563 }
564 return false;
565 }
566
WalletHandle()567 int NativeBackendKWallet::WalletHandle() {
568 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
569 // Open the wallet.
570 int handle = kInvalidKWalletHandle;
571 dbus_g_proxy_call(proxy_, "open", &error_,
572 G_TYPE_STRING, wallet_name_.c_str(), // wallet
573 G_TYPE_INT64, 0LL, // wid
574 G_TYPE_STRING, kAppId, // appid
575 G_TYPE_INVALID,
576 G_TYPE_INT, &handle,
577 G_TYPE_INVALID);
578 if (CheckError() || handle == kInvalidKWalletHandle)
579 return kInvalidKWalletHandle;
580
581 // Check if our folder exists.
582 gboolean has_folder = false;
583 dbus_g_proxy_call(proxy_, "hasFolder", &error_,
584 G_TYPE_INT, handle, // handle
585 G_TYPE_STRING, kKWalletFolder, // folder
586 G_TYPE_STRING, kAppId, // appid
587 G_TYPE_INVALID,
588 G_TYPE_BOOLEAN, &has_folder,
589 G_TYPE_INVALID);
590 if (CheckError())
591 return kInvalidKWalletHandle;
592
593 // Create it if it didn't.
594 if (!has_folder) {
595 gboolean success = false;
596 dbus_g_proxy_call(proxy_, "createFolder", &error_,
597 G_TYPE_INT, handle, // handle
598 G_TYPE_STRING, kKWalletFolder, // folder
599 G_TYPE_STRING, kAppId, // appid
600 G_TYPE_INVALID,
601 G_TYPE_BOOLEAN, &success,
602 G_TYPE_INVALID);
603 if (CheckError() || !success)
604 return kInvalidKWalletHandle;
605 }
606
607 return handle;
608 }
609