diff --git a/machines/QrLoginMachine.ts b/machines/QrLoginMachine.ts index 4c9cf32c5c..b88aa29885 100644 --- a/machines/QrLoginMachine.ts +++ b/machines/QrLoginMachine.ts @@ -8,12 +8,12 @@ import { } from 'xstate'; import {createModel} from 'xstate/lib/model'; import {AppServices} from '../shared/GlobalContext'; -import {MY_VCS_STORE_KEY, ESIGNET_BASE_URL} from '../shared/constants'; +import {ESIGNET_BASE_URL, MY_VCS_STORE_KEY} from '../shared/constants'; import {StoreEvents} from './store'; import {linkTransactionResponse, VC} from '../types/VC/ExistingMosipVC/vc'; import {request} from '../shared/request'; import { - getJwt, + getJWT, isHardwareKeystoreExists, } from '../shared/cryptoutil/cryptoUtil'; import { @@ -23,10 +23,12 @@ import { import i18n from '../i18n'; import {parseMetadatas, VCMetadata} from '../shared/VCMetadata'; import { - TelemetryConstants, getEndEventData, sendEndEvent, + TelemetryConstants, } from '../shared/telemetry/TelemetryUtils'; +import {API_URLS} from '../shared/api'; +import getAllConfigurations from '../shared/commonprops/commonProps'; const model = createModel( { @@ -157,7 +159,7 @@ export const qrLoginMachine = faceAuth: { on: { FACE_VALID: { - target: 'requestConsent', + target: 'loadingThumbprint', }, FACE_INVALID: { target: 'invalidIdentity', @@ -180,10 +182,16 @@ export const qrLoginMachine = sendingAuthenticate: { invoke: { src: 'sendAuthenticate', - onDone: { - target: 'requestConsent', - actions: 'setLinkedTransactionId', - }, + onDone: [ + { + cond: 'isConsentAlreadyCaptured', + target: 'success', + }, + { + target: 'requestConsent', + actions: 'setLinkedTransactionId', + }, + ], onError: [ { actions: 'SetErrorMessage', @@ -195,7 +203,7 @@ export const qrLoginMachine = requestConsent: { on: { CONFIRM: { - target: 'loadingThumbprint', + target: 'sendingConsent', }, TOGGLE_CONSENT_CLAIM: { actions: 'setConsentClaims', @@ -212,7 +220,7 @@ export const qrLoginMachine = on: { STORE_RESPONSE: { actions: 'setThumbprint', - target: 'sendingConsent', + target: 'sendingAuthenticate', }, }, }, @@ -353,14 +361,15 @@ export const qrLoginMachine = }, }), setLinkedTransactionId: assign({ - linkedTransactionId: (context, event) => event.data as string, + linkedTransactionId: (context, event) => + event.data.linkedTransactionId as string, }), }, services: { linkTransaction: async context => { const response = await request( - 'POST', - '/v1/esignet/linked-authorization/v2/link-transaction', + API_URLS.linkTransaction.method, + API_URLS.linkTransaction.buildURL(), { requestTime: String(new Date().toISOString()), request: { @@ -381,12 +390,25 @@ export const qrLoginMachine = ); } - var walletBindingResponse = context.selectedVc.walletBindingResponse; - var jwt = await getJwt(privateKey, individualId, context.thumbprint); + var config = await getAllConfigurations(); + const header = { + alg: 'RS256', + 'x5t#S256': context.thumbprint, + }; + + const payload = { + iss: config.issuer, + sub: individualId, + aud: config.audience, + iat: Math.floor(new Date().getTime() / 1000), + exp: Math.floor(new Date().getTime() / 1000) + 18000, + }; + + const jwt = await getJWT(header, payload, individualId, privateKey); const response = await request( - 'POST', - '/v1/esignet/linked-authorization/authenticate', + API_URLS.authenticate.method, + API_URLS.authenticate.buildURL(), { requestTime: String(new Date().toISOString()), request: { @@ -403,7 +425,7 @@ export const qrLoginMachine = }, ESIGNET_BASE_URL, ); - return response.response.linkedTransactionId; + return response.response; }, sendConsent: async context => { @@ -415,44 +437,33 @@ export const qrLoginMachine = ); } - const jwt = await getJwt( - privateKey, - individualId, - context.thumbprint, - ); - - const response = await request( - 'POST', - '/v1/esignet/linked-authorization/authenticate', - { - requestTime: String(new Date().toISOString()), - request: { - linkedTransactionId: context.linkTransactionId, - individualId: individualId, - challengeList: [ - { - authFactorType: 'WLA', - challenge: jwt, - format: 'jwt', - }, - ], - }, - }, - ESIGNET_BASE_URL, - ); - var linkedTrnId = response.response.linkedTransactionId; + const header = { + alg: 'RS256', + 'x5t#S256': context.thumbprint, + }; + const payload = { + accepted_claims: context.essentialClaims + .concat(context.selectedVoluntaryClaims) + .sort(), + permitted_authorized_scopes: context.authorizeScopes, + }; + + const JWT = await getJWT(header, payload, individualId, privateKey); + const jwtComponents = JWT.split('.'); + const detachedSignature = jwtComponents[0] + '.' + jwtComponents[2]; const resp = await request( - 'POST', - '/v1/esignet/linked-authorization/consent', + API_URLS.sendConsent.method, + API_URLS.sendConsent.buildURL(), { requestTime: String(new Date().toISOString()), request: { - linkedTransactionId: linkedTrnId, - acceptedClaims: context.essentialClaims.concat( - context.selectedVoluntaryClaims, - ), + linkedTransactionId: context.linkedTransactionId, + acceptedClaims: context.essentialClaims + .concat(context.selectedVoluntaryClaims) + .sort(), permittedAuthorizeScopes: context.authorizeScopes, + signature: detachedSignature, }, }, ESIGNET_BASE_URL, @@ -460,6 +471,10 @@ export const qrLoginMachine = console.log(resp.response.linkedTransactionId); }, }, + guards: { + isConsentAlreadyCaptured: (_, event) => + event.data?.consentAction === 'NOCAPTURE', + }, }, ); diff --git a/machines/QrLoginMachine.typegen.ts b/machines/QrLoginMachine.typegen.ts index e69de29bb2..ae58a354c3 100644 --- a/machines/QrLoginMachine.typegen.ts +++ b/machines/QrLoginMachine.typegen.ts @@ -0,0 +1,56 @@ + + // This file was automatically generated. Edits will be overwritten + + export interface Typegen0 { + '@@xstate/typegen': true; + internalEvents: { + "done.invoke.QrLogin.linkTransaction:invocation[0]": { type: "done.invoke.QrLogin.linkTransaction:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; +"done.invoke.QrLogin.sendingAuthenticate:invocation[0]": { type: "done.invoke.QrLogin.sendingAuthenticate:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; +"error.platform.QrLogin.linkTransaction:invocation[0]": { type: "error.platform.QrLogin.linkTransaction:invocation[0]"; data: unknown }; +"error.platform.QrLogin.sendingAuthenticate:invocation[0]": { type: "error.platform.QrLogin.sendingAuthenticate:invocation[0]"; data: unknown }; +"error.platform.QrLogin.sendingConsent:invocation[0]": { type: "error.platform.QrLogin.sendingConsent:invocation[0]"; data: unknown }; +"xstate.init": { type: "xstate.init" }; + }; + invokeSrcNameMap: { + "linkTransaction": "done.invoke.QrLogin.linkTransaction:invocation[0]"; +"sendAuthenticate": "done.invoke.QrLogin.sendingAuthenticate:invocation[0]"; +"sendConsent": "done.invoke.QrLogin.sendingConsent:invocation[0]"; + }; + missingImplementations: { + actions: never; + delays: never; + guards: never; + services: never; + }; + eventsCausingActions: { + "SetErrorMessage": "error.platform.QrLogin.linkTransaction:invocation[0]" | "error.platform.QrLogin.sendingAuthenticate:invocation[0]" | "error.platform.QrLogin.sendingConsent:invocation[0]"; +"expandLinkTransResp": "done.invoke.QrLogin.linkTransaction:invocation[0]"; +"forwardToParent": "DISMISS"; +"loadMyVcs": "done.invoke.QrLogin.linkTransaction:invocation[0]"; +"loadThumbprint": "FACE_VALID"; +"resetLinkTransactionId": "GET"; +"resetSelectedVoluntaryClaims": "GET"; +"setClaims": "done.invoke.QrLogin.linkTransaction:invocation[0]"; +"setConsentClaims": "TOGGLE_CONSENT_CLAIM"; +"setLinkedTransactionId": "done.invoke.QrLogin.sendingAuthenticate:invocation[0]"; +"setMyVcs": "STORE_RESPONSE"; +"setScanData": "GET"; +"setSelectedVc": "SELECT_VC"; +"setThumbprint": "STORE_RESPONSE"; +"setlinkTransactionResponse": "done.invoke.QrLogin.linkTransaction:invocation[0]"; + }; + eventsCausingDelays: { + + }; + eventsCausingGuards: { + "isConsentAlreadyCaptured": "done.invoke.QrLogin.sendingAuthenticate:invocation[0]"; + }; + eventsCausingServices: { + "linkTransaction": "GET"; +"sendAuthenticate": "STORE_RESPONSE"; +"sendConsent": "CONFIRM"; + }; + matchesStates: "ShowError" | "done" | "faceAuth" | "invalidIdentity" | "linkTransaction" | "loadMyVcs" | "loadingThumbprint" | "requestConsent" | "sendingAuthenticate" | "sendingConsent" | "showvcList" | "success" | "waitingForData"; + tags: never; + } + \ No newline at end of file diff --git a/machines/revoke.typegen.ts b/machines/revoke.typegen.ts index da3480cde1..02b7d0222c 100644 --- a/machines/revoke.typegen.ts +++ b/machines/revoke.typegen.ts @@ -1,62 +1,45 @@ -// This file was automatically generated. Edits will be overwritten -export interface Typegen0 { - '@@xstate/typegen': true; - internalEvents: { - 'done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]': { - type: 'done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]'; - data: unknown; - __tip: 'See the XState TS docs to learn how to strongly type this.'; - }; - 'error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]': { - type: 'error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]'; - data: unknown; - }; - 'xstate.init': {type: 'xstate.init'}; - }; - invokeSrcNameMap: { - requestOtp: 'done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]'; - requestRevoke: 'done.invoke.RevokeVids.requestingRevoke:invocation[0]'; - }; - missingImplementations: { - actions: never; - delays: never; - guards: never; - services: never; - }; - eventsCausingActions: { - clearOtp: - | 'DISMISS' - | 'ERROR' - | 'REVOKE_VCS' - | 'done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]' - | 'xstate.init'; - logRevoked: 'STORE_RESPONSE'; - revokeVID: 'SUCCESS'; - setIdBackendError: 'error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]'; - setOtp: 'INPUT_OTP'; - setOtpError: 'ERROR'; - setTransactionId: 'DISMISS' | 'REVOKE_VCS' | 'xstate.init'; - setVIDs: 'REVOKE_VCS'; - }; - eventsCausingDelays: {}; - eventsCausingGuards: {}; - eventsCausingServices: { - requestOtp: never; - requestRevoke: 'INPUT_OTP'; - }; - matchesStates: - | 'acceptingOtpInput' - | 'acceptingVIDs' - | 'acceptingVIDs.idle' - | 'acceptingVIDs.requestingOtp' - | 'idle' - | 'invalid' - | 'invalid.backend' - | 'invalid.otp' - | 'loggingRevoke' - | 'requestingRevoke' - | 'revokingVc' - | {acceptingVIDs?: 'idle' | 'requestingOtp'; invalid?: 'backend' | 'otp'}; - tags: never; -} + // This file was automatically generated. Edits will be overwritten + + export interface Typegen0 { + '@@xstate/typegen': true; + internalEvents: { + "done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]": { type: "done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; +"error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]": { type: "error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]"; data: unknown }; +"xstate.init": { type: "xstate.init" }; + }; + invokeSrcNameMap: { + "requestOtp": "done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]"; +"requestRevoke": "done.invoke.RevokeVids.requestingRevoke:invocation[0]"; + }; + missingImplementations: { + actions: never; + delays: never; + guards: never; + services: never; + }; + eventsCausingActions: { + "clearOtp": "DISMISS" | "ERROR" | "REVOKE_VCS" | "done.invoke.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]" | "xstate.init"; +"logRevoked": "STORE_RESPONSE"; +"revokeVID": "SUCCESS"; +"setIdBackendError": "error.platform.RevokeVids.acceptingVIDs.requestingOtp:invocation[0]"; +"setOtp": "INPUT_OTP"; +"setOtpError": "ERROR"; +"setTransactionId": "DISMISS" | "REVOKE_VCS" | "xstate.init"; +"setVIDs": "REVOKE_VCS"; + }; + eventsCausingDelays: { + + }; + eventsCausingGuards: { + + }; + eventsCausingServices: { + "requestOtp": never; +"requestRevoke": "INPUT_OTP"; + }; + matchesStates: "acceptingOtpInput" | "acceptingVIDs" | "acceptingVIDs.idle" | "acceptingVIDs.requestingOtp" | "idle" | "invalid" | "invalid.backend" | "invalid.otp" | "loggingRevoke" | "requestingRevoke" | "revokingVc" | { "acceptingVIDs"?: "idle" | "requestingOtp"; +"invalid"?: "backend" | "otp"; }; + tags: never; + } + \ No newline at end of file diff --git a/shared/api.ts b/shared/api.ts index a576978d8e..2264d15c58 100644 --- a/shared/api.ts +++ b/shared/api.ts @@ -62,6 +62,20 @@ export const API_URLS: ApiUrls = { method: 'PATCH', buildURL: (id: string): `/${string}` => `/residentmobileapp/vid/${id}`, }, + linkTransaction: { + method: 'POST', + buildURL: (): `/${string}` => + '/v1/esignet/linked-authorization/v2/link-transaction', + }, + authenticate: { + method: 'POST', + buildURL: (): `/${string}` => + '/v1/esignet/linked-authorization/v2/authenticate', + }, + sendConsent: { + method: 'POST', + buildURL: (): `/${string}` => '/v1/esignet/linked-authorization/v2/consent', + }, }; export const API = { @@ -227,4 +241,7 @@ type ApiUrls = { authLock: Api_Params; authUnLock: Api_Params; requestRevoke: Api_Params; + linkTransaction: Api_Params; + authenticate: Api_Params; + sendConsent: Api_Params; }; diff --git a/shared/cryptoutil/cryptoUtil.ts b/shared/cryptoutil/cryptoUtil.ts index 2bb5078e1c..1983de9d0f 100644 --- a/shared/cryptoutil/cryptoUtil.ts +++ b/shared/cryptoutil/cryptoUtil.ts @@ -1,6 +1,5 @@ import {KeyPair, RSA} from 'react-native-rsa-native'; import forge from 'node-forge'; -import getAllConfigurations from '../commonprops/commonProps'; import {DEBUG_MODE_ENABLED, isIOS} from '../constants'; import SecureKeystore from 'react-native-secure-keystore'; import CryptoJS from 'crypto-js'; @@ -22,47 +21,24 @@ export function generateKeys(): Promise { */ export const isHardwareKeystoreExists = isCustomSecureKeystore(); -export async function getJwt( - privateKey: string, +export async function getJWT( + header: object, + payLoad: object, individualId: string, - thumbprint: string, + privateKey: string, ) { try { - var iat = Math.floor(new Date().getTime() / 1000); - var exp = Math.floor(new Date().getTime() / 1000) + 18000; - - var config = await getAllConfigurations(); - - const header = { - alg: 'RS256', - //'kid': keyId, - 'x5t#S256': thumbprint, - }; - - const payloadJSON = JSON.stringify({ - iss: config.issuer, - sub: individualId, - aud: config.audience, - iat: iat, - exp: exp, - }); - - var payload = JSON.parse(payloadJSON); - const strHeader = JSON.stringify(header); - const strPayload = JSON.stringify(payload); - const header64 = encodeB64(strHeader); - const payload64 = encodeB64(strPayload); - const preHash = header64 + '.' + payload64; - + const header64 = encodeB64(JSON.stringify(header)); + const payLoad64 = encodeB64(JSON.stringify(payLoad)); + const preHash = header64 + '.' + payLoad64; const signature64 = await createSignature( privateKey, preHash, individualId, ); - - return header64 + '.' + payload64 + '.' + signature64; + return header64 + '.' + payLoad64 + '.' + signature64; } catch (e) { - console.log(e); + console.log('Exception Occured While Constructing JWT ', e); throw e; } } diff --git a/shared/openId4VCI/Utils.ts b/shared/openId4VCI/Utils.ts index 13508c219e..b5beea737c 100644 --- a/shared/openId4VCI/Utils.ts +++ b/shared/openId4VCI/Utils.ts @@ -1,4 +1,3 @@ -import {createSignature, encodeB64} from '../cryptoutil/cryptoUtil'; import jwtDecode from 'jwt-decode'; import jose from 'node-jose'; import {isIOS} from '../constants'; @@ -8,6 +7,7 @@ import getAllConfigurations from '../commonprops/commonProps'; import {CredentialWrapper} from '../../types/VC/EsignetMosipVC/vc'; import {VCMetadata} from '../VCMetadata'; import i18next from 'i18next'; +import {getJWT} from '../cryptoutil/cryptoUtil'; export const Protocols = { OpenId4VCI: 'OpenId4VCI', @@ -28,7 +28,26 @@ export const getIdentifier = (context, credential) => { }; export const getBody = async context => { - const proofJWT = await getJWT(context); + const header = { + alg: 'RS256', + jwk: await getJWK(context.publicKey), + typ: 'openid4vci-proof+jwt', + }; + const decodedToken = jwtDecode(context.tokenResponse.accessToken); + const payload = { + iss: context.selectedIssuer.client_id, + nonce: decodedToken.c_nonce, + aud: context.selectedIssuer.credential_audience, + iat: Math.floor(new Date().getTime() / 1000), + exp: Math.floor(new Date().getTime() / 1000) + 18000, + }; + + const proofJWT = await getJWT( + header, + payload, + Issuers_Key_Ref, + context.privateKey, + ); return { format: 'ldp_vc', credential_definition: { @@ -116,37 +135,6 @@ export const getJWK = async publicKey => { ); } }; -export const getJWT = async context => { - try { - const header64 = encodeB64( - JSON.stringify({ - alg: 'RS256', - jwk: await getJWK(context.publicKey), - typ: 'openid4vci-proof+jwt', - }), - ); - const decodedToken = jwtDecode(context.tokenResponse.accessToken); - const payload64 = encodeB64( - JSON.stringify({ - iss: context.selectedIssuer.client_id, - nonce: decodedToken.c_nonce, - aud: context.selectedIssuer.credential_audience, - iat: Math.floor(new Date().getTime() / 1000), - exp: Math.floor(new Date().getTime() / 1000) + 18000, - }), - ); - const preHash = header64 + '.' + payload64; - const signature64 = await createSignature( - context.privateKey, - preHash, - Issuers_Key_Ref, - ); - return header64 + '.' + payload64 + '.' + signature64; - } catch (e) { - console.log(e); - throw e; - } -}; export const vcDownloadTimeout = async (): Promise => { const response = await getAllConfigurations();