Skip to content

Commit

Permalink
Provide fallback for broken AES/GCM streaming implementation on Andro…
Browse files Browse the repository at this point in the history
…id (#1096)

Co-authored-by: Martin Devillers <[email protected]>
  • Loading branch information
MartinDevi and Martin Devillers authored Jun 27, 2022
1 parent ef6bf7f commit ba4eec2
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 4 deletions.
20 changes: 19 additions & 1 deletion okio/src/jvmMain/kotlin/okio/CipherSink.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ class CipherSink(
// Shorten input until output is guaranteed to fit within a segment
var outputSize = cipher.getOutputSize(size)
while (outputSize > Segment.SIZE) {
check(size > blockSize) { "Unexpected output size $outputSize for input size $size" }
if (size <= blockSize) {
// Bug: For AES-GCM on Android `update` method never outputs any data
// As a consequence, `getOutputSize` just keeps increasing indefinitely after each update
// When that happens, the fallback is to perform the update operation without using a pre-allocated segment
sink.write(cipher.update(source.readByteArray(remaining)))
return remaining.toInt()
}
size -= blockSize
outputSize = cipher.getOutputSize(size)
}
Expand Down Expand Up @@ -104,6 +110,18 @@ class CipherSink(
val outputSize = cipher.getOutputSize(0)
if (outputSize == 0) return null

if (outputSize > Segment.SIZE) {
// Bug: For AES-GCM on Android `update` method never outputs any data
// As a consequence, `doFinal` returns the fully encrypted data, which may be arbitrarily large
// When that happens, the fallback is to perform the `doFinal` operation without using a pre-allocated segment
try {
sink.write(cipher.doFinal())
} catch (t: Throwable) {
return t
}
return null
}

var thrown: Throwable? = null
val buffer = sink.buffer

Expand Down
12 changes: 9 additions & 3 deletions okio/src/jvmMain/kotlin/okio/CipherSource.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,14 @@ class CipherSource(
require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
check(!closed) { "closed" }
if (byteCount == 0L) return 0L
if (final) return buffer.read(sink, byteCount)

refill()

return buffer.read(sink, byteCount)
}

private fun refill() {
while (buffer.size == 0L) {
while (buffer.size == 0L && !final) {
if (source.exhausted()) {
final = true
doFinal()
Expand All @@ -63,7 +62,14 @@ class CipherSource(
// Shorten input until output is guaranteed to fit within a segment
var outputSize = cipher.getOutputSize(size)
while (outputSize > Segment.SIZE) {
check(size > blockSize) { "Unexpected output size $outputSize for input size $size" }
if (size <= blockSize) {
// Bug: For AES-GCM on Android `update` method never outputs any data
// As a consequence, `getOutputSize` just keeps increasing indefinitely after each update
// When that happens, the fallback is to break the streaming implementation and just cipher the rest of the data all at once
final = true
buffer.write(cipher.doFinal(source.readByteArray()))
return
}
size -= blockSize
outputSize = cipher.getOutputSize(size)
}
Expand Down

0 comments on commit ba4eec2

Please sign in to comment.