Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for SQLeet full database encryption #93

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

alinz
Copy link

@alinz alinz commented Jun 14, 2020

This is a PR related to issue #92.

@AdamSLevy AdamSLevy changed the title Port Sqleet back to v3.31.1 Add support for SQLeet full database encryption Jun 14, 2020
Copy link
Collaborator

@AdamSLevy AdamSLevy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for getting the ball rolling! Please let me know if you have any questions about my comments or need more direction or input.

c/sqlite3.c Outdated
@@ -1,3 +1,47 @@
#if (defined(__linux__) || defined(__unix__)) && !defined(_GNU_SOURCE)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to support both the latest release of the official SQLite and the latest release of SQLeet, selected using built tags. This will probably require two of these c "dummy" packages: one for the sqleet C source and one for the official SQLite source. Then two separate files in the main sqlite package will include each of the "dummy" packages. Build tags will then target one of these files.

I can push a commit that implements this with the right build tags if you want my help with this aspect. Let me know if you have any questions.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would need your help on this one as it requires touching c and go. Sqleet is on 3.31.1 version. I was tried to build with new sqlite but got into some rough compile issue in c. I'm using mac and some of the sed command is not working properly and what I ended up was running the code inside a docker.

@@ -0,0 +1,64 @@
package main
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really appreciate that you included an example. However are you familiar with Golang test examples? I'd like to see this example changed into a golang test example. https://blog.golang.org/examples

Comment on lines 9 to 26
dbString := "file:./database.db?key=swordfish&foreign_keys=on"

poolSize := 10

dbpool, err := sqlitex.Open(dbString, 0, poolSize)
if err != nil {
panic(err)
}

