blob: f1c872c31adbca759dd9f0442787ba50073bdc14 [file] [edit]
package mysql
import (
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"net"
"testing"
)
func TestConnectorRejectsUnsolicitedPublicKey(t *testing.T) {
otherKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
der, err := x509.MarshalPKIXPublicKey(&otherKey.PublicKey)
if err != nil {
t.Fatal(err)
}
keyData := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der})
for _, plugin := range []string{"caching_sha2_password", "sha256_password"} {
t.Run(plugin, func(t *testing.T) {
caps := clientMySQL | clientProtocol41 | clientSecureConn | clientPluginAuth | clientPluginAuthLenEncClientData
mock := &mockConn{
data: authTestHandshake(plugin, caps),
queuedReplies: [][]byte{makePacket(2, append([]byte{iAuthMoreData}, keyData...))},
maxReads: 2,
}
cfg := NewConfig()
cfg.User, cfg.Passwd = "user", "secret"
cfg.pubKey = testPubKeyRSA
cfg.DialFunc = func(context.Context, string, string) (net.Conn, error) { return mock, nil }
c, err := NewConnector(cfg)
if err != nil {
t.Fatal(err)
}
if _, err := c.Connect(t.Context()); !errors.Is(err, ErrMalformPkt) {
t.Fatalf("Connect = %v, want ErrMalformPkt", err)
}
// Only the initial handshake may be sent, never a password encrypted
// using the peer's unsolicited key (nor COM_QUIT after an auth error).
if mock.writes != 1 {
t.Fatalf("writes = %d, want only the handshake response", mock.writes)
}
})
}
}
func TestAuthContinuationStates(t *testing.T) {
for _, tc := range []struct {
name string
plugin string
tls bool
unix bool
pinned bool
empty bool
prefix [][]byte
invalid [][]byte
}{
{name: "caching initial", plugin: "caching_sha2_password"},
{name: "caching fast", plugin: "caching_sha2_password", prefix: [][]byte{{3}}},
{name: "caching full TLS", plugin: "caching_sha2_password", tls: true, prefix: [][]byte{{4}}},
{name: "caching full unix", plugin: "caching_sha2_password", unix: true, prefix: [][]byte{{4}}},
{name: "caching full pinned", plugin: "caching_sha2_password", pinned: true, prefix: [][]byte{{4}}},
{name: "caching key consumed", plugin: "caching_sha2_password", prefix: [][]byte{{4}, testPubKey}},
{name: "caching awaiting key", plugin: "caching_sha2_password", prefix: [][]byte{{4}}, invalid: [][]byte{nil, {3}, {4}}},
{name: "sha256 pinned", plugin: "sha256_password", pinned: true},
{name: "sha256 TLS", plugin: "sha256_password", tls: true},
{name: "sha256 empty password", plugin: "sha256_password", empty: true},
{name: "sha256 key consumed", plugin: "sha256_password", prefix: [][]byte{testPubKey}},
} {
t.Run(tc.name, func(t *testing.T) {
invalid := tc.invalid
if invalid == nil {
invalid = [][]byte{testPubKey, nil}
if len(tc.prefix) > 0 {
invalid = append(invalid, []byte{3}, []byte{4})
}
}
for i, packet := range invalid {
t.Run(fmt.Sprint(i), func(t *testing.T) {
_, mc := newRWMockConn(2)
if !tc.empty {
mc.cfg.Passwd = "secret"
}
if tc.pinned {
mc.cfg.pubKey = testPubKeyRSA
}
if tc.unix {
mc.cfg.Net = "unix"
}
seed := []byte("0123456789abcdefghij")
auth := newAuthContext(mc.cfg, mc.cfg.Passwd, tc.tls)
plugin, _, err := mc.initAuth(t.Context(), tc.plugin, seed, auth)
if err != nil {
t.Fatal(err)
}
for _, step := range tc.prefix {
if _, err := plugin.ContinuationAuth(t.Context(), step, seed, auth); err != nil {
t.Fatalf("valid prefix: %v", err)
}
}
response, err := plugin.ContinuationAuth(t.Context(), packet, seed, auth)
if !errors.Is(err, ErrMalformPkt) || response != nil {
t.Fatalf("ContinuationAuth = (%x, %v), want (nil, ErrMalformPkt)", response, err)
}
})
}
})
}
}
func TestAuthSwitchRejectsShortChallenge(t *testing.T) {
for _, tc := range []struct {
plugin string
length int
}{
{"mysql_native_password", 0},
{"mysql_native_password", 1},
{"mysql_native_password", 19},
{"mysql_old_password", 0},
{"mysql_old_password", 1},
{"mysql_old_password", 7},
} {
t.Run(fmt.Sprintf("%s/%d", tc.plugin, tc.length), func(t *testing.T) {
conn, mc := newRWMockConn(2)
mc.cfg.Passwd = "secret"
mc.cfg.AllowOldPasswords = true
payload := append([]byte{iEOF}, tc.plugin...)
payload = append(payload, 0)
payload = append(payload, bytes.Repeat([]byte{'x'}, tc.length)...)
conn.data = makePacket(2, append(payload, 0))
conn.maxReads = 1
err := mc.handleAuthResult(t.Context(), []byte("0123456789abcdefghij"), &nativePasswordPlugin{}, newAuthContext(mc.cfg, mc.cfg.Passwd, false))
if !errors.Is(err, ErrMalformPkt) || conn.writes != 0 {
t.Fatalf("handleAuthResult = %v, writes = %d; want ErrMalformPkt, 0", err, conn.writes)
}
})
}
}