예제 #1
0
        public BlobStore(string path, SymmetricKey encryptionKey)
        {
            if (path == null) {
                Log.To.Database.E(TAG, "path cannot be null in ctor, throwing...");
                throw new ArgumentNullException("path");
            }

            _path = path;
            EncryptionKey = encryptionKey;
            if (Directory.Exists(path)) {
                // Existing blob-store.
                VerifyExistingStore();
            } else {
                // New blob store; create directory:
                Directory.CreateDirectory(path);
                if (!Directory.Exists(path)) {
                    Log.To.Database.E(TAG, "Unable to create directory for: {0}", path);
                    throw new InvalidOperationException(string.Format("Unable to create directory for: {0}", path));
                }

                if (encryptionKey != null) {
                    MarkEncrypted(true);
                }
            }
        }
        public void TestUnencryptedDB()
        {
            // Create unencrypted DB:
            var options = new DatabaseOptions();
            options.Create = true;
            var seekrit = manager.OpenDatabase("seekrit", options);
            Assert.IsNotNull(seekrit, "Failed to create db");
            CreateDocumentWithProperties(seekrit, new Dictionary<string, object> {
                { "answer", 42 }
            });

            Assert.DoesNotThrow(() => seekrit.Close().Wait(15000));

            var key = new SymmetricKey(_wrong.KeyData);
            options.EncryptionKey = key;
            var e = Assert.Throws<CouchbaseLiteException>(() => seekrit = manager.OpenDatabase("seekrit", options), 
                "Shouldn't have been able to reopen encrypted db with wrong password");
            Assert.AreEqual(StatusCode.Unauthorized, e.Code);

            seekrit.ChangeEncryptionKey(null);
            options.EncryptionKey = null;
            seekrit = manager.OpenDatabase("seekrit", options);
            Assert.IsNotNull(seekrit, "Failed to reopen db");
            Assert.AreEqual(1, seekrit.GetDocumentCount());
        }
        public void OneTimeSetUp()
        {
            base.SetUp();

            Log.I(TAG, "Generating keys for test, this might take a while...");
            _letmein = SymmetricKey.Create("letmein");
            Log.I(TAG, "Keys completed (1/3)");
            _letmeout = SymmetricKey.Create("letmeout");
            Log.I(TAG, "Keys completed (2/3)");
            _wrong = SymmetricKey.Create("wrong");
            Log.I(TAG, "Keys completed (3/3)");
        }
 public void OneTimeSetUp()
 {
     var sw = System.Diagnostics.Stopwatch.StartNew();
     Console.WriteLine("Generating keys for test, this might take a while...");
     _letmein = SymmetricKey.Create("letmein");
     Console.WriteLine("Keys completed (1/3)");
     _letmeout = SymmetricKey.Create("letmeout");
     Console.WriteLine("Keys completed (2/3)");
     _wrong = SymmetricKey.Create("wrong");
     Console.WriteLine("Keys completed (3/3)");
     sw.Stop();
     Console.WriteLine("Created three keys in {0}ms", sw.ElapsedMilliseconds);
 }
예제 #5
0
        public AtomicAction ActionToChangeEncryptionKey(SymmetricKey newKey)
        {
            return(new AtomicAction(() =>
                                    ForestDBBridge.Check(err =>
            {
                var newc4key = default(C4EncryptionKey);
                if (newKey != null)
                {
                    newc4key = new C4EncryptionKey(newKey.KeyData);
                }

                return Native.c4view_rekey(_indexDB, &newc4key, err);
            }), null, null));
        }
        public BlobStore(string path, SymmetricKey encryptionKey)
        {
            if (path == null) {
                throw new ArgumentNullException("path");
            }

            _path = path;
            EncryptionKey = encryptionKey;
            FilePath directory = new FilePath(path);
            if (directory.Exists() && directory.IsDirectory()) {
                // Existing blob-store.
                VerifyExistingStore();
            } else {
                // New blob store; create directory:
                directory.Mkdirs();
                if (!directory.IsDirectory()) {
                    throw new InvalidOperationException(string.Format("Unable to create directory for: {0}", directory));
                }

                if (encryptionKey != null) {
                    MarkEncrypted(true);
                }
            }
        }
예제 #7
0
        private void ChangeEncryptionKey(SymmetricKey newKey)
        {
            if (!IsOpen) {
                Log.W(TAG, "ChangeEncryptionKey called on closed database");
                return;
            }

            var action = Storage.ActionToChangeEncryptionKey(newKey);
            action.AddLogic(Attachments.ActionToChangeEncryptionKey(newKey));
            action.AddLogic(() => Shared.SetValue("encryptionKey", "", Name, newKey), null, null);
            action.Run();
        }
        public bool Open(String path, bool readOnly, string schema, SymmetricKey encryptionKey)
        {
            if (IsOpen)
                return true;

            Path = path;
            _readOnly = readOnly;
            Factory = new TaskFactory(new SingleThreadScheduler());

            try {
                int readFlag = readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
                int writer_flags = SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN | readFlag | SQLITE_OPEN_FULLMUTEX;
                OpenSqliteConnection(writer_flags, encryptionKey, out _writeConnection);

                #if ENCRYPTION
                if (!Decrypt(encryptionKey, _writeConnection)) {
                    throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.Unauthorized, TAG,
                        "Decryption of database failed");
                }
                #endif

                if(schema != null && GetVersion() == 0) {
                    foreach (var statement in schema.Split(';')) {
                        ExecSQL(statement);
                    }
                }

                const int reader_flags = SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN | SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX;
                OpenSqliteConnection(reader_flags, encryptionKey, out _readConnection);

                #if ENCRYPTION
                if(!Decrypt(encryptionKey, _readConnection)) {
                    throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.Unauthorized, TAG,
                        "Decryption of database failed");
                }
                #endif
            } catch(CouchbaseLiteException) {
                Log.To.Database.W(TAG, "Error opening SQLite storage engine, rethrowing...");
                throw;
            } catch (Exception ex) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, ex, TAG, "Failed to open SQLite storage engine");
            }

            return true;
        }
        // Returns true on success, false if encryption key is wrong, throws exception for other cases
        public bool Decrypt(SymmetricKey encryptionKey, sqlite3 connection)
        {
            #if !ENCRYPTION
            Log.To.Database.E(TAG, "Encryption not supported on this store, throwing...");
            throw new InvalidOperationException("Encryption not supported on this store");
            #else
            if (encryptionKey != null) {
                // http://sqlcipher.net/sqlcipher-api/#key
                var sql = String.Format("PRAGMA key = \"x'{0}'\"", encryptionKey.HexData);
                try {
                    ExecSQL(sql, connection);
                } catch(CouchbaseLiteException) {
                    Log.To.Database.E(TAG, "Decryption operation failed, rethrowing...");
                    throw;
                } catch(Exception e) {
                    throw Misc.CreateExceptionAndLog(Log.To.Database, e, TAG, "Decryption operation failed");
                }
            }

            // Verify that encryption key is correct (or db is unencrypted, if no key given):
            var result = raw.sqlite3_exec(connection, "SELECT count(*) FROM sqlite_master");
            if (result != raw.SQLITE_OK) {
                if (result == raw.SQLITE_NOTADB) {
                    return false;
                } else {
                    throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.DbError, TAG, "Cannot read from database ({0})", result);
                }
            }

            return true;
            #endif
        }
        public bool MockDecrypt(SymmetricKey encryptionKey)
        {
            var givenKeyData = encryptionKey != null ? encryptionKey.KeyData : new byte[0];
            var keyPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(Path), "mock_key");
            if (!File.Exists(keyPath)) {
                File.WriteAllBytes(keyPath, givenKeyData);
            } else {
                var actualKeyData = File.ReadAllBytes(keyPath);
                if (!givenKeyData.SequenceEqual(actualKeyData)) {
                    return false;
                }
            }

            return true;
        }
        public AtomicAction ActionToChangeEncryptionKey(SymmetricKey newKey)
        {
            var retVal = new AtomicAction(() =>
                ForestDBBridge.Check(err => 
                {
                    var newc4key = default(C4EncryptionKey);
                    if (newKey != null) {
                        newc4key = new C4EncryptionKey(newKey.KeyData);
                    }

                    return Native.c4db_rekey(Forest, &newc4key, err);
                }), null, null);

            foreach (var viewName in GetAllViews()) {
                var store = GetViewStorage(viewName, false) as ForestDBViewStore;
                if (store == null) {
                    continue;
                }

                retVal.AddLogic(store.ActionToChangeEncryptionKey(newKey));
            }

            return retVal;
        }
예제 #12
0
 public void SetEncryptionKey(SymmetricKey key)
 {
     _encryptionKey = key;
 }
예제 #13
0
 public void ChangeEncryptionKey(SymmetricKey newKey)
 {
     ActionToChangeEncryptionKey(newKey).Run();
 }
예제 #14
0
        public AtomicAction ActionToChangeEncryptionKey(SymmetricKey newKey)
        {
            var action = new AtomicAction();

            // Find all the blob files:
            var blobs = default(string[]);
            var oldKey = EncryptionKey;
            blobs = Directory.GetFiles(_path, "*" + FileExtension);
            if (blobs.Length == 0) {
                // No blobs, so nothing to encrypt. Just add/remove the encryption marker file:
                action.AddLogic(() =>
                {
                    Log.To.NoDomain.D(TAG, "{0} {1}", (newKey != null) ? "encrypting" : "decrypting", _path);
                    Log.To.NoDomain.D(TAG, "    No blobs to copy; done.");
                    EncryptionKey = newKey;
                    MarkEncrypted(newKey != null);
                }, () =>
                {
                    EncryptionKey = oldKey;
                    MarkEncrypted(oldKey != null);
                }, null);
                return action;
            }

            // Create a new directory for the new blob store. Have to do this now, before starting the
            // action, because farther down we create an action to move it...
            var tempPath = Path.Combine(Path.GetTempPath(), String.Format("CouchbaseLite-Temp-{0}", Misc.CreateGUID()));
            action.AddLogic(() => 
            {
                Log.To.NoDomain.D(TAG, "{0} {1}", (newKey != null) ? "encrypting" : "decrypting", _path);
                Directory.CreateDirectory(tempPath);
            }, () => Directory.Delete(tempPath, true), null);

            var tempStore = default(BlobStore);
            action.AddLogic(() =>
            {
                tempStore = new BlobStore(tempPath, newKey);
                tempStore.MarkEncrypted(true);
            }, null, null);

            // Copy each of my blobs into the new store (which will update its encryption):
            action.AddLogic(() =>
            {
                foreach(var blobName in blobs) {
                    // Copy file by reading with old key and writing with new one:
                    Log.To.NoDomain.D(TAG, "    Copying {0}", blobName);
                    Stream readStream = File.Open(blobName, FileMode.Open, FileAccess.Read, FileShare.Read);
                    if(EncryptionKey != null) {
                        readStream = EncryptionKey.DecryptStream(readStream);
                    }

                    var writer = new BlobStoreWriter(tempStore);
                    try {
                        writer.Read(readStream);
                        writer.Finish();
                        writer.Install();
                    } catch(Exception) {
                        writer.Cancel();
                        throw;
                    } finally {
                        readStream.Dispose();
                    }
                }
            }, null, null);

            // Replace the attachment dir with the new one:
            action.AddLogic(AtomicAction.MoveDirectory(tempPath, _path));

            // Finally update EncryptionKey:
            action.AddLogic(() =>
            {
                EncryptionKey = newKey;
            }, () =>
            {
                EncryptionKey = oldKey;
            }, null);

            return action;
        }
        private IEnumerable<byte> Decrypt(byte[] key, byte[] data)
        {
            var symmetricKey = new SymmetricKey(DecryptRSA(GetPrivateKey(), key));
            if(symmetricKey == null) {
                return null;
            }

            return symmetricKey.DecryptData(data);
        }
        private byte[][] Encrypt(byte[] data)
        {
            try {
                var symmetricKey = new SymmetricKey();
                var encryptedKey = EncryptRSA(GetPublicKey(), symmetricKey.KeyData);
                if(encryptedKey == null) {
                    return null;
                }

                var encryptedData = new byte[2][];
                encryptedData[0] = encryptedKey;
                encryptedData[1] = symmetricKey.EncryptData(data);
                return encryptedData;
            } catch(Exception e) {
                Log.To.NoDomain.E(Tag, "Error during encryption", e);
                return null;
            }
        }
예제 #17
0
 /// <summary>
 /// Change the encryption key used to secure this database
 /// </summary>
 /// <param name="newPassword">The new password to derive the key from</param>
 public void ChangeEncryptionPassword(string newPassword)
 {
     var newKey = new SymmetricKey(newPassword);
     ChangeEncryptionKey(newKey);
 }
예제 #18
0
 /// <summary>
 /// Change the encryption key used to secure this database
 /// </summary>
 /// <param name="newPassKey">The new password to derive the key from</param>
 public void ChangeEncryptionKey(IEnumerable<byte> newPassKey)
 {
     var newKey = new SymmetricKey(newPassKey.ToArray());
     ChangeEncryptionKey(newKey);
 }
예제 #19
0
        private void VerifyExistingStore()
        {
            var markerPath = Path.Combine(_path, ENCRYPTION_MARKER_FILENAME);
            var fileExists = File.Exists(markerPath);
            var encryptionAlg = default(string);
            try {
                encryptionAlg = fileExists ? File.ReadAllText(markerPath) : null;
            } catch(Exception e) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, e, TAG, "Error verifying blob store");
            }

            if (encryptionAlg != null) {
                // "_encryption" file is present, so make sure we support its format & have a key:
                if (EncryptionKey == null) {
                    throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.Unauthorized, TAG,
                        "Opening encrypted blob-store without providing a key");
                } else if (ENCRYPTION_ALGORITHM != encryptionAlg) {
                    throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.Unauthorized, TAG,
                        "Blob-store uses unrecognized encryption '{0}'", encryptionAlg);
                }
            } else if (!fileExists) {
                // No "_encryption" file was found, so on-disk store isn't encrypted:
                var encryptionKey = EncryptionKey;
                if (encryptionKey != null) {
                    // This store was created before the db encryption fix, so its files are not
                    // encrypted, even though they should be. Remedy that:
                    Log.To.NoDomain.I(TAG, "**** BlobStore should be encrypted; fixing it now...");
                    EncryptionKey = null;
                    ChangeEncryptionKey(encryptionKey);
                }
            }
        }
 public void SetEncryptionKey(SymmetricKey key)
 {
     _encryptionKey = key;
 }
        void OpenSqliteConnection(int flags, SymmetricKey encryptionKey, out sqlite3 db)
        {
            LastErrorCode = raw.sqlite3_open_v2(Path, out db, flags, null);
            if (LastErrorCode != raw.SQLITE_OK) {
                Path = null;
                var errMessage = "Failed to open SQLite storage engine at path {0}".Fmt(Path);
                throw new CouchbaseLiteException(errMessage, StatusCode.DbError);
            }

#if !__ANDROID__ && !NET_3_5 && VERBOSE
                var i = 0;
                var val = raw.sqlite3_compileoption_get(i);
                while (val != null)
                {
                    Log.V(TAG, "Sqlite Config: {0}".Fmt(val));
                    val = raw.sqlite3_compileoption_get(++i);
                }
#endif

            Log.D(TAG, "Open {0} (flags={1}{2})", Path, flags, (encryptionKey != null ? ", encryption key given" : ""));

            raw.sqlite3_create_collation(db, "JSON", null, CouchbaseSqliteJsonUnicodeCollationFunction.Compare);
            raw.sqlite3_create_collation(db, "JSON_ASCII", null, CouchbaseSqliteJsonAsciiCollationFunction.Compare);
            raw.sqlite3_create_collation(db, "JSON_RAW", null, CouchbaseSqliteJsonRawCollationFunction.Compare);
            raw.sqlite3_create_collation(db, "REVID", null, CouchbaseSqliteRevIdCollationFunction.Compare);
        }
        public bool Open(String path, SymmetricKey encryptionKey = null)
        {
            if (IsOpen)
                return true;

            Path = path;
            Factory = new TaskFactory(new SingleThreadScheduler());

            try {
                shouldCommit = false;
                const int writer_flags = SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
                OpenSqliteConnection(writer_flags, encryptionKey, out _writeConnection);

                const int reader_flags = SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN | SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX;
                OpenSqliteConnection(reader_flags, encryptionKey, out _readConnection);

                if (!Decrypt(encryptionKey)) {
                    throw new CouchbaseLiteException(StatusCode.Unauthorized);
                }
            } catch(CouchbaseLiteException) {
                Log.W(TAG, "Error opening SQLite storage engine");
                throw;
            } catch (Exception ex) {
                throw new CouchbaseLiteException("Failed to open SQLite storage engine", ex) { Code = StatusCode.Exception };
            }

            return true;
        }
