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:
Gert-Jan Timmer 2018-06-05 13:45:32 +02:00
parent 9b30110b83
commit 7337e65c27
3 changed files with 422 additions and 0 deletions

View File

@ -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

120
sqlite3_func_crypt.go Normal file
View File

@ -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

View File

@ -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()
})
}