Repository navigation
Expand file tree
/
Copy pathauth_sha256.go
More file actions
143 lines (124 loc) · 4.41 KB
/
Copy pathauth_sha256.go
File metadata and controls
143 lines (124 loc) · 4.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2023 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/pem"
"fmt"
)
// sha256PasswordPlugin implements the sha256_password authentication
// This plugin provides secure password-based authentication using SHA256 and RSA encryption.
type sha256PasswordPlugin struct {
awaitingPublicKey bool
}
// Compile-time assertion that sha256PasswordPlugin implements AuthPlugin.
var _ AuthPlugin = (*sha256PasswordPlugin)(nil)
func init() {
RegisterAuthPlugin("sha256_password", func() AuthPlugin { return &sha256PasswordPlugin{} })
}
// InitAuth initializes the authentication process.
//
// The function follows these rules:
// 1. If no password is configured, sends a single byte indicating empty password
// 2. If TLS is enabled, sends the password in cleartext
// 3. If a public key is available, encrypts the password and sends it
// 4. Otherwise, requests the server's public key
func (p *sha256PasswordPlugin) InitAuth(ctx context.Context, authData []byte, auth *AuthContext) ([]byte, error) {
p.awaitingPublicKey = false
if len(auth.Password()) == 0 {
return []byte{0}, nil
}
// Unlike caching_sha2_password, sha256_password does not accept
// cleartext password on unix transport.
if auth.TLS() {
// Write cleartext auth packet
return append([]byte(auth.Password()), 0), nil
}
pubKey := auth.ServerPublicKey()
if pubKey == nil {
// Request public key from server
p.awaitingPublicKey = true
return []byte{1}, nil
}
// Encrypt password using the public key
enc, err := encryptPassword(auth.Password(), authData, pubKey)
if err != nil {
return nil, fmt.Errorf("failed to encrypt password: %w", err)
}
return enc, nil
}
// ContinuationAuth processes the server's response to our authentication attempt.
//
// The server can respond in three ways:
// 1. OK packet - Authentication successful
// 2. Error packet - Authentication failed
// 3. More data packet - Contains the server's public key for password encryption
func (p *sha256PasswordPlugin) ContinuationAuth(ctx context.Context, packet, authData []byte, auth *AuthContext) ([]byte, error) {
if !p.awaitingPublicKey {
return nil, ErrMalformPkt
}
p.awaitingPublicKey = false
if len(packet) == 0 {
return nil, fmt.Errorf("%w: empty auth response packet", ErrMalformPkt)
}
// Driver already checked for OK/ERR/EOF and stripped 0x01 continuation byte
// So we receive the PEM-encoded public key directly
// Parse public key from PEM format
block, _ := pem.Decode(packet)
if block == nil {
return nil, fmt.Errorf("%w: invalid PEM data in auth response", ErrMalformPkt)
}
if block.Type != "PUBLIC KEY" {
return nil, fmt.Errorf("%w: unexpected PEM block type %q in auth response", ErrMalformPkt, block.Type)
}
// Parse the public key
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse public key: %w", err)
}
pubKey, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("server sent an invalid public key type: %T", pub)
}
// Encrypt and send password
enc, err := encryptPassword(auth.Password(), authData, pubKey)
if err != nil {
return nil, fmt.Errorf("failed to encrypt password with server key: %w", err)
}
// Return encrypted password to be sent
return enc, nil
}
// encryptPassword encrypts the password using RSA-OAEP with SHA1 hash.
//
// The process:
// 1. XORs the password with the auth seed to prevent replay attacks
// 2. Encrypts the XORed password using RSA-OAEP with SHA1
//
// The encryption uses OAEP padding which is more secure than PKCS#1 v1.5 padding.
func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) {
if pub == nil {
return nil, fmt.Errorf("public key is nil")
}
if len(seed) == 0 {
return nil, fmt.Errorf("%w: empty auth seed", ErrMalformPkt)
}
// Create the plaintext by XORing password with seed
plain := make([]byte, len(password)+1)
copy(plain, password)
for i := range plain {
j := i % len(seed)
plain[i] ^= seed[j]
}
// Encrypt using RSA-OAEP with SHA1
sha1Hash := sha1.New()
return rsa.EncryptOAEP(sha1Hash, rand.Reader, pub, plain, nil)
}