Skip to content

Commit

Permalink
feat: Remove client id scheme for draft 22 and above #3160
Browse files Browse the repository at this point in the history
  • Loading branch information
bibash28 committed Dec 19, 2024
1 parent dcf01f4 commit 03de78c
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 30 deletions.
22 changes: 20 additions & 2 deletions lib/app/shared/helper_functions/helper_functions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1504,14 +1504,14 @@ Future<dynamic> fetchRequestUriPayload({
String getUpdatedUrlForSIOPV2OIC4VP({
required Uri uri,
required Map<String, dynamic> response,
required String clientId,
}) {
final responseType = response['response_type'];
final redirectUri = response['redirect_uri'];
final scope = response['scope'];
final responseUri = response['response_uri'];
final responseMode = response['response_mode'];
final nonce = response['nonce'];
final clientId = response['client_id'];
final claims = response['claims'];
final stateValue = response['state'];
final presentationDefinition = response['presentation_definition'];
Expand All @@ -1526,7 +1526,7 @@ String getUpdatedUrlForSIOPV2OIC4VP({
queryJson['scope'] = scope;
}

if (!uri.queryParameters.containsKey('client_id') && clientId != null) {
if (!uri.queryParameters.containsKey('client_id')) {
queryJson['client_id'] = clientId;
}

Expand Down Expand Up @@ -2426,3 +2426,21 @@ bool isContract(String reciever) {
if (reciever.startsWith('KT1')) return true;
return false;
}

String getClientIdForPresentation({
required bool draft22AndAbove,
required String? clientId,
}) {
if (clientId == null) return '';
if (draft22AndAbove) {
final parts = clientId.split(':');
if (parts.length == 2) {
return parts[1];
} else {
// this is mistake though
return clientId;
}
} else {
return clientId;
}
}
89 changes: 71 additions & 18 deletions lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ class QRCodeScanCubit extends Cubit<QRCodeScanState> {
if (requestUri != null || request != null) {
/// verifier side (oidc4vp) or (siopv2 oidc4vc) with request_uri
/// verify the encoded data first
await verifyJWTBeforeLaunchingOIDC4VCANDSIOPV2Flow();
await verifyJWTBeforeLaunchingOIDC4VPANDSIOPV2Flow();
return;
} else {
emit(state.acceptHost());
Expand Down Expand Up @@ -611,6 +611,20 @@ class QRCodeScanCubit extends Cubit<QRCodeScanState> {
final String? requestUri = uri.queryParameters['request_uri'];
final String? request = uri.queryParameters['request'];

final bool draft22AndAbove = profileCubit
.state
.model
.profileSetting
.selfSovereignIdentityOptions
.customOidc4vcProfile
.oidc4vpDraft
.draft22AndAbove;

final clientId = getClientIdForPresentation(
draft22AndAbove: draft22AndAbove,
clientId: state.uri!.queryParameters['client_id'],
);

/// check if request uri is provided or not
if (requestUri != null || request != null) {
late dynamic encodedData;
Expand All @@ -634,6 +648,7 @@ class QRCodeScanCubit extends Cubit<QRCodeScanState> {
final String newUrl = getUpdatedUrlForSIOPV2OIC4VP(
uri: uri,
response: response,
clientId: clientId,
);

emit(
Expand Down Expand Up @@ -713,8 +728,8 @@ class QRCodeScanCubit extends Cubit<QRCodeScanState> {

final redirectUri = state.uri!.queryParameters['redirect_uri'];
final responseUri = state.uri!.queryParameters['response_uri'];
final clientId = state.uri!.queryParameters['client_id'];
final isClientIdUrl = isURL(clientId.toString());

final isClientIdUrl = isURL(clientId);

/// id_token only
if (isIDTokenOnly(responseType)) {
Expand Down Expand Up @@ -791,7 +806,7 @@ class QRCodeScanCubit extends Cubit<QRCodeScanState> {
if (isSecurityHigh &&
responseUri != null &&
isClientIdUrl &&
!responseUri.contains(clientId.toString())) {
!responseUri.contains(clientId)) {
throw ResponseMessage(
data: {
'error': 'invalid_request',
Expand All @@ -806,7 +821,7 @@ class QRCodeScanCubit extends Cubit<QRCodeScanState> {
if (isSecurityHigh &&
redirectUri != null &&
isClientIdUrl &&
!redirectUri.contains(clientId.toString())) {
!redirectUri.contains(clientId)) {
throw ResponseMessage(
data: {
'error': 'invalid_request',
Expand Down Expand Up @@ -1070,7 +1085,7 @@ class QRCodeScanCubit extends Cubit<QRCodeScanState> {
}

/// verify jwt
Future<void> verifyJWTBeforeLaunchingOIDC4VCANDSIOPV2Flow() async {
Future<void> verifyJWTBeforeLaunchingOIDC4VPANDSIOPV2Flow() async {
final String? requestUri = state.uri?.queryParameters['request_uri'];
final String? request = state.uri?.queryParameters['request'];

Expand All @@ -1079,10 +1094,8 @@ class QRCodeScanCubit extends Cubit<QRCodeScanState> {
if (request != null) {
encodedData = request;
} else if (requestUri != null) {
encodedData = await fetchRequestUriPayload(
url: requestUri,
client: client,
);
encodedData =
await fetchRequestUriPayload(url: requestUri, client: client);
}

final customOidc4vcProfile = profileCubit.state.model.profileSetting
Expand All @@ -1094,7 +1107,7 @@ class QRCodeScanCubit extends Cubit<QRCodeScanState> {
final Map<String, dynamic> payload =
jwtDecode.parseJwt(encodedData as String);

final String clientId = payload['client_id'].toString();
var clientId = payload['client_id'].toString();

//check Signature
try {
Expand All @@ -1117,7 +1130,42 @@ class QRCodeScanCubit extends Cubit<QRCodeScanState> {
Map<String, dynamic>? publicKeyJwk;

final clientIdScheme = payload['client_id_scheme'];
var clientIdScheme = payload['client_id_scheme'];

/// With OIDC4VP Draft 22 and above the client_id_scheme is removed
/// from the authorization request but the value is added to the
/// client_id to be the new client_id value
///
/// in the client_id Authorization Request parameter and other places
/// where the Client Identifier is used, the Client Identifier Schemes
/// are prefixed to the usual Client Identifier, separated by a :
/// (colon) character: <client_id_scheme>:<orig_client_id>
if (clientIdScheme == null) {
final draft22AndAbove = profileCubit
.state
.model
.profileSetting
.selfSovereignIdentityOptions
.customOidc4vcProfile
.oidc4vpDraft
.draft22AndAbove;

if (draft22AndAbove) {
final parts = clientId.split(':');
if (parts.length == 2) {
clientIdScheme = parts[0];
clientId = parts[1];
} else {
throw ResponseMessage(
data: {
'error': 'invalid_request',
'error_description': 'Invalid client_id',
},
);
}
}
}

if (clientIdScheme != null) {
final Map<String, dynamic> header =
Expand Down Expand Up @@ -1174,16 +1222,24 @@ class QRCodeScanCubit extends Cubit<QRCodeScanState> {
final redirectUri = state.uri!.queryParameters['redirect_uri'];
final responseUri = state.uri!.queryParameters['response_uri'];

final clientId = state.uri!.queryParameters['client_id'] ?? '';
final customOidc4vcProfile = profileCubit.state.model.profileSetting
.selfSovereignIdentityOptions.customOidc4vcProfile;

final bool draft22AndAbove =
customOidc4vcProfile.oidc4vpDraft.draft22AndAbove;

final clientId = getClientIdForPresentation(
draft22AndAbove: draft22AndAbove,
clientId: state.uri!.queryParameters['client_id'],
);

final nonce = state.uri?.queryParameters['nonce'];
final stateValue = state.uri?.queryParameters['state'];

// final bool? isEBSI =
// await isEBSIForVerifier(client: client, uri: state.uri!);

final didKeyType = profileCubit.state.model.profileSetting
.selfSovereignIdentityOptions.customOidc4vcProfile.defaultDid;
final didKeyType = customOidc4vcProfile.defaultDid;

final privateKey = await fetchPrivateKey(
profileCubit: profileCubit,
Expand All @@ -1196,9 +1252,6 @@ class QRCodeScanCubit extends Cubit<QRCodeScanState> {
didKeyType: didKeyType,
);

final customOidc4vcProfile = profileCubit.state.model.profileSetting
.selfSovereignIdentityOptions.customOidc4vcProfile;

final Map<String, dynamic> responseData =
await oidc4vc.getDataForSiopV2Flow(
clientId: clientId,
Expand Down
36 changes: 28 additions & 8 deletions lib/scan/cubit/scan_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -558,11 +558,17 @@ class ScanCubit extends Cubit<ScanState> {
if (responseMode == 'direct_post.jwt') {
final iat = (DateTime.now().millisecondsSinceEpoch / 1000).round();

final clientId = uri.queryParameters['client_id'] ?? '';

final customOidc4vcProfile = profileCubit.state.model.profileSetting
.selfSovereignIdentityOptions.customOidc4vcProfile;

final bool draft22AndAbove =
customOidc4vcProfile.oidc4vpDraft.draft22AndAbove;

final clientId = getClientIdForPresentation(
draft22AndAbove: draft22AndAbove,
clientId: uri.queryParameters['client_id'],
);

final didKeyType = customOidc4vcProfile.defaultDid;

final (did, _) = await getDidAndKid(
Expand Down Expand Up @@ -836,10 +842,17 @@ class ScanCubit extends Cubit<ScanState> {
VCFormatType? formatFromPresentationSubmission,
}) async {
final nonce = uri.queryParameters['nonce'] ?? '';
final clientId = uri.queryParameters['client_id'] ?? '';

final customOidc4vcProfile =
profileSetting.selfSovereignIdentityOptions.customOidc4vcProfile;
final customOidc4vcProfile = profileCubit.state.model.profileSetting
.selfSovereignIdentityOptions.customOidc4vcProfile;

final bool draft22AndAbove =
customOidc4vcProfile.oidc4vpDraft.draft22AndAbove;

final clientId = getClientIdForPresentation(
draft22AndAbove: draft22AndAbove,
clientId: uri.queryParameters['client_id'],
);

if (formatFromPresentationSubmission == VCFormatType.vcSdJWT) {
final credentialListJwt = getStringCredentialsForToken(
Expand Down Expand Up @@ -935,12 +948,19 @@ class ScanCubit extends Cubit<ScanState> {
profileCubit: profileCubit,
);

final nonce = uri.queryParameters['nonce'] ?? '';
final clientId = uri.queryParameters['client_id'] ?? '';

final customOidc4vcProfile = profileCubit.state.model.profileSetting
.selfSovereignIdentityOptions.customOidc4vcProfile;

final bool draft22AndAbove =
customOidc4vcProfile.oidc4vpDraft.draft22AndAbove;

final clientId = getClientIdForPresentation(
draft22AndAbove: draft22AndAbove,
clientId: uri.queryParameters['client_id'],
);

final nonce = uri.queryParameters['nonce'] ?? '';

final idToken = await oidc4vc.extractIdToken(
clientId: clientId,
credentialsToBePresented: credentialList,
Expand Down
9 changes: 9 additions & 0 deletions lib/splash/bloclisteners/blocklisteners.dart
Original file line number Diff line number Diff line change
Expand Up @@ -331,9 +331,18 @@ final qrCodeBlocListener = BlocListener<QRCodeScanCubit, QRCodeScanState>(
token: encodedData as String,
);

final bool draft22AndAbove =
customOidc4vcProfile.oidc4vpDraft.draft22AndAbove;

final clientId = getClientIdForPresentation(
draft22AndAbove: draft22AndAbove,
clientId: state.uri!.queryParameters['client_id'],
);

url = getUpdatedUrlForSIOPV2OIC4VP(
uri: state.uri!,
response: response,
clientId: clientId,
);
}

Expand Down
4 changes: 4 additions & 0 deletions packages/oidc4vc/lib/src/oidc4vp_draft_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,8 @@ extension OIDC4VPDraftTypeX on OIDC4VPDraftType {
return 'Draft 23';
}
}

bool get draft22AndAbove {
return this == OIDC4VPDraftType.draft22 || this == OIDC4VPDraftType.draft23;
}
}
7 changes: 5 additions & 2 deletions test/app/shared/helper_functions/helper_functions_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1058,8 +1058,11 @@ void main() {
'client_metadata_uri': 'https://example.com/client_metadata',
};

final updatedUrl =
getUpdatedUrlForSIOPV2OIC4VP(uri: uri, response: response);
final updatedUrl = getUpdatedUrlForSIOPV2OIC4VP(
uri: uri,
response: response,
clientId: 'client123',
);

expect(
updatedUrl.split('&'),
Expand Down

0 comments on commit 03de78c

Please sign in to comment.