diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 9b0309037..906804906 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -2232,3 +2232,45 @@ bool useOauthServerAuthEndPoint(ProfileModel profileModel) { return false; } + +Future getDPopJwt({ + required OIDC4VC oidc4vc, + required String url, + required String publicKey, + String? accessToken, + String? nonce, +}) async { + final tokenParameters = TokenParameters( + privateKey: jsonDecode(publicKey) as Map, + mediaType: MediaType.dPop, + did: '', // just added as it is required field + clientType: + ClientType.p256JWKThumprint, // just added as it is required field + proofHeaderType: ProofHeaderType.jwk, + clientId: '', // just added as it is required field + ); + + final jti = const Uuid().v4(); + final iat = (DateTime.now().millisecondsSinceEpoch / 1000).round(); + + final payload = { + 'jti': jti, + 'htm': 'POST', + 'htu': url, + 'iat': iat, + }; + + if (accessToken != null) { + final hash = oidc4vc.sh256Hash(accessToken); + payload['ath'] = hash; + } + + if (nonce != null) payload['nonce'] = nonce; + + final jwtToken = oidc4vc.generateToken( + payload: payload, + tokenParameters: tokenParameters, + ignoreProofHeaderType: false, + ); + return jwtToken; +} diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart index e3d40410c..c4c6e07e2 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart @@ -1326,7 +1326,7 @@ class QRCodeScanCubit extends Cubit { clientSecret: clientSecret, clientAuthentication: customOidc4vcProfile.clientAuthentication, oidc4vciDraftType: customOidc4vcProfile.oidc4vciDraft, - formatsSupported: customOidc4vcProfile.formatsSupported??[], + formatsSupported: customOidc4vcProfile.formatsSupported ?? [], oAuthClientAttestation: oAuthClientAttestation, oAuthClientAttestationPop: oAuthClientAttestationPop, secureAuthorizedFlow: customOidc4vcProfile.pushAuthorizationRequest, @@ -1392,6 +1392,9 @@ class QRCodeScanCubit extends Cubit { dio: client.dio, ); + final randomKey = generateRandomP256Key(); + final publicKeyForDPop = sortedPublcJwk(randomKey); + if (savedAccessToken == null) { /// get tokendata final tokenData = oidc4vc.buildTokenData( @@ -1423,6 +1426,28 @@ class QRCodeScanCubit extends Cubit { } } + final tokenEndPoint = await oidc4vc.getTokenEndPoint( + issuer: issuer, + oidc4vciDraftType: customOidc4vcProfile.oidc4vciDraft, + openIdConfiguration: + OpenIdConfiguration.fromJson(openIdConfigurationData), + dio: client.dio, + useOAuthAuthorizationServerLink: + useOauthServerAuthEndPoint(profileCubit.state.model), + ); + + String? dPop; + + if (customOidc4vcProfile.dpopSupport) { + dPop = await getDPopJwt( + oidc4vc: profileCubit.oidc4vc, + url: tokenEndPoint, + accessToken: savedAccessToken, + nonce: savedNonce, + publicKey: publicKeyForDPop, + ); + } + /// get token response final ( Map? tokenResponse, @@ -1430,17 +1455,13 @@ class QRCodeScanCubit extends Cubit { String? cnonce, List? authorizationDetails, ) = await oidc4vc.getTokenResponse( - issuer: issuer, authorization: authorization, - oidc4vciDraftType: customOidc4vcProfile.oidc4vciDraft, - openIdConfiguration: - OpenIdConfiguration.fromJson(openIdConfigurationData), + tokenEndPoint: tokenEndPoint, oAuthClientAttestation: oAuthClientAttestation, oAuthClientAttestationPop: oAuthClientAttestationPop, dio: client.dio, - useOAuthAuthorizationServerLink: - useOauthServerAuthEndPoint(profileCubit.state.model), tokenData: tokenData, + dPop: dPop, ); savedAccessToken = accessToken; @@ -1489,6 +1510,7 @@ class QRCodeScanCubit extends Cubit { openIdConfiguration: OpenIdConfiguration.fromJson(openIdConfigurationData), qrCodeScanCubit: qrCodeScanCubit, + publicKeyForDPop: publicKeyForDPop, ); if (result == null) { diff --git a/lib/oidc4vc/get_credential.dart b/lib/oidc4vc/get_credential.dart index 077107e89..13782cca2 100644 --- a/lib/oidc4vc/get_credential.dart +++ b/lib/oidc4vc/get_credential.dart @@ -27,6 +27,7 @@ Future< required OpenIdConfiguration openIdConfiguration, required List? authorizationDetails, required QRCodeScanCubit qrCodeScanCubit, + required String publicKeyForDPop, }) async { final privateKey = await fetchPrivateKey( isEBSI: isEBSI, @@ -125,13 +126,14 @@ Future< } } - final credentialResponseDataValue = - await profileCubit.oidc4vc.getSingleCredential( + final credentialResponseDataValue = await getSingleCredentialData( + profileCubit: profileCubit, openIdConfiguration: openIdConfiguration, accessToken: accessToken, - nonce: nonce, + cnonce: nonce, dio: Dio(), credentialData: credentialData, + publicKeyForDPop: publicKeyForDPop, ); /// update nonce value @@ -162,7 +164,7 @@ Future< issuer: issuer, kid: kid, privateKey: privateKey, - formatsSupported: customOidc4vcProfile.formatsSupported??[], + formatsSupported: customOidc4vcProfile.formatsSupported ?? [], ); if (profileCubit.state.model.isDeveloperMode) { @@ -181,13 +183,14 @@ Future< } } - final credentialResponseDataValue = - await profileCubit.oidc4vc.getSingleCredential( + final credentialResponseDataValue = await getSingleCredentialData( + profileCubit: profileCubit, openIdConfiguration: openIdConfiguration, accessToken: accessToken, - nonce: cnonce, + cnonce: cnonce, dio: Dio(), credentialData: credentialData, + publicKeyForDPop: publicKeyForDPop, ); credentialResponseData.add(credentialResponseDataValue); @@ -202,3 +205,87 @@ Future< format, ); } + +int count = 0; + +Future getSingleCredentialData({ + required ProfileCubit profileCubit, + required OpenIdConfiguration openIdConfiguration, + required String accessToken, + required String? cnonce, + required Dio dio, + required Map credentialData, + required String publicKeyForDPop, +}) async { + final credentialEndpoint = + profileCubit.oidc4vc.readCredentialEndpoint(openIdConfiguration); + + final customOidc4vcProfile = profileCubit.state.model.profileSetting + .selfSovereignIdentityOptions.customOidc4vcProfile; + try { + String? dPop; + + if (customOidc4vcProfile.dpopSupport) { + dPop = await getDPopJwt( + oidc4vc: profileCubit.oidc4vc, + url: credentialEndpoint, + accessToken: accessToken, + publicKey: publicKeyForDPop, + ); + } + + final credentialResponseDataValue = + await profileCubit.oidc4vc.getSingleCredential( + openIdConfiguration: openIdConfiguration, + accessToken: accessToken, + nonce: cnonce, + dio: Dio(), + credentialData: credentialData, + credentialEndpoint: credentialEndpoint, + dPop: dPop, + ); + + return credentialResponseDataValue; + } catch (e) { + if (count == 1) { + count = 0; + rethrow; + } + + if (e is DioException && + e.response != null && + e.response!.data is Map && + (e.response!.data as Map).containsKey('c_nonce')) { + count++; + + String? dPop2; + + if (customOidc4vcProfile.dpopSupport) { + dPop2 = await getDPopJwt( + oidc4vc: profileCubit.oidc4vc, + url: credentialEndpoint, + accessToken: accessToken, + publicKey: publicKeyForDPop, + ); + } + + final nonce = e.response!.data['c_nonce'].toString(); + + final credentialResponseDataValue = + await profileCubit.oidc4vc.getSingleCredential( + openIdConfiguration: openIdConfiguration, + accessToken: accessToken, + nonce: nonce, + dio: dio, + credentialData: credentialData, + credentialEndpoint: credentialEndpoint, + dPop: dPop2, + ); + count = 0; + return credentialResponseDataValue; + } else { + count = 0; + rethrow; + } + } +} diff --git a/packages/oidc4vc/lib/src/media_type.dart b/packages/oidc4vc/lib/src/media_type.dart index 19f89511b..888906d72 100644 --- a/packages/oidc4vc/lib/src/media_type.dart +++ b/packages/oidc4vc/lib/src/media_type.dart @@ -3,6 +3,7 @@ enum MediaType { basic, walletAttestation, selectiveDisclosure, + dPop, } extension MediaTypeX on MediaType { @@ -16,6 +17,8 @@ extension MediaTypeX on MediaType { return 'wiar+jwt'; case MediaType.selectiveDisclosure: return 'kb+jwt'; + case MediaType.dPop: + return 'dpop+jwt'; } } } diff --git a/packages/oidc4vc/lib/src/oidc4vc.dart b/packages/oidc4vc/lib/src/oidc4vc.dart index 833469dd8..2a5a903c8 100644 --- a/packages/oidc4vc/lib/src/oidc4vc.dart +++ b/packages/oidc4vc/lib/src/oidc4vc.dart @@ -452,24 +452,14 @@ class OIDC4VC { return deferredCredentialEndpoint; } - /// tokenResponse, accessToken, cnonce, authorizationDetails - Future<(Map?, String?, String?, List?)> - getTokenResponse({ + /// tokenEndPoint + Future getTokenEndPoint({ required String issuer, required OIDC4VCIDraftType oidc4vciDraftType, required OpenIdConfiguration openIdConfiguration, required Dio dio, required bool useOAuthAuthorizationServerLink, - required Map tokenData, - String? authorization, - String? oAuthClientAttestation, - String? oAuthClientAttestationPop, }) async { - Map? tokenResponse; - String? accessToken; - String? cnonce; - List? authorizationDetails; - final tokenEndPoint = await readTokenEndPoint( openIdConfiguration: openIdConfiguration, issuer: issuer, @@ -478,6 +468,25 @@ class OIDC4VC { useOAuthAuthorizationServerLink: useOAuthAuthorizationServerLink, ); + return tokenEndPoint; + } + + /// tokenResponse, accessToken, cnonce, authorizationDetails + Future<(Map?, String?, String?, List?)> + getTokenResponse({ + required Dio dio, + required String tokenEndPoint, + required Map tokenData, + required String? authorization, + required String? oAuthClientAttestation, + required String? oAuthClientAttestationPop, + required String? dPop, + }) async { + Map? tokenResponse; + String? accessToken; + String? cnonce; + List? authorizationDetails; + tokenResponse = await getToken( tokenEndPoint: tokenEndPoint, tokenData: tokenData, @@ -485,6 +494,7 @@ class OIDC4VC { dio: dio, oAuthClientAttestation: oAuthClientAttestation, oAuthClientAttestationPop: oAuthClientAttestationPop, + dPop: dPop, ); if (tokenResponse.containsKey('c_nonce')) { @@ -506,53 +516,27 @@ class OIDC4VC { required String? nonce, required Dio dio, required Map credentialData, + required String credentialEndpoint, + required String? dPop, }) async { - try { - /// sign proof - final credentialEndpoint = readCredentialEndpoint(openIdConfiguration); - - final credentialHeaders = { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $accessToken', - }; - - final dynamic credentialResponse = await dio.post( - credentialEndpoint, - options: Options(headers: credentialHeaders), - data: credentialData, - ); - - final credentialResponseData = credentialResponse.data; + final credentialHeaders = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $accessToken', + }; - return credentialResponseData; - } catch (e) { - if (count == 1) { - count = 0; - rethrow; - } + if (dPop != null) { + credentialHeaders['DPoP'] = dPop; + } - if (e is DioException && - e.response != null && - e.response!.data is Map && - (e.response!.data as Map).containsKey('c_nonce')) { - count++; + final dynamic credentialResponse = await dio.post( + credentialEndpoint, + options: Options(headers: credentialHeaders), + data: credentialData, + ); - final nonce = e.response!.data['c_nonce'].toString(); + final credentialResponseData = credentialResponse.data; - final credentialResponseDataValue = await getSingleCredential( - openIdConfiguration: openIdConfiguration, - accessToken: accessToken, - nonce: nonce, - dio: dio, - credentialData: credentialData, - ); - count = 0; - return credentialResponseDataValue; - } else { - count = 0; - rethrow; - } - } + return credentialResponseData; } /// get Deferred credential from url @@ -1359,6 +1343,7 @@ class OIDC4VC { required Dio dio, required String? oAuthClientAttestation, required String? oAuthClientAttestationPop, + required String? dPop, }) async { /// getting token final tokenHeaders = { @@ -1374,6 +1359,10 @@ class OIDC4VC { tokenHeaders['OAuth-Client-Attestation-PoP'] = oAuthClientAttestationPop; } + if (dPop != null) { + tokenHeaders['DPoP'] = dPop; + } + final dynamic tokenResponse = await dio.post>( tokenEndPoint, options: Options(headers: tokenHeaders),