Skip to content

Commit ddb39b9

Browse files
committed
v3.0.3-alpha.2
1 parent 3c51537 commit ddb39b9

28 files changed

+1787
-797
lines changed

api/pool.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ type PoolOption struct {
5858

5959
// try reconnect times
6060
TryReconnectNums *int
61+
62+
// if enable SCRAM login verify
63+
EnableScram bool
6164
}
6265

6366
// NewDBConnectionPool inits a DBConnectionPool object and configures it with opt, finally returns it.
@@ -107,6 +110,7 @@ func newConn(addr string, opt *PoolOption) (dialer.Conn, error) {
107110
HighAvailabilitySites: opt.HighAvailabilitySites,
108111
Reconnect: opt.Reconnect,
109112
TryReconnectNums: opt.TryReconnectNums,
113+
EnableScram: opt.EnableScram,
110114
}
111115
conn, err := dialer.NewConn(context.TODO(), addr, bOpt)
112116
if err != nil {

dialer/behavior.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ type BehaviorOptions struct {
3838

3939
// UsePython specifies whether the session uses a Python parser
4040
UsePython bool
41+
42+
// if enable SCRAM login verify
43+
EnableScram bool
4144
}
4245

4346
// SetPriority sets the priority of the task.

dialer/dialer.go

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ type Conn interface {
6969
// GetTCPConn returns the TCPConn
7070
GetTCPConn() *net.TCPConn
7171

72+
// for inner use
7273
GetReader() protocol.Reader
74+
enableScram() bool
7375
}
7476

7577
type conn struct {
@@ -289,32 +291,9 @@ func (c *conn) connect(addr string) error {
289291
c.isConnected = true
290292
c.isClosed = false
291293
c.refreshHeaderForResponse(h)
292-
if c.userID != "" {
293-
args := make([]model.DataForm, 2)
294-
user, err := model.NewDataType(model.DtString, c.userID)
295-
if err != nil {
296-
return err
297-
}
298-
pwd, err := model.NewDataType(model.DtString, c.password)
299-
if err != nil {
300-
return err
301-
}
302-
args[0] = model.NewScalar(user)
303-
args[1] = model.NewScalar(pwd)
304-
305-
bo := defaultByteOrder
306294

307-
_, _, err = c.run(&requestParams{
308-
commandType: functionCmd,
309-
Command: generateFunctionCommand("login", bo, args),
310-
SessionID: []byte(c.GetSession()),
311-
Args: args,
312-
ByteOrder: bo,
313-
})
314-
315-
if err != nil {
316-
return err
317-
}
295+
if c.userID != "" {
296+
Login(c, c.userID, c.password)
318297
}
319298

320299
return nil
@@ -425,6 +404,7 @@ func (c *conn) run(params *requestParams) (*responseHeader, model.DataForm, erro
425404
continue
426405
}
427406
}
407+
time.Sleep(300 * time.Millisecond)
428408
err := c.switchDatanode(nil)
429409
if err != nil {
430410
return nil, nil, err
@@ -481,3 +461,7 @@ func (c *conn) runInternal(params *requestParams) (*responseHeader, model.DataFo
481461
func (c *conn) refreshHeaderForResponse(h *responseHeader) {
482462
c.sessionID = h.sessionID
483463
}
464+
465+
func (c *conn) enableScram() bool {
466+
return c.behaviorOpt.EnableScram
467+
}

dialer/util.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ package dialer
22

33
import (
44
"bytes"
5+
"crypto/hmac"
6+
"crypto/rand"
7+
"crypto/sha256"
8+
"encoding/base64"
9+
"fmt"
510
"io/ioutil"
611
"os"
712
"path/filepath"
@@ -10,6 +15,7 @@ import (
1015

1116
"github.com/dolphindb/api-go/v3/dialer/protocol"
1217
"github.com/dolphindb/api-go/v3/model"
18+
"golang.org/x/crypto/pbkdf2"
1319
)
1420

1521
const (
@@ -113,6 +119,14 @@ func parseAddr(raw string) string {
113119
}
114120

115121
func Login(conn Conn, userID, password string) error {
122+
if conn.enableScram() {
123+
return scramLogin(conn, userID, password)
124+
} else {
125+
err := scramLogin(conn, userID, password)
126+
if err == nil {
127+
return nil
128+
}
129+
}
116130
args := make([]model.DataForm, 2)
117131
user, err := model.NewDataType(model.DtString, userID)
118132
if err != nil {
@@ -131,3 +145,130 @@ func Login(conn Conn, userID, password string) error {
131145
}
132146
return nil
133147
}
148+
149+
func generateNonce(length int) (string, error) {
150+
buffer := make([]byte, length)
151+
_, err := rand.Read(buffer)
152+
if err != nil {
153+
return "", err
154+
}
155+
return base64.StdEncoding.EncodeToString(buffer), nil
156+
}
157+
158+
func xorBytes(a, b []byte) []byte {
159+
result := make([]byte, len(a))
160+
for i := range a {
161+
result[i] = a[i] ^ b[i]
162+
}
163+
return result
164+
}
165+
166+
func scramLogin(conn Conn, userID, password string) error {
167+
args := make([]model.DataForm, 2)
168+
user, err := model.NewDataType(model.DtString, userID)
169+
if err != nil {
170+
return fmt.Errorf("SCRAM login failed, %w", err)
171+
}
172+
clientNonce, err := generateNonce(16)
173+
if err != nil {
174+
return fmt.Errorf("SCRAM login failed, %w", err)
175+
}
176+
nonce, err := model.NewDataType(model.DtString, clientNonce)
177+
if err != nil {
178+
return fmt.Errorf("SCRAM login failed, %w", err)
179+
}
180+
args[0] = model.NewScalar(user)
181+
args[1] = model.NewScalar(nonce)
182+
183+
result, err := conn.RunFunc("scramClientFirst", args)
184+
if err != nil {
185+
if strings.Contains(err.Error(), "Can't recognize function name scramClientFirst") {
186+
return fmt.Errorf("SCRAM login is unavailable on current server")
187+
}
188+
if strings.Contains(err.Error(), "sha256 authMode doesn't support scram authMode") {
189+
return fmt.Errorf("user '%s' doesn't support scram authMode", userID)
190+
}
191+
return fmt.Errorf("scramClientFirst failed: %w", err)
192+
}
193+
194+
retVec := result.(*model.Vector)
195+
196+
if retVec.Rows() != 3 {
197+
return fmt.Errorf("SCRAM login failed, server error: get server nonce failed")
198+
}
199+
saltStr := retVec.Get(0).Value().(*model.Scalar).Value().(string)
200+
iterCount := int(retVec.Get(1).Value().(*model.Scalar).Value().(int32))
201+
combinedNonce := retVec.Get(2).Value().(*model.Scalar).Value().(string)
202+
203+
salt, err := base64.StdEncoding.DecodeString(saltStr)
204+
if err != nil {
205+
return fmt.Errorf("SCRAM login failed, base64 decode failed: %w", err)
206+
}
207+
208+
saltedPassword := pbkdf2.Key([]byte(password), salt, iterCount, 32, sha256.New)
209+
210+
mac := hmac.New(sha256.New, saltedPassword)
211+
_, err = mac.Write([]byte("Client Key"))
212+
if err != nil {
213+
return fmt.Errorf("SCRAM login failed, HMAC calculation failed: %w", err)
214+
}
215+
clientKey := mac.Sum(nil)
216+
217+
storedKey := sha256.Sum256(clientKey)
218+
219+
authMessage := fmt.Sprintf(`n=%s,r=%s,r=%s,s=%s,i=%d,c=biws,r=%s`,
220+
userID, clientNonce, combinedNonce, saltStr, iterCount, combinedNonce)
221+
222+
mac = hmac.New(sha256.New, storedKey[:])
223+
_, err = mac.Write([]byte(authMessage))
224+
if err != nil {
225+
return fmt.Errorf("SCRAM login failed, HMAC calculation failed: %w", err)
226+
}
227+
clientSig := mac.Sum(nil)
228+
229+
proof := xorBytes(clientKey, clientSig)
230+
231+
finalArgs := make([]model.DataForm, 3)
232+
combinedNonceScalar, err := model.NewDataType(model.DtString, combinedNonce)
233+
if err != nil {
234+
return fmt.Errorf("SCRAM login failed, %w", err)
235+
}
236+
proofScalar, err := model.NewDataType(model.DtString, base64.StdEncoding.EncodeToString(proof))
237+
if err != nil {
238+
return fmt.Errorf("SCRAM login failed, %w", err)
239+
}
240+
241+
finalArgs[0] = model.NewScalar(user)
242+
finalArgs[1] = model.NewScalar(combinedNonceScalar)
243+
finalArgs[2] = model.NewScalar(proofScalar)
244+
245+
finalResult, err := conn.RunFunc("scramClientFinal", finalArgs)
246+
if err != nil {
247+
return fmt.Errorf("scramClientFinal failed: %w", err)
248+
}
249+
serverSigBase64 := finalResult.(*model.Scalar).Value().(string)
250+
251+
mac = hmac.New(sha256.New, saltedPassword)
252+
_, err = mac.Write([]byte("Server Key"))
253+
if err != nil {
254+
return fmt.Errorf("SCRAM login failed, HMAC calculation failed: %w", err)
255+
}
256+
serverKey := mac.Sum(nil)
257+
258+
mac = hmac.New(sha256.New, serverKey)
259+
_, err = mac.Write([]byte(authMessage))
260+
if err != nil {
261+
return fmt.Errorf("SCRAM login failed, HMAC calculation failed: %w", err)
262+
}
263+
serverSig := mac.Sum(nil)
264+
265+
expectedSig := base64.StdEncoding.EncodeToString(serverSig)
266+
267+
if serverSigBase64 != "" && expectedSig != serverSigBase64 {
268+
conn.Close()
269+
return fmt.Errorf("invalid SCRAM server signature")
270+
}
271+
272+
fmt.Println("SCRAM login succeeded")
273+
return nil
274+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ require (
99

1010
require (
1111
github.com/davecgh/go-spew v1.1.1 // indirect
12+
github.com/forgoer/openssl v1.6.0
1213
github.com/kr/pretty v0.3.0 // indirect
1314
github.com/shopspring/decimal v1.3.1
1415
github.com/smartystreets/goconvey v1.7.2
16+
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8
1517
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
1618
)

streaming/abstract_client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import (
66

77
// AbstractClient is the client interface for streaming subscription.
88
type AbstractClient interface {
9-
activeCloseConnection(si *site) error
10-
doReconnect(si *site) bool
9+
activeCloseConnection(req *SubscribeRequest) error
10+
doReconnect(req *SubscribeRequest) bool
1111
getSubscriber() *subscriber
1212
getConn() (net.Conn, bool)
1313

0 commit comments

Comments
 (0)