@sphereon/ssi-sdk.ebsi-support
Version:
212 lines • 13.1 kB
JavaScript
;
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