// enable all connection to have Foreign key feature
for i := 0; i < poolSize; i++ {
conn := dbpool.Get(nil)
err := sqlitex.Exec(conn, `PRAGMA foreign_keys = ON;`, nil)
if err != nil {
panic(err)
}
dbpool.Put(conn)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep examples as minimal as possible to demonstrate a feature. Let's not use a pool here, but just a single connection created with sqlite.OpenConn. Foreign key support has nothing to do with encryption so it should not be used or mentioned in this example either.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another note for your own information regarding enabling foreign keys or other PRAGMA features. Since you already specified it in the URI, this loop to call the PRAGMA is not needed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try using foreign_keys connection string and it did not work. then I tried using PRAGMA and it didn't work until I filed an issue #91 and you answered it.

Comment on lines 28 to 36
// // enable all connection to have Foreign key feature
// for i := 0; i < poolSize; i++ {
// conn := dbpool.Get(nil)
// err := sqlitex.Exec(conn, `PRAGMA key='swordfish';`, nil)
// if err != nil {
// panic(err)
// }
// dbpool.Put(conn)
// }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is a work in progress but let's avoid keeping commented out code around.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleaned up the example

Comment on lines 39 to 47
err = sqlitex.Exec(conn, `PRAGMA key=swordfish;`, nil)
if err != nil {
panic(err)
}

err = sqlite.Crypto(conn, "swordfish2")
if err != nil {
panic(err)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These steps (PRAGMA key and sqlite.Crypto()) are redundant and use different keys. Also note that PRAGMAs never return errors, which is why it is generally better to use a programmatic API where possible. We should not use the PRAGMA, and use the programmatic API in examples to guide users to best practices.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the example. couple of observation,

  • if I use connection string key, it does encrypt and decrypt the database properly
  • if I use sqlite.ApplyKey it does not encrypt database and it errors out: sqlite.Conn.Unlock: SQLITE_AUTH
  • if I use sqlite.ApplyRekey it does encrypt an unencrypted database or encrypt with the new key. However I have to use connection string to connect back to database.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are missing some nuances to how the encryption API works. I recommend reviewing the documentation for SQLeet, and use examples that exclusively use the API. Don't confuse the issue by using settings in the connection string.

The current flaw with your example, which still persists, is that it does not distinguish between an already encrypted database, and a new, or not yet encrypted, database.

You cannot encrypt an unencrypted or new database as encrypted by using a setting in the connection string. Thus if you open a new database with the URI file:./database.db?key=swordfish, the key param will have no effect and you will still be working with an unencrypted database. Only if the database was already encrypted, and this is the correct key, will it be opened already unlocked.

Anytime you wish to encrypt an un-encrypted or new database file, you MUST use PRAGMA rekey or preferably the API equivalent. This initially will set up the encryption on the file. Thereafter you must use PRAGMA key to unlock the file before reading or writing from it on each connection. This appears to support using the param in the connection string. Only after this occurs, can PRAGMA rekey be successfully called to change the key on an already encrypted database.

I hope this clarifies the correct use of the APIs. Really the example needs a flag that controls whether we are unlocking an encrypted database or creating a new encrypted database. Ultimately though, I want these in golang test examples so they should up in the documentation. There should be two examples for each case I just mentioned.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alinz I experimented a bit more and I discovered that I was somewhat incorrect about the use of the key param in the connection string. I was not specifying the connection string properly in my own experimentation, which threw me off. You can in fact initialize a new database to be encrypted with a given key using key= in the connection string. The rest of what I said about the use of the API is correct but the SQLeet README describes many advanced settings that can only be set in the URI string. https://github.com/resilar/sqleet/tree/v0.31.1#uri-configuration-interface This is easy to get wrong so in the future I plan to introduce some helper types/functions for building a URI with the correct settings.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update.

sqlite.go Outdated
ckey := C.CString(key)
defer C.free(unsafe.Pointer(ckey))

// convert CString to void*: https://jamesadam.me/2016/03/26/c-and-go-dealing-with-void-parameters-in-cgo/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate you documenting the technique, but CGo basics are in the space of assumed knowledge for this package and don't need to be documented.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, removed the comment

sqlite.go Outdated
defer C.free(unsafe.Pointer(ckey))

// convert CString to void*: https://jamesadam.me/2016/03/26/c-and-go-dealing-with-void-parameters-in-cgo/
res := C.sqlite3_key(conn.conn, unsafe.Pointer(&ckey), 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two things wrong with this call that were likely causing you some issues. First, the function signature in C is this

int sqlite3_key(
   sqlite3 *db,        /* The connection from sqlite3_open() */
   const void *pKey,   /* The key */
   int nKey            /* Number of bytes in the key */
);

First note that by passing ckey is already a pointer. So by passing &ckey you are passing a pointer to a pointer. This is not something C can prevent you from. It has no idea you passed it the wrong thing, but this will produce unexpected behavior.

The next parameter needs to be the length of the string or byte slice. By passing zero you are using an empty key.

Copy link
Author

@alinz alinz Jun 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out, I don't know what I was thinking. I have cleaned that up and also implemented Rekey. I came up with ApplyKey and ApplyRekey. I would gladly change it if you have a better names.

sqlite.go Outdated
// convert CString to void*: https://jamesadam.me/2016/03/26/c-and-go-dealing-with-void-parameters-in-cgo/
res := C.sqlite3_key(conn.conn, unsafe.Pointer(&ckey), 0)
if res != C.SQLITE_OK {
return fmt.Errorf("something went wrong")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At minimum errors need to indicate what went wrong or where. In this package we have a function reserr to help format errors. This should be return reserr("Conn.Unlock", "", "", res).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the error issue

sqlite3.h Outdated
@@ -1,3 +1,40 @@
/*
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the C files we'll have to somehow use build tags to control which header file to use.

Copy link
Author

@alinz alinz Jun 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need your help on this

sqlite_test.go Outdated
Comment on lines 507 to 511
// {
// "",
// "delete",
// 0,
// },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you remove this test? This should be restored.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test was failing and it was kind of confusing:

--- FAIL: TestJournalMode (0.00s)
    sqlite_test.go:534: journal_mode not set properly, got: memory, want: delete

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured out the root cause here. Basically SQLeet uses a different default for temp_store than SQLite3 uses. This caused this to be a different journal_mode than expected for temporary database files. I have a commit coming that addresses this for this test.

In the future if a test is failing please just let the failure be known so it can be addressed.

@alinz alinz requested a review from AdamSLevy June 15, 2020 16:37
Copy link
Collaborator

@AdamSLevy AdamSLevy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good improvements. However it's not quite ready so I'm going to push a commit that brings it in line with what I want.

@AdamSLevy
Copy link
Collaborator

AdamSLevy commented Jun 21, 2020

@alinz I pushed a rather large commit that gets everything working as I'd like using build tags. The last things to add are some more package level and README docs about this feature. I'm probably going to add those things next weekend and likely merge it if I don't hear any objections from @crawshaw before then. I'll also deal with the conflicts with master next weekend as well.

Thanks for getting the ball rolling on this. This also lays the groundwork for adding support for SQLCipher as well if someone wants to go that route.

@AdamSLevy
Copy link
Collaborator

@alinz you don't need to do anything. I already merged master into this branch. It's good to go. Let me know if it doesn't work for you after you pull.

@AdamSLevy
Copy link
Collaborator

I see you fixed the import. Thanks.

@alinz
Copy link
Author

alinz commented Jun 22, 2020

@AdamSLevy I will revert my change. This is funny, both of us were trying to fix the same issue 🤣

This reverts commit 6ee59f0.
@alinz
Copy link
Author

alinz commented Jun 22, 2020

@AdamSLevy There is only one small issue, The version of sqlite3 in sqleet is 3.31.1 but the actual sqlite3 is 3.32.2. I did try to compile sqleet to the latest sqlite3, but wasn't be able to compile and rebuild rekeyvacuum.c. If version difference is not that important, I can defer it to later.

Amazing job, thanks for pushing this forward.

@AdamSLevy
Copy link
Collaborator

@alinz why did you revert that change? It was needed and you pushed it first. It's all good when that happens. My commit would have looked identical to yours, and if there had been issues they can always be fixed. Anyway I've merge master to this branch again, which contains that fix. So you don't need to do anything. But in the future please don't un-fix things you just fixed :)

The version of sqlite3 in sqleet is 3.31.1 but the actual sqlite3 is 3.32.2.

This is OK. If you run go build it will use the latest release of SQLite3: 3.32.2. If you run go build -tags=sqleet it will build with the latest version of SQLeet: 0.31.1. When SQLeet releases version 0.32.2 then I'll update the SQLeet C code. If you want to build the SQLeet amalgamation C file against the latest SQLite release, you are welcome to maintain that in your own fork, but I'm not going to support that for the main branch.

I did try to compile sqleet to the latest sqlite3, but wasn't be able to compile and rebuild rekeyvacuum.c

I don't understand what you're talking about here. There is no such file on this branch or in this repo.

@alinz
Copy link
Author

alinz commented Jun 22, 2020

The reason I reverted it was because my change caused an unnecessary merge conflicts. I was using multiple imports, you were using single import.

I had to be more descriptive about the last problem, I was talking about sqleet's main repo. I was trying to build it from source so I could put the amalgamation C file into this project. I guess I will wait until they support it.

@AdamSLevy
Copy link
Collaborator

So I am afraid after doing more research on updating sqleet to the latest sqlite version I am going to hold off on merging this PR. I'm going to leave it open though as it works for the latest version of sqleet.
resilar/sqleet#38

The reason is that there is currently no way for sqleet to support versions of sqlite3 past 3.32.0 because of some internal changes to sqlite. I don't want to steer users of this package towards adopting an encryption library that is not currently supported.

I am interested in finding the right encryption extension. It's likely that SQLcipher will eventually get a work around to this, but that will take some time. It's possible sqleet will adapt at some point in the future. but I don't want to merge this quite yet. Another promising project is here: https://utelle.github.io/SQLite3MultipleCiphers/

I haven't totally ruled out merging this PR yet, I'm just not ready to pull the trigger. I want to give these other projects some time to adapt to SQLite's latest changes.

I do plan to port some of the work I did in this PR to make it easier to control how SQLite is compiled. It is a goal to allow users of this package to link against a shared system library for SQLite instead of compiling it in. The work in the branch lays the groundwork for some of that.

@alinz
Copy link
Author

alinz commented Aug 27, 2020

@AdamSLevy I believe SQLite3MultipleCiphers has released first major version which support new version of sqlite interface.

The new release can be found here:
https://github.com/utelle/SQLite3MultipleCiphers/releases/tag/v1.0.0

Let me know if you need any help on my end.

@AdamSLevy
Copy link
Collaborator

That's awesome. I'm probably not going to have time to work on this few a couple weeks at least. But I will eventually take a look.

@alinz
Copy link
Author

alinz commented Oct 5, 2020

@AdamSLevy I'd like to help to push this forward. I just need some guidance on your plan.

@alinz
Copy link
Author

alinz commented Dec 31, 2020

@AdamSLevy happy new year, just wanted to see if you plan to work on this, or anything I can do to help you on this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants