mongodb.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. var format = require('util').format,
  2. dns = require('dns');
  3. var MongoAuthProcess = function(host, port, service_name, options) {
  4. // Check what system we are on
  5. if(process.platform == 'win32') {
  6. this._processor = new Win32MongoProcessor(host, port, service_name, options);
  7. } else {
  8. this._processor = new UnixMongoProcessor(host, port, service_name, options);
  9. }
  10. }
  11. MongoAuthProcess.prototype.init = function(username, password, callback) {
  12. this._processor.init(username, password, callback);
  13. }
  14. MongoAuthProcess.prototype.transition = function(payload, callback) {
  15. this._processor.transition(payload, callback);
  16. }
  17. /*******************************************************************
  18. *
  19. * Win32 SSIP Processor for MongoDB
  20. *
  21. *******************************************************************/
  22. var Win32MongoProcessor = function(host, port, service_name, options) {
  23. options = options || {};
  24. this.host = host;
  25. this.port = port
  26. // Set up service name
  27. service_name = service_name || "mongodb";
  28. // Options
  29. this.gssapiServiceName = options.gssapiServiceName || service_name;
  30. this.gssapiServiceRealm = options.gssapiServiceRealm;
  31. this.gssapiCanonicalizeHostName = typeof options.gssapiCanonicalizeHostName == 'boolean' ? options.gssapiCanonicalizeHostName : false;
  32. // SSIP classes
  33. this.ssip = require("../kerberos").SSIP;
  34. // Set up first transition
  35. this._transition = Win32MongoProcessor.first_transition(this);
  36. // Number of retries
  37. this.retries = 10;
  38. }
  39. Win32MongoProcessor.prototype.init = function(username, password, callback) {
  40. var self = this;
  41. // Save the values used later
  42. this.username = username;
  43. this.password = password;
  44. // Canonicialize host name if needed
  45. var performGssapiCanonicalizeHostName = function(gssapiCanonicalizeHostName, host, callback) {
  46. if(!gssapiCanonicalizeHostName) return callback();
  47. // Attempt to resolve the host name
  48. dns.resolveCname(host, function(err, r) {
  49. if(err) return callback(err);
  50. // Get the first resolve host id
  51. if(Array.isArray(r) && r.length > 0) {
  52. self.host = r[0];
  53. }
  54. callback();
  55. });
  56. }
  57. // Canonicialize host name if needed
  58. performGssapiCanonicalizeHostName(this.gssapiCanonicalizeHostName, this.host, function(err) {
  59. if(err) return callback(err);
  60. // Acquire credentials
  61. self.ssip.SecurityCredentials.aquire_kerberos(username, password, function(err, security_credentials) {
  62. if(err) return callback(err);
  63. // Set up target
  64. self.target = format("%s/%s", self.gssapiServiceName, self.host);
  65. // Do we have a service realm
  66. if(self.gssapiServiceRealm) {
  67. self.target = format("%s@%s", self.target, self.gssapiServiceRealm);
  68. }
  69. // Save credentials
  70. self.security_credentials = security_credentials;
  71. // Callback with success
  72. callback(null);
  73. });
  74. });
  75. }
  76. Win32MongoProcessor.prototype.transition = function(payload, callback) {
  77. if(this._transition == null) return callback(new Error("Transition finished"));
  78. this._transition(payload, callback);
  79. }
  80. Win32MongoProcessor.first_transition = function(self) {
  81. return function(payload, callback) {
  82. self.ssip.SecurityContext.initialize(
  83. self.security_credentials,
  84. self.target,
  85. payload, function(err, security_context) {
  86. if(err) return callback(err);
  87. // If no context try again until we have no more retries
  88. if(!security_context.hasContext) {
  89. if(self.retries == 0) return callback(new Error("Failed to initialize security context"));
  90. // Update the number of retries
  91. self.retries = self.retries - 1;
  92. // Set next transition
  93. return self.transition(payload, callback);
  94. }
  95. // Set next transition
  96. self._transition = Win32MongoProcessor.second_transition(self);
  97. self.security_context = security_context;
  98. // Return the payload
  99. callback(null, security_context.payload);
  100. });
  101. }
  102. }
  103. Win32MongoProcessor.second_transition = function(self) {
  104. return function(payload, callback) {
  105. // Perform a step
  106. self.security_context.initialize(self.target, payload, function(err, security_context) {
  107. if(err) return callback(err);
  108. // If no context try again until we have no more retries
  109. if(!security_context.hasContext) {
  110. if(self.retries == 0) return callback(new Error("Failed to initialize security context"));
  111. // Update the number of retries
  112. self.retries = self.retries - 1;
  113. // Set next transition
  114. self._transition = Win32MongoProcessor.first_transition(self);
  115. // Retry
  116. return self.transition(payload, callback);
  117. }
  118. // Set next transition
  119. self._transition = Win32MongoProcessor.third_transition(self);
  120. // Return the payload
  121. callback(null, security_context.payload);
  122. });
  123. }
  124. }
  125. Win32MongoProcessor.third_transition = function(self) {
  126. return function(payload, callback) {
  127. var messageLength = 0;
  128. // Get the raw bytes
  129. var encryptedBytes = new Buffer(payload, 'base64');
  130. var encryptedMessage = new Buffer(messageLength);
  131. // Copy first byte
  132. encryptedBytes.copy(encryptedMessage, 0, 0, messageLength);
  133. // Set up trailer
  134. var securityTrailerLength = encryptedBytes.length - messageLength;
  135. var securityTrailer = new Buffer(securityTrailerLength);
  136. // Copy the bytes
  137. encryptedBytes.copy(securityTrailer, 0, messageLength, securityTrailerLength);
  138. // Types used
  139. var SecurityBuffer = self.ssip.SecurityBuffer;
  140. var SecurityBufferDescriptor = self.ssip.SecurityBufferDescriptor;
  141. // Set up security buffers
  142. var buffers = [
  143. new SecurityBuffer(SecurityBuffer.DATA, encryptedBytes)
  144. , new SecurityBuffer(SecurityBuffer.STREAM, securityTrailer)
  145. ];
  146. // Set up the descriptor
  147. var descriptor = new SecurityBufferDescriptor(buffers);
  148. // Decrypt the data
  149. self.security_context.decryptMessage(descriptor, function(err, security_context) {
  150. if(err) return callback(err);
  151. var length = 4;
  152. if(self.username != null) {
  153. length += self.username.length;
  154. }
  155. var bytesReceivedFromServer = new Buffer(length);
  156. bytesReceivedFromServer[0] = 0x01; // NO_PROTECTION
  157. bytesReceivedFromServer[1] = 0x00; // NO_PROTECTION
  158. bytesReceivedFromServer[2] = 0x00; // NO_PROTECTION
  159. bytesReceivedFromServer[3] = 0x00; // NO_PROTECTION
  160. if(self.username != null) {
  161. var authorization_id_bytes = new Buffer(self.username, 'utf8');
  162. authorization_id_bytes.copy(bytesReceivedFromServer, 4, 0);
  163. }
  164. self.security_context.queryContextAttributes(0x00, function(err, sizes) {
  165. if(err) return callback(err);
  166. var buffers = [
  167. new SecurityBuffer(SecurityBuffer.TOKEN, new Buffer(sizes.securityTrailer))
  168. , new SecurityBuffer(SecurityBuffer.DATA, bytesReceivedFromServer)
  169. , new SecurityBuffer(SecurityBuffer.PADDING, new Buffer(sizes.blockSize))
  170. ]
  171. var descriptor = new SecurityBufferDescriptor(buffers);
  172. self.security_context.encryptMessage(descriptor, 0x80000001, function(err, security_context) {
  173. if(err) return callback(err);
  174. callback(null, security_context.payload);
  175. });
  176. });
  177. });
  178. }
  179. }
  180. /*******************************************************************
  181. *
  182. * UNIX MIT Kerberos processor
  183. *
  184. *******************************************************************/
  185. var UnixMongoProcessor = function(host, port, service_name, options) {
  186. options = options || {};
  187. this.host = host;
  188. this.port = port
  189. // SSIP classes
  190. this.Kerberos = require("../kerberos").Kerberos;
  191. this.kerberos = new this.Kerberos();
  192. // Set up service name
  193. service_name = service_name || "mongodb";
  194. // Options
  195. this.gssapiServiceName = options.gssapiServiceName || service_name;
  196. this.gssapiServiceRealm = options.gssapiServiceRealm;
  197. this.gssapiCanonicalizeHostName = typeof options.gssapiCanonicalizeHostName == 'boolean' ? options.gssapiCanonicalizeHostName : false;
  198. // Set up first transition
  199. this._transition = UnixMongoProcessor.first_transition(this);
  200. // Set up target
  201. this.target = format("%s@%s", service_name, host);
  202. // Number of retries
  203. this.retries = 10;
  204. }
  205. UnixMongoProcessor.prototype.init = function(username, password, callback) {
  206. var self = this;
  207. this.username = username;
  208. this.password = password;
  209. // Canonicialize host name if needed
  210. var performGssapiCanonicalizeHostName = function(gssapiCanonicalizeHostName, host, callback) {
  211. if(!gssapiCanonicalizeHostName) return callback();
  212. // Attempt to resolve the host name
  213. dns.resolveCname(host, function(err, r) {
  214. if(err) return callback(err);
  215. // Get the first resolve host id
  216. if(Array.isArray(r) && r.length > 0) {
  217. self.host = r[0];
  218. }
  219. callback();
  220. });
  221. }
  222. // Canonicialize host name if needed
  223. performGssapiCanonicalizeHostName(this.gssapiCanonicalizeHostName, this.host, function(err) {
  224. if(err) return callback(err);
  225. // Set up target
  226. self.target = format("%s@%s", self.gssapiServiceName, self.host);
  227. // Call client initiate
  228. self.kerberos.authGSSClientInit(
  229. self.target
  230. , self.Kerberos.GSS_C_MUTUAL_FLAG, function(err, context) {
  231. self.context = context;
  232. // Return the context
  233. callback(null, context);
  234. });
  235. });
  236. }
  237. UnixMongoProcessor.prototype.transition = function(payload, callback) {
  238. if(this._transition == null) return callback(new Error("Transition finished"));
  239. this._transition(payload, callback);
  240. }
  241. UnixMongoProcessor.first_transition = function(self) {
  242. return function(payload, callback) {
  243. self.kerberos.authGSSClientStep(self.context, '', function(err, result) {
  244. if(err) return callback(err);
  245. // Set up the next step
  246. self._transition = UnixMongoProcessor.second_transition(self);
  247. // Return the payload
  248. callback(null, self.context.response);
  249. })
  250. }
  251. }
  252. UnixMongoProcessor.second_transition = function(self) {
  253. return function(payload, callback) {
  254. self.kerberos.authGSSClientStep(self.context, payload, function(err, result) {
  255. if(err && self.retries == 0) return callback(err);
  256. // Attempt to re-establish a context
  257. if(err) {
  258. // Adjust the number of retries
  259. self.retries = self.retries - 1;
  260. // Call same step again
  261. return self.transition(payload, callback);
  262. }
  263. // Set up the next step
  264. self._transition = UnixMongoProcessor.third_transition(self);
  265. // Return the payload
  266. callback(null, self.context.response || '');
  267. });
  268. }
  269. }
  270. UnixMongoProcessor.third_transition = function(self) {
  271. return function(payload, callback) {
  272. // GSS Client Unwrap
  273. self.kerberos.authGSSClientUnwrap(self.context, payload, function(err, result) {
  274. if(err) return callback(err, false);
  275. // Wrap the response
  276. self.kerberos.authGSSClientWrap(self.context, self.context.response, self.username, function(err, result) {
  277. if(err) return callback(err, false);
  278. // Set up the next step
  279. self._transition = UnixMongoProcessor.fourth_transition(self);
  280. // Return the payload
  281. callback(null, self.context.response);
  282. });
  283. });
  284. }
  285. }
  286. UnixMongoProcessor.fourth_transition = function(self) {
  287. return function(payload, callback) {
  288. // Clean up context
  289. self.kerberos.authGSSClientClean(self.context, function(err, result) {
  290. if(err) return callback(err, false);
  291. // Set the transition to null
  292. self._transition = null;
  293. // Callback with valid authentication
  294. callback(null, true);
  295. });
  296. }
  297. }
  298. // Set the process
  299. exports.MongoAuthProcess = MongoAuthProcess;