diff --git a/CHANGELOG.md b/CHANGELOG.md index 2955e69ea..455406384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,12 @@ All notable changes to this project will be documented in this file. Take a look * Support for asynchronous callbacks with `onCreatePublication` (contributed by [@smoores-dev](https://github.com/readium/swift-toolkit/pull/673)). +### Fixed + +#### LCP + +* Fixed crash when an EPUB resource is declared as LCP-encrypted in the manifest but contains unencrypted data. + ## [3.5.0] diff --git a/Sources/LCP/Content Protection/LCPDecryptor.swift b/Sources/LCP/Content Protection/LCPDecryptor.swift index 7025ba84a..79846f525 100644 --- a/Sources/LCP/Content Protection/LCPDecryptor.swift +++ b/Sources/LCP/Content Protection/LCPDecryptor.swift @@ -9,7 +9,6 @@ import ReadiumInternal import ReadiumShared private let lcpScheme = "http://readium.org/2014/01/lcp" -private let AESBlockSize: UInt64 = 16 // bytes /// Decrypts a resource protected with LCP. final class LCPDecryptor { @@ -117,7 +116,7 @@ final class LCPDecryptor { guard let length = length else { return failure(.requiredEstimatedLength) } - guard length >= 2 * AESBlockSize else { + guard length.isValidAESChunk else { return failure(.invalidCBCData) } @@ -207,6 +206,10 @@ final class LCPDecryptor { private extension LCPLicense { func decryptFully(data: ReadResult, isDeflated: Bool) async -> ReadResult { data.flatMap { + guard UInt64($0.count).isValidAESChunk else { + return .failure(.decoding(LCPDecryptor.Error.invalidCBCData)) + } + do { // Decrypts the resource. guard var data = try self.decipher($0) else { @@ -242,3 +245,15 @@ private extension ReadiumShared.Encryption { algorithm == "http://www.w3.org/2001/04/xmlenc#aes256-cbc" } } + +private let AESBlockSize: UInt64 = 16 // bytes + +private extension UInt64 { + /// Checks if this number is a valid CBC length - i.e. a multiple of AES + /// block size and at least 2 blocks (IV + data). + /// If not, the file is likely not actually encrypted despite being declared + /// as such. + var isValidAESChunk: Bool { + self >= 2 * AESBlockSize && self % AESBlockSize == 0 + } +}