Skip to content

Commit 26f7faf

Browse files
author
Neno Stefanov
committed
test: validate signature verification certificate
1 parent 57ec010 commit 26f7faf

File tree

7 files changed

+178
-44
lines changed

7 files changed

+178
-44
lines changed

MIRACLTrust/MIRACLTrust.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@
240240
6DFE8FC527EB6E380085380D /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DFE8FC427EB6E380085380D /* Helpers.swift */; };
241241
6DFEC8CE26E1049100E8F682 /* MockURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DFEC8CD26E1049100E8F682 /* MockURLSession.swift */; };
242242
83695C1C2C9734AA00411EAA /* EmailVerificationMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83695C1B2C9734AA00411EAA /* EmailVerificationMethod.swift */; };
243+
838EE9302EDECD2000042452 /* SignatureCertificateJWTPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 838EE92F2EDECD1200042452 /* SignatureCertificateJWTPayload.swift */; };
243244
83BD3EFB2CC902F000D4E47F /* GmailServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83BD3EFA2CC902DF00D4E47F /* GmailServiceWrapper.swift */; };
244245
83BD3F0B2CCA23A900D4E47F /* AuthenticationJWTPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83BD3F0A2CCA23A500D4E47F /* AuthenticationJWTPayload.swift */; };
245246
83DE9EBE2CCA320D00BD7749 /* JWTKit in Frameworks */ = {isa = PBXBuildFile; productRef = 83DE9EBD2CCA320D00BD7749 /* JWTKit */; };
@@ -555,6 +556,7 @@
555556
6DFE8FC427EB6E380085380D /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
556557
6DFEC8CD26E1049100E8F682 /* MockURLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockURLSession.swift; sourceTree = "<group>"; };
557558
83695C1B2C9734AA00411EAA /* EmailVerificationMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailVerificationMethod.swift; sourceTree = "<group>"; };
559+
838EE92F2EDECD1200042452 /* SignatureCertificateJWTPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignatureCertificateJWTPayload.swift; sourceTree = "<group>"; };
558560
83BD3EF82CC7CDBD00D4E47F /* GmailService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailService.swift; sourceTree = "<group>"; };
559561
83BD3EFA2CC902DF00D4E47F /* GmailServiceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailServiceWrapper.swift; sourceTree = "<group>"; };
560562
83BD3F0A2CCA23A500D4E47F /* AuthenticationJWTPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationJWTPayload.swift; sourceTree = "<group>"; };
@@ -956,6 +958,7 @@
956958
83BD3EFA2CC902DF00D4E47F /* GmailServiceWrapper.swift */,
957959
6DFE8FC427EB6E380085380D /* Helpers.swift */,
958960
83BD3F0A2CCA23A500D4E47F /* AuthenticationJWTPayload.swift */,
961+
838EE92F2EDECD1200042452 /* SignatureCertificateJWTPayload.swift */,
959962
);
960963
path = Helpers;
961964
sourceTree = "<group>";
@@ -1501,6 +1504,7 @@
15011504
6D26142C24AB1B7C004F0B54 /* Constants.swift in Sources */,
15021505
6D2E4F192E2E7A290001B5A7 /* CrossDeviceSessionIntegrationTest.swift in Sources */,
15031506
6D91ABDA26FDF4C000EE28CF /* SessionDetailsTestCase.swift in Sources */,
1507+
838EE9302EDECD2000042452 /* SignatureCertificateJWTPayload.swift in Sources */,
15041508
6D6B94B82E46141600BC7A60 /* CrossDeviceSessionAbortCase.swift in Sources */,
15051509
6D4167A52E435C78002E59EA /* CrossDeviceSessionAuthenticationCase.swift in Sources */,
15061510
6DDEB15127070C2C00C3313D /* AbortSessionIntergrationTest.swift in Sources */,

MIRACLTrust/MIRACLTrustIntegrationTests/Cases/Swift/SigningIntegrationTests.swift

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import CryptoKit
2+
import JWTKit
23
import XCTest
34

45
@testable import MIRACLTrust
@@ -106,23 +107,33 @@ class SigningIntegrationTests: XCTestCase {
106107
XCTAssertNil(error)
107108

108109
let unwrappedSigningResult = try XCTUnwrap(signingResult)
109-
let isSignatureVerified = api.verifySignature(
110-
signingResult: unwrappedSigningResult,
111-
serviceAccountToken: serviceAccountToken,
112-
projectId: projectId,
113-
projectURL: projectURL
110+
XCTAssertEqual(messageHash.hex, unwrappedSigningResult.signature.signatureHash)
111+
112+
let verifySigningResponse = try XCTUnwrap(
113+
api.verifySignature(
114+
signingResult: unwrappedSigningResult,
115+
serviceAccountToken: serviceAccountToken,
116+
projectId: projectId,
117+
projectURL: projectURL
118+
)
114119
)
115120

116-
XCTAssertTrue(isSignatureVerified)
121+
let jwks = try XCTUnwrap(api.getDvsJWKS(projectURL: projectURL))
122+
let signers = JWTSigners()
123+
try signers.use(jwksJSON: jwks)
124+
let payload = try signers.verify(verifySigningResponse.certificate, as: SignatureCertificateJWTPayload.self)
125+
126+
XCTAssertEqual(messageHash.hex, payload.hash)
117127
}
118128

119129
func testSigningCorrectnessWithSessionDetails() throws {
130+
let hash = messageHash.hex
120131
let qrCode = try XCTUnwrap(
121132
api.startSigningSession(
122133
projectID: projectId,
123134
projectURL: projectURL,
124135
userID: userId,
125-
hash: UUID().uuidString,
136+
hash: hash,
126137
description: "Test transaction"
127138
)
128139
)
@@ -140,14 +151,23 @@ class SigningIntegrationTests: XCTestCase {
140151
XCTAssertNil(error)
141152

142153
let unwrappedSigningResult = try XCTUnwrap(signingResult)
143-
let isSignatureVerified = api.verifySignature(
144-
signingResult: unwrappedSigningResult,
145-
serviceAccountToken: serviceAccountToken,
146-
projectId: projectId,
147-
projectURL: projectURL
154+
XCTAssertEqual(hash, unwrappedSigningResult.signature.signatureHash)
155+
156+
let verifySigningResponse = try XCTUnwrap(
157+
api.verifySignature(
158+
signingResult: unwrappedSigningResult,
159+
serviceAccountToken: serviceAccountToken,
160+
projectId: projectId,
161+
projectURL: projectURL
162+
)
148163
)
149164

150-
XCTAssertTrue(isSignatureVerified)
165+
let jwks = try XCTUnwrap(api.getDvsJWKS(projectURL: projectURL))
166+
let signers = JWTSigners()
167+
try signers.use(jwksJSON: jwks)
168+
let payload = try signers.verify(verifySigningResponse.certificate, as: SignatureCertificateJWTPayload.self)
169+
170+
XCTAssertEqual(messageHash.hex, payload.hash)
151171
}
152172

153173
func testSigningCorrectnessWithCrossDeviceSession() async throws {
@@ -183,15 +203,22 @@ class SigningIntegrationTests: XCTestCase {
183203
let timeInterval = TimeInterval(signature.timestamp)
184204
let date = Date(timeIntervalSince1970: timeInterval)
185205

186-
let isSignatureVerified = api.verifySignature(
187-
signature: signature,
188-
timestamp: date,
189-
serviceAccountToken: serviceAccountToken,
190-
projectId: projectId,
191-
projectURL: projectURL
206+
let verifySigningResponse = try XCTUnwrap(
207+
api.verifySignature(
208+
signature: signature,
209+
timestamp: date,
210+
serviceAccountToken: serviceAccountToken,
211+
projectId: projectId,
212+
projectURL: projectURL
213+
)
192214
)
193215

194-
XCTAssertTrue(isSignatureVerified)
216+
let jwks = try XCTUnwrap(api.getDvsJWKS(projectURL: projectURL))
217+
let signers = JWTSigners()
218+
try signers.use(jwksJSON: jwks)
219+
let payload = try signers.verify(verifySigningResponse.certificate, as: SignatureCertificateJWTPayload.self)
220+
221+
XCTAssertEqual(signature.signatureHash, payload.hash)
195222
}
196223

197224
func testSigningCorrectnessWithCrossDeviceSessionForUniversalLink() throws {
@@ -242,14 +269,23 @@ class SigningIntegrationTests: XCTestCase {
242269
XCTAssertNil(error)
243270

244271
let unwrappedSigningResult = try XCTUnwrap(signingResult)
245-
let isSignatureVerified = api.verifySignature(
246-
signingResult: unwrappedSigningResult,
247-
serviceAccountToken: serviceAccountToken,
248-
projectId: projectId,
249-
projectURL: projectURL
272+
XCTAssertEqual(messageHash.hex, unwrappedSigningResult.signature.signatureHash)
273+
274+
let verifySigningResponse = try XCTUnwrap(
275+
api.verifySignature(
276+
signingResult: unwrappedSigningResult,
277+
serviceAccountToken: serviceAccountToken,
278+
projectId: projectId,
279+
projectURL: projectURL
280+
)
250281
)
251282

252-
XCTAssertTrue(isSignatureVerified)
283+
let jwks = try XCTUnwrap(api.getDvsJWKS(projectURL: projectURL))
284+
let signers = JWTSigners()
285+
try signers.use(jwksJSON: jwks)
286+
let payload = try signers.verify(verifySigningResponse.certificate, as: SignatureCertificateJWTPayload.self)
287+
288+
XCTAssertEqual(messageHash.hex, payload.hash)
253289
}
254290

255291
func testSigningEmptyMessageHash() throws {

MIRACLTrust/MIRACLTrustIntegrationTests/Helpers/MIRACLTrustHelperAPI/HelperAPIModel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ struct VerifySigningRequestBody: Codable {
2525
var timestamp: Int32
2626
}
2727

28+
struct VerifySigningResponse: Codable {
29+
var certificate: String
30+
}
31+
2832
struct StartSessionResponse: Codable {
2933
var qrURL: URL
3034
var webOTT: String

MIRACLTrust/MIRACLTrustIntegrationTests/Helpers/MIRACLTrustHelperAPI/PlatformAPI.swift

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,55 @@ import MIRACLTrust
133133
task.resume()
134134
}
135135

136+
public func getDvsJWKS(
137+
projectURL: String,
138+
completionHandler: @escaping @Sendable (String?, Error?) -> Void
139+
) {
140+
guard let url = URL(string: projectURL) else {
141+
return
142+
}
143+
144+
guard let request = URLRequest.dvsJwksRequest(url: url) else {
145+
return
146+
}
147+
148+
let task = URLSession.shared.dataTask(with: request) { responseData, response, error in
149+
if error != nil {
150+
DispatchQueue.main.async {
151+
completionHandler(nil, HelperAPIError.internalError)
152+
}
153+
return
154+
}
155+
156+
if let response = response as? HTTPURLResponse {
157+
if response.statusCode != 200 {
158+
DispatchQueue.main.async {
159+
completionHandler(nil, HelperAPIError.internalError)
160+
}
161+
return
162+
}
163+
}
164+
165+
guard let data = responseData else {
166+
DispatchQueue.main.async {
167+
completionHandler(nil, HelperAPIError.noData)
168+
}
169+
return
170+
}
171+
172+
if data.isEmpty {
173+
DispatchQueue.main.async {
174+
completionHandler(nil, nil)
175+
}
176+
return
177+
}
178+
179+
completionHandler(String(data: data, encoding: String.Encoding.utf8), nil)
180+
}
181+
182+
task.resume()
183+
}
184+
136185
public func accessRequest(
137186
projectURL: String,
138187
webOTT: String,
@@ -189,13 +238,13 @@ import MIRACLTrust
189238
}
190239
}
191240

192-
public func verifySignature(
241+
func verifySignature(
193242
for signature: Signature,
194243
timestamp: Date,
195244
serviceAccountToken: String,
196245
projectId: String,
197246
projectURL: String,
198-
completionHandler: @escaping @Sendable (Bool, Error?) -> Void
247+
completionHandler: @escaping @Sendable (VerifySigningResponse?, Error?) -> Void
199248
) {
200249
guard let url = URL(string: projectURL) else {
201250
return
@@ -205,12 +254,12 @@ import MIRACLTrust
205254
return
206255
}
207256

208-
requestExecutor.executeHTTPRequest(request: request) { (result: Result<EmptyResponse?, HelperAPIError>) in
257+
requestExecutor.executeHTTPRequest(request: request) { (result: Result<VerifySigningResponse?, HelperAPIError>) in
209258
switch result {
210-
case .success:
211-
completionHandler(true, nil)
259+
case let .success(success):
260+
completionHandler(success, nil)
212261
case let .failure(error):
213-
completionHandler(false, error)
262+
completionHandler(nil, error)
214263
}
215264
}
216265
}

MIRACLTrust/MIRACLTrustIntegrationTests/Helpers/MIRACLTrustHelperAPI/URLRequest+Extensions.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,20 @@ extension URLRequest {
182182
return URLRequest(url: url)
183183
}
184184

185+
static func dvsJwksRequest(url: URL) -> Self? {
186+
guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
187+
return nil
188+
}
189+
190+
components.path = "/dvs/jwks"
191+
192+
guard let url = components.url else {
193+
return nil
194+
}
195+
196+
return URLRequest(url: url)
197+
}
198+
185199
static func signingSessionRequest(
186200
url: URL,
187201
projectID: String,

MIRACLTrust/MIRACLTrustIntegrationTests/Helpers/PlatformAPIWrapper.swift

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ import MIRACLTrust
5151
return jwkSet
5252
}
5353

54+
@objc func getDvsJWKS(projectURL: String) -> String? {
55+
let jwksExpectation = XCTestExpectation(description: "wait for JWKS")
56+
nonisolated(unsafe) var jwkSet: String?
57+
58+
platformAPI.getDvsJWKS(projectURL: projectURL) { jwks, error in
59+
if let jwks = jwks {
60+
jwkSet = jwks
61+
} else if let error = error {
62+
print("Error when fetching JWKS \(error)")
63+
}
64+
jwksExpectation.fulfill()
65+
}
66+
_ = XCTWaiter.wait(for: [jwksExpectation], timeout: operationTimeout)
67+
return jwkSet
68+
}
69+
5470
@objc func startSession(
5571
projectId: String,
5672
projectURL: String,
@@ -108,13 +124,13 @@ import MIRACLTrust
108124
return qrCode
109125
}
110126

111-
@objc func verifySignature(
127+
func verifySignature(
112128
signingResult: SigningResult,
113129
serviceAccountToken: String,
114130
projectId: String,
115131
projectURL: String
116-
) -> Bool {
117-
nonisolated(unsafe) var verifiedSignature = false
132+
) -> VerifySigningResponse? {
133+
nonisolated(unsafe) var verifySigningResponse: VerifySigningResponse?
118134
let expectation = XCTestExpectation(description: "Waiting for signature verification")
119135

120136
platformAPI.verifySignature(
@@ -123,23 +139,23 @@ import MIRACLTrust
123139
serviceAccountToken: serviceAccountToken,
124140
projectId: projectId,
125141
projectURL: projectURL
126-
) { isVerified, _ in
127-
verifiedSignature = isVerified
142+
) { signingResponse, _ in
143+
verifySigningResponse = signingResponse
128144
expectation.fulfill()
129145
}
130146
_ = XCTWaiter.wait(for: [expectation], timeout: operationTimeout)
131147

132-
return verifiedSignature
148+
return verifySigningResponse
133149
}
134150

135-
@objc func verifySignature(
151+
func verifySignature(
136152
signature: Signature,
137153
timestamp: Date,
138154
serviceAccountToken: String,
139155
projectId: String,
140156
projectURL: String
141-
) -> Bool {
142-
nonisolated(unsafe) var verifiedSignature = false
157+
) -> VerifySigningResponse? {
158+
nonisolated(unsafe) var verifySigningResponse: VerifySigningResponse?
143159
let expectation = XCTestExpectation(description: "Waiting for signature verification")
144160

145161
platformAPI.verifySignature(
@@ -148,13 +164,13 @@ import MIRACLTrust
148164
serviceAccountToken: serviceAccountToken,
149165
projectId: projectId,
150166
projectURL: projectURL
151-
) { isVerified, _ in
152-
verifiedSignature = isVerified
167+
) { signingResponse, _ in
168+
verifySigningResponse = signingResponse
153169
expectation.fulfill()
154170
}
155171
_ = XCTWaiter.wait(for: [expectation], timeout: operationTimeout)
156172

157-
return verifiedSignature
173+
return verifySigningResponse
158174
}
159175

160176
func getVerificationURL(
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import JWTKit
2+
3+
struct SignatureCertificateJWTPayload: JWTPayload {
4+
var cAt: IssuedAtClaim
5+
var exp: ExpirationClaim
6+
var hash: String
7+
8+
func verify(using _: JWTKit.JWTSigner) throws {
9+
try exp.verifyNotExpired()
10+
}
11+
}

0 commit comments

Comments
 (0)