UNPKG

10.2 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 inline char ToCharVersion(const std::string& str) {
63 return str[0];
64 }
65
66 /* SALT GENERATION */
67
68 class SaltAsyncWorker : public Napi::AsyncWorker {
69 public:
70 SaltAsyncWorker(const Napi::Function& callback, const std::string& seed, ssize_t rounds, char minor_ver)
71 : Napi::AsyncWorker(callback, "bcrypt:SaltAsyncWorker"), seed(seed), rounds(rounds), minor_ver(minor_ver) {
72 }
73
74 ~SaltAsyncWorker() {}
75
76 void Execute() {
77 bcrypt_gensalt(minor_ver, rounds, (u_int8_t *)&seed[0], salt);
78 }
79
80 void OnOK() {
81 Napi::HandleScope scope(Env());
82 Callback().Call({Env().Undefined(), Napi::String::New(Env(), salt)});
83 }
84
85 private:
86 std::string seed;
87 ssize_t rounds;
88 char minor_ver;
89 char salt[_SALT_LEN];
90 };
91
92 Napi::Value GenerateSalt(const Napi::CallbackInfo& info) {
93 Napi::Env env = info.Env();
94 if (info.Length() < 4) {
95 throw Napi::TypeError::New(env, "4 arguments expected");
96 }
97 if (!info[0].IsString()) {
98 throw Napi::TypeError::New(env, "First argument must be a string");
99 }
100 if (!info[2].IsBuffer() || (info[2].As<Napi::Buffer<char>>()).Length() != 16) {
101 throw Napi::TypeError::New(env, "Second argument must be a 16 byte Buffer");
102 }
103
104 const char minor_ver = ToCharVersion(info[0].As<Napi::String>());
105 const int32_t rounds = info[1].As<Napi::Number>();
106 Napi::Buffer<char> seed = info[2].As<Napi::Buffer<char>>();
107 Napi::Function callback = info[3].As<Napi::Function>();
108 SaltAsyncWorker* saltWorker = new SaltAsyncWorker(callback, std::string(seed.Data(), 16), rounds, minor_ver);
109 saltWorker->Queue();
110 return env.Undefined();
111 }
112
113 Napi::Value GenerateSaltSync(const Napi::CallbackInfo& info) {
114 Napi::Env env = info.Env();
115 if (info.Length() < 3) {
116 throw Napi::TypeError::New(env, "3 arguments expected");
117 }
118 if (!info[0].IsString()) {
119 throw Napi::TypeError::New(env, "First argument must be a string");
120 }
121 if (!info[2].IsBuffer() || (info[2].As<Napi::Buffer<char>>()).Length() != 16) {
122 throw Napi::TypeError::New(env, "Third argument must be a 16 byte Buffer");
123 }
124 const char minor_ver = ToCharVersion(info[0].As<Napi::String>());
125 const int32_t rounds = info[1].As<Napi::Number>();
126 Napi::Buffer<u_int8_t> buffer = info[2].As<Napi::Buffer<u_int8_t>>();
127 u_int8_t* seed = (u_int8_t*) buffer.Data();
128 char salt[_SALT_LEN];
129 bcrypt_gensalt(minor_ver, rounds, seed, salt);
130 return Napi::String::New(env, salt, strlen(salt));
131 }
132
133 inline std::string BufferToString(const Napi::Buffer<char> &buf) {
134 return std::string(buf.Data(), buf.Length());
135 }
136
137 /* ENCRYPT DATA - USED TO BE HASHPW */
138
139 class EncryptAsyncWorker : public Napi::AsyncWorker {
140 public:
141 EncryptAsyncWorker(const Napi::Function& callback, const std::string& input, const std::string& salt)
142 : Napi::AsyncWorker(callback, "bcrypt:EncryptAsyncWorker"), input(input), salt(salt) {
143 }
144
145 ~EncryptAsyncWorker() {}
146
147 void Execute() {
148 if (!(ValidateSalt(salt.c_str()))) {
149 SetError("Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue");
150 }
151 bcrypt(input.c_str(), input.length(), salt.c_str(), bcrypted);
152 }
153
154 void OnOK() {
155 Napi::HandleScope scope(Env());
156 Callback().Call({Env().Undefined(),Napi::String::New(Env(), bcrypted)});
157 }
158 private:
159 std::string input;
160 std::string salt;
161 char bcrypted[_PASSWORD_LEN];
162 };
163
164 Napi::Value Encrypt(const Napi::CallbackInfo& info) {
165 if (info.Length() < 3) {
166 throw Napi::TypeError::New(info.Env(), "3 arguments expected");
167 }
168 std::string data = info[0].IsBuffer()
169 ? BufferToString(info[0].As<Napi::Buffer<char>>())
170 : info[0].As<Napi::String>();
171 std::string salt = info[1].As<Napi::String>();
172 Napi::Function callback = info[2].As<Napi::Function>();
173 EncryptAsyncWorker* encryptWorker = new EncryptAsyncWorker(callback, data, salt);
174 encryptWorker->Queue();
175 return info.Env().Undefined();
176 }
177
178 Napi::Value EncryptSync(const Napi::CallbackInfo& info) {
179 Napi::Env env = info.Env();
180 if (info.Length() < 2) {
181 throw Napi::TypeError::New(info.Env(), "2 arguments expected");
182 }
183 std::string data = info[0].IsBuffer()
184 ? BufferToString(info[0].As<Napi::Buffer<char>>())
185 : info[0].As<Napi::String>();
186 std::string salt = info[1].As<Napi::String>();
187 if (!(ValidateSalt(salt.c_str()))) {
188 throw Napi::Error::New(env, "Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue");
189 }
190 char bcrypted[_PASSWORD_LEN];
191 bcrypt(data.c_str(), data.length(), salt.c_str(), bcrypted);
192 return Napi::String::New(env, bcrypted, strlen(bcrypted));
193 }
194
195 /* COMPARATOR */
196 inline bool CompareStrings(const char* s1, const char* s2) {
197 return strcmp(s1, s2) == 0;
198 }
199
200 class CompareAsyncWorker : public Napi::AsyncWorker {
201 public:
202 CompareAsyncWorker(const Napi::Function& callback, const std::string& input, const std::string& encrypted)
203 : Napi::AsyncWorker(callback, "bcrypt:CompareAsyncWorker"), input(input), encrypted(encrypted) {
204 result = false;
205 }
206
207 ~CompareAsyncWorker() {}
208
209 void Execute() {
210 char bcrypted[_PASSWORD_LEN];
211 if (ValidateSalt(encrypted.c_str())) {
212 bcrypt(input.c_str(), input.length(), encrypted.c_str(), bcrypted);
213 result = CompareStrings(bcrypted, encrypted.c_str());
214 }
215 }
216
217 void OnOK() {
218 Napi::HandleScope scope(Env());
219 Callback().Call({Env().Undefined(), Napi::Boolean::New(Env(), result)});
220 }
221
222 private:
223 std::string input;
224 std::string encrypted;
225 bool result;
226 };
227
228 Napi::Value Compare(const Napi::CallbackInfo& info) {
229 if (info.Length() < 3) {
230 throw Napi::TypeError::New(info.Env(), "3 arguments expected");
231 }
232 std::string input = info[0].IsBuffer()
233 ? BufferToString(info[0].As<Napi::Buffer<char>>())
234 : info[0].As<Napi::String>();
235 std::string encrypted = info[1].As<Napi::String>();
236 Napi::Function callback = info[2].As<Napi::Function>();
237 CompareAsyncWorker* compareWorker = new CompareAsyncWorker(callback, input, encrypted);
238 compareWorker->Queue();
239 return info.Env().Undefined();
240 }
241
242 Napi::Value CompareSync(const Napi::CallbackInfo& info) {
243 Napi::Env env = info.Env();
244 if (info.Length() < 2) {
245 throw Napi::TypeError::New(info.Env(), "2 arguments expected");
246 }
247 std::string pw = info[0].IsBuffer()
248 ? BufferToString(info[0].As<Napi::Buffer<char>>())
249 : info[0].As<Napi::String>();
250 std::string hash = info[1].As<Napi::String>();
251 char bcrypted[_PASSWORD_LEN];
252 if (ValidateSalt(hash.c_str())) {
253 bcrypt(pw.c_str(), pw.length(), hash.c_str(), bcrypted);
254 return Napi::Boolean::New(env, CompareStrings(bcrypted, hash.c_str()));
255 } else {
256 return Napi::Boolean::New(env, false);
257 }
258 }
259
260 Napi::Value GetRounds(const Napi::CallbackInfo& info) {
261 Napi::Env env = info.Env();
262 if (info.Length() < 1) {
263 throw Napi::TypeError::New(env, "1 argument expected");
264 }
265 std::string hash = info[0].As<Napi::String>();
266 u_int32_t rounds;
267 if (!(rounds = bcrypt_get_rounds(hash.c_str()))) {
268 throw Napi::Error::New(env, "invalid hash provided");
269 }
270 return Napi::Number::New(env, rounds);
271 }
272
273} // anonymous namespace
274
275Napi::Object init(Napi::Env env, Napi::Object exports) {
276 exports.Set(Napi::String::New(env, "gen_salt_sync"), Napi::Function::New(env, GenerateSaltSync));
277 exports.Set(Napi::String::New(env, "encrypt_sync"), Napi::Function::New(env, EncryptSync));
278 exports.Set(Napi::String::New(env, "compare_sync"), Napi::Function::New(env, CompareSync));
279 exports.Set(Napi::String::New(env, "get_rounds"), Napi::Function::New(env, GetRounds));
280 exports.Set(Napi::String::New(env, "gen_salt"), Napi::Function::New(env, GenerateSalt));
281 exports.Set(Napi::String::New(env, "encrypt"), Napi::Function::New(env, Encrypt));
282 exports.Set(Napi::String::New(env, "compare"), Napi::Function::New(env, Compare));
283 return exports;
284}
285
286NODE_API_MODULE(NODE_GYP_MODULE_NAME, init)