Skip to content

Commit ab5118f

Browse files
committed
auth: allow using a local server pub key
1 parent cdec9e2 commit ab5118f

File tree

5 files changed

+360
-29
lines changed

5 files changed

+360
-29
lines changed

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,19 @@ other cases. You should ensure your application will never cause an ERROR 1290
301301
except for `read-only` mode when enabling this option.
302302

303303

304+
##### `serverPubKey`
305+
306+
```
307+
Type: string
308+
Valid Values: <name>
309+
Default: none
310+
```
311+
312+
Server public keys can be registered with [`mysql.RegisterServerPubKey`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterServerPubKey), which can then be used by the assigned name in the DSN.
313+
Public keys are used to transmit encrypted data, e.g. for authentication.
314+
If the server's public key is known, it should be set manually to avoid expensive and potentially insecure transmissions of the public key from the server to the client each time it is required.
315+
316+
304317
##### `timeout`
305318

306319
```

auth.go

+102-27
Original file line numberDiff line numberDiff line change
@@ -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
2286
type 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

Comments
 (0)