UNPKG

9.89 kBtext/x-cView Raw
1#include <napi.h>
2
3#include <string>
4#include <cstring>
5#include <vector>
6#include <stdlib.h> // atoi
7
8#include "node_blf.h"
9
10#define NODE_LESS_THAN (!(NODE_VERSION_AT_LEAST(0, 5, 4)))
11
12namespace {
13
14 bool ValidateSalt(const char* salt) {
15
16 if (!salt || *salt != '$') {
17 return false;
18 }
19
20 // discard $
21 salt++;
22
23 if (*salt > BCRYPT_VERSION) {
24 return false;
25 }
26
27 if (salt[1] != '$') {
28 switch (salt[1]) {
29 case 'a':
30 case 'b':
31 salt++;
32 break;
33 default:
34 return false;
35 }
36 }
37
38 // discard version + $
39 salt += 2;
40
41 if (salt[2] != '$') {
42 return false;
43 }
44
45 int n = atoi(salt);
46 if (n > 31 || n < 0) {
47 return false;
48 }
49
50 if (((uint8_t)1 << (uint8_t)n) < BCRYPT_MINROUNDS) {
51 return false;
52 }
53
54 salt += 3;
55 if (strlen(salt) * 3 / 4 < BCRYPT_MAXSALT) {
56 return false;
57 }
58
59 return true;
60 }
61
62 char ToCharVersion(Napi::String str) {
63 std::string our_str = str.Utf8Value();
64 return our_str.c_str()[0];
65 }
66
67 /* SALT GENERATION */
68
69 class SaltAsyncWorker : public Napi::AsyncWorker {
70 public:
71 SaltAsyncWorker(Napi::Function& callback, std::string seed, ssize_t rounds, char minor_ver)
72 : Napi::AsyncWorker(callback, "bcrypt:SaltAsyncWorker"), seed(seed), rounds(rounds), minor_ver(minor_ver) {
73 }
74
75 ~SaltAsyncWorker() {}
76
77 void Execute() {
78 char salt[_SALT_LEN];
79 bcrypt_gensalt(minor_ver, rounds, (u_int8_t *)&seed[0], salt);
80 this->salt = std::string(salt);
81 }
82
83 void OnOK() {
84 Napi::HandleScope scope(Env());
85 Callback().Call({Env().Undefined(), Napi::String::New(Env(), salt)});
86 }
87
88 private:
89 std::string seed;
90 std::string salt;
91 ssize_t rounds;
92 char minor_ver;
93 };
94
95 Napi::Value GenerateSalt(const Napi::CallbackInfo& info) {
96 Napi::Env env = info.Env();
97 if (info.Length() < 4) {
98 throw Napi::TypeError::New(env, "4 arguments expected");
99 }
100 if (!info[0].IsString()) {
101 throw Napi::TypeError::New(env, "First argument must be a string");
102 }
103 if (!info[2].IsBuffer() || (info[2].As<Napi::Buffer<char>>()).Length() != 16) {
104 throw Napi::TypeError::New(env, "Second argument must be a 16 byte Buffer");
105 }
106
107 const char minor_ver = ToCharVersion(info[0].As<Napi::String>());
108 const int32_t rounds = info[1].As<Napi::Number>();
109 Napi::Buffer<char> seed = info[2].As<Napi::Buffer<char>>();
110 Napi::Function callback = info[3].As<Napi::Function>();
111 SaltAsyncWorker* saltWorker = new SaltAsyncWorker(callback, std::string(seed.Data(), 16), rounds, minor_ver);
112 saltWorker->Queue();
113 return env.Undefined();
114 }
115
116 Napi::Value GenerateSaltSync(const Napi::CallbackInfo& info) {
117 Napi::Env env = info.Env();
118 if (info.Length() < 3) {
119 throw Napi::TypeError::New(env, "3 arguments expected");
120 }
121 if (!info[0].IsString()) {
122 throw Napi::TypeError::New(env, "First argument must be a string");
123 }
124 if (!info[2].IsBuffer() || (info[2].As<Napi::Buffer<char>>()).Length() != 16) {
125 throw Napi::TypeError::New(env, "Third argument must be a 16 byte Buffer");
126 }
127 const char minor_ver = ToCharVersion(info[0].As<Napi::String>());
128 const int32_t rounds = info[1].As<Napi::Number>();
129 Napi::Buffer<u_int8_t> buffer = info[2].As<Napi::Buffer<u_int8_t>>();
130 u_int8_t* seed = (u_int8_t*) buffer.Data();
131 char salt[_SALT_LEN];
132 bcrypt_gensalt(minor_ver, rounds, seed, salt);
133 return Napi::String::New(env, salt, strlen(salt));
134 }
135
136 /* ENCRYPT DATA - USED TO BE HASHPW */
137
138 class EncryptAsyncWorker : public Napi::AsyncWorker {
139 public:
140 EncryptAsyncWorker(Napi::Function& callback, std::string input, std::string salt)
141 : Napi::AsyncWorker(callback, "bcrypt:EncryptAsyncWorker"), input(input), salt(salt) {
142 }
143
144 ~EncryptAsyncWorker() {}
145
146 void Execute() {
147 if (!(ValidateSalt(salt.c_str()))) {
148 SetError("Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue");
149 }
150 char bcrypted[_PASSWORD_LEN];
151 bcrypt(input.c_str(), input.length(), salt.c_str(), bcrypted);
152 output = std::string(bcrypted);
153 }
154
155 void OnOK() {
156 Napi::HandleScope scope(Env());
157 Callback().Call({Env().Undefined(),Napi::String::New(Env(), output)});
158 }
159 private:
160 std::string input;
161 std::string salt;
162 std::string output;
163 };
164
165 Napi::Value Encrypt(const Napi::CallbackInfo& info) {
166 if (info.Length() < 3) {
167 throw Napi::TypeError::New(info.Env(), "3 arguments expected");
168 }
169 std::string data = info[0].As<Napi::String>();;
170 std::string salt = info[1].As<Napi::String>();;
171 Napi::Function callback = info[2].As<Napi::Function>();
172 EncryptAsyncWorker* encryptWorker = new EncryptAsyncWorker(callback, data, salt);
173 encryptWorker->Queue();
174 return info.Env().Undefined();
175 }
176
177 Napi::Value EncryptSync(const Napi::CallbackInfo& info) {
178 Napi::Env env = info.Env();
179 if (info.Length() < 2) {
180 throw Napi::TypeError::New(info.Env(), "2 arguments expected");
181 }
182 std::string data = info[0].As<Napi::String>();;
183 std::string salt = info[1].As<Napi::String>();;
184 if (!(ValidateSalt(salt.c_str()))) {
185 throw Napi::Error::New(env, "Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue");
186 }
187 char bcrypted[_PASSWORD_LEN];
188 bcrypt(data.c_str(), data.length(), salt.c_str(), bcrypted);
189 return Napi::String::New(env, bcrypted, strlen(bcrypted));
190 }
191
192 /* COMPARATOR */
193 inline bool CompareStrings(const char* s1, const char* s2) {
194 return strcmp(s1, s2) == 0;
195 }
196
197 class CompareAsyncWorker : public Napi::AsyncWorker {
198 public:
199 CompareAsyncWorker(Napi::Function& callback, std::string input, std::string encrypted)
200 : Napi::AsyncWorker(callback, "bcrypt:CompareAsyncWorker"), input(input), encrypted(encrypted) {
201 result = false;
202 }
203
204 ~CompareAsyncWorker() {}
205
206 void Execute() {
207 char bcrypted[_PASSWORD_LEN];
208 if (ValidateSalt(encrypted.c_str())) {
209 bcrypt(input.c_str(), input.length(), encrypted.c_str(), bcrypted);
210 result = CompareStrings(bcrypted, encrypted.c_str());
211 }
212 }
213
214 void OnOK() {
215 Napi::HandleScope scope(Env());
216 Callback().Call({Env().Undefined(), Napi::Boolean::New(Env(), result)});
217 }
218
219 private:
220 std::string input;
221 std::string encrypted;
222 bool result;
223 };
224
225 Napi::Value Compare(const Napi::CallbackInfo& info) {
226 if (info.Length() < 3) {
227 throw Napi::TypeError::New(info.Env(), "3 arguments expected");
228 }
229 std::string input = info[0].As<Napi::String>();
230 std::string encrypted = info[1].As<Napi::String>();
231 Napi::Function callback = info[2].As<Napi::Function>();
232 CompareAsyncWorker* compareWorker = new CompareAsyncWorker(callback, input, encrypted);
233 compareWorker->Queue();
234 return info.Env().Undefined();
235 }
236
237 Napi::Value CompareSync(const Napi::CallbackInfo& info) {
238 Napi::Env env = info.Env();
239 if (info.Length() < 2) {
240 throw Napi::TypeError::New(info.Env(), "2 arguments expected");
241 }
242 std::string pw = info[0].As<Napi::String>();
243 std::string hash = info[1].As<Napi::String>();
244 char bcrypted[_PASSWORD_LEN];
245 if (ValidateSalt(hash.c_str())) {
246 bcrypt(pw.c_str(), pw.length(), hash.c_str(), bcrypted);
247 return Napi::Boolean::New(env, CompareStrings(bcrypted, hash.c_str()));
248 } else {
249 return Napi::Boolean::New(env, false);
250 }
251 }
252
253 Napi::Value GetRounds(const Napi::CallbackInfo& info) {
254 Napi::Env env = info.Env();
255 if (info.Length() < 1) {
256 throw Napi::TypeError::New(env, "1 argument expected");
257 }
258 std::string hash = info[0].As<Napi::String>();
259 u_int32_t rounds;
260 if (!(rounds = bcrypt_get_rounds(hash.c_str()))) {
261 throw Napi::Error::New(env, "invalid hash provided");
262 }
263 return Napi::Number::New(env, rounds);
264 }
265
266} // anonymous namespace
267
268Napi::Object init(Napi::Env env, Napi::Object exports) {
269 exports.Set(Napi::String::New(env, "gen_salt_sync"), Napi::Function::New(env, GenerateSaltSync));
270 exports.Set(Napi::String::New(env, "encrypt_sync"), Napi::Function::New(env, EncryptSync));
271 exports.Set(Napi::String::New(env, "compare_sync"), Napi::Function::New(env, CompareSync));
272 exports.Set(Napi::String::New(env, "get_rounds"), Napi::Function::New(env, GetRounds));
273 exports.Set(Napi::String::New(env, "gen_salt"), Napi::Function::New(env, GenerateSalt));
274 exports.Set(Napi::String::New(env, "encrypt"), Napi::Function::New(env, Encrypt));
275 exports.Set(Napi::String::New(env, "compare"), Napi::Function::New(env, Compare));
276 return exports;
277}
278
279NODE_API_MODULE(NODE_GYP_MODULE_NAME, init)