Skip to content
Draft
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
20 changes: 10 additions & 10 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/xtool-org/xtool-core", .upToNextMinor(from: "1.5.1")),
.package(url: "https://github.com/xtool-org/SwiftyMobileDevice", .upToNextMinor(from: "1.7.0")),
.package(url: "https://github.com/xtool-org/zsign", .upToNextMinor(from: "1.9.0")),
.package(url: "https://github.com/xtool-org/swift-rcodesign", .upToNextMinor(from: "0.2.0")),

.package(url: "https://github.com/apple/swift-system", from: "1.4.0"),
.package(url: "https://github.com/apple/swift-http-types", from: "1.3.1"),
Expand Down Expand Up @@ -132,8 +132,7 @@ let package = Package(
.product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"),
.product(name: "Dependencies", package: "swift-dependencies"),
.product(name: "SwiftyMobileDevice", package: "SwiftyMobileDevice"),
.product(name: "Zupersign", package: "zsign"),
.product(name: "SignerSupport", package: "xtool-core"),
.product(name: "RCodesign", package: "swift-rcodesign"),
.product(name: "ProtoCodable", package: "xtool-core"),
.product(name: "Superutils", package: "xtool-core"),
.product(name: "Crypto", package: "swift-crypto"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@

import Foundation
import DeveloperAPI
import Dependencies

public struct DeveloperServicesAddAppOperation: DeveloperServicesOperation {

@Dependency(\.signer) private var signer

public enum Error: LocalizedError {
case invalidApp(URL)

Expand Down Expand Up @@ -172,7 +175,7 @@ public struct DeveloperServicesAddAppOperation: DeveloperServicesOperation {
var entitlements: Entitlements
// if the executable doesn't already have entitlements, that's
// okay. We don't have to throw an error.
if let entitlementsData = try? await context.signer.analyze(executable: executableURL),
if let entitlementsData = try? await signer.analyze(executable: executableURL),
let decodedEntitlements = try? PropertyListDecoder().decode(Entitlements.self, from: entitlementsData) {
entitlements = decodedEntitlements

Expand Down
2 changes: 1 addition & 1 deletion Sources/XKit/Integration/IntegratedInstaller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ public actor IntegratedInstaller {
app: URL,
targetDevice: SigningContext.TargetDevice? = nil,
) async throws -> String {
let context = try SigningContext(
let context = SigningContext(
auth: auth,
targetDevice: targetDevice,
)
Expand Down
13 changes: 8 additions & 5 deletions Sources/XKit/Signer/AutoSigner.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import Foundation
import Dependencies

/// Provisions and codesigns.
public struct AutoSigner {

@Dependency(\.signer) private var signer

public enum Error: LocalizedError {
case noSigners
case errorReading(String)
case errorWriting(String)

public var errorDescription: String? {
switch self {
case .noSigners:
return NSLocalizedString("signer.error.no_signers", value: "No signers found", comment: "")
case .errorReading(let file):
return "\(file)".withCString {
String.localizedStringWithFormat(
Expand Down Expand Up @@ -82,13 +82,16 @@ public struct AutoSigner {
try info.mobileprovision.data().write(to: profURL)
}

let entitlements = provisioningDict.mapValues(\.entitlements)
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
let entitlements = try provisioningDict.mapValues { try encoder.encode($0.entitlements) }

status(NSLocalizedString("signer.signing", value: "Signing", comment: ""))
try await context.signer.sign(
try await signer.sign(
app: app,
identity: .real(signingInfo.certificate, signingInfo.privateKey),
entitlementMapping: entitlements,
deep: true,
progress: progress
)
progress(1)
Expand Down
57 changes: 57 additions & 0 deletions Sources/XKit/Signer/RCodesignSigner.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Foundation
import RCodesign

public actor RCodesignSigner: Signer {

public enum Error: LocalizedError {
case badFilePath

public var errorDescription: String? {
switch self {
case .badFilePath:
return NSLocalizedString(
"signer_impl.error.bad_file_path", value: "A bad file path was provided", comment: ""
)
}
}
}

public init() {}

public func sign(
app: URL,
identity: SignerIdentity,
entitlementMapping: [URL: Data],
deep: Bool,
progress: @escaping @Sendable (Double?) -> Void
) throws {
progress(nil)
guard app.isFileURL else { throw Error.badFilePath }
let root = app.standardizedFileURL.pathComponents
var entitlements: [String: Data] = [:]
for (bundle, value) in entitlementMapping {
let components = bundle.standardizedFileURL.pathComponents
guard bundle.isFileURL, components.starts(with: root) else { throw Error.badFilePath }
// RCodesign scopes entitlements by bundle-relative path: "" for the
// app itself and e.g. "PlugIns/Widget.appex" for an extension. Compare
// path components so a sibling such as App.app-copy cannot match App.app.
let path = components.dropFirst(root.count).joined(separator: "/")
guard entitlements[path] == nil else { throw Error.badFilePath }
entitlements[path] = value
}

let signingIdentity: RCodesign.Identity
switch identity {
case .real(let certificate, let privateKey):
signingIdentity = .certificate(certificateDER: try certificate.data(), privateKeyPEM: privateKey.data)
case .adhoc:
signingIdentity = .adHoc
}
try RCodesign.sign(at: app, identity: signingIdentity, options: .init(entitlements: entitlements, shallow: !deep))
}

public func analyze(executable: URL) throws -> Data? {
try RCodesign.entitlements(at: executable)
}

}
50 changes: 50 additions & 0 deletions Sources/XKit/Signer/Signer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Foundation
import Dependencies

public enum SignerIdentity: Sendable {
case real(Certificate, PrivateKey)
case adhoc
}

public protocol Signer: Sendable {
// entitlementMapping is [bundle url] -> [plist data]
func sign(
app: URL,
identity: SignerIdentity,
entitlementMapping: [URL: Data],
deep: Bool,
progress: @escaping @Sendable (Double?) -> Void
) async throws

func analyze(executable: URL) async throws -> Data?
}

private enum SignerDependencyKey: DependencyKey {
static var liveValue: any Signer { RCodesignSigner() }
static var testValue: any Signer { UnimplementedSigner() }
}

extension DependencyValues {
public var signer: any Signer {
get { self[SignerDependencyKey.self] }
set { self[SignerDependencyKey.self] = newValue }
}
}

private struct UnimplementedSigner: Signer {
func sign(
app: URL,
identity: SignerIdentity,
entitlementMapping: [URL: Data],
deep: Bool,
progress: @escaping @Sendable (Double?) -> Void
) async throws {
let operation: () throws -> Void = unimplemented()
try operation()
}

func analyze(executable: URL) async throws -> Data? {
let operation: () throws -> Data? = unimplemented()
return try operation()
}
}
Loading
Loading