UNPKG

@sphereon/ssi-sdk.ebsi-support

Version:

212 lines • 13.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ebsiGetIssuer = exports.ebsiAuthRequestExecution = exports.ebsiGetAttestation = exports.ebsiGetAttestationInterpreter = exports.ebsiCreateAttestationAuthRequestURL = void 0; const oid4vci_client_1 = require("@sphereon/oid4vci-client"); const oid4vci_common_1 = require("@sphereon/oid4vci-common"); const ssi_sdk_ext_did_utils_1 = require("@sphereon/ssi-sdk-ext.did-utils"); const ssi_sdk_ext_key_utils_1 = require("@sphereon/ssi-sdk-ext.key-utils"); const ssi_sdk_oid4vci_holder_1 = require("@sphereon/ssi-sdk.oid4vci-holder"); const ssi_sdk_siopv2_oid4vp_op_auth_1 = require("@sphereon/ssi-sdk.siopv2-oid4vp-op-auth"); const waitFor_1 = require("xstate/lib/waitFor"); const index_1 = require("../index"); const AttestationHeadlessCallbacks_1 = require("./AttestationHeadlessCallbacks"); const index_2 = require("./index"); /** * Method to generate an authz url for getting attestation credentials from a (R)TAO on EBSI using a cloud/service wallet * * This method can be used standalone. But it can also be used as input for the `oid4vciHolderStart` agent method, * to start a OID4VCI holder flow. * * @param opts * @param context */ const ebsiCreateAttestationAuthRequestURL = (_a, context_1) => __awaiter(void 0, [_a, context_1], void 0, function* ({ clientId: clientIdArg, credentialIssuer, credentialType, idOpts, redirectUri, requestObjectOpts, formats = ['jwt_vc', 'jwt_vc_json'], }, context) { var _b, _c, _d, _e; index_1.logger.info(`create attestation ${credentialType} auth req URL for ${clientIdArg} and issuer ${credentialIssuer}`); const resolution = yield context.agent.identifierManagedGetByDid(idOpts); const identifier = resolution.identifier; if (identifier.provider !== 'did:ebsi' && identifier.provider !== 'did:key') { throw Error(`EBSI only supports did:key for natural persons and did:ebsi for legal persons. Provider: ${identifier.provider}, did: ${identifier.did}`); } // This only works if the DID is actually registered, otherwise use our internal KMS; // that is why the offline argument is passed in when type is Verifiable Auth to Onboard, as no DID is present at that point yet // We are getting the ES256 key here, as that is the one needed for auth in EBSI const authKey = yield (0, ssi_sdk_ext_did_utils_1.getAuthenticationKey)({ identifier, offlineWhenNoDIDRegistered: credentialType === 'VerifiableAuthorisationToOnboard', noVerificationMethodFallback: true, keyType: 'Secp256r1', }, context); const kid = (_c = (_b = authKey.meta) === null || _b === void 0 ? void 0 : _b.jwkThumbprint) !== null && _c !== void 0 ? _c : (0, ssi_sdk_ext_key_utils_1.calculateJwkThumbprintForKey)({ key: authKey }); const clientId = clientIdArg !== null && clientIdArg !== void 0 ? clientIdArg : identifier.did; const vciClient = yield oid4vci_client_1.OpenID4VCIClient.fromCredentialIssuer({ credentialIssuer, kid, clientId, createAuthorizationRequestURL: false, // We will do that down below retrieveServerMetadata: true, }); const allMatches = vciClient.getCredentialsSupported(false); let arrayMatches; if (Array.isArray(allMatches)) { arrayMatches = allMatches; } else { arrayMatches = Object.entries(allMatches).map(([id, supported]) => { supported.id = id; return supported; }); } const supportedConfigurations = arrayMatches .filter((supported) => (0, oid4vci_common_1.getTypesFromCredentialSupported)(supported, { filterVerifiableCredential: false }).includes(credentialType)) .filter((supported) => (supported.format === 'jwt_vc' || supported.format === 'jwt_vc_json') && formats.includes(supported.format)); if (supportedConfigurations.length === 0) { throw Error(`Could not find '${credentialType}' with format(s) '${formats.join(',')}' in list of supported types for issuer: ${credentialIssuer}`); } const authorizationDetails = supportedConfigurations.map((supported) => { return { type: 'openid_credential', format: supported.format, types: (0, oid4vci_common_1.getTypesFromCredentialSupported)(supported), }; }); const signCallbacks = (_d = requestObjectOpts.signCallbacks) !== null && _d !== void 0 ? _d : { signCallback: (0, ssi_sdk_oid4vci_holder_1.signCallback)(idOpts, context), }; const authorizationRequestOpts = { redirectUri, clientId, authorizationDetails, requestObjectOpts: Object.assign(Object.assign({}, requestObjectOpts), { signCallbacks, kid: (_e = requestObjectOpts.kid) !== null && _e !== void 0 ? _e : kid }), }; // todo: Do we really need to do this, or can we just set the create option to true at this point? We are passing in the authzReq opts const authorizationCodeURL = yield vciClient.createAuthorizationRequestUrl({ authorizationRequest: authorizationRequestOpts, }); index_1.logger.info(`create attestation ${credentialType} auth req URL for ${clientIdArg} and issuer ${credentialIssuer}, result: ${authorizationCodeURL}`); const jwaAlg = yield (0, ssi_sdk_ext_key_utils_1.signatureAlgorithmFromKey)({ key: authKey }); if (!(jwaAlg in oid4vci_common_1.Alg)) { return Promise.reject(Error(`${jwaAlg} is not supported`)); } // @ts-ignore const alg = oid4vci_common_1.Alg[jwaAlg]; return { requestData: { createAuthorizationRequestURL: false, flowType: oid4vci_common_1.AuthzFlowType.AUTHORIZATION_CODE_FLOW, uri: credentialIssuer, existingClientState: JSON.parse(yield vciClient.exportState()), }, accessTokenOpts: { clientOpts: { alg, clientId, kid, signCallbacks, clientAssertionType: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', }, }, authorizationRequestOpts, authorizationCodeURL, identifier: resolution, authKey, didMethodPreferences: [ssi_sdk_ext_did_utils_1.SupportedDidMethodEnum.DID_EBSI, ssi_sdk_ext_did_utils_1.SupportedDidMethodEnum.DID_KEY], }; }); exports.ebsiCreateAttestationAuthRequestURL = ebsiCreateAttestationAuthRequestURL; const ebsiGetAttestationInterpreter = (_a, context_1) => __awaiter(void 0, [_a, context_1], void 0, function* ({ clientId, authReqResult }, context) { var _b, _c, _d, _e; const identifier = authReqResult.identifier; const vciStateCallbacks = new Map(); const vpStateCallbacks = new Map(); const oid4vciMachine = yield context.agent.oid4vciHolderGetMachineInterpreter(Object.assign(Object.assign({}, authReqResult), { issuanceOpt: { identifier, supportedPreferredDidMethod: ssi_sdk_ext_did_utils_1.SupportedDidMethodEnum.DID_EBSI, kid: (_c = (_b = authReqResult.authKey.meta) === null || _b === void 0 ? void 0 : _b.jwkThumbprint) !== null && _c !== void 0 ? _c : authReqResult.authKey.kid, }, clientOpts: { clientAssertionType: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', kid: (_e = (_d = authReqResult.authKey.meta) === null || _d === void 0 ? void 0 : _d.jwkThumbprint) !== null && _e !== void 0 ? _e : authReqResult.authKey.kid, clientId, }, didMethodPreferences: [ssi_sdk_ext_did_utils_1.SupportedDidMethodEnum.DID_EBSI, ssi_sdk_ext_did_utils_1.SupportedDidMethodEnum.DID_KEY], stateNavigationListener: (0, ssi_sdk_oid4vci_holder_1.OID4VCICallbackStateListener)(vciStateCallbacks) })); const vpLinkHandler = new ssi_sdk_siopv2_oid4vp_op_auth_1.Siopv2OID4VPLinkHandler({ protocols: ['openid:'], // @ts-ignore context, noStateMachinePersistence: true, stateNavigationListener: (0, ssi_sdk_siopv2_oid4vp_op_auth_1.OID4VPCallbackStateListener)(vpStateCallbacks), }); vpStateCallbacks .set(ssi_sdk_siopv2_oid4vp_op_auth_1.Siopv2MachineStates.done, (0, AttestationHeadlessCallbacks_1.siopDoneCallback)({ oid4vciMachine }, context)) .set(ssi_sdk_siopv2_oid4vp_op_auth_1.Siopv2MachineStates.handleError, (0, AttestationHeadlessCallbacks_1.handleErrorCallback)(context)) .set(ssi_sdk_siopv2_oid4vp_op_auth_1.Siopv2MachineStates.error, (0, AttestationHeadlessCallbacks_1.handleErrorCallback)(context)); vciStateCallbacks .set(ssi_sdk_oid4vci_holder_1.OID4VCIMachineStates.handleError, (0, AttestationHeadlessCallbacks_1.handleErrorCallback)(context)) .set(ssi_sdk_oid4vci_holder_1.OID4VCIMachineStates.addContact, (0, AttestationHeadlessCallbacks_1.addContactCallback)(context)) .set(ssi_sdk_oid4vci_holder_1.OID4VCIMachineStates.selectCredentials, (0, AttestationHeadlessCallbacks_1.selectCredentialsCallback)(context)) .set(ssi_sdk_oid4vci_holder_1.OID4VCIMachineStates.initiateAuthorizationRequest, (0, AttestationHeadlessCallbacks_1.authorizationCodeUrlCallback)({ authReqResult, vpLinkHandler, }, context)) .set(ssi_sdk_oid4vci_holder_1.OID4VCIMachineStates.reviewCredentials, (0, AttestationHeadlessCallbacks_1.reviewCredentialsCallback)(context)); return oid4vciMachine.interpreter; }); exports.ebsiGetAttestationInterpreter = ebsiGetAttestationInterpreter; const ebsiGetAttestation = (_a, agentContext_1) => __awaiter(void 0, [_a, agentContext_1], void 0, function* ({ clientId, authReqResult, opts = { timeout: 30000 } }, agentContext) { var _b; index_1.logger.info(`Getting EBSI attestation for ${authReqResult.identifier.did} and ${clientId}`); const interpreter = yield (0, exports.ebsiGetAttestationInterpreter)({ clientId, authReqResult }, agentContext); const state = yield (0, waitFor_1.waitFor)(interpreter.start(), (state) => state.matches('done') || state.matches('handleError') || state.matches('error'), { timeout: (_b = opts.timeout) !== null && _b !== void 0 ? _b : 30000, }); const { contactAlias, contact, credentialBranding, issuanceOpt, error, credentialsToAccept } = state.context; if (state.matches('handleError') || state.matches('error')) { index_1.logger.error(JSON.stringify(state.context.error)); throw Error(JSON.stringify(state.context.error)); } const result = { contactAlias, contact: contact, credentialBranding, identifier: (issuanceOpt === null || issuanceOpt === void 0 ? void 0 : issuanceOpt.identifier) ? (yield agentContext.agent.identifierManagedGet(issuanceOpt.identifier)) : authReqResult.identifier, error, credentials: credentialsToAccept, }; index_1.logger.info(`EBSI attestation for ${authReqResult.identifier.did} and ${clientId}`, result); return result; }); exports.ebsiGetAttestation = ebsiGetAttestation; /** * Normally you would use the browser to let the user make this call in the front channel, * however EBSI mainly uses mocks at present, and we want to be able to test as well */ const ebsiAuthRequestExecution = (authRequestResult, opts) => __awaiter(void 0, void 0, void 0, function* () { const { requestData, authorizationCodeURL } = authRequestResult; const vciClient = yield oid4vci_client_1.OpenID4VCIClient.fromState({ state: requestData === null || requestData === void 0 ? void 0 : requestData.existingClientState }); index_1.logger.debug(`URL: ${authorizationCodeURL}, according to client: ${vciClient.authorizationURL}`); const authResponse = yield (0, oid4vci_common_1.getJson)(authorizationCodeURL); const location = authResponse.origResponse.headers.get('location'); index_1.logger.debug(`LOCATION: ${location}`); }); exports.ebsiAuthRequestExecution = ebsiAuthRequestExecution; const ebsiGetIssuer = ({ credentialIssuer, environment = 'pilot' }) => { if (credentialIssuer) { return credentialIssuer; } if (environment !== 'pilot') { return `${(0, index_2.getEbsiApiBaseUrl)({ environment, version: 'v3' })}/issuer-mock`; } throw Error(`EBSI environment ${environment} needs explicit credential issuer`); }; exports.ebsiGetIssuer = ebsiGetIssuer; //# sourceMappingURL=Attestation.js.map