Skip to content

Commit 7486785

Browse files
Merge pull request #86 from dimitribouniol/dimitri/sendable
Missing Hashable and Sendable Conformances
2 parents e82b8d3 + 47c3bf9 commit 7486785

File tree

4 files changed

+39
-32
lines changed

4 files changed

+39
-32
lines changed

Sources/AppStoreServerLibrary/AppStoreServerAPIClient.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import NIOFoundationCompat
99

1010
public class AppStoreServerAPIClient {
1111

12-
public enum ConfigurationError: Error {
12+
public enum ConfigurationError: Error, Hashable, Sendable {
1313
/// Xcode is not a supported environment for an AppStoreServerAPIClient
1414
case invalidEnvironment
1515
}
@@ -476,12 +476,12 @@ public class AppStoreServerAPIClient {
476476
private struct APIFetchError: Error {}
477477
}
478478

479-
public enum APIResult<T> {
479+
public enum APIResult<T: Sendable>: Sendable {
480480
case success(response: T)
481481
case failure(statusCode: Int?, rawApiError: Int64?, apiError: APIError?, errorMessage: String?, causedBy: Error?)
482482
}
483483

484-
public enum APIError: Int64 {
484+
public enum APIError: Int64, Hashable, Sendable {
485485
///An error that indicates an invalid request.
486486
///
487487
///[GeneralBadRequestError](https://developer.apple.com/documentation/appstoreserverapi/generalbadrequesterror)
@@ -859,7 +859,7 @@ public enum APIError: Int64 {
859859
case generalInternalRetryable = 5000001
860860
}
861861

862-
public enum GetTransactionHistoryVersion: String {
862+
public enum GetTransactionHistoryVersion: String, Hashable, Sendable {
863863
@available(*, deprecated)
864864
case v1 = "v1"
865865
case v2 = "v2"

Sources/AppStoreServerLibrary/ChainVerifier.swift

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import Crypto
88
import AsyncHTTPClient
99
import NIOFoundationCompat
1010

11-
class ChainVerifier {
12-
11+
actor ChainVerifier {
1312
private static let EXPECTED_CHAIN_LENGTH = 3
1413
private static let EXPECTED_JWT_SEGMENTS = 3
1514
private static let EXPECTED_ALGORITHM = "ES256"
@@ -28,7 +27,7 @@ class ChainVerifier {
2827
self.verifiedPublicKeyCache = [:]
2928
}
3029

31-
func verify<T: DecodedSignedData>(signedData: String, type: T.Type, onlineVerification: Bool, environment: AppStoreEnvironment) async -> VerificationResult<T> where T: Decodable {
30+
func verify<T: DecodedSignedData>(signedData: String, type: T.Type, onlineVerification: Bool, environment: AppStoreEnvironment, currentTime: Date = .now) async -> VerificationResult<T> where T: Decodable & Sendable {
3231
let header: JWTHeader
3332
let decodedBody: T
3433
do {
@@ -67,9 +66,9 @@ class ChainVerifier {
6766
do {
6867
let leafCertificate = try Certificate(derEncoded: Array(leaf_der_enocded))
6968
let intermediateCertificate = try Certificate(derEncoded: Array(intermeidate_der_encoded))
70-
let validationTime = !onlineVerification && decodedBody.signedDateOptional != nil ? decodedBody.signedDateOptional! : getDate()
69+
let validationTime = !onlineVerification && decodedBody.signedDateOptional != nil ? decodedBody.signedDateOptional! : currentTime
7170

72-
let verificationResult = await verifyChain(leaf: leafCertificate, intermediate: intermediateCertificate, online: onlineVerification, validationTime: validationTime)
71+
let verificationResult = await verifyChain(leaf: leafCertificate, intermediate: intermediateCertificate, online: onlineVerification, validationTime: validationTime, currentTime: currentTime)
7372
switch verificationResult {
7473
case .validCertificate(let chain):
7574
let leafCertificate = chain.first!
@@ -90,22 +89,22 @@ class ChainVerifier {
9089
}
9190
}
9291

93-
func verifyChain(leaf: Certificate, intermediate: Certificate, online: Bool, validationTime: Date) async -> X509.VerificationResult {
92+
func verifyChain(leaf: Certificate, intermediate: Certificate, online: Bool, validationTime: Date, currentTime: Date = .now) async -> X509.VerificationResult {
9493
if online {
9594
if let cachedResult = verifiedPublicKeyCache[CacheKey(leaf: leaf, intermediate: intermediate)] {
96-
if cachedResult.expirationTime > getDate() {
95+
if cachedResult.expirationTime > currentTime {
9796
return cachedResult.publicKey
9897
}
9998
}
10099
}
101-
let verificationResult = await verifyChainWithoutCaching(leaf: leaf, intermediate: intermediate, online: online, validationTime: validationTime)
100+
let verificationResult = await verifyChainWithoutCaching(leaf: leaf, intermediate: intermediate, online: online, validationTime: validationTime, currentTime: currentTime)
102101

103102
if online {
104103
if case .validCertificate = verificationResult {
105-
verifiedPublicKeyCache[CacheKey(leaf: leaf, intermediate: intermediate)] = CacheValue(expirationTime: getDate().addingTimeInterval(TimeInterval(integerLiteral: ChainVerifier.CACHE_TIME_LIMIT)), publicKey: verificationResult)
104+
verifiedPublicKeyCache[CacheKey(leaf: leaf, intermediate: intermediate)] = CacheValue(expirationTime: currentTime.addingTimeInterval(TimeInterval(integerLiteral: ChainVerifier.CACHE_TIME_LIMIT)), publicKey: verificationResult)
106105
if verifiedPublicKeyCache.count > ChainVerifier.MAXIMUM_CACHE_SIZE {
107106
for kv in verifiedPublicKeyCache {
108-
if kv.value.expirationTime < getDate() {
107+
if kv.value.expirationTime < currentTime {
109108
verifiedPublicKeyCache.removeValue(forKey: kv.key)
110109
}
111110
}
@@ -116,21 +115,17 @@ class ChainVerifier {
116115
return verificationResult
117116
}
118117

119-
func verifyChainWithoutCaching(leaf: Certificate, intermediate: Certificate, online: Bool, validationTime: Date) async -> X509.VerificationResult {
118+
nonisolated func verifyChainWithoutCaching(leaf: Certificate, intermediate: Certificate, online: Bool, validationTime: Date, currentTime: Date = .now) async -> X509.VerificationResult {
120119
var verifier = Verifier(rootCertificates: self.store) {
121120
RFC5280Policy(validationTime: validationTime)
122121
AppStoreOIDPolicy()
123122
if online {
124-
OCSPVerifierPolicy(failureMode: .hard, requester: requester, validationTime: getDate())
123+
OCSPVerifierPolicy(failureMode: .hard, requester: requester, validationTime: currentTime)
125124
}
126125
}
127126
let intermediateStore = CertificateStore([intermediate])
128127
return await verifier.validate(leafCertificate: leaf, intermediates: intermediateStore)
129128
}
130-
131-
func getDate() -> Date {
132-
return Date()
133-
}
134129
}
135130

136131
struct CacheKey: Hashable {
@@ -217,7 +212,7 @@ final class Requester: OCSPRequester {
217212
private struct OCSPFetchError: Error {}
218213
}
219214

220-
public enum VerificationResult<T> {
215+
public enum VerificationResult<T: Hashable & Sendable>: Hashable, Sendable {
221216
case valid(T)
222217
case invalid(VerificationError)
223218
}

Sources/AppStoreServerLibrary/SignedDataVerifier.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import Foundation
44

55
///A verifier and decoder class designed to decode signed data from the App Store.
6-
public struct SignedDataVerifier {
6+
public struct SignedDataVerifier: Sendable {
77

8-
public enum ConfigurationError: Error {
8+
public enum ConfigurationError: Error, Hashable, Sendable {
99
case INVALID_APP_APPLE_ID
1010
}
1111

@@ -168,7 +168,7 @@ public struct SignedDataVerifier {
168168
return realtimeRequestResult
169169
}
170170

171-
private func decodeSignedData<T: DecodedSignedData>(signedData: String, type: T.Type) async -> VerificationResult<T> where T : Decodable {
171+
private func decodeSignedData<T: DecodedSignedData>(signedData: String, type: T.Type) async -> VerificationResult<T> where T : Decodable & Sendable {
172172
return await chainVerifier.verify(signedData: signedData, type: type, onlineVerification: self.enableOnlineChecks, environment: self.environment)
173173
}
174174
}

Tests/AppStoreServerLibraryTests/SignedDataVerifierTests.swift

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ final class SignedDataVerifierTests: XCTestCase {
8787
}
8888

8989
func testOcspResponseCaching() async throws {
90-
let verifier: DateOverrideChainVerifier = DateOverrideChainVerifier(expectedCalls: 1, currentDate: CLOCK_DATE, base64EncodedRootCertificate: ROOT_CA_BASE64_ENCODED)!
90+
var verifier: DateOverrideChainVerifier = DateOverrideChainVerifier(expectedCalls: 1, currentDate: CLOCK_DATE, base64EncodedRootCertificate: ROOT_CA_BASE64_ENCODED)!
9191
let leaf = try! Certificate(derEncoded: Array(Data(base64Encoded: LEAF_CERT_BASE64_ENCODED)!))
9292
let intermediate = try! Certificate(derEncoded: Array(Data(base64Encoded: INTERMEDIATE_CA_BASE64_ENCODED)!))
9393
let _ = await verifier.verifyChain(leaf: leaf, intermediate: intermediate, online: true, validationTime: EFFECTIVE_DATE)
@@ -96,7 +96,7 @@ final class SignedDataVerifierTests: XCTestCase {
9696
}
9797

9898
func testOcspResponseCachingHasExpiration() async throws {
99-
let verifier: DateOverrideChainVerifier = DateOverrideChainVerifier(expectedCalls: 2, currentDate: CLOCK_DATE, base64EncodedRootCertificate: ROOT_CA_BASE64_ENCODED)!
99+
var verifier: DateOverrideChainVerifier = DateOverrideChainVerifier(expectedCalls: 2, currentDate: CLOCK_DATE, base64EncodedRootCertificate: ROOT_CA_BASE64_ENCODED)!
100100
let leaf = try! Certificate(derEncoded: Array(Data(base64Encoded: LEAF_CERT_BASE64_ENCODED)!))
101101
let intermediate = try! Certificate(derEncoded: Array(Data(base64Encoded: INTERMEDIATE_CA_BASE64_ENCODED)!))
102102
let _ = await verifier.verifyChain(leaf: leaf, intermediate: intermediate, online: true, validationTime: EFFECTIVE_DATE)
@@ -125,7 +125,7 @@ final class SignedDataVerifierTests: XCTestCase {
125125

126126
// The following test will communicate with Apple's OCSP servers, disable this test for offline testing
127127
func testAppleChainIsValidWithOCSP() async throws {
128-
let verifier: ChainVerifier = getChainVerifier(base64EncodedRootCertificate: REAL_APPLE_ROOT_BASE64_ENCODED)
128+
var verifier: ChainVerifier = getChainVerifier(base64EncodedRootCertificate: REAL_APPLE_ROOT_BASE64_ENCODED)
129129
let leaf = try! Certificate(derEncoded: Array(Data(base64Encoded: REAL_APPLE_SIGNING_CERTIFICATE_BASE64_ENCODED)!))
130130
let intermediate = try! Certificate(derEncoded: Array(Data(base64Encoded: REAL_APPLE_INTERMEDIATE_BASE64_ENCODED)!))
131131
let root = try! Certificate(derEncoded: Array(Data(base64Encoded: REAL_APPLE_ROOT_BASE64_ENCODED)!))
@@ -255,7 +255,8 @@ final class SignedDataVerifierTests: XCTestCase {
255255
return try! ChainVerifier(rootCertificates: [Data(base64Encoded: base64EncodedRootCertificate)!])
256256
}
257257

258-
class DateOverrideChainVerifier: ChainVerifier {
258+
struct DateOverrideChainVerifier {
259+
let chainVerifier: ChainVerifier
259260
var currentDate: Int64
260261
var expectation : XCTestExpectation
261262

@@ -264,19 +265,30 @@ final class SignedDataVerifierTests: XCTestCase {
264265
self.expectation = XCTestExpectation()
265266
self.expectation.assertForOverFulfill = true
266267
self.expectation.expectedFulfillmentCount = expectedCalls
267-
try? super.init(rootCertificates: [Data(base64Encoded: base64EncodedRootCertificate)!])
268+
guard let chainVerifier = try? ChainVerifier(rootCertificates: [Data(base64Encoded: base64EncodedRootCertificate)!])
269+
else { return nil }
270+
271+
self.chainVerifier = chainVerifier
268272
}
269273

270-
func setDate(newDate: Int64) {
274+
mutating func setDate(newDate: Int64) {
271275
self.currentDate = newDate
272276
}
273277

274-
override func verifyChainWithoutCaching(leaf: Certificate, intermediate: Certificate, online: Bool, validationTime: Date) async -> X509.VerificationResult {
278+
func verify<T: DecodedSignedData>(signedData: String, type: T.Type, onlineVerification: Bool, environment: AppStoreEnvironment) async -> AppStoreServerLibrary.VerificationResult<T> where T: Decodable & Sendable {
279+
await chainVerifier.verify(signedData: signedData, type: type, onlineVerification: onlineVerification, environment: environment, currentTime: getDate())
280+
}
281+
282+
func verifyChain(leaf: Certificate, intermediate: Certificate, online: Bool, validationTime: Date) async -> X509.VerificationResult {
283+
await chainVerifier.verifyChain(leaf: leaf, intermediate: intermediate, online: online, validationTime: validationTime, currentTime: getDate())
284+
}
285+
286+
func verifyChainWithoutCaching(leaf: Certificate, intermediate: Certificate, online: Bool, validationTime: Date) async -> X509.VerificationResult {
275287
expectation.fulfill()
276288
return .validCertificate([])
277289
}
278290

279-
override func getDate() -> Date {
291+
func getDate() -> Date {
280292
return Date(timeIntervalSince1970: TimeInterval(currentDate))
281293
}
282294
}

0 commit comments

Comments
 (0)