6

I have a Xamarin.iOS app that is using Akavache to cache data and reduce the number on unnecessary requests to the server for data. Everything is working great as expected using BlobCache.LocalMachine, love it.

Now I would like to make sure that the data at rest in encrypted. From researching this and reading documentation online I saw that the default implementation doesn't actually encrypt the cache on iOS and that I actually need to provide a custom implementation of IEncryptionProvider.

I followed this article: http://kent-boogaart.com/blog/password-protected-encryption-provider-for-akavache

Added input from this thread: https://github.com/akavache/Akavache/issues/190

And ended up with this implementation:

    public interface IPasswordProtectedEncryptionProvider : IEncryptionProvider
    {
        void SetPassword(string password);
    }

    public sealed class MyEncryptionProvider : IPasswordProtectedEncryptionProvider
    {
        static readonly byte[] salt = Encoding.ASCII.GetBytes("dVBZMQWyFRcJOIas");
        readonly IScheduler scheduler;
        readonly SymmetricAlgorithm symmetricAlgorithm;
        ICryptoTransform decryptTransform;
        ICryptoTransform encryptTransform;

        public MyEncryptionProvider()
        {
            scheduler = BlobCache.TaskpoolScheduler ?? throw new ArgumentNullException(nameof(scheduler), "Scheduler instance is null");
            symmetricAlgorithm = new RijndaelManaged();
            var securePassword = "kadhaskdhsakhaskjdhaskjdhaskdjashdkjahkfghkjhew";
            SetPassword(securePassword);
        }

        public void SetPassword(string password)
        {
            if(password == null)
               throw new ArgumentNullException(nameof(password), "password can't be null");

            var derived = new Rfc2898DeriveBytes(password, salt);
            var bytesForKey = symmetricAlgorithm.KeySize / 8;
            var bytesForIV = symmetricAlgorithm.BlockSize / 8;
            symmetricAlgorithm.Key = derived.GetBytes(bytesForKey);
            symmetricAlgorithm.IV = derived.GetBytes(bytesForIV);
            decryptTransform = symmetricAlgorithm.CreateDecryptor(symmetricAlgorithm.Key, symmetricAlgorithm.IV);
            encryptTransform = symmetricAlgorithm.CreateEncryptor(symmetricAlgorithm.Key, symmetricAlgorithm.IV);
        }

        public IObservable<byte[]> DecryptBlock(byte[] block)
        {
            if (block == null)
            {
                throw new ArgumentNullException(nameof(block), "block can't be null");
            }

            if (decryptTransform == null)
            {
                return Observable.Throw<byte[]>(new InvalidOperationException("You must call SetPassword first."));
            }

            return Observable.Start(() => InMemoryTransform(block, decryptTransform), scheduler);
        }

        public IObservable<byte[]> EncryptBlock(byte[] block)
        {
            if (block == null)
            {
                throw new ArgumentNullException(nameof(block), "block can't be null");
            }

            if (encryptTransform == null)
            {
                return Observable.Throw<byte[]>(new InvalidOperationException("You must call SetPassword first."));
            }

            return Observable.Start(() => InMemoryTransform(block, encryptTransform), scheduler);
        }

        static byte[] InMemoryTransform(byte[] bytesToTransform, ICryptoTransform transform)
        {
            using (var memoryStream = new MemoryStream())
            {
                using (var cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
                {
                    cryptoStream.Write(bytesToTransform, 0, bytesToTransform.Length);
                }

                return memoryStream.ToArray();
            }
        }
    }

I register this implementation like this: Locator.CurrentMutable.RegisterConstant(new MyEncryptionProvider(), typeof(IEncryptionProvider)); before calling BlobCache.Secure

And I see the EncryptBlock and DecryptBlock being called when I use .GetOrFetchObject on that secure cache. So far so good.

The problem I run into is that it fails when trying to Deserialize with this error from Newtonsoft:

{Newtonsoft.Json.JsonReaderException: Read past end of current container context. Path ''.
  at Newtonsoft.Json.Bson.BsonReader.ReadNormal () [0x0013c] in <c19705166c7c4a608e182e859c4de6d2>:0 
  at Newtonsoft.Json.Bson.BsonReader.Read () [0x00033] in <c19705166c7c4a608e182e859c4de6d2>:0 
  at Newtonsoft.Json.JsonReader.ReadAndAssert () [0x00000] in <c19705166c7c4a608e182e859c4de6d2>:0 
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x000b6] in <c19705166c7c4a608e182e859c4de6d2>:0 
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x0006d] in <c19705166c7c4a608e182e859c4de6d2>:0 
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType, System.Boolean checkAdditionalContent) [0x000db] in <c19705166c7c4a608e182e859c4de6d2>:0 
  at Newtonsoft.Json.JsonSerializer.DeserializeInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType) [0x00053] in <c19705166c7c4a608e182e859c4de6d2>:0 
  at Newtonsoft.Json.JsonSerializer.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType) [0x00000] in <c19705166c7c4a608e182e859c4de6d2>:0 
  at Newtonsoft.Json.JsonSerializer.Deserialize[T] (Newtonsoft.Json.JsonReader reader) [0x00000] in <c19705166c7c4a608e182e859c4de6d2>:0 
  at Akavache.Sqlite3.SQLitePersistentBlobCache.DeserializeObject[T] (System.Byte[] data) [0x00074] in <67aced6c5c1a4c15b03e120d7300429d>:0 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Library/Frameworks/Xamarin.iOS.framework/Versions/10.12.0.20/src/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:151 
  at System.Reactive.PlatformServices.ExceptionServicesImpl.Rethrow (System.Exception exception) [0x00006] in <427ee2007f4d40bb9d3f1fcd73e9b750>:0 
  at System.Reactive.ExceptionHelpers.ThrowIfNotNull (System.Exception exception) [0x0000d] in <ab76b1b1678341d69f8fc2c1f68d0bf5>:0 
  at System.Reactive.Subjects.AsyncSubject`1[T].GetResult () [0x00039]

When the same object is cached using the BlobCache.LocalMachine the same data structure is successfully stored like this:

[
  {
    "Name": "John Smith",
    "Date": "2017-04-03T20:00:00-04:00",
    "Number1": -8820768.6192349959,
    "Number2": -25540081.8275,
    "Number3": -0.0045255076670528034,
    "Number4": 0.0457761358483606,
    "RelatedObjects": [],
    "LastUpdateTime": "2017-04-04T17:36:06.247-04:00"
  }
]

What am I missing here? Very frustrated, really hoping someone sees something I am not. Thanks for your help.

Dmitry Samuylov
  • 1,454
  • 2
  • 14
  • 35
  • 1
    Just referencing the other issue with the sample in case someone arrives here first https://stackoverflow.com/questions/46104014/xamarin-ios-akavache-working-example – Shane Neuville Sep 11 '17 at 16:04
  • With the help of the example in this post: https://stackoverflow.com/questions/46104014/xamarin-ios-akavache-working-example I was able to get this problem resolved and a working example is shown in the sample solution here: https://github.com/dmitrysamuylov/xamarin-ios-akavache-secure-example hope it helps someone – Dmitry Samuylov Sep 11 '17 at 21:28

1 Answers1

3

With the help of the example in this post: Xamarin.iOS / Akavache Working Example

I was able to get this problem resolved and a working example is shown in the sample solution here: https://github.com/dmitrysamuylov/xamarin-ios-akavache-secure-example

hope it helps someone in the future

Dmitry Samuylov
  • 1,454
  • 2
  • 14
  • 35
  • Hi I have exactly the same problem as you had (`[Error] Error during refreshing documentsNewtonsoft.Json.JsonReaderException: Read past end of current container context. Path ''. at Newtonsoft.Json.Bson.BsonReader.ReadNormal () [0x0013c] in :0`). I tried to follow your example but I can't find what actually is causing this exception. Was there sth specific that was causing this issue? – Dominik Roszkowski Sep 06 '19 at 08:03
  • Hey @DominikRoszkowski, i wish i could be of more help, but I honestly don't remember the exact detail since this was 2 years ago. Pretty sure that the key was in the `CustomEncryptionProvider.cs` implementation in the example i linked to above and it was something rather subtle and also specific to how you make use of it. Did you try running the example from the repo? Does it work for you? If it does, I would just try comparing it to what you're doing and see what's different, that should give you the hint you need. Sorry I can't help more than that though. – Dmitry Samuylov Sep 06 '19 at 13:44
  • 1
    It seems that even though I had the exact same implementation te problem was with nuget versions. So it seems that the newest working Akavache is 6.5.20. Also I had to remove `SQLitePCLRaw.bundle_green` files via adding: ``` All ``` – Dominik Roszkowski Sep 06 '19 at 13:56
  • @DominikRoszkowski yes, it is very possible that there was a bug somewhere in the packages being used that was being triggered from a specific usecase, in the last 2 years since, this may have been adressed in the newest versions of the packages, that's definitely worth trying as well. Back when I was trying to resolve this issue, that was not an option for me. – Dmitry Samuylov Sep 06 '19 at 13:59