Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Commit 92eb512

Browse files
committed
Adds a findCredentials method which yields an array of account/password objects of all matching saved credentials #56
1 parent 0e1757a commit 92eb512

12 files changed

+387
-1
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,11 @@ Find a password for the `service` in the keychain.
7575
`service` - The string service name.
7676

7777
Yields the string password, or `null` if an entry for the given service and account was not found.
78+
79+
### findCredentials(service)
80+
81+
Find all accounts and password for the `service` in the keychain.
82+
83+
`service` - The string service name.
84+
85+
Yields an array of `{ account: 'foo', password: 'bar' }`.

binding.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
'src/async.cc',
88
'src/main.cc',
99
'src/keytar.h',
10+
'src/credentials.h',
1011
],
1112
'conditions': [
1213
['OS=="mac"', {

lib/keytar.js

+6
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,11 @@ module.exports = {
4949
checkRequired(service, 'Service')
5050

5151
return callbackPromise(callback => keytar.findPassword(service, callback))
52+
},
53+
54+
findCredentials: function (service) {
55+
checkRequired(service, 'Service')
56+
57+
return callbackPromise(callback => keytar.findCredentials(service, callback))
5258
}
5359
}

spec/keytar-spec.js

+28
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var keytar = require('../')
33

44
describe("keytar", function() {
55
var service = 'keytar tests'
6+
var service2 = 'other tests'
67
var account = 'buster'
78
var password = 'secret'
89
var account2 = 'buster2'
@@ -11,11 +12,14 @@ describe("keytar", function() {
1112
beforeEach(async function() {
1213
await keytar.deletePassword(service, account),
1314
await keytar.deletePassword(service, account2)
15+
await keytar.deletePassword(service2, account)
16+
1417
})
1518

1619
afterEach(async function() {
1720
await keytar.deletePassword(service, account),
1821
await keytar.deletePassword(service, account2)
22+
await keytar.deletePassword(service2, account)
1923
})
2024

2125
describe("setPassword/getPassword(service, account)", function() {
@@ -68,4 +72,28 @@ describe("keytar", function() {
6872
assert.equal(await keytar.findPassword(service), null)
6973
})
7074
})
75+
76+
describe('findCredentials(service)', function() {
77+
it('yields an array of the credentials', async function() {
78+
await keytar.setPassword(service, account, password)
79+
await keytar.setPassword(service, account2, password2)
80+
await keytar.setPassword(service2, account, password)
81+
82+
const found = await keytar.findCredentials(service)
83+
const sorted = found.sort(function(a, b) {
84+
return a.account.localeCompare(b.account)
85+
})
86+
87+
assert.deepEqual([{account: account, password: password}, {account: account2, password: password2}], sorted)
88+
});
89+
90+
it('returns an empty array when no credentials are found', async function() {
91+
const accounts = await keytar.findCredentials(service)
92+
assert.deepEqual([], accounts)
93+
})
94+
95+
afterEach(async function() {
96+
await keytar.deletePassword(service2, account)
97+
})
98+
});
7199
})

src/async.cc

+65
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <string>
2+
#include <vector>
23

34
#include "nan.h"
45
#include "keytar.h"
@@ -145,3 +146,67 @@ void FindPasswordWorker::HandleOKCallback() {
145146

146147
callback->Call(2, argv);
147148
}
149+
150+
151+
152+
FindCredentialsWorker::FindCredentialsWorker(
153+
const std::string& service,
154+
Nan::Callback* callback
155+
) : AsyncWorker(callback),
156+
service(service) {}
157+
158+
FindCredentialsWorker::~FindCredentialsWorker() {}
159+
160+
void FindCredentialsWorker::Execute() {
161+
std::string error;
162+
KEYTAR_OP_RESULT result = keytar::FindCredentials(service,
163+
&credentials,
164+
&error);
165+
if (result == keytar::FAIL_ERROR) {
166+
SetErrorMessage(error.c_str());
167+
} else if (result == keytar::FAIL_NONFATAL) {
168+
success = false;
169+
} else {
170+
success = true;
171+
}
172+
}
173+
174+
void FindCredentialsWorker::HandleOKCallback() {
175+
Nan::HandleScope scope;
176+
177+
if (success) {
178+
v8::Local<v8::Array> val = Nan::New<v8::Array>(credentials.size());
179+
unsigned int idx = 0;
180+
std::vector<keytar::Credentials>::iterator it;
181+
for (it = credentials.begin(); it != credentials.end(); it++) {
182+
keytar::Credentials cred = *it;
183+
v8::Local<v8::Object> obj = Nan::New<v8::Object>();
184+
185+
v8::Local<v8::String> account = Nan::New<v8::String>(
186+
cred.first.data(),
187+
cred.first.length()).ToLocalChecked();
188+
189+
v8::Local<v8::String> password = Nan::New<v8::String>(
190+
cred.second.data(),
191+
cred.second.length()).ToLocalChecked();
192+
193+
obj->Set(Nan::New("account").ToLocalChecked(), account);
194+
obj->Set(Nan::New("password").ToLocalChecked(), password);
195+
196+
Nan::Set(val, idx, obj);
197+
++idx;
198+
}
199+
200+
v8::Local<v8::Value> argv[] = {
201+
Nan::Null(),
202+
val
203+
};
204+
callback->Call(2, argv);
205+
} else {
206+
v8::Local<v8::Value> argv[] = {
207+
Nan::Null(),
208+
Nan::New<v8::Array>(0)
209+
};
210+
callback->Call(2, argv);
211+
}
212+
}

src/async.h

+17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include <string>
55
#include "nan.h"
66

7+
#include "credentials.h"
8+
79
class SetPasswordWorker : public Nan::AsyncWorker {
810
public:
911
SetPasswordWorker(const std::string& service, const std::string& account, const std::string& password,
@@ -65,4 +67,19 @@ class FindPasswordWorker : public Nan::AsyncWorker {
6567
bool success;
6668
};
6769

70+
class FindCredentialsWorker : public Nan::AsyncWorker {
71+
public:
72+
FindCredentialsWorker(const std::string& service, Nan::Callback* callback);
73+
74+
~FindCredentialsWorker();
75+
76+
void Execute();
77+
void HandleOKCallback();
78+
79+
private:
80+
const std::string service;
81+
std::vector<keytar::Credentials> credentials;
82+
bool success;
83+
};
84+
6885
#endif // SRC_ASYNC_H_

src/credentials.h

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#ifndef SRC_CREDENTIALS_H_
2+
#define SRC_CREDENTIALS_H_
3+
4+
#include <string>
5+
#include <utility>
6+
7+
namespace keytar {
8+
9+
typedef std::pair<std::string, std::string> Credentials;
10+
11+
}
12+
13+
#endif // SRC_CREDENTIALS_H_

src/keytar.h

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
#define SRC_KEYTAR_H_
33

44
#include <string>
5+
#include <vector>
6+
7+
#include "credentials.h"
58

69
namespace keytar {
710

@@ -29,6 +32,10 @@ KEYTAR_OP_RESULT FindPassword(const std::string& service,
2932
std::string* password,
3033
std::string* error);
3134

35+
KEYTAR_OP_RESULT FindCredentials(const std::string& service,
36+
std::vector<Credentials>*,
37+
std::string* error);
38+
3239
} // namespace keytar
3340

3441
#endif // SRC_KEYTAR_H_

src/keytar_mac.cc

+128-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,43 @@
1+
#include <Security/Security.h>
12
#include "keytar.h"
3+
#include "credentials.h"
24

3-
#include <Security/Security.h>
45

56
namespace keytar {
67

8+
/**
9+
* Converts a CFString to a std::string
10+
*
11+
* This either uses CFStringGetCStringPtr or (if that fails)
12+
* CFStringGetCString, trying to be as efficient as possible.
13+
*/
14+
const std::string CFStringToStdString(CFStringRef cfstring) {
15+
const char* cstr = CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8);
16+
17+
if (cstr != NULL) {
18+
return std::string(cstr);
19+
}
20+
21+
CFIndex length = CFStringGetLength(cfstring);
22+
// Worst case: 2 bytes per character + NUL
23+
CFIndex cstrPtrLen = length * 2 + 1;
24+
char* cstrPtr = static_cast<char*>(malloc(cstrPtrLen));
25+
26+
Boolean result = CFStringGetCString(cfstring,
27+
cstrPtr,
28+
cstrPtrLen,
29+
kCFStringEncodingUTF8);
30+
31+
std::string stdstring;
32+
if (result) {
33+
stdstring = std::string(cstrPtr);
34+
}
35+
36+
free(cstrPtr);
37+
38+
return stdstring;
39+
}
40+
741
const std::string errorStatusToString(OSStatus status) {
842
std::string errorStr;
943
CFStringRef errorMessageString = SecCopyErrorMessageString(status, NULL);
@@ -149,4 +183,97 @@ KEYTAR_OP_RESULT FindPassword(const std::string& service,
149183
return SUCCESS;
150184
}
151185

186+
Credentials getCredentialsForItem(CFDictionaryRef item) {
187+
CFStringRef service = (CFStringRef) CFDictionaryGetValue(item,
188+
kSecAttrService);
189+
CFStringRef account = (CFStringRef) CFDictionaryGetValue(item,
190+
kSecAttrAccount);
191+
192+
CFMutableDictionaryRef query = CFDictionaryCreateMutable(
193+
NULL,
194+
0,
195+
&kCFTypeDictionaryKeyCallBacks,
196+
&kCFTypeDictionaryValueCallBacks);
197+
198+
CFDictionaryAddValue(query, kSecAttrService, service);
199+
CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword);
200+
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitOne);
201+
CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue);
202+
CFDictionaryAddValue(query, kSecReturnData, kCFBooleanTrue);
203+
CFDictionaryAddValue(query, kSecAttrAccount, account);
204+
205+
CFTypeRef result;
206+
OSStatus status = SecItemCopyMatching((CFDictionaryRef) query, &result);
207+
208+
if (status == errSecSuccess) {
209+
CFDataRef passwordData = (CFDataRef) CFDictionaryGetValue(
210+
(CFDictionaryRef) result,
211+
CFSTR("v_Data"));
212+
CFStringRef password = CFStringCreateFromExternalRepresentation(
213+
NULL,
214+
passwordData,
215+
kCFStringEncodingUTF8);
216+
217+
Credentials cred = Credentials(
218+
CFStringToStdString(account),
219+
CFStringToStdString(password));
220+
CFRelease(password);
221+
222+
return cred;
223+
}
224+
225+
return Credentials();
226+
}
227+
228+
KEYTAR_OP_RESULT FindCredentials(const std::string& service,
229+
std::vector<Credentials>* credentials,
230+
std::string* error) {
231+
CFStringRef serviceStr = CFStringCreateWithCString(
232+
NULL,
233+
service.c_str(),
234+
kCFStringEncodingUTF8);
235+
236+
CFMutableDictionaryRef query = CFDictionaryCreateMutable(
237+
NULL,
238+
0,
239+
&kCFTypeDictionaryKeyCallBacks,
240+
&kCFTypeDictionaryValueCallBacks);
241+
CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword);
242+
CFDictionaryAddValue(query, kSecAttrService, serviceStr);
243+
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
244+
CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
245+
CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue);
246+
247+
CFTypeRef result;
248+
OSStatus status = SecItemCopyMatching((CFDictionaryRef) query, &result);
249+
250+
if (status == errSecSuccess) {
251+
CFArrayRef resultArray = (CFArrayRef) result;
252+
int resultCount = CFArrayGetCount(resultArray);
253+
254+
for (int idx = 0; idx < resultCount; idx++) {
255+
CFDictionaryRef item = (CFDictionaryRef) CFArrayGetValueAtIndex(
256+
resultArray,
257+
idx);
258+
259+
Credentials cred = getCredentialsForItem(item);
260+
credentials->push_back(cred);
261+
}
262+
} else if (status == errSecItemNotFound) {
263+
return FAIL_NONFATAL;
264+
} else {
265+
*error = errorStatusToString(status);
266+
return FAIL_ERROR;
267+
}
268+
269+
270+
if (result != NULL) {
271+
CFRelease(result);
272+
}
273+
274+
CFRelease(query);
275+
276+
return SUCCESS;
277+
}
278+
152279
} // namespace keytar

0 commit comments

Comments
 (0)