Created
October 27, 2019 03:27
-
-
Save mandrachek/5da100d508eebf57a94de81448dcadaa to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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