10
10
11
11
import Basics
12
12
import Dispatch
13
+ import struct Foundation. Data
13
14
import struct Foundation. Date
14
15
import class Foundation. JSONDecoder
15
16
import struct Foundation. URL
16
17
17
18
import PackageCollectionsModel
19
+ import PackageCollectionsSigning
18
20
import PackageModel
19
21
import SourceControl
20
22
import TSCBasic
@@ -27,13 +29,21 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider {
27
29
private let httpClient : HTTPClient
28
30
private let decoder : JSONDecoder
29
31
private let validator : JSONModel . Validator
32
+ private let signatureValidator : PackageCollectionSignatureValidator
30
33
31
- init ( configuration: Configuration = . init( ) , httpClient: HTTPClient ? = nil , diagnosticsEngine: DiagnosticsEngine ? = nil ) {
34
+ init ( configuration: Configuration = . init( ) ,
35
+ httpClient: HTTPClient ? = nil ,
36
+ signatureValidator: PackageCollectionSignatureValidator ? = nil ,
37
+ diagnosticsEngine: DiagnosticsEngine ? = nil ) {
32
38
self . configuration = configuration
33
39
self . diagnosticsEngine = diagnosticsEngine
34
40
self . httpClient = httpClient ?? Self . makeDefaultHTTPClient ( diagnosticsEngine: diagnosticsEngine)
35
41
self . decoder = JSONDecoder . makeWithDefaults ( )
36
42
self . validator = JSONModel . Validator ( configuration: configuration. validator)
43
+ self . signatureValidator = signatureValidator ?? PackageCollectionSigning (
44
+ trustedRootCertsDir: configuration. trustedRootCertsDir,
45
+ diagnosticsEngine: diagnosticsEngine
46
+ )
37
47
}
38
48
39
49
func get( _ source: Model . CollectionSource , callback: @escaping ( Result < Model . Collection , Error > ) -> Void ) {
@@ -49,14 +59,9 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider {
49
59
if let absolutePath = source. absolutePath {
50
60
do {
51
61
let fileContents = try localFileSystem. readFileContents ( absolutePath)
52
- let collection : JSONModel . Collection = try fileContents. withData { data in
53
- do {
54
- return try self . decoder. decode ( JSONModel . Collection. self, from: data)
55
- } catch {
56
- throw Errors . invalidJSON ( error)
57
- }
62
+ return fileContents. withData { data in
63
+ self . decodeAndRunSignatureCheck ( source: source, data: data, certPolicyKey: . default, callback: callback)
58
64
}
59
- return callback ( self . makeCollection ( from: collection, source: source, signature: nil ) )
60
65
} catch {
61
66
return callback ( . failure( error) )
62
67
}
@@ -97,33 +102,40 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider {
97
102
return callback ( . failure( Errors . invalidResponse ( " Body is empty " ) ) )
98
103
}
99
104
100
- do {
101
- // parse json and construct result
102
- do {
103
- // This fails if "signature" is missing
104
- let signature = try JSONModel . SignedCollection. signature ( from: body, using: self . decoder)
105
- // TODO: Check collection's signature
106
- // If signature is
107
- // a. valid: process the collection; set isSigned=true
108
- // b. invalid: includes expired cert, untrusted cert, signature-payload mismatch => return error
109
- let collection = try JSONModel . SignedCollection. collection ( from: body, using: self . decoder)
110
- callback ( self . makeCollection ( from: collection, source: source, signature: Model . SignatureData ( from: signature) ) )
111
- } catch {
112
- // Collection is not signed
113
- guard let collection = try response. decodeBody ( JSONModel . Collection. self, using: self . decoder) else {
114
- return callback ( . failure( Errors . invalidResponse ( " Invalid body " ) ) )
115
- }
116
- callback ( self . makeCollection ( from: collection, source: source, signature: nil ) )
117
- }
118
- } catch {
119
- callback ( . failure( Errors . invalidJSON ( error) ) )
120
- }
105
+ let certPolicyKey = self . configuration. certificatePolicyKey ( for: source) ?? . default
106
+ self . decodeAndRunSignatureCheck ( source: source, data: body, certPolicyKey: certPolicyKey, callback: callback)
121
107
}
122
108
}
123
109
}
124
110
}
125
111
}
126
112
113
+ private func decodeAndRunSignatureCheck( source: Model . CollectionSource ,
114
+ data: Data ,
115
+ certPolicyKey: CertificatePolicyKey ,
116
+ callback: @escaping ( Result < Model . Collection , Error > ) -> Void ) {
117
+ do {
118
+ // This fails if collection is not signed (i.e., no "signature")
119
+ let signedCollection = try self . decoder. decode ( JSONModel . SignedCollection. self, from: data)
120
+ // Check the signature
121
+ self . signatureValidator. validate ( signedCollection: signedCollection, certPolicyKey: certPolicyKey) { result in
122
+ switch result {
123
+ case . failure( let error) :
124
+ self . diagnosticsEngine? . emit ( warning: " The signature of package collection [ \( source) ] is invalid: \( error) " )
125
+ callback ( . failure( Errors . invalidSignature) )
126
+ case . success:
127
+ callback ( self . makeCollection ( from: signedCollection. collection, source: source, signature: Model . SignatureData ( from: signedCollection. signature) ) )
128
+ }
129
+ }
130
+ } catch {
131
+ // Collection is not signed
132
+ guard let collection = try ? self . decoder. decode ( JSONModel . Collection. self, from: data) else {
133
+ return callback ( . failure( Errors . invalidJSON ( error) ) )
134
+ }
135
+ callback ( self . makeCollection ( from: collection, source: source, signature: nil ) )
136
+ }
137
+ }
138
+
127
139
private func makeCollection( from collection: JSONModel . Collection , source: Model . CollectionSource , signature: Model . SignatureData ? ) -> Result < Model . Collection , Error > {
128
140
if let errors = self . validator. validate ( collection: collection) ? . errors ( ) {
129
141
return . failure( MultipleErrors ( errors) )
@@ -240,7 +252,10 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider {
240
252
241
253
public struct Configuration {
242
254
public var maximumSizeInBytes : Int64
243
- public var validator : PackageCollectionModel . V1 . Validator . Configuration
255
+ public var trustedRootCertsDir : URL ?
256
+ public var sourceCertPolicies : [ String : CertificatePolicyKey ]
257
+
258
+ var validator : PackageCollectionModel . V1 . Validator . Configuration
244
259
245
260
public var maximumPackageCount : Int {
246
261
get {
@@ -270,22 +285,33 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider {
270
285
}
271
286
272
287
public init ( maximumSizeInBytes: Int64 ? = nil ,
288
+ trustedRootCertsDir: URL ? = nil ,
289
+ sourceCertPolicies: [ String : CertificatePolicyKey ] ? = nil ,
273
290
maximumPackageCount: Int ? = nil ,
274
291
maximumMajorVersionCount: Int ? = nil ,
275
292
maximumMinorVersionCount: Int ? = nil ) {
276
293
// TODO: where should we read defaults from?
277
294
self . maximumSizeInBytes = maximumSizeInBytes ?? 5_000_000 // 5MB
295
+ self . trustedRootCertsDir = trustedRootCertsDir
296
+ self . sourceCertPolicies = sourceCertPolicies ?? [ : ]
278
297
self . validator = JSONModel . Validator. Configuration (
279
298
maximumPackageCount: maximumPackageCount,
280
299
maximumMajorVersionCount: maximumMajorVersionCount,
281
300
maximumMinorVersionCount: maximumMinorVersionCount
282
301
)
283
302
}
303
+
304
+ func certificatePolicyKey( for source: Model . CollectionSource ) -> CertificatePolicyKey ? {
305
+ // Certificate policy is associated to a collection host
306
+ guard let host = source. url. host else { return nil }
307
+ return self . sourceCertPolicies [ host]
308
+ }
284
309
}
285
310
286
311
public enum Errors : Error {
287
312
case invalidJSON( Error )
288
313
case invalidResponse( String )
314
+ case invalidSignature
289
315
}
290
316
}
291
317
0 commit comments