Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,30 @@ jobs:
PGHOST: localhost
PGDATABASE: ci_db_test
PGTESTNOSSL: 'true'
# SCRAM_TEST_PGUSER: scram_test
# SCRAM_TEST_PGPASSWORD: test4scram
SHA256_TEST_PGUSER: sha256_test
SHA256_TEST_PGPASSWORD: test4@scram
steps:
- name: Show OS
run: |
uname -a
# - run: |
# psql \
# -c "SET password_encryption = 'scram-sha-256'" \
# -c "CREATE ROLE scram_test LOGIN PASSWORD 'test4scram'"
- name: Wait for GaussDB to be ready
run: |
timeout 60 bash -c 'until pg_isready -h localhost -p 5432; do sleep 2; done'
- name: Setup SHA256 authentication
run: |
# Wait for database to be fully started
sleep 15

# Get container ID
CONTAINER_ID=$(docker ps --filter "ancestor=opengauss/opengauss" --format "{{.ID}}")
docker exec $CONTAINER_ID su - omm -c "gs_guc set -D /var/lib/opengauss/data/ -c 'password_encryption_type = 2'"

# Add SHA256 authentication rule to pg_hba.conf
docker exec $CONTAINER_ID su - omm -c "gs_guc set -D /var/lib/opengauss/data/ -h 'host all sha256_test 0.0.0.0/0 sha256'"
docker exec $CONTAINER_ID su - omm -c "gs_ctl reload -D /var/lib/opengauss/data/"
sleep 5

