From ec6392a86f0cb04a038bed4e092b2d0eb4fb902c Mon Sep 17 00:00:00 2001 From: dtsiflit Date: Fri, 20 Sep 2024 14:01:05 +0300 Subject: [PATCH] [fix] json serialization of sdjwt --- Sources/Builders/JSONBuilder.swift | 74 +++++++++++++ Sources/Issuer/SDJWT.swift | 83 +++++++++------ Sources/Parser/CompactParser.swift | 80 ++++++++++---- Sources/Utilities/JwsJsonSupportOption.swift | 77 ++++++++++++++ Tests/Helpers/Constants.swift | 23 ++++ .../JSONSerializationTest.swift | 100 ++++++++++++++++++ 6 files changed, 384 insertions(+), 53 deletions(-) create mode 100644 Sources/Builders/JSONBuilder.swift create mode 100644 Sources/Utilities/JwsJsonSupportOption.swift create mode 100644 Tests/JSONSerialization/JSONSerializationTest.swift diff --git a/Sources/Builders/JSONBuilder.swift b/Sources/Builders/JSONBuilder.swift new file mode 100644 index 0000000..41fcdca --- /dev/null +++ b/Sources/Builders/JSONBuilder.swift @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2023 European Commission + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import Foundation +import SwiftyJSON + +@resultBuilder +internal struct JSONBuilder { + + // Handle key-value pairs + static func buildBlock(_ components: [String: JSON?]...) -> [String: JSON?] { + var result: [String: JSON?] = [:] + for component in components { + result.merge(component) { (_, new) in new } + } + return result + } + + // Handle individual expressions + static func buildExpression(_ expression: [String: JSON?]) -> [String: JSON?] { + return expression + } + + static func buildExpression(_ expression: [JSON]) -> [String: JSON?] { + // This expression now returns empty dictionary, as array should be handled in JSON context + return [:] + } + + // Handle inline JSON objects + static func buildExpression(_ expression: JSON) -> [String: JSON?] { + // Assuming this JSON is a dictionary, we merge it + guard let dictionary = expression.dictionary else { + return [:] // Ignore if it's not an object (could handle arrays differently) + } + return dictionary.mapValues { $0 } + } + + // Handle building arrays of JSON objects + static func buildArray(_ components: [[String: JSON?]]) -> [String: JSON?] { + var result: [String: JSON?] = [:] + for component in components { + result.merge(component) { (_, new) in new } + } + return result + } +} + +// Function to create JSON objects +internal func JSONObject(@JSONBuilder _ content: () -> [String: JSON?]) -> JSON { + var result: [String: JSON] = [:] + for (key, value) in content() { + if let unwrappedValue = value { + result[key] = unwrappedValue + } + } + return JSON(result) +} + +// DSL for JSON array construction +internal func JSONArray(_ build: () -> [JSON]) -> JSON { + return JSON(build()) +} diff --git a/Sources/Issuer/SDJWT.swift b/Sources/Issuer/SDJWT.swift index d866aba..9441ec6 100644 --- a/Sources/Issuer/SDJWT.swift +++ b/Sources/Issuer/SDJWT.swift @@ -22,21 +22,21 @@ import SwiftyJSON public typealias KBJWT = JWT struct SDJWT { - + // MARK: - Properties - + public internal(set) var jwt: JWT public internal(set) var disclosures: [Disclosure] public internal(set) var kbJwt: JWT? - + // MARK: - Lifecycle - + init(jwt: JWT, disclosures: [Disclosure], kbJWT: KBJWT?) throws { self.jwt = jwt self.disclosures = disclosures self.kbJwt = kbJWT } - + func extractDigestCreator() throws -> DigestCreator { if jwt.payload[Keys.sdAlg.rawValue].exists() { let stringValue = jwt.payload[Keys.sdAlg.rawValue].stringValue @@ -49,7 +49,7 @@ struct SDJWT { throw SDJWTVerifierError.missingOrUnknownHashingAlgorithm } } - + func recreateClaims() throws -> ClaimExtractorResult { let digestCreator = try extractDigestCreator() var digestsOfDisclosuresDict = [DisclosureDigest: Disclosure]() @@ -61,31 +61,31 @@ struct SDJWT { throw SDJWTVerifierError.failedToCreateVerifier } } - + return try ClaimExtractor(digestsOfDisclosuresDict: digestsOfDisclosuresDict) .findDigests(payload: jwt.payload, disclosures: disclosures) } } public struct SignedSDJWT { - + // MARK: - Properties - + public let jwt: JWS public internal(set) var disclosures: [Disclosure] public internal(set) var kbJwt: JWS? - + var delineatedCompactSerialisation: String { let separator = "~" - let input = ([jwt.compactSerialization] + disclosures).reduce("") { $0.isEmpty ? $1 : $0 + separator + $1 } + separator + let input = ([jwt.compactSerialization] + disclosures).reduce("") { $0.isEmpty ? $1 : $0 + separator + $1 } + separator return DigestCreator() .hashAndBase64Encode( input: input - ) ?? "" + ) ?? "" } - + // MARK: - Lifecycle - + init( serializedJwt: String, disclosures: [Disclosure], @@ -95,83 +95,83 @@ public struct SignedSDJWT { self.disclosures = disclosures self.kbJwt = try? JWS(jwsString: serializedKbJwt ?? "") } - + private init?(sdJwt: SDJWT, issuersPrivateKey: KeyType) { // Create a Signed SDJWT with no key binding guard let signedJwt = try? SignedSDJWT.createSignedJWT(key: issuersPrivateKey, jwt: sdJwt.jwt) else { return nil } - + self.jwt = signedJwt self.disclosures = sdJwt.disclosures self.kbJwt = nil } - + private init?(signedSDJWT: SignedSDJWT, kbJWT: JWT, holdersPrivateKey: KeyType) { // Assume that we have a valid signed jwt from the issuer // And key exchange has been established // signed SDJWT might contain or not the cnf claim - + self.jwt = signedSDJWT.jwt self.disclosures = signedSDJWT.disclosures let signedKBJwt = try? SignedSDJWT.createSignedJWT(key: holdersPrivateKey, jwt: kbJWT) self.kbJwt = signedKBJwt } - + // MARK: - Methods - + // expose static func initializers to distinguish between 2 cases of // signed SDJWT creation - + static func nonKeyBondedSDJWT(sdJwt: SDJWT, issuersPrivateKey: KeyType) throws -> SignedSDJWT { try .init(sdJwt: sdJwt, issuersPrivateKey: issuersPrivateKey) ?? { throw SDJWTVerifierError.invalidJwt }() } - + static func keyBondedSDJWT(signedSDJWT: SignedSDJWT, kbJWT: JWT, holdersPrivateKey: KeyType) throws -> SignedSDJWT { try .init(signedSDJWT: signedSDJWT, kbJWT: kbJWT, holdersPrivateKey: holdersPrivateKey) ?? { throw SDJWTVerifierError.invalidJwt }() } - + private static func createSignedJWT(key: KeyType, jwt: JWT) throws -> JWS { try jwt.sign(key: key) } - + func disclosuresToPresent(disclosures: [Disclosure]) -> Self { var updated = self updated.disclosures = disclosures return updated } - + func toSDJWT() throws -> SDJWT { - if let kbJwtHeader = kbJwt?.protectedHeader, + if let kbJwtHeader = kbJwt?.protectedHeader, let kbJWtPayload = try? kbJwt?.payloadJSON() { return try SDJWT( jwt: JWT(header: jwt.protectedHeader, payload: jwt.payloadJSON()), disclosures: disclosures, kbJWT: JWT(header: kbJwtHeader, kbJwtPayload: kbJWtPayload)) } - + return try SDJWT( jwt: JWT(header: jwt.protectedHeader, payload: jwt.payloadJSON()), disclosures: disclosures, kbJWT: nil) } - + func extractHoldersPublicKey() throws -> JWK { let payloadJson = try self.jwt.payloadJSON() let jwk = payloadJson[Keys.cnf]["jwk"] - + guard jwk.exists() else { throw SDJWTVerifierError.keyBindingFailed(description: "Failled to find holders public key") } - + guard let jwkObject = try? JSONDecoder.jwt.decode(JWK.self, from: jwk.rawData()) else { throw SDJWTVerifierError.keyBindingFailed(description: "failled to extract key type") } - + return jwkObject } } @@ -180,14 +180,29 @@ extension SignedSDJWT { func serialised(serialiser: (SignedSDJWT) -> (SerialiserProtocol)) throws -> Data { serialiser(self).data } - + func serialised(serialiser: (SignedSDJWT) -> (SerialiserProtocol)) throws -> String { serialiser(self).serialised } } -extension SignedSDJWT { - public func recreateClaims() throws -> ClaimExtractorResult { +public extension SignedSDJWT { + func recreateClaims() throws -> ClaimExtractorResult { return try self.toSDJWT().recreateClaims() } + + func asJwsJsonObject( + option: JwsJsonSupportOption = .flattened, + kbJwt: JWTString?, + getParts: (JWTString) throws -> (String, String, String) + ) throws -> JSON { + let (protected, payload, signature) = try getParts(jwt.compactSerialization) + return option.buildJwsJson( + protected: protected, + payload: payload, + signature: signature, + disclosures: Set(disclosures), + kbJwt: kbJwt + ) + } } diff --git a/Sources/Parser/CompactParser.swift b/Sources/Parser/CompactParser.swift index 274e2f1..f83cb97 100644 --- a/Sources/Parser/CompactParser.swift +++ b/Sources/Parser/CompactParser.swift @@ -22,47 +22,89 @@ public enum SerialisationFormat { } public class CompactParser: ParserProtocol { - + // MARK: - Properties - + + private static let TILDE = "~" + var serialisedString: String var serialisationFormat: SerialisationFormat = .serialised // MARK: - Lifecycle - + public required init(serialiserProtocol: SerialiserProtocol) { self.serialisedString = serialiserProtocol.serialised } - + public init(serialisedString: String) { self.serialisedString = serialisedString } - + // MARK: - Methods - + public func getSignedSdJwt() throws -> SignedSDJWT { let (serialisedJWT, disclosuresInBase64, serialisedKBJWT) = try self.parseCombined() return try SignedSDJWT(serializedJwt: serialisedJWT, disclosures: disclosuresInBase64, serializedKbJwt: serialisedKBJWT) } - + + func extractJWTParts(_ jwt: String) throws -> (String, String, String) { + // Split the JWT string into its components: header, payload, signature + let parts = jwt.split(separator: ".") + + // Ensure that we have exactly 3 parts (header, payload, signature) + guard parts.count == 3 else { + throw SDJWTVerifierError.parsingError + } + + var header: Substring? + var payload: Substring? + var signature: Substring? + + // Iterate over the components and assign them to respective variables + for (index, part) in parts.enumerated() { + switch index { + case 0: + header = part // Assigning Substring + case 1: + payload = part // Assigning Substring + case 2: + signature = part // Assigning Substring + default: + break + } + } + + // Ensure that all components are properly assigned + guard let unwrappedHeader = header, + let unwrappedPayload = payload, + let unwrappedSignature = signature else { + throw SDJWTVerifierError.parsingError + } + + // Convert Substring to String just before returning + return (String(unwrappedHeader), String(unwrappedPayload), String(unwrappedSignature)) + + } + private func parseCombined() throws -> (String, [Disclosure], String?) { let parts = self.serialisedString .split(separator: "~") .map {String($0)} + guard parts.count > 1 else { throw SDJWTVerifierError.parsingError } + let jwt = String(parts[0]) - if serialisedString.hasSuffix("~") == true { - // means no key binding is present - let disclosures = parts[safe: 1.., + kbJwt: JWTString? + ) -> JSON { + let headersAndSignature = JSONObject { + [ + Self.JWS_JSON_HEADER: JSONObject { + [ + Self.JWS_JSON_DISCLOSURES: JSONArray { + disclosures.map { JSON($0) } + }, + Self.JWS_JSON_KB_JWT: kbJwt == nil ? nil : JSON(kbJwt!) + ] + }, + Self.JWS_JSON_PROTECTED: JSON(protected), + Self.JWS_JSON_SIGNATURE: JSON(signature) + ] + } + + switch self { + case .general: + return JSONObject { + [ + Self.JWS_JSON_PAYLOAD: JSON(payload), + Self.JWS_JSON_SIGNATURES: JSONArray { + [headersAndSignature] + } + ] + } + case .flattened: + return JSONObject { + [ + Self.JWS_JSON_PAYLOAD: JSON(payload), + ] + headersAndSignature + } + } + } +} + + diff --git a/Tests/Helpers/Constants.swift b/Tests/Helpers/Constants.swift index bbfe94a..efff808 100644 --- a/Tests/Helpers/Constants.swift +++ b/Tests/Helpers/Constants.swift @@ -29,3 +29,26 @@ let key = let holdersKeyPair = generateES256KeyPair() let issuersKeyPair = generateES256KeyPair() + +struct SDJWTConstants { + + static let compactSdJwt = "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJfc2QiOiBbIkNyUWU3UzVrcUJBSHQtbk1ZWGdjNmJkdDJTSDVhVFkxc1VfTS1QZ2tqUEkiLCAiSnpZakg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSIsICJQb3JGYnBLdVZ1Nnh5bUphZ3ZrRnNGWEFiUm9jMkpHbEFVQTJCQTRvN2NJIiwgIlRHZjRvTGJnd2Q1SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCAiWFFfM2tQS3QxWHlYN0tBTmtxVlI2eVoyVmE1TnJQSXZQWWJ5TXZSS0JNTSIsICJYekZyendzY002R242Q0pEYzZ2Vks4QmtNbmZHOHZPU0tmcFBJWmRBZmRFIiwgImdiT3NJNEVkcTJ4Mkt3LXc1d1BFemFrb2I5aFYxY1JEMEFUTjNvUUw5Sk0iLCAianN1OXlWdWx3UVFsaEZsTV8zSmx6TWFTRnpnbGhRRzBEcGZheVF3TFVLNCJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAic3ViIjogInVzZXJfNDIiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJwRm5kamtaX1ZDem15VGE2VWpsWm8zZGgta284YUlLUWM5RGxHemhhVllvIn0sIHsiLi4uIjogIjdDZjZKa1B1ZHJ5M2xjYndIZ2VaOGtoQXYxVTFPU2xlclAwVmtCSnJXWjAifV0sICJfc2RfYWxnIjogInNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJjcnYiOiAiUC0yNTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbERsczd2Q2VHZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U2dDRqVDlGMkhaUSJ9fX0.ZfSxIFLHf7f84WIMqt7Fzme8-586WutjFnXH4TO5XuWG_peQ4hPsqDpiMBClkh2aUJdl83bwyyOriqvdFra-bg~WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd~WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZhbWlseV9uYW1lIiwgIkRvZSJd~WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ~WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ~WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgInBob25lX251bWJlcl92ZXJpZmllZCIsIHRydWVd~WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImFkZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0~WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIlVTIl0~WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgIkRFIl0~" + + static let payload = "eyJfc2QiOiBbIkNyUWU3UzVrcUJBSHQtbk1ZWGdjNmJkdDJTSDVhVFkxc1VfTS1QZ2tqUEkiLCAiSnpZakg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSIsICJQb3JGYnBLdVZ1Nnh5bUphZ3ZrRnNGWEFiUm9jMkpHbEFVQTJCQTRvN2NJIiwgIlRHZjRvTGJnd2Q1SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCAiWFFfM2tQS3QxWHlYN0tBTmtxVlI2eVoyVmE1TnJQSXZQWWJ5TXZSS0JNTSIsICJYekZyendzY002R242Q0pEYzZ2Vks4QmtNbmZHOHZPU0tmcFBJWmRBZmRFIiwgImdiT3NJNEVkcTJ4Mkt3LXc1d1BFemFrb2I5aFYxY1JEMEFUTjNvUUw5Sk0iLCAianN1OXlWdWx3UVFsaEZsTV8zSmx6TWFTRnpnbGhRRzBEcGZheVF3TFVLNCJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAic3ViIjogInVzZXJfNDIiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJwRm5kamtaX1ZDem15VGE2VWpsWm8zZGgta284YUlLUWM5RGxHemhhVllvIn0sIHsiLi4uIjogIjdDZjZKa1B1ZHJ5M2xjYndIZ2VaOGtoQXYxVTFPU2xlclAwVmtCSnJXWjAifV0sICJfc2RfYWxnIjogInNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJjcnYiOiAiUC0yNTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbERsczd2Q2VHZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U2dDRqVDlGMkhaUSJ9fX0" + + static let disclosures = [ + "WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ", + "WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgInBob25lX251bWJlcl92ZXJpZmllZCIsIHRydWVd", + "WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZhbWlseV9uYW1lIiwgIkRvZSJd", + "WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0", + "WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIlVTIl0", + "WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgIkRFIl0", + "WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImFkZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0", + "WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd", + "WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ", + "WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ" + ] + + static let signature = "ZfSxIFLHf7f84WIMqt7Fzme8-586WutjFnXH4TO5XuWG_peQ4hPsqDpiMBClkh2aUJdl83bwyyOriqvdFra-bg" + static let protected = "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0" +} diff --git a/Tests/JSONSerialization/JSONSerializationTest.swift b/Tests/JSONSerialization/JSONSerializationTest.swift new file mode 100644 index 0000000..0cded15 --- /dev/null +++ b/Tests/JSONSerialization/JSONSerializationTest.swift @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2023 European Commission + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import Foundation +import XCTest + +@testable import eudi_lib_sdjwt_swift + +final class JSONSerializationTest: XCTestCase { + + override func setUp() async throws { + + } + + override func tearDown() { + + } + + func testSdJWTGeneralSerialization() async throws { + + // Given + let parser = CompactParser(serialisedString: SDJWTConstants.compactSdJwt) + let sdJwt = try! parser.getSignedSdJwt() + + // When + let json = try sdJwt.asJwsJsonObject( + option: .general, + kbJwt: sdJwt.kbJwt?.compactSerialization, + getParts: parser.extractJWTParts + ) + + // Then + let payload = json["payload"].stringValue + XCTAssertNotNil(payload, "The value should not be nil") + XCTAssertEqual(payload, SDJWTConstants.payload) + + let signature = json["signatures"].arrayValue.first?["signature"].stringValue + XCTAssertNotNil(signature, "The value should not be nil") + if let signature = signature { + XCTAssertEqual(signature, SDJWTConstants.signature) + } + + let protected = json["signatures"].arrayValue.first?["protected"].stringValue + XCTAssertNotNil(protected, "The value should not be nil") + if let protected = protected { + XCTAssertEqual(protected, SDJWTConstants.protected) + } + + let disclosures = json["signatures"].arrayValue.first?["header"]["disclosures"].arrayValue.map { $0.stringValue } + XCTAssertNotNil(disclosures, "The value should not be nil") + if let disclosures = disclosures { + XCTAssertEqual(disclosures.count, 10) + XCTAssertEqual(disclosures.contains(where: { $0 == "WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ" }), true) + } + } + + func testSdJWTFlattendedSerializationtest() async throws { + + // Given + let parser = CompactParser(serialisedString: SDJWTConstants.compactSdJwt) + let sdJwt = try! parser.getSignedSdJwt() + + // When + let json = try sdJwt.asJwsJsonObject( + option: .flattened, + kbJwt: sdJwt.kbJwt?.compactSerialization, + getParts: parser.extractJWTParts + ) + + // Then + let payload = json["payload"].stringValue + XCTAssertEqual(payload, SDJWTConstants.payload) + + let signature = json["signature"].stringValue + XCTAssertNotNil(signature, "The value should not be nil") + XCTAssertEqual(signature, SDJWTConstants.signature) + + let protected = json["protected"].stringValue + XCTAssertNotNil(protected, "The value should not be nil") + XCTAssertEqual(protected, SDJWTConstants.protected) + + let disclosures = json["header"]["disclosures"].arrayValue.map { $0.stringValue } + XCTAssertNotNil(disclosures, "The value should not be nil") + XCTAssertEqual(disclosures.count, 10) + XCTAssertEqual(disclosures.contains(where: { $0 == "WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ" }), true) + } +} +