@@ -15,8 +15,72 @@ import (
1515 "crypto/sha256"
1616 "crypto/x509"
1717 "encoding/pem"
18+ "sync"
1819)
1920
21+ // server pub keys registry
22+ var (
23+ serverPubKeyLock sync.RWMutex
24+ serverPubKeyRegistry map [string ]* rsa.PublicKey
25+ )
26+
27+ // RegisterServerPubKey registers a server RSA public key which can be used to
28+ // send data in a secure manner to the server without receiving the public key
29+ // in a potentially insecure way from the server first.
30+ // Registered keys can afterwards be used adding serverPubKey=<name> to the DSN.
31+ //
32+ // Note: The provided rsa.PublicKey instance is exclusively owned by the driver
33+ // after registering it and may not be modified.
34+ //
35+ // data, err := ioutil.ReadFile("mykey.pem")
36+ // if err != nil {
37+ // log.Fatal(err)
38+ // }
39+ //
40+ // block, _ := pem.Decode(data)
41+ // if block == nil || block.Type != "PUBLIC KEY" {
42+ // log.Fatal("failed to decode PEM block containing public key")
43+ // }
44+ //
45+ // pub, err := x509.ParsePKIXPublicKey(block.Bytes)
46+ // if err != nil {
47+ // log.Fatal(err)
48+ // }
49+ //
50+ // if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
51+ // mysql.RegisterServerPubKey("mykey", rsaPubKey)
52+ // } else {
53+ // log.Fatal("not a RSA public key")
54+ // }
55+ //
56+ func RegisterServerPubKey (name string , pubKey * rsa.PublicKey ) {
57+ serverPubKeyLock .Lock ()
58+ if serverPubKeyRegistry == nil {
59+ serverPubKeyRegistry = make (map [string ]* rsa.PublicKey )
60+ }
61+
62+ serverPubKeyRegistry [name ] = pubKey
63+ serverPubKeyLock .Unlock ()
64+ }
65+
66+ // DeregisterServerPubKey removes the public key registered with the given name.
67+ func DeregisterServerPubKey (name string ) {
68+ serverPubKeyLock .Lock ()
69+ if serverPubKeyRegistry != nil {
70+ delete (serverPubKeyRegistry , name )
71+ }
72+ serverPubKeyLock .Unlock ()
73+ }
74+
75+ func getServerPubKey (name string ) (pubKey * rsa.PublicKey ) {
76+ serverPubKeyLock .RLock ()
77+ if v , ok := serverPubKeyRegistry [name ]; ok {
78+ pubKey = v
79+ }
80+ serverPubKeyLock .RUnlock ()
81+ return
82+ }
83+
2084// Hash password using pre 4.1 (old password) method
2185// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
2286type myRnd struct {
@@ -154,19 +218,22 @@ func scrambleSHA256Password(scramble []byte, password string) []byte {
154218 return message1
155219}
156220
157- func ( mc * mysqlConn ) sendEncryptedPassword ( seed []byte , pub * rsa.PublicKey ) error {
158- plain := make ([]byte , len (mc . cfg . Passwd )+ 1 )
159- copy (plain , mc . cfg . Passwd )
221+ func encryptPassword ( password string , seed []byte , pub * rsa.PublicKey ) ([] byte , error ) {
222+ plain := make ([]byte , len (password )+ 1 )
223+ copy (plain , password )
160224 for i := range plain {
161225 j := i % len (seed )
162226 plain [i ] ^= seed [j ]
163227 }
164228 sha1 := sha1 .New ()
165- enc , err := rsa .EncryptOAEP (sha1 , rand .Reader , pub , plain , nil )
229+ return rsa .EncryptOAEP (sha1 , rand .Reader , pub , plain , nil )
230+ }
231+
232+ func (mc * mysqlConn ) sendEncryptedPassword (seed []byte , pub * rsa.PublicKey ) error {
233+ enc , err := encryptPassword (mc .cfg .Passwd , seed , pub )
166234 if err != nil {
167235 return err
168236 }
169-
170237 return mc .writeAuthSwitchPacket (enc , false )
171238}
172239
@@ -211,9 +278,16 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, bool, error)
211278 // write cleartext auth packet
212279 return []byte (mc .cfg .Passwd ), true , nil
213280 }
214- // request public key
215- // TODO: allow to specify a local file with the pub key via the DSN
216- return []byte {1 }, false , nil
281+
282+ pubKey := mc .cfg .pubKey
283+ if pubKey == nil {
284+ // request public key from server
285+ return []byte {1 }, false , nil
286+ }
287+
288+ // encrypted password
289+ enc , err := encryptPassword (mc .cfg .Passwd , authData , pubKey )
290+ return enc , false , err
217291
218292 default :
219293 errLog .Print ("unknown auth plugin:" , plugin )
@@ -283,28 +357,29 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
283357 return err
284358 }
285359 } else {
286- // TODO: allow to specify a local file with the pub key via
287- // the DSN
288-
289- // request public key
290- data := mc .buf .takeSmallBuffer (4 + 1 )
291- data [4 ] = cachingSha2PasswordRequestPublicKey
292- mc .writePacket (data )
293-
294- // parse public key
295- data , err := mc .readPacket ()
296- if err != nil {
297- return err
298- }
299-
300- block , _ := pem .Decode (data [1 :])
301- pub , err := x509 .ParsePKIXPublicKey (block .Bytes )
302- if err != nil {
303- return err
360+ pubKey := mc .cfg .pubKey
361+ if pubKey == nil {
362+ // request public key from server
363+ data := mc .buf .takeSmallBuffer (4 + 1 )
364+ data [4 ] = cachingSha2PasswordRequestPublicKey
365+ mc .writePacket (data )
366+
367+ // parse public key
368+ data , err := mc .readPacket ()
369+ if err != nil {
370+ return err
371+ }
372+
373+ block , _ := pem .Decode (data [1 :])
374+ pkix , err := x509 .ParsePKIXPublicKey (block .Bytes )
375+ if err != nil {
376+ return err
377+ }
378+ pubKey = pkix .(* rsa.PublicKey )
304379 }
305380
306381 // send encrypted password
307- err = mc .sendEncryptedPassword (oldAuthData , pub .( * rsa. PublicKey ) )
382+ err = mc .sendEncryptedPassword (oldAuthData , pubKey )
308383 if err != nil {
309384 return err
310385 }
0 commit comments