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