Skip to content

Commit

Permalink
Merge pull request #39 from moratori/bugs/proper-selection-of-input-d…
Browse files Browse the repository at this point in the history
…escriptor

Bugs/proper selection of input descriptor
  • Loading branch information
ryosuke-wakaba authored Nov 22, 2024
2 parents a9d6b53 + ba7688f commit 713fe2e
Show file tree
Hide file tree
Showing 10 changed files with 401 additions and 26 deletions.
24 changes: 24 additions & 0 deletions tw2023_wallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@
A8ADC69B2BCEAD090077A0C4 /* idTokenSharingHistories.json in Resources */ = {isa = PBXBuildFile; fileRef = A8ADC69A2BCEAD090077A0C4 /* idTokenSharingHistories.json */; };
A8AF22882C12A59B00D6EDA5 /* RestoreHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8AF22872C12A59B00D6EDA5 /* RestoreHelper.swift */; };
A8AF228D2C12A6C100D6EDA5 /* RestoreHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8AF228C2C12A6C100D6EDA5 /* RestoreHelperTests.swift */; };
A8B0D5A82CEF347800624E26 /* PresentationExchangeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B0D5A72CEF347000624E26 /* PresentationExchangeTest.swift */; };
A8B0D5AA2CEF356800624E26 /* presentation_definition_multi_descriptors_2.json in Resources */ = {isa = PBXBuildFile; fileRef = A8B0D5A92CEF355800624E26 /* presentation_definition_multi_descriptors_2.json */; };
A8B0D5AB2CEF356800624E26 /* presentation_definition_multi_descriptors_2.json in Resources */ = {isa = PBXBuildFile; fileRef = A8B0D5A92CEF355800624E26 /* presentation_definition_multi_descriptors_2.json */; };
A8B0E66C2CE078E800D9E823 /* presentation_definition_multi_descriptors_1.json in Resources */ = {isa = PBXBuildFile; fileRef = A8B0E66B2CE078E800D9E823 /* presentation_definition_multi_descriptors_1.json */; };
A8B0E66D2CE078E800D9E823 /* presentation_definition_multi_descriptors_1.json in Resources */ = {isa = PBXBuildFile; fileRef = A8B0E66B2CE078E800D9E823 /* presentation_definition_multi_descriptors_1.json */; };
A8C0A3D32CABD088008998C5 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C0A3D22CABD088008998C5 /* Errors.swift */; };
A8C0A3D42CABD089008998C5 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C0A3D22CABD088008998C5 /* Errors.swift */; };
A8C810422B82042300CF8CD6 /* SharingToViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C8103C2B82042200CF8CD6 /* SharingToViewModel.swift */; };
Expand Down Expand Up @@ -449,6 +454,9 @@
A8ADC69A2BCEAD090077A0C4 /* idTokenSharingHistories.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = idTokenSharingHistories.json; sourceTree = "<group>"; };
A8AF22872C12A59B00D6EDA5 /* RestoreHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreHelper.swift; sourceTree = "<group>"; };
A8AF228C2C12A6C100D6EDA5 /* RestoreHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreHelperTests.swift; sourceTree = "<group>"; };
A8B0D5A72CEF347000624E26 /* PresentationExchangeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationExchangeTest.swift; sourceTree = "<group>"; };
A8B0D5A92CEF355800624E26 /* presentation_definition_multi_descriptors_2.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = presentation_definition_multi_descriptors_2.json; sourceTree = "<group>"; };
A8B0E66B2CE078E800D9E823 /* presentation_definition_multi_descriptors_1.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = presentation_definition_multi_descriptors_1.json; sourceTree = "<group>"; };
A8C0A3D22CABD088008998C5 /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
A8C810382B80951800CF8CD6 /* SharingToRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingToRow.swift; sourceTree = "<group>"; };
A8C8103C2B82042200CF8CD6 /* SharingToViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharingToViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -568,6 +576,7 @@
8B297C2D2B393A4900D2998D /* Resources */ = {
isa = PBXGroup;
children = (
A8B0D5AC2CEF407900624E26 /* presentation_definition */,
A8AB4BD42C29853300C009A1 /* credential_response */,
A88D323B2C26A75500429E75 /* metadata */,
A82601B02C26908A00BF8139 /* credential_offer */,
Expand Down Expand Up @@ -711,6 +720,7 @@
8B81E2AC2B33CC4300ED3B4E /* tw2023_walletTests */ = {
isa = PBXGroup;
children = (
A8B0D5A72CEF347000624E26 /* PresentationExchangeTest.swift */,
A8EF7F2E2C75D76100BA9D9C /* Models */,
A88779B92C33DBDA002EE9C2 /* Feature */,
A8AF22892C12A61E00D6EDA5 /* Helper */,
Expand Down Expand Up @@ -1137,6 +1147,15 @@
path = Helper;
sourceTree = "<group>";
};
A8B0D5AC2CEF407900624E26 /* presentation_definition */ = {
isa = PBXGroup;
children = (
A8B0D5A92CEF355800624E26 /* presentation_definition_multi_descriptors_2.json */,
A8B0E66B2CE078E800D9E823 /* presentation_definition_multi_descriptors_1.json */,
);
path = presentation_definition;
sourceTree = "<group>";
};
A8C0A3C82CABCEA0008998C5 /* Provider */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1478,6 +1497,7 @@
A8AB4BDA2C29873400C009A1 /* credential_response_vc_sd_jwt.json in Resources */,
8B297C452B39779000D2998D /* credential_supported_ldp_vc.json in Resources */,
8B297C3F2B39746000D2998D /* credential_supported_jwt_vc.json in Resources */,
A8B0E66D2CE078E800D9E823 /* presentation_definition_multi_descriptors_1.json in Resources */,
A8AB4BC52C294FFD00C009A1 /* credential_offer_minimum.json in Resources */,
A8AB4BE82C2A62B000C009A1 /* credential_issuer_metadata_ldp_vc.json in Resources */,
8B43AE3B2B3AA1300016CF83 /* authorization_server.json in Resources */,
Expand All @@ -1487,6 +1507,7 @@
F657D5C72B3C44F100901A6A /* sharingHistoryData.json in Resources */,
A8AB4BC82C295C9500C009A1 /* credential_offer_tx_code_required.json in Resources */,
A8AB4BD02C296AB900C009A1 /* claim_map_empty.json in Resources */,
A8B0D5AB2CEF356800624E26 /* presentation_definition_multi_descriptors_2.json in Resources */,
8BB513902B3B24C400D4EFB3 /* credential_response_mock.json in Resources */,
F6A239A62B4E2A6500B09F17 /* clientInfo.json in Resources */,
8B81E2E62B34413A00ED3B4E /* credentialData.json in Resources */,
Expand Down Expand Up @@ -1518,8 +1539,10 @@
A8AB4BE92C2A62B000C009A1 /* credential_issuer_metadata_ldp_vc.json in Resources */,
8B0E0AB32B4054E80080F6A3 /* presentation_definition.json in Resources */,
A82601A92C267A2A00BF8139 /* credential_display_filled.json in Resources */,
A8B0D5AA2CEF356800624E26 /* presentation_definition_multi_descriptors_2.json in Resources */,
A8AB4BD22C296C7800C009A1 /* claim_map_filled.json in Resources */,
8B43AE3A2B3A9DD20016CF83 /* authorization_server.json in Resources */,
A8B0E66C2CE078E800D9E823 /* presentation_definition_multi_descriptors_1.json in Resources */,
8B297C442B39778B00D2998D /* credential_supported_ldp_vc.json in Resources */,
A8AB4BE02C298D3B00C009A1 /* credential_response_deferred.json in Resources */,
A8AB4BE32C298D9700C009A1 /* credential_response_notification.json in Resources */,
Expand Down Expand Up @@ -1779,6 +1802,7 @@
8BB513942B3BB69A00D4EFB3 /* AsynTestRunner.swift in Sources */,
8BB5138D2B3AD0CC00D4EFB3 /* VCIClient.swift in Sources */,
8B5C65962B5237C200D72289 /* Types.swift in Sources */,
A8B0D5A82CEF347800624E26 /* PresentationExchangeTest.swift in Sources */,
A8EF7F302C75D78000BA9D9C /* ModelDataTests.swift in Sources */,
A84AB70B2B50B98C00E8C88B /* CredentialSharingHistoryManagerTest.swift in Sources */,
8B43AE2D2B3A5A730016CF83 /* VCIMetadataClient.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class CredentialDetailViewModel {
if let pd = presentationDefinition {
switch credential.format {
case "vc+sd-jwt":
if let matched = pd.matchSdJwtVcToRequirement(
if let matched = pd.firstMatchedInputDescriptor(
sdJwt: credential.payload)
{
let (inputDescriptors, disclosuresWithOptionality) = matched
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class CredentialListViewModel {
print("format: \(format)")
do {
if format == "vc+sd-jwt" {
let ret = presentationDefinition.matchSdJwtVcToRequirement(
let ret = presentationDefinition.firstMatchedInputDescriptor(
sdJwt: credential.payload)
if let (_, disclosures) = ret {
return 0
Expand Down
56 changes: 44 additions & 12 deletions tw2023_wallet/Services/OID/PresentationExchange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ struct PresentationDefinition: Codable {
// extension
let submissionRequirements: [SubmissionRequirement]?

func matchSdJwtVcToRequirement(sdJwt: String) -> (
func firstMatchedInputDescriptor(sdJwt: String) -> (
InputDescriptor, [DisclosureWithOptionality]
)? {
guard let sdJwtParts = try? SDJwtUtil.divideSDJwt(sdJwt: sdJwt) else {
Expand All @@ -88,32 +88,64 @@ struct PresentationDefinition: Codable {
}
})

// 各InputDescriptorをループ
for inputDescriptor in inputDescriptors {
// fieldKeysを取得
let requiredOrOptionalKeys = inputDescriptor.filterKeysWithOptionality(

// inputDescriptorとsourcePayload(クレデンシャル側)に共通するkeyを、optionality付きで取得
let commonKeysWithOptionality = inputDescriptor.filterKeysWithOptionality(
from: sourcePayload)

let matchingDisclosures = createDisclosureWithOptionality(
from: allDisclosures,
with: requiredOrOptionalKeys
)
// sourcePayload(クレデンシャル側)とinputDescriptorに
// 共通するキーがないならば、このループのinputDescriptorにマッチしていない。
if commonKeysWithOptionality.isEmpty {
continue
}

if !matchingDisclosures.isEmpty {
return (inputDescriptor, matchingDisclosures)
// inputDescriptorで必須とされる全てのキーが、共通キーに含まれていないならば、
// このループのinputDescriptorにマッチしていない
guard let fields = inputDescriptor.constraints.fields else {
continue
}
let allIncluded = fields.allSatisfy { field in
let optionalField = field.optional ?? false
if optionalField {
return true
}
return field.path.contains { jsonPath in
let simplifiedPath = String(jsonPath.dropFirst(2))
return commonKeysWithOptionality.contains { (key, _) in key == simplifiedPath }
}
}
if !allIncluded {
continue
}

// 引数に与えられたクレデンシャルは、このループの inputDescriptor に合致している。
// クレデンシャルの各クレームについて、「送信必須のクレーム」、「送信するか否かを選択できるクレーム」、「送信しないクレーム」の情報を返す。
let claimDisclosability = createDisclosureWithOptionality(
from: allDisclosures,
with: commonKeysWithOptionality
)
return (inputDescriptor, claimDisclosability)
}
return nil
}

private func createDisclosureWithOptionality(
from allDisclosures: [Disclosure], with requiredOrOptionalKeys: [(String, Bool)]
from allDisclosures: [Disclosure], with commonKeysWithOptionality: [(String, Bool)]
) -> [DisclosureWithOptionality] {
// 自身が開示可能なSD-JWTクレーム(allDisclosures)について、
// 1.送信が必須なもの、2.送信するか否かを選択できるもの、3.送信しないもの の情報をつけて返す。
//
// 1. isSubmit: true, isUserSelectable: false
// 2. isSubmit: false(デフォルトでは送信しないということ), isUserSelectable: true
// 3. isSubmit: false, isUserSelectable: false
//
return allDisclosures.map { disclosure in
guard let dkey = disclosure.key else {
return DisclosureWithOptionality(
disclosure: disclosure, isSubmit: false, isUserSelectable: false)
}
for (keyName, optionality) in requiredOrOptionalKeys {
for (keyName, optionality) in commonKeysWithOptionality {
if keyName.contains(dkey) {
return DisclosureWithOptionality(
disclosure: disclosure, isSubmit: !optionality,
Expand Down
2 changes: 1 addition & 1 deletion tw2023_wallet/Signature/JWTUtil.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func convertRstoDer(r: Data, s: Data) -> Data? {
}

enum JWTUtil {

/*
For verification-related methods, we plan to enhance the checking process
and implement a mechanism to control the level of checking in the future.
Expand Down
79 changes: 76 additions & 3 deletions tw2023_walletTests/AuthorizationRquestTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ final class AuthorizationRquestTests: XCTestCase {
}
}
}

func testPresentationDefinitionMatchSdJwt() {
let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [MockURLProtocol.self]
Expand Down Expand Up @@ -266,8 +266,9 @@ final class AuthorizationRquestTests: XCTestCase {
let pdOptional = try await processPresentationDefinition(
authorizationRequest, requestObject, using: mockSession)
let pd = try XCTUnwrap(pdOptional, "PresentationDefinition should not be nil.")
let sdJwt = "eyJ0eXAiOiJzZCtqd3QiLCJhbGciOiJFUzI1NiJ9.eyJfc2QiOlsiOWhnWm5VbGEyT1JhTHB3Wkp6T0pBTUZfVUd2dzVOekIwTEdmU1VaNTN6cyJdLCJfc2RfYWxnIjoiU0hBLTI1NiJ9.nzsiKRK39ijCaw0oD9nmhrB41HnZj_CiShckWZAVRW3tCDTm3vrJHyoVj4F7_2mx2aMvbT4iAekDGGtsXyhdvw~WyJmZWE3MTcwYTc3OGRiNzk1IiwiaXNfb2xkZXJfdGhhbl8xMyIsdHJ1ZV0~"
XCTAssertNotNil(pd.matchSdJwtVcToRequirement(sdJwt: sdJwt))
let sdJwt =
"eyJ0eXAiOiJzZCtqd3QiLCJhbGciOiJFUzI1NiJ9.eyJfc2QiOlsiOWhnWm5VbGEyT1JhTHB3Wkp6T0pBTUZfVUd2dzVOekIwTEdmU1VaNTN6cyJdLCJfc2RfYWxnIjoiU0hBLTI1NiJ9.nzsiKRK39ijCaw0oD9nmhrB41HnZj_CiShckWZAVRW3tCDTm3vrJHyoVj4F7_2mx2aMvbT4iAekDGGtsXyhdvw~WyJmZWE3MTcwYTc3OGRiNzk1IiwiaXNfb2xkZXJfdGhhbl8xMyIsdHJ1ZV0~"
XCTAssertNotNil(pd.firstMatchedInputDescriptor(sdJwt: sdJwt))
XCTAssertEqual(pd.id, "12345")
}
catch {
Expand All @@ -276,6 +277,78 @@ final class AuthorizationRquestTests: XCTestCase {
}
}

func testPresentationDefinitionProperDescriptorSelection() {
let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [MockURLProtocol.self]
let mockSession = URLSession(configuration: configuration)

let testURL = URL(string: "https://example.com/presentation_definition.json")!
guard
let url = Bundle.main.url(
forResource: "presentation_definition_multi_descriptors_1", withExtension: "json"),
let mockData = try? Data(contentsOf: url)
else {
XCTFail("Cannot read presentation_definition.json")
return
}
let response = HTTPURLResponse(
url: url, statusCode: 200, httpVersion: nil, headerFields: nil)
MockURLProtocol.mockResponses[testURL.absoluteString] = (mockData, response)

let authorizationRequest = AuthorizationRequestPayloadImpl(
presentationDefinition: nil
)
let requestObject = RequestObjectPayloadImpl(
presentationDefinitionUri: testURL.absoluteString
)

runAsyncTest {
do {
guard
let pd = try await processPresentationDefinition(
authorizationRequest, requestObject, using: mockSession)
else {
XCTFail("PresentationDefinition shoud be present")
return
}

// is_older_than13
let sdJwt1 =
"eyJ0eXAiOiJzZCtqd3QiLCJhbGciOiJFUzI1NiJ9.eyJfc2QiOlsiOWhnWm5VbGEyT1JhTHB3Wkp6T0pBTUZfVUd2dzVOekIwTEdmU1VaNTN6cyJdLCJfc2RfYWxnIjoiU0hBLTI1NiJ9.nzsiKRK39ijCaw0oD9nmhrB41HnZj_CiShckWZAVRW3tCDTm3vrJHyoVj4F7_2mx2aMvbT4iAekDGGtsXyhdvw~WyJmZWE3MTcwYTc3OGRiNzk1IiwiaXNfb2xkZXJfdGhhbl8xMyIsdHJ1ZV0~"
// postal_address
let sdJwt2 =
"eyJ0eXAiOiJzZCtqd3QiLCJhbGciOiJFUzI1NiJ9.eyJfc2QiOlsiQmVHOFVNc1VHdzJFUFNodUhGbDZsek9CZERnaW1LNzE3bjJ5VnN6SWRRbyJdLCJfc2RfYWxnIjoiU0hBLTI1NiJ9.FI4uBC_pL9nMcrzrrMBhZXrgNR6WEJNQxCruoz5gWlc4fDV7Y4UYj-NYJ0O_IVXkfvJJSG4mBRu63LJaTrKdGA~WyJhNDZjZmJlOTUyN2YzOWI3IiwicG9zdGFsX2FkZHJlc3MiLCJUb2t5byBKYXBhbiJd~"

guard
let (inputDescriptor1, disclosureWithOptionallity1) =
pd.firstMatchedInputDescriptor(sdJwt: sdJwt1)
else {
XCTFail("inputDescriptor should not be nil")
return
}
guard
let (inputDescriptor2, disclosureWithOptionallity2) =
pd.firstMatchedInputDescriptor(sdJwt: sdJwt2)
else {
XCTFail("inputDescriptor should not be nil")
return
}
XCTAssertEqual(inputDescriptor1.id, "input2")
XCTAssertTrue(disclosureWithOptionallity1.count == 1)
XCTAssertTrue(!disclosureWithOptionallity1[0].isUserSelectable)
XCTAssertTrue(disclosureWithOptionallity1[0].isSubmit)

XCTAssertEqual(inputDescriptor2.id, "input1")
XCTAssertTrue(disclosureWithOptionallity2.count == 1)
XCTAssertTrue(!disclosureWithOptionallity2[0].isUserSelectable)
XCTAssertTrue(disclosureWithOptionallity2[0].isSubmit)
}
catch {
XCTFail("Request should not fail. \(error)")
}
}
}

func testPresentationDefinitionFromQueryParameter() {
let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [MockURLProtocol.self]
Expand Down
Loading

0 comments on commit 713fe2e

Please sign in to comment.