diff --git a/lib/app/shared/constants/parameters.dart b/lib/app/shared/constants/parameters.dart index 2b6f2f864..1272f7b2b 100644 --- a/lib/app/shared/constants/parameters.dart +++ b/lib/app/shared/constants/parameters.dart @@ -21,7 +21,8 @@ class Parameters { isWalletIntegrityEnabled: true, ); - static const ebsiUniversalLink = 'https://app.altme.io/app/download/ebsi'; + static const oidc4vcUniversalLink = + 'https://app.altme.io/app/download/oidc4vc/'; static const web3RpcMainnetUrl = 'https://mainnet.infura.io/v3/'; diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 232be4c61..7a0cefd8a 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -569,10 +569,15 @@ Future<(String?, String)> getIssuerAndPreAuthorizedCode({ ); if (credentialOfferJson == null) throw Exception(); - preAuthorizedCode = credentialOfferJson['grants'] - ['urn:ietf:params:oauth:grant-type:pre-authorized_code'] - ['pre-authorized_code'] - .toString(); + final dynamic preAuthorizedCodeGrant = credentialOfferJson['grants'] + ['urn:ietf:params:oauth:grant-type:pre-authorized_code']; + + if (preAuthorizedCodeGrant != null && + preAuthorizedCodeGrant is Map && + preAuthorizedCodeGrant.containsKey('authorized_code')) { + preAuthorizedCode = preAuthorizedCodeGrant['authorized_code'] as String; + } + issuer = credentialOfferJson['credential_issuer'].toString(); case OIDC4VCType.GAIAX: diff --git a/lib/dashboard/home/tab_bar/credentials/oid4c4vc_pick/oid4c4vc_credential_pick/view/oid4c4vc_credential_pick_page.dart b/lib/dashboard/home/tab_bar/credentials/oid4c4vc_pick/oid4c4vc_credential_pick/view/oid4c4vc_credential_pick_page.dart index 21c6df239..dcd5dff69 100644 --- a/lib/dashboard/home/tab_bar/credentials/oid4c4vc_pick/oid4c4vc_credential_pick/view/oid4c4vc_credential_pick_page.dart +++ b/lib/dashboard/home/tab_bar/credentials/oid4c4vc_pick/oid4c4vc_credential_pick/view/oid4c4vc_credential_pick_page.dart @@ -76,8 +76,11 @@ class Oidc4vcCredentialPickView extends StatelessWidget { : () { if (state.isEmpty) return; + final selectedCredentials = + state.map((index) => credentials[index]).toList(); + context.read().addCredentialsInLoop( - credentials: credentials, + credentials: selectedCredentials, userPin: userPin, ); }, 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 515716f7d..19dd1b527 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 @@ -497,6 +497,7 @@ class QRCodeScanCubit extends Cubit { required OIDC4VCType currentOIIDC4VCType, required QRCodeScanCubit qrCodeScanCubit, }) async { + emit(state.copyWith(uri: Uri.parse(scannedResponse))); switch (currentOIIDC4VCType) { case OIDC4VCType.DEFAULT: case OIDC4VCType.GREENCYPHER: diff --git a/lib/oidc4vc/initiate_oidv4vc_credential_issuance.dart b/lib/oidc4vc/initiate_oidv4vc_credential_issuance.dart index 858972135..f63c36e7d 100644 --- a/lib/oidc4vc/initiate_oidv4vc_credential_issuance.dart +++ b/lib/oidc4vc/initiate_oidv4vc_credential_issuance.dart @@ -80,8 +80,6 @@ Future getAndAddCredential({ required DioClient dioClient, required String? userPin, }) async { - final Uri uriFromScannedResponse = Uri.parse(scannedResponse); - final (preAuthorizedCode, issuer) = await getIssuerAndPreAuthorizedCode( oidc4vcType: oidc4vcType, scannedResponse: scannedResponse, @@ -102,7 +100,10 @@ Future getAndAddCredential({ didKitProvider: didKitProvider, ); - if (preAuthorizedCode != null) { + final codeForAuthorisedFlow = + Uri.parse(scannedResponse).queryParameters['code']; + + if (preAuthorizedCode != null || codeForAuthorisedFlow != null) { final ( dynamic encodedCredentialOrFutureToken, String? deferredCredentialEndpoint, @@ -113,10 +114,10 @@ Future getAndAddCredential({ credential: credential, did: did, kid: kid, - credentialRequestUri: uriFromScannedResponse, privateKey: privateKey, indexValue: oidc4vcType.indexValue, userPin: userPin, + code: codeForAuthorisedFlow, ); final String credentialName = getCredentialData(credential); final acceptanceToken = encodedCredentialOrFutureToken['acceptance_token']; @@ -177,10 +178,16 @@ Future getAndAddCredential({ ); } } else { + final dynamic credentialOfferJson = await getCredentialOfferJson( + scannedResponse: scannedResponse, + dioClient: dioClient, + ); + final Uri ebsiAuthenticationUri = await oidc4vc.getAuthorizationUriForIssuer( - scannedResponse, - Parameters.ebsiUniversalLink, + credentialOfferJson: credentialOfferJson, + clientId: did, + redirectUrl: '${Parameters.oidc4vcUniversalLink}$scannedResponse', ); await LaunchUrl.launchUri(ebsiAuthenticationUri); } diff --git a/lib/splash/view/splash_page.dart b/lib/splash/view/splash_page.dart index ce0a7cf93..1414f5c82 100644 --- a/lib/splash/view/splash_page.dart +++ b/lib/splash/view/splash_page.dart @@ -90,6 +90,30 @@ class _SplashViewState extends State { return; } + if (uri.toString().startsWith(Parameters.oidc4vcUniversalLink)) { + final url = uri.toString().split(Parameters.oidc4vcUniversalLink)[1]; + + final List parts = url.split('?'); + + final String modifiedUrl = '${parts[0]}?${parts.sublist(1).join('&')}'; + + final OIDC4VCType? currentOIIDC4VCTypeForIssuance = + getOIDC4VCTypeForIssuance(modifiedUrl); + + if (currentOIIDC4VCTypeForIssuance != null) { + /// issuer side (oidc4VCI) + + await context.read().startOIDC4VCCredentialIssuance( + scannedResponse: modifiedUrl, + currentOIIDC4VCType: currentOIIDC4VCTypeForIssuance, + qrCodeScanCubit: context.read(), + ); + return; + } + + return; + } + if (uri.toString().startsWith('iden3comm://')) { /// if wallet has not been created then alert user final ssiKey = diff --git a/packages/oidc4vc/lib/src/oidc4vc.dart b/packages/oidc4vc/lib/src/oidc4vc.dart index 1afd0195d..6f0174625 100644 --- a/packages/oidc4vc/lib/src/oidc4vc.dart +++ b/packages/oidc4vc/lib/src/oidc4vc.dart @@ -90,106 +90,93 @@ class OIDC4VC { /// Received JWT keys are already sorted in lexicographic order /// getAuthorizationUriForIssuer - Future getAuthorizationUriForIssuer( - String openIdRequest, - String redirectUrl, - ) async { - if (openIdRequest.startsWith(oidc4vcModel.offerPrefix)) { - try { - final authorizationRequestParemeters = - getAuthorizationRequestParemeters(openIdRequest, redirectUrl); - - final baseUrl = getIssuerFromOpenidRequest(openIdRequest); - - final openidConfigurationResponse = await getOpenIdConfig(baseUrl); - - late String authorizationEndpoint; - - final authorizationServer = - openidConfigurationResponse['authorization_server']; - if (authorizationServer != null) { - final url = '$authorizationServer/.well-known/openid-configuration'; - final response = await client.get(url); - - authorizationEndpoint = - response.data['authorization_endpoint'] as String; - } else { - authorizationEndpoint = - openidConfigurationResponse['authorization_endpoint'] as String; - } - - final url = Uri.parse(authorizationEndpoint); - final authorizationUri = - Uri.https(url.authority, url.path, authorizationRequestParemeters); - return authorizationUri; - } catch (e) { - throw Exception(e); - } - } - throw Exception('Not a valid openid url to initiate issuance'); - } + Future getAuthorizationUriForIssuer({ + required dynamic credentialOfferJson, + required String clientId, + required String redirectUrl, + }) async { + try { + final issuer = credentialOfferJson['credential_issuer'] as String; + final openidConfigurationResponse = await getOpenIdConfig(issuer); - @visibleForTesting - Map getAuthorizationRequestParemeters( - String openIdRequest, - String redirectUrl, - ) { - final openIdRequestUri = Uri.parse(openIdRequest); - final credentialType = openIdRequestUri.queryParameters['credential_type']; - final opState = openIdRequestUri.queryParameters['op_state']; - final issuer = openIdRequestUri.queryParameters['issuer']; - final myRequest = { - 'scope': 'openid', - 'client_id': redirectUrl, - 'response_type': 'code', - 'authorization_details': jsonEncode([ - { - 'type': 'openid_credential', - 'credential_type': credentialType, - 'format': 'jwt_vc', - } - ]), - 'redirect_uri': - '$redirectUrl?credential_type=$credentialType&issuer=$issuer', - 'state': opState, - 'op_state': opState, - }; - return myRequest; - } + final authorizationEndpoint = + await readAuthorizationEndPoint(openidConfigurationResponse); - @visibleForTesting - String getIssuerFromOpenidRequest(String openIdRequest) { - final openIdRequestUri = Uri.parse(openIdRequest); - final issuer = openIdRequestUri.queryParameters['issuer'] ?? ''; - return issuer; - } + final authorizationRequestParemeters = getAuthorizationRequestParemeters( + credentialOfferJson: credentialOfferJson, + clientId: clientId, + redirectUrl: redirectUrl, + ); - /// Extract credential_type's Url from openid request - String getCredentialRequest(String openidRequest) { - var credentialType = ''; - try { - final uri = Uri.parse(openidRequest); - if (uri.scheme == 'openid') { - credentialType = uri.queryParameters['credential_type'] ?? ''; - } + final url = Uri.parse(authorizationEndpoint); + final authorizationUri = + Uri.https(url.authority, url.path, authorizationRequestParemeters); + return authorizationUri; } catch (e) { - credentialType = ''; + throw Exception(e); } - return credentialType; } - /// extract issuer from initial openid request - String getIssuerRequest(String openidRequest) { - var issuer = ''; - try { - final uri = Uri.parse(openidRequest); - if (uri.scheme == 'openid') { - issuer = uri.queryParameters['issuer'] ?? ''; - } - } catch (e) { - issuer = ''; + @visibleForTesting + Map getAuthorizationRequestParemeters({ + required dynamic credentialOfferJson, + required String clientId, + required String redirectUrl, + }) { + //https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-successful-authorization-re + final issuer = credentialOfferJson['credential_issuer'] as String; + + final credentials = credentialOfferJson['credentials'] as List; + + final issuerState = credentialOfferJson['grants']['authorization_code'] + ['issuer_state'] as String; + + final authorizationDetails = []; + + for (final credential in credentials) { + final data = { + 'type': 'openid_credential', + 'locations': [issuer], + 'format': credential['format'], + 'types': credential['types'], + }; + authorizationDetails.add(data); } - return issuer; + + final myRequest = { + 'response_type': 'code', + 'client_id': clientId, + 'redirect_uri': redirectUrl, + 'scope': 'openid', + 'issuer_state': issuerState, + 'state': '', + 'nonce': const Uuid().v4(), + 'code_challenge': 'lf3q5-NObcyp41iDSIL51qI7pBLmeYNeyWnNcY2FlW4', + 'code_challenge_method': 'S256', + 'authorization_details': jsonEncode(authorizationDetails), + 'client_metadata': jsonEncode({ + 'authorization_endpoint': 'openid:', + 'scopes_supported': ['openid'], + 'response_types_supported': ['vp_token', 'id_token'], + 'subject_types_supported': ['public'], + 'id_token_signing_alg_values_supported': ['ES256'], + 'request_object_signing_alg_values_supported': ['ES256'], + 'vp_formats_supported': { + 'jwt_vp': { + 'alg_values_supported': ['ES256'], + }, + 'jwt_vc': { + 'alg_values_supported': ['ES256'], + }, + }, + 'subject_syntax_types_supported': [ + 'urn:ietf:params:oauth:jwk-thumbprint', + 'did🔑jwk_jcs-pub', + ], + 'id_token_types_supported': ['subject_signed_id_token'], + }), + }; + return myRequest; } String? nonce; @@ -201,26 +188,20 @@ class OIDC4VC { required dynamic credential, required String did, required String kid, - required Uri credentialRequestUri, required int indexValue, String? preAuthorizedCode, String? mnemonic, String? privateKey, String? userPin, + String? code, }) async { - final kIssuer = getIssuer( - preAuthorizedCode: preAuthorizedCode, - issuer: issuer, - credentialRequestUri: credentialRequestUri, - ); - final tokenData = buildTokenData( preAuthorizedCode: preAuthorizedCode, - credentialRequestUri: credentialRequestUri, userPin: userPin, + code: code, ); - final openidConfigurationResponse = await getOpenIdConfig(kIssuer); + final openidConfigurationResponse = await getOpenIdConfig(issuer); final tokenEndPoint = await readTokenEndPoint(openidConfigurationResponse); @@ -240,7 +221,7 @@ class OIDC4VC { private, did, kid, - kIssuer, + issuer, ); if (nonce == null) throw Exception(); @@ -248,7 +229,6 @@ class OIDC4VC { final (credentialData, format) = await buildCredentialData( nonce: nonce!, issuerTokenParameters: issuerTokenParameters, - credentialRequestUri: credentialRequestUri, openidConfigurationResponse: openidConfigurationResponse, credential: credential, ); @@ -302,9 +282,9 @@ class OIDC4VC { } Map buildTokenData({ - required Uri credentialRequestUri, String? preAuthorizedCode, String? userPin, + String? code, }) { late Map tokenData; @@ -313,14 +293,13 @@ class OIDC4VC { 'pre-authorized_code': preAuthorizedCode, 'grant_type': 'urn:ietf:params:oauth:grant-type:pre-authorized_code', }; - } else { - final issuerAndCode = credentialRequestUri.queryParameters['issuer']; - final issuerAndCodeUri = Uri.parse(issuerAndCode!); - final code = issuerAndCodeUri.queryParameters['code']; + } else if (code != null) { tokenData = { 'code': code, 'grant_type': 'authorization_code', }; + } else { + throw Exception(); } if (userPin != null) { @@ -330,20 +309,6 @@ class OIDC4VC { return tokenData; } - String getIssuer({ - required Uri credentialRequestUri, - String? preAuthorizedCode, - String? issuer, - }) { - if (preAuthorizedCode != null) { - return issuer!; - } else { - final issuerAndCode = credentialRequestUri.queryParameters['issuer']; - final issuerAndCodeUri = Uri.parse(issuerAndCode!); - return '${issuerAndCodeUri.scheme}://${issuerAndCodeUri.authority}${issuerAndCodeUri.path}'; - } - } - Future>> getDidDocument(String didKey) async { try { final didDocument = await client.get>( @@ -373,6 +338,25 @@ class OIDC4VC { return tokenEndPoint; } + Future readAuthorizationEndPoint( + Map openidConfigurationResponse, + ) async { + late String authorizationEndpoint; + + final authorizationServer = + openidConfigurationResponse['authorization_server']; + if (authorizationServer != null) { + final url = '$authorizationServer/.well-known/openid-configuration'; + final response = await client.get(url); + + authorizationEndpoint = response.data['authorization_endpoint'] as String; + } else { + authorizationEndpoint = + openidConfigurationResponse['authorization_endpoint'] as String; + } + return authorizationEndpoint; + } + String readIssuerDid( Response> openidConfigurationResponse, ) { @@ -423,7 +407,6 @@ class OIDC4VC { Future<(Map, String)> buildCredentialData({ required String nonce, required IssuerTokenParameters issuerTokenParameters, - required Uri credentialRequestUri, required Map openidConfigurationResponse, required dynamic credential, }) async { diff --git a/pubspec.yaml b/pubspec.yaml index 47e47c1a1..7b1b7afcc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.20.14+261 +version: 1.20.15+262 publish_to: none environment: