-
Notifications
You must be signed in to change notification settings - Fork 67
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
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
examples/encryption/main.go
Outdated
@@ -0,0 +1,64 @@ | |||
package main |
There was a problem hiding this comment.
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
examples/encryption/main.go
Outdated
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) | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
examples/encryption/main.go
Outdated
// // 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) | ||
// } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cleaned up the example
examples/encryption/main.go
Outdated
err = sqlitex.Exec(conn, `PRAGMA key=swordfish;`, nil) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
err = sqlite.Crypto(conn, "swordfish2") | ||
if err != nil { | ||
panic(err) | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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/ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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)
.
There was a problem hiding this comment.
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 @@ | |||
/* |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
// { | ||
// "", | ||
// "delete", | ||
// 0, | ||
// }, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
@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. |
@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. |
I see you fixed the import. Thanks. |
@AdamSLevy I will revert my change. This is funny, both of us were trying to fix the same issue 🤣 |
This reverts commit 6ee59f0.
@AdamSLevy There is only one small issue, The version of sqlite3 in sqleet is Amazing job, thanks for pushing this forward. |
@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 :)
This is OK. If you run
I don't understand what you're talking about here. There is no such file on this branch or in this repo. |
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. |
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. 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. |
@AdamSLevy I believe SQLite3MultipleCiphers has released first major version which support new version of sqlite interface. The new release can be found here: Let me know if you need any help on my end. |
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. |
@AdamSLevy I'd like to help to push this forward. I just need some guidance on your plan. |
@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 |
This is a PR related to issue #92.