secretblob.SecretBlob.kt Maven / Gradle / Ivy
package se.wollan.crypto.secretblob
import se.wollan.crypto.fromHexString
import se.wollan.crypto.xor
import java.io.IOException
import java.io.RandomAccessFile
import java.nio.file.Path
import kotlin.io.path.absolutePathString
interface SecretBlob {
/**
* @return a fresh byte array - implementations are not allowed to not hold on to the instance after it is returned
*/
fun getSecretDataForKey(key: SecretBlobKey): ByteArray
}
internal class SecretBlobImpl(
private val keys: List,
private val secretBlobFilePath: Path
) : SecretBlob {
private val skipLength by lazy { getInitialNumberOfBytesToSkip(secretBlobFilePath) }
override fun getSecretDataForKey(key: SecretBlobKey): ByteArray {
val (startIndex, length) = key.startIndexAndLength(keys)
val hexBytes = readBytesFromFile(secretBlobFilePath, skipLength + startIndex * 2, length * 2)
val hexString = hexBytes.toString(Charsets.UTF_8)
val secretData = hexString.fromHexString()
check(secretData.size == key.length) // sanity check
val decodedSecretData = key.hexMask.fromHexString().xor(secretData)
return decodedSecretData
}
}
internal fun SecretBlobKey.startIndexAndLength(keys: List): Pair {
var i = 0
for (key in keys) {
if (this == key)
return i to key.length
i += key.length
}
throw IndexOutOfBoundsException("couldn't find secret blob key: $this")
}
private fun getInitialNumberOfBytesToSkip(filePath: Path): Int {
val batchSize = 16
var index = 0
while (true) { // until exception
val bytes = readBytesFromFile(filePath, index, batchSize)
val indexOfFirstByteToUse = bytes.indexOfFirst { it.isHex() }
if (indexOfFirstByteToUse != -1)
return index + indexOfFirstByteToUse
index += batchSize
}
}
private fun Byte.isHex(): Boolean {
val c = toInt().toChar()
return c.isDigit() || (c in 'a'..'f') || (c in 'A'..'F')
}
private fun readBytesFromFile(filePath: Path, startIndex: Int, length: Int): ByteArray {
RandomAccessFile(filePath.absolutePathString(), "r").use { file ->
val buffer = ByteArray(length)
file.seek(startIndex.toLong())
val read = file.read(buffer, 0, length)
if (read != length)
throw IOException("end of file reached, please generate more secret blob data")
return buffer
}
}