@@ -20,6 +20,12 @@ import SwiftASN1
20
20
import X509
21
21
import XCTest
22
22
23
+ #if canImport(FoundationEssentials)
24
+ import FoundationEssentials
25
+ #else
26
+ import Foundation
27
+ #endif
28
+
23
29
final class TimedCertificateReloaderTests : XCTestCase {
24
30
func testCertificatePathDoesNotExist( ) async throws {
25
31
try await runTimedCertificateReloaderTest (
@@ -58,7 +64,7 @@ final class TimedCertificateReloaderTests: XCTestCase {
58
64
func testKeyPathDoesNotExist( ) async throws {
59
65
try await runTimedCertificateReloaderTest (
60
66
certificate: . init(
61
- location: . memory( provider: { try ? Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
67
+ location: . memory( provider: { try Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
62
68
format: . der
63
69
) ,
64
70
privateKey: . init(
@@ -77,7 +83,7 @@ final class TimedCertificateReloaderTests: XCTestCase {
77
83
do {
78
84
try await runTimedCertificateReloaderTest (
79
85
certificate: . init(
80
- location: . memory( provider: { try ? Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
86
+ location: . memory( provider: { try Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
81
87
format: . der
82
88
) ,
83
89
privateKey: . init(
@@ -95,10 +101,39 @@ final class TimedCertificateReloaderTests: XCTestCase {
95
101
}
96
102
}
97
103
98
- func testCertificateIsInUnexpectedFormat( ) async throws {
104
+ func testCertificateIsInUnexpectedFormat_FromMemory( ) async throws {
105
+ try await runTimedCertificateReloaderTest (
106
+ certificate: . init(
107
+ location: . memory( provider: { try Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
108
+ format: . pem
109
+ ) ,
110
+ privateKey: . init(
111
+ location: . memory( provider: { Array ( Self . samplePrivateKey. derRepresentation) } ) ,
112
+ format: . der
113
+ )
114
+ ) { reloader in
115
+ let override = reloader. sslContextConfigurationOverride
116
+ XCTAssertNil ( override. certificateChain)
117
+ XCTAssertNil ( override. privateKey)
118
+ }
119
+ }
120
+
121
+ private func createTempFile( contents: Data ) throws -> URL {
122
+ let directory = FileManager . default. temporaryDirectory
123
+ let filename = UUID ( ) . uuidString
124
+ let fileURL = directory. appendingPathComponent ( filename)
125
+ guard FileManager . default. createFile ( atPath: fileURL. path, contents: contents) else {
126
+ throw TestError . couldNotCreateFile
127
+ }
128
+ return fileURL
129
+ }
130
+
131
+ func testCertificateIsInUnexpectedFormat_FromFile( ) async throws {
132
+ let certBytes = try Self . sampleCert. serializeAsPEM ( ) . derBytes
133
+ let file = try self . createTempFile ( contents: Data ( certBytes) )
99
134
try await runTimedCertificateReloaderTest (
100
135
certificate: . init(
101
- location: . memory ( provider : { try ? Self . sampleCert . serializeAsPEM ( ) . derBytes } ) ,
136
+ location: . file ( path : file . path ) ,
102
137
format: . pem
103
138
) ,
104
139
privateKey: . init(
@@ -112,10 +147,10 @@ final class TimedCertificateReloaderTests: XCTestCase {
112
147
}
113
148
}
114
149
115
- func testKeyIsInUnexpectedFormat ( ) async throws {
150
+ func testKeyIsInUnexpectedFormat_FromMemory ( ) async throws {
116
151
try await runTimedCertificateReloaderTest (
117
152
certificate: . init(
118
- location: . memory( provider: { try ? Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
153
+ location: . memory( provider: { try Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
119
154
format: . der
120
155
) ,
121
156
privateKey: . init(
@@ -129,10 +164,29 @@ final class TimedCertificateReloaderTests: XCTestCase {
129
164
}
130
165
}
131
166
167
+ func testKeyIsInUnexpectedFormat_FromFile( ) async throws {
168
+ let keyBytes = Self . samplePrivateKey. derRepresentation
169
+ let file = try self . createTempFile ( contents: keyBytes)
170
+ try await runTimedCertificateReloaderTest (
171
+ certificate: . init(
172
+ location: . memory( provider: { try Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
173
+ format: . der
174
+ ) ,
175
+ privateKey: . init(
176
+ location: . file( path: file. path) ,
177
+ format: . pem
178
+ )
179
+ ) { reloader in
180
+ let override = reloader. sslContextConfigurationOverride
181
+ XCTAssertNil ( override. certificateChain)
182
+ XCTAssertNil ( override. privateKey)
183
+ }
184
+ }
185
+
132
186
func testCertificateAndKeyDoNotMatch( ) async throws {
133
187
try await runTimedCertificateReloaderTest (
134
188
certificate: . init(
135
- location: . memory( provider: { try ? Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
189
+ location: . memory( provider: { try Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
136
190
format: . der
137
191
) ,
138
192
privateKey: . init(
@@ -146,17 +200,31 @@ final class TimedCertificateReloaderTests: XCTestCase {
146
200
}
147
201
}
148
202
149
- func testReloadSuccessfully( ) async throws {
150
- let certificateBox : NIOLockedValueBox < [ UInt8 ] ? > = NIOLockedValueBox ( nil )
203
+ enum TestError : Error {
204
+ case emptyCertificate
205
+ case emptyPrivateKey
206
+ case couldNotCreateFile
207
+ }
208
+
209
+ func testReloadSuccessfully_FromMemory( ) async throws {
210
+ let certificateBox : NIOLockedValueBox < [ UInt8 ] > = NIOLockedValueBox ( [ ] )
151
211
try await runTimedCertificateReloaderTest (
152
212
certificate: . init(
153
- location: . memory( provider: { certificateBox. withLockedValue ( { $0 } ) } ) ,
213
+ location: . memory( provider: {
214
+ let cert = certificateBox. withLockedValue ( { $0 } )
215
+ if cert. isEmpty {
216
+ throw TestError . emptyCertificate
217
+ }
218
+ return cert
219
+ } ) ,
154
220
format: . der
155
221
) ,
156
222
privateKey: . init(
157
223
location: . memory( provider: { Array ( Self . samplePrivateKey. derRepresentation) } ) ,
158
224
format: . der
159
- )
225
+ ) ,
226
+ // We need to disable validation because the provider will initially be empty.
227
+ validateSources: false
160
228
) { reloader in
161
229
// On first attempt, we should have no certificate or private key overrides available,
162
230
// since the certificate box is empty.
@@ -183,13 +251,61 @@ final class TimedCertificateReloaderTests: XCTestCase {
183
251
}
184
252
}
185
253
254
+ func testReloadSuccessfully_FromFile( ) async throws {
255
+ // Start with empty files.
256
+ let certificateFile = try self . createTempFile ( contents: Data ( ) )
257
+ let privateKeyFile = try self . createTempFile ( contents: Data ( ) )
258
+ try await runTimedCertificateReloaderTest (
259
+ certificate: . init(
260
+ location: . file( path: certificateFile. path) ,
261
+ format: . der
262
+ ) ,
263
+ privateKey: . init(
264
+ location: . file( path: privateKeyFile. path) ,
265
+ format: . der
266
+ ) ,
267
+ // We need to disable validation because the files will not initially have any contents.
268
+ validateSources: false
269
+ ) { reloader in
270
+ // On first attempt, we should have no certificate or private key overrides available,
271
+ // since the certificate box is empty.
272
+ var override = reloader. sslContextConfigurationOverride
273
+ XCTAssertNil ( override. certificateChain)
274
+ XCTAssertNil ( override. privateKey)
275
+
276
+ // Update the files to contain data
277
+ try Data ( try Self . sampleCert. serializeAsPEM ( ) . derBytes) . write ( to: certificateFile)
278
+ try Self . samplePrivateKey. derRepresentation. write ( to: privateKeyFile)
279
+
280
+ // Give the reload loop some time to run and update the cert-key pair.
281
+ try await Task . sleep ( for: . milliseconds( 100 ) , tolerance: . zero)
282
+
283
+ // Now the overrides should be present.
284
+ override = reloader. sslContextConfigurationOverride
285
+ XCTAssertEqual (
286
+ override. certificateChain,
287
+ [ . certificate( try . init( bytes: Self . sampleCert. serializeAsPEM ( ) . derBytes, format: . der) ) ]
288
+ )
289
+ XCTAssertEqual (
290
+ override. privateKey,
291
+ . privateKey( try . init( bytes: Array ( Self . samplePrivateKey. derRepresentation) , format: . der) )
292
+ )
293
+ }
294
+ }
295
+
186
296
func testCertificateNotFoundAtReload( ) async throws {
187
- let certificateBox : NIOLockedValueBox < [ UInt8 ] ? > = NIOLockedValueBox (
297
+ let certificateBox : NIOLockedValueBox < [ UInt8 ] > = NIOLockedValueBox (
188
298
try ! Self . sampleCert. serializeAsPEM ( ) . derBytes
189
299
)
190
300
try await runTimedCertificateReloaderTest (
191
301
certificate: . init(
192
- location: . memory( provider: { certificateBox. withLockedValue ( { $0 } ) } ) ,
302
+ location: . memory( provider: {
303
+ let cert = certificateBox. withLockedValue ( { $0 } )
304
+ if cert. isEmpty {
305
+ throw TestError . emptyCertificate
306
+ }
307
+ return cert
308
+ } ) ,
193
309
format: . der
194
310
) ,
195
311
privateKey: . init(
@@ -208,8 +324,8 @@ final class TimedCertificateReloaderTests: XCTestCase {
208
324
. privateKey( try . init( bytes: Array ( Self . samplePrivateKey. derRepresentation) , format: . der) )
209
325
)
210
326
211
- // Update the box to not contain a certificate .
212
- certificateBox. withLockedValue ( { $0 = nil } )
327
+ // Update the box to contain empty bytes: this will cause the provider to throw .
328
+ certificateBox. withLockedValue ( { $0 = [ ] } )
213
329
214
330
// Give the reload loop some time to run and update the cert-key pair.
215
331
try await Task . sleep ( for: . milliseconds( 100 ) , tolerance: . zero)
@@ -228,16 +344,22 @@ final class TimedCertificateReloaderTests: XCTestCase {
228
344
}
229
345
230
346
func testKeyNotFoundAtReload( ) async throws {
231
- let keyBox : NIOLockedValueBox < [ UInt8 ] ? > = NIOLockedValueBox (
347
+ let keyBox : NIOLockedValueBox < [ UInt8 ] > = NIOLockedValueBox (
232
348
Array ( Self . samplePrivateKey. derRepresentation)
233
349
)
234
350
try await runTimedCertificateReloaderTest (
235
351
certificate: . init(
236
- location: . memory( provider: { try ! Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
352
+ location: . memory( provider: { try Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
237
353
format: . der
238
354
) ,
239
355
privateKey: . init(
240
- location: . memory( provider: { keyBox. withLockedValue ( { $0 } ) } ) ,
356
+ location: . memory( provider: {
357
+ let key = keyBox. withLockedValue ( { $0 } )
358
+ if key. isEmpty {
359
+ throw TestError . emptyPrivateKey
360
+ }
361
+ return key
362
+ } ) ,
241
363
format: . der
242
364
)
243
365
) { reloader in
@@ -252,8 +374,8 @@ final class TimedCertificateReloaderTests: XCTestCase {
252
374
. privateKey( try . init( bytes: Array ( Self . samplePrivateKey. derRepresentation) , format: . der) )
253
375
)
254
376
255
- // Update the box to not contain a key .
256
- keyBox. withLockedValue ( { $0 = nil } )
377
+ // Update the box to contain empty bytes: this will cause the provider to throw .
378
+ keyBox. withLockedValue ( { $0 = [ ] } )
257
379
258
380
// Give the reload loop some time to run and update the cert-key pair.
259
381
try await Task . sleep ( for: . milliseconds( 100 ) , tolerance: . zero)
@@ -272,12 +394,12 @@ final class TimedCertificateReloaderTests: XCTestCase {
272
394
}
273
395
274
396
func testCertificateAndKeyDoNotMatchOnReload( ) async throws {
275
- let keyBox : NIOLockedValueBox < [ UInt8 ] ? > = NIOLockedValueBox (
397
+ let keyBox : NIOLockedValueBox < [ UInt8 ] > = NIOLockedValueBox (
276
398
Array ( Self . samplePrivateKey. derRepresentation)
277
399
)
278
400
try await runTimedCertificateReloaderTest (
279
401
certificate: . init(
280
- location: . memory( provider: { try ! Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
402
+ location: . memory( provider: { try Self . sampleCert. serializeAsPEM ( ) . derBytes } ) ,
281
403
format: . der
282
404
) ,
283
405
privateKey: . init(
0 commit comments