Пример #1
0
    async def _set_rev_reg(self, rr_id: str, rr_size: int) -> None:
        """
        Move precomputed revocation registry data from hopper into place within tails directory.

        :param rr_id: revocation registry identifier
        :param rr_size: revocation registry size, in case creation required
        """

        LOGGER.debug('Issuer._set_rev_reg >>> rr_id: %s, rr_size: %s', rr_id, rr_size)
        assert self._rrbx

        dir_hopper_rr_id = join(self._dir_tails_hopper, rr_id)

        while Tails.linked(dir_hopper_rr_id, rr_id) is None:
            await asyncio.sleep(1)
        await self._send_rev_reg_def(rr_id)

        cd_id = rev_reg_id2cred_def_id(rr_id)
        (next_tag, rr_size_suggested) = Tails.next_tag(self._dir_tails, cd_id)
        rr_id = rev_reg_id(cd_id, next_tag)
        try:
            makedirs(join(self._dir_tails_sentinel, rr_id), exist_ok=False)
        except FileExistsError:
            LOGGER.warning('Rev reg %s construction already in progress', rr_id)
        else:
            open(join(self._dir_tails_sentinel, rr_id, '.{}'.format(rr_size or rr_size_suggested)), 'w').close()

        LOGGER.debug('Issuer._set_rev_reg <<<')
Пример #2
0
    def current_rev_reg_id(base_dir: str, cd_id: str) -> str:
        """
        Return the current revocation registry identifier for
        input credential definition identifier, in input directory.

        Raise AbsentTails if no corresponding tails file, signifying no such revocation registry defined.

        :param base_dir: base directory for tails files, thereafter split by cred def id
        :param cd_id: credential definition identifier of interest
        :return: identifier for current revocation registry on input credential definition identifier
        """

        LOGGER.debug('Tails.current_rev_reg_id >>> base_dir: %s, cd_id: %s',
                     base_dir, cd_id)

        if not ok_cred_def_id(cd_id):
            LOGGER.debug('Tails.current_rev_reg_id <!< Bad cred def id %s',
                         cd_id)
            raise BadIdentifier('Bad cred def id {}'.format(cd_id))

        tags = [
            int(rev_reg_id2tag(basename(f))) for f in Tails.links(base_dir)
            if cd_id in basename(f)
        ]
        if not tags:
            raise AbsentTails(
                'No tails files present for cred def id {}'.format(cd_id))

        rv = rev_reg_id(cd_id, str(max(tags)))  # ensure 10 > 9, not '9' > '10'
        LOGGER.debug('Tails.current_rev_reg_id <<< %s', rv)
        return rv
Пример #3
0
    def __init__(self, base_dir: str, cd_id: str, tag: str = None):
        """
        Initialize programmatic association between revocation registry identifier
        (on credential definition on input identifier plus tag, default most recent),
        and tails file, via symbolic link.

        Raise AbsentTails if (rev reg id) symbolic link or (tails hash) tails file not present.

        :param base_dir: base directory for tails files, thereafter split by cred def id
        :param cd_id: credential definition identifier of interest
        :param tag: revocation registry identifier tag of interest, default to most recent
        """

        LOGGER.debug('Issuer.__init__ >>> base_dir: %s, cd_id: %s, tag: %s',
                     base_dir, cd_id, tag)

        if not ok_cred_def_id(cd_id):
            LOGGER.debug('Tails.__init__ <!< Bad cred def id %s', cd_id)
            raise BadIdentifier('Bad cred def id {}'.format(cd_id))

        if tag is None:
            self._rr_id = Tails.current_rev_reg_id(base_dir, cd_id)
        else:  # including tag == 0
            self._rr_id = rev_reg_id(cd_id, tag)
            if self._rr_id not in [basename(f) for f in Tails.links(base_dir)]:
                LOGGER.debug(
                    'Tails.__init__ <!< No tails file present for cred def id %s on rev reg id tag %s',
                    cd_id, tag)
                raise AbsentTails(
                    'No tails file present for cred def id {} on rev reg id tag {}'
                    .format(cd_id, tag))

        path_link = join(Tails.dir(base_dir, self._rr_id), self._rr_id)
        if not islink(path_link):
            raise AbsentTails(
                'No symbolic link present at {} for rev reg id {}'.format(
                    path_link, self._rr_id))

        path_tails = Tails.linked(base_dir, self._rr_id)
        if not isfile(path_tails):
            raise AbsentTails(
                'No tails file present at {} for rev reg id {}'.format(
                    path_tails, self._rr_id))

        self._tails_config_json = json.dumps({
            'base_dir': dirname(path_tails),
            'uri_pattern': '',
            'file': basename(path_tails)
        })

        self._reader_handle = None

        LOGGER.debug('Tails.__init__ <<<')
