Skip to content

Commit

Permalink
Patch old code signature revisions as fallback (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
majd authored Jul 3, 2022
1 parent 07a0f33 commit ff43c07
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 4 deletions.
28 changes: 26 additions & 2 deletions Sources/CLI/Commands/Download.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ extension Download {
"The country provided does not match with the account you are using.",
"Supply a valid country using the \"--country\" flag."
].joined(separator: " "), level: .error)
case StoreResponse.Error.passwordTokenExpired:
case StoreResponse.Error.passwordTokenExpired, StoreResponse.Error.passwordChanged:
logger.log("Token expired. Login again using the \"auth\" command.", level: .error)
default:
logger.log("An unknown error has occurred.", level: .error)
Expand Down Expand Up @@ -164,7 +164,7 @@ extension Download {
"The country provided does not match with the account you are using.",
"Supply a valid country using the \"--country\" flag."
].joined(separator: " "), level: .error)
case StoreResponse.Error.passwordTokenExpired:
case StoreResponse.Error.passwordTokenExpired, StoreResponse.Error.passwordChanged:
logger.log("Token expired. Login again using the \"auth\" command.", level: .error)
default:
logger.log([
Expand Down Expand Up @@ -203,6 +203,30 @@ extension Download {
logger.log("Applying patches...", level: .info)
try signatureClient.appendMetadata(item: item, email: email)
try signatureClient.appendSignature(item: item)
} catch {
switch error {
case SignatureClient.Error.fileNotFound:
logger.log(
"App uses old code signature version, falling back to alternative patching mechanism.",
level: .debug
)
logger.log("The produced app package may not be compatible with modern iOS releases.", level: .info)
applyOldPatches(item: item, email: email, path: path)
default:
logger.log("\(error)", level: .debug)
logger.log("Failed to apply patches. The ipa file will be left incomplete.", level: .error)
_exit(1)
}
}
}

private mutating func applyOldPatches(item: StoreResponse.Item, email: String, path: String) {
logger.log("Creating signature client...", level: .debug)
let signatureClient = SignatureClient(fileManager: .default, filePath: path)

do {
logger.log("Applying fallback patches...", level: .info)
try signatureClient.appendOldSignature(item: item)
} catch {
logger.log("\(error)", level: .debug)
logger.log("Failed to apply patches. The ipa file will be left incomplete.", level: .error)
Expand Down
2 changes: 1 addition & 1 deletion Sources/CLI/Commands/Purchase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ extension Purchase {
"The country provided does not match with the account you are using.",
"Supply a valid country using the \"--country\" flag."
].joined(separator: " "), level: .error)
case StoreResponse.Error.passwordTokenExpired:
case StoreResponse.Error.passwordTokenExpired, StoreResponse.Error.passwordChanged:
logger.log("Token expired. Login again using the \"auth\" command.", level: .error)
default:
logger.log("An unknown error has occurred.", level: .error)
Expand Down
60 changes: 59 additions & 1 deletion Sources/StoreAPI/Signature/SignatureClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ public final class SignatureClient: SignatureClientInterface {
throw Error.invalidArchive
}

try appendSignatureFromManfiest(forItem: item, inArchive: archive)
}

public func appendOldSignature(item: StoreResponse.Item) throws {
guard let archive = Archive(url: URL(fileURLWithPath: filePath), accessMode: .update) else {
throw Error.invalidArchive
}

try appendOldSignature(forItem: item, inArchive: archive)
}

private func appendSignatureFromManfiest(forItem item: StoreResponse.Item, inArchive archive: Archive) throws {
let manifest = try readPlist(
archive: archive,
matchingSuffix: ".app/SC_Info/Manifest.plist",
Expand Down Expand Up @@ -87,6 +99,52 @@ public final class SignatureClient: SignatureClientInterface {
try fileManager.removeItem(at: signatureBaseUrl)
}

private func appendOldSignature(forItem item: StoreResponse.Item, inArchive archive: Archive) throws {
guard let infoEntry = archive.first(where: { $0.path.hasSuffix(".app/Info.plist") }) else {
throw Error.invalidAppBundle
}

let temporaryInfoURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
_ = try archive.extract(infoEntry, to: temporaryInfoURL, skipCRC32: true)
let infoData = try Data(contentsOf: temporaryInfoURL)

guard let infoPlist = try PropertyListSerialization.propertyList(
from: infoData,
format: nil
) as? [String: Any] else {
throw Error.invalidAppBundle
}

guard let executableName = infoPlist["CFBundleExecutable"] as? String else {
throw Error.invalidAppBundle
}

let appBundleName = URL(fileURLWithPath: infoEntry.path)
.deletingLastPathComponent()
.deletingPathExtension()
.lastPathComponent

guard let signatureItem = item.signatures.first(where: { $0.id == 0 }) else {
throw Error.invalidSignature
}

let signatureBaseUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
let signatureUrl = signatureBaseUrl
.appendingPathComponent("Payload")
.appendingPathComponent(appBundleName)
.appendingPathExtension("app")
.appendingPathComponent("SC_Info")
.appendingPathComponent(executableName)
.appendingPathExtension("sinf")

let signatureRelativePath = signatureUrl.path.replacingOccurrences(of: "\(signatureBaseUrl.path)/", with: "")

try fileManager.createDirectory(at: signatureUrl.deletingLastPathComponent(), withIntermediateDirectories: true)
try signatureItem.sinf.write(to: signatureUrl)
try archive.addEntry(with: signatureRelativePath, relativeTo: signatureBaseUrl)
try fileManager.removeItem(at: signatureBaseUrl)
}

private func readPlist<T: Decodable>(archive: Archive, matchingSuffix: String, type: T.Type) throws -> T {
guard let entry = archive.first(where: { $0.path.hasSuffix(matchingSuffix) }) else {
throw Error.fileNotFound(matchingSuffix)
Expand Down Expand Up @@ -115,7 +173,7 @@ extension SignatureClient {
}
}

enum Error: Swift.Error {
public enum Error: Swift.Error {
case invalidArchive
case invalidAppBundle
case invalidSignature
Expand Down
1 change: 1 addition & 0 deletions Sources/StoreAPI/Store/StoreResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public enum StoreResponse {

public enum Error: Int, Swift.Error {
case unknownError = 0
case passwordChanged = 2002
case genericError = 5002
case codeRequired = 1
case invalidLicense = 9610
Expand Down

0 comments on commit ff43c07

Please sign in to comment.