Exemple #1
0
    async def create_cred_offer(self, schema_seq_no: int) -> str:
        """
        Create credential offer as Issuer for given schema.

        Raise CorruptWallet if the wallet has no private key for the corresponding credential definition.

        :param schema_seq_no: schema sequence number
        :return: credential offer json for use in storing credentials at HolderProver.
        """

        LOGGER.debug('Issuer.create_cred_offer >>> schema_seq_no: %s',
                     schema_seq_no)

        rv = None
        cd_id = cred_def_id(self.did, schema_seq_no, self.pool.protocol)
        try:
            rv = await anoncreds.issuer_create_credential_offer(
                self.wallet.handle, cd_id)
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.WalletNotFoundError:
                LOGGER.debug(
                    'Issuer.create_cred_offer: <!< did not issue cred definition from wallet %s',
                    self.wallet.name)
                raise CorruptWallet(
                    'Cannot create cred offer: did not issue cred definition from wallet {}'
                    .format(self.wallet.name))
            else:
                LOGGER.debug(
                    'Issuer.create_cred_offer: <!<  cannot create cred offer, indy error code %s',
                    x_indy.error_code)
                raise

        LOGGER.debug('Issuer.create_cred_offer <<< %s', rv)
        return rv
Exemple #2
0
    async def _sign_submit(self, req_json: str) -> str:
        """
        Sign and submit (json) request to ledger; return (json) result.

        Raise ClosedPool if pool is not yet open, CorruptWallet if existing wallet's
        pool is no longer extant, or BadLedgerTxn on any other failure.

        :param req_json: json of request to sign and submit
        :param wait: whether to wait for the transaction to appear on the ledger before proceeding
        :return: json response
        """

        LOGGER.debug('_BaseAnchor._sign_submit >>> json: %s', req_json)

        if not self.pool.handle:
            LOGGER.debug('_BaseAnchor._submit <!< closed pool %s',
                         self.pool.name)
            raise ClosedPool('Cannot submit request to closed pool {}'.format(
                self.pool.name))

        try:
            rv_json = await ledger.sign_and_submit_request(
                self.pool.handle, self.wallet.handle, self.did, req_json)
            await asyncio.sleep(0)
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.WalletIncompatiblePoolError:
                LOGGER.debug(
                    '_BaseAnchor._sign_submit: <!< Corrupt wallet %s is not compatible with pool %s',
                    self.wallet.name, self.pool.name)
                raise CorruptWallet(
                    'Corrupt wallet {} is not compatible with pool {}'.format(
                        self.wallet.name, self.pool.name))
            else:
                LOGGER.debug(
                    '_BaseAnchor._sign_submit: <!<  cannot sign/submit request for ledger: indy error code %s',
                    self.wallet.name)
                raise BadLedgerTxn(
                    'Cannot sign/submit request for ledger: indy error code {}'
                    .format(x_indy.error_code))

        resp = json.loads(rv_json)
        if ('op' in resp) and (resp['op'] in ('REQNACK', 'REJECT')):
            LOGGER.debug(
                '_BaseAnchor._sign_submit: ledger rejected request: %s',
                resp['reason'])
            raise BadLedgerTxn(
                'Ledger rejected transaction request: {}'.format(
                    resp['reason']))

        seq_no = resp.get('result', {}).get('seqNo', None)
        if 'reason' in resp and seq_no is None:
            LOGGER.debug(
                '_BaseAnchor._sign_submit: <!< response indicates no transaction: %s',
                resp['reason'])
            raise BadLedgerTxn('Response indicates no transaction: {}'.format(
                resp['reason']))

        LOGGER.debug('_BaseAnchor._sign_submit <<< %s', rv_json)
        return rv_json
Exemple #3
0
    async def _create_cred_def(self, schema: dict, ledger_cred_def: dict,
                               revo: bool) -> (str, bool):
        """
        Create credential definition in wallet as part of the send_cred_def() sequence.
        Return whether the private key for the cred def is OK to continue with the sequence,
        propagating the cred def and revocation registry info to the ledger.

        :param schema: schema on which to create cred def
        :param ledger_cred_def: credential definition as ledger has it (typically, None)
        :param revo: whether cred def supports revocation
        :return: cred def json and whether local cred def private key is OK, hence cred def is OK to send to the ledger
        """

        LOGGER.debug(
            'Issuer._create_cred_def >>> schema: %s, ledger_cred_def: %s, revo: %s',
            schema, ledger_cred_def, revo)

        cred_def_json = '{}'
        private_key_ok = True
        try:
            (_, cred_def_json
             ) = await anoncreds.issuer_create_and_store_credential_def(
                 self.wallet.handle,
                 self.did,  # issuer DID
                 json.dumps(schema),
                 self.pool.protocol.cd_id_tag(
                     False),  # expect only one cred def per schema and issuer
                 'CL',
                 json.dumps({'support_revocation': revo}))
            if ledger_cred_def:
                private_key_ok = False
                LOGGER.warning(
                    'New cred def on %s in wallet shadows existing one on ledger: private key not usable',
                    cred_def_id(self.did, schema['seqNo'], self.pool.protocol))
                # carry on though, this anchor may have other capacities so public key may be good enough
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.AnoncredsCredDefAlreadyExistsError:
                if ledger_cred_def:
                    LOGGER.info(
                        'Issuer wallet %s reusing existing cred def on schema %s version %s',
                        self.name, schema['name'], schema['version'])
                else:
                    LOGGER.debug(
                        'Issuer._create_cred_def <!< corrupt wallet %s',
                        self.name)
                    raise CorruptWallet(
                        'Corrupt Issuer wallet {} has cred def on schema {} not on ledger'
                        .format(self.name, schema['id']))
            else:
                LOGGER.debug(
                    'Issuer._create_cred_def <!< cannot store cred def in wallet %s: indy error code %s',
                    self.name, x_indy.error_code)
                raise

        rv = (cred_def_json, private_key_ok)
        LOGGER.debug('Issuer._create_cred_def <<< %s', rv)
        return rv
    async def _sign_submit(self, req_json: str) -> str:
        """
        Sign and submit (json) request to ledger; return (json) result.

        Raise:
        * AbsentPool for no pool
        * ClosedPool if pool is not yet open
        * CorruptWallet if existing wallet appears not to pertain to the anchor's pool
        * WalletState if wallet is closed
        * BadLedgerTxn on ledger rejection of transaction.

        :param req_json: json of request to sign and submit
        :return: json response
        """

        LOGGER.debug('BaseAnchor._sign_submit >>> req_json: %s', req_json)

        if not self.pool:
            LOGGER.debug('BaseAnchor._sign_submit <!< absent pool')
            raise AbsentPool('Cannot sign and submit request: absent pool')

        if not self.pool.handle:
            LOGGER.debug('BaseAnchor._submit <!< closed pool %s', self.pool.name)
            raise ClosedPool('Cannot sign and submit request to closed pool {}'.format(self.pool.name))

        if not self.wallet.handle:
            LOGGER.debug('BaseAnchor._sign_submit <!< Wallet %s is closed', self.name)
            raise WalletState('Wallet {} is closed'.format(self.name))

        try:
            rv_json = await ledger.sign_and_submit_request(self.pool.handle, self.wallet.handle, self.did, req_json)
            await asyncio.sleep(0)
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.WalletIncompatiblePoolError:
                LOGGER.debug(
                    'BaseAnchor._sign_submit <!< Corrupt wallet %s is not compatible with pool %s',
                    self.name,
                    self.pool.name)
                raise CorruptWallet('Corrupt wallet {} is not compatible with pool {}'.format(
                    self.name,
                    self.pool.name))
            LOGGER.debug(
                'BaseAnchor._sign_submit <!< cannot sign/submit request for ledger: indy error code %s',
                x_indy.error_code)
            raise BadLedgerTxn('Cannot sign/submit request for ledger: indy error code {}'.format(
                x_indy.error_code))

        resp = json.loads(rv_json)
        if resp.get('op', '') in ('REQNACK', 'REJECT'):
            LOGGER.debug('BaseAnchor._sign_submit: ledger rejected request: %s', resp['reason'])
            raise BadLedgerTxn('Ledger rejected transaction request: {}'.format(resp['reason']))

        LOGGER.debug('BaseAnchor._sign_submit <<< %s', rv_json)
        return rv_json
