I want to encrypt/decrypt all cached data from a NSURLSession
using AES256. I'm new using Alamofire but I think it is possible to do it without involving the library itself.
I don't know exactly what is the most seamless way to encrypt the data before caching and decrypt it after being retrieved from cache.
I see I can use Alamofire's SessionDelegate
and the methods dataTaskWillCacheResponse
and dataTaskWillCacheResponseWithCompletion
to encrypt but I don't see anything related with the data being extracted from the cache to do the decrypting.
On the other hand I was thinking about a custom NSURLProtocol
to override cachedResponse
but I don't see anything related with the caching of that response, only with the extracted data.
In summary, I don't know if it is possible to accomplish this, or I have to use a mix between the NSURLSessionDelegate/SessionDelegate
and NSURLProtocol
, or maybe subclass NSURLCache
to do the job and pass it to the Alamofire session, or there is something simpler out there, or I'm terribly wrong :P
Any help will be really appreciated.
EDIT
I'm trying to achieve it with the next implementation. First of all a very simple subclass of the cache:
class EncryptedURLCache: URLCache {
let encryptionKey: String
init(memoryCapacity: Int, diskCapacity: Int, diskPath path: String? = nil, encryptionKey: String) {
guard !encryptionKey.isEmpty else {
fatalError("No encryption key provided")
}
self.encryptionKey = encryptionKey
super.init(memoryCapacity: memoryCapacity, diskCapacity: diskCapacity, diskPath: path)
}
override func cachedResponse(for request: URLRequest) -> CachedURLResponse? {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
return super.cachedResponse(for: request)?.cloneDecryptingData(withKey: encryptionKey)
}
override func storeCachedResponse(_ cachedResponse: CachedURLResponse, for request: URLRequest) {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
super.storeCachedResponse(cachedResponse.cloneEncryptingData(withKey: encryptionKey), for: request)
}
}
And an extension of the cached response to return the encrypted/decrypted data
extension CachedURLResponse {
func cloneEncryptingData(withKey key: String) -> CachedURLResponse {
return clone(withData: data.aes256Encrypted(withKey: key))
}
func cloneDecryptingData(withKey key: String) -> CachedURLResponse {
return clone(withData: data.aes256Decrypted(withKey: key) ?? data)
}
private func clone(withData data: Data) -> CachedURLResponse {
return CachedURLResponse(
response: response,
data: data,
userInfo: userInfo,
storagePolicy: storagePolicy
)
}
}
This is working but only for a mockable.io that I mounted with the header Cache-Control: max-age=60
. I'm also testing against the SWAPI http://swapi.co/api/people/1/
and against Google Books https://www.googleapis.com/books/v1/volumes?q=swift+programming
.
In all three cases the responses are correctly encrypted and cached. I'm doing my testing cutting off the Internet connection and setting the session configuration's requestCachePolicy = .returnCacheDataDontLoad
.
In this scenario, the request made to mockable.io is correctly decrypted and returned from cache but the others say NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline."
. This is VERY strange because, with that policy, it has to say NSURLErrorDomain Code=-1008 "resource unavailable"
if there is no possibility to return the cached data. If there is an error decrypting then it says it was an error serializing to a JSON object.
I've also tested with the common shared cache and it works as expected, with that policy the data is returned. I thought it could be something related with the absence of cache headers in the SWAPI and GBooks responses but this test works, it returns the cached data.
Then I made another test: using my cache but without encrypting/decrypting data, simply cloning the returned cached response with the data as is, with no results. Then I tried a final and very stupid test: to avoid cloning the response, just return the cachedResponse
and then IT WORKED. How the h*** is that possible? If I clone the cachedResponse to inject my encrypted/decrypted data it does not work! Even in examples from Apple they are creating new cached responses with no fear.
I don't know where is the error but I'm going to jump over the window in a minute or two.
Please, any help? Thank you so much.
EDIT 2
I was changing emails with a DTS engineer from Apple and the conclusion is that this is not possible to achieve this because the backing CF type is doing more logic than the Foundation object, in this case it is doing a validation against the URLRequest that is passed to it when the system caches the response, but I cannot pass it when make the clone with the regular NSCachedURLResponse.
When the system validates against the request, there is none to match with.