@@ -59,7 +59,7 @@ internal class LcpDecryptor(
5959 encryption.isDeflated || ! encryption.isCbcEncrypted ->
6060 FullLcpResource (resource, encryption, license)
6161 else ->
62- CbcLcpResource (resource, encryption, license)
62+ CbcLcpResource (resource, license)
6363 }
6464 }
6565 }
@@ -91,7 +91,6 @@ internal class LcpDecryptor(
9191 */
9292 private class CbcLcpResource (
9393 private val resource : Resource ,
94- private val encryption : Encryption ,
9594 private val license : LcpLicense
9695 ) : Resource by resource {
9796
@@ -103,14 +102,14 @@ internal class LcpDecryptor(
103102 private lateinit var _length : Try <Long , ReadError >
104103
105104 /*
106- * Decryption needs to look around the data strictly matching the content to decipher.
107- * That means that in case of contiguous read requests, data fetched from the underlying
108- * resource are not contiguous. Every request to the underlying resource starts slightly
109- * before the end of the previous one. This is an issue with remote publications because
110- * you have to make a new HTTP request every time instead of reusing the previous one.
111- * To alleviate this, we cache the three last bytes read in each call and reuse them
112- * in the next call if possible.
113- */
105+ * Decryption needs to look around the data strictly matching the content to decipher.
106+ * That means that in case of contiguous read requests, data fetched from the underlying
107+ * resource are not contiguous. Every request to the underlying resource starts slightly
108+ * before the end of the previous one. This is an issue with remote publications because
109+ * you have to make a new HTTP request every time instead of reusing the previous one.
110+ * To alleviate this, we cache the three last bytes read in each call and reuse them
111+ * in the next call if possible.
112+ */
114113 private val _cache : Cache = Cache ()
115114
116115 /* * Plain text size. */
@@ -119,8 +118,8 @@ internal class LcpDecryptor(
119118 return _length
120119 }
121120
122- _length = encryption.originalLength?. let { Try .success(it) }
123- ? : lengthFromPadding()
121+ // Unfortunately, encryption.originalLength is not reliable.
122+ _length = lengthFromPadding()
124123
125124 return _length
126125 }
@@ -141,7 +140,16 @@ internal class LcpDecryptor(
141140 val bytes = resource.read(readOffset.. length)
142141 .getOrElse { return Try .failure(it) }
143142
144- val decryptedBytes = license.decrypt(bytes)
143+ return lengthFromLastTwoBlocks(length, bytes)
144+ }
145+
146+ private suspend fun lengthFromLastTwoBlocks (
147+ cipheredLength : Long ,
148+ lastTwoBlocks : ByteArray
149+ ): Try <Long , ReadError > {
150+ require(lastTwoBlocks.size == 2 * AES_BLOCK_SIZE )
151+
152+ val decryptedBytes = license.decrypt(lastTwoBlocks)
145153 .getOrElse {
146154 return Try .failure(
147155 ReadError .Decoding (
@@ -152,11 +160,19 @@ internal class LcpDecryptor(
152160
153161 check(decryptedBytes.size == AES_BLOCK_SIZE )
154162
155- val adjustedLength = length -
163+ val adjustedLength = cipheredLength -
156164 AES_BLOCK_SIZE - // Minus IV
157165 decryptedBytes.last().toInt() // Minus padding size
158166
159- return Try .success(adjustedLength)
167+ return if (adjustedLength >= 0 ) {
168+ Try .success(adjustedLength)
169+ } else {
170+ Try .failure(
171+ ReadError .Decoding (
172+ DebugError (" Padding length seems invalid." )
173+ )
174+ )
175+ }
160176 }
161177
162178 override suspend fun read (range : LongRange ? ): Try <ByteArray , ReadError > {
@@ -173,25 +189,27 @@ internal class LcpDecryptor(
173189 return Try .success(ByteArray (0 ))
174190 }
175191
192+ val rangeSize = range.last + 1 - range.first
193+
176194 val encryptedLength = resource.length()
177195 .getOrElse { return Try .failure(it) }
178196
197+ // range bounds must be multiple of AES_BLOCK_SIZE and
198+ val startPadding = range.first - range.first.floorMultipleOf(AES_BLOCK_SIZE .toLong())
199+ val endPadding = (range.last + 1 ).ceilMultipleOf(AES_BLOCK_SIZE .toLong()) - range.last - 1
200+
179201 // encrypted data is shifted by AES_BLOCK_SIZE because of IV and
180202 // the previous block must be provided to perform XOR on intermediate blocks
181- val encryptedStart = range.first.floorMultipleOf( AES_BLOCK_SIZE .toLong())
182- val encryptedEndExclusive = ( range.last + 1 ).ceilMultipleOf( AES_BLOCK_SIZE .toLong()) + AES_BLOCK_SIZE
203+ val encryptedStart = range.first - startPadding
204+ val encryptedEndExclusive = range.last + 1 + endPadding + AES_BLOCK_SIZE
183205
184206 val encryptedData = getEncryptedData(encryptedStart until encryptedEndExclusive)
185207 .getOrElse { return Try .failure(it) }
186208
187- if (encryptedData.size >= _cache .data.size) {
188- // cache the three last encrypted blocks that have been read for future use
189- val cacheStart = encryptedData.size - _cache .data.size
190- _cache .startIndex = (encryptedEndExclusive - _cache .data.size).toInt()
191- encryptedData.copyInto(_cache .data, 0 , cacheStart)
192- }
193-
194209 val bytes = license.decrypt(encryptedData)
210+ .onSuccess {
211+ check(it.isEmpty() || it.size == encryptedData.size - AES_BLOCK_SIZE )
212+ }
195213 .getOrElse {
196214 return Try .failure(
197215 ReadError .Decoding (
@@ -203,29 +221,49 @@ internal class LcpDecryptor(
203221 )
204222 }
205223
206- // exclude the bytes added to match a multiple of AES_BLOCK_SIZE
207- val sliceStart = (range.first - encryptedStart).toInt()
208-
209224 // was the last block read to provide the desired range
210225 val lastBlockRead = encryptedLength - encryptedEndExclusive <= AES_BLOCK_SIZE
211226
212- val rangeLength =
227+ val dataSlice =
213228 if (lastBlockRead) {
229+ val decryptedLength =
230+ if (::_length .isInitialized) {
231+ _length
232+ } else {
233+ val lastTwoBlocks = encryptedData.sliceArray(
234+ encryptedData.size - 2 until encryptedData.size
235+ )
236+ lengthFromLastTwoBlocks(encryptedLength, lastTwoBlocks)
237+ .onSuccess { _length = Try .success(it) }
238+ }.getOrElse { return Try .failure(it) }
239+
214240 // use decrypted length to ensure range.last doesn't exceed decrypted length - 1
215- val decryptedLength = length()
216- .getOrElse { return Try .failure(it) }
217- range.last.coerceAtMost(decryptedLength - 1 ) - range.first + 1
241+ val dataLength = (range.last + 1 ).coerceAtMost(decryptedLength) - range.first
242+
243+ // keep only enough bytes to fit the length corrected request in order to never include padding
244+ val sliceEnd = startPadding + dataLength.toInt()
245+
246+ startPadding.toInt() until sliceEnd.toInt()
218247 } else {
219- // the last block won't be read, so there's no need to compute length
220- range.last - range.first + 1
248+ // the last block was not read, so there's no need to compute decrypted length
249+
250+ // bytes contains deciphered data for startPadding, then for the requested
251+ // range, and then for endPadding
252+ // the requested range might have been far too large, in which case bytes doesn't
253+ // content all of that data
254+ // if there are any data for endPadding, it begins at endPaddingStartIndex.
255+ val endPaddingStartIndex = (startPadding + rangeSize).coerceAtMost(
256+ bytes.size.toLong()
257+ )
258+ startPadding.toInt() until endPaddingStartIndex.toInt()
221259 }
222260
223- // keep only enough bytes to fit the length corrected request in order to never include padding
224- val sliceEnd = sliceStart + rangeLength.toInt()
225-
226- return Try .success(bytes.sliceArray(sliceStart until sliceEnd))
261+ return Try .success(bytes.sliceArray(dataSlice))
227262 }
228263
264+ /* *
265+ * Reads encrypted data using the cache when suitable.
266+ */
229267 private suspend fun getEncryptedData (range : LongRange ): Try <ByteArray , ReadError > {
230268 val cacheStartIndex = _cache .startIndex
231269 ?.takeIf { cacheStart ->
@@ -241,6 +279,13 @@ internal class LcpDecryptor(
241279 _cache .data.copyInto(bytes, 0 , offsetInCache)
242280 it.copyInto(bytes, fromCacheLength)
243281 bytes
282+ }.onSuccess { result ->
283+ if (result.size >= _cache .data.size) {
284+ // cache the three last encrypted blocks that have been read for future use
285+ val cacheStart = result.size - _cache .data.size
286+ _cache .startIndex = (range.last + 1 - _cache .data.size).toInt()
287+ result.copyInto(_cache .data, 0 , cacheStart)
288+ }
244289 }
245290 }
246291
0 commit comments