diff --git a/Sources/Claim/ClaimsExtractor.swift b/Sources/Claim/ClaimsExtractor.swift index 8e90042..1c86d14 100644 --- a/Sources/Claim/ClaimsExtractor.swift +++ b/Sources/Claim/ClaimsExtractor.swift @@ -15,18 +15,22 @@ */ import SwiftyJSON -public typealias ClaimExtractorResult = (digestsFoundOnPayload: [DigestType], recreatedClaims: JSON) +public typealias ClaimExtractorResult = ( + digestsFoundOnPayload: [DigestType], + recreatedClaims: JSON, + disclosuresPerClaim: DisclosuresPerClaim? +) public class ClaimExtractor { // MARK: - Properties - var digestsOfDisclosuresDict: [DisclosureDigest: Disclosure] + var digestsOfDisclosures: [DisclosureDigest: Disclosure] // MARK: - Lifecycle public init(digestsOfDisclosuresDict: [DisclosureDigest: Disclosure]) { - self.digestsOfDisclosuresDict = digestsOfDisclosuresDict + self.digestsOfDisclosures = digestsOfDisclosuresDict } // MARK: - Methods @@ -46,9 +50,9 @@ public class ClaimExtractor { var sdArray = sdArray.compactMap(\.string) // try to find matching digests in order to be replaced with the value while true { - let (updatedSdArray, foundDigest) = sdArray.findAndRemoveFirst(from: digestsOfDisclosuresDict.compactMap({$0.key})) + let (updatedSdArray, foundDigest) = sdArray.findAndRemoveFirst(from: digestsOfDisclosures.compactMap({$0.key})) if let foundDigest, - let foundDisclosure = digestsOfDisclosuresDict[foundDigest]?.base64URLDecode()?.objectProperty { + let foundDisclosure = digestsOfDisclosures[foundDigest]?.base64URLDecode()?.objectProperty { json[Keys.sd.rawValue].arrayObject = updatedSdArray guard !json[foundDisclosure.key].exists() else { @@ -57,10 +61,15 @@ public class ClaimExtractor { json[foundDisclosure.key] = foundDisclosure.value - if let d = digestsOfDisclosuresDict[foundDigest] { + if let disclosure = digestsOfDisclosures[foundDigest] { let currentJsonPointer = "/" + (currentPath + [foundDisclosure.key]).joined(separator: "/") // visitor?.call(key: foundDisclosure.key, disclosure: foundDisclosure.value.stringValue + " " + foundDigest + " " + d + " " + currentJsonPointer) - visitor?.call(key: currentJsonPointer, disclosure: d) + visitor?.call( + pointer: .init( + pointer: currentJsonPointer + ), + disclosure: disclosure + ) } foundDigests.append(.object(foundDigest)) @@ -89,7 +98,7 @@ public class ClaimExtractor { for (index, object) in subJson.arrayValue.enumerated() { let newPath = currentPath + [key, "\(index)"] // Update the path for array elements if object[Keys.dots.rawValue].exists() { - if let foundDisclosedArrayElement = digestsOfDisclosuresDict[object[Keys.dots].stringValue]? + if let foundDisclosedArrayElement = digestsOfDisclosures[object[Keys.dots].stringValue]? .base64URLDecode()? .arrayProperty { @@ -105,7 +114,7 @@ public class ClaimExtractor { currentPath: newPath // Pass the updated path for the nested JSON ), - !ifHasNested.digestsFoundOnPayload.isEmpty { + !ifHasNested.digestsFoundOnPayload.isEmpty { foundDigests += ifHasNested.digestsFoundOnPayload json[key].arrayObject?[index] = ifHasNested.recreatedClaims } @@ -114,6 +123,6 @@ public class ClaimExtractor { } } } - return (foundDigests, json) + return (foundDigests, json, visitor?.disclosuresPerClaim) } } diff --git a/Sources/Issuer/SDJWT.swift b/Sources/Issuer/SDJWT.swift index 96def1e..86f9f4a 100644 --- a/Sources/Issuer/SDJWT.swift +++ b/Sources/Issuer/SDJWT.swift @@ -54,7 +54,7 @@ public struct SDJWT { } } - func recreateClaims() throws -> ClaimExtractorResult { + func recreateClaims(visitor: Visitor? = nil) throws -> ClaimExtractorResult { let digestCreator = try extractDigestCreator() var digestsOfDisclosuresDict = [DisclosureDigest: Disclosure]() for disclosure in self.disclosures { @@ -66,7 +66,6 @@ public struct SDJWT { } } - let visitor = Visitor() return try ClaimExtractor( digestsOfDisclosuresDict: digestsOfDisclosuresDict ).findDigests( diff --git a/Sources/Issuer/SignedSDJWT.swift b/Sources/Issuer/SignedSDJWT.swift index 5ee037d..07e5510 100644 --- a/Sources/Issuer/SignedSDJWT.swift +++ b/Sources/Issuer/SignedSDJWT.swift @@ -18,7 +18,7 @@ import SwiftyJSON import JSONWebSignature import JSONWebKey -typealias DisclosuresPerClaim = Dictionary +public typealias DisclosuresPerClaim = Dictionary public struct SignedSDJWT { @@ -157,8 +157,11 @@ public extension SignedSDJWT { serialiser(self).serialised } - func recreateClaims() throws -> ClaimExtractorResult { - return try self.toSDJWT().recreateClaims() + func recreateClaims(visitor: Visitor? = nil) throws -> ClaimExtractorResult { + return try self.toSDJWT() + .recreateClaims( + visitor: visitor + ) } func asJwsJsonObject( @@ -176,18 +179,25 @@ public extension SignedSDJWT { ) } - func present(query: Set) async throws -> SignedSDJWT? { + func present( + query: Set, + visitor: Visitor? = Visitor() + ) async throws -> SignedSDJWT? { return try await present( query: { jsonPointer in return query.contains(jsonPointer) - } + }, + visitor: visitor ) } - func present( - query: (JSONPointer) -> Bool + private func present( + query: (JSONPointer) -> Bool, + visitor: Visitor? ) async throws -> SignedSDJWT? { - let (_, disclosuresPerClaim) = try recreateClaimsAndDisclosuresPerClaim() + let (_, disclosuresPerClaim) = try recreateClaimsAndDisclosuresPerClaim( + visitor: visitor + ) let keys = disclosuresPerClaim.keys.filter(query) if keys.isEmpty { return nil @@ -209,30 +219,14 @@ public extension SignedSDJWT { } private extension SignedSDJWT { - func recreateClaimsAndDisclosuresPerClaim() throws -> (JSON, DisclosuresPerClaim) { + func recreateClaimsAndDisclosuresPerClaim(visitor: Visitor?) throws -> (JSON, DisclosuresPerClaim) { - let claims = try recreateClaims() + let claims = try recreateClaims(visitor: visitor) print(claims) - return (JSON.empty, [:]) - } -} - -public protocol ClaimVisitor { - func call(pointer: JSONPointer, disclosure: Disclosure?) - func call(key: String, disclosure: Disclosure?) -} - -public class Visitor: ClaimVisitor { - - public init() { - } - - public func call(pointer: JSONPointer, disclosure: Disclosure?) { - print("Visitor") - } - - public func call(key: String, disclosure: Disclosure?) { - print("Visitor: \(key) \(disclosure ?? "N/A")") + return ( + claims.recreatedClaims, + claims.disclosuresPerClaim ?? [:] + ) } } diff --git a/Sources/Utilities/ClaimVisitor.swift b/Sources/Utilities/ClaimVisitor.swift index 81c32d8..16261cd 100644 --- a/Sources/Utilities/ClaimVisitor.swift +++ b/Sources/Utilities/ClaimVisitor.swift @@ -29,7 +29,10 @@ public final class Visitor: ClaimVisitor { public init() { } - public func call(pointer: JSONPointer, disclosure: Disclosure) { + public func call( + pointer: JSONPointer, + disclosure: Disclosure + ) { // Ensure that the path (pointer) does not already exist in disclosuresPerClaim guard disclosuresPerClaim[pointer] == nil else { fatalError("Disclosures for \(pointer.pointer) have already been calculated.") diff --git a/Sources/Verifier/KeyBindingVerifier.swift b/Sources/Verifier/KeyBindingVerifier.swift index 5508494..ec5e822 100644 --- a/Sources/Verifier/KeyBindingVerifier.swift +++ b/Sources/Verifier/KeyBindingVerifier.swift @@ -53,6 +53,8 @@ public class KeyBindingVerifier: VerifierProtocol { try verifyIat(iatOffset: iatOffset, iat: Date(timeIntervalSince1970: TimeInterval(timeInterval))) try verifyAud(aud: aud, expectedAudience: expectedAudience) + + try verify() } public func verify( @@ -70,6 +72,8 @@ public class KeyBindingVerifier: VerifierProtocol { } self.signatureVerifier = try SignatureVerifier(signedJWT: challenge, publicKey: extractedKey) + + try verify() } @discardableResult diff --git a/Tests/Helpers/Utilities.swift b/Tests/Helpers/Utilities.swift index 32aa23d..548c570 100644 --- a/Tests/Helpers/Utilities.swift +++ b/Tests/Helpers/Utilities.swift @@ -72,7 +72,7 @@ func validateObjectResults(factoryResult result: Result, expect } XCTAssert(expectedDigests + numberOfDecoys <= expectedDigests + decoysLimit) return (json, disclosures) - case .failure(let err): + case .failure: XCTFail("Failed to Create SDJWT") return(.empty, []) } diff --git a/Tests/Issuance/BuilderTest.swift b/Tests/Issuance/BuilderTest.swift index 654c536..89a1237 100644 --- a/Tests/Issuance/BuilderTest.swift +++ b/Tests/Issuance/BuilderTest.swift @@ -54,9 +54,9 @@ final class BuilderTest: XCTestCase { let unsignedJwt = factory.createSDJWTPayload(sdJwtObject: sdObject.asObject) switch unsignedJwt { - case .success((let json, let disclosures)): + case .success: XCTAssert(true) - case .failure(let err): + case .failure: XCTFail("Failed to Create SDJWT") } @@ -112,8 +112,6 @@ final class BuilderTest: XCTestCase { FlatDisclosedClaim("array", ["GR", "DE"]) } - let json = plainJWT.asJSON - @SDJWTBuilder var objects: SdElement { FlatDisclosedClaim("Flat Object", plainJWT.asJSON) diff --git a/Tests/Presentation/PresentationTest.swift b/Tests/Presentation/PresentationTest.swift index f0da223..73eaef0 100644 --- a/Tests/Presentation/PresentationTest.swift +++ b/Tests/Presentation/PresentationTest.swift @@ -32,29 +32,13 @@ final class PresentationTest: XCTestCase { try await super.tearDown() } - func test() async throws { - - - let issuersKey = issuersKeyPair.public - let issuerJwk = try issuersKey.jwk + func testSDJWTPresentationWithSelectiveDisclosures() async throws { + // Given + let visitor = Visitor() let holdersKey = holdersKeyPair.public let holdersJwk = try holdersKey.jwk - - let jsonObject: JSON = [ - "issuer": "https://example.com/issuer", - "jwks": [ - "keys": [ - [ - "crv": "P-256", - "kid": "Ao50Swzv_uWu805LcuaTTysu_6GwoqnvJh9rnc44U48", - "kty": "EC", - "x": issuerJwk.x?.base64URLEncode(), - "y": issuerJwk.y?.base64URLEncode() - ] - ] - ] - ] + var verifier: KeyBindingVerifier = KeyBindingVerifier() let issuerSignedSDJWT = try SDJWTIssuer.issue( issuersPrivateKey: issuersKeyPair.private, @@ -87,22 +71,76 @@ final class PresentationTest: XCTestCase { PlainClaim("crv", "P-256") } } + RecursiveObject("test_recursive") { + FlatDisclosedClaim("recursive_address", "東京都港区芝公園4丁目2−8") + } } + // When let query: Set = Set( - ["/address/region", "/address/country"] + ["/address/region", "/address/country", "/dimitri_recursive/recursive_address"] .compactMap { JSONPointer(pointer: $0) } ) - let presentedSdJwt = try await issuerSignedSDJWT.present( - query: query + query: query, + visitor: visitor ) - // po CompactSerialiser(signedSDJWT: presentedSdJwt!).serialised -// print(presentedSdJwt) + guard let presentedSdJwt = presentedSdJwt else { + XCTFail("Expected presentedSdJwt value to be non-nil but it was nil") + return + } + + let sdHash = DigestCreator() + .hashAndBase64Encode( + input: CompactSerialiser( + signedSDJWT: presentedSdJwt + ).serialised + )! + + var holderPresentation: SignedSDJWT? + XCTAssertNoThrow( + holderPresentation = try SDJWTIssuer + .presentation( + holdersPrivateKey: holdersKeyPair.private, + signedSDJWT: issuerSignedSDJWT, + disclosuresToPresent: presentedSdJwt.disclosures, + keyBindingJWT: KBJWT( + header: DefaultJWSHeaderImpl(algorithm: .ES256), + kbJwtPayload: .init([ + Keys.nonce.rawValue: "123456789", + Keys.aud.rawValue: "example.com", + Keys.iat.rawValue: 1694600000, + Keys.sdHash.rawValue: sdHash + ]) + ) + ) + ) + + let kbJwt = holderPresentation?.kbJwt + + // Then + XCTAssertNoThrow( + try verifier.verify( + iatOffset: .init( + startTime: Date(timeIntervalSince1970: 1694600000 - 1000), + endTime: Date(timeIntervalSince1970: 1694600000) + )!, + expectedAudience: "example.com", + challenge: kbJwt!, + extractedKey: holdersJwk + ) + ) + + XCTAssertNotNil(kbJwt) + XCTAssertEqual(presentedSdJwt.disclosures.count, 4) + + let presentedDisclosures = Set(presentedSdJwt.disclosures) + let visitedDisclosures = Set(visitor.disclosures) + XCTAssertTrue(presentedDisclosures.isSubset(of: visitedDisclosures)) } }