Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ All notable changes to this project will be documented in this file. Take a look
#### Streamer

* Fixed the rendering of PDF covers in some edge cases.
* Fixed reading ranges of obfuscated EPUB resources.

#### Navigator

Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,49 @@
/*
* Module: r2-streamer-kotlin
* Developers: Aferdita Muriqi, Clément Baumannn, Quentin Gliosca
*
* Copyright (c) 2020. Readium Foundation. All rights reserved.
* Use of this source code is governed by a BSD-style license which is detailed in the
* LICENSE file present in the project repository where this source code is maintained.
* Copyright 2020 Readium Foundation. All rights reserved.
* Use of this source code is governed by the BSD-style license
* available in the top-level LICENSE file of the project.
*/

package org.readium.r2.streamer.parser.epub

import com.mcxiaoke.koi.HASH
import com.mcxiaoke.koi.ext.toHexBytes
import org.readium.r2.shared.fetcher.ProxyResource
import org.readium.r2.shared.fetcher.Resource
import org.readium.r2.shared.fetcher.ResourceTry
import org.readium.r2.shared.fetcher.mapCatching
import org.readium.r2.shared.fetcher.*
import org.readium.r2.shared.publication.encryption.encryption
import kotlin.experimental.xor

internal class EpubDeobfuscator(private val pubId: String) {

fun transform(resource: Resource): Resource = DeobfuscatingResource(resource)

inner class DeobfuscatingResource(resource: Resource): ProxyResource(resource) {
inner class DeobfuscatingResource(resource: Resource): TransformingResource(resource, cacheBytes = true) {

override suspend fun read(range: LongRange?): ResourceTry<ByteArray> {
val algorithm = resource.link().properties.encryption?.algorithm
override suspend fun transform(data: ResourceTry<ByteArray>): ResourceTry<ByteArray> =
data.map { bytes ->
val algorithm = resource.link().properties.encryption?.algorithm

if (algorithm !in algorithm2length.keys)
return resource.read(range)
val obfuscationLength: Int = algorithm2length[algorithm]
?: return@map bytes

return resource.read(range).mapCatching {
val obfuscationLength: Int = algorithm2length[algorithm]!!
val obfuscationKey: ByteArray = when (algorithm) {
"http://ns.adobe.com/pdf/enc#RC" -> getHashKeyAdobe(pubId)
else -> HASH.sha1(pubId).toHexBytes()
}

deobfuscate(it, range, obfuscationKey, obfuscationLength)
it
deobfuscate(bytes = bytes, obfuscationKey = obfuscationKey, obfuscationLength = obfuscationLength)
bytes
}
}
}

private val algorithm2length: Map<String, Int> = mapOf(
"http://www.idpf.org/2008/embedding" to 1040,
"http://ns.adobe.com/pdf/enc#RC" to 1024
)

private fun deobfuscate(bytes: ByteArray, range: LongRange?, obfuscationKey: ByteArray, obfuscationLength: Int) {
private fun deobfuscate(bytes: ByteArray, obfuscationKey: ByteArray, obfuscationLength: Int) {
@Suppress("NAME_SHADOWING")
val range = range ?: (0L until bytes.size)
if (range.first >= obfuscationLength) {
return
}

val toDeobfuscate = Math.max(range.first, 0L)..Math.min(range.last, obfuscationLength - 1L)
for (i in toDeobfuscate.map { it.toInt() })
val toDeobfuscate = 0 until obfuscationLength
for (i in toDeobfuscate)
bytes[i] = bytes[i].xor(obfuscationKey[i % obfuscationKey.size])
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

package org.readium.r2.streamer.parser.epub

import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
Expand Down Expand Up @@ -73,6 +74,17 @@ class EpubDeobfuscatorTest {
assertThat(deobfuscatedRes).isEqualTo(font)
}

@Test
fun testIdpfDeobfuscationWithRange() {
runBlocking {
val deobfuscatedRes = deobfuscate(
"/deobfuscation/cut-cut.obf.woff",
"http://www.idpf.org/2008/embedding"
).read(20L until 40L).getOrThrow()
assertThat(deobfuscatedRes).isEqualTo(font.copyOfRange(20, 40))
}
}

@Test
fun testAdobeDeobfuscation() {
val deobfuscatedRes = deobfuscate(
Expand Down