Exemple #5
0
    async def send_cred_def(self,
                            s_id: str,
                            revocation: bool = True,
                            rr_size: int = None) -> str:
        """
        Create a credential definition as Issuer, store it in its wallet, and send it to the ledger.

        Raise CorruptWallet for wallet not pertaining to current ledger, BadLedgerTxn on failure
        to send credential definition to ledger if need be, or IndyError for any other failure
        to create and store credential definition in wallet.

        :param s_id: schema identifier
        :param revocation: whether to support revocation for cred def
        :param rr_size: size of initial revocation registry (default as per _create_rev_reg()), if revocation supported
        :return: json credential definition as it appears on ledger
        """

        LOGGER.debug(
            'Issuer.send_cred_def >>> s_id: %s, revocation: %s, rr_size: %s',
            s_id, revocation, rr_size)

        if not ok_schema_id(s_id):
            LOGGER.debug('Issuer.send_cred_def <!< Bad schema id %s', s_id)
            raise BadIdentifier('Bad schema id {}'.format(s_id))

        rv_json = json.dumps({})
        schema_json = await self.get_schema(schema_key(s_id))
        schema = json.loads(schema_json)

        cd_id = cred_def_id(self.did, schema['seqNo'], self.pool.protocol)
        private_key_ok = True
        with CRED_DEF_CACHE.lock:
            try:
                rv_json = await self.get_cred_def(cd_id)
                LOGGER.info(
                    'Cred def on schema %s version %s already exists on ledger; Issuer %s not sending another',
                    schema['name'], schema['version'], self.wallet.name)
            except AbsentCredDef:
                pass  # OK - about to create, store, and send it

            try:
                (_, cred_def_json
                 ) = await anoncreds.issuer_create_and_store_credential_def(
                     self.wallet.handle,
                     self.did,  # issuer DID
                     schema_json,
                     self.pool.protocol.cd_id_tag(
                         False
                     ),  # expect only one cred def per schema and issuer
                     'CL',
                     json.dumps({'support_revocation': revocation}))
                if json.loads(rv_json):
                    private_key_ok = False
                    LOGGER.warning(
                        'New cred def on %s in wallet shadows existing one on ledger: private key not usable',
                        cd_id)
                    # carry on though, this anchor may have other roles so public key may be good enough
            except IndyError as x_indy:
                if x_indy.error_code == ErrorCode.AnoncredsCredDefAlreadyExistsError:
                    if json.loads(rv_json):
                        LOGGER.info(
                            'Issuer wallet %s reusing existing cred def on schema %s version %s',
                            self.wallet.name, schema['name'],
                            schema['version'])
                    else:
                        LOGGER.debug(
                            'Issuer.send_cred_def: <!< corrupt wallet %s',
                            self.wallet.name)
                        raise CorruptWallet(
                            'Corrupt Issuer wallet {} has cred def on schema {} version {} not on ledger'
                            .format(self.wallet.name, schema['name'],
                                    schema['version']))
                else:
                    LOGGER.debug(
                        'Issuer.send_cred_def: <!< cannot store cred def in wallet %s: indy error code %s',
                        self.wallet.name, x_indy.error_code)
                    raise

            if not json.loads(
                    rv_json
            ):  # checking the ledger returned no cred def: send it
                req_json = await ledger.build_cred_def_request(
                    self.did, cred_def_json)
                await self._sign_submit(req_json)

                for _ in range(16):  # reasonable timeout
                    try:
                        rv_json = await self.get_cred_def(cd_id
                                                          )  # adds to cache
                        break
                    except AbsentCredDef:
                        await sleep(1)
                        LOGGER.info(
                            'Sent cred def %s to ledger, waiting 1s for its appearance',
                            cd_id)

                if not rv_json:
                    LOGGER.debug(
                        'Issuer.send_cred_def <!< timed out waiting on sent cred_def %s',
                        cd_id)
                    raise BadLedgerTxn(
                        'Timed out waiting on sent cred_def {}'.format(cd_id))

                if revocation:
                    await self._sync_revoc(
                        rev_reg_id(cd_id, 0),
                        rr_size)  # create new rev reg, tails file for tag 0

        if revocation and private_key_ok:
            for tag in [
                    str(t) for t in range(
                        int(Tails.next_tag(self._dir_tails, cd_id)[0]))
            ]:  # '0' to str(next-1)
                await self._sync_revoc(rev_reg_id(cd_id, tag),
                                       rr_size if tag == 0 else None)

        makedirs(join(self._dir_tails, cd_id), exist_ok=True
                 )  # make sure dir exists for box id collection, revo or not

        LOGGER.debug('Issuer.send_cred_def <<< %s', rv_json)
        return rv_json
Exemple #6
0
    async def create(self) -> 'Wallet':
        """
        Create wallet as configured and store DID, or else re-use any existing configuration.
        Operation sequence create/store-DID/close does not auto-remove the wallet on close,
        even if so configured.

        :return: current object
        """

        LOGGER.debug('Wallet.create >>>')

        try:
            await wallet.create_wallet(
                config=json.dumps(self.cfg),
                credentials=json.dumps(self.access_creds))
            self._created = True
            LOGGER.info('Created wallet %s', self.name)
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.WalletAlreadyExistsError:
                LOGGER.info('Wallet already exists: %s', self.name)
            else:
                LOGGER.debug(
                    'Wallet.create <!< indy error code %s on creation of wallet %s',
                    x_indy.error_code,
                    self.name)
                raise

        LOGGER.debug('Attempting to open wallet %s', self.name)
        self._handle = await wallet.open_wallet(
            json.dumps(self.cfg),
            json.dumps(self.access_creds))
        LOGGER.info('Opened wallet %s on handle %s', self.name, self.handle)

        if self._created:
            (self._did, self.verkey) = await did.create_and_store_my_did(
                self.handle,
                json.dumps({'seed': self._seed}))
            LOGGER.debug('Wallet %s stored new DID %s, verkey %s from seed', self.name, self.did, self.verkey)
            await did.set_did_metadata(
                self.handle,
                self.did,
                json.dumps({
                    'seed_hash': sha256(self._seed.encode()).hexdigest()
                }))
            LOGGER.info('Wallet %s set seed hash metadata for DID %s', self.name, self.did)
        else:
            self._created = True
            LOGGER.debug('Attempting to derive seed to did for wallet %s', self.name)
            self._did = await self._seed2did()
            try:
                self.verkey = await did.key_for_local_did(self.handle, self.did)
            except IndyError:
                LOGGER.debug(
                    'Wallet.create <!< no verkey for DID %s on ledger, wallet %s may pertain to another',
                    self.did,
                    self.name)
                raise CorruptWallet(
                    'No verkey for DID {} on ledger, wallet {} may pertain to another'.format(
                        self.did,
                        self.name))
            LOGGER.info('Wallet %s got verkey %s for existing DID %s', self.name, self.verkey, self.did)

        await wallet.close_wallet(self.handle)

        LOGGER.debug('Wallet.create <<<')
        return self