Skip to content

Commit d3280c6

Browse files
committed
Add PackageSigningEntityTOFUTests
1 parent a1f17c8 commit d3280c6

File tree

3 files changed

+818
-32
lines changed

3 files changed

+818
-32
lines changed

Sources/PackageRegistry/RegistryClient.swift

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public final class RegistryClient: Cancellable {
4141
URL,
4242
(status: Result<AvailabilityStatus, Error>, expires: DispatchTime)
4343
>()
44-
44+
4545
private let metadataCache = ThreadSafeKeyValueStore<
4646
MetadataCacheKey,
4747
(metadata: Serialization.VersionMetadata, expires: DispatchTime)
@@ -339,7 +339,7 @@ public final class RegistryClient: Cancellable {
339339
completion: @escaping (Result<Serialization.VersionMetadata, Error>) -> Void
340340
) {
341341
let completion = self.makeAsync(completion, on: callbackQueue)
342-
342+
343343
let cacheKey = MetadataCacheKey(registry: registry, package: package)
344344
if let cached = self.metadataCache[cacheKey], cached.expires < .now() {
345345
return completion(.success(cached.metadata))
@@ -1195,7 +1195,7 @@ public final class RegistryClient: Cancellable {
11951195
options.authorizationProvider = self.authorizationProvider
11961196
return options
11971197
}
1198-
1198+
11991199
private struct MetadataCacheKey: Hashable {
12001200
let registry: Registry
12011201
let package: PackageIdentity.RegistryIdentity
@@ -1236,16 +1236,26 @@ public enum RegistryError: Error, CustomStringConvertible {
12361236
case packageVersionNotFound
12371237
case sourceArchiveNotSigned(registry: Registry, package: PackageIdentity, version: Version)
12381238
case failedLoadingSignature
1239-
case failedRetrievingSourceArchiveSignature(registry: Registry, package: PackageIdentity, version: Version, error: Error)
1239+
case failedRetrievingSourceArchiveSignature(
1240+
registry: Registry,
1241+
package: PackageIdentity,
1242+
version: Version,
1243+
error: Error
1244+
)
12401245
case missingConfiguration(details: String)
12411246
case missingSignatureFormat
12421247
case unknownSignatureFormat(String)
12431248
case invalidSignature(reason: String)
12441249
case invalidSigningCertificate(reason: String)
12451250
case signerNotTrusted
12461251
case failedToValidateSignature(Error)
1247-
case signingEntityForReleaseHasChanged(package: PackageIdentity, version: Version, latest: SigningEntity?, previous: SigningEntity)
1248-
case signingEntityForPackageHasChanged(package: PackageIdentity, latest: SigningEntity?, previous: SigningEntity)
1252+
case signingEntityForReleaseChanged(
1253+
package: PackageIdentity,
1254+
version: Version,
1255+
latest: SigningEntity?,
1256+
previous: SigningEntity
1257+
)
1258+
case signingEntityForPackageChanged(package: PackageIdentity, latest: SigningEntity?, previous: SigningEntity)
12491259

12501260
public var description: String {
12511261
switch self {
@@ -1335,9 +1345,9 @@ public enum RegistryError: Error, CustomStringConvertible {
13351345
return "The signer is not trusted"
13361346
case .failedToValidateSignature(let error):
13371347
return "Failed to validate signature: \(error)"
1338-
case .signingEntityForReleaseHasChanged(let package, let version, let latest, let previous):
1348+
case .signingEntityForReleaseChanged(let package, let version, let latest, let previous):
13391349
return "The signing entity '\(String(describing: latest))' for '\(package)@\(version)' is different from the previously recorded value '\(previous)'"
1340-
case .signingEntityForPackageHasChanged(let package, let latest, let previous):
1350+
case .signingEntityForPackageChanged(let package, let latest, let previous):
13411351
return "The signing entity '\(String(describing: latest))' for '\(package)' is different from the previously recorded value '\(previous)'"
13421352
}
13431353
}
@@ -1667,7 +1677,7 @@ extension RegistryClient {
16671677
public let version: String
16681678
public let resources: [Resource]
16691679
public let metadata: AdditionalMetadata?
1670-
1680+
16711681
var sourceArchive: Resource? {
16721682
self.resources.first(where: { $0.name == "source-archive" })
16731683
}
@@ -1697,7 +1707,7 @@ extension RegistryClient {
16971707
self.signing = signing
16981708
}
16991709
}
1700-
1710+
17011711
public struct Signing: Codable {
17021712
public let signatureBase64Encoded: String
17031713
public let signatureFormat: String

Sources/PackageRegistry/SigningEntityTOFU.swift

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,17 @@ struct PackageSigningEntityTOFU {
4949
) { result in
5050
switch result {
5151
case .success(let signerVersions):
52-
self.isSigningEntityOK(
52+
self.validateSigningEntity(
5353
package: package,
5454
version: version,
5555
signingEntity: signingEntity,
5656
signerVersions: signerVersions,
5757
observabilityScope: observabilityScope
58-
) { isOKResult in
59-
switch isOKResult {
58+
) { validateResult in
59+
switch validateResult {
6060
case .success(let shouldWrite):
6161
// We only use certain type(s) of signing entity for TOFU
62-
guard shouldWrite, let signingEntity = signingEntity, signingEntity.type != nil else {
62+
guard shouldWrite, let signingEntity = signingEntity, signingEntity.isRecognized else {
6363
return completion(.success(()))
6464
}
6565
self.writeToStorage(
@@ -81,7 +81,7 @@ struct PackageSigningEntityTOFU {
8181
}
8282
}
8383

84-
private func isSigningEntityOK(
84+
private func validateSigningEntity(
8585
package: PackageIdentity.RegistryIdentity,
8686
version: Version,
8787
signingEntity: SigningEntity?,
@@ -91,7 +91,7 @@ struct PackageSigningEntityTOFU {
9191
) {
9292
// Package is never signed.
9393
// If signingEntity is nil, it means package remains unsigned, which is OK. (none -> none)
94-
// Otherwise, we are now assigning a signing entity, which is also OK. (none -> some)
94+
// Otherwise, package has gained a signing entity, which is also OK. (none -> some)
9595
if signerVersions.isEmpty {
9696
return completion(.success(true))
9797
}
@@ -102,8 +102,10 @@ struct PackageSigningEntityTOFU {
102102
// e.g., change of package ownership, package author decides to stop signing releases, etc.
103103
// Instead of failing, we should allow and prompt user to add/replace/remove signing entity.
104104

105+
// We recorded the version's signer previously
105106
if let signerForVersion = signerVersions.signingEntity(of: version) {
106-
// We recorded a different signer for the given version before. This is NOT OK.
107+
// The given signer is different
108+
// TODO: This could indicate a legitimate change in package ownership
107109
guard signerForVersion == signingEntity else {
108110
return self.handleSigningEntityChanged(
109111
package: package,
@@ -115,11 +117,12 @@ struct PackageSigningEntityTOFU {
115117
completion(result.tryMap { false })
116118
}
117119
}
118-
// Signer remains the same, which could be nil, for the version.
119-
completion(.success(false))
120+
// Signer remains the same for the version
121+
return completion(.success(false))
120122
}
121123

122124
switch signingEntity {
125+
// Is the package changing from one signer to another?
123126
case .some(let signingEntity):
124127
// Check signer(s) of other version(s)
125128
if let otherSigner = signerVersions.keys.filter({ $0 != signingEntity }).first {
@@ -137,15 +140,29 @@ struct PackageSigningEntityTOFU {
137140
// Package doesn't have any other signer besides the given one, which is good.
138141
completion(.success(true))
139142
}
143+
// Or is the package going from having a signer to .none?
140144
case .none:
141145
let versionSigners = signerVersions.versionSigners
142-
let sortedSignedVersions = Array(versionSigners.keys).sorted(by: <)
143-
for signedVersion in sortedSignedVersions {
144-
// If the given version is semantically newer than any signed version,
145-
// then it must be signed. (i.e., when a package starts being signed
146-
// at a version, then all future versions must be signed.)
147-
// TODO: we might want to allow package becoming unsigned
148-
if version > signedVersion, let versionSigner = versionSigners[signedVersion] {
146+
// If the given version is semantically newer than any signed version,
147+
// then it must be signed. (i.e., when a package starts being signed
148+
// at a version, then all future versions must be signed.)
149+
// TODO: We might want to allow package becoming unsigned
150+
//
151+
// Here we try to handle the scenario where there is more than
152+
// one major version branch, and signing didn't start from the beginning
153+
// for both of them. For example, suppose a project has 1.x and 2.x active
154+
// major versions, and signing starts at 1.2.0 and 2.2.0. The first version
155+
// that SwiftPM downloads is 1.5.0, which is signed and signer gets recorded.
156+
// - When unsigned v1.1.0 is downloaded, we don't fail because it's
157+
// an older version (i.e., < 1.5.0) and we allow it to be unsigned.
158+
// - When unsigned v1.6.0 is downloaded, we fail because it's
159+
// a newer version (i.e., < 1.5.0) and we assume it to be signed.
160+
// - When unsigned v2.0.0 is downloaded, we don't fail because we haven't
161+
// seen a signed 2.x release yet, so we assume 2.x releases are not signed.
162+
let olderSignedVersions = versionSigners.keys.filter { $0.major == version.major && $0 < version }
163+
.sorted(by: >)
164+
for signedVersion in olderSignedVersions {
165+
if let versionSigner = versionSigners[signedVersion] {
149166
return self.handleSigningEntityChanged(
150167
package: package,
151168
latest: signingEntity,
@@ -156,7 +173,7 @@ struct PackageSigningEntityTOFU {
156173
}
157174
}
158175
}
159-
// Assume this is an older version before package started getting signed
176+
// Assume the given version is an older version before package started getting signed
160177
completion(.success(false))
161178
}
162179
}
@@ -208,7 +225,7 @@ struct PackageSigningEntityTOFU {
208225
) {
209226
switch self.signingEntityCheckingMode {
210227
case .strict:
211-
completion(.failure(RegistryError.signingEntityForReleaseHasChanged(
228+
completion(.failure(RegistryError.signingEntityForReleaseChanged(
212229
package: package.underlying,
213230
version: version,
214231
latest: latest,
@@ -232,10 +249,10 @@ struct PackageSigningEntityTOFU {
232249
) {
233250
switch self.signingEntityCheckingMode {
234251
case .strict:
235-
completion(.failure(
236-
RegistryError
237-
.signingEntityForPackageHasChanged(package: package.underlying, latest: latest, previous: existing)
238-
))
252+
completion(.failure(RegistryError.signingEntityForPackageChanged(
253+
package: package.underlying,
254+
latest: latest, previous: existing
255+
)))
239256
case .warn:
240257
observabilityScope
241258
.emit(

0 commit comments

Comments
 (0)