diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 9ef5f3487..c131e20de 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -153,6 +153,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 336B5EA3C3EB96365DF03060 /* [CP] Embed Pods Frameworks */, + BCBC5F3194C8151A0253FE19 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -282,6 +283,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; + BCBC5F3194C8151A0253FE19 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index fb28cdd34..f775fc72b 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -7,6 +7,7 @@ import 'package:altme/oidc4vc/oidc4vc.dart'; import 'package:altme/selective_disclosure/selective_disclosure.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart'; import 'package:dartez/dartez.dart'; import 'package:device_info_plus/device_info_plus.dart'; @@ -1508,7 +1509,7 @@ Future<(String?, String?, String?, String?)> getClientDetails({ final jwtProofOfPossession = profileCubit.oidc4vc.generateToken( payload: payload, tokenParameters: tokenParameters, - clientSecretJwt: true, + ignoreProofHeaderType: true, ); clientAssertion = '$walletAttestationData~$jwtProofOfPossession'; @@ -1681,3 +1682,9 @@ List getStringCredentialsForToken({ return credentialList; } + +String hash(String text) { + final bytes = utf8.encode(text); + final digest = sha256.convert(bytes); + return base64Url.encode(digest.bytes).replaceAll('=', ''); +} diff --git a/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/view/selective_disclosure_pick_page.dart b/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/view/selective_disclosure_pick_page.dart index ef1a8069e..128795ffc 100644 --- a/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/view/selective_disclosure_pick_page.dart +++ b/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/view/selective_disclosure_pick_page.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; @@ -5,6 +7,7 @@ import 'package:altme/scan/cubit/scan_cubit.dart'; import 'package:altme/selective_disclosure/widget/display_selective_disclosure.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:oidc4vc/oidc4vc.dart'; class SelectiveDisclosurePickPage extends StatelessWidget { const SelectiveDisclosurePickPage({ @@ -112,7 +115,7 @@ class SelectiveDisclosurePickView extends StatelessWidget { : () => present( context: context, selectedIndex: state.selected, - skip: false, + uri: uri, ), text: l10n.credentialPickPresent, ), @@ -128,7 +131,7 @@ class SelectiveDisclosurePickView extends StatelessWidget { Future present({ required BuildContext context, required List selectedIndex, - required bool skip, + required Uri uri, }) async { final bool userPINCodeForAuthentication = context .read() @@ -165,6 +168,52 @@ class SelectiveDisclosurePickView extends StatelessWidget { newJwt = '$newJwt${encryptedValues[index]}~'; } + // Key Binding JWT + + final profileCubit = context.read(); + + final customOidc4vcProfile = profileCubit.state.model.profileSetting + .selfSovereignIdentityOptions.customOidc4vcProfile; + + final didKeyType = customOidc4vcProfile.defaultDid; + + final privateKey = await fetchPrivateKey( + profileCubit: profileCubit, + didKeyType: didKeyType, + ); + + final tokenParameters = TokenParameters( + privateKey: jsonDecode(privateKey) as Map, + did: '', // just added as it is required field + mediaType: MediaType.selectiveDisclosure, + clientType: + ClientType.jwkThumbprint, // just added as it is required field + proofHeaderType: customOidc4vcProfile.proofHeader, + clientId: '', // just added as it is required field + ); + + final iat = (DateTime.now().millisecondsSinceEpoch / 1000).round(); + final sdHash = hash(newJwt); + + final nonce = uri.queryParameters['nonce'] ?? ''; + final clientId = uri.queryParameters['client_id'] ?? ''; + + final payload = { + 'nonce': nonce, + 'aud': clientId, + 'iat': iat, + 'sd_hash': sdHash, + }; + + /// sign and get token + final jwtToken = profileCubit.oidc4vc.generateToken( + payload: payload, + tokenParameters: tokenParameters, + ignoreProofHeaderType: true, + ); + + newJwt = '$newJwt$jwtToken'; + final CredentialModel newModel = credentialToBePresented.copyWith(selectiveDisclosureJwt: newJwt); diff --git a/lib/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index 5f3dd2096..55741187a 100644 --- a/lib/scan/cubit/scan_cubit.dart +++ b/lib/scan/cubit/scan_cubit.dart @@ -909,7 +909,7 @@ class ScanCubit extends Cubit { privateKey, ); return vpToken; - } else if (presentJwtVc || presentJwtVcJson || presentVcSdJwt) { + } else if (presentJwtVc || presentJwtVcJson) { final credentialList = getStringCredentialsForToken( credentialsToBePresented: credentialsToBePresented, profileCubit: profileCubit, @@ -925,6 +925,16 @@ class ScanCubit extends Cubit { proofHeaderType: customOidc4vcProfile.proofHeader, ); + return vpToken; + } else if (presentVcSdJwt) { + final credentialList = getStringCredentialsForToken( + credentialsToBePresented: credentialsToBePresented, + profileCubit: profileCubit, + ); + + final vpToken = credentialList.first; + // considering only one + return vpToken; } else { throw Exception(); diff --git a/packages/oidc4vc/lib/src/media_type.dart b/packages/oidc4vc/lib/src/media_type.dart index def43e588..19f89511b 100644 --- a/packages/oidc4vc/lib/src/media_type.dart +++ b/packages/oidc4vc/lib/src/media_type.dart @@ -2,6 +2,7 @@ enum MediaType { proofOfOwnership, basic, walletAttestation, + selectiveDisclosure, } extension MediaTypeX on MediaType { @@ -13,6 +14,8 @@ extension MediaTypeX on MediaType { return 'JWT'; case MediaType.walletAttestation: return 'wiar+jwt'; + case MediaType.selectiveDisclosure: + return 'kb+jwt'; } } } diff --git a/packages/oidc4vc/lib/src/oidc4vc.dart b/packages/oidc4vc/lib/src/oidc4vc.dart index 1083602da..50d589e2a 100644 --- a/packages/oidc4vc/lib/src/oidc4vc.dart +++ b/packages/oidc4vc/lib/src/oidc4vc.dart @@ -1409,7 +1409,7 @@ class OIDC4VC { String generateToken({ required Map payload, required TokenParameters tokenParameters, - bool clientSecretJwt = false, + bool ignoreProofHeaderType = false, }) { final kty = tokenParameters.privateKey['kty'].toString(); @@ -1441,8 +1441,9 @@ class OIDC4VC { // add a key to sign, can only add one for JWT ..addRecipient(key, algorithm: tokenParameters.alg); - if (!clientSecretJwt) { + if (!ignoreProofHeaderType) { /// Proof Header Type is ignored for clientSecretJwt + // also ignored for KB jwt vpBuilder.setProtectedHeader('typ', tokenParameters.mediaType.typ); switch (tokenParameters.proofHeaderType) {