From 4d7b9f2c17d6052134ffc27adb8b094fd32cc082 Mon Sep 17 00:00:00 2001 From: Stefan Rinner Date: Mon, 18 Jan 2021 09:41:29 +0100 Subject: [PATCH] Allow use of a single cert for multiple provisioning profiles (#2193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use correct security call Was using wrong syntax and therefore failed * Do not check for duplicate import of keys as KeyChain takes care of that itself * Adjust tests * Adjust test * Formatting * Parse certificate fingerprint from certs and provisioning profiles * Address issues from PR review * Reformat * Adapt fixture to new requirments * Remove targetName and configurationName from cert and match using fingerprint * Update changelog * Update documentation. * Improve certificate lookup (faster and deterministic) * Revert Gemfile * Remove duplicate declaration * Catch openssl parsing errors * Remove unused error * Apply suggestions from code review Co-authored-by: Marek Fořt * Update documentation * Apply PR review comments * Log detailed openssl error when parsing fails * Update changelog * Remove duplicate switch case Co-authored-by: Marek Fořt --- CHANGELOG.md | 1 + .../Certificate/Certificate.swift | 4 +- .../Certificate/CertificateParser.swift | 55 ++++++++++++++----- .../GraphMappers/Dictionary+Fingerprint.swift | 7 +++ .../GraphMappers/SigningMapper.swift | 4 +- .../ProvisioningProfile.swift | 4 ++ .../ProvisioningProfileParser.swift | 11 +++- Sources/TuistSigning/SigningInteractor.swift | 4 +- Sources/TuistSigning/SigningMatcher.swift | 11 ++-- .../Extensions/Certificate+TestData.swift | 6 +- .../ProvisioningProfile+TestData.swift | 6 +- .../CertificateParserIntegrationTests.swift | 11 ++-- .../ProvisioningProfileParserTests.swift | 47 ++++++++++++++++ .../CertificateParserTests.swift | 38 +++++++------ .../Mocks/MockCertificateParser.swift | 6 ++ .../Mocks/MockSigningMatcher.swift | 4 +- .../ProvisioningProfileParserTests.swift | 6 +- .../SigningInteractorTests.swift | 9 +-- .../SigningMapperTests.swift | 8 +-- .../SigningMatcherTests.swift | 35 +++++------- .../Tuist/Signing/AppB.Debug.cer.encrypted | 1 - .../Tuist/Signing/AppB.Debug.p12.encrypted | 1 - ...cer.encrypted => RandomName.cer.encrypted} | 0 ...p12.encrypted => RandomName.p12.encrypted} | 0 website/markdown/docs/commands/signing.mdx | 13 +++-- 25 files changed, 195 insertions(+), 97 deletions(-) create mode 100644 Sources/TuistSigning/GraphMappers/Dictionary+Fingerprint.swift create mode 100644 Tests/TuistSigningIntegrationTests/ProvisioningProfileParserTests.swift delete mode 100644 fixtures/ios_app_with_signing/Tuist/Signing/AppB.Debug.cer.encrypted delete mode 100644 fixtures/ios_app_with_signing/Tuist/Signing/AppB.Debug.p12.encrypted rename fixtures/ios_app_with_signing/Tuist/Signing/{AppA.Debug.cer.encrypted => RandomName.cer.encrypted} (100%) rename fixtures/ios_app_with_signing/Tuist/Signing/{AppA.Debug.p12.encrypted => RandomName.p12.encrypted} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 459e6ddcf..683c8af09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/ ### Added - Add linting for paths of local packages and for URL validity of remote packages [#2255](https://github.com/tuist/tuist/pull/2255) by [@adellibovi](https://github.com/adellibovi). +- Allow use of a single cert for multiple provisioning profiles [#2193](https://github.com/tuist/tuist/pull/2193) by [@rist](https://github.com/rist). ### Fixed diff --git a/Sources/TuistSigning/Certificate/Certificate.swift b/Sources/TuistSigning/Certificate/Certificate.swift index 6968ffd7f..4ea195d4e 100644 --- a/Sources/TuistSigning/Certificate/Certificate.swift +++ b/Sources/TuistSigning/Certificate/Certificate.swift @@ -4,9 +4,9 @@ import TSCBasic struct Certificate: Equatable { let publicKey: AbsolutePath let privateKey: AbsolutePath + /// Content of the fingerprint property of the public key + let fingerprint: String let developmentTeam: String let name: String - let targetName: String - let configurationName: String let isRevoked: Bool } diff --git a/Sources/TuistSigning/Certificate/CertificateParser.swift b/Sources/TuistSigning/Certificate/CertificateParser.swift index 6eed26a74..e6c24fcf8 100644 --- a/Sources/TuistSigning/Certificate/CertificateParser.swift +++ b/Sources/TuistSigning/Certificate/CertificateParser.swift @@ -5,23 +5,23 @@ import TuistSupport enum CertificateParserError: FatalError, Equatable { case nameParsingFailed(AbsolutePath, String) case developmentTeamParsingFailed(AbsolutePath, String) - case invalidFormat(String) + case fileParsingFailed(AbsolutePath) var type: ErrorType { switch self { - case .nameParsingFailed, .developmentTeamParsingFailed, .invalidFormat: + case .nameParsingFailed, .developmentTeamParsingFailed, .fileParsingFailed: return .abort } } var description: String { switch self { - case let .invalidFormat(certificate): - return "Certificate \(certificate) is in invalid format. Please name your certificates in the following way: Target.Configuration.p12" case let .nameParsingFailed(path, input): return "We couldn't parse the name while parsing the following output from the file \(path.pathString): \(input)" case let .developmentTeamParsingFailed(path, input): return "We couldn't parse the development team while parsing the following output from the file \(path.pathString): \(input)" + case let .fileParsingFailed(path): + return "We couldn't parse the file \(path.pathString)" } } } @@ -31,6 +31,9 @@ protocol CertificateParsing { /// Parse public-private key pair /// - Returns: Parse `Certificate` func parse(publicKey: AbsolutePath, privateKey: AbsolutePath) throws -> Certificate + + /// Retrieve fingerprint of a public key + func parseFingerPrint(developerCertificate: Data) throws -> String } /// Subject attributes that are returnen with `openssl x509 -subject` @@ -48,12 +51,8 @@ private enum SubjectAttribute: String { final class CertificateParser: CertificateParsing { func parse(publicKey: AbsolutePath, privateKey: AbsolutePath) throws -> Certificate { - let publicKeyComponents = publicKey.basenameWithoutExt.components(separatedBy: ".") - guard publicKeyComponents.count == 2 else { throw CertificateParserError.invalidFormat(publicKey.pathString) } - let targetName = publicKeyComponents[0] - let configurationName = publicKeyComponents[1] - let subject = try self.subject(at: publicKey) + let fingerprint = try self.fingerprint(at: publicKey) let isRevoked = subject.contains("REVOKED") let nameRegex = try NSRegularExpression( @@ -61,9 +60,9 @@ final class CertificateParser: CertificateParsing { options: [] ) guard - let result = nameRegex.firstMatch(in: subject, options: [], range: NSRange(location: 0, length: subject.count)) + let nameResult = nameRegex.firstMatch(in: subject, options: [], range: NSRange(location: 0, length: subject.count)) else { throw CertificateParserError.nameParsingFailed(publicKey, subject) } - let name = NSString(string: subject).substring(with: result.range(at: 1)).spm_chomp() + let name = NSString(string: subject).substring(with: nameResult.range(at: 1)).spm_chomp() let developmentTeamRegex = try NSRegularExpression( pattern: SubjectAttribute.organizationalUnit.rawValue + " *= *([^/,]+)", @@ -77,18 +76,46 @@ final class CertificateParser: CertificateParsing { return Certificate( publicKey: publicKey, privateKey: privateKey, + fingerprint: fingerprint, developmentTeam: developmentTeam, name: name.sanitizeEncoding(), - targetName: targetName, - configurationName: configurationName, isRevoked: isRevoked ) } + func parseFingerPrint(developerCertificate: Data) throws -> String { + let temporaryFile = try FileHandler.shared.temporaryDirectory().appending(component: "developerCertificate.cer") + try developerCertificate.write(to: temporaryFile.asURL) + + return try fingerprint(at: temporaryFile) + } + // MARK: - Helpers private func subject(at path: AbsolutePath) throws -> String { - try System.shared.capture("openssl", "x509", "-inform", "der", "-in", path.pathString, "-noout", "-subject") + do { + return try System.shared.capture("openssl", "x509", "-inform", "der", "-in", path.pathString, "-noout", "-subject") + } catch let TuistSupport.SystemError.terminated(_, _, standardError) { + if let string = String(data: standardError, encoding: .utf8) { + logger.warning("Parsing subject of \(path) failed with: \(string)") + } + throw CertificateParserError.fileParsingFailed(path) + } catch { + throw CertificateParserError.fileParsingFailed(path) + } + } + + private func fingerprint(at path: AbsolutePath) throws -> String { + do { + return try System.shared.capture("openssl", "x509", "-inform", "der", "-in", path.pathString, "-noout", "-fingerprint").spm_chomp() + } catch let TuistSupport.SystemError.terminated(_, _, standardError) { + if let string = String(data: standardError, encoding: .utf8) { + logger.warning("Parsing fingerprint of \(path) failed with: \(string)") + } + throw CertificateParserError.fileParsingFailed(path) + } catch { + throw CertificateParserError.fileParsingFailed(path) + } } } diff --git a/Sources/TuistSigning/GraphMappers/Dictionary+Fingerprint.swift b/Sources/TuistSigning/GraphMappers/Dictionary+Fingerprint.swift new file mode 100644 index 000000000..b4d8937e4 --- /dev/null +++ b/Sources/TuistSigning/GraphMappers/Dictionary+Fingerprint.swift @@ -0,0 +1,7 @@ +import Foundation + +extension Dictionary where Key == Fingerprint, Value == Certificate { + func first(for provisioningProfile: ProvisioningProfile) -> Certificate? { + provisioningProfile.developerCertificateFingerprints.compactMap { self[$0] }.first + } +} diff --git a/Sources/TuistSigning/GraphMappers/SigningMapper.swift b/Sources/TuistSigning/GraphMappers/SigningMapper.swift index 967846617..764e7cd79 100644 --- a/Sources/TuistSigning/GraphMappers/SigningMapper.swift +++ b/Sources/TuistSigning/GraphMappers/SigningMapper.swift @@ -59,7 +59,7 @@ public class SigningMapper: ProjectMapping { private func map(target: Target, project: Project, keychainPath: AbsolutePath, - certificates: [TargetName: [ConfigurationName: Certificate]], + certificates: [Fingerprint: Certificate], provisioningProfiles: [TargetName: [ConfigurationName: ProvisioningProfile]]) throws -> Target { var target = target @@ -70,7 +70,7 @@ public class SigningMapper: ProjectMapping { .reduce(into: [:]) { dict, configurationPair in guard let provisioningProfile = provisioningProfiles[target.name]?[configurationPair.key.name], - let certificate = certificates[target.name]?[configurationPair.key.name] + let certificate = certificates.first(for: provisioningProfile) else { dict[configurationPair.key] = configurationPair.value return diff --git a/Sources/TuistSigning/ProvisioningProfile/ProvisioningProfile.swift b/Sources/TuistSigning/ProvisioningProfile/ProvisioningProfile.swift index aec6b7c92..3d720d195 100644 --- a/Sources/TuistSigning/ProvisioningProfile/ProvisioningProfile.swift +++ b/Sources/TuistSigning/ProvisioningProfile/ProvisioningProfile.swift @@ -16,6 +16,7 @@ struct ProvisioningProfile: Equatable { let applicationIdPrefix: [String] let platforms: [String] let expirationDate: Date + let developerCertificateFingerprints: [String] struct Content { let name: String @@ -26,6 +27,7 @@ struct ProvisioningProfile: Equatable { let applicationIdPrefix: [String] let platforms: [String] let expirationDate: Date + let developerCertificates: [Data] } } @@ -52,6 +54,7 @@ extension ProvisioningProfile.Content: Decodable { case platforms = "Platform" case entitlements = "Entitlements" case expirationDate = "ExpirationDate" + case developerCertificates = "DeveloperCertificates" } init(from decoder: Decoder) throws { @@ -65,5 +68,6 @@ extension ProvisioningProfile.Content: Decodable { let entitlements = try container.decode(Entitlements.self, forKey: .entitlements) appId = entitlements.appId expirationDate = try container.decode(Date.self, forKey: .expirationDate) + developerCertificates = try container.decode([Data].self, forKey: .developerCertificates) } } diff --git a/Sources/TuistSigning/ProvisioningProfile/ProvisioningProfileParser.swift b/Sources/TuistSigning/ProvisioningProfile/ProvisioningProfileParser.swift index 782dd0103..d40cb7cfe 100644 --- a/Sources/TuistSigning/ProvisioningProfile/ProvisioningProfileParser.swift +++ b/Sources/TuistSigning/ProvisioningProfile/ProvisioningProfileParser.swift @@ -29,9 +29,11 @@ protocol ProvisioningProfileParsing { final class ProvisioningProfileParser: ProvisioningProfileParsing { private let securityController: SecurityControlling + private let certificateParser: CertificateParsing - init(securityController: SecurityControlling = SecurityController()) { + init(securityController: SecurityControlling = SecurityController(), certificateParser: CertificateParsing = CertificateParser()) { self.securityController = securityController + self.certificateParser = certificateParser } func parse(at path: AbsolutePath) throws -> ProvisioningProfile { @@ -44,6 +46,10 @@ final class ProvisioningProfileParser: ProvisioningProfileParsing { let plistData = Data(unencryptedProvisioningProfile.utf8) let provisioningProfileContent = try PropertyListDecoder().decode(ProvisioningProfile.Content.self, from: plistData) + let developerCertificateFingerprints = try provisioningProfileContent.developerCertificates.map { + try certificateParser.parseFingerPrint(developerCertificate: $0) + } + return ProvisioningProfile(path: path, name: provisioningProfileContent.name, targetName: targetName, @@ -54,6 +60,7 @@ final class ProvisioningProfileParser: ProvisioningProfileParsing { appIdName: provisioningProfileContent.appIdName, applicationIdPrefix: provisioningProfileContent.applicationIdPrefix, platforms: provisioningProfileContent.platforms, - expirationDate: provisioningProfileContent.expirationDate) + expirationDate: provisioningProfileContent.expirationDate, + developerCertificateFingerprints: developerCertificateFingerprints) } } diff --git a/Sources/TuistSigning/SigningInteractor.swift b/Sources/TuistSigning/SigningInteractor.swift index 70114004f..1d83e0460 100644 --- a/Sources/TuistSigning/SigningInteractor.swift +++ b/Sources/TuistSigning/SigningInteractor.swift @@ -83,7 +83,7 @@ public final class SigningInteractor: SigningInteracting { private func install(target: Target, project: Project, keychainPath: AbsolutePath, - certificates: [TargetName: [ConfigurationName: Certificate]], + certificates: [Fingerprint: Certificate], provisioningProfiles: [TargetName: [ConfigurationName: ProvisioningProfile]]) throws { let targetConfigurations = target.settings?.configurations ?? [:] @@ -97,7 +97,7 @@ public final class SigningInteractor: SigningInteracting { .compactMap { configuration -> (certificate: Certificate, provisioningProfile: ProvisioningProfile)? in guard let provisioningProfile = provisioningProfiles[target.name]?[configuration.name], - let certificate = certificates[target.name]?[configuration.name] + let certificate = certificates.first(for: provisioningProfile) else { return nil } diff --git a/Sources/TuistSigning/SigningMatcher.swift b/Sources/TuistSigning/SigningMatcher.swift index c21b37dc0..282e08940 100644 --- a/Sources/TuistSigning/SigningMatcher.swift +++ b/Sources/TuistSigning/SigningMatcher.swift @@ -2,6 +2,7 @@ import Foundation import TSCBasic import TuistCore +typealias Fingerprint = String typealias TargetName = String typealias ConfigurationName = String @@ -10,7 +11,7 @@ protocol SigningMatching { /// - Returns: Certificates and provisioning profiles matched with their configuration and target /// - Warning: Expects certificates and provisioning profiles already decrypted func match(from path: AbsolutePath) throws -> ( - certificates: [TargetName: [ConfigurationName: Certificate]], + certificates: [Fingerprint: Certificate], provisioningProfiles: [TargetName: [ConfigurationName: ProvisioningProfile]] ) } @@ -30,19 +31,17 @@ final class SigningMatcher: SigningMatching { } func match(from path: AbsolutePath) throws -> ( - certificates: [TargetName: [ConfigurationName: Certificate]], + certificates: [Fingerprint: Certificate], provisioningProfiles: [TargetName: [ConfigurationName: ProvisioningProfile]] ) { let certificateFiles = try signingFilesLocator.locateUnencryptedCertificates(from: path) .sorted() let privateKeyFiles = try signingFilesLocator.locateUnencryptedPrivateKeys(from: path) .sorted() - let certificates: [TargetName: [ConfigurationName: Certificate]] = try zip(certificateFiles, privateKeyFiles) + let certificates: [Fingerprint: Certificate] = try zip(certificateFiles, privateKeyFiles) .map(certificateParser.parse) .reduce(into: [:]) { dict, certificate in - var currentTargetDict = dict[certificate.targetName] ?? [:] - currentTargetDict[certificate.configurationName] = certificate - dict[certificate.targetName] = currentTargetDict + dict[certificate.fingerprint] = certificate } // swiftlint:disable:next line_length diff --git a/Sources/TuistSigningTesting/Extensions/Certificate+TestData.swift b/Sources/TuistSigningTesting/Extensions/Certificate+TestData.swift index c358ad49f..3d3a2842e 100644 --- a/Sources/TuistSigningTesting/Extensions/Certificate+TestData.swift +++ b/Sources/TuistSigningTesting/Extensions/Certificate+TestData.swift @@ -5,18 +5,16 @@ import TSCBasic extension Certificate { static func test(publicKey: AbsolutePath = AbsolutePath("/"), privateKey: AbsolutePath = AbsolutePath("/"), + fingerprint: String = "", developmentTeam: String = "", name: String = "", - targetName: String = "", - configurationName: String = "", isRevoked: Bool = false) -> Certificate { Certificate(publicKey: publicKey, privateKey: privateKey, + fingerprint: fingerprint, developmentTeam: developmentTeam, name: name, - targetName: targetName, - configurationName: configurationName, isRevoked: isRevoked) } } diff --git a/Sources/TuistSigningTesting/Extensions/ProvisioningProfile+TestData.swift b/Sources/TuistSigningTesting/Extensions/ProvisioningProfile+TestData.swift index eb63564fc..efff087d1 100644 --- a/Sources/TuistSigningTesting/Extensions/ProvisioningProfile+TestData.swift +++ b/Sources/TuistSigningTesting/Extensions/ProvisioningProfile+TestData.swift @@ -14,7 +14,8 @@ extension ProvisioningProfile { appIdName: String = "appIdName", applicationIdPrefix: [String] = [], platforms: [String] = ["iOS"], - expirationDate: Date = Date().addingTimeInterval(100) + expirationDate: Date = Date().addingTimeInterval(100), + developerCertificateFingerprints: [String] = ["developerCertificateFingerprint"] ) -> ProvisioningProfile { ProvisioningProfile( path: path, @@ -27,7 +28,8 @@ extension ProvisioningProfile { appIdName: appIdName, applicationIdPrefix: applicationIdPrefix, platforms: platforms, - expirationDate: expirationDate + expirationDate: expirationDate, + developerCertificateFingerprints: developerCertificateFingerprints ) } } diff --git a/Tests/TuistSigningIntegrationTests/CertificateParserIntegrationTests.swift b/Tests/TuistSigningIntegrationTests/CertificateParserIntegrationTests.swift index c80209144..71a3612a9 100644 --- a/Tests/TuistSigningIntegrationTests/CertificateParserIntegrationTests.swift +++ b/Tests/TuistSigningIntegrationTests/CertificateParserIntegrationTests.swift @@ -5,18 +5,18 @@ import XCTest @testable import TuistSupportTesting final class CertificateParserIntegrationTests: TuistTestCase { - var subject: CertificateParser! + var certificateParser: CertificateParser! override func setUp() { super.setUp() - subject = CertificateParser() + certificateParser = CertificateParser() } override func tearDown() { super.tearDown() - subject = nil + certificateParser = nil } func test_parse_certificate() throws { @@ -27,15 +27,14 @@ final class CertificateParserIntegrationTests: TuistTestCase { let expectedCertificate = Certificate( publicKey: publicKey, privateKey: privateKey, + fingerprint: "SHA1 Fingerprint=80:B8:6E:55:1D:8E:F2:38:CD:95:3C:7E:72:3B:DB:B1:A5:B0:5D:60", developmentTeam: "QH95ER52SG", name: "Apple Development: Marek Fort (54GSF6G47V)", - targetName: "Target", - configurationName: "Debug", isRevoked: false ) // When - let certificate = try subject.parse(publicKey: publicKey, privateKey: privateKey) + let certificate = try certificateParser.parse(publicKey: publicKey, privateKey: privateKey) // Then XCTAssertEqual(certificate, expectedCertificate) diff --git a/Tests/TuistSigningIntegrationTests/ProvisioningProfileParserTests.swift b/Tests/TuistSigningIntegrationTests/ProvisioningProfileParserTests.swift new file mode 100644 index 000000000..b3575417d --- /dev/null +++ b/Tests/TuistSigningIntegrationTests/ProvisioningProfileParserTests.swift @@ -0,0 +1,47 @@ +import TSCBasic +import XCTest +@testable import TuistSigning +@testable import TuistSigningTesting +@testable import TuistSupportTesting + +final class ProvisioningProfileParserTests: TuistTestCase { + var provisioningProfileParser: ProvisioningProfileParser! + + override func setUp() { + super.setUp() + + provisioningProfileParser = ProvisioningProfileParser() + } + + override func tearDown() { + super.tearDown() + + provisioningProfileParser = nil + } + + func test_parse_provisioningProfile() throws { + // Given + let currentDirectory = AbsolutePath(#file.replacingOccurrences(of: "file://", with: "")).removingLastComponent() + let provisioningProfileFile = currentDirectory.appending(component: "SignApp.debug.mobileprovision") + let expectedProvisioningProfile = ProvisioningProfile( + path: provisioningProfileFile, + name: "SignApp.Debug", + targetName: "SignApp", + configurationName: "debug", + uuid: "d34fb066-f494-4d85-a556-d469c2196f46", + teamId: "QH95ER52SG", + appId: "QH95ER52SG.io.tuist.SignApp", + appIdName: "SignApp", + applicationIdPrefix: ["QH95ER52SG"], + platforms: ["iOS"], + expirationDate: Date(timeIntervalSince1970: 1_619_208_757.0), + developerCertificateFingerprints: ["SHA1 Fingerprint=80:B8:6E:55:1D:8E:F2:38:CD:95:3C:7E:72:3B:DB:B1:A5:B0:5D:60"] + ) + + // When + let provisioningProfile = try provisioningProfileParser.parse(at: provisioningProfileFile) + + // Then + XCTAssertEqual(provisioningProfile, expectedProvisioningProfile) + } +} diff --git a/Tests/TuistSigningTests/CertificateParserTests.swift b/Tests/TuistSigningTests/CertificateParserTests.swift index ecb039f35..b34d144e9 100644 --- a/Tests/TuistSigningTests/CertificateParserTests.swift +++ b/Tests/TuistSigningTests/CertificateParserTests.swift @@ -27,6 +27,11 @@ final class CertificateParserTests: TuistUnitTestCase { "openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-subject", output: subjectOutput ) + let fingerprintOutput = "subject= /UID=VD55TKL3V6/CN=Apple Development: Name (54GSF6G47V)/OU=QH95ER52SG/O=Name/C=US\n" + system.succeedCommand( + "openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-fingerprint", + output: fingerprintOutput + ) // When XCTAssertThrowsSpecific( @@ -44,6 +49,11 @@ final class CertificateParserTests: TuistUnitTestCase { "openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-subject", output: subjectOutput ) + let fingerprintOutput = "subject= /UID=VD55TKL3V6/CN=Apple Development: Name (54GSF6G47V)/OU=QH95ER52SG/O=Name/C=US\n" + system.succeedCommand( + "openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-fingerprint", + output: fingerprintOutput + ) // When XCTAssertThrowsSpecific( @@ -52,18 +62,6 @@ final class CertificateParserTests: TuistUnitTestCase { ) } - func test_throws_invalid_name_when_wrong_format() throws { - // Given - let publicKey = try temporaryPath() - let privateKey = try temporaryPath() - - // When - XCTAssertThrowsSpecific( - try subject.parse(publicKey: publicKey, privateKey: privateKey), - CertificateParserError.invalidFormat(publicKey.pathString) - ) - } - func test_parsing_succeeds() throws { // Given let publicKey = try temporaryPath().appending(component: "Target.Debug.p12") @@ -73,13 +71,17 @@ final class CertificateParserTests: TuistUnitTestCase { "openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-subject", output: subjectOutput ) + let fingerprintOutput = "subject= /UID=VD55TKL3V6/CN=Apple Development: Name (54GSF6G47V)/OU=QH95ER52SG/O=Name/C=US\n" + system.succeedCommand( + "openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-fingerprint", + output: fingerprintOutput + ) let expectedCertificate = Certificate( publicKey: publicKey, privateKey: privateKey, + fingerprint: "subject= /UID=VD55TKL3V6/CN=Apple Development: Name (54GSF6G47V)/OU=QH95ER52SG/O=Name/C=US", developmentTeam: "QH95ER52SG", name: "Apple Development: Name (54GSF6G47V)", - targetName: "Target", - configurationName: "Debug", isRevoked: false ) @@ -99,13 +101,17 @@ final class CertificateParserTests: TuistUnitTestCase { "openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-subject", output: subjectOutput ) + let fingerprintOutput = "subject= /UID=VD55TKL3V6/CN=Apple Development: Name (54GSF6G47V)/OU=QH95ER52SG/O=Name/C=US\n" + system.succeedCommand( + "openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-fingerprint", + output: fingerprintOutput + ) let expectedCertificate = Certificate( publicKey: publicKey, privateKey: privateKey, + fingerprint: "subject= /UID=VD55TKL3V6/CN=Apple Development: Name (54GSF6G47V)/OU=QH95ER52SG/O=Name/C=US", developmentTeam: "QH95ER52SG", name: "Apple Development: Name (54GSF6G47V)", - targetName: "Target", - configurationName: "Debug", isRevoked: false ) diff --git a/Tests/TuistSigningTests/Mocks/MockCertificateParser.swift b/Tests/TuistSigningTests/Mocks/MockCertificateParser.swift index caab19c64..cab393eb9 100644 --- a/Tests/TuistSigningTests/Mocks/MockCertificateParser.swift +++ b/Tests/TuistSigningTests/Mocks/MockCertificateParser.swift @@ -1,3 +1,4 @@ +import Foundation import TSCBasic @testable import TuistSigning @testable import TuistSigningTesting @@ -7,4 +8,9 @@ final class MockCertificateParser: CertificateParsing { func parse(publicKey: AbsolutePath, privateKey: AbsolutePath) throws -> Certificate { try parseStub?(publicKey, privateKey) ?? Certificate.test() } + + var parseFingerPrintStub: ((Data) throws -> String)? + func parseFingerPrint(developerCertificate: Data) throws -> String { + try parseFingerPrintStub?(developerCertificate) ?? "" + } } diff --git a/Tests/TuistSigningTests/Mocks/MockSigningMatcher.swift b/Tests/TuistSigningTests/Mocks/MockSigningMatcher.swift index 104ad1f75..ff5cf052f 100644 --- a/Tests/TuistSigningTests/Mocks/MockSigningMatcher.swift +++ b/Tests/TuistSigningTests/Mocks/MockSigningMatcher.swift @@ -3,8 +3,8 @@ import TuistCore @testable import TuistSigning final class MockSigningMatcher: SigningMatching { - var matchStub: ((AbsolutePath) throws -> (certificates: [String: [String: Certificate]], provisioningProfiles: [String: [String: ProvisioningProfile]]))? - func match(from path: AbsolutePath) throws -> (certificates: [String: [String: Certificate]], provisioningProfiles: [String: [String: ProvisioningProfile]]) { + var matchStub: ((AbsolutePath) throws -> (certificates: [String: Certificate], provisioningProfiles: [String: [String: ProvisioningProfile]]))? + func match(from path: AbsolutePath) throws -> (certificates: [String: Certificate], provisioningProfiles: [String: [String: ProvisioningProfile]]) { try matchStub?(path) ?? (certificates: [:], provisioningProfiles: [:]) } } diff --git a/Tests/TuistSigningTests/ProvisioningProfileParserTests.swift b/Tests/TuistSigningTests/ProvisioningProfileParserTests.swift index 98ac1d388..df116e6f3 100644 --- a/Tests/TuistSigningTests/ProvisioningProfileParserTests.swift +++ b/Tests/TuistSigningTests/ProvisioningProfileParserTests.swift @@ -37,7 +37,8 @@ final class ProvisioningProfileParserTests: TuistUnitTestCase { appIdName: "AppIDName", applicationIdPrefix: ["Prefix"], platforms: ["iOS"], - expirationDate: Date(timeIntervalSinceReferenceDate: 640_729_461) + expirationDate: Date(timeIntervalSinceReferenceDate: 640_729_461), + developerCertificateFingerprints: [] ) securityController.decodeFileStub = { _ in .testProvisioningProfile( @@ -110,6 +111,9 @@ private extension String { \(expirationDate) Name \(name) + DeveloperCertificates + + ProvisionedDevices 2b41533fd2df499800f493b261d060fe6d60838b diff --git a/Tests/TuistSigningTests/SigningInteractorTests.swift b/Tests/TuistSigningTests/SigningInteractorTests.swift index e3378f160..5607a5546 100644 --- a/Tests/TuistSigningTests/SigningInteractorTests.swift +++ b/Tests/TuistSigningTests/SigningInteractorTests.swift @@ -207,14 +207,11 @@ final class SigningInteractorTests: TuistUnitTestCase { let targetName = "target" let configuration = "configuration" let expectedCertificate = Certificate.test(name: "certA") - let expectedProvisioningProfile = ProvisioningProfile.test(name: "profileA") + let expectedProvisioningProfile = ProvisioningProfile.test(name: "profileA", developerCertificateFingerprints: ["fingerprint"]) signingMatcher.matchStub = { _ in (certificates: [ - targetName: [ - configuration: expectedCertificate, - // Used to ensure only certificates that have configuration are installed - "other-config": Certificate.test(name: "certB"), - ], + "fingerprint": expectedCertificate, + "otherFingerprint": Certificate.test(name: "certB"), ], provisioningProfiles: [ targetName: [ diff --git a/Tests/TuistSigningTests/SigningMapperTests.swift b/Tests/TuistSigningTests/SigningMapperTests.swift index dd798b786..644eb4093 100644 --- a/Tests/TuistSigningTests/SigningMapperTests.swift +++ b/Tests/TuistSigningTests/SigningMapperTests.swift @@ -44,16 +44,16 @@ final class SigningMapperTests: TuistUnitTestCase { let targetName = "target" let configuration = "configuration" let certificate = Certificate.test(name: "certA") + let fingerprint = "fingerprint" let provisioningProfile = ProvisioningProfile.test( name: "profileA", teamId: "TeamID", - appId: "TeamID.BundleID" + appId: "TeamID.BundleID", + developerCertificateFingerprints: ["otherFingerPrint", fingerprint] ) signingMatcher.matchStub = { _ in (certificates: [ - targetName: [ - configuration: certificate, - ], + fingerprint: certificate, ], provisioningProfiles: [ targetName: [ diff --git a/Tests/TuistSigningTests/SigningMatcherTests.swift b/Tests/TuistSigningTests/SigningMatcherTests.swift index 7e545e49c..9eb170636 100644 --- a/Tests/TuistSigningTests/SigningMatcherTests.swift +++ b/Tests/TuistSigningTests/SigningMatcherTests.swift @@ -73,35 +73,30 @@ final class SigningMatcherTests: TuistUnitTestCase { ] } certificateParser.parseStub = { publicKey, privateKey in - let configurationName: String + let fingerprint: String if publicKey == publicKeyPath { - configurationName = debugConfiguration + fingerprint = "fingerprint" } else { - configurationName = releaseConfiguration + fingerprint = "otherFingerprint" } return Certificate.test( publicKey: publicKey, privateKey: privateKey, - targetName: targetName, - configurationName: configurationName + fingerprint: fingerprint ) } - let expectedCertificates: [String: [String: Certificate]] = [ - targetName: [ - debugConfiguration: Certificate.test( - publicKey: publicKeyPath, - privateKey: privateKeyPath, - targetName: targetName, - configurationName: debugConfiguration - ), - releaseConfiguration: Certificate.test( - publicKey: releasePublicKeyPath, - privateKey: releasePrivateKeyPath, - targetName: targetName, - configurationName: releaseConfiguration - ), - ], + let expectedCertificates: [String: Certificate] = [ + "fingerprint": Certificate.test( + publicKey: publicKeyPath, + privateKey: privateKeyPath, + fingerprint: "fingerprint" + ), + "otherFingerprint": Certificate.test( + publicKey: releasePublicKeyPath, + privateKey: releasePrivateKeyPath, + fingerprint: "otherFingerprint" + ), ] let debugProvisioningProfilePath = AbsolutePath("/\(targetName).\(debugConfiguration).mobileprovision") diff --git a/fixtures/ios_app_with_signing/Tuist/Signing/AppB.Debug.cer.encrypted b/fixtures/ios_app_with_signing/Tuist/Signing/AppB.Debug.cer.encrypted deleted file mode 100644 index 85f8881b9..000000000 --- a/fixtures/ios_app_with_signing/Tuist/Signing/AppB.Debug.cer.encrypted +++ /dev/null @@ -1 +0,0 @@ -5AHwOTcfzNr1TYeAnL/pAg==-we3696+agUK2mv4hFbJqvLgBADWtUknOKJLZ+UhMFaNfOx/hBSbLtTwE56+3K1vDrY1RBiSg8zkWjm/aXpLtgCwGekvNWwLGBwOvPL5r+fxp96QvYUbvWMTZBvA1yVGFwptgGSYZ4xCqqOZdLNzB8UtuB6c+WlSKSlUdcx6wbjNoEMPVYxP7APkJnlZTZqqgXn27IqWSrD5zCahshxep8F4xefOboHQL4aUe/KdxX0dZTbnE2OrrAFbNmOcc7kwTL0pSdEhBZDjCSYB9gK+90P+t9HZWcYzDnYQbR73wq7QEDONoF0VgwUGYtsTd3c4wPddezEo0Gw07o/CEnccho+RD9t1gwKuWAXKZxnM3jI46wGVJ4v4qHZuZy+t/Q/GxhsaFDhrIrPTsthw5fzZbOm20BDL2BCH4GiWSW2QCHBvctsTXQ9JKXHiQsW72UlVMKz1oj5Xh3+QGyiDZZxrfAQmN0SwI4WGsOateLEOV5lfQ57+9oS3VewC60lXL8iYSzq0/w2ecOZxCu3NLECQLN83xFJYKeloZT8g0eZXgNeLPs0eaOkRZN8NBl/ZvOBZKitk/L/G1OSFHTL4J0zv3ykDxV4kfHxMIgxE9BCYEvIOCSOrhDB5KjYQc9Phv6bL9jXjlyScPnKsl6t7+7r58R9P9fY5FNoMs1YZEmBbny1UjZ/Ny6KL2ADYTlJs0MKikJvFi3SLgzm3tT+l9G1yximpwwETMAT473oAspIKJcaNdxFtv0ctEonyDTy8svvQ+IvZjkdWMtv91Ze5Ht3YG8iR2CAOhlr1JcPz9B/+UuxO8hWMNYI19Ff4cqYy8dZSI+DJ3IgoDzEUnUUDTagUTWVG3pEJG0uoKYKedNCR8quLf+XzABSe8FP0/u1xMCKWVxgVbFdUfgEvxPQKFmkyKhF0PtFHQnQRtHo66AdfqdjPqDb/WULTQeXBnv2vIXt4DtIj7w7Z0xNuMCjxEDYREOwglMMGHqfAkbXC2XL4bVKmVRleC3LdjpISh2308YqBTJwlEInIBNAOS02+0BJdp3/vP4I83FXJWHhQdT9wrzaXsysn1jVlD+GPh3pxjUfOWj45nuPO+Mt/KhO0HM/o4/1qC75vsvsvOjnEU6EWucBUy5GCcV+TYIzKJvyJ+k81R90JXILSkZPkFpNMXjCCD22r7fbyWfZXn9hgUHjZ02DhBGpfcHszIMVP8hxv5Ptt9kFJekceMBPLiRLZh/C2skLDE7rkzYPEnVzGIZ0oUT8rYZJ7kjjc56eXvPQ7oCwMPQQeayzwJVobREQnn6ypxvaWpRscq39uWl8kC7OiS3T1mt+75e+kQ5jqY7nf8fsQlPKn8w/hUL32f80FOR11iZ3/aid1/gt5FpT8QoLpZ6R3CSM+spedi3XWUvPMdhUOeEvzg8qoBVuzq/aDTo1qIQ6hNcEmlT+TA9bK4LraTmkHguZ8IW+Vej+yFucfEh+zqqwte+FTwjCwrHlRvcx+Ks5nykVz6ubZAXZ3szat7y6ltjEMFpRvTjZ3hPcLwsBFv88h/1pnNS2oRDU/RikgQJ+b0nyRQF7m170MCzmmMGnIV+ZFvEaCfCTleay4V5IsC1uHA04UtPANsUig7Pm71o5NpwHckWykvcf6LdtCpInJmVlWDoTcFmykxWuQGogz6t3/xkVNULkIQtmQilI+usPBoNm3YM62orkks4hVFqgHqDvN5VC7Bt/35yzumIAOCedqmDRltrzbHTjt66Gfuh9FQIqFEE1muSuuCXsW9erD1Oi7VKuYfg9/i3yyCQyAT05wM5anRGjVbLlg9r4O1m30WfVB7A6/Je47gKbYzVq40PR37YyGrs/2hHLqiUv6Kan1fGS2DjwUzH8xpGWFy6jNzVzMUYH7Y0V15n+rticuki6L5AHn8OgoNEwivLmRxG7/mKrRV \ No newline at end of file diff --git a/fixtures/ios_app_with_signing/Tuist/Signing/AppB.Debug.p12.encrypted b/fixtures/ios_app_with_signing/Tuist/Signing/AppB.Debug.p12.encrypted deleted file mode 100644 index 7a2f06b34..000000000 --- a/fixtures/ios_app_with_signing/Tuist/Signing/AppB.Debug.p12.encrypted +++ /dev/null @@ -1 +0,0 @@ -dThPH8pFcEHgwpG1mqFDPg==-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 \ No newline at end of file diff --git a/fixtures/ios_app_with_signing/Tuist/Signing/AppA.Debug.cer.encrypted b/fixtures/ios_app_with_signing/Tuist/Signing/RandomName.cer.encrypted similarity index 100% rename from fixtures/ios_app_with_signing/Tuist/Signing/AppA.Debug.cer.encrypted rename to fixtures/ios_app_with_signing/Tuist/Signing/RandomName.cer.encrypted diff --git a/fixtures/ios_app_with_signing/Tuist/Signing/AppA.Debug.p12.encrypted b/fixtures/ios_app_with_signing/Tuist/Signing/RandomName.p12.encrypted similarity index 100% rename from fixtures/ios_app_with_signing/Tuist/Signing/AppA.Debug.p12.encrypted rename to fixtures/ios_app_with_signing/Tuist/Signing/RandomName.p12.encrypted diff --git a/website/markdown/docs/commands/signing.mdx b/website/markdown/docs/commands/signing.mdx index e33f4240b..08d4eb88c 100644 --- a/website/markdown/docs/commands/signing.mdx +++ b/website/markdown/docs/commands/signing.mdx @@ -18,12 +18,13 @@ Tuist aims to solve all of the above pain points in a simple and _deterministic_ ## How to Get Started -As the first step, download all your provisioning profiles and certficates that you'll need. -Once you download them, make sure to rename the files, so they abide by the following convention: -`Target.Configuration.extension` -Where `Target` should be a name of the target, `Configuration` a name of the configuration and leave the default extension -that you downloaded the file with (eg. `p12` for certificate, etc.). -Now you can put all those files in `Tuist/Signing` directory. +As the first step, download all your provisioning profiles that you'll need. +Once you downloaded them, make sure to rename the provisioning profiles, so they abide by the following convention: +`Target.Configuration.mobileprovision` for iOS apps or `Target.Configuration.provisionprofile` for macOS apps. +Where `Target` should be a name of the target, `Configuration` a name of the configuration. +Export the public (as .cer file) and private key (as .p12 file with an empty string as a password) from your keychain. Use the same basename for these two files: `SomeName.cer` and `SomeName.p12` - this name doesn't need to match any target or configuration. +If multiple provisioning profiles use the same certificate, it's fine to have `.p12` and `.cer` files just once in the folder - Tuist will find the matching one based on information embedded into the provisioning profile. +Now you can put all those files in `Tuist/Signing` directory within your project. To make it all work, create a secure password by running [tuist secret](/docs/commands/secret/) and place its contents into `Tuist/master.key` that will be used for encrypting and decrypting your files.