PGPASSWORD=openGauss@123 psql -h localhost -U ci_user -d ci_db_test -c "CREATE ROLE sha256_test login password 'test4@scram';"
- uses: actions/checkout@v4
with:
persist-credentials: false
Expand Down
14 changes: 11 additions & 3 deletions packages/pg-protocol/src/inbound-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,10 @@ const testForMessage = function (buffer: Buffer, expectedMessage: any) {

const plainPasswordBuffer = buffers.authenticationCleartextPassword()
const md5PasswordBuffer = buffers.authenticationMD5Password()
const SASLBuffer = buffers.authenticationSASL()
const SASLContinueBuffer = buffers.authenticationSASLContinue()
const SASLFinalBuffer = buffers.authenticationSASLFinal()
// SASL authentication is no longer supported
// const SASLBuffer = buffers.authenticationSASL()
// const SASLContinueBuffer = buffers.authenticationSASLContinue()
// const SASLFinalBuffer = buffers.authenticationSASLFinal()

const expectedPlainPasswordMessage = {
name: 'authenticationCleartextPassword',
Expand All @@ -207,6 +208,8 @@ const expectedMD5PasswordMessage = {
salt: Buffer.from([1, 2, 3, 4]),
}

// SASL authentication is no longer supported
/*
const expectedSASLMessage = {
name: 'authenticationSASL',
mechanisms: ['SCRAM-SHA-256'],
Expand All @@ -221,6 +224,7 @@ const expectedSASLFinalMessage = {
name: 'authenticationSASLFinal',
data: 'data',
}
*/

const notificationResponseBuffer = buffers.notification(4, 'hi', 'boom')
const expectedNotificationResponseMessage = {
Expand All @@ -245,6 +249,9 @@ describe('PgPacketStream', function () {
testForMessage(authOkBuffer, expectedAuthenticationOkayMessage)
testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage)
testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage)

// SASL authentication tests are commented out as SASL is no longer supported
/*
testForMessage(SASLBuffer, expectedSASLMessage)
testForMessage(SASLContinueBuffer, expectedSASLContinueMessage)

Expand All @@ -261,6 +268,7 @@ describe('PgPacketStream', function () {
// and adds a test which is deterministic, rather than relying on network packet chunking
const extendedSASLFinalBuffer = Buffer.concat([SASLFinalBuffer, Buffer.from([1, 2, 4, 5])])
testForMessage(extendedSASLFinalBuffer, expectedSASLFinalMessage)
*/

testForMessage(paramStatusBuffer, expectedParameterStatusMessage)
testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage)
Expand Down
2 changes: 1 addition & 1 deletion packages/pg-protocol/src/outbound-serializer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('serializer', () => {
actual,
new BufferList()
.addInt16(3)
.addInt16(0)
.addInt16(0x33) // this is for gaussdb
.addCString('user')
.addCString('brian')
.addCString('database')
Expand Down
5 changes: 3 additions & 2 deletions packages/pg-protocol/src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import {
BackendMessage,
MessageName,
AuthenticationMD5Password,
AuthenticationSHA256Password,
// AuthenticationSHA256Password is imported but not used - temporarily commented
// AuthenticationSHA256Password,
NoticeMessage,
} from './messages'
import { BufferReader } from './buffer-reader'
Expand Down Expand Up @@ -340,7 +341,7 @@ export class Parser {
// } while (mechanism)
// }
// break
case 10: // AuthenticationSHA256Password
case 10: // AuthenticationSHA256Password
{
message.name = 'authenticationSHA256Password'
message.data = this.reader.bytes(length - 8)
Expand Down
3 changes: 3 additions & 0 deletions packages/pg-protocol/src/testing/test-buffers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const buffers = {
.join(true, 'R')
},

// SASL authentication is no longer supported - commented out
/*
authenticationSASL: function () {
return new BufferList().addInt32(10).addCString('SCRAM-SHA-256').addCString('').join(true, 'R')
},
Expand All @@ -32,6 +34,7 @@ const buffers = {
authenticationSASLFinal: function () {
return new BufferList().addInt32(12).addString('data').join(true, 'R')
},
*/

parameterStatus: function (name: string, value: string) {
return new BufferList().addCString(name).addCString(value).join(true, 'S')
Expand Down
172 changes: 83 additions & 89 deletions packages/pg/lib/crypto/rfc5802.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const crypto = require('crypto')
/**
* RFC5802 algorithm implementation
* reference: https://github.com/jackc/pgx/commit/3d247719df5910a2adcf935a5038efa26fde58e2#diff-e7952278d2dec72dcda153feed6236482904c063027f34c6474302b112e3d1c9
*
*
* @param {string} password - Password
* @param {string} random64code - 64-bit random code
* @param {string} token - 8-bit token
Expand All @@ -14,165 +14,159 @@ const crypto = require('crypto')
* @returns {Buffer} - Result byte array
*/
function RFC5802Algorithm(password, random64code, token, serverSignature, serverIteration, method) {
const k = generateKFromPBKDF2(password, random64code, serverIteration)
const serverKey = getKeyFromHmac(k, Buffer.from('Sever Key'))
const clientKey = getKeyFromHmac(k, Buffer.from('Client Key'))

let storedKey
if (method.toLowerCase() === 'sha256') {
storedKey = getSha256(clientKey)
} else {
throw new Error('Only sha256 method is supported')
}

const tokenByte = hexStringToBytes(token)
const clientSignature = getKeyFromHmac(serverKey, tokenByte)

if (serverSignature && serverSignature !== bytesToHexString(clientSignature)) {
return Buffer.from('')
}

const hmacResult = getKeyFromHmac(storedKey, tokenByte)
const h = xorBetweenPassword(hmacResult, clientKey, clientKey.length)
const k = generateKFromPBKDF2(password, random64code, serverIteration)
const serverKey = getKeyFromHmac(k, Buffer.from('Sever Key'))
const clientKey = getKeyFromHmac(k, Buffer.from('Client Key'))

let storedKey
if (method.toLowerCase() === 'sha256') {
storedKey = getSha256(clientKey)
} else {
throw new Error('Only sha256 method is supported')
}

const tokenByte = hexStringToBytes(token)
const clientSignature = getKeyFromHmac(serverKey, tokenByte)

return bytesToHex(h)
if (serverSignature && serverSignature !== bytesToHexString(clientSignature)) {
return Buffer.from('')
}

const hmacResult = getKeyFromHmac(storedKey, tokenByte)
const h = xorBetweenPassword(hmacResult, clientKey, clientKey.length)

return bytesToHex(h)
}

/**
* Generate key using PBKDF2
*
*
* @param {string} password - Password
* @param {string} random64code - 64-bit random code
* @param {number} serverIteration - Server iteration count
* @returns {Buffer} - Generated key
*/
function generateKFromPBKDF2(password, random64code, serverIteration) {
const random32code = hexStringToBytes(random64code)
return crypto.pbkdf2Sync(
password,
random32code,
serverIteration,
32,
'sha1'
)
const random32code = hexStringToBytes(random64code)
return crypto.pbkdf2Sync(password, random32code, serverIteration, 32, 'sha1')
}

/**
* Convert hex string to byte array
*
*
* @param {string} hexString - Hex string
* @returns {Buffer} - Byte array
*/
function hexStringToBytes(hexString) {
if (hexString === '') {
return Buffer.from('')
}
const upperString = hexString.toUpperCase()
const bytesLen = Math.floor(upperString.length / 2)
const array = Buffer.alloc(bytesLen)
for (let i = 0; i < bytesLen; i++) {
const pos = i * 2
array[i] = (charToByte(upperString.charAt(pos)) << 4) | charToByte(upperString.charAt(pos + 1))
}
return array
if (hexString === '') {
return Buffer.from('')
}

const upperString = hexString.toUpperCase()
const bytesLen = Math.floor(upperString.length / 2)
const array = Buffer.alloc(bytesLen)

for (let i = 0; i < bytesLen; i++) {
const pos = i * 2
array[i] = (charToByte(upperString.charAt(pos)) << 4) | charToByte(upperString.charAt(pos + 1))
}

return array
}

/**
* Convert hex character to byte
*
*
* @param {string} c - Hex character
* @returns {number} - Byte value
*/
function charToByte(c) {
return '0123456789ABCDEF'.indexOf(c)
return '0123456789ABCDEF'.indexOf(c)
}

/**
* Convert byte array to hex string
*
*
* @param {Buffer} src - Byte array
* @returns {string} - Hex string
*/
function bytesToHexString(src) {
let s = ''
for (let i = 0; i < src.length; i++) {
const v = src[i] & 0xFF
const hv = v.toString(16)
if (hv.length < 2) {
s += '0' + hv
} else {
s += hv
}
let s = ''
for (let i = 0; i < src.length; i++) {
const v = src[i] & 0xff
const hv = v.toString(16)
if (hv.length < 2) {
s += '0' + hv
} else {
s += hv
}
return s
}
return s
}

/**
* Calculate HMAC with key and data
*
*
* @param {Buffer} key - Key
* @param {Buffer} data - Data
* @returns {Buffer} - HMAC result
*/
function getKeyFromHmac(key, data) {
const hmac = crypto.createHmac('sha256', key)
hmac.update(data)
return hmac.digest()
const hmac = crypto.createHmac('sha256', key)
hmac.update(data)
return hmac.digest()
}

/**
* Calculate SHA256 hash
*
*
* @param {Buffer} message - Message
* @returns {Buffer} - SHA256 hash
*/
function getSha256(message) {
const hash = crypto.createHash('sha256')
hash.update(message)
return hash.digest()
const hash = crypto.createHash('sha256')
hash.update(message)
return hash.digest()
}

/**
* Perform XOR operation on two passwords
*
*
* @param {Buffer} password1 - First password
* @param {Buffer} password2 - Second password
* @param {number} length - Length
* @returns {Buffer} - XOR result
*/
function xorBetweenPassword(password1, password2, length) {
const array = Buffer.alloc(length)
for (let i = 0; i < length; i++) {
array[i] = password1[i] ^ password2[i]
}
return array
const array = Buffer.alloc(length)
for (let i = 0; i < length; i++) {
array[i] = password1[i] ^ password2[i]
}
return array
}

/**
* Convert byte array to hex bytes
*
*
* @param {Buffer} bytes - Byte array
* @returns {Buffer} - Hex bytes
*/
function bytesToHex(bytes) {
const lookup = Buffer.from('0123456789abcdef')
const result = Buffer.alloc(bytes.length * 2)
let pos = 0
for (let i = 0; i < bytes.length; i++) {
const c = bytes[i] & 0xFF
const j = c >> 4
result[pos] = lookup[j]
pos++
const k = c & 0xF
result[pos] = lookup[k]
pos++
}
return result
const lookup = Buffer.from('0123456789abcdef')
const result = Buffer.alloc(bytes.length * 2)
let pos = 0

for (let i = 0; i < bytes.length; i++) {
const c = bytes[i] & 0xff
const j = c >> 4
result[pos] = lookup[j]
pos++
const k = c & 0xf
result[pos] = lookup[k]
pos++
}

return result
}

module.exports = { RFC5802Algorithm }
Loading