예제 #23
0
        /// <summary>
        /// Change the encryption key used to secure this database
        /// </summary>
        /// <param name="newKey">The new key to use</param>
        public void ChangeEncryptionKey(SymmetricKey newKey)
        {
            if(!IsOpen) {
                Log.To.Database.W(TAG, "{0} ChangeEncryptionKey called on closed database, returning early...", this);
                return;
            }

            var action = Storage.ActionToChangeEncryptionKey(newKey);
            action.AddLogic(Attachments.ActionToChangeEncryptionKey(newKey));
            action.AddLogic(() => Shared.SetValue("encryptionKey", "", Name, newKey), null, null);
            action.Run();
        }
        // Returns true on success, false if encryption key is wrong, throws exception for other cases
        public bool Decrypt(SymmetricKey encryptionKey)
        {
            var hasRealEncryption = false;
            try {
                hasRealEncryption = raw.sqlite3_compileoption_used("SQLITE_HAS_CODEC") != 0;
            } catch(EntryPointNotFoundException) {
                // Android omits the compileoption_used from its system SQLite
                hasRealEncryption = false;
            }

            if (encryptionKey != null) {
                if (!hasRealEncryption) {
                    throw new CouchbaseLiteException("Encryption not available (app not built with SQLCipher)", StatusCode.NotImplemented);
                }

                // http://sqlcipher.net/sqlcipher-api/#key
                var sql = String.Format("PRAGMA key = \"x'{0}'\"", encryptionKey.HexData);
                try {
                    ExecSQL(sql, _writeConnection);
                    ExecSQL(sql, _readConnection);
                } catch(CouchbaseLiteException) {
                    Log.W(TAG, "Decryption operation failed");
                    throw;
                } catch(Exception e) {
                    throw new CouchbaseLiteException("Decryption operation failed", e);
                }
            }

            // Verify that encryption key is correct (or db is unencrypted, if no key given):
            var result = raw.sqlite3_exec(_readConnection, "SELECT count(*) FROM sqlite_master");
            if (result != raw.SQLITE_OK) {
                if (result == raw.SQLITE_NOTADB) {
                    return false;
                } else {
                    throw new CouchbaseLiteException(String.Format("Cannot read from database ({0})", result), StatusCode.DbError);
                }
            }

            return true;
        }
        public bool Open(String path, bool readOnly, string schema, SymmetricKey encryptionKey)
        {
            if (IsOpen)
                return true;

            Path = path;
            _readOnly = readOnly;
            Factory = new TaskFactory(new SingleThreadScheduler());

            try {
                shouldCommit = false;
                int readFlag = readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
                int writer_flags = SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN | readFlag | SQLITE_OPEN_FULLMUTEX;
                OpenSqliteConnection(writer_flags, encryptionKey, out _writeConnection);

                if (!Decrypt(encryptionKey, _writeConnection)) {
                    throw new CouchbaseLiteException(StatusCode.Unauthorized);
                }

				if(schema != null && GetVersion() == 0) {
					foreach (var statement in schema.Split(';')) {
						ExecSQL(statement);
					}
				}

				const int reader_flags = SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN | SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX;
				OpenSqliteConnection(reader_flags, encryptionKey, out _readConnection);

				if(!Decrypt(encryptionKey, _readConnection)) {
					throw new CouchbaseLiteException(StatusCode.Unauthorized);
				}
            } catch(CouchbaseLiteException) {
                Log.W(TAG, "Error opening SQLite storage engine");
                throw;
            } catch (Exception ex) {
                throw new CouchbaseLiteException("Failed to open SQLite storage engine", ex) { Code = StatusCode.Exception };
            }

            return true;
        }