Пример #4
0
    async def _set_rev_reg(self, rr_id: str, rr_size: int) -> None:
        """
        Move precomputed revocation registry data from hopper into place within tails directory.

        :param rr_id: revocation registry identifier
        :param rr_size: revocation registry size, in case creation required
        """

        LOGGER.debug('Issuer._set_rev_reg >>> rr_id: %s, rr_size: %s', rr_id,
                     rr_size)
        assert self.rrbx

        dir_hopper_rr_id = join(self.rrb.dir_tails_hopper, rr_id)

        while Tails.linked(dir_hopper_rr_id, rr_id) is None:
            await asyncio.sleep(1)
        await self._send_rev_reg_def(rr_id)

        cd_id = rev_reg_id2cred_def_id(rr_id)
        (next_tag, rr_size_suggested) = Tails.next_tag(self.dir_tails, cd_id)
        rr_id = rev_reg_id(cd_id, next_tag)
        self.rrb.mark_in_progress(rr_id, rr_size or rr_size_suggested)

        LOGGER.debug('Issuer._set_rev_reg <<<')
Пример #5
0
    async def create_cred(self,
                          cred_offer_json,
                          cred_req_json: str,
                          cred_attrs: dict,
                          rr_size: int = None) -> (str, str, int):
        """
        Create credential as Issuer out of credential request and dict of key:value (raw, unencoded)
        entries for attributes.

        Return credential json, and if cred def supports revocation, credential revocation identifier
        and revocation registry delta ledger timestamp (epoch seconds).

        If the credential definition supports revocation, and the current revocation registry is full,
        the processing creates a new revocation registry en passant. Depending on the revocation
        registry size (by default starting at 256 and doubling iteratively through 4096), this
        operation may delay credential creation by several seconds.

        :param cred_offer_json: credential offer json as created by Issuer
        :param cred_req_json: credential request json as created by HolderProver
        :param cred_attrs: dict mapping each attribute to its raw value (the operation encodes it); e.g.,

        ::

            {
                'favourite_drink': 'martini',
                'height': 180,
                'last_visit_date': '2017-12-31',
                'weaknesses': None
            }

        :param rr_size: size of new revocation registry (default as per _create_rev_reg()) if necessary
        :return: newly issued credential json; credential revocation identifier (if cred def supports
            revocation, None otherwise), and ledger timestamp (if cred def supports revocation, None otherwise)
        """

        LOGGER.debug(
            'Issuer.create_cred >>> cred_offer_json: %s, cred_req_json: %s, cred_attrs: %s, rr_size: %s',
            cred_offer_json, cred_req_json, cred_attrs, rr_size)

        cd_id = json.loads(cred_offer_json)['cred_def_id']
        if not ok_cred_def_id(cd_id):
            LOGGER.debug('Issuer.create_cred <!< Bad cred def id %s', cd_id)
            raise BadIdentifier('Bad cred def id {}'.format(cd_id))

        cred_def = json.loads(
            await self.get_cred_def(cd_id))  # ensure cred def is in cache

        if 'revocation' in cred_def['value']:
            with REVO_CACHE.lock:
                rr_id = Tails.current_rev_reg_id(self._dir_tails, cd_id)
                tails = REVO_CACHE[rr_id].tails
                assert tails  # at (re)start, at cred def, Issuer sync_revoc() sets this index in revocation cache

                try:
                    (cred_json, cred_revoc_id,
                     rr_delta_json) = await anoncreds.issuer_create_credential(
                         self.wallet.handle, cred_offer_json, cred_req_json,
                         json.dumps({
                             k: cred_attr_value(cred_attrs[k])
                             for k in cred_attrs
                         }), rr_id, tails.reader_handle)
                    # do not create rr delta frame and append to cached delta frames list: timestamp could lag or skew
                    rre_req_json = await ledger.build_revoc_reg_entry_request(
                        self.did, rr_id, 'CL_ACCUM', rr_delta_json)
                    await self._sign_submit(rre_req_json)
                    assert rr_id == tails.rr_id
                    resp_json = await self._sign_submit(rre_req_json)
                    resp = json.loads(resp_json)
                    rv = (cred_json, cred_revoc_id,
                          self.pool.protocol.txn2epoch(resp))

                except IndyError as x_indy:
                    if x_indy.error_code == ErrorCode.AnoncredsRevocationRegistryFullError:
                        (tag, rr_size_suggested) = Tails.next_tag(
                            self._dir_tails, cd_id)
                        rr_id = rev_reg_id(cd_id, tag)
                        await self._create_rev_reg(
                            rr_id, rr_size or rr_size_suggested)
                        REVO_CACHE[rr_id].tails = await Tails(
                            self._dir_tails, cd_id).open()
                        return await self.create_cred(cred_offer_json,
                                                      cred_req_json, cred_attrs
                                                      )  # should be ok now

                    LOGGER.debug(
                        'Issuer.create_cred: <!<  cannot create cred, indy error code %s',
                        x_indy.error_code)
                    raise
        else:
            try:
                (cred_json, _, _) = await anoncreds.issuer_create_credential(
                    self.wallet.handle, cred_offer_json, cred_req_json,
                    json.dumps({
                        k: cred_attr_value(cred_attrs[k])
                        for k in cred_attrs
                    }), None, None)
                rv = (cred_json, _, _)
            except IndyError as x_indy:
                LOGGER.debug(
                    'Issuer.create_cred: <!<  cannot create cred, indy error code %s',
                    x_indy.error_code)
                raise

        LOGGER.debug('Issuer.create_cred <<< %s', rv)
        return rv
