diff --git a/r2-lcp-swift.xcodeproj/project.pbxproj b/r2-lcp-swift.xcodeproj/project.pbxproj index 98f4c03..9dc67b5 100644 --- a/r2-lcp-swift.xcodeproj/project.pbxproj +++ b/r2-lcp-swift.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -20,6 +20,10 @@ CA4A3892220994E300599297 /* EPUBLicenseContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4A3891220994E300599297 /* EPUBLicenseContainer.swift */; }; CA50B88022B2A777003AFF24 /* R2LCPLocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA50B87F22B2A777003AFF24 /* R2LCPLocalizedString.swift */; }; CA50B88422B2A822003AFF24 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CA50B88622B2A822003AFF24 /* Localizable.strings */; }; + CA5A5E0A25208F1B00CF1CCE /* ReadiumLCP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3B2C8871F667222007601E4 /* ReadiumLCP.framework */; platformFilter = ios; }; + CA5A5E152520908900CF1CCE /* Fixtures in Resources */ = {isa = PBXBuildFile; fileRef = CA5A5E142520908900CF1CCE /* Fixtures */; }; + CA5A5E1B252090C100CF1CCE /* LCPDecryptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA5A5E1A252090C100CF1CCE /* LCPDecryptorTests.swift */; }; + CA5A5E21252091E000CF1CCE /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA5A5E20252091DF00CF1CCE /* Fixtures.swift */; }; CA78C9EB244F53BA00E559B3 /* R2Shared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA78C9E8244F531A00E559B3 /* R2Shared.framework */; }; CA9428FD22B9442300305CDB /* ReadiumLicenseContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9428FC22B9442300305CDB /* ReadiumLicenseContainer.swift */; }; CAB131F6220D81E60097DFB5 /* LicensesRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB131F5220D81E60097DFB5 /* LicensesRepository.swift */; }; @@ -54,6 +58,16 @@ F3B2C8AC1F66727C007601E4 /* Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B2C8A61F66727C007601E4 /* Link.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + CA5A5E0B25208F1B00CF1CCE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F3B2C87E1F667222007601E4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F3B2C8861F667222007601E4; + remoteInfo = "r2-lcp-swift"; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 03A5DB9F23B78AD500D5FCBD /* R2LCPClient.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = R2LCPClient.framework; path = Carthage/Build/iOS/R2LCPClient.framework; sourceTree = ""; }; CA26EF7B2280331E0011653E /* Connection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; @@ -69,6 +83,12 @@ CA4A3891220994E300599297 /* EPUBLicenseContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPUBLicenseContainer.swift; sourceTree = ""; }; CA50B87F22B2A777003AFF24 /* R2LCPLocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = R2LCPLocalizedString.swift; sourceTree = ""; }; CA50B88522B2A822003AFF24 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + CA5A5E0525208F1B00CF1CCE /* readium-lcp-swiftTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "readium-lcp-swiftTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + CA5A5E0925208F1B00CF1CCE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CA5A5E142520908900CF1CCE /* Fixtures */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Fixtures; sourceTree = ""; }; + CA5A5E19252090C100CF1CCE /* readium-lcp-swiftTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "readium-lcp-swiftTests-Bridging-Header.h"; sourceTree = ""; }; + CA5A5E1A252090C100CF1CCE /* LCPDecryptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCPDecryptorTests.swift; sourceTree = ""; }; + CA5A5E20252091DF00CF1CCE /* Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; CA78C9E8244F531A00E559B3 /* R2Shared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = R2Shared.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CA9428FC22B9442300305CDB /* ReadiumLicenseContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadiumLicenseContainer.swift; sourceTree = ""; }; CAB131F5220D81E60097DFB5 /* LicensesRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensesRepository.swift; sourceTree = ""; }; @@ -98,8 +118,6 @@ F3B2C8871F667222007601E4 /* ReadiumLCP.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReadiumLCP.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F3B2C88A1F667223007601E4 /* readium-lcp-swift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "readium-lcp-swift.h"; sourceTree = ""; }; F3B2C88B1F667223007601E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F3B2C8951F667223007601E4 /* readium_lcp_swiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = readium_lcp_swiftTests.swift; sourceTree = ""; }; - F3B2C8971F667223007601E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F3B2C8A11F66727C007601E4 /* LicenseDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LicenseDocument.swift; sourceTree = ""; }; F3B2C8A21F66727C007601E4 /* StatusDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusDocument.swift; sourceTree = ""; }; F3B2C8A41F66727C007601E4 /* Event.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; @@ -108,6 +126,14 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + CA5A5E0225208F1B00CF1CCE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CA5A5E0A25208F1B00CF1CCE /* ReadiumLCP.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F3B2C8831F667222007601E4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -147,6 +173,26 @@ path = Resources; sourceTree = ""; }; + CA5A5E0625208F1B00CF1CCE /* readium-lcp-swiftTests */ = { + isa = PBXGroup; + children = ( + CA5A5E18252090A600CF1CCE /* Content Protection */, + CA5A5E20252091DF00CF1CCE /* Fixtures.swift */, + CA5A5E142520908900CF1CCE /* Fixtures */, + CA5A5E0925208F1B00CF1CCE /* Info.plist */, + CA5A5E19252090C100CF1CCE /* readium-lcp-swiftTests-Bridging-Header.h */, + ); + path = "readium-lcp-swiftTests"; + sourceTree = ""; + }; + CA5A5E18252090A600CF1CCE /* Content Protection */ = { + isa = PBXGroup; + children = ( + CA5A5E1A252090C100CF1CCE /* LCPDecryptorTests.swift */, + ); + path = "Content Protection"; + sourceTree = ""; + }; CAB13214220DB5930097DFB5 /* License */ = { isa = PBXGroup; children = ( @@ -244,7 +290,7 @@ isa = PBXGroup; children = ( F3B2C8891F667222007601E4 /* readium-lcp-swift */, - F3B2C8941F667223007601E4 /* readium-lcp-swiftTests */, + CA5A5E0625208F1B00CF1CCE /* readium-lcp-swiftTests */, F3B2C8881F667222007601E4 /* Products */, F3B2C8AD1F6672E1007601E4 /* Frameworks */, ); @@ -254,6 +300,7 @@ isa = PBXGroup; children = ( F3B2C8871F667222007601E4 /* ReadiumLCP.framework */, + CA5A5E0525208F1B00CF1CCE /* readium-lcp-swiftTests.xctest */, ); name = Products; sourceTree = ""; @@ -277,15 +324,6 @@ path = "readium-lcp-swift"; sourceTree = ""; }; - F3B2C8941F667223007601E4 /* readium-lcp-swiftTests */ = { - isa = PBXGroup; - children = ( - F3B2C8951F667223007601E4 /* readium_lcp_swiftTests.swift */, - F3B2C8971F667223007601E4 /* Info.plist */, - ); - path = "readium-lcp-swiftTests"; - sourceTree = ""; - }; F3B2C8AD1F6672E1007601E4 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -309,6 +347,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + CA5A5E0425208F1B00CF1CCE /* readium-lcp-swiftTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = CA5A5E0D25208F1B00CF1CCE /* Build configuration list for PBXNativeTarget "readium-lcp-swiftTests" */; + buildPhases = ( + CA5A5E0125208F1B00CF1CCE /* Sources */, + CA5A5E0225208F1B00CF1CCE /* Frameworks */, + CA5A5E0325208F1B00CF1CCE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + CA5A5E0C25208F1B00CF1CCE /* PBXTargetDependency */, + ); + name = "readium-lcp-swiftTests"; + productName = "readium-lcp-swiftTests"; + productReference = CA5A5E0525208F1B00CF1CCE /* readium-lcp-swiftTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; F3B2C8861F667222007601E4 /* r2-lcp-swift */ = { isa = PBXNativeTarget; buildConfigurationList = F3B2C89B1F667223007601E4 /* Build configuration list for PBXNativeTarget "r2-lcp-swift" */; @@ -333,10 +389,16 @@ F3B2C87E1F667222007601E4 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0830; + LastSwiftUpdateCheck = 1200; LastUpgradeCheck = 1020; ORGANIZATIONNAME = Readium; TargetAttributes = { + CA5A5E0425208F1B00CF1CCE = { + CreatedOnToolsVersion = 12.0; + DevelopmentTeam = 327YA3JNGT; + LastSwiftMigration = 1200; + ProvisioningStyle = Automatic; + }; F3B2C8861F667222007601E4 = { CreatedOnToolsVersion = 8.3.3; LastSwiftMigration = 1010; @@ -358,11 +420,20 @@ projectRoot = ""; targets = ( F3B2C8861F667222007601E4 /* r2-lcp-swift */, + CA5A5E0425208F1B00CF1CCE /* readium-lcp-swiftTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + CA5A5E0325208F1B00CF1CCE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CA5A5E152520908900CF1CCE /* Fixtures in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F3B2C8851F667222007601E4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -375,6 +446,15 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + CA5A5E0125208F1B00CF1CCE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CA5A5E1B252090C100CF1CCE /* LCPDecryptorTests.swift in Sources */, + CA5A5E21252091E000CF1CCE /* Fixtures.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F3B2C8821F667222007601E4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -425,6 +505,15 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + CA5A5E0C25208F1B00CF1CCE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + platformFilter = ios; + target = F3B2C8861F667222007601E4 /* r2-lcp-swift */; + targetProxy = CA5A5E0B25208F1B00CF1CCE /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ CA50B88622B2A822003AFF24 /* Localizable.strings */ = { isa = PBXVariantGroup; @@ -437,6 +526,62 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + CA5A5E0E25208F1B00CF1CCE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 327YA3JNGT; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "readium-lcp-swiftTests/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "org.readium.readium-lcp-swift.readium-lcp-swiftTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "readium-lcp-swiftTests/readium-lcp-swiftTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + CA5A5E0F25208F1B00CF1CCE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 327YA3JNGT; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "readium-lcp-swiftTests/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "org.readium.readium-lcp-swift.readium-lcp-swiftTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "readium-lcp-swiftTests/readium-lcp-swiftTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; F3B2C8991F667223007601E4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -549,7 +694,8 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -578,7 +724,12 @@ INFOPLIST_FILE = "readium-lcp-swift/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(PROJECT_DIR)/Carthage/Build/iOS"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", @@ -611,7 +762,12 @@ INFOPLIST_FILE = "readium-lcp-swift/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(PROJECT_DIR)/Carthage/Build/iOS"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", @@ -628,6 +784,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + CA5A5E0D25208F1B00CF1CCE /* Build configuration list for PBXNativeTarget "readium-lcp-swiftTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CA5A5E0E25208F1B00CF1CCE /* Debug */, + CA5A5E0F25208F1B00CF1CCE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F3B2C8811F667222007601E4 /* Build configuration list for PBXProject "r2-lcp-swift" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/readium-lcp-swift/Content Protection/LCPDecryptor.swift b/readium-lcp-swift/Content Protection/LCPDecryptor.swift index bb386fe..b8e6262 100644 --- a/readium-lcp-swift/Content Protection/LCPDecryptor.swift +++ b/readium-lcp-swift/Content Protection/LCPDecryptor.swift @@ -23,6 +23,7 @@ final class LCPDecryptor { enum Error: Swift.Error { case emptyDecryptedData case invalidCBCData + case invalidRange(Range) case inflateFailed } @@ -102,9 +103,11 @@ final class LCPDecryptor { throw LCPDecryptor.Error.emptyDecryptedData } + let paddingSize = UInt64(data.last ?? 0) + return length - AESBlockSize // Minus IV or previous block - - (AESBlockSize - UInt64(data.count)) % AESBlockSize // Minus padding part + - paddingSize // Minus padding part } } }() @@ -114,43 +117,36 @@ final class LCPDecryptor { return license.decryptFully(data: resource.read(), isDeflated: resource.link.isDeflated) } - return resource.length.tryFlatMap { totalLength in - let length = range.upperBound - range.lowerBound - let blockPosition = range.lowerBound % AESBlockSize - - // For beginning of the cipher text, IV used for XOR. - // For cipher text in the middle, previous block used for XOR. - let readPosition = range.lowerBound - blockPosition - - // Count blocks to read. - // First block for IV or previous block to perform XOR. - var blocksCount: UInt64 = 1 - var bytesInFirstBlock = (AESBlockSize - blockPosition) % AESBlockSize - if (length < bytesInFirstBlock) { - bytesInFirstBlock = 0 - } - if (bytesInFirstBlock > 0) { - blocksCount += 1 + return resource.length.tryFlatMap { encryptedLength in + guard let rangeFirst = range.first, let rangeLast = range.last else { + throw LCPDecryptor.Error.invalidRange(range) } - blocksCount += (length - bytesInFirstBlock) / AESBlockSize - if (length - bytesInFirstBlock) % AESBlockSize != 0 { - blocksCount += 1 - } - - let readSize = blocksCount * AESBlockSize - - return resource.read(range: readPosition..<(readPosition + readSize)) - .tryMap { encryptedData in - guard var data = try license.decipher(encryptedData) else { - throw LCPDecryptor.Error.emptyDecryptedData - } - - if (data.count > length) { - data = data[0.. UInt64 { + divisor * (self / divisor + ((self % divisor == 0) ? 0 : 1)) + } + + func floorMultiple(of divisor: UInt64) -> UInt64 { + divisor * (self / divisor) + } + +} diff --git a/readium-lcp-swift/LCPAuthenticating.swift b/readium-lcp-swift/LCPAuthenticating.swift index e3b498b..eb6f1e4 100644 --- a/readium-lcp-swift/LCPAuthenticating.swift +++ b/readium-lcp-swift/LCPAuthenticating.swift @@ -75,3 +75,36 @@ public struct LCPAuthenticatedLicense { } } + +/// An `LCPAuthenticating` implementation which can directly use a provided clear or hashed +/// passphrase. +/// +/// If the provided `passphrase` is incorrect, the given `fallback` authentication is used. +public class LCPPassphrase: LCPAuthenticating { + + private let passphrase: String + private let fallback: LCPAuthenticating? + + public init(_ passphrase: String, fallback: LCPAuthenticating? = nil) { + self.passphrase = passphrase + self.fallback = fallback + } + + public var requiresUserInteraction: Bool { + fallback?.requiresUserInteraction ?? false + } + + public func requestPassphrase(for license: LCPAuthenticatedLicense, reason: LCPAuthenticationReason, sender: Any?, completion: @escaping (String?) -> Void) { + guard reason == .passphraseNotFound else { + if let fallback = fallback { + fallback.requestPassphrase(for: license, reason: reason, sender: sender, completion: completion) + } else { + completion(nil) + } + return + } + + completion(passphrase) + } + +} diff --git a/readium-lcp-swift/License/Model/Components/Link.swift b/readium-lcp-swift/License/Model/Components/Link.swift index 4357da5..7a077b4 100644 --- a/readium-lcp-swift/License/Model/Components/Link.swift +++ b/readium-lcp-swift/License/Model/Components/Link.swift @@ -56,23 +56,14 @@ public struct Link { /// Gets the valid URL if possible, applying the given template context as query parameters if the link is templated. /// eg. http://url{?id,name} + [id: x, name: y] -> http://url?id=x&name=y - func url(with parameters: [String: CustomStringConvertible]) -> URL? { - if !templated { - return URL(string: href) - } - - let urlString = href.replacingOccurrences(of: "\\{\\?.+?\\}", with: "", options: [.regularExpression]) - - guard var urlBuilder = URLComponents(string: urlString) else { - return nil - } + func url(with parameters: [String: LosslessStringConvertible]) -> URL? { + var href = self.href - // Add the template context as query parameters - urlBuilder.queryItems = parameters.map { param in - URLQueryItem(name: param.key, value: param.value.description) + if templated { + href = URITemplate(href).expand(with: parameters.mapValues { String(describing: $0) }) } - return urlBuilder.url + return URL(string: href) } /// Expands the href without any template context. diff --git a/readium-lcp-swift/License/Model/LicenseDocument.swift b/readium-lcp-swift/License/Model/LicenseDocument.swift index 946c270..d8dbcf0 100644 --- a/readium-lcp-swift/License/Model/LicenseDocument.swift +++ b/readium-lcp-swift/License/Model/LicenseDocument.swift @@ -101,7 +101,7 @@ public struct LicenseDocument { /// Gets and expands the URL for the given rel, if it exits. /// - Throws: `LCPError.invalidLink` if the URL can't be built. - func url(for rel: Rel, with parameters: [String: CustomStringConvertible] = [:]) throws -> URL { + func url(for rel: Rel, with parameters: [String: LosslessStringConvertible] = [:]) throws -> URL { guard let url = link(for: rel)?.url(with: parameters) else { throw ParsingError.url(rel: rel.rawValue) } diff --git a/readium-lcp-swift/License/Model/StatusDocument.swift b/readium-lcp-swift/License/Model/StatusDocument.swift index 11b392e..ec4c401 100644 --- a/readium-lcp-swift/License/Model/StatusDocument.swift +++ b/readium-lcp-swift/License/Model/StatusDocument.swift @@ -101,7 +101,7 @@ public struct StatusDocument { /// Gets and expands the URL for the given rel, if it exits. /// - Throws: `LCPError.invalidLink` if the URL can't be built. - func url(for rel: Rel, with parameters: [String: CustomStringConvertible] = [:]) throws -> URL { + func url(for rel: Rel, with parameters: [String: LosslessStringConvertible] = [:]) throws -> URL { guard let url = link(for: rel)?.url(with: parameters) else { throw ParsingError.url(rel: rel.rawValue) } diff --git a/readium-lcp-swiftTests/Content Protection/LCPDecryptorTests.swift b/readium-lcp-swiftTests/Content Protection/LCPDecryptorTests.swift new file mode 100644 index 0000000..d704eb6 --- /dev/null +++ b/readium-lcp-swiftTests/Content Protection/LCPDecryptorTests.swift @@ -0,0 +1,83 @@ +// +// 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. +// + +import XCTest +import R2Shared +import PDFKit +@testable import ReadiumLCP + +class LCPDecryptorTests: XCTestCase { + + let fixtures = Fixtures() + var service: LCPService! + var encryptedResource: Resource! + var clearData: Data! + + override func setUpWithError() throws { + service = R2MakeLCPService() + + let fetcher = ArchiveFetcher(archive: try DefaultArchiveFactory().open(url: self.fixtures.url(for: "daisy.lcpdf"), password: nil)) + encryptedResource = fetcher.get(Link( + href: "/publication.pdf", + properties: Properties([ + "encrypted": [ + "scheme": "http://readium.org/2014/01/lcp", + "profile": "http://readium.org/lcp/basic-profile", + "algorithm": "http://www.w3.org/2001/04/xmlenc#aes256-cbc" + ] + ]) + )) + + clearData = try Data(contentsOf: fixtures.url(for: "daisy.pdf")) + } + + /// Checks that we can decrypt the full content successfully. + func testDecryptFull() throws { + retrieveLicense(path: "daisy.lcpdf", passphrase: "test") { license in + let decryptedResource = LCPDecryptor(license: license).decrypt(resource: self.encryptedResource) + + XCTAssertEqual(try decryptedResource.read().get(), self.clearData) + } + } + + /// Checks that we can decrypt various ranges successfully. + func testDecryptRanges() throws { + retrieveLicense(path: "daisy.lcpdf", passphrase: "test") { license in + let decryptedResource = LCPDecryptor(license: license).decrypt(resource: self.encryptedResource) + + // These ranges seem arbirtrary, but some of them were failing before the fix in the + // same commit. + let ranges: [Range] = [ + 0..<2048, // 2048 + 817152..<819200, // 2048 + 819200..<819856, // 656 + 0..<16384, // 16384 + 819792..<819856, // 64 + 819565..<819856 // 291 + ] + + for range in ranges { + let intRange = Int(range.lowerBound).. Void) { + let completionExpectation = expectation(description: "License opened") + + let url = fixtures.url(for: path) + service.retrieveLicense(from: url, authentication: LCPPassphrase(passphrase)) { result in + try! completion((try! result.get())!) + completionExpectation.fulfill() + } + + waitForExpectations(timeout: 10, handler: nil) + } + +} diff --git a/readium-lcp-swiftTests/Fixtures.swift b/readium-lcp-swiftTests/Fixtures.swift new file mode 100644 index 0000000..094869b --- /dev/null +++ b/readium-lcp-swiftTests/Fixtures.swift @@ -0,0 +1,32 @@ +// +// 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. +// + +import Foundation +import XCTest + +class Fixtures { + + let path: String? + + init(path: String? = nil) { + self.path = path + } + + private lazy var bundle = Bundle(for: type(of: self)) + + func url(for filepath: String) -> URL { + return try! XCTUnwrap(bundle.resourceURL?.appendingPathComponent("Fixtures/\(path ?? "")/\(filepath)")) + } + + func data(at filepath: String) -> Data { + return try! XCTUnwrap(try? Data(contentsOf: url(for: filepath))) + } + + func json(at filepath: String) -> T { + return try! XCTUnwrap(JSONSerialization.jsonObject(with: data(at: filepath)) as? T) + } + +} diff --git a/readium-lcp-swiftTests/Fixtures/daisy.lcpdf b/readium-lcp-swiftTests/Fixtures/daisy.lcpdf new file mode 100644 index 0000000..d9d9525 Binary files /dev/null and b/readium-lcp-swiftTests/Fixtures/daisy.lcpdf differ diff --git a/readium-lcp-swiftTests/Fixtures/daisy.pdf b/readium-lcp-swiftTests/Fixtures/daisy.pdf new file mode 100644 index 0000000..0f3a129 Binary files /dev/null and b/readium-lcp-swiftTests/Fixtures/daisy.pdf differ diff --git a/readium-lcp-swiftTests/Info.plist b/readium-lcp-swiftTests/Info.plist index 6c6c23c..64d65ca 100644 --- a/readium-lcp-swiftTests/Info.plist +++ b/readium-lcp-swiftTests/Info.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -13,7 +13,7 @@ CFBundleName $(PRODUCT_NAME) CFBundlePackageType - BNDL + $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion diff --git a/readium-lcp-swiftTests/readium-lcp-swiftTests-Bridging-Header.h b/readium-lcp-swiftTests/readium-lcp-swiftTests-Bridging-Header.h new file mode 100644 index 0000000..1b2cb5d --- /dev/null +++ b/readium-lcp-swiftTests/readium-lcp-swiftTests-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/readium-lcp-swiftTests/readium_lcp_swiftTests.swift b/readium-lcp-swiftTests/readium_lcp_swiftTests.swift deleted file mode 100644 index 66d6d20..0000000 --- a/readium-lcp-swiftTests/readium_lcp_swiftTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// readium_lcp_swiftTests.swift -// readium-lcp-swiftTests -// -// Created by Alexandre Camilleri on 9/11/17. -// Copyright © 2017 Readium. All rights reserved. -// - -import XCTest -@testable import readium_lcp_swift - -class readium_lcp_swiftTests: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -}