Skip to content

Commit 68789d1

Browse files
committed
Add client_ed25519 authentication
- Implements the necessary client code for [ed25519 authentication](https://mariadb.com/kb/en/authentication-plugin-ed25519/). - Add a test directly from the reference implementation to verify it works. - A continuation of #1220, but doesn't use CGO. - This patch uses filippo.io/edwards25519 to implement the crypto bits. The standard library `crypto/ed25519` cannot be used as MariaDB chose a scheme that is simply not compatible with what the standard library provides.
1 parent 98d7289 commit 68789d1

File tree

5 files changed

+104
-0
lines changed

5 files changed

+104
-0
lines changed

Diff for: AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Evan Elias <evan at skeema.net>
3939
Evan Shaw <evan at vendhq.com>
4040
Frederick Mayle <frederickmayle at gmail.com>
4141
Gustavo Kristic <gkristic at gmail.com>
42+
Gusted <postmaster at gusted.xyz>
4243
Hajime Nakagami <nakagami at gmail.com>
4344
Hanno Braun <mail at hannobraun.com>
4445
Henri Yandell <flamefew at gmail.com>

Diff for: auth.go

+48
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ import (
1313
"crypto/rsa"
1414
"crypto/sha1"
1515
"crypto/sha256"
16+
"crypto/sha512"
1617
"crypto/x509"
1718
"encoding/pem"
1819
"fmt"
1920
"sync"
21+
22+
"filippo.io/edwards25519"
2023
)
2124

2225
// server pub keys registry
@@ -225,6 +228,44 @@ func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte,
225228
return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil)
226229
}
227230

231+
// Derived from https://github.com/MariaDB/server/blob/d8e6bb00888b1f82c031938f4c8ac5d97f6874c3/plugin/auth_ed25519/ref10/sign.c
232+
func doEd25519Auth(scramble []byte, password string) ([]byte, error) {
233+
h := sha512.Sum512([]byte(password))
234+
235+
s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32])
236+
if err != nil {
237+
return nil, err
238+
}
239+
240+
nonceHash := sha512.New()
241+
nonceHash.Write(h[32:])
242+
nonceHash.Write(scramble)
243+
nonce := nonceHash.Sum(nil)
244+
245+
r, err := edwards25519.NewScalar().SetUniformBytes(nonce)
246+
if err != nil {
247+
return nil, err
248+
}
249+
R := (&edwards25519.Point{}).ScalarBaseMult(r)
250+
251+
A := (&edwards25519.Point{}).ScalarBaseMult(s)
252+
253+
kHash := sha512.New()
254+
kHash.Write(R.Bytes())
255+
kHash.Write(A.Bytes())
256+
kHash.Write(scramble)
257+
k := kHash.Sum(nil)
258+
259+
K, err := edwards25519.NewScalar().SetUniformBytes(k)
260+
if err != nil {
261+
return nil, err
262+
}
263+
264+
S := K.MultiplyAdd(K, s, r)
265+
266+
return append(R.Bytes(), S.Bytes()...), nil
267+
}
268+
228269
func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) error {
229270
enc, err := encryptPassword(mc.cfg.Passwd, seed, pub)
230271
if err != nil {
@@ -290,6 +331,13 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
290331
enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
291332
return enc, err
292333

334+
case "client_ed25519":
335+
if len(authData) != 32 {
336+
return nil, ErrMalformPkt
337+
}
338+
339+
return doEd25519Auth(authData, mc.cfg.Passwd)
340+
293341
default:
294342
mc.cfg.Logger.Print("unknown auth plugin:", plugin)
295343
return nil, ErrUnknownPlugin

Diff for: auth_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -1328,3 +1328,54 @@ func TestAuthSwitchSHA256PasswordSecure(t *testing.T) {
13281328
t.Errorf("got unexpected data: %v", conn.written)
13291329
}
13301330
}
1331+
1332+
// Derived from https://github.com/MariaDB/server/blob/6b2287fff23fbdc362499501c562f01d0d2db52e/plugin/auth_ed25519/ed25519-t.c
1333+
func TestEd25519Auth(t *testing.T) {
1334+
conn, mc := newRWMockConn(1)
1335+
mc.cfg.User = "root"
1336+
mc.cfg.Passwd = "foobar"
1337+
1338+
authData := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
1339+
plugin := "client_ed25519"
1340+
1341+
// Send Client Authentication Packet
1342+
authResp, err := mc.auth(authData, plugin)
1343+
if err != nil {
1344+
t.Fatal(err)
1345+
}
1346+
err = mc.writeHandshakeResponsePacket(authResp, plugin)
1347+
if err != nil {
1348+
t.Fatal(err)
1349+
}
1350+
1351+
// check written auth response
1352+
authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
1353+
authRespEnd := authRespStart + 1 + len(authResp)
1354+
writtenAuthRespLen := conn.written[authRespStart]
1355+
writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
1356+
expectedAuthResp := []byte{
1357+
232, 61, 201, 63, 67, 63, 51, 53, 86, 73, 238, 35, 170, 117, 146,
1358+
214, 26, 17, 35, 9, 8, 132, 245, 141, 48, 99, 66, 58, 36, 228, 48,
1359+
84, 115, 254, 187, 168, 88, 162, 249, 57, 35, 85, 79, 238, 167, 106,
1360+
68, 117, 56, 135, 171, 47, 20, 14, 133, 79, 15, 229, 124, 160, 176,
1361+
100, 138, 14,
1362+
}
1363+
if writtenAuthRespLen != 64 {
1364+
t.Fatalf("expected 64 bytes from client, got %d", writtenAuthRespLen)
1365+
}
1366+
if !bytes.Equal(writtenAuthResp, expectedAuthResp) {
1367+
t.Fatalf("auth response did not match expected value:\n%v\n%v", writtenAuthResp, expectedAuthResp)
1368+
}
1369+
conn.written = nil
1370+
1371+
// auth response
1372+
conn.data = []byte{
1373+
7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, // OK
1374+
}
1375+
conn.maxReads = 1
1376+
1377+
// Handle response to auth packet
1378+
if err := mc.handleAuthResult(authData, plugin); err != nil {
1379+
t.Errorf("got error: %v", err)
1380+
}
1381+
}

Diff for: go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
module github.com/go-sql-driver/mysql
22

33
go 1.18
4+
5+
require filippo.io/edwards25519 v1.1.0

Diff for: go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
2+
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=

0 commit comments

Comments
 (0)