Пример #6
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
Пример #7
0
    async def create_cred(self,
                          cred_offer_json,
                          cred_req_json: str,
                          cred_attrs: dict,
                          rr_size: int = None) -> (str, str):
        """
        Create credential as Issuer out of credential request and dict of key:value (raw, unencoded)
        entries for attributes.

        Return credential json, and if cred def supports revocation, credential revocation identifier.
        Raise WalletState for closed wallet.

        If the credential definition supports revocation, and the current revocation registry is full,
        the processing creates a new revocation registry en passant. Depending on the revocation
        registry size (by default starting at 64 and doubling iteratively through a maximum of 100000)
        and the revocation registry builder posture (see RevRegBuilder.__init__()), this operation may
        delay credential creation by several seconds. The use of an external revocation registry builder
        runs a parallel process, skirting this delay, but is more costly at initialization.

        :param cred_offer_json: credential offer json as created by Issuer
        :param cred_req_json: credential request json as created by HolderProver
        :param cred_attrs: dict mapping each attribute to its original value (the operation encodes it); e.g.,

        ::

            {
                'favourite_drink': 'martini',
                'height': 180,
                'last_visit_date': '2017-12-31',
                'weaknesses': None
            }

        :param rr_size: size of new revocation registry (default as per RevRegBuilder.create_rev_reg()) if necessary
        :return: tuple with newly issued credential json, credential revocation identifier (if cred def
            supports revocation, None otherwise).
        """

        LOGGER.debug(
            'Issuer.create_cred >>> cred_offer_json: %s, cred_req_json: %s, cred_attrs: %s, rr_size: %s',
            cred_offer_json, cred_req_json, cred_attrs, rr_size)

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

        cd_id = json.loads(cred_offer_json)['cred_def_id']
        if not ok_cred_def_id(cd_id):
            LOGGER.debug('Issuer.create_cred <!< Bad cred def id %s', cd_id)
            raise BadIdentifier('Bad cred def id {}'.format(cd_id))

        cred_def = json.loads(
            await self.get_cred_def(cd_id))  # ensure cred def is in cache

        if 'revocation' in cred_def['value']:
            with REVO_CACHE.lock:
                rr_id = Tails.current_rev_reg_id(self.dir_tails, cd_id)
                tails = REVO_CACHE[rr_id].tails
                assert tails  # at (re)start, at cred def, Issuer sync_revoc_for_issue() sets this index in revo cache

                try:
                    (
                        cred_json, cred_revoc_id, _
                    ) = await anoncreds.issuer_create_credential(  # issue by default to rr
                        self.wallet.handle, cred_offer_json, cred_req_json,
                        json.dumps({
                            k: cred_attr_value(cred_attrs[k])
                            for k in cred_attrs
                        }), rr_id, tails.reader_handle)
                    rv = (cred_json, cred_revoc_id)

                except IndyError as x_indy:
                    if x_indy.error_code == ErrorCode.AnoncredsRevocationRegistryFullError:
                        (tag, rr_size_suggested) = Tails.next_tag(
                            self.dir_tails, cd_id)
                        rr_id = rev_reg_id(cd_id, tag)
                        if self.rrbx:
                            await self._set_rev_reg(rr_id, rr_size)
                        else:
                            await self.rrb.create_rev_reg(
                                rr_id, rr_size or rr_size_suggested)
                            await self._send_rev_reg_def(rr_id)

                        REVO_CACHE[rr_id].tails = await Tails(
                            self.dir_tails, cd_id).open()  # symlink OK now
                        return await self.create_cred(cred_offer_json,
                                                      cred_req_json,
                                                      cred_attrs)

                    LOGGER.debug(
                        'Issuer.create_cred <!< cannot create cred, indy error code %s',
                        x_indy.error_code)
                    raise
        else:
            try:
                (cred_json, _, _) = await anoncreds.issuer_create_credential(
                    self.wallet.handle, cred_offer_json, cred_req_json,
                    json.dumps({
                        k: cred_attr_value(cred_attrs[k])
                        for k in cred_attrs
                    }), None, None)
                rv = (cred_json, None)
            except IndyError as x_indy:
                LOGGER.debug(
                    'Issuer.create_cred <!< cannot create cred, indy error code %s',
                    x_indy.error_code)
                raise

        LOGGER.debug('Issuer.create_cred <<< %s', rv)
        return rv
Пример #8
0
    async def send_cred_def(self,
                            s_id: str,
                            revo: 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, WalletState for closed wallet,
        or IndyError for any other failure to create and store credential definition in wallet.

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

        LOGGER.debug(
            'Issuer.send_cred_def >>> s_id: %s, revo: %s, rr_size: %s', s_id,
            revo, 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))

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

        if not self.pool:
            LOGGER.debug('Issuer.send_cred_def <!< issuer %s has no pool',
                         self.name)
            raise AbsentPool(
                'Issuer {} has no pool: cannot send cred def'.format(
                    self.name))

        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.name)
            except AbsentCredDef:
                pass  # OK - about to create, store, and send it

            (cred_def_json, private_key_ok) = await self._create_cred_def(
                schema, json.loads(rv_json), revo)

            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 asyncio.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 revo:  # create new rev reg for tag '0'
                    if self.rrbx:
                        (_, rr_size_suggested) = Tails.next_tag(
                            self.dir_tails, cd_id)
                        self.rrb.mark_in_progress(rev_reg_id(cd_id, '0'),
                                                  rr_size or rr_size_suggested)

                    await self._sync_revoc_for_issue(rev_reg_id(
                        cd_id, '0'), rr_size)  # sync rev reg on tag '0'

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

        makedirs(
            join(self.dir_tails, cd_id),
            exist_ok=True)  # dir required for box id collection, revo or not

        LOGGER.debug('Issuer.send_cred_def <<< %s', rv_json)
        return rv_json