Skip to content

Instantly share code, notes, and snippets.

@mandrachek
Created October 27, 2019 03:27
Show Gist options
  • Save mandrachek/5da100d508eebf57a94de81448dcadaa to your computer and use it in GitHub Desktop.
Save mandrachek/5da100d508eebf57a94de81448dcadaa to your computer and use it in GitHub Desktop.
package okhttp3
import okhttp3.Cache
import okhttp3.internal.io.FileSystem
import okio.*
import java.io.*
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import javax.crypto.CipherOutputStream
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
class EncryptingFileSystem(private val key : SecretKey) : FileSystem {
@Suppress("ArrayInDataClass")
internal data class CipherHolder(val iv: ByteArray, val cipher: Cipher)
// we don't encrypt the journal
private fun isJournal(file: File) : Boolean = (file.name == "journal" || file.name == "journal.tmp")
private fun getEncryptionCipher() : CipherHolder {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val iv = ByteArray(12)
SecureRandom().nextBytes(iv)
cipher.init(Cipher.ENCRYPT_MODE, key, GCMParameterSpec(128,iv))
return CipherHolder(iv,cipher)
}
private fun getDecryptionCipher(iv: ByteArray) : Cipher {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(128, iv))
return cipher
}
/**
* This is only used by OkHttp to write it's journal. Don't attempt to encyrpt this. Not sure
* how an appendingSink would work with a CipherOutputStream?
*/
@Throws(FileNotFoundException::class)
override fun appendingSink(file: File): Sink {
return try {
file.appendingSink()
} catch (_: FileNotFoundException) {
// Maybe the parent directory doesn't exist? Try creating it first.
file.parentFile?.mkdirs()
file.appendingSink()
}
}
@Throws(IOException::class)
override fun delete(file: File) {
// try to delete the .iv file first
val ivFile = File(file.absolutePath + ".iv")
ivFile.delete()
// delete the file
if (!file.delete() && file.exists()) {
throw IOException("failed to delete $file")
}
}
@Throws(IOException::class)
override fun deleteContents(directory: File) {
val files = directory.listFiles() ?: throw IOException("not a readable directory: $directory")
for (file in files) {
if (file.isDirectory) {
deleteContents(file)
}
if (!file.delete()) {
throw IOException("failed to delete $file")
}
}
}
override fun exists(file: File): Boolean = file.exists()
@Throws(IOException::class)
override fun rename(from: File, to: File) {
val toIv = File(to.absolutePath + ".iv")
delete(toIv)
delete(to)
val fromIv = File(from.absolutePath + ".iv")
fromIv.renameTo(toIv)
if (!from.renameTo(to)) {
throw IOException("failed to rename $from to $to")
}
}
@Throws(FileNotFoundException::class)
override fun sink(file: File): Sink {
if (isJournal(file)) {
if (!file.exists()) {
file.parentFile?.mkdirs()
}
return file.sink()
}
val cipherHolder = getEncryptionCipher()
// write the iv to disk next to the file.
val ivFile = File(file.absolutePath + ".iv")
if (!ivFile.exists()) {
ivFile.parentFile?.mkdirs()
}
val ivBuffer = ivFile.sink().buffer()
ivBuffer.write(cipherHolder.iv)
ivBuffer.flush()
ivBuffer.close()
return CipherOutputStream(file.outputStream().buffered(),cipherHolder.cipher).sink()
}
override fun size(file: File): Long = file.length()
@Throws(FileNotFoundException::class)
override fun source(file: File): Source {
if (isJournal(file)) {
return file.source()
}
val ivFile = File(file.absolutePath + ".iv")
if (ivFile.exists()) {
ivFile.source().buffer().use {
val iv = it.readByteArray()
val cipher = getDecryptionCipher(iv)
return CipherInputStream(BufferedInputStream(FileInputStream(file)),cipher).source()
}
}
return file.source()
}
}
fun getCache(directory : File, maxSize: Long, key: SecretKey) : Cache {
val constructor = Cache::class.java.getDeclaredConstructor(File::class.java,Long::class.java,FileSystem::class.java)
constructor.isAccessible = true
return constructor.newInstance(directory, maxSize, EncryptingFileSystem(key))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment