@@ -2,6 +2,11 @@ package dialer
22
33import (
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
1521const (
@@ -113,6 +119,14 @@ func parseAddr(raw string) string {
113119}
114120
115121func 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+ }
0 commit comments