pg 7.8.2 → 7.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ For richer information consult the commit log on github with referenced pull req
4
4
 
5
5
  We do not include break-fix version release in this file.
6
6
 
7
+ ### 7.9.0
8
+
9
+ - Add support for [sasl/scram authentication](https://github.com/brianc/node-postgres/pull/1835).
10
+
7
11
  ### 7.8.0
8
12
 
9
13
  - Add support for passing [secureOptions](https://github.com/brianc/node-postgres/pull/1804) SSL config.
package/SPONSORS.md CHANGED
@@ -2,6 +2,7 @@ node-postgres is made possible by the helpful contributors from the community we
2
2
 
3
3
  # Leaders
4
4
  - [MadKudu](https://www.madkudu.com) - [@madkudu](https://twitter.com/madkudu)
5
+ - [Third Iron](https://thirdiron.com/)
5
6
 
6
7
  # Supporters
7
8
  - John Fawcett
package/lib/client.js CHANGED
@@ -10,6 +10,7 @@
10
10
  var EventEmitter = require('events').EventEmitter
11
11
  var util = require('util')
12
12
  var utils = require('./utils')
13
+ var sasl = require('./sasl')
13
14
  var pgPass = require('pgpass')
14
15
  var TypeOverrides = require('./type-overrides')
15
16
 
@@ -126,6 +127,28 @@ Client.prototype._connect = function (callback) {
126
127
  con.password(utils.postgresMd5PasswordHash(self.user, self.password, msg.salt))
127
128
  }))
128
129
 
130
+ // password request handling (SASL)
131
+ var saslSession
132
+ con.on('authenticationSASL', checkPgPass(function (msg) {
133
+ saslSession = sasl.startSession(msg.mechanisms)
134
+
135
+ con.sendSASLInitialResponseMessage(saslSession.mechanism, saslSession.response)
136
+ }))
137
+
138
+ // password request handling (SASL)
139
+ con.on('authenticationSASLContinue', function (msg) {
140
+ sasl.continueSession(saslSession, self.password, msg.data)
141
+
142
+ con.sendSCRAMClientFinalMessage(saslSession.response)
143
+ })
144
+
145
+ // password request handling (SASL)
146
+ con.on('authenticationSASLFinal', function (msg) {
147
+ sasl.finalizeSession(saslSession, msg.data)
148
+
149
+ saslSession = null
150
+ })
151
+
129
152
  con.once('backendKeyData', function (msg) {
130
153
  self.processID = msg.processID
131
154
  self.secretKey = msg.secretKey
package/lib/connection.js CHANGED
@@ -191,6 +191,24 @@ Connection.prototype.password = function (password) {
191
191
  this._send(0x70, this.writer.addCString(password))
192
192
  }
193
193
 
194
+ Connection.prototype.sendSASLInitialResponseMessage = function (mechanism, initialResponse) {
195
+ // 0x70 = 'p'
196
+ this.writer
197
+ .addCString(mechanism)
198
+ .addInt32(Buffer.byteLength(initialResponse))
199
+ .addString(initialResponse)
200
+
201
+ this._send(0x70)
202
+ }
203
+
204
+ Connection.prototype.sendSCRAMClientFinalMessage = function (additionalData) {
205
+ // 0x70 = 'p'
206
+ this.writer
207
+ .addString(additionalData)
208
+
209
+ this._send(0x70)
210
+ }
211
+
194
212
  Connection.prototype._send = function (code, more) {
195
213
  if (!this.stream.writable) {
196
214
  return false
@@ -421,25 +439,53 @@ Connection.prototype.parseMessage = function (buffer) {
421
439
  }
422
440
 
423
441
  Connection.prototype.parseR = function (buffer, length) {
424
- var code = 0
442
+ var code = this.parseInt32(buffer)
443
+
425
444
  var msg = new Message('authenticationOk', length)
426
- if (msg.length === 8) {
427
- code = this.parseInt32(buffer)
428
- if (code === 3) {
429
- msg.name = 'authenticationCleartextPassword'
430
- }
431
- return msg
432
- }
433
- if (msg.length === 12) {
434
- code = this.parseInt32(buffer)
435
- if (code === 5) { // md5 required
436
- msg.name = 'authenticationMD5Password'
437
- msg.salt = Buffer.alloc(4)
438
- buffer.copy(msg.salt, 0, this.offset, this.offset + 4)
439
- this.offset += 4
445
+
446
+ switch (code) {
447
+ case 0: // AuthenticationOk
448
+ return msg
449
+ case 3: // AuthenticationCleartextPassword
450
+ if (msg.length === 8) {
451
+ msg.name = 'authenticationCleartextPassword'
452
+ return msg
453
+ }
454
+ break
455
+ case 5: // AuthenticationMD5Password
456
+ if (msg.length === 12) {
457
+ msg.name = 'authenticationMD5Password'
458
+ msg.salt = Buffer.alloc(4)
459
+ buffer.copy(msg.salt, 0, this.offset, this.offset + 4)
460
+ this.offset += 4
461
+ return msg
462
+ }
463
+
464
+ break
465
+ case 10: // AuthenticationSASL
466
+ msg.name = 'authenticationSASL'
467
+ msg.mechanisms = []
468
+ do {
469
+ var mechanism = this.parseCString(buffer)
470
+
471
+ if (mechanism) {
472
+ msg.mechanisms.push(mechanism)
473
+ }
474
+ } while (mechanism)
475
+
476
+ return msg
477
+ case 11: // AuthenticationSASLContinue
478
+ msg.name = 'authenticationSASLContinue'
479
+ msg.data = this.readString(buffer, length - 4)
480
+
481
+ return msg
482
+ case 12: // AuthenticationSASLFinal
483
+ msg.name = 'authenticationSASLFinal'
484
+ msg.data = this.readString(buffer, length - 4)
485
+
440
486
  return msg
441
- }
442
487
  }
488
+
443
489
  throw new Error('Unknown authenticationOk message type' + util.inspect(msg))
444
490
  }
445
491
 
package/lib/sasl.js ADDED
@@ -0,0 +1,146 @@
1
+ const crypto = require('crypto')
2
+
3
+ function startSession (mechanisms) {
4
+ if (mechanisms.indexOf('SCRAM-SHA-256') === -1) {
5
+ throw new Error('SASL: Only mechanism SCRAM-SHA-256 is currently supported')
6
+ }
7
+
8
+ const clientNonce = crypto.randomBytes(18).toString('base64')
9
+
10
+ return {
11
+ mechanism: 'SCRAM-SHA-256',
12
+ clientNonce,
13
+ response: 'n,,n=*,r=' + clientNonce,
14
+ message: 'SASLInitialResponse'
15
+ }
16
+ }
17
+
18
+ function continueSession (session, password, serverData) {
19
+ if (session.message !== 'SASLInitialResponse') {
20
+ throw new Error('SASL: Last message was not SASLInitialResponse')
21
+ }
22
+
23
+ const sv = extractVariablesFromFirstServerMessage(serverData)
24
+
25
+ if (!sv.nonce.startsWith(session.clientNonce)) {
26
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not start with client nonce')
27
+ }
28
+
29
+ var saltBytes = Buffer.from(sv.salt, 'base64')
30
+
31
+ var saltedPassword = Hi(password, saltBytes, sv.iteration)
32
+
33
+ var clientKey = createHMAC(saltedPassword, 'Client Key')
34
+ var storedKey = crypto.createHash('sha256').update(clientKey).digest()
35
+
36
+ var clientFirstMessageBare = 'n=*,r=' + session.clientNonce
37
+ var serverFirstMessage = 'r=' + sv.nonce + ',s=' + sv.salt + ',i=' + sv.iteration
38
+
39
+ var clientFinalMessageWithoutProof = 'c=biws,r=' + sv.nonce
40
+
41
+ var authMessage = clientFirstMessageBare + ',' + serverFirstMessage + ',' + clientFinalMessageWithoutProof
42
+
43
+ var clientSignature = createHMAC(storedKey, authMessage)
44
+ var clientProofBytes = xorBuffers(clientKey, clientSignature)
45
+ var clientProof = clientProofBytes.toString('base64')
46
+
47
+ var serverKey = createHMAC(saltedPassword, 'Server Key')
48
+ var serverSignatureBytes = createHMAC(serverKey, authMessage)
49
+
50
+ session.message = 'SASLResponse'
51
+ session.serverSignature = serverSignatureBytes.toString('base64')
52
+ session.response = clientFinalMessageWithoutProof + ',p=' + clientProof
53
+ }
54
+
55
+ function finalizeSession (session, serverData) {
56
+ if (session.message !== 'SASLResponse') {
57
+ throw new Error('SASL: Last message was not SASLResponse')
58
+ }
59
+
60
+ var serverSignature
61
+
62
+ String(serverData).split(',').forEach(function (part) {
63
+ switch (part[0]) {
64
+ case 'v':
65
+ serverSignature = part.substr(2)
66
+ break
67
+ }
68
+ })
69
+
70
+ if (serverSignature !== session.serverSignature) {
71
+ throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match')
72
+ }
73
+ }
74
+
75
+ function extractVariablesFromFirstServerMessage (data) {
76
+ var nonce, salt, iteration
77
+
78
+ String(data).split(',').forEach(function (part) {
79
+ switch (part[0]) {
80
+ case 'r':
81
+ nonce = part.substr(2)
82
+ break
83
+ case 's':
84
+ salt = part.substr(2)
85
+ break
86
+ case 'i':
87
+ iteration = parseInt(part.substr(2), 10)
88
+ break
89
+ }
90
+ })
91
+
92
+ if (!nonce) {
93
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing')
94
+ }
95
+
96
+ if (!salt) {
97
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt missing')
98
+ }
99
+
100
+ if (!iteration) {
101
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing')
102
+ }
103
+
104
+ return {
105
+ nonce,
106
+ salt,
107
+ iteration
108
+ }
109
+ }
110
+
111
+ function xorBuffers (a, b) {
112
+ if (!Buffer.isBuffer(a)) a = Buffer.from(a)
113
+ if (!Buffer.isBuffer(b)) b = Buffer.from(b)
114
+ var res = []
115
+ if (a.length > b.length) {
116
+ for (var i = 0; i < b.length; i++) {
117
+ res.push(a[i] ^ b[i])
118
+ }
119
+ } else {
120
+ for (var j = 0; j < a.length; j++) {
121
+ res.push(a[j] ^ b[j])
122
+ }
123
+ }
124
+ return Buffer.from(res)
125
+ }
126
+
127
+ function createHMAC (key, msg) {
128
+ return crypto.createHmac('sha256', key).update(msg).digest()
129
+ }
130
+
131
+ function Hi (password, saltBytes, iterations) {
132
+ var ui1 = createHMAC(password, Buffer.concat([saltBytes, Buffer.from([0, 0, 0, 1])]))
133
+ var ui = ui1
134
+ for (var i = 0; i < iterations - 1; i++) {
135
+ ui1 = createHMAC(password, ui1)
136
+ ui = xorBuffers(ui, ui1)
137
+ }
138
+
139
+ return ui
140
+ }
141
+
142
+ module.exports = {
143
+ startSession,
144
+ continueSession,
145
+ finalizeSession
146
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pg",
3
- "version": "7.8.2",
3
+ "version": "7.9.0",
4
4
  "description": "PostgreSQL client - pure javascript & libpq with the same API",
5
5
  "keywords": [
6
6
  "database",
package/.travis.yml DELETED
@@ -1,38 +0,0 @@
1
- language: node_js
2
- sudo: false
3
- dist: trusty
4
- before_script:
5
- - node script/create-test-tables.js pg://postgres@127.0.0.1:5432/postgres
6
- env:
7
- - CC=clang CXX=clang++ npm_config_clang=1 PGUSER=postgres PGDATABASE=postgres
8
-
9
- matrix:
10
- include:
11
- - node_js: "lts/boron"
12
- addons:
13
- postgresql: "9.6"
14
- - node_js: "lts/argon"
15
- addons:
16
- postgresql: "9.6"
17
- - node_js: "10"
18
- addons:
19
- postgresql: "9.6"
20
- - node_js: "lts/carbon"
21
- addons:
22
- postgresql: "9.1"
23
- dist: precise
24
- - node_js: "lts/carbon"
25
- addons:
26
- postgresql: "9.2"
27
- - node_js: "lts/carbon"
28
- addons:
29
- postgresql: "9.3"
30
- - node_js: "lts/carbon"
31
- addons:
32
- postgresql: "9.4"
33
- - node_js: "lts/carbon"
34
- addons:
35
- postgresql: "9.5"
36
- - node_js: "lts/carbon"
37
- addons:
38
- postgresql: "9.6"