-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f0b8d92
commit 4e213ef
Showing
2 changed files
with
87 additions
and
113 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,84 +38,35 @@ public struct PassBuilder: Sendable { | |
self.openSSLURL = URL(fileURLWithPath: openSSLPath) | ||
} | ||
|
||
private static func sourceFiles(in directory: URL, isPersonalized: Bool = false) throws -> [String: Data] { | ||
var files: [String: Data] = [:] | ||
|
||
let paths = try FileManager.default.subpathsOfDirectory(atPath: directory.path) | ||
|
||
if isPersonalized { | ||
guard | ||
paths.contains("personalizationLogo.png") | ||
|| paths.contains("[email protected]") | ||
|| paths.contains("[email protected]") | ||
|| paths.contains("[email protected]") | ||
else { | ||
throw WalletPassesError.noPersonalizationLogo | ||
} | ||
} | ||
|
||
guard | ||
paths.contains("icon.png") | ||
|| paths.contains("[email protected]") | ||
|| paths.contains("[email protected]") | ||
|| paths.contains("[email protected]") | ||
else { | ||
throw WalletPassesError.noIcon | ||
} | ||
|
||
for relativePath in paths { | ||
let file = URL(fileURLWithPath: relativePath, relativeTo: directory) | ||
guard !file.hasDirectoryPath else { | ||
continue | ||
} | ||
|
||
guard !(file.lastPathComponent == ".gitkeep" || file.lastPathComponent == ".DS_Store") else { | ||
continue | ||
} | ||
|
||
files[relativePath] = try Data(contentsOf: file) | ||
} | ||
|
||
return files | ||
} | ||
|
||
private func manifest(for sourceFiles: [String: Data]) throws -> Data { | ||
let manifest = sourceFiles.mapValues { data in | ||
Insecure.SHA1.hash(data: data).map { "0\(String($0, radix: 16))".suffix(2) }.joined() | ||
} | ||
|
||
return try self.encoder.encode(manifest) | ||
} | ||
|
||
/// Generates a signature for a given manifest or personalization token. | ||
/// Generates a signature for a given personalization token. | ||
/// | ||
/// - Parameter manifest: The manifest or personalization token data to sign. | ||
/// - Parameter data: The personalization token data to sign. | ||
/// | ||
/// - Returns: The generated signature as `Data`. | ||
public func signature(for manifest: Data) throws -> Data { | ||
public func signature(for data: Data) throws -> Data { | ||
// Swift Crypto doesn't support encrypted PEM private keys, so we have to use OpenSSL for that. | ||
if let pemPrivateKeyPassword { | ||
guard FileManager.default.fileExists(atPath: self.openSSLURL.path) else { | ||
throw WalletPassesError.noOpenSSLExecutable | ||
} | ||
|
||
let dir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) | ||
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) | ||
defer { try? FileManager.default.removeItem(at: dir) } | ||
let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) | ||
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) | ||
defer { try? FileManager.default.removeItem(at: tempDir) } | ||
|
||
let manifestURL = dir.appendingPathComponent(Self.manifestFileName) | ||
let wwdrURL = dir.appendingPathComponent("wwdr.pem") | ||
let certificateURL = dir.appendingPathComponent("certificate.pem") | ||
let privateKeyURL = dir.appendingPathComponent("private.pem") | ||
let signatureURL = dir.appendingPathComponent(Self.signatureFileName) | ||
let manifestURL = tempDir.appendingPathComponent(Self.manifestFileName) | ||
let wwdrURL = tempDir.appendingPathComponent("wwdr.pem") | ||
let certificateURL = tempDir.appendingPathComponent("certificate.pem") | ||
let privateKeyURL = tempDir.appendingPathComponent("private.pem") | ||
let signatureURL = tempDir.appendingPathComponent(Self.signatureFileName) | ||
|
||
try manifest.write(to: manifestURL) | ||
try data.write(to: manifestURL) | ||
try self.pemWWDRCertificate.write(to: wwdrURL, atomically: true, encoding: .utf8) | ||
try self.pemCertificate.write(to: certificateURL, atomically: true, encoding: .utf8) | ||
try self.pemPrivateKey.write(to: privateKeyURL, atomically: true, encoding: .utf8) | ||
|
||
let process = Process() | ||
process.currentDirectoryURL = dir | ||
process.currentDirectoryURL = tempDir | ||
process.executableURL = self.openSSLURL | ||
process.arguments = [ | ||
"smime", "-binary", "-sign", | ||
|
@@ -133,7 +84,7 @@ public struct PassBuilder: Sendable { | |
return try Data(contentsOf: signatureURL) | ||
} else { | ||
let signature = try CMS.sign( | ||
manifest, | ||
data, | ||
signatureAlgorithm: .sha256WithRSAEncryption, | ||
additionalIntermediateCertificates: [ | ||
Certificate(pemEncoded: self.pemWWDRCertificate) | ||
|
@@ -183,16 +134,52 @@ public struct PassBuilder: Sendable { | |
archiveFiles.append(ArchiveFile(filename: "personalization.json", data: personalizationJSONData)) | ||
} | ||
|
||
let sourceFiles = try Self.sourceFiles(in: tempDir, isPersonalized: personalization != nil) | ||
let sourceFilesPaths = try FileManager.default.subpathsOfDirectory(atPath: tempDir.path) | ||
|
||
if personalization != nil { | ||
guard | ||
sourceFilesPaths.contains("personalizationLogo.png") | ||
|| sourceFilesPaths.contains("[email protected]") | ||
|| sourceFilesPaths.contains("[email protected]") | ||
|| sourceFilesPaths.contains("[email protected]") | ||
else { | ||
throw WalletPassesError.noPersonalizationLogo | ||
} | ||
} | ||
|
||
guard | ||
sourceFilesPaths.contains("icon.png") | ||
|| sourceFilesPaths.contains("[email protected]") | ||
|| sourceFilesPaths.contains("[email protected]") | ||
|| sourceFilesPaths.contains("[email protected]") | ||
else { | ||
throw WalletPassesError.noIcon | ||
} | ||
|
||
var manifestJSON: [String: String] = [:] | ||
|
||
for relativePath in sourceFilesPaths { | ||
let fileURL = URL(fileURLWithPath: relativePath, relativeTo: tempDir) | ||
|
||
guard !fileURL.hasDirectoryPath else { | ||
continue | ||
} | ||
|
||
guard !(fileURL.lastPathComponent == ".gitkeep" || fileURL.lastPathComponent == ".DS_Store") else { | ||
continue | ||
} | ||
|
||
let fileData = try Data(contentsOf: fileURL) | ||
|
||
let manifest = try self.manifest(for: sourceFiles) | ||
archiveFiles.append(ArchiveFile(filename: Self.manifestFileName, data: manifest)) | ||
try archiveFiles.append(ArchiveFile(filename: Self.signatureFileName, data: self.signature(for: manifest))) | ||
archiveFiles.append(ArchiveFile(filename: relativePath, data: fileData)) | ||
|
||
for file in sourceFiles { | ||
archiveFiles.append(ArchiveFile(filename: file.key, data: file.value)) | ||
manifestJSON[relativePath] = Insecure.SHA1.hash(data: fileData).map { "0\(String($0, radix: 16))".suffix(2) }.joined() | ||
} | ||
|
||
let manifestData = try self.encoder.encode(manifestJSON) | ||
archiveFiles.append(ArchiveFile(filename: Self.manifestFileName, data: manifestData)) | ||
try archiveFiles.append(ArchiveFile(filename: Self.signatureFileName, data: self.signature(for: manifestData))) | ||
|
||
let zipFile = tempDir.appendingPathComponent("\(UUID().uuidString).pkpass") | ||
try Zip.zipData(archiveFiles: archiveFiles, zipFilePath: zipFile) | ||
return try Data(contentsOf: zipFile) | ||
|