• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hyperledger / identus-edge-agent-sdk-swift / 10163242789

30 Jul 2024 01:24PM UTC coverage: 43.367% (+1.6%) from 41.758%
10163242789

Pull #152

github

web-flow
Merge 8d6e6af2d into 0b302f5ab
Pull Request #152: test: add backup and revocation message e2e scenarios

5 of 52 new or added lines in 4 files covered. (9.62%)

2 existing lines in 1 file now uncovered.

5080 of 11714 relevant lines covered (43.37%)

97.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.4
/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift
1
import AnoncredsSwift
2
import Core
3
import Domain
4
import Foundation
5
import JSONWebAlgorithms
6
import JSONWebToken
7
import JSONWebSignature
8
import Sextant
9
import JSONSchema
10

11
extension PolluxImpl {
12

13
    private enum ValidJsonTypeFilter: String {
14
        case string
15
        case number
16
        case boolean
17
    }
18

19
    public func verifyPresentation(message: Message, options: [CredentialOperationsOptions]) async throws -> Bool {
4✔
20
        guard 
4✔
21
            let attachment = message.attachments.first,
4✔
22
            let requestId = message.thid
4✔
23
        else {
4✔
24
            throw PolluxError.couldNotFindPresentationInAttachments
×
25
        }
4✔
26

4✔
27
        let jsonData: Data
4✔
28
        switch attachment.data {
4✔
29
        case let attchedData as AttachmentBase64:
4✔
30
            guard let decoded = Data(fromBase64URL: attchedData.base64) else {
4✔
31
                throw CommonError.invalidCoding(message: "Invalid base64 url attachment")
×
32
            }
4✔
33
            jsonData = decoded
4✔
34
        case let attchedData as AttachmentJsonData:
4✔
35
            jsonData = attchedData.data
×
36
        default:
4✔
37
            throw PolluxError.invalidAttachmentType(supportedTypes: ["Json", "Base64"])
×
38
        }
4✔
39

4✔
40
        switch attachment.format {
4✔
41
        case "dif/presentation-exchange/submission@v1.0":
4✔
42
            return try await verifyPresentationSubmission(json: jsonData, requestId: requestId)
2✔
43
        case "anoncreds/proof@v1.0":
4✔
44
            return try await verifyAnoncreds(
2✔
45
                presentation: jsonData,
2✔
46
                requestId: requestId,
2✔
47
                options: options
2✔
48
            )
2✔
49
        default:
4✔
50
            throw PolluxError.unsupportedAttachmentFormat(attachment.format)
×
51
        }
4✔
52
    }
4✔
53

54
    private func verifyPresentationSubmission(json: Data, requestId: String) async throws -> Bool {
2✔
55
        let presentationContainer = try JSONDecoder.didComm().decode(PresentationContainer.self, from: json)
2✔
56
        let presentationRequest = try await getDefinition(id: requestId)
2✔
57
        guard let submission = presentationContainer.presentationSubmission else {
2✔
58
            throw PolluxError.presentationSubmissionNotAvailable
×
59
        }
2✔
60
        let credentials = try getCredentialJWT(submission: submission, presentationData: json)
2✔
61

2✔
62
        try VerifyPresentationSubmission.verifyPresentationSubmissionClaims(
2✔
63
            request: presentationRequest.presentationDefinition,
2✔
64
            credentials: try credentials.map {
2✔
65
                try JWT.getPayload(jwtString: $0)
2✔
66
            }
2✔
67
        )
2✔
68

2✔
69
        try await verifyJWTs(credentials: credentials)
2✔
70
        return true
2✔
71
    }
2✔
72

73
    private func getCredentialJWT(submission: PresentationSubmission, presentationData: Data) throws -> [String] {
2✔
74
        return submission.descriptorMap
2✔
75
            .filter({ $0.format == "jwt" || $0.format == "jwt_vc" || $0.format == "jwt_vp" })
2✔
76
            .compactMap { try? processJWTPath(descriptor: $0, presentationData: presentationData) }
2✔
77
    }
2✔
78

79
    private func processJWTPath(descriptor: PresentationSubmission.Descriptor, presentationData: Data) throws -> String {
4✔
80
        guard descriptor.format == "jwt" || descriptor.format == "jwt_vc" || descriptor.format == "jwt_vp" else {
4✔
81
            throw UnknownError.somethingWentWrongError(
×
82
                customMessage: "This should not happen since its filtered before",
×
83
                underlyingErrors: nil
×
84
            )
×
85
        }
4✔
86

4✔
87
        guard
4✔
88
            let jwts = presentationData.query(string: descriptor.path)
4✔
89
        else {
4✔
90
            throw PolluxError.credentialPathInvalid(path: descriptor.path)
×
91
        }
4✔
92

4✔
93
        guard let nestedDescriptor = descriptor.pathNested else {
4✔
94
            return jwts
2✔
95
        }
2✔
96
        let nestedPayload: Data = try JWT.getPayload(jwtString: jwts)
2✔
97
        return try processJWTPath(descriptor: nestedDescriptor, presentationData: nestedPayload)
2✔
98
    }
4✔
99

100
    private func verifyJWTs(credentials: [String]) async throws {
2✔
101
        var errors = [Error]()
2✔
102
        await credentials
2✔
103
            .asyncForEach {
2✔
104
                do {
2✔
105
                    try await verifyJWT(jwtString: $0)
2✔
106
                } catch {
2✔
UNCOV
107
                    errors.append(error)
×
108
                }
2✔
109
            }
2✔
110
        guard errors.isEmpty else {
2✔
NEW
111
            throw PolluxError.cannotVerifyPresentationInputs(errors: errors)
×
112
        }
2✔
113
    }
2✔
114

115
    private func verifyJWT(jwtString: String) async throws -> Bool {
2✔
116
        try await verifyJWTCredentialRevocation(jwtString: jwtString)
2✔
117
        let payload: DefaultJWTClaimsImpl = try JWT.getPayload(jwtString: jwtString)
2✔
118
        guard let issuer = payload.iss else {
2✔
119
            throw PolluxError.requiresThatIssuerExistsAndIsAPrismDID
×
120
        }
2✔
121

2✔
122
        let issuerDID = try DID(string: issuer)
2✔
123
        let issuerKeys = try await castor.getDIDPublicKeys(did: issuerDID)
2✔
124
        
2✔
125
        ES256KVerifier.bouncyCastleFailSafe = true
2✔
126

2✔
127
        let validations = issuerKeys
2✔
128
            .compactMap(\.exporting)
4✔
129
            .compactMap {
4✔
130
                try? JWT.verify(jwtString: jwtString, senderKey: $0.jwk.toJoseJWK())
4✔
131
            }
4✔
132
        ES256KVerifier.bouncyCastleFailSafe = false
2✔
133
        return !validations.isEmpty
2✔
134
    }
2✔
135

136
    private func verifyJWTCredentialRevocation(jwtString: String) async throws {
2✔
137
        guard let credential = try? JWTCredential(data: jwtString.tryToData()) else {
2✔
138
            return
×
139
        }
2✔
140
        let isRevoked = try await credential.isRevoked
2✔
141
        let isSuspended = try await credential.isSuspended
2✔
142
        guard !isRevoked else {
2✔
UNCOV
143
            throw PolluxError.credentialIsRevoked(jwtString: jwtString)
×
144
        }
2✔
145
        guard !isSuspended else {
2✔
146
            throw PolluxError.credentialIsSuspended(jwtString: jwtString)
×
147
        }
2✔
148
    }
2✔
149

150
    private func getDefinition(id: String) async throws -> PresentationExchangeRequest {
2✔
151
        guard 
2✔
152
            let request = try await pluto.getMessage(id: id).first().await(),
2✔
153
            let attachmentData = request.attachments.first?.data
2✔
154
        else {
2✔
155
            throw PolluxError.couldNotFindPresentationRequest(id: id)
×
156
        }
2✔
157

2✔
158
        let json: Data
2✔
159
        switch attachmentData {
2✔
160
        case let jsonData as AttachmentJsonData:
2✔
161
            json = jsonData.data
×
162
        case let base64Data as AttachmentBase64:
2✔
163
            json = try base64Data.decoded()
2✔
164
        default:
2✔
165
            throw PolluxError.invalidAttachmentType(supportedTypes: ["base64", "json"])
×
166
        }
2✔
167

2✔
168
        return try JSONDecoder.didComm().decode(PresentationExchangeRequest.self, from: json)
2✔
169
    }
2✔
170

171
    private func verifyAnoncreds(
172
        presentation: Data,
173
        requestId: String,
174
        options: [CredentialOperationsOptions]
175
    ) async throws -> Bool {
2✔
176
        let anonPresentation = try AnoncredsSwift.Presentation(jsonString: presentation.tryToString())
2✔
177
        let requestPresentation = try await getAnoncredsRequest(id: requestId)
2✔
178

2✔
179
        let internalPresentation = try JSONDecoder.didComm().decode(AnonPresentation.self, from: presentation)
2✔
180
        let schemaIds = internalPresentation.identifiers.map(\.schemaId)
2✔
181
        let credentialDefinitionsIds = internalPresentation.identifiers.map(\.credDefId)
2✔
182
        let anonSchemas = try await getSchemas(ids: schemaIds, options: options)
2✔
183
            .mapValues { try JSONDecoder.didComm().decode(AnonCredentialSchema.self, from: $0) }
2✔
184
            .mapValues {
2✔
185
                Schema(
2✔
186
                    name: $0.name,
2✔
187
                    version: $0.version,
2✔
188
                    attrNames: $0.attrNames.map { AttributeNames($0) } ?? AttributeNames(),
2✔
189
                    issuerId: $0.issuerId
2✔
190
                )
2✔
191
            }
2✔
192
        let anonCredentialDefinitions = try await getCredentialDefinitions(
2✔
193
            ids: credentialDefinitionsIds,
2✔
194
            options: options
2✔
195
        ).mapValues { try AnoncredsSwift.CredentialDefinition(jsonString: $0.tryToString()) }
2✔
196

2✔
197
        return try Verifier().verifyPresentation(
2✔
198
            presentation: anonPresentation,
2✔
199
            presentationRequest: requestPresentation,
2✔
200
            schemas: anonSchemas,
2✔
201
            credentialDefinitions: anonCredentialDefinitions
2✔
202
        )
2✔
203
    }
2✔
204

205
    private func getAnoncredsRequest(id: String) async throws -> AnoncredsSwift.PresentationRequest {
2✔
206
        guard
2✔
207
            let request = try await pluto.getMessage(id: id).first().await(),
2✔
208
            let attachmentData = request.attachments.first?.data
2✔
209
        else {
2✔
210
            throw PolluxError.couldNotFindPresentationRequest(id: id)
×
211
        }
2✔
212

2✔
213
        let json: Data
2✔
214
        switch attachmentData {
2✔
215
        case let jsonData as AttachmentJsonData:
2✔
216
            json = jsonData.data
×
217
        case let base64Data as AttachmentBase64:
2✔
218
            guard let data = try Data(fromBase64URL: base64Data.base64.tryToData()) else {
2✔
219
                throw CommonError.invalidCoding(message: "Could not decode base64 message attchment")
×
220
            }
2✔
221
            json = data
2✔
222
        default:
2✔
223
            throw PolluxError.invalidAttachmentType(supportedTypes: [])
×
224
        }
2✔
225

2✔
226
        return try PresentationRequest(jsonString: json.tryToString())
2✔
227
    }
2✔
228

229
    private func getSchemas(ids: [String], options: [CredentialOperationsOptions]) async throws -> [String: Data] {
2✔
230
        if
2✔
231
            let schemasOption = options.first(where: {
2✔
232
                if case .schema = $0 { return true }
2✔
233
                return false
×
234
            }),
2✔
235
            case let CredentialOperationsOptions.schema(id, json) = schemasOption
2✔
236
        {
2✔
237
            return try [id: json.tryToData()]
2✔
238
        }
2✔
239

×
240
        guard
×
241
            let schemaDownloaderOption = options.first(where: {
×
242
                if case .schemaDownloader = $0 { return true }
×
243
                return false
×
244
            }),
×
245
            case let CredentialOperationsOptions.schemaDownloader(downloader) = schemaDownloaderOption
×
246
        else {
×
247
            throw PolluxError.missingAndIsRequiredForOperation(type: "schemaDownloader")
×
248
        }
×
249
        let schemas = try await ids.asyncMap { ($0, try await downloader.downloadFromEndpoint(urlOrDID: $0)) }
×
250
        return schemas.reduce( [String: Data]()) { partialResult, schemas in
×
251
            var dic = partialResult
×
252
            dic[schemas.0] = schemas.1
×
253
            return dic
×
254
        }
×
255
    }
×
256

257
    private func getCredentialDefinitions(
258
        ids: [String],
259
        options: [CredentialOperationsOptions]
260
    ) async throws -> [String: Data] {
2✔
261
        if
2✔
262
            let credentialDefinitionsOption = options.first(where: {
4✔
263
                if case .credentialDefinition = $0 { return true }
4✔
264
                return false
2✔
265
            }),
4✔
266
            case let CredentialOperationsOptions.credentialDefinition(id, json) = credentialDefinitionsOption
2✔
267
        {
2✔
268
            return try [id: json.tryToData()]
2✔
269
        }
2✔
270

×
271
        guard
×
272
            let credentialDefinitionsDownloaderOption = options.first(where: {
×
273
                if case .credentialDefinitionDownloader = $0 { return true }
×
274
                return false
×
275
            }),
×
276
            case let CredentialOperationsOptions.credentialDefinitionDownloader(downloader) = credentialDefinitionsDownloaderOption
×
277
        else {
×
278
            throw PolluxError.missingAndIsRequiredForOperation(type: "credentialDefinitionDownloader")
×
279
        }
×
280
        let definitions = try await ids.asyncMap { ($0, try await downloader.downloadFromEndpoint(urlOrDID: $0)) }
×
281
        return definitions.reduce( [String: Data]()) { partialResult, definitions in
×
282
            var dic = partialResult
×
283
            dic[definitions.0] = definitions.1
×
284
            return dic
×
285
        }
×
286
    }
×
287
}
288

289
private struct AnonPresentation: Codable {
290
    struct Identifiers: Codable {
291
        let schemaId: String
292
        let credDefId: String
293
    }
294

295
    let identifiers: [Identifiers]
296
}
297

298
private struct ValidateJsonSchemaContainer: Codable {
299
    let value: AnyCodable
300
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc