Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit fc589cb

Browse files
author
Gusted
authored
Add client_ed25519 authentication (#1518)
Implements the necessary client code for [ed25519 authentication](https://mariadb.com/kb/en/authentication-plugin-ed25519/). 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 d9f4383 commit fc589cb

File tree

6 files changed

+108
-5
lines changed

6 files changed

+108
-5
lines changed

‎AUTHORS‎

Lines changed: 1 addition & 0 deletions
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>

‎auth.go‎

Lines changed: 47 additions & 0 deletions
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+
// authEd25519 does ed25519 authentication used by MariaDB.
232+
func authEd25519(scramble []byte, password string) ([]byte, error) {
233+
// Derived from https://github.com/MariaDB/server/blob/d8e6bb00888b1f82c031938f4c8ac5d97f6874c3/plugin/auth_ed25519/ref10/sign.c
234+
// Code style is from https://cs.opensource.google/go/go/+/refs/tags/go1.21.5:src/crypto/ed25519/ed25519.go;l=207
235+
h := sha512.Sum512([]byte(password))
236+
237+
s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32])
238+
if err != nil {
239+
return nil, err
240+
}
241+
A := (&edwards25519.Point{}).ScalarBaseMult(s)
242+
243+
mh := sha512.New()
244+
mh.Write(h[32:])
245+
mh.Write(scramble)
246+
messageDigest := mh.Sum(nil)
247+
r, err := edwards25519.NewScalar().SetUniformBytes(messageDigest)
248+
if err != nil {
249+
return nil, err
250+
}
251+
252+
R := (&edwards25519.Point{}).ScalarBaseMult(r)
253+
254+
kh := sha512.New()
255+
kh.Write(R.Bytes())
256+
kh.Write(A.Bytes())
257+
kh.Write(scramble)
258+
hramDigest := kh.Sum(nil)
259+
k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest)
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,12 @@ 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+
return authEd25519(authData, mc.cfg.Passwd)
339+
293340
default:
294341
mc.cfg.Logger.Print("unknown auth plugin:", plugin)
295342
return nil, ErrUnknownPlugin

‎auth_test.go‎

Lines changed: 51 additions & 0 deletions
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+
}

‎driver_test.go‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,14 @@ func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
165165
for _, test := range tests {
166166
t.Run("default", func(t *testing.T) {
167167
dbt := &DBTest{t, db}
168+
defer dbt.db.Exec("DROP TABLE IF EXISTS test")
168169
test(dbt)
169-
dbt.db.Exec("DROP TABLE IF EXISTS test")
170170
})
171171
if db2 != nil {
172172
t.Run("interpolateParams", func(t *testing.T) {
173173
dbt2 := &DBTest{t, db2}
174+
defer dbt2.db.Exec("DROP TABLE IF EXISTS test")
174175
test(dbt2)
175-
dbt2.db.Exec("DROP TABLE IF EXISTS test")
176176
})
177177
}
178178
}
@@ -3181,14 +3181,14 @@ func TestRawBytesAreNotModified(t *testing.T) {
31813181

31823182
rows, err := dbt.db.QueryContext(ctx, `SELECT id, value FROM test`)
31833183
if err != nil {
3184-
t.Fatal(err)
3184+
dbt.Fatal(err)
31853185
}
31863186

31873187
var b int
31883188
var raw sql.RawBytes
31893189
for rows.Next() {
31903190
if err := rows.Scan(&b, &raw); err != nil {
3191-
t.Fatal(err)
3191+
dbt.Fatal(err)
31923192
}
31933193

31943194
before := string(raw)
@@ -3198,7 +3198,7 @@ func TestRawBytesAreNotModified(t *testing.T) {
31983198
after := string(raw)
31993199

32003200
if before != after {
3201-
t.Fatalf("the backing storage for sql.RawBytes has been modified (i=%v)", i)
3201+
dbt.Fatalf("the backing storage for sql.RawBytes has been modified (i=%v)", i)
32023202
}
32033203
}
32043204
rows.Close()

‎go.mod‎

Lines changed: 2 additions & 0 deletions
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

‎go.sum‎

Lines changed: 2 additions & 0 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /