mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
* go modified fiddling with vendor got rid of the vendor directory revendored but with the exact same versions of things maybe better added mods for the images revendored using `GOFLAGS` instead of repeating my self vendor everything to the exact same commit hash as before and fixed ugorji Delete Deproxy.toml empty file cleaned up some file cleaned up some cruft get rid of some unused packages and exclude some Microsoft packages added flags to the variables that get pushed into docker in the makefile It works I suppose added noop excluded what we did not want even less hacky reverted to a version that has not been mangled * get rid of my experiment
421 lines
10 KiB
Go
421 lines
10 KiB
Go
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
|
//
|
|
// Copyright 2018 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 (
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"sync"
|
|
)
|
|
|
|
// server pub keys registry
|
|
var (
|
|
serverPubKeyLock sync.RWMutex
|
|
serverPubKeyRegistry map[string]*rsa.PublicKey
|
|
)
|
|
|
|
// RegisterServerPubKey registers a server RSA public key which can be used to
|
|
// send data in a secure manner to the server without receiving the public key
|
|
// in a potentially insecure way from the server first.
|
|
// Registered keys can afterwards be used adding serverPubKey=<name> to the DSN.
|
|
//
|
|
// Note: The provided rsa.PublicKey instance is exclusively owned by the driver
|
|
// after registering it and may not be modified.
|
|
//
|
|
// data, err := ioutil.ReadFile("mykey.pem")
|
|
// if err != nil {
|
|
// log.Fatal(err)
|
|
// }
|
|
//
|
|
// block, _ := pem.Decode(data)
|
|
// if block == nil || block.Type != "PUBLIC KEY" {
|
|
// log.Fatal("failed to decode PEM block containing public key")
|
|
// }
|
|
//
|
|
// pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
|
// if err != nil {
|
|
// log.Fatal(err)
|
|
// }
|
|
//
|
|
// if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
|
|
// mysql.RegisterServerPubKey("mykey", rsaPubKey)
|
|
// } else {
|
|
// log.Fatal("not a RSA public key")
|
|
// }
|
|
//
|
|
func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) {
|
|
serverPubKeyLock.Lock()
|
|
if serverPubKeyRegistry == nil {
|
|
serverPubKeyRegistry = make(map[string]*rsa.PublicKey)
|
|
}
|
|
|
|
serverPubKeyRegistry[name] = pubKey
|
|
serverPubKeyLock.Unlock()
|
|
}
|
|
|
|
// DeregisterServerPubKey removes the public key registered with the given name.
|
|
func DeregisterServerPubKey(name string) {
|
|
serverPubKeyLock.Lock()
|
|
if serverPubKeyRegistry != nil {
|
|
delete(serverPubKeyRegistry, name)
|
|
}
|
|
serverPubKeyLock.Unlock()
|
|
}
|
|
|
|
func getServerPubKey(name string) (pubKey *rsa.PublicKey) {
|
|
serverPubKeyLock.RLock()
|
|
if v, ok := serverPubKeyRegistry[name]; ok {
|
|
pubKey = v
|
|
}
|
|
serverPubKeyLock.RUnlock()
|
|
return
|
|
}
|
|
|
|
// Hash password using pre 4.1 (old password) method
|
|
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
|
|
type myRnd struct {
|
|
seed1, seed2 uint32
|
|
}
|
|
|
|
const myRndMaxVal = 0x3FFFFFFF
|
|
|
|
// Pseudo random number generator
|
|
func newMyRnd(seed1, seed2 uint32) *myRnd {
|
|
return &myRnd{
|
|
seed1: seed1 % myRndMaxVal,
|
|
seed2: seed2 % myRndMaxVal,
|
|
}
|
|
}
|
|
|
|
// Tested to be equivalent to MariaDB's floating point variant
|
|
// http://play.golang.org/p/QHvhd4qved
|
|
// http://play.golang.org/p/RG0q4ElWDx
|
|
func (r *myRnd) NextByte() byte {
|
|
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
|
|
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
|
|
|
|
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
|
|
}
|
|
|
|
// Generate binary hash from byte string using insecure pre 4.1 method
|
|
func pwHash(password []byte) (result [2]uint32) {
|
|
var add uint32 = 7
|
|
var tmp uint32
|
|
|
|
result[0] = 1345345333
|
|
result[1] = 0x12345671
|
|
|
|
for _, c := range password {
|
|
// skip spaces and tabs in password
|
|
if c == ' ' || c == '\t' {
|
|
continue
|
|
}
|
|
|
|
tmp = uint32(c)
|
|
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
|
|
result[1] += (result[1] << 8) ^ result[0]
|
|
add += tmp
|
|
}
|
|
|
|
// Remove sign bit (1<<31)-1)
|
|
result[0] &= 0x7FFFFFFF
|
|
result[1] &= 0x7FFFFFFF
|
|
|
|
return
|
|
}
|
|
|
|
// Hash password using insecure pre 4.1 method
|
|
func scrambleOldPassword(scramble []byte, password string) []byte {
|
|
if len(password) == 0 {
|
|
return nil
|
|
}
|
|
|
|
scramble = scramble[:8]
|
|
|
|
hashPw := pwHash([]byte(password))
|
|
hashSc := pwHash(scramble)
|
|
|
|
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
|
|
|
|
var out [8]byte
|
|
for i := range out {
|
|
out[i] = r.NextByte() + 64
|
|
}
|
|
|
|
mask := r.NextByte()
|
|
for i := range out {
|
|
out[i] ^= mask
|
|
}
|
|
|
|
return out[:]
|
|
}
|
|
|
|
// Hash password using 4.1+ method (SHA1)
|
|
func scramblePassword(scramble []byte, password string) []byte {
|
|
if len(password) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// stage1Hash = SHA1(password)
|
|
crypt := sha1.New()
|
|
crypt.Write([]byte(password))
|
|
stage1 := crypt.Sum(nil)
|
|
|
|
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
|
|
// inner Hash
|
|
crypt.Reset()
|
|
crypt.Write(stage1)
|
|
hash := crypt.Sum(nil)
|
|
|
|
// outer Hash
|
|
crypt.Reset()
|
|
crypt.Write(scramble)
|
|
crypt.Write(hash)
|
|
scramble = crypt.Sum(nil)
|
|
|
|
// token = scrambleHash XOR stage1Hash
|
|
for i := range scramble {
|
|
scramble[i] ^= stage1[i]
|
|
}
|
|
return scramble
|
|
}
|
|
|
|
// Hash password using MySQL 8+ method (SHA256)
|
|
func scrambleSHA256Password(scramble []byte, password string) []byte {
|
|
if len(password) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
|
|
|
|
crypt := sha256.New()
|
|
crypt.Write([]byte(password))
|
|
message1 := crypt.Sum(nil)
|
|
|
|
crypt.Reset()
|
|
crypt.Write(message1)
|
|
message1Hash := crypt.Sum(nil)
|
|
|
|
crypt.Reset()
|
|
crypt.Write(message1Hash)
|
|
crypt.Write(scramble)
|
|
message2 := crypt.Sum(nil)
|
|
|
|
for i := range message1 {
|
|
message1[i] ^= message2[i]
|
|
}
|
|
|
|
return message1
|
|
}
|
|
|
|
func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) {
|
|
plain := make([]byte, len(password)+1)
|
|
copy(plain, password)
|
|
for i := range plain {
|
|
j := i % len(seed)
|
|
plain[i] ^= seed[j]
|
|
}
|
|
sha1 := sha1.New()
|
|
return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil)
|
|
}
|
|
|
|
func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) error {
|
|
enc, err := encryptPassword(mc.cfg.Passwd, seed, pub)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return mc.writeAuthSwitchPacket(enc, false)
|
|
}
|
|
|
|
func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, bool, error) {
|
|
switch plugin {
|
|
case "caching_sha2_password":
|
|
authResp := scrambleSHA256Password(authData, mc.cfg.Passwd)
|
|
return authResp, (authResp == nil), nil
|
|
|
|
case "mysql_old_password":
|
|
if !mc.cfg.AllowOldPasswords {
|
|
return nil, false, ErrOldPassword
|
|
}
|
|
// Note: there are edge cases where this should work but doesn't;
|
|
// this is currently "wontfix":
|
|
// https://github.com/go-sql-driver/mysql/issues/184
|
|
authResp := scrambleOldPassword(authData[:8], mc.cfg.Passwd)
|
|
return authResp, true, nil
|
|
|
|
case "mysql_clear_password":
|
|
if !mc.cfg.AllowCleartextPasswords {
|
|
return nil, false, ErrCleartextPassword
|
|
}
|
|
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
|
|
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
|
|
return []byte(mc.cfg.Passwd), true, nil
|
|
|
|
case "mysql_native_password":
|
|
if !mc.cfg.AllowNativePasswords {
|
|
return nil, false, ErrNativePassword
|
|
}
|
|
// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
|
|
// Native password authentication only need and will need 20-byte challenge.
|
|
authResp := scramblePassword(authData[:20], mc.cfg.Passwd)
|
|
return authResp, false, nil
|
|
|
|
case "sha256_password":
|
|
if len(mc.cfg.Passwd) == 0 {
|
|
return nil, true, nil
|
|
}
|
|
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
|
|
// write cleartext auth packet
|
|
return []byte(mc.cfg.Passwd), true, nil
|
|
}
|
|
|
|
pubKey := mc.cfg.pubKey
|
|
if pubKey == nil {
|
|
// request public key from server
|
|
return []byte{1}, false, nil
|
|
}
|
|
|
|
// encrypted password
|
|
enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
|
|
return enc, false, err
|
|
|
|
default:
|
|
errLog.Print("unknown auth plugin:", plugin)
|
|
return nil, false, ErrUnknownPlugin
|
|
}
|
|
}
|
|
|
|
func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
|
|
// Read Result Packet
|
|
authData, newPlugin, err := mc.readAuthResult()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// handle auth plugin switch, if requested
|
|
if newPlugin != "" {
|
|
// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
|
|
// sent and we have to keep using the cipher sent in the init packet.
|
|
if authData == nil {
|
|
authData = oldAuthData
|
|
} else {
|
|
// copy data from read buffer to owned slice
|
|
copy(oldAuthData, authData)
|
|
}
|
|
|
|
plugin = newPlugin
|
|
|
|
authResp, addNUL, err := mc.auth(authData, plugin)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = mc.writeAuthSwitchPacket(authResp, addNUL); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Read Result Packet
|
|
authData, newPlugin, err = mc.readAuthResult()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Do not allow to change the auth plugin more than once
|
|
if newPlugin != "" {
|
|
return ErrMalformPkt
|
|
}
|
|
}
|
|
|
|
switch plugin {
|
|
|
|
// https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
|
|
case "caching_sha2_password":
|
|
switch len(authData) {
|
|
case 0:
|
|
return nil // auth successful
|
|
case 1:
|
|
switch authData[0] {
|
|
case cachingSha2PasswordFastAuthSuccess:
|
|
if err = mc.readResultOK(); err == nil {
|
|
return nil // auth successful
|
|
}
|
|
|
|
case cachingSha2PasswordPerformFullAuthentication:
|
|
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
|
|
// write cleartext auth packet
|
|
err = mc.writeAuthSwitchPacket([]byte(mc.cfg.Passwd), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
pubKey := mc.cfg.pubKey
|
|
if pubKey == nil {
|
|
// request public key from server
|
|
data := mc.buf.takeSmallBuffer(4 + 1)
|
|
data[4] = cachingSha2PasswordRequestPublicKey
|
|
mc.writePacket(data)
|
|
|
|
// parse public key
|
|
data, err := mc.readPacket()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
block, _ := pem.Decode(data[1:])
|
|
pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pubKey = pkix.(*rsa.PublicKey)
|
|
}
|
|
|
|
// send encrypted password
|
|
err = mc.sendEncryptedPassword(oldAuthData, pubKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return mc.readResultOK()
|
|
|
|
default:
|
|
return ErrMalformPkt
|
|
}
|
|
default:
|
|
return ErrMalformPkt
|
|
}
|
|
|
|
case "sha256_password":
|
|
switch len(authData) {
|
|
case 0:
|
|
return nil // auth successful
|
|
default:
|
|
block, _ := pem.Decode(authData)
|
|
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// send encrypted password
|
|
err = mc.sendEncryptedPassword(oldAuthData, pub.(*rsa.PublicKey))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return mc.readResultOK()
|
|
}
|
|
|
|
default:
|
|
return nil // auth successful
|
|
}
|
|
|
|
return err
|
|
}
|