aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGert-Jan Timmer <gjr.timmer@gmail.com>2018-06-05 13:45:32 +0200
committerGert-Jan Timmer <gjr.timmer@gmail.com>2018-06-05 13:45:32 +0200
commit7337e65c27313aec52f96e6da520acd2fe48c00f (patch)
tree61339d8f38ea7e11cc2614d37d12bc403729c820
parentADD: sqlite_auth to goconvey test suite (diff)
downloadgolite-7337e65c27313aec52f96e6da520acd2fe48c00f.tar.gz
golite-7337e65c27313aec52f96e6da520acd2fe48c00f.tar.xz
ADD: User Authentication Password Encoders
Allow user to choose how to encode passwords with connection string overrides of embedded `sqlite_crypt` function.
-rw-r--r--sqlite3.go58
-rw-r--r--sqlite3_func_crypt.go120
-rw-r--r--sqlite3_opt_userauth_test.go244
3 files changed, 422 insertions, 0 deletions
diff --git a/sqlite3.go b/sqlite3.go
index e6f8c55..979aedc 100644
--- a/sqlite3.go
+++ b/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
diff --git a/sqlite3_func_crypt.go b/sqlite3_func_crypt.go
new file mode 100644
index 0000000..3774a97
--- /dev/null
+++ b/sqlite3_func_crypt.go
@@ -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
diff --git a/sqlite3_opt_userauth_test.go b/sqlite3_opt_userauth_test.go
index a71716c..cd05b05 100644
--- a/sqlite3_opt_userauth_test.go
+++ b/sqlite3_opt_userauth_test.go
@@ -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()
+ })
+}