pg 8.13.2 → 8.14.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/lib/client.js CHANGED
@@ -43,6 +43,7 @@ class Client extends EventEmitter {
43
43
  this._connectionError = false
44
44
  this._queryable = true
45
45
 
46
+ this.enableChannelBinding = Boolean(c.enableChannelBinding) // set true to use SCRAM-SHA-256-PLUS when offered
46
47
  this.connection =
47
48
  c.connection ||
48
49
  new Connection({
@@ -104,6 +105,10 @@ class Client extends EventEmitter {
104
105
  con._ending = true
105
106
  con.stream.destroy(new Error('timeout expired'))
106
107
  }, this._connectionTimeoutMillis)
108
+
109
+ if (this.connectionTimeoutHandle.unref) {
110
+ this.connectionTimeoutHandle.unref()
111
+ }
107
112
  }
108
113
 
109
114
  if (this.host && this.host.indexOf('/') === 0) {
@@ -258,7 +263,7 @@ class Client extends EventEmitter {
258
263
  _handleAuthSASL(msg) {
259
264
  this._checkPgPass(() => {
260
265
  try {
261
- this.saslSession = sasl.startSession(msg.mechanisms)
266
+ this.saslSession = sasl.startSession(msg.mechanisms, this.enableChannelBinding && this.connection.stream)
262
267
  this.connection.sendSASLInitialResponseMessage(this.saslSession.mechanism, this.saslSession.response)
263
268
  } catch (err) {
264
269
  this.connection.emit('error', err)
@@ -268,7 +273,12 @@ class Client extends EventEmitter {
268
273
 
269
274
  async _handleAuthSASLContinue(msg) {
270
275
  try {
271
- await sasl.continueSession(this.saslSession, this.password, msg.data)
276
+ await sasl.continueSession(
277
+ this.saslSession,
278
+ this.password,
279
+ msg.data,
280
+ this.enableChannelBinding && this.connection.stream
281
+ )
272
282
  this.connection.sendSCRAMClientFinalMessage(this.saslSession.response)
273
283
  } catch (err) {
274
284
  this.connection.emit('error', err)
@@ -0,0 +1,121 @@
1
+ function x509Error(msg, cert) {
2
+ throw new Error('SASL channel binding: ' + msg + ' when parsing public certificate ' + cert.toString('base64'))
3
+ }
4
+
5
+ function readASN1Length(data, index) {
6
+ let length = data[index++]
7
+ if (length < 0x80) return { length, index }
8
+
9
+ const lengthBytes = length & 0x7f
10
+ if (lengthBytes > 4) x509Error('bad length', data)
11
+
12
+ length = 0
13
+ for (let i = 0; i < lengthBytes; i++) {
14
+ length = (length << 8) | data[index++]
15
+ }
16
+
17
+ return { length, index }
18
+ }
19
+
20
+ function readASN1OID(data, index) {
21
+ if (data[index++] !== 0x6) x509Error('non-OID data', data) // 6 = OID
22
+
23
+ const { length: OIDLength, index: indexAfterOIDLength } = readASN1Length(data, index)
24
+ index = indexAfterOIDLength
25
+ lastIndex = index + OIDLength
26
+
27
+ const byte1 = data[index++]
28
+ let oid = ((byte1 / 40) >> 0) + '.' + (byte1 % 40)
29
+
30
+ while (index < lastIndex) {
31
+ // loop over numbers in OID
32
+ let value = 0
33
+ while (index < lastIndex) {
34
+ // loop over bytes in number
35
+ const nextByte = data[index++]
36
+ value = (value << 7) | (nextByte & 0x7f)
37
+ if (nextByte < 0x80) break
38
+ }
39
+ oid += '.' + value
40
+ }
41
+
42
+ return { oid, index }
43
+ }
44
+
45
+ function expectASN1Seq(data, index) {
46
+ if (data[index++] !== 0x30) x509Error('non-sequence data', data) // 30 = Sequence
47
+ return readASN1Length(data, index)
48
+ }
49
+
50
+ function signatureAlgorithmHashFromCertificate(data, index) {
51
+ // read this thread: https://www.postgresql.org/message-id/17760-b6c61e752ec07060%40postgresql.org
52
+ if (index === undefined) index = 0
53
+ index = expectASN1Seq(data, index).index
54
+ const { length: certInfoLength, index: indexAfterCertInfoLength } = expectASN1Seq(data, index)
55
+ index = indexAfterCertInfoLength + certInfoLength // skip over certificate info
56
+ index = expectASN1Seq(data, index).index // skip over signature length field
57
+ const { oid, index: indexAfterOID } = readASN1OID(data, index)
58
+ switch (oid) {
59
+ // RSA
60
+ case '1.2.840.113549.1.1.4':
61
+ return 'MD5'
62
+ case '1.2.840.113549.1.1.5':
63
+ return 'SHA-1'
64
+ case '1.2.840.113549.1.1.11':
65
+ return 'SHA-256'
66
+ case '1.2.840.113549.1.1.12':
67
+ return 'SHA-384'
68
+ case '1.2.840.113549.1.1.13':
69
+ return 'SHA-512'
70
+ case '1.2.840.113549.1.1.14':
71
+ return 'SHA-224'
72
+ case '1.2.840.113549.1.1.15':
73
+ return 'SHA512-224'
74
+ case '1.2.840.113549.1.1.16':
75
+ return 'SHA512-256'
76
+ // ECDSA
77
+ case '1.2.840.10045.4.1':
78
+ return 'SHA-1'
79
+ case '1.2.840.10045.4.3.1':
80
+ return 'SHA-224'
81
+ case '1.2.840.10045.4.3.2':
82
+ return 'SHA-256'
83
+ case '1.2.840.10045.4.3.3':
84
+ return 'SHA-384'
85
+ case '1.2.840.10045.4.3.4':
86
+ return 'SHA-512'
87
+ // RSASSA-PSS: hash is indicated separately
88
+ case '1.2.840.113549.1.1.10':
89
+ index = indexAfterOID
90
+ index = expectASN1Seq(data, index).index
91
+ if (data[index++] !== 0xa0) x509Error('non-tag data', data) // a0 = constructed tag 0
92
+ index = readASN1Length(data, index).index // skip over tag length field
93
+ index = expectASN1Seq(data, index).index // skip over sequence length field
94
+ const { oid: hashOID } = readASN1OID(data, index)
95
+ switch (hashOID) {
96
+ // standalone hash OIDs
97
+ case '1.2.840.113549.2.5':
98
+ return 'MD5'
99
+ case '1.3.14.3.2.26':
100
+ return 'SHA-1'
101
+ case '2.16.840.1.101.3.4.2.1':
102
+ return 'SHA-256'
103
+ case '2.16.840.1.101.3.4.2.2':
104
+ return 'SHA-384'
105
+ case '2.16.840.1.101.3.4.2.3':
106
+ return 'SHA-512'
107
+ }
108
+ x509Error('unknown hash OID ' + hashOID, data)
109
+ // Ed25519 -- see https: return//github.com/openssl/openssl/issues/15477
110
+ case '1.3.101.110':
111
+ case '1.3.101.112': // ph
112
+ return 'SHA-512'
113
+ // Ed448 -- still not in pg 17.2 (if supported, digest would be SHAKE256 x 64 bytes)
114
+ case '1.3.101.111':
115
+ case '1.3.101.113': // ph
116
+ x509Error('Ed448 certificate channel binding is not currently supported by Postgres')
117
+ }
118
+ x509Error('unknown OID ' + oid, data)
119
+ }
120
+
121
+ module.exports = { signatureAlgorithmHashFromCertificate }
@@ -1,22 +1,34 @@
1
1
  'use strict'
2
2
  const crypto = require('./utils')
3
+ const { signatureAlgorithmHashFromCertificate } = require('./cert-signatures')
3
4
 
4
- function startSession(mechanisms) {
5
- if (mechanisms.indexOf('SCRAM-SHA-256') === -1) {
6
- throw new Error('SASL: Only mechanism SCRAM-SHA-256 is currently supported')
5
+ function startSession(mechanisms, stream) {
6
+ const candidates = ['SCRAM-SHA-256']
7
+ if (stream) candidates.unshift('SCRAM-SHA-256-PLUS') // higher-priority, so placed first
8
+
9
+ const mechanism = candidates.find((candidate) => mechanisms.includes(candidate))
10
+
11
+ if (!mechanism) {
12
+ throw new Error('SASL: Only mechanism(s) ' + candidates.join(' and ') + ' are supported')
13
+ }
14
+
15
+ if (mechanism === 'SCRAM-SHA-256-PLUS' && typeof stream.getPeerCertificate !== 'function') {
16
+ // this should never happen if we are really talking to a Postgres server
17
+ throw new Error('SASL: Mechanism SCRAM-SHA-256-PLUS requires a certificate')
7
18
  }
8
19
 
9
20
  const clientNonce = crypto.randomBytes(18).toString('base64')
21
+ const gs2Header = mechanism === 'SCRAM-SHA-256-PLUS' ? 'p=tls-server-end-point' : stream ? 'y' : 'n'
10
22
 
11
23
  return {
12
- mechanism: 'SCRAM-SHA-256',
24
+ mechanism,
13
25
  clientNonce,
14
- response: 'n,,n=*,r=' + clientNonce,
26
+ response: gs2Header + ',,n=*,r=' + clientNonce,
15
27
  message: 'SASLInitialResponse',
16
28
  }
17
29
  }
18
30
 
19
- async function continueSession(session, password, serverData) {
31
+ async function continueSession(session, password, serverData, stream) {
20
32
  if (session.message !== 'SASLInitialResponse') {
21
33
  throw new Error('SASL: Last message was not SASLInitialResponse')
22
34
  }
@@ -40,7 +52,21 @@ async function continueSession(session, password, serverData) {
40
52
 
41
53
  var clientFirstMessageBare = 'n=*,r=' + session.clientNonce
42
54
  var serverFirstMessage = 'r=' + sv.nonce + ',s=' + sv.salt + ',i=' + sv.iteration
43
- var clientFinalMessageWithoutProof = 'c=biws,r=' + sv.nonce
55
+
56
+ // without channel binding:
57
+ let channelBinding = stream ? 'eSws' : 'biws' // 'y,,' or 'n,,', base64-encoded
58
+
59
+ // override if channel binding is in use:
60
+ if (session.mechanism === 'SCRAM-SHA-256-PLUS') {
61
+ const peerCert = stream.getPeerCertificate().raw
62
+ let hashName = signatureAlgorithmHashFromCertificate(peerCert)
63
+ if (hashName === 'MD5' || hashName === 'SHA-1') hashName = 'SHA-256'
64
+ const certHash = await crypto.hashByName(hashName, peerCert)
65
+ const bindingData = Buffer.concat([Buffer.from('p=tls-server-end-point,,'), Buffer.from(certHash)])
66
+ channelBinding = bindingData.toString('base64')
67
+ }
68
+
69
+ var clientFinalMessageWithoutProof = 'c=' + channelBinding + ',r=' + sv.nonce
44
70
  var authMessage = clientFirstMessageBare + ',' + serverFirstMessage + ',' + clientFinalMessageWithoutProof
45
71
 
46
72
  var saltBytes = Buffer.from(sv.salt, 'base64')
@@ -19,6 +19,11 @@ function sha256(text) {
19
19
  return nodeCrypto.createHash('sha256').update(text).digest()
20
20
  }
21
21
 
22
+ function hashByName(hashName, text) {
23
+ hashName = hashName.replace(/(\D)-/, '$1') // e.g. SHA-256 -> SHA256
24
+ return nodeCrypto.createHash(hashName).update(text).digest()
25
+ }
26
+
22
27
  function hmacSha256(key, msg) {
23
28
  return nodeCrypto.createHmac('sha256', key).update(msg).digest()
24
29
  }
@@ -32,6 +37,7 @@ module.exports = {
32
37
  randomBytes: nodeCrypto.randomBytes,
33
38
  deriveKey,
34
39
  sha256,
40
+ hashByName,
35
41
  hmacSha256,
36
42
  md5,
37
43
  }
@@ -5,6 +5,7 @@ module.exports = {
5
5
  randomBytes,
6
6
  deriveKey,
7
7
  sha256,
8
+ hashByName,
8
9
  hmacSha256,
9
10
  md5,
10
11
  }
@@ -60,6 +61,10 @@ async function sha256(text) {
60
61
  return await subtleCrypto.digest('SHA-256', text)
61
62
  }
62
63
 
64
+ async function hashByName(hashName, text) {
65
+ return await subtleCrypto.digest(hashName, text)
66
+ }
67
+
63
68
  /**
64
69
  * Sign the message with the given key
65
70
  * @param {ArrayBuffer} keyBuffer
package/lib/result.js CHANGED
@@ -88,11 +88,11 @@ class Result {
88
88
  this._parsers = new Array(fieldDescriptions.length)
89
89
  }
90
90
 
91
- this._prebuiltEmptyResultObject = {}
91
+ var row = {}
92
92
 
93
93
  for (var i = 0; i < fieldDescriptions.length; i++) {
94
94
  var desc = fieldDescriptions[i]
95
- this._prebuiltEmptyResultObject[desc.name] = null
95
+ row[desc.name] = null
96
96
 
97
97
  if (this._types) {
98
98
  this._parsers[i] = this._types.getTypeParser(desc.dataTypeID, desc.format || 'text')
@@ -100,6 +100,8 @@ class Result {
100
100
  this._parsers[i] = types.getTypeParser(desc.dataTypeID, desc.format || 'text')
101
101
  }
102
102
  }
103
+
104
+ this._prebuiltEmptyResultObject = { ...row }
103
105
  }
104
106
  }
105
107
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pg",
3
- "version": "8.13.2",
3
+ "version": "8.14.0",
4
4
  "description": "PostgreSQL client - pure javascript & libpq with the same API",
5
5
  "keywords": [
6
6
  "database",
@@ -21,8 +21,8 @@
21
21
  "main": "./lib",
22
22
  "dependencies": {
23
23
  "pg-connection-string": "^2.7.0",
24
- "pg-pool": "^3.7.1",
25
- "pg-protocol": "^1.7.1",
24
+ "pg-pool": "^3.8.0",
25
+ "pg-protocol": "^1.8.0",
26
26
  "pg-types": "^2.1.0",
27
27
  "pgpass": "1.x"
28
28
  },
@@ -58,5 +58,5 @@
58
58
  "engines": {
59
59
  "node": ">= 8.0.0"
60
60
  },
61
- "gitHead": "732580782ffa2f9299f7112ff27cfa4534cd22f3"
61
+ "gitHead": "f7c92e487c6a9c9600585f9de14cb17e7a65e76e"
62
62
  }