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 +4 -0
- package/SPONSORS.md +1 -0
- package/lib/client.js +23 -0
- package/lib/connection.js +62 -16
- package/lib/sasl.js +146 -0
- package/package.json +1 -1
- package/.travis.yml +0 -38
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
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 =
|
|
442
|
+
var code = this.parseInt32(buffer)
|
|
443
|
+
|
|
425
444
|
var msg = new Message('authenticationOk', length)
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
msg
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
msg.
|
|
438
|
-
|
|
439
|
-
|
|
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
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"
|