Initial commit

This commit is contained in:
2025-03-07 19:22:02 +01:00
commit 4a98255d83
55743 changed files with 5280367 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2013 GitHub Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+94
View File
@@ -0,0 +1,94 @@
# keytar - Node module to manage system keychain
[![Travis Build Status](https://travis-ci.org/atom/node-keytar.svg?branch=master)](https://travis-ci.org/atom/node-keytar)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/atom/node-keytar?svg=true)](https://ci.appveyor.com/project/Atom/node-keytar)
[![Dependency Status](https://david-dm.org/atom/node-keytar.svg)](https://david-dm.org/atom/node-keytar)
A native Node module to get, add, replace, and delete passwords in system's keychain. On macOS the passwords are managed by the Keychain, on Linux they are managed by the Secret Service API/libsecret, and on Windows they are managed by Credential Vault.
## Installing
```sh
npm install keytar
```
### On Linux
Currently this library uses `libsecret` so you may need to install it before running `npm install`.
Depending on your distribution, you will need to run the following command:
* Debian/Ubuntu: `sudo apt-get install libsecret-1-dev`
* Red Hat-based: `sudo yum install libsecret-devel`
* Arch Linux: `sudo pacman -S libsecret`
## Building
* Clone the repository
* Run `npm install`
* Run `npm test` to run the tests
## Supported versions
Each release of `keytar` includes prebuilt binaries for the versions of Node and Electron that are actively supported by these projects. Please refer to the release documentation for [Node](https://github.com/nodejs/Release) and [Electron](https://electronjs.org/docs/tutorial/support) to see what is supported currently.
## Bindings from other languages
- [Rust](https://crates.io/crates/keytar)
## Docs
```javascript
const keytar = require('keytar')
```
Every function in keytar is asynchronous and returns a promise. The promise will be rejected with any error that occurs or will be resolved with the function's "yields" value.
### getPassword(service, account)
Get the stored password for the `service` and `account`.
`service` - The string service name.
`account` - The string account name.
Yields the string password or `null` if an entry for the given service and account was not found.
### setPassword(service, account, password)
Save the `password` for the `service` and `account` to the keychain. Adds a new entry if necessary, or updates an existing entry if one exists.
`service` - The string service name.
`account` - The string account name.
`password` - The string password.
Yields nothing.
### deletePassword(service, account)
Delete the stored password for the `service` and `account`.
`service` - The string service name.
`account` - The string account name.
Yields `true` if a password was deleted, or `false` if an entry with the given service and account was not found.
### findCredentials(service)
Find all accounts and password for the `service` in the keychain.
`service` - The string service name.
Yields an array of `{ account: 'foo', password: 'bar' }`.
### findPassword(service)
Find a password for the `service` in the keychain. This is ideal for scenarios where an `account` is not required.
`service` - The string service name.
Yields the string password, or `null` if an entry for the given service was not found.
+66
View File
@@ -0,0 +1,66 @@
{
'targets': [
{
'target_name': 'keytar',
'defines': [
"NAPI_VERSION=<(napi_build_version)",
],
'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
'xcode_settings': { 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'CLANG_CXX_LIBRARY': 'libc++',
'MACOSX_DEPLOYMENT_TARGET': '10.7',
},
'msvs_settings': {
'VCCLCompilerTool': { 'ExceptionHandling': 1 },
},
'include_dirs': ["<!(node -p \"require('node-addon-api').include_dir\")"],
'sources': [
'src/async.cc',
'src/main.cc',
'src/keytar.h',
'src/credentials.h',
],
'conditions': [
['OS=="mac"', {
'sources': [
'src/keytar_mac.cc',
],
'link_settings': {
'libraries': [
'$(SDKROOT)/System/Library/Frameworks/AppKit.framework',
],
},
}],
['OS=="win"', {
'sources': [
'src/keytar_win.cc',
],
'msvs_disabled_warnings': [
4267, # conversion from 'size_t' to 'int', possible loss of data
4530, # C++ exception handler used, but unwind semantics are not enabled
4506, # no definition for inline function
],
}],
['OS not in ["mac", "win"]', {
'sources': [
'src/keytar_posix.cc',
],
'cflags': [
'<!(pkg-config --cflags libsecret-1)',
'-Wno-missing-field-initializers',
'-Wno-deprecated-declarations',
],
'link_settings': {
'ldflags': [
'<!(pkg-config --libs-only-L --libs-only-other libsecret-1)',
],
'libraries': [
'<!(pkg-config --libs-only-l libsecret-1)',
],
},
}]
],
}
]
}
BIN
View File
Binary file not shown.
+51
View File
@@ -0,0 +1,51 @@
// Definitions by: Milan Burda <https://github.com/miniak>, Brendan Forster <https://github.com/shiftkey>, Hari Juturu <https://github.com/juturu>
// Adapted from DefinitelyTyped: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/keytar/index.d.ts
/**
* Get the stored password for the service and account.
*
* @param service The string service name.
* @param account The string account name.
*
* @returns A promise for the password string.
*/
export declare function getPassword(service: string, account: string): Promise<string | null>;
/**
* Add the password for the service and account to the keychain.
*
* @param service The string service name.
* @param account The string account name.
* @param password The string password.
*
* @returns A promise for the set password completion.
*/
export declare function setPassword(service: string, account: string, password: string): Promise<void>;
/**
* Delete the stored password for the service and account.
*
* @param service The string service name.
* @param account The string account name.
*
* @returns A promise for the deletion status. True on success.
*/
export declare function deletePassword(service: string, account: string): Promise<boolean>;
/**
* Find a password for the service in the keychain.
*
* @param service The string service name.
*
* @returns A promise for the password string.
*/
export declare function findPassword(service: string): Promise<string | null>;
/**
* Find all accounts and passwords for `service` in the keychain.
*
* @param service The string service name.
*
* @returns A promise for the array of found credentials.
*/
export declare function findCredentials(service: string): Promise<Array<{ account: string, password: string}>>;
+43
View File
@@ -0,0 +1,43 @@
var keytar = require('../build/Release/keytar.node')
function checkRequired(val, name) {
if (!val || val.length <= 0) {
throw new Error(name + ' is required.');
}
}
module.exports = {
getPassword: function (service, account) {
checkRequired(service, 'Service')
checkRequired(account, 'Account')
return keytar.getPassword(service, account)
},
setPassword: function (service, account, password) {
checkRequired(service, 'Service')
checkRequired(account, 'Account')
checkRequired(password, 'Password')
return keytar.setPassword(service, account, password)
},
deletePassword: function (service, account) {
checkRequired(service, 'Service')
checkRequired(account, 'Account')
return keytar.deletePassword(service, account)
},
findPassword: function (service) {
checkRequired(service, 'Service')
return keytar.findPassword(service)
},
findCredentials: function (service) {
checkRequired(service, 'Service')
return keytar.findCredentials(service)
}
}
+66
View File
@@ -0,0 +1,66 @@
{
"main": "./lib/keytar.js",
"typings": "keytar.d.ts",
"name": "keytar",
"description": "Bindings to native Mac/Linux/Windows password APIs",
"version": "7.9.0",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/atom/node-keytar.git"
},
"bugs": {
"url": "https://github.com/atom/node-keytar/issues"
},
"homepage": "http://atom.github.io/node-keytar",
"keywords": [
"keychain",
"password",
"passwords",
"credential",
"credentials",
"vault",
"credential vault"
],
"files": [
"lib",
"src",
"binding.gyp",
"keytar.d.ts"
],
"types": "./keytar.d.ts",
"scripts": {
"install": "prebuild-install || npm run build",
"build": "node-gyp rebuild",
"lint": "npm run cpplint",
"cpplint": "node-cpplint --filters legal-copyright,build-include,build-namespaces src/*.cc",
"test": "npm run lint && npm rebuild && mocha --require babel-core/register spec/",
"prebuild-napi-x64": "prebuild -t 3 -r napi -a x64 --strip",
"prebuild-napi-ia32": "prebuild -t 3 -r napi -a ia32 --strip",
"prebuild-napi-arm64": "prebuild -t 3 -r napi -a arm64 --strip",
"prebuild-napi-armv7l": "prebuild -t 3 -r napi -a armv7l --strip",
"upload": "node ./script/upload.js"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-plugin-transform-async-to-generator": "^6.24.1",
"chai": "^4.2.0",
"mocha": "^9.2.0",
"node-cpplint": "~0.4.0",
"node-gyp": "^8.4.1",
"prebuild": "^11.0.2"
},
"dependencies": {
"node-addon-api": "^4.3.0",
"prebuild-install": "^7.0.1"
},
"binary": {
"napi_versions": [
3
]
},
"config": {
"runtime": "napi",
"target": 3
}
}
+242
View File
@@ -0,0 +1,242 @@
#include <string>
#include <vector>
#include "napi.h"
#include "keytar.h"
#include "async.h"
using keytar::KEYTAR_OP_RESULT;
SetPasswordWorker::SetPasswordWorker(
const std::string& service,
const std::string& account,
const std::string& password,
const Napi::Env &env
) : AsyncWorker(env),
service(service),
account(account),
password(password),
deferred(Napi::Promise::Deferred::New(env)) {}
SetPasswordWorker::~SetPasswordWorker() {}
Napi::Promise SetPasswordWorker::Promise() {
return deferred.Promise();
}
void SetPasswordWorker::Execute() {
std::string error;
KEYTAR_OP_RESULT result = keytar::SetPassword(service,
account,
password,
&error);
if (result == keytar::FAIL_ERROR) {
SetError(error.c_str());
}
}
void SetPasswordWorker::OnOK() {
Napi::HandleScope scope(Env());
deferred.Resolve(Env().Undefined());
}
void SetPasswordWorker::OnError(Napi::Error const &error) {
Napi::HandleScope scope(Env());
deferred.Reject(error.Value());
}
GetPasswordWorker::GetPasswordWorker(
const std::string& service,
const std::string& account,
const Napi::Env &env
) : AsyncWorker(env),
service(service),
account(account),
deferred(Napi::Promise::Deferred::New(env)) {}
GetPasswordWorker::~GetPasswordWorker() {}
Napi::Promise GetPasswordWorker::Promise() {
return deferred.Promise();
}
void GetPasswordWorker::Execute() {
std::string error;
KEYTAR_OP_RESULT result = keytar::GetPassword(service,
account,
&password,
&error);
if (result == keytar::FAIL_ERROR) {
SetError(error.c_str());
} else if (result == keytar::FAIL_NONFATAL) {
success = false;
} else {
success = true;
}
}
void GetPasswordWorker::OnOK() {
Napi::HandleScope scope(Env());
Napi::Value val = Env().Null();
if (success) {
val = Napi::String::New(Env(), password.data(),
password.length());
}
deferred.Resolve(val);
}
void GetPasswordWorker::OnError(Napi::Error const &error) {
Napi::HandleScope scope(Env());
deferred.Reject(error.Value());
}
DeletePasswordWorker::DeletePasswordWorker(
const std::string& service,
const std::string& account,
const Napi::Env &env
) : AsyncWorker(env),
service(service),
account(account),
deferred(Napi::Promise::Deferred::New(env)) {}
DeletePasswordWorker::~DeletePasswordWorker() {}
Napi::Promise DeletePasswordWorker::Promise() {
return deferred.Promise();
}
void DeletePasswordWorker::Execute() {
std::string error;
KEYTAR_OP_RESULT result = keytar::DeletePassword(service, account, &error);
if (result == keytar::FAIL_ERROR) {
SetError(error.c_str());
} else if (result == keytar::FAIL_NONFATAL) {
success = false;
} else {
success = true;
}
}
void DeletePasswordWorker::OnOK() {
Napi::HandleScope scope(Env());
deferred.Resolve(Napi::Boolean::New(Env(), success));
}
void DeletePasswordWorker::OnError(Napi::Error const &error) {
Napi::HandleScope scope(Env());
deferred.Reject(error.Value());
}
FindPasswordWorker::FindPasswordWorker(
const std::string& service,
const Napi::Env &env
) : AsyncWorker(env),
service(service),
deferred(Napi::Promise::Deferred::New(env)) {}
FindPasswordWorker::~FindPasswordWorker() {}
Napi::Promise FindPasswordWorker::Promise() {
return deferred.Promise();
}
void FindPasswordWorker::Execute() {
std::string error;
KEYTAR_OP_RESULT result = keytar::FindPassword(service,
&password,
&error);
if (result == keytar::FAIL_ERROR) {
SetError(error.c_str());
} else if (result == keytar::FAIL_NONFATAL) {
success = false;
} else {
success = true;
}
}
void FindPasswordWorker::OnOK() {
Napi::HandleScope scope(Env());
Napi::Value val = Env().Null();
if (success) {
val = Napi::String::New(Env(), password.data(),
password.length());
}
deferred.Resolve(val);
}
void FindPasswordWorker::OnError(Napi::Error const &error) {
Napi::HandleScope scope(Env());
deferred.Reject(error.Value());
}
FindCredentialsWorker::FindCredentialsWorker(
const std::string& service,
const Napi::Env &env
) : AsyncWorker(env),
service(service),
deferred(Napi::Promise::Deferred::New(env)) {}
FindCredentialsWorker::~FindCredentialsWorker() {}
Napi::Promise FindCredentialsWorker::Promise() {
return deferred.Promise();
}
void FindCredentialsWorker::Execute() {
std::string error;
KEYTAR_OP_RESULT result = keytar::FindCredentials(service,
&credentials,
&error);
if (result == keytar::FAIL_ERROR) {
SetError(error.c_str());
} else if (result == keytar::FAIL_NONFATAL) {
success = false;
} else {
success = true;
}
}
void FindCredentialsWorker::OnOK() {
Napi::HandleScope scope(Env());
Napi::Env env = Env();
if (success) {
Napi::Array val = Napi::Array::New(env, credentials.size());
unsigned int idx = 0;
std::vector<keytar::Credentials>::iterator it;
for (it = credentials.begin(); it != credentials.end(); it++) {
keytar::Credentials cred = *it;
Napi::Object obj = Napi::Object::New(env);
Napi::String account = Napi::String::New(env,
cred.first.data(),
cred.first.length());
Napi::String password = Napi::String::New(env,
cred.second.data(),
cred.second.length());
#ifndef _WIN32
#pragma GCC diagnostic ignored "-Wunused-result"
#endif
obj.Set("account", account);
#ifndef _WIN32
#pragma GCC diagnostic ignored "-Wunused-result"
#endif
obj.Set("password", password);
(val).Set(idx, obj);
++idx;
}
deferred.Resolve(val);
} else {
deferred.Resolve(Napi::Array::New(env, 0));
}
}
void FindCredentialsWorker::OnError(Napi::Error const &error) {
Napi::HandleScope scope(Env());
deferred.Reject(error.Value());
}
+103
View File
@@ -0,0 +1,103 @@
#ifndef SRC_ASYNC_H_
#define SRC_ASYNC_H_
#include <string>
#include "napi.h"
#include "credentials.h"
class SetPasswordWorker : public Napi::AsyncWorker {
public:
SetPasswordWorker(const std::string& service, const std::string& account, const std::string& password,
const Napi::Env &env);
~SetPasswordWorker();
void Execute();
void OnOK();
void OnError(Napi::Error const &error);
Napi::Promise Promise();
private:
const std::string service;
const std::string account;
const std::string password;
Napi::Promise::Deferred deferred;
};
class GetPasswordWorker : public Napi::AsyncWorker {
public:
GetPasswordWorker(const std::string& service, const std::string& account,
const Napi::Env &env);
~GetPasswordWorker();
void Execute();
void OnOK();
void OnError(Napi::Error const &error);
Napi::Promise Promise();
private:
const std::string service;
const std::string account;
std::string password;
bool success;
const Napi::Promise::Deferred deferred;
};
class DeletePasswordWorker : public Napi::AsyncWorker {
public:
DeletePasswordWorker(const std::string& service, const std::string& account,
const Napi::Env &env);
~DeletePasswordWorker();
void Execute();
void OnOK();
void OnError(Napi::Error const &error);
Napi::Promise Promise();
private:
const std::string service;
const std::string account;
bool success;
Napi::Promise::Deferred deferred;
};
class FindPasswordWorker : public Napi::AsyncWorker {
public:
FindPasswordWorker(const std::string& service, const Napi::Env &env);
~FindPasswordWorker();
void Execute();
void OnOK();
void OnError(Napi::Error const &error);
Napi::Promise Promise();
private:
const std::string service;
std::string password;
bool success;
const Napi::Promise::Deferred deferred;
};
class FindCredentialsWorker : public Napi::AsyncWorker {
public:
FindCredentialsWorker(const std::string& service, const Napi::Env &env);
~FindCredentialsWorker();
void Execute();
void OnOK();
void OnError(Napi::Error const &error);
Napi::Promise Promise();
private:
const std::string service;
std::vector<keytar::Credentials> credentials;
bool success;
const Napi::Promise::Deferred deferred;
};
#endif // SRC_ASYNC_H_
+13
View File
@@ -0,0 +1,13 @@
#ifndef SRC_CREDENTIALS_H_
#define SRC_CREDENTIALS_H_
#include <string>
#include <utility>
namespace keytar {
typedef std::pair<std::string, std::string> Credentials;
}
#endif // SRC_CREDENTIALS_H_
+41
View File
@@ -0,0 +1,41 @@
#ifndef SRC_KEYTAR_H_
#define SRC_KEYTAR_H_
#include <string>
#include <vector>
#include "credentials.h"
namespace keytar {
enum KEYTAR_OP_RESULT {
SUCCESS,
FAIL_ERROR,
FAIL_NONFATAL
};
KEYTAR_OP_RESULT SetPassword(const std::string& service,
const std::string& account,
const std::string& password,
std::string* error);
KEYTAR_OP_RESULT GetPassword(const std::string& service,
const std::string& account,
std::string* password,
std::string* error);
KEYTAR_OP_RESULT DeletePassword(const std::string& service,
const std::string& account,
std::string* error);
KEYTAR_OP_RESULT FindPassword(const std::string& service,
std::string* password,
std::string* error);
KEYTAR_OP_RESULT FindCredentials(const std::string& service,
std::vector<Credentials>*,
std::string* error);
} // namespace keytar
#endif // SRC_KEYTAR_H_
+296
View File
@@ -0,0 +1,296 @@
#include <Security/Security.h>
#include "keytar.h"
#include "credentials.h"
namespace keytar {
/**
* Converts a CFString to a std::string
*
* This either uses CFStringGetCStringPtr or (if that fails)
* CFStringGetCString, trying to be as efficient as possible.
*/
const std::string CFStringToStdString(CFStringRef cfstring) {
const char* cstr = CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8);
if (cstr != NULL) {
return std::string(cstr);
}
CFIndex length = CFStringGetLength(cfstring);
// Worst case: 2 bytes per character + NUL
CFIndex cstrPtrLen = length * 2 + 1;
char* cstrPtr = static_cast<char*>(malloc(cstrPtrLen));
Boolean result = CFStringGetCString(cfstring,
cstrPtr,
cstrPtrLen,
kCFStringEncodingUTF8);
std::string stdstring;
if (result) {
stdstring = std::string(cstrPtr);
}
free(cstrPtr);
return stdstring;
}
const std::string errorStatusToString(OSStatus status) {
std::string errorStr;
CFStringRef errorMessageString = SecCopyErrorMessageString(status, NULL);
const char* errorCStringPtr = CFStringGetCStringPtr(errorMessageString,
kCFStringEncodingUTF8);
if (errorCStringPtr) {
errorStr = std::string(errorCStringPtr);
} else {
errorStr = std::string("An unknown error occurred.");
}
CFRelease(errorMessageString);
return errorStr;
}
KEYTAR_OP_RESULT AddPassword(const std::string& service,
const std::string& account,
const std::string& password,
std::string* error) {
OSStatus status = SecKeychainAddGenericPassword(NULL,
service.length(),
service.data(),
account.length(),
account.data(),
password.length(),
password.data(),
NULL);
if (status != errSecSuccess) {
*error = errorStatusToString(status);
return FAIL_ERROR;
}
return SUCCESS;
}
KEYTAR_OP_RESULT SetPassword(const std::string& service,
const std::string& account,
const std::string& password,
std::string* error) {
SecKeychainItemRef item;
OSStatus result = SecKeychainFindGenericPassword(NULL,
service.length(),
service.data(),
account.length(),
account.data(),
NULL,
NULL,
&item);
if (result == errSecItemNotFound) {
return AddPassword(service, account, password, error);
} else if (result != errSecSuccess) {
*error = errorStatusToString(result);
return FAIL_ERROR;
}
result = SecKeychainItemModifyAttributesAndData(item,
NULL,
password.length(),
password.data());
CFRelease(item);
if (result != errSecSuccess) {
*error = errorStatusToString(result);
return FAIL_ERROR;
}
return SUCCESS;
}
KEYTAR_OP_RESULT GetPassword(const std::string& service,
const std::string& account,
std::string* password,
std::string* error) {
void *data;
UInt32 length;
OSStatus status = SecKeychainFindGenericPassword(NULL,
service.length(),
service.data(),
account.length(),
account.data(),
&length,
&data,
NULL);
if (status == errSecItemNotFound) {
return FAIL_NONFATAL;
} else if (status != errSecSuccess) {
*error = errorStatusToString(status);
return FAIL_ERROR;
}
*password = std::string(reinterpret_cast<const char*>(data), length);
SecKeychainItemFreeContent(NULL, data);
return SUCCESS;
}
KEYTAR_OP_RESULT DeletePassword(const std::string& service,
const std::string& account,
std::string* error) {
SecKeychainItemRef item;
OSStatus status = SecKeychainFindGenericPassword(NULL,
service.length(),
service.data(),
account.length(),
account.data(),
NULL,
NULL,
&item);
if (status == errSecItemNotFound) {
// Item could not be found, so already deleted.
return FAIL_NONFATAL;
} else if (status != errSecSuccess) {
*error = errorStatusToString(status);
return FAIL_ERROR;
}
status = SecKeychainItemDelete(item);
CFRelease(item);
if (status != errSecSuccess) {
*error = errorStatusToString(status);
return FAIL_ERROR;
}
return SUCCESS;
}
KEYTAR_OP_RESULT FindPassword(const std::string& service,
std::string* password,
std::string* error) {
SecKeychainItemRef item;
void *data;
UInt32 length;
OSStatus status = SecKeychainFindGenericPassword(NULL,
service.length(),
service.data(),
0,
NULL,
&length,
&data,
&item);
if (status == errSecItemNotFound) {
return FAIL_NONFATAL;
} else if (status != errSecSuccess) {
*error = errorStatusToString(status);
return FAIL_ERROR;
}
*password = std::string(reinterpret_cast<const char*>(data), length);
SecKeychainItemFreeContent(NULL, data);
CFRelease(item);
return SUCCESS;
}
Credentials getCredentialsForItem(CFDictionaryRef item) {
CFStringRef service = (CFStringRef) CFDictionaryGetValue(item,
kSecAttrService);
CFStringRef account = (CFStringRef) CFDictionaryGetValue(item,
kSecAttrAccount);
CFMutableDictionaryRef query = CFDictionaryCreateMutable(
NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(query, kSecAttrService, service);
CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword);
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitOne);
CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue);
CFDictionaryAddValue(query, kSecReturnData, kCFBooleanTrue);
CFDictionaryAddValue(query, kSecAttrAccount, account);
Credentials cred;
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching((CFDictionaryRef) query, &result);
CFRelease(query);
if (status == errSecSuccess) {
CFDataRef passwordData = (CFDataRef) CFDictionaryGetValue(
(CFDictionaryRef) result,
CFSTR("v_Data"));
CFStringRef password = CFStringCreateFromExternalRepresentation(
NULL,
passwordData,
kCFStringEncodingUTF8);
cred = Credentials(
CFStringToStdString(account),
CFStringToStdString(password));
CFRelease(password);
}
if (result != NULL) {
CFRelease(result);
}
return cred;
}
KEYTAR_OP_RESULT FindCredentials(const std::string& service,
std::vector<Credentials>* credentials,
std::string* error) {
CFStringRef serviceStr = CFStringCreateWithCString(
NULL,
service.c_str(),
kCFStringEncodingUTF8);
CFMutableDictionaryRef query = CFDictionaryCreateMutable(
NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword);
CFDictionaryAddValue(query, kSecAttrService, serviceStr);
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue);
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching((CFDictionaryRef) query, &result);
CFRelease(serviceStr);
CFRelease(query);
if (status == errSecSuccess) {
CFArrayRef resultArray = (CFArrayRef) result;
int resultCount = CFArrayGetCount(resultArray);
for (int idx = 0; idx < resultCount; idx++) {
CFDictionaryRef item = (CFDictionaryRef) CFArrayGetValueAtIndex(
resultArray,
idx);
Credentials cred = getCredentialsForItem(item);
credentials->push_back(cred);
}
} else if (status == errSecItemNotFound) {
return FAIL_NONFATAL;
} else {
*error = errorStatusToString(status);
return FAIL_ERROR;
}
if (result != NULL) {
CFRelease(result);
}
return SUCCESS;
}
} // namespace keytar
+184
View File
@@ -0,0 +1,184 @@
#include "keytar.h"
// This is needed to make the builds on Ubuntu 14.04 / libsecret v0.16 work.
// The API we use has already stabilized.
#define SECRET_API_SUBJECT_TO_CHANGE
#include <libsecret/secret.h>
#include <stdio.h>
#include <string.h>
namespace keytar {
namespace {
static const SecretSchema schema = {
"org.freedesktop.Secret.Generic", SECRET_SCHEMA_NONE, {
{ "service", SECRET_SCHEMA_ATTRIBUTE_STRING },
{ "account", SECRET_SCHEMA_ATTRIBUTE_STRING }
}
};
} // namespace
KEYTAR_OP_RESULT SetPassword(const std::string& service,
const std::string& account,
const std::string& password,
std::string* errStr) {
GError* error = NULL;
secret_password_store_sync(
&schema, // The schema.
SECRET_COLLECTION_DEFAULT, // Default collection.
(service + "/" + account).c_str(), // The label.
password.c_str(), // The password.
NULL, // Cancellable. (unneeded)
&error, // Reference to the error.
"service", service.c_str(),
"account", account.c_str(),
NULL); // End of arguments.
if (error != NULL) {
*errStr = std::string(error->message);
g_error_free(error);
return FAIL_ERROR;
}
return SUCCESS;
}
KEYTAR_OP_RESULT GetPassword(const std::string& service,
const std::string& account,
std::string* password,
std::string* errStr) {
GError* error = NULL;
gchar* raw_password = secret_password_lookup_sync(
&schema, // The schema.
NULL, // Cancellable. (unneeded)
&error, // Reference to the error.
"service", service.c_str(),
"account", account.c_str(),
NULL); // End of arguments.
if (error != NULL) {
*errStr = std::string(error->message);
g_error_free(error);
return FAIL_ERROR;
}
if (raw_password == NULL)
return FAIL_NONFATAL;
*password = raw_password;
secret_password_free(raw_password);
return SUCCESS;
}
KEYTAR_OP_RESULT DeletePassword(const std::string& service,
const std::string& account,
std::string* errStr) {
GError* error = NULL;
gboolean result = secret_password_clear_sync(
&schema, // The schema.
NULL, // Cancellable. (unneeded)
&error, // Reference to the error.
"service", service.c_str(),
"account", account.c_str(),
NULL); // End of arguments.
if (error != NULL) {
*errStr = std::string(error->message);
g_error_free(error);
return FAIL_ERROR;
}
if (!result)
return FAIL_NONFATAL;
return SUCCESS;
}
KEYTAR_OP_RESULT FindPassword(const std::string& service,
std::string* password,
std::string* errStr) {
GError* error = NULL;
gchar* raw_password = secret_password_lookup_sync(
&schema, // The schema.
NULL, // Cancellable. (unneeded)
&error, // Reference to the error.
"service", service.c_str(),
NULL); // End of arguments.
if (error != NULL) {
*errStr = std::string(error->message);
g_error_free(error);
return FAIL_ERROR;
}
if (raw_password == NULL)
return FAIL_NONFATAL;
*password = raw_password;
secret_password_free(raw_password);
return SUCCESS;
}
KEYTAR_OP_RESULT FindCredentials(const std::string& service,
std::vector<Credentials>* credentials,
std::string* errStr) {
GError* error = NULL;
GHashTable* attributes = g_hash_table_new(NULL, NULL);
g_hash_table_replace(attributes,
(gpointer) "service",
(gpointer) service.c_str());
GList* items = secret_service_search_sync(
NULL,
&schema, // The schema.
attributes,
static_cast<SecretSearchFlags>(SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK |
SECRET_SEARCH_LOAD_SECRETS),
NULL, // Cancellable. (unneeded)
&error); // Reference to the error.
g_hash_table_destroy(attributes);
if (error != NULL) {
*errStr = std::string(error->message);
g_error_free(error);
return FAIL_ERROR;
}
GList* current = items;
for (current = items; current != NULL; current = current->next) {
SecretItem* item = reinterpret_cast<SecretItem*>(current->data);
GHashTable* itemAttrs = secret_item_get_attributes(item);
char* account = strdup(
reinterpret_cast<char*>(g_hash_table_lookup(itemAttrs, "account")));
SecretValue* secret = secret_item_get_secret(item);
char* password = strdup(secret_value_get_text(secret));
if (account == NULL || password == NULL) {
if (account)
free(account);
if (password)
free(password);
continue;
}
credentials->push_back(Credentials(account, password));
free(account);
free(password);
}
return SUCCESS;
}
} // namespace keytar
+272
View File
@@ -0,0 +1,272 @@
#include "keytar.h"
#define UNICODE
#include <windows.h>
#include <wincred.h>
#include "credentials.h"
namespace keytar {
LPWSTR utf8ToWideChar(std::string utf8) {
int wide_char_length = MultiByteToWideChar(CP_UTF8,
0,
utf8.c_str(),
-1,
NULL,
0);
if (wide_char_length == 0) {
return NULL;
}
LPWSTR result = new WCHAR[wide_char_length];
if (MultiByteToWideChar(CP_UTF8,
0,
utf8.c_str(),
-1,
result,
wide_char_length) == 0) {
delete[] result;
return NULL;
}
return result;
}
std::string wideCharToAnsi(LPWSTR wide_char) {
if (wide_char == NULL) {
return std::string();
}
int ansi_length = WideCharToMultiByte(CP_ACP,
0,
wide_char,
-1,
NULL,
0,
NULL,
NULL);
if (ansi_length == 0) {
return std::string();
}
char* buffer = new char[ansi_length];
if (WideCharToMultiByte(CP_ACP,
0,
wide_char,
-1,
buffer,
ansi_length,
NULL,
NULL) == 0) {
delete[] buffer;
return std::string();
}
std::string result = std::string(buffer);
delete[] buffer;
return result;
}
std::string wideCharToUtf8(LPWSTR wide_char) {
if (wide_char == NULL) {
return std::string();
}
int utf8_length = WideCharToMultiByte(CP_UTF8,
0,
wide_char,
-1,
NULL,
0,
NULL,
NULL);
if (utf8_length == 0) {
return std::string();
}
char* buffer = new char[utf8_length];
if (WideCharToMultiByte(CP_UTF8,
0,
wide_char,
-1,
buffer,
utf8_length,
NULL,
NULL) == 0) {
delete[] buffer;
return std::string();
}
std::string result = std::string(buffer);
delete[] buffer;
return result;
}
std::string getErrorMessage(DWORD errorCode) {
LPWSTR errBuffer;
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, errorCode, 0, (LPWSTR) &errBuffer, 0, NULL);
std::string errMsg = wideCharToAnsi(errBuffer);
LocalFree(errBuffer);
return errMsg;
}
KEYTAR_OP_RESULT SetPassword(const std::string& service,
const std::string& account,
const std::string& password,
std::string* errStr) {
LPWSTR target_name = utf8ToWideChar(service + '/' + account);
if (target_name == NULL) {
return FAIL_ERROR;
}
LPWSTR user_name = utf8ToWideChar(account);
if (user_name == NULL) {
return FAIL_ERROR;
}
CREDENTIAL cred = { 0 };
cred.Type = CRED_TYPE_GENERIC;
cred.TargetName = target_name;
cred.UserName = user_name;
cred.CredentialBlobSize = password.size();
cred.CredentialBlob = (LPBYTE)(password.data());
cred.Persist = CRED_PERSIST_ENTERPRISE;
bool result = ::CredWrite(&cred, 0);
delete[] target_name;
if (!result) {
*errStr = getErrorMessage(::GetLastError());
return FAIL_ERROR;
} else {
return SUCCESS;
}
}
KEYTAR_OP_RESULT GetPassword(const std::string& service,
const std::string& account,
std::string* password,
std::string* errStr) {
LPWSTR target_name = utf8ToWideChar(service + '/' + account);
if (target_name == NULL) {
return FAIL_ERROR;
}
CREDENTIAL* cred;
bool result = ::CredRead(target_name, CRED_TYPE_GENERIC, 0, &cred);
delete[] target_name;
if (!result) {
DWORD code = ::GetLastError();
if (code == ERROR_NOT_FOUND) {
return FAIL_NONFATAL;
} else {
*errStr = getErrorMessage(code);
return FAIL_ERROR;
}
}
*password = std::string(reinterpret_cast<char*>(cred->CredentialBlob),
cred->CredentialBlobSize);
::CredFree(cred);
return SUCCESS;
}
KEYTAR_OP_RESULT DeletePassword(const std::string& service,
const std::string& account,
std::string* errStr) {
LPWSTR target_name = utf8ToWideChar(service + '/' + account);
if (target_name == NULL) {
return FAIL_ERROR;
}
bool result = ::CredDelete(target_name, CRED_TYPE_GENERIC, 0);
delete[] target_name;
if (!result) {
DWORD code = ::GetLastError();
if (code == ERROR_NOT_FOUND) {
return FAIL_NONFATAL;
} else {
*errStr = getErrorMessage(code);
return FAIL_ERROR;
}
}
return SUCCESS;
}
KEYTAR_OP_RESULT FindPassword(const std::string& service,
std::string* password,
std::string* errStr) {
LPWSTR filter = utf8ToWideChar(service + "*");
if (filter == NULL) {
return FAIL_ERROR;
}
DWORD count;
CREDENTIAL** creds;
bool result = ::CredEnumerate(filter, 0, &count, &creds);
delete[] filter;
if (!result) {
DWORD code = ::GetLastError();
if (code == ERROR_NOT_FOUND) {
return FAIL_NONFATAL;
} else {
*errStr = getErrorMessage(code);
return FAIL_ERROR;
}
}
*password = std::string(reinterpret_cast<char*>(creds[0]->CredentialBlob),
creds[0]->CredentialBlobSize);
::CredFree(creds);
return SUCCESS;
}
KEYTAR_OP_RESULT FindCredentials(const std::string& service,
std::vector<Credentials>* credentials,
std::string* errStr) {
LPWSTR filter = utf8ToWideChar(service + "*");
if (filter == NULL) {
*errStr = "Error generating credential filter";
return FAIL_ERROR;
}
DWORD count;
CREDENTIAL **creds;
bool result = ::CredEnumerate(filter, 0, &count, &creds);
if (!result) {
DWORD code = ::GetLastError();
if (code == ERROR_NOT_FOUND) {
return FAIL_NONFATAL;
} else {
*errStr = getErrorMessage(code);
return FAIL_ERROR;
}
}
for (unsigned int i = 0; i < count; ++i) {
CREDENTIAL* cred = creds[i];
if (cred->UserName == NULL || cred->CredentialBlobSize == NULL) {
continue;
}
std::string login = wideCharToUtf8(cred->UserName);
std::string password(
reinterpret_cast<char*>(
cred->CredentialBlob),
cred->CredentialBlobSize);
credentials->push_back(Credentials(login, password));
}
CredFree(creds);
return SUCCESS;
}
} // namespace keytar
+139
View File
@@ -0,0 +1,139 @@
#include "napi.h"
#include "async.h"
namespace {
Napi::Value SetPassword(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (!info[0].IsString()) {
Napi::TypeError::New(env, "Parameter 'service' must be a string").
ThrowAsJavaScriptException();
return env.Null();
}
std::string service = info[0].As<Napi::String>();
if (!info[1].IsString()) {
Napi::TypeError::New(env, "Parameter 'username' must be a string").
ThrowAsJavaScriptException();
return env.Null();
}
std::string username = info[1].As<Napi::String>();
if (!info[2].IsString()) {
Napi::TypeError::New(env, "Parameter 'password' must be a string").
ThrowAsJavaScriptException();
return env.Null();
}
std::string password = info[2].As<Napi::String>();
SetPasswordWorker* worker = new SetPasswordWorker(
service,
username,
password,
env);
worker->Queue();
return worker->Promise();
}
Napi::Value GetPassword(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (!info[0].IsString()) {
Napi::TypeError::New(env, "Parameter 'service' must be a string").
ThrowAsJavaScriptException();
return env.Null();
}
std::string service = info[0].As<Napi::String>();
if (!info[1].IsString()) {
Napi::TypeError::New(env, "Parameter 'username' must be a string").
ThrowAsJavaScriptException();
return env.Null();
}
std::string username = info[1].As<Napi::String>();
GetPasswordWorker* worker = new GetPasswordWorker(
service,
username,
env);
worker->Queue();
return worker->Promise();
}
Napi::Value DeletePassword(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (!info[0].IsString()) {
Napi::TypeError::New(env, "Parameter 'service' must be a string").
ThrowAsJavaScriptException();
return env.Null();
}
std::string service = info[0].As<Napi::String>();
if (!info[1].IsString()) {
Napi::TypeError::New(env, "Parameter 'username' must be a string").
ThrowAsJavaScriptException();
return env.Null();
}
std::string username = info[1].As<Napi::String>();
DeletePasswordWorker *worker = new DeletePasswordWorker(
service,
username,
env);
worker->Queue();
return worker->Promise();
}
Napi::Value FindPassword(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (!info[0].IsString()) {
Napi::TypeError::New(env, "Parameter 'service' must be a string").
ThrowAsJavaScriptException();
return env.Null();
}
std::string service = info[0].As<Napi::String>();
FindPasswordWorker* worker = new FindPasswordWorker(
service,
env);
worker->Queue();
return worker->Promise();
}
Napi::Value FindCredentials(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (!info[0].IsString()) {
Napi::TypeError::New(env, "Parameter 'service' must be a string").
ThrowAsJavaScriptException();
return env.Null();
}
std::string service = info[0].As<Napi::String>();
FindCredentialsWorker* worker = new FindCredentialsWorker(
service,
env);
worker->Queue();
return worker->Promise();
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("getPassword", Napi::Function::New(env, GetPassword));
exports.Set("setPassword", Napi::Function::New(env, SetPassword));
exports.Set("deletePassword", Napi::Function::New(env, DeletePassword));
exports.Set("findPassword", Napi::Function::New(env, FindPassword));
exports.Set("findCredentials", Napi::Function::New(env, FindCredentials));
return exports;
}
} // namespace
NODE_API_MODULE(keytar, Init)