Skip to content

Commit 4d2f75d

Browse files
committed
Include signing entity in version metadata
Clients who are asking for metadata prior to download an archive from the registry may still want to display information from the signature which is non-trivial to extract. This includes it if possible.
1 parent af783e1 commit 4d2f75d

File tree

5 files changed

+122
-23
lines changed

5 files changed

+122
-23
lines changed

Sources/PackageMetadata/PackageMetadata.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import SourceControl
2020
import struct Foundation.URL
2121
import struct TSCBasic.AbsolutePath
2222
import protocol TSCBasic.FileSystem
23+
import var TSCBasic.localFileSystem
2324
import func TSCBasic.withTemporaryDirectory
2425
import struct TSCUtility.Version
2526

@@ -87,6 +88,7 @@ public struct PackageSearchClient {
8788
self.registryClient.getPackageVersionMetadata(
8889
package: package,
8990
version: version,
91+
fileSystem: localFileSystem,
9092
observabilityScope: observabilityScope,
9193
callbackQueue: DispatchQueue.sharedConcurrent
9294
) { result in

Sources/PackageRegistry/RegistryClient.swift

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ public final class RegistryClient: Cancellable {
266266
package: PackageIdentity,
267267
version: Version,
268268
timeout: DispatchTimeInterval? = .none,
269+
fileSystem: FileSystem,
269270
observabilityScope: ObservabilityScope,
270271
callbackQueue: DispatchQueue,
271272
completion: @escaping (Result<PackageVersionMetadata, Error>) -> Void
@@ -286,6 +287,7 @@ public final class RegistryClient: Cancellable {
286287
package: registryIdentity,
287288
version: version,
288289
timeout: timeout,
290+
fileSystem: fileSystem,
289291
observabilityScope: observabilityScope,
290292
callbackQueue: callbackQueue,
291293
completion: completion
@@ -314,6 +316,7 @@ public final class RegistryClient: Cancellable {
314316
package: PackageIdentity.RegistryIdentity,
315317
version: Version,
316318
timeout: DispatchTimeInterval?,
319+
fileSystem: FileSystem,
317320
observabilityScope: ObservabilityScope,
318321
callbackQueue: DispatchQueue,
319322
completion: @escaping (Result<PackageVersionMetadata, Error>) -> Void
@@ -343,6 +346,22 @@ public final class RegistryClient: Cancellable {
343346
signatureBase64Encoded: $0.signatureBase64Encoded,
344347
signatureFormat: $0.signatureFormat
345348
)
349+
},
350+
signingEntity: $0.signing.flatMap {
351+
guard let signatureData = Data(base64Encoded: $0.signatureBase64Encoded) else {
352+
return nil
353+
}
354+
guard let signatureFormat = SignatureFormat(rawValue: $0.signatureFormat) else {
355+
return nil
356+
}
357+
let configuration = self.configuration.signing(for: package, registry: registry)
358+
return try? tsc_await { SignatureValidation.extractSigningEntity(
359+
signature: [UInt8](signatureData),
360+
signatureFormat: signatureFormat,
361+
configuration: configuration,
362+
fileSystem: fileSystem,
363+
completion: $0
364+
) }
346365
}
347366
)
348367
},
@@ -501,6 +520,7 @@ public final class RegistryClient: Cancellable {
501520
package: package,
502521
version: version,
503522
timeout: timeout,
523+
fileSystem: localFileSystem,
504524
observabilityScope: observabilityScope,
505525
callbackQueue: callbackQueue
506526
) { result in
@@ -733,6 +753,7 @@ public final class RegistryClient: Cancellable {
733753
package: package,
734754
version: version,
735755
timeout: timeout,
756+
fileSystem: localFileSystem,
736757
observabilityScope: observabilityScope,
737758
callbackQueue: callbackQueue
738759
) { result in
@@ -950,6 +971,7 @@ public final class RegistryClient: Cancellable {
950971
package: package,
951972
version: version,
952973
timeout: timeout,
974+
fileSystem: fileSystem,
953975
observabilityScope: observabilityScope,
954976
callbackQueue: callbackQueue
955977
) { result in
@@ -1839,12 +1861,14 @@ extension RegistryClient {
18391861
public let type: String
18401862
public let checksum: String?
18411863
public let signing: Signing?
1864+
public let signingEntity: SigningEntity?
18421865

1843-
public init(name: String, type: String, checksum: String?, signing: Signing?) {
1866+
public init(name: String, type: String, checksum: String?, signing: Signing?, signingEntity: SigningEntity?) {
18441867
self.name = name
18451868
self.type = type
18461869
self.checksum = checksum
18471870
self.signing = signing
1871+
self.signingEntity = signingEntity
18481872
}
18491873
}
18501874

Sources/PackageRegistry/SignatureValidation.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,30 @@ struct SignatureValidation {
513513
}
514514
}
515515
}
516+
517+
// MARK: - signing entity
518+
519+
static func extractSigningEntity(
520+
signature: [UInt8],
521+
signatureFormat: SignatureFormat,
522+
configuration: RegistryConfiguration.Security.Signing,
523+
fileSystem: FileSystem,
524+
completion: @Sendable @escaping (Result<SigningEntity?, Error>) -> Void
525+
) {
526+
Task {
527+
do {
528+
let verifierConfiguration = try VerifierConfiguration.from(configuration, fileSystem: fileSystem)
529+
let signingEntity = try await SignatureProvider.extractSigningEntity(
530+
signature: signature,
531+
format: signatureFormat,
532+
verifierConfiguration: verifierConfiguration
533+
)
534+
return completion(.success(signingEntity))
535+
} catch {
536+
return completion(.failure(error))
537+
}
538+
}
539+
}
516540
}
517541

518542
extension VerifierConfiguration {

Sources/PackageSigning/SignatureProvider.swift

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ public enum SignatureProvider {
5555
observabilityScope: observabilityScope
5656
)
5757
}
58+
59+
public static func extractSigningEntity(
60+
signature: [UInt8],
61+
format: SignatureFormat,
62+
verifierConfiguration: VerifierConfiguration
63+
) async throws -> SigningEntity {
64+
let provider = format.provider
65+
return try await provider.extractSigningEntity(
66+
signature: signature,
67+
verifierConfiguration: verifierConfiguration
68+
)
69+
}
5870
}
5971

6072
public struct VerifierConfiguration {
@@ -162,6 +174,11 @@ protocol SignatureProviderProtocol {
162174
verifierConfiguration: VerifierConfiguration,
163175
observabilityScope: ObservabilityScope
164176
) async throws -> SignatureStatus
177+
178+
func extractSigningEntity(
179+
signature: [UInt8],
180+
verifierConfiguration: VerifierConfiguration
181+
) async throws -> SigningEntity
165182
}
166183

167184
// MARK: - CMS signature provider
@@ -232,34 +249,66 @@ struct CMSSignatureProvider: SignatureProviderProtocol {
232249
}
233250
}
234251

252+
private func isValidSignature(
253+
signature: [UInt8],
254+
content: [UInt8],
255+
verifierConfiguration: VerifierConfiguration
256+
) async throws -> CMS.SignatureVerificationResult {
257+
var trustRoots: [Certificate] = []
258+
if verifierConfiguration.includeDefaultTrustStore {
259+
trustRoots.append(contentsOf: CertificateStores.defaultTrustRoots)
260+
}
261+
trustRoots.append(contentsOf: try verifierConfiguration.trustedRoots.map { try Certificate($0) })
262+
263+
return await CMS.isValidSignature(
264+
dataBytes: content,
265+
signatureBytes: signature,
266+
// The intermediates supplied here will be combined with those
267+
// included in the signature to build cert chain for validation.
268+
//
269+
// Those who use ADP certs for signing are not required to provide
270+
// the entire cert chain, thus we must supply WWDR intermediates
271+
// here so that the chain can be constructed during validation.
272+
// Whether the signing cert is trusted still depends on whether
273+
// the WWDR roots are in the trust store or not, which by default
274+
// they are but user may disable that through configuration.
275+
additionalIntermediateCertificates: Certificates.wwdrIntermediates,
276+
trustRoots: CertificateStore(trustRoots),
277+
policy: self.buildPolicySet(configuration: verifierConfiguration, httpClient: self.httpClient)
278+
)
279+
}
280+
281+
func extractSigningEntity(
282+
signature: [UInt8],
283+
verifierConfiguration: VerifierConfiguration
284+
) async throws -> SigningEntity {
285+
let result = try await isValidSignature(
286+
signature: signature,
287+
content: [],
288+
verifierConfiguration: verifierConfiguration
289+
)
290+
291+
switch result {
292+
case .success(let valid):
293+
return SigningEntity.from(certificate: valid.signer)
294+
case .failure(CMS.VerificationError.unableToValidateSigner(let invalid)):
295+
return SigningEntity.from(certificate: invalid.signer)
296+
case .failure(let error):
297+
throw error
298+
}
299+
}
300+
235301
func status(
236302
signature: [UInt8],
237303
content: [UInt8],
238304
verifierConfiguration: VerifierConfiguration,
239305
observabilityScope: ObservabilityScope
240306
) async throws -> SignatureStatus {
241307
do {
242-
var trustRoots: [Certificate] = []
243-
if verifierConfiguration.includeDefaultTrustStore {
244-
trustRoots.append(contentsOf: CertificateStores.defaultTrustRoots)
245-
}
246-
trustRoots.append(contentsOf: try verifierConfiguration.trustedRoots.map { try Certificate($0) })
247-
248-
let result = await CMS.isValidSignature(
249-
dataBytes: content,
250-
signatureBytes: signature,
251-
// The intermediates supplied here will be combined with those
252-
// included in the signature to build cert chain for validation.
253-
//
254-
// Those who use ADP certs for signing are not required to provide
255-
// the entire cert chain, thus we must supply WWDR intermediates
256-
// here so that the chain can be constructed during validation.
257-
// Whether the signing cert is trusted still depends on whether
258-
// the WWDR roots are in the trust store or not, which by default
259-
// they are but user may disable that through configuration.
260-
additionalIntermediateCertificates: Certificates.wwdrIntermediates,
261-
trustRoots: CertificateStore(trustRoots),
262-
policy: self.buildPolicySet(configuration: verifierConfiguration, httpClient: self.httpClient)
308+
let result = try await isValidSignature(
309+
signature: signature,
310+
content: content,
311+
verifierConfiguration: verifierConfiguration
263312
)
264313

265314
switch result {

Sources/PackageSigning/SigningEntity/SigningEntity.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
// MARK: - SigningEntity is the entity that generated the signature
1717

18-
public enum SigningEntity: Hashable, Codable, CustomStringConvertible {
18+
public enum SigningEntity: Hashable, Codable, CustomStringConvertible, Sendable {
1919
case recognized(type: SigningEntityType, name: String, organizationalUnit: String, organization: String)
2020
case unrecognized(name: String?, organizationalUnit: String?, organization: String?)
2121

0 commit comments

Comments
 (0)