Skip to content

Support GSSAPI Authentication/encryption #1095

@Freddo3000

Description

@Freddo3000

Postgres supports authentication and optionally encryption using GSSAPI, allowing authentication using Kerberos, SSPI and the like. I'm currently trying to create a PR using https://www.npmjs.com/package/kerberos which is used by mongodb-js, and poking around it seems like it should be possible to implement, though I've run into a bit of a roadblock when it comes to sending raw bytes as required by the GSSAPI authentication process.

WIP code diff
Index: src/connection.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/connection.js b/src/connection.js
--- a/src/connection.js	(revision 32feb259a3c9abffab761bd1758b3168d9e0cebc)
+++ b/src/connection.js	(date 1752946656209)
@@ -11,6 +11,8 @@
 import { Query, CLOSE } from './query.js'
 import b from './bytes.js'
 
+import {Kerberos} from "kerberos" // todo: make optional
+
 export default Connection
 
 let uid = 1
@@ -18,6 +20,7 @@
 const Sync = b().S().end()
     , Flush = b().H().end()
     , SSLRequest = b().i32(8).i32(80877103).end(8)
+    , GSSRequest = b().i32(8).i32(80877104).end(8)
     , ExecuteUnnamed = Buffer.concat([b().E().str(b.N).i32(0).end(), Sync])
     , DescribeUnnamed = b().D().str('S').str(b.N).end()
     , noop = () => { /* noop */ }
@@ -104,6 +107,8 @@
     , nonce = null
     , query = null
     , final = null
+    , kerberosClient = null
+    , kerberosEncClient = null
 
   const connection = {
     queue: queues.closed,
@@ -327,7 +332,6 @@
     terminated = false
     backendParameters = {}
     socket || (socket = await createSocket())
-
     if (!socket)
       return
 
@@ -353,7 +357,57 @@
     setTimeout(connect, closedDate ? closedDate + delay - performance.now() : 0)
   }
 
-  function connected() {
+  async function connectedGss() {
+    let gssToken
+    try {
+      kerberosEncClient = await Kerberos.initializeClient("postgres@" + host)
+      gssToken = await kerberosEncClient.step('')
+    } catch (e) {
+      return false
+    }
+
+    write(GSSRequest)
+    const canGSS = await new Promise(r => socket.once('data', x => r(x[0] === 71))) // G
+    if (!canGSS) {
+      return false
+    }
+    try {
+      while (!kerberosEncClient.contextComplete) {
+        let buffer = Buffer.from(gssToken, 'base64')
+        write(b().raw(buffer).end())
+        let ret = await new Promise(r => socket.once('data', x => r(x)))
+
+      }
+    } catch (e) {
+      console.log(e)
+      return false
+    }
+    const s = StartupMessage()
+    write(s)
+
+    statements = {}
+    needsTypes = options.fetch_types
+    statementId = Math.random().toString(36).slice(2)
+    statementCount = 1
+    lifeTimer.start()
+
+    socket.on('data', data)
+
+    keep_alive && socket.setKeepAlive && socket.setKeepAlive(true, 1000 * keep_alive)
+  }
+
+  async function connected() {
+    if (Kerberos !== null) {
+      try {
+        await connectedGss()
+      } catch (e) {
+        error(e)
+      }
+    }
     try {
       statements = {}
       needsTypes = options.fetch_types
@@ -654,6 +708,8 @@
     (
       type === 3 ? AuthenticationCleartextPassword :
       type === 5 ? AuthenticationMD5Password :
+      Kerberos !== null && type === 7 ? AuthenticationGss :
+      Kerberos !== null && type === 8 ? AuthenticationGssContinue :
       type === 10 ? SASL :
       type === 11 ? SASLContinue :
       type === 12 ? SASLFinal :
@@ -684,6 +740,22 @@
     )
   }
 
+  async function AuthenticationGss() {
+    console.assert(Kerberos !== null)
+
+    kerberosClient = await Kerberos.initializeClient("postgres/" + host) //todo
+    b().p().str('GSSAPI' + b.N)
+    const i = b.i
+    write(b.inc(1).str('p=' + kerberosClient.response).i32(b.i - i - 1, i).end())
+  }
+
+  async function AuthenticationGssContinue(x) {
+    console.assert(Kerberos !== null)
+    console.assert(kerberosClient !== null)
+
+
+  }
+
   async function SASL() {
     nonce = (await crypto.randomBytes(18)).toString('base64')
     b().p().str('SCRAM-SHA-256' + b.N)

The above code fails at write(b().raw(buffer).end()), and I'm not sure if returning data using ret = await new Promise(r => socket.once('data', x => r(x))) is correct.

ref:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions