ADD: User Authentication Password Encoders
Allow user to choose how to encode passwords with connection string overrides of embedded `sqlite_crypt` function.
This commit is contained in:
parent
9b30110b83
commit
7337e65c27
58
sqlite3.go
58
sqlite3.go
|
@ -894,6 +894,8 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
|||
authCreate := false
|
||||
authUser := ""
|
||||
authPass := ""
|
||||
authCrypt := ""
|
||||
authSalt := ""
|
||||
mutex := C.int(C.SQLITE_OPEN_FULLMUTEX)
|
||||
txlock := "BEGIN"
|
||||
|
||||
|
@ -929,6 +931,12 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
|||
if val := params.Get("_auth_pass"); val != "" {
|
||||
authPass = val
|
||||
}
|
||||
if val := params.Get("_auth_crypt"); val != "" {
|
||||
authCrypt = val
|
||||
}
|
||||
if val := params.Get("_auth_salt"); val != "" {
|
||||
authSalt = val
|
||||
}
|
||||
|
||||
// _loc
|
||||
if val := params.Get("_loc"); val != "" {
|
||||
|
@ -1287,6 +1295,56 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
|||
// Create connection to SQLite
|
||||
conn := &SQLiteConn{db: db, loc: loc, txlock: txlock}
|
||||
|
||||
// Password Cipher has to be registerd before authentication
|
||||
if len(authCrypt) > 0 {
|
||||
switch strings.ToUpper(authCrypt) {
|
||||
case "SHA1":
|
||||
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA1, true); err != nil {
|
||||
return nil, fmt.Errorf("CryptEncoderSHA1: %s", err)
|
||||
}
|
||||
case "SSHA1":
|
||||
if len(authSalt) == 0 {
|
||||
return nil, fmt.Errorf("_auth_crypt=ssha1, requires _auth_salt")
|
||||
}
|
||||
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA1(authSalt), true); err != nil {
|
||||
return nil, fmt.Errorf("CryptEncoderSSHA1: %s", err)
|
||||
}
|
||||
case "SHA256":
|
||||
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA256, true); err != nil {
|
||||
return nil, fmt.Errorf("CryptEncoderSHA256: %s", err)
|
||||
}
|
||||
case "SSHA256":
|
||||
if len(authSalt) == 0 {
|
||||
return nil, fmt.Errorf("_auth_crypt=ssha256, requires _auth_salt")
|
||||
}
|
||||
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA256(authSalt), true); err != nil {
|
||||
return nil, fmt.Errorf("CryptEncoderSSHA256: %s", err)
|
||||
}
|
||||
case "SHA384":
|
||||
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA384, true); err != nil {
|
||||
return nil, fmt.Errorf("CryptEncoderSHA384: %s", err)
|
||||
}
|
||||
case "SSHA384":
|
||||
if len(authSalt) == 0 {
|
||||
return nil, fmt.Errorf("_auth_crypt=ssha384, requires _auth_salt")
|
||||
}
|
||||
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA384(authSalt), true); err != nil {
|
||||
return nil, fmt.Errorf("CryptEncoderSSHA384: %s", err)
|
||||
}
|
||||
case "SHA512":
|
||||
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA512, true); err != nil {
|
||||
return nil, fmt.Errorf("CryptEncoderSHA512: %s", err)
|
||||
}
|
||||
case "SSHA512":
|
||||
if len(authSalt) == 0 {
|
||||
return nil, fmt.Errorf("_auth_crypt=ssha512, requires _auth_salt")
|
||||
}
|
||||
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA512(authSalt), true); err != nil {
|
||||
return nil, fmt.Errorf("CryptEncoderSSHA512: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Preform Authentication
|
||||
if err := conn.Authenticate(authUser, authPass); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
)
|
||||
|
||||
// This file provides several different implementations for the
|
||||
// default embedded sqlite_crypt function.
|
||||
// This function is uses a ceasar-cypher by default
|
||||
// and is used within the UserAuthentication module to encode
|
||||
// the password.
|
||||
//
|
||||
// The provided functions can be used as an overload to the sqlite_crypt
|
||||
// function through the use of the RegisterFunc on the connection.
|
||||
//
|
||||
// Because the functions can serv a purpose to an end-user
|
||||
// without using the UserAuthentication module
|
||||
// the functions are default compiled in.
|
||||
//
|
||||
// From SQLITE3 - user-auth.txt
|
||||
// The sqlite_user.pw field is encoded by a built-in SQL function
|
||||
// "sqlite_crypt(X,Y)". The two arguments are both BLOBs. The first argument
|
||||
// is the plaintext password supplied to the sqlite3_user_authenticate()
|
||||
// interface. The second argument is the sqlite_user.pw value and is supplied
|
||||
// so that the function can extract the "salt" used by the password encoder.
|
||||
// The result of sqlite_crypt(X,Y) is another blob which is the value that
|
||||
// ends up being stored in sqlite_user.pw. To verify credentials X supplied
|
||||
// by the sqlite3_user_authenticate() routine, SQLite runs:
|
||||
//
|
||||
// sqlite_user.pw == sqlite_crypt(X, sqlite_user.pw)
|
||||
//
|
||||
// To compute an appropriate sqlite_user.pw value from a new or modified
|
||||
// password X, sqlite_crypt(X,NULL) is run. A new random salt is selected
|
||||
// when the second argument is NULL.
|
||||
//
|
||||
// The built-in version of of sqlite_crypt() uses a simple Ceasar-cypher
|
||||
// which prevents passwords from being revealed by searching the raw database
|
||||
// for ASCII text, but is otherwise trivally broken. For better password
|
||||
// security, the database should be encrypted using the SQLite Encryption
|
||||
// Extension or similar technology. Or, the application can use the
|
||||
// sqlite3_create_function() interface to provide an alternative
|
||||
// implementation of sqlite_crypt() that computes a stronger password hash,
|
||||
// perhaps using a cryptographic hash function like SHA1.
|
||||
|
||||
// CryptEncoderSHA1 encodes a password with SHA1
|
||||
func CryptEncoderSHA1(pass []byte, hash interface{}) []byte {
|
||||
h := sha1.Sum(pass)
|
||||
return h[:]
|
||||
}
|
||||
|
||||
// CryptEncoderSSHA1 encodes a password with SHA1 with the
|
||||
// configured salt.
|
||||
func CryptEncoderSSHA1(salt string) func(pass []byte, hash interface{}) []byte {
|
||||
return func(pass []byte, hash interface{}) []byte {
|
||||
s := []byte(salt)
|
||||
p := append(pass, s...)
|
||||
h := sha1.Sum(p)
|
||||
return h[:]
|
||||
}
|
||||
}
|
||||
|
||||
// CryptEncoderSHA256 encodes a password with SHA256
|
||||
func CryptEncoderSHA256(pass []byte, hash interface{}) []byte {
|
||||
h := sha256.Sum256(pass)
|
||||
return h[:]
|
||||
}
|
||||
|
||||
// CryptEncoderSSHA256 encodes a password with SHA256
|
||||
// with the configured salt
|
||||
func CryptEncoderSSHA256(salt string) func(pass []byte, hash interface{}) []byte {
|
||||
return func(pass []byte, hash interface{}) []byte {
|
||||
s := []byte(salt)
|
||||
p := append(pass, s...)
|
||||
h := sha256.Sum256(p)
|
||||
return h[:]
|
||||
}
|
||||
}
|
||||
|
||||
// CryptEncoderSHA384 encodes a password with SHA256
|
||||
func CryptEncoderSHA384(pass []byte, hash interface{}) []byte {
|
||||
h := sha512.Sum384(pass)
|
||||
return h[:]
|
||||
}
|
||||
|
||||
// CryptEncoderSSHA384 encodes a password with SHA256
|
||||
// with the configured salt
|
||||
func CryptEncoderSSHA384(salt string) func(pass []byte, hash interface{}) []byte {
|
||||
return func(pass []byte, hash interface{}) []byte {
|
||||
s := []byte(salt)
|
||||
p := append(pass, s...)
|
||||
h := sha512.Sum384(p)
|
||||
return h[:]
|
||||
}
|
||||
}
|
||||
|
||||
// CryptEncoderSHA512 encodes a password with SHA256
|
||||
func CryptEncoderSHA512(pass []byte, hash interface{}) []byte {
|
||||
h := sha512.Sum512(pass)
|
||||
return h[:]
|
||||
}
|
||||
|
||||
// CryptEncoderSSHA512 encodes a password with SHA256
|
||||
// with the configured salt
|
||||
func CryptEncoderSSHA512(salt string) func(pass []byte, hash interface{}) []byte {
|
||||
return func(pass []byte, hash interface{}) []byte {
|
||||
s := []byte(salt)
|
||||
p := append(pass, s...)
|
||||
h := sha512.Sum512(p)
|
||||
return h[:]
|
||||
}
|
||||
}
|
||||
|
||||
// EOF
|
|
@ -1108,3 +1108,247 @@ func TestUserAuthenticationDeleteUser(t *testing.T) {
|
|||
So(err, ShouldEqual, ErrAdminRequired)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserAuthenticationEncoder(t *testing.T) {
|
||||
connectWithCrypt := func(t *testing.T, f string, username, password string, crypt string, salt string) (file string, db *sql.DB, c *SQLiteConn, err error) {
|
||||
conn = nil // Clear connection
|
||||
file = f // Copy provided file (f) => file
|
||||
if file == "" {
|
||||
// Create dummy file
|
||||
file = TempFilename(t)
|
||||
}
|
||||
|
||||
db, err = sql.Open("sqlite3_with_conn", "file:"+file+fmt.Sprintf("?_auth&_auth_user=%s&_auth_pass=%s&_auth_crypt=%s&_auth_salt=%s", username, password, crypt, salt))
|
||||
if err != nil {
|
||||
defer os.Remove(file)
|
||||
return file, nil, nil, err
|
||||
}
|
||||
|
||||
// Dummy query to force connection and database creation
|
||||
// Will return ErrUnauthorized (SQLITE_AUTH) if user authentication fails
|
||||
if _, err = db.Exec("SELECT 1;"); err != nil {
|
||||
defer os.Remove(file)
|
||||
defer db.Close()
|
||||
return file, nil, nil, err
|
||||
}
|
||||
c = conn
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
Convey("SHA1 Encoder", t, func() {
|
||||
f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "sha1", "")
|
||||
So(f1, ShouldNotBeBlank)
|
||||
So(db1, ShouldNotBeNil)
|
||||
So(c1, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer os.Remove(f1)
|
||||
|
||||
e, err := userExists(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(e, ShouldEqual, 1)
|
||||
|
||||
a, err := isAdmin(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(a, ShouldEqual, true)
|
||||
db1.Close()
|
||||
|
||||
// Preform authentication
|
||||
f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "sha1", "")
|
||||
So(f2, ShouldNotBeBlank)
|
||||
So(f1, ShouldEqual, f2)
|
||||
So(db2, ShouldNotBeNil)
|
||||
So(c2, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer db2.Close()
|
||||
})
|
||||
|
||||
Convey("SSHA1 Encoder", t, func() {
|
||||
f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "ssha1", "salted")
|
||||
So(f1, ShouldNotBeBlank)
|
||||
So(db1, ShouldNotBeNil)
|
||||
So(c1, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer os.Remove(f1)
|
||||
|
||||
e, err := userExists(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(e, ShouldEqual, 1)
|
||||
|
||||
a, err := isAdmin(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(a, ShouldEqual, true)
|
||||
db1.Close()
|
||||
|
||||
// Preform authentication
|
||||
f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "ssha1", "salted")
|
||||
So(f2, ShouldNotBeBlank)
|
||||
So(f1, ShouldEqual, f2)
|
||||
So(db2, ShouldNotBeNil)
|
||||
So(c2, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer db2.Close()
|
||||
})
|
||||
|
||||
Convey("SHA256 Encoder", t, func() {
|
||||
f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "sha256", "")
|
||||
So(f1, ShouldNotBeBlank)
|
||||
So(db1, ShouldNotBeNil)
|
||||
So(c1, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer os.Remove(f1)
|
||||
|
||||
e, err := userExists(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(e, ShouldEqual, 1)
|
||||
|
||||
a, err := isAdmin(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(a, ShouldEqual, true)
|
||||
db1.Close()
|
||||
|
||||
// Preform authentication
|
||||
f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "sha256", "")
|
||||
So(f2, ShouldNotBeBlank)
|
||||
So(f1, ShouldEqual, f2)
|
||||
So(db2, ShouldNotBeNil)
|
||||
So(c2, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer db2.Close()
|
||||
})
|
||||
|
||||
Convey("SSHA256 Encoder", t, func() {
|
||||
f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "ssha256", "salted")
|
||||
So(f1, ShouldNotBeBlank)
|
||||
So(db1, ShouldNotBeNil)
|
||||
So(c1, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer os.Remove(f1)
|
||||
|
||||
e, err := userExists(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(e, ShouldEqual, 1)
|
||||
|
||||
a, err := isAdmin(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(a, ShouldEqual, true)
|
||||
db1.Close()
|
||||
|
||||
// Preform authentication
|
||||
f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "ssha256", "salted")
|
||||
So(f2, ShouldNotBeBlank)
|
||||
So(f1, ShouldEqual, f2)
|
||||
So(db2, ShouldNotBeNil)
|
||||
So(c2, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer db2.Close()
|
||||
})
|
||||
|
||||
Convey("SHA384 Encoder", t, func() {
|
||||
f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "sha384", "")
|
||||
So(f1, ShouldNotBeBlank)
|
||||
So(db1, ShouldNotBeNil)
|
||||
So(c1, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer os.Remove(f1)
|
||||
|
||||
e, err := userExists(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(e, ShouldEqual, 1)
|
||||
|
||||
a, err := isAdmin(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(a, ShouldEqual, true)
|
||||
db1.Close()
|
||||
|
||||
// Preform authentication
|
||||
f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "sha384", "")
|
||||
So(f2, ShouldNotBeBlank)
|
||||
So(f1, ShouldEqual, f2)
|
||||
So(db2, ShouldNotBeNil)
|
||||
So(c2, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer db2.Close()
|
||||
})
|
||||
|
||||
Convey("SSHA384 Encoder", t, func() {
|
||||
f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "ssha384", "salted")
|
||||
So(f1, ShouldNotBeBlank)
|
||||
So(db1, ShouldNotBeNil)
|
||||
So(c1, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer os.Remove(f1)
|
||||
|
||||
e, err := userExists(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(e, ShouldEqual, 1)
|
||||
|
||||
a, err := isAdmin(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(a, ShouldEqual, true)
|
||||
db1.Close()
|
||||
|
||||
// Preform authentication
|
||||
f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "ssha384", "salted")
|
||||
So(f2, ShouldNotBeBlank)
|
||||
So(f1, ShouldEqual, f2)
|
||||
So(db2, ShouldNotBeNil)
|
||||
So(c2, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer db2.Close()
|
||||
})
|
||||
|
||||
Convey("SHA512 Encoder", t, func() {
|
||||
f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "sha512", "")
|
||||
So(f1, ShouldNotBeBlank)
|
||||
So(db1, ShouldNotBeNil)
|
||||
So(c1, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer os.Remove(f1)
|
||||
|
||||
e, err := userExists(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(e, ShouldEqual, 1)
|
||||
|
||||
a, err := isAdmin(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(a, ShouldEqual, true)
|
||||
db1.Close()
|
||||
|
||||
// Preform authentication
|
||||
f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "sha512", "")
|
||||
So(f2, ShouldNotBeBlank)
|
||||
So(f1, ShouldEqual, f2)
|
||||
So(db2, ShouldNotBeNil)
|
||||
So(c2, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer db2.Close()
|
||||
})
|
||||
|
||||
Convey("SSHA512 Encoder", t, func() {
|
||||
f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "ssha512", "salted")
|
||||
So(f1, ShouldNotBeBlank)
|
||||
So(db1, ShouldNotBeNil)
|
||||
So(c1, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer os.Remove(f1)
|
||||
|
||||
e, err := userExists(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(e, ShouldEqual, 1)
|
||||
|
||||
a, err := isAdmin(db1, "admin")
|
||||
So(err, ShouldBeNil)
|
||||
So(a, ShouldEqual, true)
|
||||
db1.Close()
|
||||
|
||||
// Preform authentication
|
||||
f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "ssha512", "salted")
|
||||
So(f2, ShouldNotBeBlank)
|
||||
So(f1, ShouldEqual, f2)
|
||||
So(db2, ShouldNotBeNil)
|
||||
So(c2, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
defer db2.Close()
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue