From 017f8a655ae2cb9b85fa6663a18350deb1a59c76 Mon Sep 17 00:00:00 2001 From: Bassam Khouri Date: Fri, 1 Nov 2024 22:28:34 -0400 Subject: [PATCH 1/2] Tests: Convert more tests to Swift Testing Convert additional test from XCTest to Swift Testing to make use of parallel execution and, in some cases, test parameterization. --- ...FilePackageSigningEntityStorageTests.swift | 248 ++-- .../SigningEntityTests.swift | 53 +- .../SigningIdentityTests.swift | 29 +- Tests/PackageSigningTests/SigningTests.swift | 1092 +++++++++-------- Tests/QueryEngineTests/QueryEngineTests.swift | 208 ++-- .../GitRepositoryProviderTests.swift | 86 +- .../InMemoryGitRepositoryTests.swift | 79 +- 7 files changed, 972 insertions(+), 823 deletions(-) diff --git a/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift b/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift index fb9620b09dc..c9ed46468d0 100644 --- a/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift +++ b/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift @@ -16,12 +16,13 @@ import Basics import PackageModel @testable import PackageSigning import _InternalTestSupport -import XCTest +import Testing import struct TSCUtility.Version -final class FilePackageSigningEntityStorageTests: XCTestCase { - func testHappyCase() async throws { +struct FilePackageSigningEntityStorageTests { + @Test + func happyCase() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -68,36 +69,32 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) // A data file should have been created for each package - XCTAssertTrue(mockFileSystem.exists(storage.directoryPath.appending(component: package.signedVersionsFilename))) - XCTAssertTrue( - mockFileSystem - .exists(storage.directoryPath.appending(component: otherPackage.signedVersionsFilename)) - ) + #expect(mockFileSystem.exists(storage.directoryPath.appending(component: package.signedVersionsFilename))) + #expect(mockFileSystem + .exists(storage.directoryPath.appending(component: otherPackage.signedVersionsFilename))) // Signed versions should be saved do { let packageSigners = try storage.get(package: package) - XCTAssertNil(packageSigners.expectedSigner) - XCTAssertEqual(packageSigners.signers.count, 2) - XCTAssertEqual(packageSigners.signers[davinci]?.versions, [Version("1.0.0"), Version("1.1.0")]) - XCTAssertEqual( - packageSigners.signers[davinci]?.origins, - [.registry(URL("http://foo.com")), .registry(URL("http://bar.com"))] - ) - XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("2.0.0")]) - XCTAssertEqual(packageSigners.signers[appleseed]?.origins, [.registry(URL("http://foo.com"))]) + #expect(packageSigners.expectedSigner == nil) + #expect(packageSigners.signers.count == 2) + #expect(packageSigners.signers[davinci]?.versions == [Version("1.0.0"), Version("1.1.0")]) + #expect(packageSigners.signers[davinci]?.origins == [.registry(URL("http://foo.com")), .registry(URL("http://bar.com"))]) + #expect(packageSigners.signers[appleseed]?.versions == [Version("2.0.0")]) + #expect(packageSigners.signers[appleseed]?.origins == [.registry(URL("http://foo.com"))]) } do { let packageSigners = try storage.get(package: otherPackage) - XCTAssertNil(packageSigners.expectedSigner) - XCTAssertEqual(packageSigners.signers.count, 1) - XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.0.0")]) - XCTAssertEqual(packageSigners.signers[appleseed]?.origins, [.registry(URL("http://foo.com"))]) + #expect(packageSigners.expectedSigner == nil) + #expect(packageSigners.signers.count == 1) + #expect(packageSigners.signers[appleseed]?.versions == [Version("1.0.0")]) + #expect(packageSigners.signers[appleseed]?.origins == [.registry(URL("http://foo.com"))]) } } - func testPutDifferentSigningEntityShouldConflict() async throws { + @Test + func putDifferentSigningEntityShouldConflict() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -124,19 +121,35 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) // Writing different signing entities for the same version should fail - await XCTAssertAsyncThrowsError(try storage.put( - package: package, - version: version, - signingEntity: appleseed, - origin: .registry(URL("http://foo.com")) - )) { error in + #expect { + try storage.put( + package: package, + version: version, + signingEntity: appleseed, + origin: .registry(URL("http://foo.com")) + ) + } throws: { error in guard case PackageSigningEntityStorageError.conflict = error else { - return XCTFail("Expected PackageSigningEntityStorageError.conflict, got \(error)") + Issue.record("Expected PackageSigningEntityStorageError.conflict, got \(error)") + return false } + return true } + // await XCTAssertAsyncThrowsError(try storage.put( + // package: package, + // version: version, + // signingEntity: appleseed, + // origin: .registry(URL("http://foo.com")) + // )) { error in + // guard case PackageSigningEntityStorageError.conflict = error else { + // Issue.record("Expected PackageSigningEntityStorageError.conflict, got \(error)") + // return + // } + // } } - func testPutSameSigningEntityShouldNotConflict() async throws { + @Test + func putSameSigningEntityShouldNotConflict() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -165,16 +178,14 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) let packageSigners = try storage.get(package: package) - XCTAssertNil(packageSigners.expectedSigner) - XCTAssertEqual(packageSigners.signers.count, 1) - XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.0.0")]) - XCTAssertEqual( - packageSigners.signers[appleseed]?.origins, - [.registry(URL("http://foo.com")), .registry(URL("http://bar.com"))] - ) + #expect(packageSigners.expectedSigner == nil) + #expect(packageSigners.signers.count == 1) + #expect(packageSigners.signers[appleseed]?.versions == [Version("1.0.0")]) + #expect(packageSigners.signers[appleseed]?.origins == [.registry(URL("http://foo.com")), .registry(URL("http://bar.com"))]) } - func testPutUnrecognizedSigningEntityShouldError() async throws { + @Test + func putUnrecognizedSigningEntityShouldError() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -183,19 +194,35 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { let appleseed = SigningEntity.unrecognized(name: "J. Appleseed", organizationalUnit: nil, organization: nil) let version = Version("1.0.0") - await XCTAssertAsyncThrowsError(try storage.put( - package: package, - version: version, - signingEntity: appleseed, - origin: .registry(URL("http://bar.com")) // origin is different and should be added - )) { error in + #expect { + try storage.put( + package: package, + version: version, + signingEntity: appleseed, + origin: .registry(URL("http://bar.com")) // origin is different and should be added + ) + } throws: { error in guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { - return XCTFail("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + return false } + return true } + // await XCTAssertAsyncThrowsError(try storage.put( + // package: package, + // version: version, + // signingEntity: appleseed, + // origin: .registry(URL("http://bar.com")) // origin is different and should be added + // )) { error in + // guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { + // Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + // return + // } + // } } - func testAddDifferentSigningEntityShouldNotConflict() async throws { + @Test + func addDifferentSigningEntityShouldNotConflict() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -230,16 +257,17 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) let packageSigners = try storage.get(package: package) - XCTAssertNil(packageSigners.expectedSigner) - XCTAssertEqual(packageSigners.signers.count, 2) - XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.0.0")]) - XCTAssertEqual(packageSigners.signers[appleseed]?.origins, [.registry(URL("http://bar.com"))]) - XCTAssertEqual(packageSigners.signers[davinci]?.versions, [Version("1.0.0")]) - XCTAssertEqual(packageSigners.signers[davinci]?.origins, [.registry(URL("http://foo.com"))]) - XCTAssertEqual(packageSigners.signingEntities(of: Version("1.0.0")), [appleseed, davinci]) + #expect(packageSigners.expectedSigner == nil) + #expect(packageSigners.signers.count == 2) + #expect(packageSigners.signers[appleseed]?.versions == [Version("1.0.0")]) + #expect(packageSigners.signers[appleseed]?.origins == [.registry(URL("http://bar.com"))]) + #expect(packageSigners.signers[davinci]?.versions == [Version("1.0.0")]) + #expect(packageSigners.signers[davinci]?.origins == [.registry(URL("http://foo.com"))]) + #expect(packageSigners.signingEntities(of: Version("1.0.0")) == [appleseed, davinci]) } - func testAddUnrecognizedSigningEntityShouldError() async throws { + @Test + func addUnrecognizedSigningEntityShouldError() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -260,19 +288,35 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { origin: .registry(URL("http://foo.com")) ) - await XCTAssertAsyncThrowsError(try storage.add( - package: package, - version: version, - signingEntity: appleseed, - origin: .registry(URL("http://bar.com")) - )) { error in + #expect { + try storage.add( + package: package, + version: version, + signingEntity: appleseed, + origin: .registry(URL("http://bar.com")) + ) + } throws: { error in guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { - return XCTFail("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + return false } + return true } + // await XCTAssertAsyncThrowsError(try storage.add( + // package: package, + // version: version, + // signingEntity: appleseed, + // origin: .registry(URL("http://bar.com")) + // )) { error in + // guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { + // Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + // return + // } + // } } - func testChangeSigningEntityFromVersion() async throws { + @Test + func changeSigningEntityFromVersion() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -306,16 +350,17 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) let packageSigners = try storage.get(package: package) - XCTAssertEqual(packageSigners.expectedSigner?.signingEntity, appleseed) - XCTAssertEqual(packageSigners.expectedSigner?.fromVersion, Version("1.5.0")) - XCTAssertEqual(packageSigners.signers.count, 2) - XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.5.0")]) - XCTAssertEqual(packageSigners.signers[appleseed]?.origins, [.registry(URL("http://bar.com"))]) - XCTAssertEqual(packageSigners.signers[davinci]?.versions, [Version("1.0.0")]) - XCTAssertEqual(packageSigners.signers[davinci]?.origins, [.registry(URL("http://foo.com"))]) + #expect(packageSigners.expectedSigner?.signingEntity == appleseed) + #expect(packageSigners.expectedSigner?.fromVersion == Version("1.5.0")) + #expect(packageSigners.signers.count == 2) + #expect(packageSigners.signers[appleseed]?.versions == [Version("1.5.0")]) + #expect(packageSigners.signers[appleseed]?.origins == [.registry(URL("http://bar.com"))]) + #expect(packageSigners.signers[davinci]?.versions == [Version("1.0.0")]) + #expect(packageSigners.signers[davinci]?.origins == [.registry(URL("http://foo.com"))]) } - func testChangeSigningEntityFromVersion_unrecognizedSigningEntityShouldError() async throws { + @Test + func changeSigningEntityFromVersion_unrecognizedSigningEntityShouldError() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -335,19 +380,24 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { origin: .registry(URL("http://foo.com")) ) - await XCTAssertAsyncThrowsError(try storage.changeSigningEntityFromVersion( - package: package, - version: Version("1.5.0"), - signingEntity: appleseed, - origin: .registry(URL("http://bar.com")) - )) { error in + #expect { + try storage.changeSigningEntityFromVersion( + package: package, + version: Version("1.5.0"), + signingEntity: appleseed, + origin: .registry(URL("http://bar.com")) + ) + } throws: { error in guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { - return XCTFail("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + return false } + return true } } - func testChangeSigningEntityForAllVersions() async throws { + @Test + func changeSigningEntityForAllVersions() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -387,14 +437,15 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) let packageSigners = try storage.get(package: package) - XCTAssertEqual(packageSigners.expectedSigner?.signingEntity, appleseed) - XCTAssertEqual(packageSigners.expectedSigner?.fromVersion, Version("1.5.0")) - XCTAssertEqual(packageSigners.signers.count, 1) - XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.5.0"), Version("2.0.0")]) - XCTAssertEqual(packageSigners.signers[appleseed]?.origins, [.registry(URL("http://bar.com"))]) + #expect(packageSigners.expectedSigner?.signingEntity == appleseed) + #expect(packageSigners.expectedSigner?.fromVersion == Version("1.5.0")) + #expect(packageSigners.signers.count == 1) + #expect(packageSigners.signers[appleseed]?.versions == [Version("1.5.0"), Version("2.0.0")]) + #expect(packageSigners.signers[appleseed]?.origins == [.registry(URL("http://bar.com"))]) } - func testChangeSigningEntityForAllVersions_unrecognizedSigningEntityShouldError() async throws { + @Test + func changeSigningEntityForAllVersions_unrecognizedSigningEntityShouldError() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -414,16 +465,31 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { origin: .registry(URL("http://foo.com")) ) - await XCTAssertAsyncThrowsError(try storage.changeSigningEntityForAllVersions( - package: package, - version: Version("1.5.0"), - signingEntity: appleseed, - origin: .registry(URL("http://bar.com")) - )) { error in + #expect { + try storage.changeSigningEntityForAllVersions( + package: package, + version: Version("1.5.0"), + signingEntity: appleseed, + origin: .registry(URL("http://bar.com")) + ) + } throws: { error in guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { - return XCTFail("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + return false } + return true } + // await XCTAsserttAsyncThrowsError(try storage.changeSigningEntityForAllVersions( + // package: package, + // version: Version("1.5.0"), + // signingEntity: appleseed, + // origin: .registry(URL("http://bar.com")) + // )) { error in + // guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { + // Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + // return + // } + // } } } diff --git a/Tests/PackageSigningTests/SigningEntityTests.swift b/Tests/PackageSigningTests/SigningEntityTests.swift index e53aa99975b..46ae6a98701 100644 --- a/Tests/PackageSigningTests/SigningEntityTests.swift +++ b/Tests/PackageSigningTests/SigningEntityTests.swift @@ -9,7 +9,8 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// - +import Foundation +import Testing import XCTest import Basics @@ -17,8 +18,9 @@ import Basics import _InternalTestSupport import X509 -final class SigningEntityTests: XCTestCase { - func testTwoADPSigningEntitiesAreEqualIfTeamIDEqual() { +struct SigningEntityTests { + @Test + func twoADPSigningEntitiesAreEqualIfTeamIDEqual() { let adp1 = SigningEntity.recognized( type: .adp, name: "A. Appleseed", @@ -37,48 +39,39 @@ final class SigningEntityTests: XCTestCase { organizationalUnit: "SwiftPM Test Unit Y", organization: "C" ) - XCTAssertEqual(adp1, adp2) // Only team ID (org unit) needs to match - XCTAssertNotEqual(adp1, adp3) + #expect(adp1 == adp2) // Only team ID (org unit) needs to match + #expect(adp1 != adp3) } - func testFromECKeyCertificate() throws { + @Test( + "From certificate key", + arguments: [ + (certificateFilename: "Test_ec.cer", id: "EC Key"), + (certificateFilename: "Test_rsa.cer", id: "RSA Key") + ] + ) + func fromCertificate(certificateFilename: String, id: String) throws { try fixture(name: "Signing", createGitRepo: false) { fixturePath in let certificateBytes = try readFileContents( in: fixturePath, pathComponents: "Certificates", - "Test_ec.cer" + certificateFilename ) let certificate = try Certificate(certificateBytes) let signingEntity = SigningEntity.from(certificate: certificate) guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } - XCTAssertEqual(name, certificate.subject.commonName) - XCTAssertEqual(organizationalUnit, certificate.subject.organizationalUnitName) - XCTAssertEqual(organization, certificate.subject.organizationName) - } - } - - func testFromRSAKeyCertificate() throws { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let certificateBytes = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", - "Test_rsa.cer" - ) - let certificate = try Certificate(certificateBytes) - - let signingEntity = SigningEntity.from(certificate: certificate) - guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") - } - XCTAssertEqual(name, certificate.subject.commonName) - XCTAssertEqual(organizationalUnit, certificate.subject.organizationalUnitName) - XCTAssertEqual(organization, certificate.subject.organizationName) + #expect(name == certificate.subject.commonName) + #expect(organizationalUnit == certificate.subject.organizationalUnitName) + #expect(organization == certificate.subject.organizationName) } } +} +final class SigningEntityXCTests: XCTestCase { #if os(macOS) func testFromKeychainCertificate() async throws { #if ENABLE_REAL_SIGNING_IDENTITY_TEST diff --git a/Tests/PackageSigningTests/SigningIdentityTests.swift b/Tests/PackageSigningTests/SigningIdentityTests.swift index 0f32499ba8d..b426cce6b5f 100644 --- a/Tests/PackageSigningTests/SigningIdentityTests.swift +++ b/Tests/PackageSigningTests/SigningIdentityTests.swift @@ -9,7 +9,9 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation +import Testing import XCTest import _CryptoExtras // For RSA @@ -19,8 +21,9 @@ import Crypto import _InternalTestSupport import X509 -final class SigningIdentityTests: XCTestCase { - func testSwiftSigningIdentityWithECKey() throws { +struct SigningIdentityTests { + @Test + func swiftSigningIdentityWithECKey() throws { try fixture(name: "Signing", createGitRepo: false) { fixturePath in let certificateBytes = try readFileContents( in: fixturePath, @@ -30,9 +33,9 @@ final class SigningIdentityTests: XCTestCase { let certificate = try Certificate(certificateBytes) let subject = certificate.subject - XCTAssertEqual("Test (EC) leaf", subject.commonName) - XCTAssertEqual("Test (EC) org unit", subject.organizationalUnitName) - XCTAssertEqual("Test (EC) org", subject.organizationName) + #expect("Test (EC) leaf" == subject.commonName) + #expect("Test (EC) org unit" == subject.organizationalUnitName) + #expect("Test (EC) org" == subject.organizationName) let privateKeyBytes = try readFileContents( in: fixturePath, @@ -43,17 +46,19 @@ final class SigningIdentityTests: XCTestCase { _ = SwiftSigningIdentity(certificate: certificate, privateKey: Certificate.PrivateKey(privateKey)) // Test public API - XCTAssertNoThrow( + #expect(throws: Never.self) { + try SwiftSigningIdentity( derEncodedCertificate: certificateBytes, derEncodedPrivateKey: privateKeyBytes, privateKeyType: .p256 ) - ) + } } } - func testSwiftSigningIdentityWithRSAKey() throws { + @Test + func swiftSigningIdentityWithRSAKey() throws { try fixture(name: "Signing", createGitRepo: false) { fixturePath in let certificateBytes = try readFileContents( in: fixturePath, @@ -63,9 +68,9 @@ final class SigningIdentityTests: XCTestCase { let certificate = try Certificate(certificateBytes) let subject = certificate.subject - XCTAssertEqual("Test (RSA) leaf", subject.commonName) - XCTAssertEqual("Test (RSA) org unit", subject.organizationalUnitName) - XCTAssertEqual("Test (RSA) org", subject.organizationName) + #expect("Test (RSA) leaf" == subject.commonName) + #expect("Test (RSA) org unit" == subject.organizationalUnitName) + #expect("Test (RSA) org" == subject.organizationName) let privateKeyBytes = try readFileContents( in: fixturePath, @@ -76,6 +81,8 @@ final class SigningIdentityTests: XCTestCase { _ = SwiftSigningIdentity(certificate: certificate, privateKey: Certificate.PrivateKey(privateKey)) } } +} +final class SigningIdentityXCTests: XCTestCase { #if os(macOS) func testSigningIdentityFromKeychain() async throws { diff --git a/Tests/PackageSigningTests/SigningTests.swift b/Tests/PackageSigningTests/SigningTests.swift index 67d7706c620..24d859bb12d 100644 --- a/Tests/PackageSigningTests/SigningTests.swift +++ b/Tests/PackageSigningTests/SigningTests.swift @@ -18,55 +18,76 @@ import Foundation import _InternalTestSupport import SwiftASN1 @testable import X509 // need internal APIs for OCSP testing +import Testing import XCTest -final class SigningTests: XCTestCase { - func testCMS1_0_0EndToEnd() async throws { - let keyAndCertChain = try self.ecTestKeyAndCertChain() +final class SigningXCTests: XCTestCase { + func testCMSEndToEndWithRSAKeyADPCertificate() async throws { + #if ENABLE_REAL_SIGNING_IDENTITY_TEST + #else + try XCTSkipIf(true) + #endif + + let keyAndCertChain = try rsaADPKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate - .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) + .PrivateKey(_RSA.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) ) let content = Array("per aspera ad astra".utf8) - let signatureFormat = SignatureFormat.cms_1_0_0 - let signature = try SignatureProvider.sign( + let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) + let signature = try cmsProvider.sign( content: content, identity: signingIdentity, intermediateCertificates: keyAndCertChain.intermediateCertificates, - format: signatureFormat, observabilityScope: ObservabilitySystem.NOOP ) let verifierConfiguration = VerifierConfiguration( trustedRoots: [keyAndCertChain.rootCertificate], - includeDefaultTrustStore: false, - certificateExpiration: .disabled, - certificateRevocation: .disabled + includeDefaultTrustStore: true, + certificateExpiration: .enabled(validationTime: nil), + certificateRevocation: .strict(validationTime: nil) ) - let status = try await SignatureProvider.status( + let status = try await cmsProvider.status( signature: signature, content: content, - format: signatureFormat, verifierConfiguration: verifierConfiguration, observabilityScope: ObservabilitySystem.NOOP ) - guard case .valid(let signingEntity) = status else { + guard case .valid = status else { return XCTFail("Expected signature status to be .valid but got \(status)") } - guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + + func rsaADPKeyAndCertChain() throws -> KeyAndCertChain { + try fixture(name: "Signing", createGitRepo: false) { fixturePath in + let privateKey = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "development_key.p8" + ) + let certificate = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "development.cer" + ) + + return KeyAndCertChain( + privateKey: privateKey, + certificateChain: [certificate] + ) + } } - XCTAssertEqual("Test (EC) leaf", name) - XCTAssertEqual("Test (EC) org unit", organizationalUnit) - XCTAssertEqual("Test (EC) org", organization) } - func testCMSEndToEndWithECSigningIdentity() async throws { - let keyAndCertChain = try self.ecTestKeyAndCertChain() + func testCMSEndToEndWithECKeyADPCertificate() async throws { + #if ENABLE_REAL_SIGNING_IDENTITY_TEST + #else + try XCTSkipIf(true) + #endif + + let keyAndCertChain = try ecADPKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -84,9 +105,9 @@ final class SigningTests: XCTestCase { let verifierConfiguration = VerifierConfiguration( trustedRoots: [keyAndCertChain.rootCertificate], - includeDefaultTrustStore: false, - certificateExpiration: .disabled, - certificateRevocation: .disabled + includeDefaultTrustStore: true, + certificateExpiration: .enabled(validationTime: nil), + certificateRevocation: .strict(validationTime: nil) ) let status = try await cmsProvider.status( @@ -96,109 +117,61 @@ final class SigningTests: XCTestCase { observabilityScope: ObservabilitySystem.NOOP ) - guard case .valid(let signingEntity) = status else { + guard case .valid = status else { return XCTFail("Expected signature status to be .valid but got \(status)") } - guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") - } - XCTAssertEqual("Test (EC) leaf", name) - XCTAssertEqual("Test (EC) org unit", organizationalUnit) - XCTAssertEqual("Test (EC) org", organization) - } - - func testCMSEndToEndWithRSASigningIdentity() async throws { - let keyAndCertChain = try self.rsaTestKeyAndCertChain() - let signingIdentity = SwiftSigningIdentity( - certificate: try Certificate(keyAndCertChain.leafCertificate), - privateKey: try Certificate - .PrivateKey(_RSA.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) - ) - let content = Array("per aspera ad astra".utf8) - - let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) - let signature = try cmsProvider.sign( - content: content, - identity: signingIdentity, - intermediateCertificates: keyAndCertChain.intermediateCertificates, - observabilityScope: ObservabilitySystem.NOOP - ) - - let verifierConfiguration = VerifierConfiguration( - trustedRoots: [keyAndCertChain.rootCertificate], - includeDefaultTrustStore: false, - certificateExpiration: .disabled, - certificateRevocation: .disabled - ) - let status = try await cmsProvider.status( - signature: signature, - content: content, - verifierConfiguration: verifierConfiguration, - observabilityScope: ObservabilitySystem.NOOP - ) + func ecADPKeyAndCertChain() throws -> KeyAndCertChain { + try fixture(name: "Signing", createGitRepo: false) { fixturePath in + let privateKey = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "swift_package_key.p8" + ) + let certificate = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "swift_package.cer" + ) - guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") - } - guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + return KeyAndCertChain( + privateKey: privateKey, + certificateChain: [certificate] + ) + } } - XCTAssertEqual("Test (RSA) leaf", name) - XCTAssertEqual("Test (RSA) org unit", organizationalUnit) - XCTAssertEqual("Test (RSA) org", organization) } - func testCMSWrongKeyTypeForSignatureAlgorithm() async throws { - let keyAndCertChain = try self.ecTestKeyAndCertChain() - let signingIdentity = SwiftSigningIdentity( - certificate: try Certificate(keyAndCertChain.leafCertificate), - privateKey: try Certificate - .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) - ) - let content = Array("per aspera ad astra".utf8) - - // Key is EC but signature algorithm is RSA - let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) + #if os(macOS) + func testCMS1_0_0EndToEndWithADPSigningIdentityFromKeychain() async throws { + #if ENABLE_REAL_SIGNING_IDENTITY_TEST + #else + try XCTSkipIf(true) + #endif - do { - _ = try cmsProvider.sign( - content: content, - identity: signingIdentity, - intermediateCertificates: keyAndCertChain.intermediateCertificates, - observabilityScope: ObservabilitySystem.NOOP - ) - XCTFail("Expected error") - } catch { - guard case SigningError.keyDoesNotSupportSignatureAlgorithm = error else { - return XCTFail("Expected SigningError.keyDoesNotSupportSignatureAlgorithm but got \(error)") - } + guard let label = Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"] else { + throw XCTSkip("Skipping because 'REAL_SIGNING_IDENTITY_EC_LABEL' env var is not set") } - } + let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) + let matches = identityStore.find(by: label) + XCTAssertTrue(!matches.isEmpty) - func testCMS1_0_0EndToEndWithSelfSignedCertificate() async throws { - let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() - let signingIdentity = SwiftSigningIdentity( - certificate: try Certificate(keyAndCertChain.leafCertificate), - privateKey: try Certificate - .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) - ) + let signingIdentity = matches[0] let content = Array("per aspera ad astra".utf8) let signatureFormat = SignatureFormat.cms_1_0_0 + // This call will trigger OS prompt(s) for key access let signature = try SignatureProvider.sign( content: content, identity: signingIdentity, - intermediateCertificates: keyAndCertChain.intermediateCertificates, + intermediateCertificates: [], // No need to pass intermediates for WWDR certs format: signatureFormat, observabilityScope: ObservabilitySystem.NOOP ) let verifierConfiguration = VerifierConfiguration( - trustedRoots: [keyAndCertChain.rootCertificate], - includeDefaultTrustStore: false, - certificateExpiration: .disabled, - certificateRevocation: .disabled + trustedRoots: [], + includeDefaultTrustStore: true, // WWDR roots are in the default trust store + certificateExpiration: .enabled(validationTime: nil), + certificateRevocation: .strict(validationTime: nil) ) let status = try await SignatureProvider.status( @@ -212,36 +185,50 @@ final class SigningTests: XCTestCase { guard case .valid(let signingEntity) = status else { return XCTFail("Expected signature status to be .valid but got \(status)") } - guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + switch signingEntity { + case .recognized(_, let name, let organizationalUnit, let organization): + XCTAssertNotNil(name) + XCTAssertNotNil(organizationalUnit) + XCTAssertNotNil(organization) + case .unrecognized(let name, let organizationalUnit, let organization): + XCTAssertNotNil(name) + XCTAssertNotNil(organizationalUnit) + XCTAssertNotNil(organization) } - XCTAssertEqual("Test (EC)", name) - XCTAssertEqual("Test (EC) org unit", organizationalUnit) - XCTAssertEqual("Test (EC) org", organization) } + #endif - func testCMSEndToEndWithSelfSignedECSigningIdentity() async throws { - let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() - let signingIdentity = SwiftSigningIdentity( - certificate: try Certificate(keyAndCertChain.leafCertificate), - privateKey: try Certificate - .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) - ) - let content = Array("per aspera ad astra".utf8) + #if os(macOS) + func testCMSEndToEndWithECKeyADPSigningIdentityFromKeychain() async throws { + #if ENABLE_REAL_SIGNING_IDENTITY_TEST + #else + try XCTSkipIf(true) + #endif + + guard let label = Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"] else { + throw XCTSkip("Skipping because 'REAL_SIGNING_IDENTITY_EC_LABEL' env var is not set") + } + let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) + let matches = identityStore.find(by: label) + XCTAssertTrue(!matches.isEmpty) + let signingIdentity = matches[0] + let content = Array("per aspera ad astra".utf8) let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) + + // This call will trigger OS prompt(s) for key access let signature = try cmsProvider.sign( content: content, identity: signingIdentity, - intermediateCertificates: keyAndCertChain.intermediateCertificates, + intermediateCertificates: [], // No need to pass intermediates for WWDR certs observabilityScope: ObservabilitySystem.NOOP ) let verifierConfiguration = VerifierConfiguration( - trustedRoots: [keyAndCertChain.rootCertificate], - includeDefaultTrustStore: false, - certificateExpiration: .disabled, - certificateRevocation: .disabled + trustedRoots: [], + includeDefaultTrustStore: true, // WWDR roots are in the default trust store + certificateExpiration: .enabled(validationTime: nil), + certificateRevocation: .strict(validationTime: nil) ) let status = try await cmsProvider.status( @@ -254,36 +241,50 @@ final class SigningTests: XCTestCase { guard case .valid(let signingEntity) = status else { return XCTFail("Expected signature status to be .valid but got \(status)") } - guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + switch signingEntity { + case .recognized(_, let name, let organizationalUnit, let organization): + XCTAssertNotNil(name) + XCTAssertNotNil(organizationalUnit) + XCTAssertNotNil(organization) + case .unrecognized(let name, let organizationalUnit, let organization): + XCTAssertNotNil(name) + XCTAssertNotNil(organizationalUnit) + XCTAssertNotNil(organization) } - XCTAssertEqual("Test (EC)", name) - XCTAssertEqual("Test (EC) org unit", organizationalUnit) - XCTAssertEqual("Test (EC) org", organization) } + #endif - func testCMSEndToEndWithSelfSignedRSASigningIdentity() async throws { - let keyAndCertChain = try self.rsaSelfSignedTestKeyAndCertChain() - let signingIdentity = SwiftSigningIdentity( - certificate: try Certificate(keyAndCertChain.leafCertificate), - privateKey: try Certificate - .PrivateKey(_RSA.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) - ) - let content = Array("per aspera ad astra".utf8) + #if os(macOS) + func testCMSEndToEndWithRSAKeyADPSigningIdentityFromKeychain() async throws { + #if ENABLE_REAL_SIGNING_IDENTITY_TEST + #else + try XCTSkipIf(true) + #endif + + guard let label = Environment.current["REAL_SIGNING_IDENTITY_RSA_LABEL"] else { + throw XCTSkip("Skipping because 'REAL_SIGNING_IDENTITY_RSA_LABEL' env var is not set") + } + let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) + let matches = identityStore.find(by: label) + XCTAssertTrue(!matches.isEmpty) + let signingIdentity = matches[0] + let content = Array("per aspera ad astra".utf8) let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) + + // This call will trigger OS prompt(s) for key access let signature = try cmsProvider.sign( content: content, identity: signingIdentity, - intermediateCertificates: keyAndCertChain.intermediateCertificates, + intermediateCertificates: [], // No need to pass intermediates for WWDR certs observabilityScope: ObservabilitySystem.NOOP ) let verifierConfiguration = VerifierConfiguration( - trustedRoots: [keyAndCertChain.rootCertificate], - includeDefaultTrustStore: false, - certificateExpiration: .disabled, - certificateRevocation: .disabled + trustedRoots: [], + includeDefaultTrustStore: true, // WWDR roots are in the default trust store + certificateExpiration: .enabled(validationTime: nil), + certificateRevocation: .strict(validationTime: nil) ) let status = try await cmsProvider.status( @@ -296,46 +297,58 @@ final class SigningTests: XCTestCase { guard case .valid(let signingEntity) = status else { return XCTFail("Expected signature status to be .valid but got \(status)") } - guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") - } - XCTAssertEqual("Test (RSA)", name) - XCTAssertEqual("Test (RSA) org unit", organizationalUnit) - XCTAssertEqual("Test (RSA) org", organization) - } - - func testCMSBadSignature() async throws { - let content = Array("per aspera ad astra".utf8) - let signature = Array("bad signature".utf8) + switch signingEntity { + case .recognized(_, let name, let organizationalUnit, let organization): + XCTAssertNotNil(name) + XCTAssertNotNil(organizationalUnit) + XCTAssertNotNil(organization) + case .unrecognized(let name, let organizationalUnit, let organization): + XCTAssertNotNil(name) + XCTAssertNotNil(organizationalUnit) + XCTAssertNotNil(organization) + } + } + #endif - let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) - let status = try await cmsProvider.status( - signature: signature, - content: content, - verifierConfiguration: .init(), - observabilityScope: ObservabilitySystem.NOOP - ) + private struct KeyAndCertChain { + let privateKey: [UInt8] + let certificateChain: [[UInt8]] - guard case .invalid = status else { - return XCTFail("Expected signature status to be .invalid but got \(status)") + var leafCertificate: [UInt8] { + self.certificateChain.first! + } + + var intermediateCertificates: [[UInt8]] { + guard self.certificateChain.count > 1 else { + return [] + } + return Array(self.certificateChain.dropLast(1)[1...]) + } + + var rootCertificate: [UInt8] { + self.certificateChain.last! } } - func testCMSInvalidSignature() async throws { +} + +struct SigningTests { + @Test + func cMS1_0_0EndToEnd() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) ) - let signatureContent = Array("per aspera ad astra".utf8) - let otherContent = Array("ad infinitum".utf8) + let content = Array("per aspera ad astra".utf8) - let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) - let signature = try cmsProvider.sign( - content: signatureContent, + let signatureFormat = SignatureFormat.cms_1_0_0 + let signature = try SignatureProvider.sign( + content: content, identity: signingIdentity, intermediateCertificates: keyAndCertChain.intermediateCertificates, + format: signatureFormat, observabilityScope: ObservabilitySystem.NOOP ) @@ -346,19 +359,29 @@ final class SigningTests: XCTestCase { certificateRevocation: .disabled ) - let status = try await cmsProvider.status( + let status = try await SignatureProvider.status( signature: signature, - content: otherContent, + content: content, + format: signatureFormat, verifierConfiguration: verifierConfiguration, observabilityScope: ObservabilitySystem.NOOP ) - guard case .invalid = status else { - return XCTFail("Expected signature status to be .invalid but got \(status)") + guard case .valid(let signingEntity) = status else { + Issue.record("Expected signature status to be .valid but got \(status)") + return } + guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return + } + #expect("Test (EC) leaf" == name) + #expect("Test (EC) org unit" == organizationalUnit) + #expect("Test (EC) org" == organization) } - func testCMSUntrustedCertificate() async throws { + @Test + func cMSEndToEndWithECSigningIdentity() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -376,7 +399,7 @@ final class SigningTests: XCTestCase { ) let verifierConfiguration = VerifierConfiguration( - trustedRoots: [], // trust store is empty + trustedRoots: [keyAndCertChain.rootCertificate], includeDefaultTrustStore: false, certificateExpiration: .disabled, certificateRevocation: .disabled @@ -389,21 +412,30 @@ final class SigningTests: XCTestCase { observabilityScope: ObservabilitySystem.NOOP ) - guard case .certificateNotTrusted = status else { - return XCTFail("Expected signature status to be .certificateNotTrusted but got \(status)") + guard case .valid(let signingEntity) = status else { + Issue.record("Expected signature status to be .valid but got \(status)") + return + } + guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } + #expect("Test (EC) leaf" == name) + #expect("Test (EC) org unit" == organizationalUnit) + #expect("Test (EC) org" == organization) } - func testCMSCheckCertificateValidityPeriod() async throws { - let keyAndCertChain = try self.ecTestKeyAndCertChain() + @Test + func cMSEndToEndWithRSASigningIdentity() async throws { + let keyAndCertChain = try self.rsaTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate - .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) + .PrivateKey(_RSA.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) ) let content = Array("per aspera ad astra".utf8) - let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) + let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) let signature = try cmsProvider.sign( content: content, identity: signingIdentity, @@ -411,244 +443,112 @@ final class SigningTests: XCTestCase { observabilityScope: ObservabilitySystem.NOOP ) - do { - let verifierConfiguration = VerifierConfiguration( - trustedRoots: [keyAndCertChain.rootCertificate], - includeDefaultTrustStore: false, - certificateExpiration: .enabled( - validationTime: signingIdentity.certificate.notValidBefore - .days(3) - ), - certificateRevocation: .disabled - ) + let verifierConfiguration = VerifierConfiguration( + trustedRoots: [keyAndCertChain.rootCertificate], + includeDefaultTrustStore: false, + certificateExpiration: .disabled, + certificateRevocation: .disabled + ) - let status = try await cmsProvider.status( - signature: signature, - content: content, - verifierConfiguration: verifierConfiguration, - observabilityScope: ObservabilitySystem.NOOP - ) + let status = try await cmsProvider.status( + signature: signature, + content: content, + verifierConfiguration: verifierConfiguration, + observabilityScope: ObservabilitySystem.NOOP + ) - guard case .certificateInvalid(let reason) = status else { - return XCTFail("Expected signature status to be .certificateInvalid but got \(status)") - } - XCTAssertTrue(reason.contains("not yet valid")) + guard case .valid(let signingEntity) = status else { + Issue.record("Expected signature status to be .valid but got \(status)") + return } - - do { - let verifierConfiguration = VerifierConfiguration( - trustedRoots: [keyAndCertChain.rootCertificate], - includeDefaultTrustStore: false, - certificateExpiration: .enabled( - validationTime: signingIdentity.certificate.notValidAfter + .days(3) - ), - certificateRevocation: .disabled - ) - - let status = try await cmsProvider.status( - signature: signature, - content: content, - verifierConfiguration: verifierConfiguration, - observabilityScope: ObservabilitySystem.NOOP - ) - - guard case .certificateInvalid(let reason) = status else { - return XCTFail("Expected signature status to be .certificateInvalid but got \(status)") - } - XCTAssertTrue(reason.contains("has expired")) + guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } + #expect("Test (RSA) leaf" == name) + #expect("Test (RSA) org unit" == organizationalUnit) + #expect("Test (RSA) org" == organization) } - func testCMSCheckCertificateRevocationStatus() async throws { - let leafName = try OCSPTestHelper.distinguishedName(commonName: "localhost") - let intermediateName = try OCSPTestHelper.distinguishedName(commonName: "SwiftPM Test Intermediate CA") - let caName = try OCSPTestHelper.distinguishedName(commonName: "SwiftPM Test CA") - - let leafPrivateKey = P256.Signing.PrivateKey() - let intermediatePrivateKey = P256.Signing.PrivateKey() - let caPrivateKey = P256.Signing.PrivateKey() - - let ocspResponderURI = "http://ocsp.local" - let chainWithSingleCertWithOCSP = [ - try OCSPTestHelper.certificate( - subject: leafName, - publicKey: leafPrivateKey.publicKey, - issuer: intermediateName, - issuerPrivateKey: intermediatePrivateKey, - isIntermediate: false, - isCodeSigning: true, - ocspServer: ocspResponderURI - ), - try OCSPTestHelper.certificate( - subject: intermediateName, - publicKey: intermediatePrivateKey.publicKey, - issuer: caName, - issuerPrivateKey: caPrivateKey, - isIntermediate: true, - isCodeSigning: false - ), - ] - + @Test + func cMSWrongKeyTypeForSignatureAlgorithm() async throws { + let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( - certificate: chainWithSingleCertWithOCSP[0], - privateKey: Certificate.PrivateKey(leafPrivateKey) + certificate: try Certificate(keyAndCertChain.leafCertificate), + privateKey: try Certificate + .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) ) - - let validationTime = signingIdentity.certificate.notValidAfter - .days(3) - - let ocspHandler: HTTPClient.Implementation = { request, _ in - switch (request.method, request.url) { - case (.post, URL(ocspResponderURI)): - guard let requestBody = request.body else { - throw StringError("Empty request body") - } - - let ocspRequest = try OCSPRequest(derEncoded: Array(requestBody)) - - guard let nonce = try? ocspRequest.tbsRequest.requestExtensions?.ocspNonce else { - throw StringError("Missing nonce") - } - guard let singleRequest = ocspRequest.tbsRequest.requestList.first else { - throw StringError("Missing OCSP request") - } - - let ocspResponse = try OCSPResponse.successful(.signed( - responderID: ResponderID.byName(intermediateName), - producedAt: GeneralizedTime(validationTime), - responses: [OCSPSingleResponse( - certID: singleRequest.certID, - certStatus: .unknown, - thisUpdate: GeneralizedTime(validationTime - .days(1)), - nextUpdate: GeneralizedTime(validationTime + .days(1)) - )], - privateKey: intermediatePrivateKey, - responseExtensions: { nonce } - )) - return HTTPClientResponse(statusCode: 200, body: try Data(ocspResponse.derEncodedBytes())) - default: - throw StringError("method and url should match") - } - } - let content = Array("per aspera ad astra".utf8) - let cmsProvider = CMSSignatureProvider( - signatureAlgorithm: .ecdsaP256, - customHTTPClient: HTTPClient(implementation: ocspHandler) - ) - let signature = try cmsProvider.sign( - content: content, - identity: signingIdentity, - intermediateCertificates: [], - observabilityScope: ObservabilitySystem.NOOP - ) - - // certificateRevocation = .strict doesn't allow status 'unknown' - do { - let verifierConfiguration = VerifierConfiguration( - trustedRoots: [try chainWithSingleCertWithOCSP[1].derEncodedBytes()], - includeDefaultTrustStore: false, - certificateExpiration: .disabled, - certificateRevocation: .strict(validationTime: validationTime) - ) - let status = try await cmsProvider.status( - signature: signature, - content: content, - verifierConfiguration: verifierConfiguration, - observabilityScope: ObservabilitySystem.NOOP - ) - guard case .certificateInvalid(let reason) = status else { - return XCTFail("Expected signature status to be .certificateInvalid but got \(status)") - } - XCTAssertTrue(reason.contains("status unknown")) - } + // Key is EC but signature algorithm is RSA + let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) - // certificateRevocation = .allowSoftFail allows status 'unknown' do { - let verifierConfiguration = VerifierConfiguration( - trustedRoots: [try chainWithSingleCertWithOCSP[1].derEncodedBytes()], - includeDefaultTrustStore: false, - certificateExpiration: .disabled, - certificateRevocation: .allowSoftFail(validationTime: validationTime) - ) - - let status = try await cmsProvider.status( - signature: signature, + _ = try cmsProvider.sign( content: content, - verifierConfiguration: verifierConfiguration, + identity: signingIdentity, + intermediateCertificates: keyAndCertChain.intermediateCertificates, observabilityScope: ObservabilitySystem.NOOP ) - guard case .valid = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected error") + } catch { + guard case SigningError.keyDoesNotSupportSignatureAlgorithm = error else { + Issue.record("Expected SigningError.keyDoesNotSupportSignatureAlgorithm but got \(error)") + return } } } - func testCMSEndToEndWithRSAKeyADPCertificate() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - - let keyAndCertChain = try rsaADPKeyAndCertChain() + @Test + func cMS1_0_0EndToEndWithSelfSignedCertificate() async throws { + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate - .PrivateKey(_RSA.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) + .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) ) let content = Array("per aspera ad astra".utf8) - let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) - let signature = try cmsProvider.sign( + let signatureFormat = SignatureFormat.cms_1_0_0 + let signature = try SignatureProvider.sign( content: content, identity: signingIdentity, intermediateCertificates: keyAndCertChain.intermediateCertificates, + format: signatureFormat, observabilityScope: ObservabilitySystem.NOOP ) let verifierConfiguration = VerifierConfiguration( trustedRoots: [keyAndCertChain.rootCertificate], - includeDefaultTrustStore: true, - certificateExpiration: .enabled(validationTime: nil), - certificateRevocation: .strict(validationTime: nil) + includeDefaultTrustStore: false, + certificateExpiration: .disabled, + certificateRevocation: .disabled ) - let status = try await cmsProvider.status( + let status = try await SignatureProvider.status( signature: signature, content: content, + format: signatureFormat, verifierConfiguration: verifierConfiguration, observabilityScope: ObservabilitySystem.NOOP ) - guard case .valid = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + guard case .valid(let signingEntity) = status else { + Issue.record("Expected signature status to be .valid but got \(status)") + return } + guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return + } + #expect("Test (EC)" == name) + #expect("Test (EC) org unit" == organizationalUnit) + #expect("Test (EC) org" == organization) + } - func rsaADPKeyAndCertChain() throws -> KeyAndCertChain { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let privateKey = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "development_key.p8" - ) - let certificate = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "development.cer" - ) - - return KeyAndCertChain( - privateKey: privateKey, - certificateChain: [certificate] - ) - } - } - } - - func testCMSEndToEndWithECKeyADPCertificate() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - - let keyAndCertChain = try ecADPKeyAndCertChain() + @Test + func cMSEndToEndWithSelfSignedECSigningIdentity() async throws { + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -666,9 +566,9 @@ final class SigningTests: XCTestCase { let verifierConfiguration = VerifierConfiguration( trustedRoots: [keyAndCertChain.rootCertificate], - includeDefaultTrustStore: true, - certificateExpiration: .enabled(validationTime: nil), - certificateRevocation: .strict(validationTime: nil) + includeDefaultTrustStore: false, + certificateExpiration: .disabled, + certificateRevocation: .disabled ) let status = try await cmsProvider.status( @@ -678,174 +578,145 @@ final class SigningTests: XCTestCase { observabilityScope: ObservabilitySystem.NOOP ) - guard case .valid = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + guard case .valid(let signingEntity) = status else { + Issue.record("Expected signature status to be .valid but got \(status)") + return } - - func ecADPKeyAndCertChain() throws -> KeyAndCertChain { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let privateKey = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "swift_package_key.p8" - ) - let certificate = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "swift_package.cer" - ) - - return KeyAndCertChain( - privateKey: privateKey, - certificateChain: [certificate] - ) - } + guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } + #expect("Test (EC)" == name) + #expect("Test (EC) org unit" == organizationalUnit) + #expect("Test (EC) org" == organization) } - #if os(macOS) - func testCMS1_0_0EndToEndWithADPSigningIdentityFromKeychain() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - - guard let label = Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"] else { - throw XCTSkip("Skipping because 'REAL_SIGNING_IDENTITY_EC_LABEL' env var is not set") - } - let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) - let matches = identityStore.find(by: label) - XCTAssertTrue(!matches.isEmpty) - - let signingIdentity = matches[0] + @Test + func cMSEndToEndWithSelfSignedRSASigningIdentity() async throws { + let keyAndCertChain = try self.rsaSelfSignedTestKeyAndCertChain() + let signingIdentity = SwiftSigningIdentity( + certificate: try Certificate(keyAndCertChain.leafCertificate), + privateKey: try Certificate + .PrivateKey(_RSA.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) + ) let content = Array("per aspera ad astra".utf8) - let signatureFormat = SignatureFormat.cms_1_0_0 - // This call will trigger OS prompt(s) for key access - let signature = try SignatureProvider.sign( + let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) + let signature = try cmsProvider.sign( content: content, identity: signingIdentity, - intermediateCertificates: [], // No need to pass intermediates for WWDR certs - format: signatureFormat, + intermediateCertificates: keyAndCertChain.intermediateCertificates, observabilityScope: ObservabilitySystem.NOOP ) let verifierConfiguration = VerifierConfiguration( - trustedRoots: [], - includeDefaultTrustStore: true, // WWDR roots are in the default trust store - certificateExpiration: .enabled(validationTime: nil), - certificateRevocation: .strict(validationTime: nil) + trustedRoots: [keyAndCertChain.rootCertificate], + includeDefaultTrustStore: false, + certificateExpiration: .disabled, + certificateRevocation: .disabled ) - let status = try await SignatureProvider.status( + let status = try await cmsProvider.status( signature: signature, content: content, - format: signatureFormat, verifierConfiguration: verifierConfiguration, observabilityScope: ObservabilitySystem.NOOP ) guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } - switch signingEntity { - case .recognized(_, let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) - case .unrecognized(let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) + guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } + #expect("Test (RSA)" == name) + #expect("Test (RSA) org unit" == organizationalUnit) + #expect("Test (RSA) org" == organization) } - #endif - #if os(macOS) - func testCMSEndToEndWithECKeyADPSigningIdentityFromKeychain() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif + @Test + func cMSBadSignature() async throws { + let content = Array("per aspera ad astra".utf8) + let signature = Array("bad signature".utf8) - guard let label = Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"] else { - throw XCTSkip("Skipping because 'REAL_SIGNING_IDENTITY_EC_LABEL' env var is not set") + let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) + let status = try await cmsProvider.status( + signature: signature, + content: content, + verifierConfiguration: .init(), + observabilityScope: ObservabilitySystem.NOOP + ) + + guard case .invalid = status else { + Issue.record("Expected signature status to be .invalid but got \(status)") + return } - let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) - let matches = identityStore.find(by: label) - XCTAssertTrue(!matches.isEmpty) + } - let signingIdentity = matches[0] - let content = Array("per aspera ad astra".utf8) - let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) + @Test + func cMSInvalidSignature() async throws { + let keyAndCertChain = try self.ecTestKeyAndCertChain() + let signingIdentity = SwiftSigningIdentity( + certificate: try Certificate(keyAndCertChain.leafCertificate), + privateKey: try Certificate + .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) + ) + let signatureContent = Array("per aspera ad astra".utf8) + let otherContent = Array("ad infinitum".utf8) - // This call will trigger OS prompt(s) for key access + let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) let signature = try cmsProvider.sign( - content: content, + content: signatureContent, identity: signingIdentity, - intermediateCertificates: [], // No need to pass intermediates for WWDR certs + intermediateCertificates: keyAndCertChain.intermediateCertificates, observabilityScope: ObservabilitySystem.NOOP ) let verifierConfiguration = VerifierConfiguration( - trustedRoots: [], - includeDefaultTrustStore: true, // WWDR roots are in the default trust store - certificateExpiration: .enabled(validationTime: nil), - certificateRevocation: .strict(validationTime: nil) + trustedRoots: [keyAndCertChain.rootCertificate], + includeDefaultTrustStore: false, + certificateExpiration: .disabled, + certificateRevocation: .disabled ) let status = try await cmsProvider.status( signature: signature, - content: content, + content: otherContent, verifierConfiguration: verifierConfiguration, observabilityScope: ObservabilitySystem.NOOP ) - guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") - } - switch signingEntity { - case .recognized(_, let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) - case .unrecognized(let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) + guard case .invalid = status else { + Issue.record("Expected signature status to be .invalid but got \(status)") + return } } - #endif - - #if os(macOS) - func testCMSEndToEndWithRSAKeyADPSigningIdentityFromKeychain() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - guard let label = Environment.current["REAL_SIGNING_IDENTITY_RSA_LABEL"] else { - throw XCTSkip("Skipping because 'REAL_SIGNING_IDENTITY_RSA_LABEL' env var is not set") - } - let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) - let matches = identityStore.find(by: label) - XCTAssertTrue(!matches.isEmpty) - - let signingIdentity = matches[0] + @Test + func cMSUntrustedCertificate() async throws { + let keyAndCertChain = try self.ecTestKeyAndCertChain() + let signingIdentity = SwiftSigningIdentity( + certificate: try Certificate(keyAndCertChain.leafCertificate), + privateKey: try Certificate + .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) + ) let content = Array("per aspera ad astra".utf8) - let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) - // This call will trigger OS prompt(s) for key access + let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) let signature = try cmsProvider.sign( content: content, identity: signingIdentity, - intermediateCertificates: [], // No need to pass intermediates for WWDR certs + intermediateCertificates: keyAndCertChain.intermediateCertificates, observabilityScope: ObservabilitySystem.NOOP ) let verifierConfiguration = VerifierConfiguration( - trustedRoots: [], - includeDefaultTrustStore: true, // WWDR roots are in the default trust store - certificateExpiration: .enabled(validationTime: nil), - certificateRevocation: .strict(validationTime: nil) + trustedRoots: [], // trust store is empty + includeDefaultTrustStore: false, + certificateExpiration: .disabled, + certificateRevocation: .disabled ) let status = try await cmsProvider.status( @@ -855,23 +726,209 @@ final class SigningTests: XCTestCase { observabilityScope: ObservabilitySystem.NOOP ) - guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + guard case .certificateNotTrusted = status else { + Issue.record("Expected signature status to be .certificateNotTrusted but got \(status)") + return } - switch signingEntity { - case .recognized(_, let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) - case .unrecognized(let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) + } + + @Test + func cMSCheckCertificateValidityPeriod() async throws { + let keyAndCertChain = try self.ecTestKeyAndCertChain() + let signingIdentity = SwiftSigningIdentity( + certificate: try Certificate(keyAndCertChain.leafCertificate), + privateKey: try Certificate + .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) + ) + let content = Array("per aspera ad astra".utf8) + + let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) + let signature = try cmsProvider.sign( + content: content, + identity: signingIdentity, + intermediateCertificates: keyAndCertChain.intermediateCertificates, + observabilityScope: ObservabilitySystem.NOOP + ) + + do { + let verifierConfiguration = VerifierConfiguration( + trustedRoots: [keyAndCertChain.rootCertificate], + includeDefaultTrustStore: false, + certificateExpiration: .enabled( + validationTime: signingIdentity.certificate.notValidBefore - .days(3) + ), + certificateRevocation: .disabled + ) + + let status = try await cmsProvider.status( + signature: signature, + content: content, + verifierConfiguration: verifierConfiguration, + observabilityScope: ObservabilitySystem.NOOP + ) + + guard case .certificateInvalid(let reason) = status else { + Issue.record("Expected signature status to be .certificateInvalid but got \(status)") + return + } + #expect(reason.contains("not yet valid")) + } + + do { + let verifierConfiguration = VerifierConfiguration( + trustedRoots: [keyAndCertChain.rootCertificate], + includeDefaultTrustStore: false, + certificateExpiration: .enabled( + validationTime: signingIdentity.certificate.notValidAfter + .days(3) + ), + certificateRevocation: .disabled + ) + + let status = try await cmsProvider.status( + signature: signature, + content: content, + verifierConfiguration: verifierConfiguration, + observabilityScope: ObservabilitySystem.NOOP + ) + + guard case .certificateInvalid(let reason) = status else { + Issue.record("Expected signature status to be .certificateInvalid but got \(status)") + return + } + #expect(reason.contains("has expired")) + } + } + + @Test + func cMSCheckCertificateRevocationStatus() async throws { + let leafName = try OCSPTestHelper.distinguishedName(commonName: "localhost") + let intermediateName = try OCSPTestHelper.distinguishedName(commonName: "SwiftPM Test Intermediate CA") + let caName = try OCSPTestHelper.distinguishedName(commonName: "SwiftPM Test CA") + + let leafPrivateKey = P256.Signing.PrivateKey() + let intermediatePrivateKey = P256.Signing.PrivateKey() + let caPrivateKey = P256.Signing.PrivateKey() + + let ocspResponderURI = "http://ocsp.local" + let chainWithSingleCertWithOCSP = [ + try OCSPTestHelper.certificate( + subject: leafName, + publicKey: leafPrivateKey.publicKey, + issuer: intermediateName, + issuerPrivateKey: intermediatePrivateKey, + isIntermediate: false, + isCodeSigning: true, + ocspServer: ocspResponderURI + ), + try OCSPTestHelper.certificate( + subject: intermediateName, + publicKey: intermediatePrivateKey.publicKey, + issuer: caName, + issuerPrivateKey: caPrivateKey, + isIntermediate: true, + isCodeSigning: false + ), + ] + + let signingIdentity = SwiftSigningIdentity( + certificate: chainWithSingleCertWithOCSP[0], + privateKey: Certificate.PrivateKey(leafPrivateKey) + ) + + let validationTime = signingIdentity.certificate.notValidAfter - .days(3) + + let ocspHandler: HTTPClient.Implementation = { request, _ in + switch (request.method, request.url) { + case (.post, URL(ocspResponderURI)): + guard let requestBody = request.body else { + throw StringError("Empty request body") + } + + let ocspRequest = try OCSPRequest(derEncoded: Array(requestBody)) + + guard let nonce = try? ocspRequest.tbsRequest.requestExtensions?.ocspNonce else { + throw StringError("Missing nonce") + } + guard let singleRequest = ocspRequest.tbsRequest.requestList.first else { + throw StringError("Missing OCSP request") + } + + let ocspResponse = try OCSPResponse.successful(.signed( + responderID: ResponderID.byName(intermediateName), + producedAt: GeneralizedTime(validationTime), + responses: [OCSPSingleResponse( + certID: singleRequest.certID, + certStatus: .unknown, + thisUpdate: GeneralizedTime(validationTime - .days(1)), + nextUpdate: GeneralizedTime(validationTime + .days(1)) + )], + privateKey: intermediatePrivateKey, + responseExtensions: { nonce } + )) + return HTTPClientResponse(statusCode: 200, body: try Data(ocspResponse.derEncodedBytes())) + default: + throw StringError("method and url should match") + } + } + + let content = Array("per aspera ad astra".utf8) + let cmsProvider = CMSSignatureProvider( + signatureAlgorithm: .ecdsaP256, + customHTTPClient: HTTPClient(implementation: ocspHandler) + ) + let signature = try cmsProvider.sign( + content: content, + identity: signingIdentity, + intermediateCertificates: [], + observabilityScope: ObservabilitySystem.NOOP + ) + + // certificateRevocation = .strict doesn't allow status 'unknown' + do { + let verifierConfiguration = VerifierConfiguration( + trustedRoots: [try chainWithSingleCertWithOCSP[1].derEncodedBytes()], + includeDefaultTrustStore: false, + certificateExpiration: .disabled, + certificateRevocation: .strict(validationTime: validationTime) + ) + + let status = try await cmsProvider.status( + signature: signature, + content: content, + verifierConfiguration: verifierConfiguration, + observabilityScope: ObservabilitySystem.NOOP + ) + guard case .certificateInvalid(let reason) = status else { + Issue.record("Expected signature status to be .certificateInvalid but got \(status)") + return + } + #expect(reason.contains("status unknown")) + } + + // certificateRevocation = .allowSoftFail allows status 'unknown' + do { + let verifierConfiguration = VerifierConfiguration( + trustedRoots: [try chainWithSingleCertWithOCSP[1].derEncodedBytes()], + includeDefaultTrustStore: false, + certificateExpiration: .disabled, + certificateRevocation: .allowSoftFail(validationTime: validationTime) + ) + + let status = try await cmsProvider.status( + signature: signature, + content: content, + verifierConfiguration: verifierConfiguration, + observabilityScope: ObservabilitySystem.NOOP + ) + guard case .valid = status else { + Issue.record("Expected signature status to be .valid but got \(status)") + return + } } } - #endif - func testCMS1_0_0ExtractSigningEntity() async throws { + @Test + func cMS1_0_0ExtractSigningEntity() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -903,14 +960,16 @@ final class SigningTests: XCTestCase { ) guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } - XCTAssertEqual("Test (EC) leaf", name) - XCTAssertEqual("Test (EC) org unit", organizationalUnit) - XCTAssertEqual("Test (EC) org", organization) + #expect("Test (EC) leaf" == name) + #expect("Test (EC) org unit" == organizationalUnit) + #expect("Test (EC) org" == organization) } - func testCMS1_0_0ExtractSigningEntityWithSelfSignedCertificate() async throws { + @Test + func cMS1_0_0ExtractSigningEntityWithSelfSignedCertificate() async throws { let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -942,14 +1001,16 @@ final class SigningTests: XCTestCase { ) guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } - XCTAssertEqual("Test (EC)", name) - XCTAssertEqual("Test (EC) org unit", organizationalUnit) - XCTAssertEqual("Test (EC) org", organization) + #expect("Test (EC)" == name) + #expect("Test (EC) org unit" == organizationalUnit) + #expect("Test (EC) org" == organization) } - func testCMS1_0_0ExtractSigningEntityWithUntrustedCertificate() async throws { + @Test + func cMS1_0_0ExtractSigningEntityWithUntrustedCertificate() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -980,10 +1041,11 @@ final class SigningTests: XCTestCase { format: signatureFormat, verifierConfiguration: verifierConfiguration ) - XCTFail("expected error") + Issue.record("expected error") } catch { guard case SigningError.certificateNotTrusted = error else { - return XCTFail("Expected error to be SigningError.certificateNotTrusted but got \(error)") + Issue.record("Expected error to be SigningError.certificateNotTrusted but got \(error)") + return } } } diff --git a/Tests/QueryEngineTests/QueryEngineTests.swift b/Tests/QueryEngineTests/QueryEngineTests.swift index def80727b93..01c258e8168 100644 --- a/Tests/QueryEngineTests/QueryEngineTests.swift +++ b/Tests/QueryEngineTests/QueryEngineTests.swift @@ -9,6 +9,7 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation import _AsyncFileSystem import Basics @@ -17,154 +18,157 @@ import struct Foundation.Data @testable import QueryEngine import struct SystemPackage.FilePath import _InternalTestSupport -import XCTest +import Testing private let encoder = JSONEncoder() private let decoder = JSONDecoder() private extension AsyncFileSystem { - func read(_ path: FilePath, bufferLimit: Int = 10 * 1024 * 1024, as: V.Type) async throws -> V { - let data = try await self.withOpenReadableFile(path) { - var data = Data() - for try await chunk in try await $0.read() { - data.append(contentsOf: chunk) - - assert(data.count < bufferLimit) - } - return data + func read(_ path: FilePath, bufferLimit: Int = 10 * 1024 * 1024, as: V.Type) async throws -> V { + let data = try await self.withOpenReadableFile(path) { + var data = Data() + for try await chunk in try await $0.read() { + data.append(contentsOf: chunk) + + assert(data.count < bufferLimit) + } + return data + } + + return try decoder.decode(V.self, from: data) } - return try decoder.decode(V.self, from: data) - } - - func write(_ path: FilePath, _ value: some Encodable) async throws { - let data = try encoder.encode(value) - try await self.withOpenWritableFile(path) { fileHandle in - try await fileHandle.write(data) + func write(_ path: FilePath, _ value: some Encodable) async throws { + let data = try encoder.encode(value) + try await self.withOpenWritableFile(path) { fileHandle in + try await fileHandle.write(data) + } } - } } private struct Const: CachingQuery { - let x: Int + let x: Int - func run(engine: QueryEngine) async throws -> FilePath { - let resultPath = FilePath("/Const-\(x)") - try await engine.fileSystem.write(resultPath, self.x) - return resultPath - } + func run(engine: QueryEngine) async throws -> FilePath { + let resultPath = FilePath("/Const-\(x)") + try await engine.fileSystem.write(resultPath, self.x) + return resultPath + } } private struct MultiplyByTwo: CachingQuery { - let x: Int + let x: Int - func run(engine: QueryEngine) async throws -> FilePath { - let constPath = try await engine[Const(x: self.x)].path - let constResult = try await engine.fileSystem.read(constPath, as: Int.self) + func run(engine: QueryEngine) async throws -> FilePath { + let constPath = try await engine[Const(x: self.x)].path + let constResult = try await engine.fileSystem.read(constPath, as: Int.self) - let resultPath = FilePath("/MultiplyByTwo-\(constResult)") - try await engine.fileSystem.write(resultPath, constResult * 2) - return resultPath - } + let resultPath = FilePath("/MultiplyByTwo-\(constResult)") + try await engine.fileSystem.write(resultPath, constResult * 2) + return resultPath + } } private struct AddThirty: CachingQuery { - let x: Int + let x: Int - func run(engine: QueryEngine) async throws -> FilePath { - let constPath = try await engine[Const(x: self.x)].path - let constResult = try await engine.fileSystem.read(constPath, as: Int.self) + func run(engine: QueryEngine) async throws -> FilePath { + let constPath = try await engine[Const(x: self.x)].path + let constResult = try await engine.fileSystem.read(constPath, as: Int.self) - let resultPath = FilePath("/AddThirty-\(constResult)") - try await engine.fileSystem.write(resultPath, constResult + 30) - return resultPath - } + let resultPath = FilePath("/AddThirty-\(constResult)") + try await engine.fileSystem.write(resultPath, constResult + 30) + return resultPath + } } private struct Expression: CachingQuery { - let x: Int - let y: Int + let x: Int + let y: Int - func run(engine: QueryEngine) async throws -> FilePath { - let multiplyPath = try await engine[MultiplyByTwo(x: self.x)].path - let addThirtyPath = try await engine[AddThirty(x: self.y)].path + func run(engine: QueryEngine) async throws -> FilePath { + let multiplyPath = try await engine[MultiplyByTwo(x: self.x)].path + let addThirtyPath = try await engine[AddThirty(x: self.y)].path - let multiplyResult = try await engine.fileSystem.read(multiplyPath, as: Int.self) - let addThirtyResult = try await engine.fileSystem.read(addThirtyPath, as: Int.self) + let multiplyResult = try await engine.fileSystem.read(multiplyPath, as: Int.self) + let addThirtyResult = try await engine.fileSystem.read(addThirtyPath, as: Int.self) - let resultPath = FilePath("/Expression-\(multiplyResult)-\(addThirtyResult)") - try await engine.fileSystem.write(resultPath, multiplyResult + addThirtyResult) - return resultPath - } + let resultPath = FilePath("/Expression-\(multiplyResult)-\(addThirtyResult)") + try await engine.fileSystem.write(resultPath, multiplyResult + addThirtyResult) + return resultPath + } } -final class QueryEngineTests: XCTestCase { - func testFilePathHashing() throws { - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8541") +struct QueryEngineTests { + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8541"), + .disabled(if: ProcessInfo.hostOperatingSystem == .windows), + ) + func filePathHashing() throws { + let path = "/root" - let path = "/root" + let hashEncoder1 = HashEncoder() + try hashEncoder1.encode(FilePath(path)) + let digest1 = hashEncoder1.finalize() - let hashEncoder1 = HashEncoder() - try hashEncoder1.encode(FilePath(path)) - let digest1 = hashEncoder1.finalize() + let hashEncoder2 = HashEncoder() + try hashEncoder2.encode(String(reflecting: FilePath.self)) + try hashEncoder2.encode(path) + let digest2 = hashEncoder2.finalize() - let hashEncoder2 = HashEncoder() - try hashEncoder2.encode(String(reflecting: FilePath.self)) - try hashEncoder2.encode(path) - let digest2 = hashEncoder2.finalize() + #expect(digest1 == digest2) + } - XCTAssertEqual(digest1, digest2) - } + @Test + func simpleCaching() async throws { + let observabilitySystem = ObservabilitySystem.makeForTesting() + let engine = QueryEngine( + MockFileSystem(), + observabilitySystem.topScope, + cacheLocation: .memory + ) - func testSimpleCaching() async throws { - let observabilitySystem = ObservabilitySystem.makeForTesting() - let engine = QueryEngine( - MockFileSystem(), - observabilitySystem.topScope, - cacheLocation: .memory - ) + var resultPath = try await engine[Expression(x: 1, y: 2)].path + var result = try await engine.fileSystem.read(resultPath, as: Int.self) - var resultPath = try await engine[Expression(x: 1, y: 2)].path - var result = try await engine.fileSystem.read(resultPath, as: Int.self) + #expect(result == 34) - XCTAssertEqual(result, 34) + var cacheMisses = await engine.cacheMisses + #expect(cacheMisses == 5) - var cacheMisses = await engine.cacheMisses - XCTAssertEqual(cacheMisses, 5) + var cacheHits = await engine.cacheHits + #expect(cacheHits == 0) - var cacheHits = await engine.cacheHits - XCTAssertEqual(cacheHits, 0) + resultPath = try await engine[Expression(x: 1, y: 2)].path + result = try await engine.fileSystem.read(resultPath, as: Int.self) + #expect(result == 34) - resultPath = try await engine[Expression(x: 1, y: 2)].path - result = try await engine.fileSystem.read(resultPath, as: Int.self) - XCTAssertEqual(result, 34) + cacheMisses = await engine.cacheMisses + #expect(cacheMisses == 5) - cacheMisses = await engine.cacheMisses - XCTAssertEqual(cacheMisses, 5) + cacheHits = await engine.cacheHits + #expect(cacheHits == 1) - cacheHits = await engine.cacheHits - XCTAssertEqual(cacheHits, 1) + resultPath = try await engine[Expression(x: 2, y: 1)].path + result = try await engine.fileSystem.read(resultPath, as: Int.self) + #expect(result == 35) - resultPath = try await engine[Expression(x: 2, y: 1)].path - result = try await engine.fileSystem.read(resultPath, as: Int.self) - XCTAssertEqual(result, 35) + cacheMisses = await engine.cacheMisses + #expect(cacheMisses == 8) - cacheMisses = await engine.cacheMisses - XCTAssertEqual(cacheMisses, 8) + cacheHits = await engine.cacheHits + #expect(cacheHits == 3) - cacheHits = await engine.cacheHits - XCTAssertEqual(cacheHits, 3) + resultPath = try await engine[Expression(x: 2, y: 1)].path + result = try await engine.fileSystem.read(resultPath, as: Int.self) + #expect(result == 35) - resultPath = try await engine[Expression(x: 2, y: 1)].path - result = try await engine.fileSystem.read(resultPath, as: Int.self) - XCTAssertEqual(result, 35) + cacheMisses = await engine.cacheMisses + #expect(cacheMisses == 8) - cacheMisses = await engine.cacheMisses - XCTAssertEqual(cacheMisses, 8) + cacheHits = await engine.cacheHits + #expect(cacheHits == 4) - cacheHits = await engine.cacheHits - XCTAssertEqual(cacheHits, 4) - - try await engine.shutDown() - } + try await engine.shutDown() + } } diff --git a/Tests/SourceControlTests/GitRepositoryProviderTests.swift b/Tests/SourceControlTests/GitRepositoryProviderTests.swift index d1cd1bfac99..cbc86a38f7f 100644 --- a/Tests/SourceControlTests/GitRepositoryProviderTests.swift +++ b/Tests/SourceControlTests/GitRepositoryProviderTests.swift @@ -9,17 +9,19 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation import Basics import _InternalTestSupport @testable import SourceControl -import XCTest +import Testing -class GitRepositoryProviderTests: XCTestCase { - func testIsValidDirectory() throws { - // Skipping all tests that call git on Windows. - // We have a hang in CI when running in parallel. - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) +struct GitRepositoryProviderTests { + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8564"), + .disabled(if: isSelfHostedCiEnvironment && ProcessInfo.hostOperatingSystem == .windows), + ) + func isValidDirectory() throws { try testWithTemporaryDirectory { sandbox in let provider = GitRepositoryProvider() @@ -27,40 +29,50 @@ class GitRepositoryProviderTests: XCTestCase { let repositoryPath = sandbox.appending("test") try localFileSystem.createDirectory(repositoryPath) initGitRepo(repositoryPath) - XCTAssertTrue(try provider.isValidDirectory(repositoryPath)) + #expect(try provider.isValidDirectory(repositoryPath)) // no-checkout bare repository let noCheckoutRepositoryPath = sandbox.appending("test-no-checkout") try localFileSystem.copy(from: repositoryPath.appending(".git"), to: noCheckoutRepositoryPath) - XCTAssertTrue(try provider.isValidDirectory(noCheckoutRepositoryPath)) + #expect(try provider.isValidDirectory(noCheckoutRepositoryPath)) // non-git directory let notGitPath = sandbox.appending("test-not-git") - XCTAssertThrowsError(try provider.isValidDirectory(notGitPath)) + #expect(throws: (any Error).self) { + try provider.isValidDirectory(notGitPath) + } // non-git child directory of a git directory let notGitChildPath = repositoryPath.appending("test-not-git") - XCTAssertThrowsError(try provider.isValidDirectory(notGitChildPath)) + #expect(throws: (any Error).self) { + try provider.isValidDirectory(notGitChildPath) + } } } - func testIsValidDirectoryThrowsPrintableError() throws { - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8564"), + .disabled(if: isSelfHostedCiEnvironment && ProcessInfo.hostOperatingSystem == .windows), + ) + func isValidDirectoryThrowsPrintableError() throws { try testWithTemporaryDirectory { temp in let provider = GitRepositoryProvider() let expectedErrorMessage = "not a git repository" - XCTAssertThrowsError(try provider.isValidDirectory(temp)) { error in + #expect { + try provider.isValidDirectory(temp) + } throws: { error in let errorString = String(describing: error) - XCTAssertTrue( - errorString.contains(expectedErrorMessage), - "Error string '\(errorString)' should contain '\(expectedErrorMessage)'" - ) + let matched = errorString.contains(expectedErrorMessage) + return matched } } } - func testGitShellErrorIsPrintable() throws { - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8564"), + .disabled(if: isSelfHostedCiEnvironment && ProcessInfo.hostOperatingSystem == .windows), + ) + func gitShellErrorIsPrintable() throws { let stdOut = "An error from Git - stdout" let stdErr = "An error from Git - stderr" let arguments = ["git", "error"] @@ -74,22 +86,22 @@ class GitRepositoryProviderTests: XCTestCase { ) let error = GitShellError(result: result) let errorString = "\(error)" - XCTAssertTrue( + #expect( errorString.contains(stdOut), - "Error string '\(errorString)' should contain '\(stdOut)'" - ) - XCTAssertTrue( + "Error string '\(errorString)' should contain '\(stdOut)'") + #expect( errorString.contains(stdErr), - "Error string '\(errorString)' should contain '\(stdErr)'" - ) - XCTAssertTrue( + "Error string '\(errorString)' should contain '\(stdErr)'") + #expect( errorString.contains(command), - "Error string '\(errorString)' should contain '\(command)'" - ) + "Error string '\(errorString)' should contain '\(command)'") } - func testGitShellErrorEmptyStdOut() throws { - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8564"), + .disabled(if: isSelfHostedCiEnvironment && ProcessInfo.hostOperatingSystem == .windows), + ) + func gitShellErrorEmptyStdOut() throws { let stdErr = "An error from Git - stderr" let result = AsyncProcessResult( arguments: ["git", "error"], @@ -100,14 +112,16 @@ class GitRepositoryProviderTests: XCTestCase { ) let error = GitShellError(result: result) let errorString = "\(error)" - XCTAssertTrue( + #expect( errorString.contains(stdErr), - "Error string '\(errorString)' should contain '\(stdErr)'" - ) + "Error string '\(errorString)' should contain '\(stdErr)'") } - func testGitShellErrorEmptyStdErr() throws { - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8564"), + .disabled(if: isSelfHostedCiEnvironment && ProcessInfo.hostOperatingSystem == .windows), + ) + func gitShellErrorEmptyStdErr() throws { let stdOut = "An error from Git - stdout" let result = AsyncProcessResult( arguments: ["git", "error"], @@ -118,7 +132,7 @@ class GitRepositoryProviderTests: XCTestCase { ) let error = GitShellError(result: result) let errorString = "\(error)" - XCTAssertTrue( + #expect( errorString.contains(stdOut), "Error string '\(errorString)' should contain '\(stdOut)'" ) diff --git a/Tests/SourceControlTests/InMemoryGitRepositoryTests.swift b/Tests/SourceControlTests/InMemoryGitRepositoryTests.swift index d2ab60b6b80..416fac6164f 100644 --- a/Tests/SourceControlTests/InMemoryGitRepositoryTests.swift +++ b/Tests/SourceControlTests/InMemoryGitRepositoryTests.swift @@ -9,69 +9,72 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation import Basics import SourceControl import _InternalTestSupport -import XCTest +import Testing -final class InMemoryGitRepositoryTests: XCTestCase { - func testBasics() throws { +struct InMemoryGitRepositoryTests { + @Test + func basics() throws { let fs = InMemoryFileSystem() let repo = InMemoryGitRepository(path: .root, fs: fs) try repo.createDirectory("/new-dir/subdir", recursive: true) - XCTAssertTrue(!repo.hasUncommittedChanges()) + #expect(!repo.hasUncommittedChanges()) let filePath = AbsolutePath("/new-dir/subdir").appending("new-file.txt") try repo.writeFileContents(filePath, bytes: "one") - XCTAssertEqual(try repo.readFileContents(filePath), "one") - XCTAssertTrue(repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "one") + #expect(repo.hasUncommittedChanges()) let firstCommit = try repo.commit() - XCTAssertTrue(!repo.hasUncommittedChanges()) + #expect(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "one") - XCTAssertEqual(try fs.readFileContents(filePath), "one") + #expect(try repo.readFileContents(filePath) == "one") + #expect(try fs.readFileContents(filePath) == "one") try repo.writeFileContents(filePath, bytes: "two") - XCTAssertEqual(try repo.readFileContents(filePath), "two") - XCTAssertTrue(repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "two") + #expect(repo.hasUncommittedChanges()) let secondCommit = try repo.commit() - XCTAssertTrue(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "two") + #expect(!repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "two") try repo.writeFileContents(filePath, bytes: "three") - XCTAssertTrue(repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "three") + #expect(repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "three") try repo.checkout(revision: firstCommit) - XCTAssertTrue(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "one") - XCTAssertEqual(try fs.readFileContents(filePath), "one") + #expect(!repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "one") + #expect(try fs.readFileContents(filePath) == "one") try repo.checkout(revision: secondCommit) - XCTAssertTrue(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "two") + #expect(!repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "two") - XCTAssert(try repo.getTags().isEmpty) + #expect(try repo.getTags().isEmpty) try repo.tag(name: "2.0.0") - XCTAssertEqual(try repo.getTags(), ["2.0.0"]) - XCTAssertTrue(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "two") - XCTAssertEqual(try fs.readFileContents(filePath), "two") + #expect(try repo.getTags() == ["2.0.0"]) + #expect(!repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "two") + #expect(try fs.readFileContents(filePath) == "two") try repo.checkout(revision: firstCommit) - XCTAssertTrue(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "one") + #expect(!repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "one") try repo.checkout(tag: "2.0.0") - XCTAssertTrue(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "two") + #expect(!repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "two") } - func testProvider() throws { + @Test + func provider() throws { let v1 = "1.0.0" let v2 = "2.0.0" let repo = InMemoryGitRepository(path: .root, fs: InMemoryFileSystem()) @@ -95,23 +98,23 @@ final class InMemoryGitRepositoryTests: XCTestCase { // Adding a new tag in original repo shouldn't show up in fetched repo. try repo.tag(name: "random") - XCTAssertEqual(try fooRepo.getTags().sorted(), [v1, v2]) - XCTAssert(fooRepo.exists(revision: try fooRepo.resolveRevision(tag: v1))) + #expect(try fooRepo.getTags().sorted() == [v1, v2]) + #expect(fooRepo.exists(revision: try fooRepo.resolveRevision(tag: v1))) let fooCheckoutPath = AbsolutePath("/fooCheckout") - XCTAssertFalse(try provider.workingCopyExists(at: fooCheckoutPath)) + #expect(!(try provider.workingCopyExists(at: fooCheckoutPath))) _ = try provider.createWorkingCopy(repository: specifier, sourcePath: fooRepoPath, at: fooCheckoutPath, editable: false) - XCTAssertTrue(try provider.workingCopyExists(at: fooCheckoutPath)) + #expect(try provider.workingCopyExists(at: fooCheckoutPath)) let fooCheckout = try provider.openWorkingCopy(at: fooCheckoutPath) - XCTAssertEqual(try fooCheckout.getTags().sorted(), [v1, v2]) - XCTAssert(fooCheckout.exists(revision: try fooCheckout.getCurrentRevision())) + #expect(try fooCheckout.getTags().sorted() == [v1, v2]) + #expect(fooCheckout.exists(revision: try fooCheckout.getCurrentRevision())) let checkoutRepo = try provider.openRepo(at: fooCheckoutPath) try fooCheckout.checkout(tag: v1) - XCTAssertEqual(try checkoutRepo.readFileContents(filePath), "one") + #expect(try checkoutRepo.readFileContents(filePath) == "one") try fooCheckout.checkout(tag: v2) - XCTAssertEqual(try checkoutRepo.readFileContents(filePath), "two") + #expect(try checkoutRepo.readFileContents(filePath) == "two") } } From f5fd53b6b6659a833876175c2a67472927fc6468 Mon Sep 17 00:00:00 2001 From: Sam Khouri Date: Fri, 2 May 2025 11:02:34 -0400 Subject: [PATCH 2/2] Add PR comments --- Sources/_InternalTestSupport/misc.swift | 11 + ...FilePackageSigningEntityStorageTests.swift | 44 -- Tests/PackageSigningTests/SigningTests.swift | 690 +++++++++--------- 3 files changed, 347 insertions(+), 398 deletions(-) diff --git a/Sources/_InternalTestSupport/misc.swift b/Sources/_InternalTestSupport/misc.swift index 73f5834d6f9..afcc670cc23 100644 --- a/Sources/_InternalTestSupport/misc.swift +++ b/Sources/_InternalTestSupport/misc.swift @@ -42,6 +42,17 @@ import enum TSCUtility.Git public let isInCiEnvironment = ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] != nil public let isSelfHostedCiEnvironment = ProcessInfo.processInfo.environment["SWIFTCI_IS_SELF_HOSTED"] != nil +public let isRealSigningIdentyEcLabelEnvVarSet = + ProcessInfo.processInfo.environment["REAL_SIGNING_IDENTITY_EC_LABEL"] != nil + +public let isRealSigningIdentitTestDefined = { + #if ENABLE_REAL_SIGNING_IDENTITY_TEST + return true + #else + return false + #endif +}() + /// Test helper utility for executing a block with a temporary directory. public func testWithTemporaryDirectory( function: StaticString = #function, diff --git a/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift b/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift index c9ed46468d0..8f8db5838d1 100644 --- a/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift +++ b/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift @@ -135,17 +135,6 @@ struct FilePackageSigningEntityStorageTests { } return true } - // await XCTAssertAsyncThrowsError(try storage.put( - // package: package, - // version: version, - // signingEntity: appleseed, - // origin: .registry(URL("http://foo.com")) - // )) { error in - // guard case PackageSigningEntityStorageError.conflict = error else { - // Issue.record("Expected PackageSigningEntityStorageError.conflict, got \(error)") - // return - // } - // } } @Test @@ -208,17 +197,6 @@ struct FilePackageSigningEntityStorageTests { } return true } - // await XCTAssertAsyncThrowsError(try storage.put( - // package: package, - // version: version, - // signingEntity: appleseed, - // origin: .registry(URL("http://bar.com")) // origin is different and should be added - // )) { error in - // guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { - // Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") - // return - // } - // } } @Test @@ -302,17 +280,6 @@ struct FilePackageSigningEntityStorageTests { } return true } - // await XCTAssertAsyncThrowsError(try storage.add( - // package: package, - // version: version, - // signingEntity: appleseed, - // origin: .registry(URL("http://bar.com")) - // )) { error in - // guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { - // Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") - // return - // } - // } } @Test @@ -479,17 +446,6 @@ struct FilePackageSigningEntityStorageTests { } return true } - // await XCTAsserttAsyncThrowsError(try storage.changeSigningEntityForAllVersions( - // package: package, - // version: Version("1.5.0"), - // signingEntity: appleseed, - // origin: .registry(URL("http://bar.com")) - // )) { error in - // guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { - // Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") - // return - // } - // } } } diff --git a/Tests/PackageSigningTests/SigningTests.swift b/Tests/PackageSigningTests/SigningTests.swift index 24d859bb12d..4fd1fd6db61 100644 --- a/Tests/PackageSigningTests/SigningTests.swift +++ b/Tests/PackageSigningTests/SigningTests.swift @@ -19,322 +19,10 @@ import _InternalTestSupport import SwiftASN1 @testable import X509 // need internal APIs for OCSP testing import Testing -import XCTest - -final class SigningXCTests: XCTestCase { - func testCMSEndToEndWithRSAKeyADPCertificate() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - - let keyAndCertChain = try rsaADPKeyAndCertChain() - let signingIdentity = SwiftSigningIdentity( - certificate: try Certificate(keyAndCertChain.leafCertificate), - privateKey: try Certificate - .PrivateKey(_RSA.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) - ) - let content = Array("per aspera ad astra".utf8) - - let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) - let signature = try cmsProvider.sign( - content: content, - identity: signingIdentity, - intermediateCertificates: keyAndCertChain.intermediateCertificates, - observabilityScope: ObservabilitySystem.NOOP - ) - - let verifierConfiguration = VerifierConfiguration( - trustedRoots: [keyAndCertChain.rootCertificate], - includeDefaultTrustStore: true, - certificateExpiration: .enabled(validationTime: nil), - certificateRevocation: .strict(validationTime: nil) - ) - - let status = try await cmsProvider.status( - signature: signature, - content: content, - verifierConfiguration: verifierConfiguration, - observabilityScope: ObservabilitySystem.NOOP - ) - - guard case .valid = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") - } - - func rsaADPKeyAndCertChain() throws -> KeyAndCertChain { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let privateKey = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "development_key.p8" - ) - let certificate = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "development.cer" - ) - - return KeyAndCertChain( - privateKey: privateKey, - certificateChain: [certificate] - ) - } - } - } - - func testCMSEndToEndWithECKeyADPCertificate() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - - let keyAndCertChain = try ecADPKeyAndCertChain() - let signingIdentity = SwiftSigningIdentity( - certificate: try Certificate(keyAndCertChain.leafCertificate), - privateKey: try Certificate - .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) - ) - let content = Array("per aspera ad astra".utf8) - - let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) - let signature = try cmsProvider.sign( - content: content, - identity: signingIdentity, - intermediateCertificates: keyAndCertChain.intermediateCertificates, - observabilityScope: ObservabilitySystem.NOOP - ) - - let verifierConfiguration = VerifierConfiguration( - trustedRoots: [keyAndCertChain.rootCertificate], - includeDefaultTrustStore: true, - certificateExpiration: .enabled(validationTime: nil), - certificateRevocation: .strict(validationTime: nil) - ) - - let status = try await cmsProvider.status( - signature: signature, - content: content, - verifierConfiguration: verifierConfiguration, - observabilityScope: ObservabilitySystem.NOOP - ) - - guard case .valid = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") - } - - func ecADPKeyAndCertChain() throws -> KeyAndCertChain { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let privateKey = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "swift_package_key.p8" - ) - let certificate = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "swift_package.cer" - ) - - return KeyAndCertChain( - privateKey: privateKey, - certificateChain: [certificate] - ) - } - } - } - - #if os(macOS) - func testCMS1_0_0EndToEndWithADPSigningIdentityFromKeychain() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - - guard let label = Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"] else { - throw XCTSkip("Skipping because 'REAL_SIGNING_IDENTITY_EC_LABEL' env var is not set") - } - let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) - let matches = identityStore.find(by: label) - XCTAssertTrue(!matches.isEmpty) - - let signingIdentity = matches[0] - let content = Array("per aspera ad astra".utf8) - - let signatureFormat = SignatureFormat.cms_1_0_0 - // This call will trigger OS prompt(s) for key access - let signature = try SignatureProvider.sign( - content: content, - identity: signingIdentity, - intermediateCertificates: [], // No need to pass intermediates for WWDR certs - format: signatureFormat, - observabilityScope: ObservabilitySystem.NOOP - ) - - let verifierConfiguration = VerifierConfiguration( - trustedRoots: [], - includeDefaultTrustStore: true, // WWDR roots are in the default trust store - certificateExpiration: .enabled(validationTime: nil), - certificateRevocation: .strict(validationTime: nil) - ) - - let status = try await SignatureProvider.status( - signature: signature, - content: content, - format: signatureFormat, - verifierConfiguration: verifierConfiguration, - observabilityScope: ObservabilitySystem.NOOP - ) - - guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") - } - switch signingEntity { - case .recognized(_, let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) - case .unrecognized(let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) - } - } - #endif - - #if os(macOS) - func testCMSEndToEndWithECKeyADPSigningIdentityFromKeychain() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - - guard let label = Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"] else { - throw XCTSkip("Skipping because 'REAL_SIGNING_IDENTITY_EC_LABEL' env var is not set") - } - let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) - let matches = identityStore.find(by: label) - XCTAssertTrue(!matches.isEmpty) - - let signingIdentity = matches[0] - let content = Array("per aspera ad astra".utf8) - let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) - - // This call will trigger OS prompt(s) for key access - let signature = try cmsProvider.sign( - content: content, - identity: signingIdentity, - intermediateCertificates: [], // No need to pass intermediates for WWDR certs - observabilityScope: ObservabilitySystem.NOOP - ) - - let verifierConfiguration = VerifierConfiguration( - trustedRoots: [], - includeDefaultTrustStore: true, // WWDR roots are in the default trust store - certificateExpiration: .enabled(validationTime: nil), - certificateRevocation: .strict(validationTime: nil) - ) - - let status = try await cmsProvider.status( - signature: signature, - content: content, - verifierConfiguration: verifierConfiguration, - observabilityScope: ObservabilitySystem.NOOP - ) - - guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") - } - switch signingEntity { - case .recognized(_, let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) - case .unrecognized(let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) - } - } - #endif - - #if os(macOS) - func testCMSEndToEndWithRSAKeyADPSigningIdentityFromKeychain() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - - guard let label = Environment.current["REAL_SIGNING_IDENTITY_RSA_LABEL"] else { - throw XCTSkip("Skipping because 'REAL_SIGNING_IDENTITY_RSA_LABEL' env var is not set") - } - let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) - let matches = identityStore.find(by: label) - XCTAssertTrue(!matches.isEmpty) - - let signingIdentity = matches[0] - let content = Array("per aspera ad astra".utf8) - let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) - - // This call will trigger OS prompt(s) for key access - let signature = try cmsProvider.sign( - content: content, - identity: signingIdentity, - intermediateCertificates: [], // No need to pass intermediates for WWDR certs - observabilityScope: ObservabilitySystem.NOOP - ) - - let verifierConfiguration = VerifierConfiguration( - trustedRoots: [], - includeDefaultTrustStore: true, // WWDR roots are in the default trust store - certificateExpiration: .enabled(validationTime: nil), - certificateRevocation: .strict(validationTime: nil) - ) - - let status = try await cmsProvider.status( - signature: signature, - content: content, - verifierConfiguration: verifierConfiguration, - observabilityScope: ObservabilitySystem.NOOP - ) - - guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") - } - switch signingEntity { - case .recognized(_, let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) - case .unrecognized(let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) - } - } - #endif - - private struct KeyAndCertChain { - let privateKey: [UInt8] - let certificateChain: [[UInt8]] - - var leafCertificate: [UInt8] { - self.certificateChain.first! - } - - var intermediateCertificates: [[UInt8]] { - guard self.certificateChain.count > 1 else { - return [] - } - return Array(self.certificateChain.dropLast(1)[1...]) - } - - var rootCertificate: [UInt8] { - self.certificateChain.last! - } - } - -} struct SigningTests { @Test - func cMS1_0_0EndToEnd() async throws { + func CMS1_0_0EndToEnd() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -381,7 +69,7 @@ struct SigningTests { } @Test - func cMSEndToEndWithECSigningIdentity() async throws { + func CMSEndToEndWithECSigningIdentity() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -426,7 +114,7 @@ struct SigningTests { } @Test - func cMSEndToEndWithRSASigningIdentity() async throws { + func CMSEndToEndWithRSASigningIdentity() async throws { let keyAndCertChain = try self.rsaTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -471,7 +159,7 @@ struct SigningTests { } @Test - func cMSWrongKeyTypeForSignatureAlgorithm() async throws { + func CMSWrongKeyTypeForSignatureAlgorithm() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -493,14 +181,15 @@ struct SigningTests { Issue.record("Expected error") } catch { guard case SigningError.keyDoesNotSupportSignatureAlgorithm = error else { - Issue.record("Expected SigningError.keyDoesNotSupportSignatureAlgorithm but got \(error)") + Issue.record( + "Expected SigningError.keyDoesNotSupportSignatureAlgorithm but got \(error)") return } } } @Test - func cMS1_0_0EndToEndWithSelfSignedCertificate() async throws { + func CMS1_0_0EndToEndWithSelfSignedCertificate() async throws { let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -547,7 +236,7 @@ struct SigningTests { } @Test - func cMSEndToEndWithSelfSignedECSigningIdentity() async throws { + func CMSEndToEndWithSelfSignedECSigningIdentity() async throws { let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -592,7 +281,7 @@ struct SigningTests { } @Test - func cMSEndToEndWithSelfSignedRSASigningIdentity() async throws { + func CMSEndToEndWithSelfSignedRSASigningIdentity() async throws { let keyAndCertChain = try self.rsaSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -637,7 +326,7 @@ struct SigningTests { } @Test - func cMSBadSignature() async throws { + func CMSBadSignature() async throws { let content = Array("per aspera ad astra".utf8) let signature = Array("bad signature".utf8) @@ -656,7 +345,7 @@ struct SigningTests { } @Test - func cMSInvalidSignature() async throws { + func CMSInvalidSignature() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -695,7 +384,7 @@ struct SigningTests { } @Test - func cMSUntrustedCertificate() async throws { + func CMSUntrustedCertificate() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -727,13 +416,14 @@ struct SigningTests { ) guard case .certificateNotTrusted = status else { - Issue.record("Expected signature status to be .certificateNotTrusted but got \(status)") + Issue.record( + "Expected signature status to be .certificateNotTrusted but got \(status)") return } } @Test - func cMSCheckCertificateValidityPeriod() async throws { + func CMSCheckCertificateValidityPeriod() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -768,7 +458,8 @@ struct SigningTests { ) guard case .certificateInvalid(let reason) = status else { - Issue.record("Expected signature status to be .certificateInvalid but got \(status)") + Issue.record( + "Expected signature status to be .certificateInvalid but got \(status)") return } #expect(reason.contains("not yet valid")) @@ -792,7 +483,8 @@ struct SigningTests { ) guard case .certificateInvalid(let reason) = status else { - Issue.record("Expected signature status to be .certificateInvalid but got \(status)") + Issue.record( + "Expected signature status to be .certificateInvalid but got \(status)") return } #expect(reason.contains("has expired")) @@ -800,7 +492,7 @@ struct SigningTests { } @Test - func cMSCheckCertificateRevocationStatus() async throws { + func CMSCheckCertificateRevocationStatus() async throws { let leafName = try OCSPTestHelper.distinguishedName(commonName: "localhost") let intermediateName = try OCSPTestHelper.distinguishedName(commonName: "SwiftPM Test Intermediate CA") let caName = try OCSPTestHelper.distinguishedName(commonName: "SwiftPM Test CA") @@ -839,35 +531,39 @@ struct SigningTests { let ocspHandler: HTTPClient.Implementation = { request, _ in switch (request.method, request.url) { - case (.post, URL(ocspResponderURI)): - guard let requestBody = request.body else { - throw StringError("Empty request body") - } + case (.post, URL(ocspResponderURI)): + guard let requestBody = request.body else { + throw StringError("Empty request body") + } - let ocspRequest = try OCSPRequest(derEncoded: Array(requestBody)) + let ocspRequest = try OCSPRequest(derEncoded: Array(requestBody)) - guard let nonce = try? ocspRequest.tbsRequest.requestExtensions?.ocspNonce else { - throw StringError("Missing nonce") - } - guard let singleRequest = ocspRequest.tbsRequest.requestList.first else { - throw StringError("Missing OCSP request") - } + guard let nonce = try? ocspRequest.tbsRequest.requestExtensions?.ocspNonce else { + throw StringError("Missing nonce") + } + guard let singleRequest = ocspRequest.tbsRequest.requestList.first else { + throw StringError("Missing OCSP request") + } - let ocspResponse = try OCSPResponse.successful(.signed( + let ocspResponse = try OCSPResponse.successful( + .signed( responderID: ResponderID.byName(intermediateName), producedAt: GeneralizedTime(validationTime), - responses: [OCSPSingleResponse( - certID: singleRequest.certID, - certStatus: .unknown, - thisUpdate: GeneralizedTime(validationTime - .days(1)), - nextUpdate: GeneralizedTime(validationTime + .days(1)) - )], + responses: [ + OCSPSingleResponse( + certID: singleRequest.certID, + certStatus: .unknown, + thisUpdate: GeneralizedTime(validationTime - .days(1)), + nextUpdate: GeneralizedTime(validationTime + .days(1)) + ) + ], privateKey: intermediatePrivateKey, responseExtensions: { nonce } )) - return HTTPClientResponse(statusCode: 200, body: try Data(ocspResponse.derEncodedBytes())) - default: - throw StringError("method and url should match") + return HTTPClientResponse( + statusCode: 200, body: try Data(ocspResponse.derEncodedBytes())) + default: + throw StringError("method and url should match") } } @@ -899,7 +595,8 @@ struct SigningTests { observabilityScope: ObservabilitySystem.NOOP ) guard case .certificateInvalid(let reason) = status else { - Issue.record("Expected signature status to be .certificateInvalid but got \(status)") + Issue.record( + "Expected signature status to be .certificateInvalid but got \(status)") return } #expect(reason.contains("status unknown")) @@ -927,8 +624,292 @@ struct SigningTests { } } + @Test( + .enabled(if: isRealSigningIdentitTestDefined) + ) + func CMSEndToEndWithRSAKeyADPCertificate() async throws { + let keyAndCertChain = try rsaADPKeyAndCertChain() + let signingIdentity = SwiftSigningIdentity( + certificate: try Certificate(keyAndCertChain.leafCertificate), + privateKey: try Certificate + .PrivateKey(_RSA.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) + ) + let content = Array("per aspera ad astra".utf8) + + let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) + let signature = try cmsProvider.sign( + content: content, + identity: signingIdentity, + intermediateCertificates: keyAndCertChain.intermediateCertificates, + observabilityScope: ObservabilitySystem.NOOP + ) + + let verifierConfiguration = VerifierConfiguration( + trustedRoots: [keyAndCertChain.rootCertificate], + includeDefaultTrustStore: true, + certificateExpiration: .enabled(validationTime: nil), + certificateRevocation: .strict(validationTime: nil) + ) + + let status = try await cmsProvider.status( + signature: signature, + content: content, + verifierConfiguration: verifierConfiguration, + observabilityScope: ObservabilitySystem.NOOP + ) + + guard case .valid = status else { + Issue.record("Expected signature status to be .valid but got \(status)") + return + } + + func rsaADPKeyAndCertChain() throws -> KeyAndCertChain { + try fixture(name: "Signing", createGitRepo: false) { fixturePath in + let privateKey = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "development_key.p8" + ) + let certificate = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "development.cer" + ) + + return KeyAndCertChain( + privateKey: privateKey, + certificateChain: [certificate] + ) + } + } + } + + @Test( + .enabled(if: isRealSigningIdentitTestDefined) + ) + func CMSEndToEndWithECKeyADPCertificate() async throws { + let keyAndCertChain = try ecADPKeyAndCertChain() + let signingIdentity = SwiftSigningIdentity( + certificate: try Certificate(keyAndCertChain.leafCertificate), + privateKey: try Certificate + .PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey)) + ) + let content = Array("per aspera ad astra".utf8) + + let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) + let signature = try cmsProvider.sign( + content: content, + identity: signingIdentity, + intermediateCertificates: keyAndCertChain.intermediateCertificates, + observabilityScope: ObservabilitySystem.NOOP + ) + + let verifierConfiguration = VerifierConfiguration( + trustedRoots: [keyAndCertChain.rootCertificate], + includeDefaultTrustStore: true, + certificateExpiration: .enabled(validationTime: nil), + certificateRevocation: .strict(validationTime: nil) + ) + + let status = try await cmsProvider.status( + signature: signature, + content: content, + verifierConfiguration: verifierConfiguration, + observabilityScope: ObservabilitySystem.NOOP + ) + + guard case .valid = status else { + Issue.record("Expected signature status to be .valid but got \(status)") + return + } + + func ecADPKeyAndCertChain() throws -> KeyAndCertChain { + try fixture(name: "Signing", createGitRepo: false) { fixturePath in + let privateKey = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "swift_package_key.p8" + ) + let certificate = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "swift_package.cer" + ) + + return KeyAndCertChain( + privateKey: privateKey, + certificateChain: [certificate] + ) + } + } + } + + // #if os(macOS) + @Test( + .enabled(if: ProcessInfo.hostOperatingSystem == .windows), + .enabled(if: isRealSigningIdentitTestDefined), + .enabled(if: isRealSigningIdentyEcLabelEnvVarSet), + ) + func CMS1_0_0EndToEndWithADPSigningIdentityFromKeychain() async throws { + let label = try #require(Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"]) + + let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) + let matches = identityStore.find(by: label) + #expect(!matches.isEmpty) + + let signingIdentity = matches[0] + let content = Array("per aspera ad astra".utf8) + + let signatureFormat = SignatureFormat.cms_1_0_0 + // This call will trigger OS prompt(s) for key access + let signature = try SignatureProvider.sign( + content: content, + identity: signingIdentity, + intermediateCertificates: [], // No need to pass intermediates for WWDR certs + format: signatureFormat, + observabilityScope: ObservabilitySystem.NOOP + ) + + let verifierConfiguration = VerifierConfiguration( + trustedRoots: [], + includeDefaultTrustStore: true, // WWDR roots are in the default trust store + certificateExpiration: .enabled(validationTime: nil), + certificateRevocation: .strict(validationTime: nil) + ) + + let status = try await SignatureProvider.status( + signature: signature, + content: content, + format: signatureFormat, + verifierConfiguration: verifierConfiguration, + observabilityScope: ObservabilitySystem.NOOP + ) + + guard case .valid(let signingEntity) = status else { + Issue.record("Expected signature status to be .valid but got \(status)") + return + } + switch signingEntity { + case .recognized(_, let name, let organizationalUnit, let organization): + #expect(name != nil) + #expect(organizationalUnit != nil) + #expect(organization != nil) + case .unrecognized(let name, let organizationalUnit, let organization): + #expect(name != nil) + #expect(organizationalUnit != nil) + #expect(organization != nil) + } + } + // #endif + + // #if os(macOS) + @Test( + .enabled(if: ProcessInfo.hostOperatingSystem == .windows), + .enabled(if: isRealSigningIdentitTestDefined), + .enabled(if: isRealSigningIdentyEcLabelEnvVarSet), + ) + func CMSEndToEndWithECKeyADPSigningIdentityFromKeychain() async throws { + let label = try #require(Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"]) + let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) + let matches = identityStore.find(by: label) + #expect(!matches.isEmpty) + + let signingIdentity = matches[0] + let content = Array("per aspera ad astra".utf8) + let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .ecdsaP256) + + // This call will trigger OS prompt(s) for key access + let signature = try cmsProvider.sign( + content: content, + identity: signingIdentity, + intermediateCertificates: [], // No need to pass intermediates for WWDR certs + observabilityScope: ObservabilitySystem.NOOP + ) + + let verifierConfiguration = VerifierConfiguration( + trustedRoots: [], + includeDefaultTrustStore: true, // WWDR roots are in the default trust store + certificateExpiration: .enabled(validationTime: nil), + certificateRevocation: .strict(validationTime: nil) + ) + + let status = try await cmsProvider.status( + signature: signature, + content: content, + verifierConfiguration: verifierConfiguration, + observabilityScope: ObservabilitySystem.NOOP + ) + + guard case .valid(let signingEntity) = status else { + Issue.record("Expected signature status to be .valid but got \(status)") + return + } + switch signingEntity { + case .recognized(_, let name, let organizationalUnit, let organization): + #expect(name != nil) + #expect(organizationalUnit != nil) + #expect(organization != nil) + case .unrecognized(let name, let organizationalUnit, let organization): + #expect(name != nil) + #expect(organizationalUnit != nil) + #expect(organization != nil) + } + } + // #endif + + // #if os(macOS) + @Test( + .enabled(if: ProcessInfo.hostOperatingSystem == .windows), + .enabled(if: isRealSigningIdentitTestDefined), + .enabled(if: isRealSigningIdentyEcLabelEnvVarSet), + ) + func testCMSEndToEndWithRSAKeyADPSigningIdentityFromKeychain() async throws { + let label = try #require(Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"]) + let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) + let matches = identityStore.find(by: label) + #expect(!matches.isEmpty) + + let signingIdentity = matches[0] + let content = Array("per aspera ad astra".utf8) + let cmsProvider = CMSSignatureProvider(signatureAlgorithm: .rsa) + + // This call will trigger OS prompt(s) for key access + let signature = try cmsProvider.sign( + content: content, + identity: signingIdentity, + intermediateCertificates: [], // No need to pass intermediates for WWDR certs + observabilityScope: ObservabilitySystem.NOOP + ) + + let verifierConfiguration = VerifierConfiguration( + trustedRoots: [], + includeDefaultTrustStore: true, // WWDR roots are in the default trust store + certificateExpiration: .enabled(validationTime: nil), + certificateRevocation: .strict(validationTime: nil) + ) + + let status = try await cmsProvider.status( + signature: signature, + content: content, + verifierConfiguration: verifierConfiguration, + observabilityScope: ObservabilitySystem.NOOP + ) + + guard case .valid(let signingEntity) = status else { + Issue.record("Expected signature status to be .valid but got \(status)") + return + } + switch signingEntity { + case .recognized(_, let name, let organizationalUnit, let organization): + #expect(name != nil) + #expect(organizationalUnit != nil) + #expect(organization != nil) + case .unrecognized(let name, let organizationalUnit, let organization): + #expect(name != nil) + #expect(organizationalUnit != nil) + #expect(organization != nil) + } + } + // #endif + @Test - func cMS1_0_0ExtractSigningEntity() async throws { + func CMS1_0_0ExtractSigningEntity() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -969,7 +950,7 @@ struct SigningTests { } @Test - func cMS1_0_0ExtractSigningEntityWithSelfSignedCertificate() async throws { + func CMS1_0_0ExtractSigningEntityWithSelfSignedCertificate() async throws { let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -1010,7 +991,7 @@ struct SigningTests { } @Test - func cMS1_0_0ExtractSigningEntityWithUntrustedCertificate() async throws { + func CMS1_0_0ExtractSigningEntityWithUntrustedCertificate() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -1044,7 +1025,8 @@ struct SigningTests { Issue.record("expected error") } catch { guard case SigningError.certificateNotTrusted = error else { - Issue.record("Expected error to be SigningError.certificateNotTrusted but got \(error)") + Issue.record( + "Expected error to be SigningError.certificateNotTrusted but got \(error)") return } }