예제 #1
0
    async def _sync_revoc(self, rr_id: str) -> None:
        """
        Pick up tails file reader handle for input revocation registry identifier.  If no symbolic
        link is present, get the revocation registry definition to retrieve its tails file hash,
        then find the tails file and link it.

        Raise AbsentTails for missing corresponding tails file.

        :param rr_id: revocation registry identifier
        """

        LOGGER.debug('HolderProver._sync_revoc >>> rr_id: %s', rr_id)

        (cd_id, tag) = rev_reg_id2cred_def_id__tag(rr_id)

        try:
            json.loads(await self.get_cred_def(cd_id))
        except AbsentCredDef:
            LOGGER.debug(
                'HolderProver._sync_revoc: <!< corrupt tails tree %s may be for another ledger',
                self._dir_tails)
            raise AbsentCredDef(
                'Corrupt tails tree {} may be for another ledger'.format(
                    self._dir_tails))
        except ClosedPool:
            pass  # carry on, may be OK from cache only

        with REVO_CACHE.lock:
            revo_cache_entry = REVO_CACHE.get(rr_id, None)
            tails = revo_cache_entry.tails if revo_cache_entry else None
            if tails is None:  #  it's not yet set in cache
                try:
                    tails = await Tails(self._dir_tails, cd_id, tag).open()
                except AbsentTails:  # get hash from ledger and check for tails file
                    rrdef = json.loads(await self._get_rev_reg_def(rr_id))
                    tails_hash = rrdef['value']['tailsHash']
                    path_tails = join(Tails.dir(self._dir_tails, rr_id),
                                      tails_hash)
                    if not isfile(path_tails):
                        LOGGER.debug(
                            'HolderProver._sync_revoc: <!< No tails file present at %s',
                            path_tails)
                        raise AbsentTails(
                            'No tails file present at {}'.format(path_tails))
                    Tails.associate(self._dir_tails, rr_id, tails_hash)
                    tails = await Tails(
                        self._dir_tails, cd_id,
                        tag).open()  # OK now since tails file present

                if revo_cache_entry is None:
                    REVO_CACHE[rr_id] = RevoCacheEntry(None, tails)
                else:
                    REVO_CACHE[rr_id].tails = tails

        LOGGER.debug('HolderProver._sync_revoc <<<')
예제 #2
0
파일: base.py 프로젝트: swcurran/von_agent
    async def _get_rev_reg_def(self, rr_id: str) -> str:
        """
        Get revocation registry definition from ledger by its identifier. Raise AbsentRevReg
        for no such revocation registry, logging any error condition and raising BadLedgerTxn
        on bad request.

        Retrieve the revocation registry definition from the agent's revocation cache if it has it;
        cache it en passant if it does not (and such a revocation registry definition exists on the ledger).

        :param rr_id: (revocation registry) identifier string, of the format
            '<issuer-did>:4:<issuer-did>:3:CL:<schema-seq-no>:<tag>:CL_ACCUM:<tag>'
        :return: revocation registry definition json as retrieved from ledger
        """

        LOGGER.debug('_BaseAgent._get_rev_reg_def >>> rr_id: %s', rr_id)

        rv_json = json.dumps({})

        with REVO_CACHE.lock:
            revo_cache_entry = REVO_CACHE.get(rr_id, None)
            rr_def = revo_cache_entry.rev_reg_def if revo_cache_entry else None
            if rr_def:
                LOGGER.info(
                    '_BaseAgent._get_rev_reg_def: rev reg def for %s from cache',
                    rr_id)
                rv_json = json.dumps(rr_def)
            else:
                get_rrd_req_json = await ledger.build_get_revoc_reg_def_request(
                    self.did, rr_id)
                resp_json = await self._submit(get_rrd_req_json)
                try:
                    (_,
                     rv_json) = await ledger.parse_get_revoc_reg_def_response(
                         resp_json)
                    rr_def = json.loads(rv_json)
                except IndyError:  # ledger replied, but there is no such rev reg
                    LOGGER.debug(
                        '_BaseAgent._get_rev_reg_def: <!< no rev reg exists on %s',
                        rr_id)
                    raise AbsentRevReg('No rev reg exists on {}'.format(rr_id))

                if revo_cache_entry is None:
                    REVO_CACHE[rr_id] = RevoCacheEntry(rr_def, None)
                else:
                    REVO_CACHE[rr_id].rev_reg_def = rr_def

        LOGGER.debug('_BaseAgent._get_rev_reg_def <<< %s', rv_json)
        return rv_json
예제 #3
0
    async def _sync_revoc(self, rr_id: str, rr_size: int = None) -> None:
        """
        Create revoc registry if need be for input revocation registry identifier;
        open and cache tails file reader.

        :param rr_id: revocation registry identifier
        :param rr_size: if new revocation registry necessary, its size (default as per _create_rev_reg())
        """

        LOGGER.debug('Issuer._sync_revoc >>> rr_id: %s, rr_size: %s', rr_id,
                     rr_size)

        (cd_id, tag) = rev_reg_id2cred_def_id__tag(rr_id)

        try:
            await self.get_cred_def(cd_id)
        except AbsentCredDef:
            LOGGER.debug(
                'Issuer._sync_revoc: <!< tails tree %s may be for another ledger; no cred def found on %s',
                self._dir_tails, cd_id)
            raise AbsentCredDef(
                'Tails tree {} may be for another ledger; no cred def found on {}'
                .format(self._dir_tails, cd_id))

        with REVO_CACHE.lock:
            revo_cache_entry = REVO_CACHE.get(rr_id, None)
            tails = None if revo_cache_entry is None else revo_cache_entry.tails
            if tails is None:  #  it's a new revocation registry, or not yet set in cache
                try:
                    tails = await Tails(self._dir_tails, cd_id, tag).open()
                except AbsentTails:
                    await self._create_rev_reg(
                        rr_id, rr_size)  # it's a new revocation registry
                    tails = await Tails(self._dir_tails, cd_id,
                                        tag).open()  # symlink should exist now

                if revo_cache_entry is None:
                    REVO_CACHE[rr_id] = RevoCacheEntry(None, tails)
                else:
                    REVO_CACHE[rr_id].tails = tails

        LOGGER.debug('Issuer._sync_revoc <<<')
예제 #4
0
    async def load_cache(self, archive: bool = False) -> int:
        """
        Load caches and archive enough to go offline and be able to verify proof
        on content marked of interest in configuration.

        Return timestamp (epoch seconds) of cache load event, also used as subdirectory
        for cache archives.

        :param archive: whether to archive caches to disk
        :return: cache load event timestamp (epoch seconds)
        """

        LOGGER.debug('Verifier.load_cache >>> archive: %s', archive)

        rv = int(time())
        for s_id in self.cfg.get('archive-on-close', {}).get('schema_id', {}):
            with SCHEMA_CACHE.lock:
                await self.get_schema(s_id)
        for cd_id in self.cfg.get('archive-on-close',
                                  {}).get('cred_def_id', {}):
            with CRED_DEF_CACHE.lock:
                await self.get_cred_def(cd_id)
        for rr_id in self.cfg.get('archive-on-close',
                                  {}).get('rev_reg_id', {}):
            await self._get_rev_reg_def(rr_id)
            with REVO_CACHE.lock:
                revo_cache_entry = REVO_CACHE.get(rr_id, None)
                if revo_cache_entry:
                    try:
                        await revo_cache_entry.get_state_json(
                            self._build_rr_state_json, rv, rv)
                    except ClosedPool:
                        LOGGER.warning(
                            'Verifier %s is offline from pool %s, cannot update revo cache reg state for %s to %s',
                            self.wallet.name, self.pool.name, rr_id, rv)

        if archive:
            Caches.archive(self.dir_cache)
        LOGGER.debug('Verifier.load_cache <<< %s', rv)
        return rv
예제 #5
0
    async def load_cache(self, archive: bool = False) -> int:
        """
        Load caches and archive enough to go offline and be able to generate proof
        on all credentials in wallet.

        Return timestamp (epoch seconds) of cache load event, also used as subdirectory
        for cache archives.

        :return: cache load event timestamp (epoch seconds)
        """

        LOGGER.debug('HolderProver.load_cache >>> archive: %s', archive)

        rv = int(time())
        box_ids = json.loads(await self.get_box_ids_json())
        for s_id in box_ids['schema_id']:
            with SCHEMA_CACHE.lock:
                await self.get_schema(s_id)
        for cd_id in box_ids['cred_def_id']:
            with CRED_DEF_CACHE.lock:
                await self.get_cred_def(cd_id)
        for rr_id in box_ids['rev_reg_id']:
            await self._get_rev_reg_def(rr_id)
            with REVO_CACHE.lock:
                revo_cache_entry = REVO_CACHE.get(rr_id, None)
                if revo_cache_entry:
                    try:
                        await revo_cache_entry.get_delta_json(
                            self._build_rr_delta_json, rv, rv)
                    except ClosedPool:
                        LOGGER.warning(
                            'Holder-Prover %s is offline from pool %s, cannot update revo cache reg delta for %s to %s',
                            self.wallet.name, self.pool.name, rr_id, rv)

        if archive:
            Caches.archive(self.dir_cache)
        LOGGER.debug('HolderProver.load_cache <<< %s', rv)
        return rv
예제 #6
0
    async def verify_proof(self, proof_req: dict, proof: dict) -> str:
        """
        Verify proof as Verifier. Raise AbsentRevReg if a proof cites a revocation registry
        that does not exist on the distributed ledger.

        :param proof_req: proof request as Verifier creates, as per proof_req_json above
        :param proof: proof as HolderProver creates
        :return: json encoded True if proof is valid; False if not
        """

        LOGGER.debug('Verifier.verify_proof >>> proof_req: %s, proof: %s',
                     proof_req, proof)

        s_id2schema = {}
        cd_id2cred_def = {}
        rr_id2rr_def = {}
        rr_id2rr = {}
        proof_ids = proof['identifiers']

        for proof_id in proof_ids:
            # schema
            s_id = proof_id['schema_id']
            if s_id not in s_id2schema:
                schema = json.loads(
                    await self.get_schema(s_id))  # add to cache en passant
                if not schema:
                    LOGGER.debug(
                        'Verifier.verify_proof: <!< absent schema %s, proof req may be for another ledger',
                        s_id)
                    raise AbsentSchema(
                        'Absent schema {}, proof req may be for another ledger'
                        .format(s_id))
                s_id2schema[s_id] = schema

            # cred def
            cd_id = proof_id['cred_def_id']
            if cd_id not in cd_id2cred_def:
                cred_def = json.loads(
                    await self.get_cred_def(cd_id))  # add to cache en passant
                cd_id2cred_def[cd_id] = cred_def

            # rev reg def
            rr_id = proof_id['rev_reg_id']
            if not rr_id:
                continue

            rr_def_json = await self._get_rev_reg_def(rr_id)
            rr_id2rr_def[rr_id] = json.loads(rr_def_json)

            # timestamp
            timestamp = proof_id['timestamp']
            with REVO_CACHE.lock:
                revo_cache_entry = REVO_CACHE.get(rr_id, None)
                (rr_json, _) = await revo_cache_entry.get_state_json(
                    self._build_rr_state_json, timestamp, timestamp)
                if rr_id not in rr_id2rr:
                    rr_id2rr[rr_id] = {}
                rr_id2rr[rr_id][timestamp] = json.loads(rr_json)

        rv = json.dumps(await anoncreds.verifier_verify_proof(
            json.dumps(proof_req), json.dumps(proof), json.dumps(s_id2schema),
            json.dumps(cd_id2cred_def), json.dumps(rr_id2rr_def),
            json.dumps(rr_id2rr)))

        LOGGER.debug('Verifier.verify_proof <<< %s', rv)
        return rv
예제 #7
0
    async def build_proof_req_json(self,
                                   cd_id2spec: dict,
                                   cache_only: bool = False) -> str:
        """
        Build and return indy-sdk proof request for input attributes and timestamps by cred def id.

        Raise AbsentInterval if caller specifies cache_only and default non-revocation intervals, but
        revocation cache does not have delta frames for any revocation registries on a specified cred def.

        :param cd_id2spec: dict mapping cred def ids to:
            - (optionally) 'attrs': lists of names of attributes of interest (omit for all, empty list or None for none)
            - (optionally) 'minima': (pred) integer lower-bounds of interest (omit, empty list, or None for none)
            - (optionally), 'interval': (2-tuple) pair of epoch second counts marking 'from' and 'to' timestamps,
                or single epoch second count to set 'from' and 'to' the same: default (now, now) if cache_only
                is clear, or latest values from cache if cache_only is set.
            e.g.,

        ::

            {
                'Vx4E82R17q...:3:CL:16:0': {
                    'attrs': [  # request attrs 'name' and 'favouriteDrink' from this cred def's schema
                        'name',
                        'favouriteDrink'
                    ],
                    'minima': {  # request predicate score>=80 from this cred def
                        'score': 80
                    }
                    'interval': 1528116008  # same instant for all attrs and preds of corresponding schema
                },
                'R17v42T4pk...:3:CL:19:0': None,  # request all attrs, no preds, default intervals on all attrs
                'e3vc5K168n...:3:CL:23:0': {},  # request all attrs, no preds, default intervals on all attrs
                'Z9ccax812j...:3:CL:27:0': {  # request all attrs, no preds, this interval on all attrs
                    'interval': (1528112408, 1528116008)
                },
                '9cHbp54C8n...:3:CL:37:0': {  # request no attrs, one pred, specify interval on pred
                    'attrs': [],  # or equivalently, 'attrs': None
                    'minima': {
                        'employees': '50'  # nicety: implementation converts to int for caller
                    },
                    'interval': (1528029608, 1528116008)
                },
                '6caBcmLi33...:3:CL:41:0': {  # all attrs, one pred, default intervals to now on attrs & pred
                    'minima': {
                        'regEpoch': 1514782800
                    }
                }
                ...
            }

        :param cache_only: (True) take default intervals (per cred def id) from latest cached deltas, or
            (default False) use current time
        :return: indy-sdk proof request json
        """

        LOGGER.debug(
            'HolderProver.build_proof_req_json >>> cd_id2spec: %s, cache_only: %s',
            cd_id2spec, cache_only)

        cd_id2schema = {}
        now = int(time())
        proof_req = {
            'nonce': str(int(time())),
            'name': 'proof_req',
            'version': '0.0',
            'requested_attributes': {},
            'requested_predicates': {}
        }

        for cd_id in cd_id2spec:
            interval = None
            cred_def = json.loads(await self.get_cred_def(cd_id))
            seq_no = cred_def_id2seq_no(cd_id)
            cd_id2schema[cd_id] = json.loads(await self.get_schema(seq_no))

            if 'revocation' in cred_def['value']:
                if cache_only and not (cd_id2spec.get(cd_id, {}) or {}).get(
                        'interval', None):
                    with REVO_CACHE.lock:
                        (fro, to) = REVO_CACHE.dflt_interval(cd_id)
                        if not (fro and to):
                            LOGGER.debug(
                                'HolderProver.build_proof_req_json: <!< no cached delta for non-revoc interval on %s',
                                cd_id)
                            raise AbsentInterval(
                                'No cached delta for non-revoc interval on {}'.
                                format(cd_id))
                        interval = {'from': fro, 'to': to}
                else:
                    fro_to = cd_id2spec[cd_id].get(
                        'interval',
                        (now, now)) if cd_id2spec[cd_id] else (now, now)
                    interval = {
                        'from':
                        fro_to if isinstance(fro_to, int) else min(fro_to),
                        'to':
                        fro_to if isinstance(fro_to, int) else max(fro_to)
                    }

            for attr in (cd_id2spec[cd_id].get(
                    'attrs', cd_id2schema[cd_id]['attrNames']) or []
                         if cd_id2spec[cd_id] else
                         cd_id2schema[cd_id]['attrNames']):
                attr_uuid = '{}_{}_uuid'.format(seq_no, attr)
                proof_req['requested_attributes'][attr_uuid] = {
                    'name': attr,
                    'restrictions': [{
                        'cred_def_id': cd_id
                    }]
                }
                if interval:
                    proof_req['requested_attributes'][attr_uuid][
                        'non_revoked'] = interval

            for attr in (cd_id2spec[cd_id].get('minima', {}) or {}
                         if cd_id2spec[cd_id] else {}):
                pred_uuid = '{}_{}_uuid'.format(seq_no, attr)
                try:
                    proof_req['requested_predicates'][pred_uuid] = {
                        'name': attr,
                        'p_type': '>=',
                        'p_value': int(cd_id2spec[cd_id]['minima'][attr]),
                        'restrictions': [{
                            'cred_def_id': cd_id
                        }]
                    }
                except ValueError:
                    LOGGER.info(
                        'cannot build predicate on non-int minimum %s for %s',
                        cd_id2spec[cd_id]['minima'][attr], attr)
                    continue  # int conversion failed - reject candidate
                if interval:
                    proof_req['requested_predicates'][pred_uuid][
                        'non_revoked'] = interval

        rv_json = json.dumps(proof_req)
        LOGGER.debug('HolderProver.build_proof_req_json <<< %s', rv_json)
        return rv_json
예제 #8
0
    async def create_proof(self, proof_req: dict, creds: dict,
                           requested_creds: dict) -> str:
        """
        Create proof as HolderProver.

        Raise:
            * AbsentLinkSecret if link secret not set
            * CredentialFocus on attempt to create proof on no creds or multiple creds for a credential definition
            * AbsentTails if missing required tails file
            * BadRevStateTime if a timestamp for a revocation registry state in the proof request
              occurs before revocation registry creation
            * IndyError for any other indy-sdk error.
            * AbsentInterval if creds missing non-revocation interval, but cred def supports revocation

        :param proof_req: proof request as per get_creds() above
        :param creds: credentials to prove
        :param requested_creds: data structure with self-attested attribute info, requested attribute info
            and requested predicate info, assembled from get_creds() and filtered for content of interest. I.e.,

        ::

            {
                'self_attested_attributes': {},
                'requested_attributes': {
                    'attr0_uuid': {
                        'cred_id': string,
                        'timestamp': integer,  # for revocation state
                        'revealed': bool
                    },
                    ...
                },
                'requested_predicates': {
                    'predicate0_uuid': {
                        'cred_id': string,
                        'timestamp': integer  # for revocation state
                    }
                }
            }

        :return: proof json
        """

        LOGGER.debug(
            'HolderProver.create_proof >>> proof_req: %s, creds: %s, requested_creds: %s',
            proof_req, creds, requested_creds)

        self._assert_link_secret('create_proof')

        x_uuids = [
            attr_uuid for attr_uuid in creds['attrs']
            if len(creds['attrs'][attr_uuid]) != 1
        ]
        if x_uuids:
            LOGGER.debug(
                'HolderProver.create_proof: <!< creds specification out of focus (non-uniqueness)'
            )
            raise CredentialFocus(
                'Proof request requires unique cred per attribute; violators: {}'
                .format(x_uuids))

        s_id2schema = {}  # schema identifier to schema
        cd_id2cred_def = {
        }  # credential definition identifier to credential definition
        rr_id2timestamp = {
        }  # revocation registry of interest to timestamp of interest (or None)
        rr_id2cr_id = {
        }  # revocation registry of interest to credential revocation identifier
        for referents in {**creds['attrs'], **creds['predicates']}.values():
            interval = referents[0].get('interval', None)
            cred_info = referents[0]['cred_info']
            s_id = cred_info['schema_id']
            if s_id not in s_id2schema:
                schema = json.loads(
                    await self.get_schema(s_id))  # add to cache en passant
                if not schema:
                    LOGGER.debug(
                        'HolderProver.create_proof: <!< absent schema %s, proof req may be for another ledger',
                        s_id)
                    raise AbsentSchema(
                        'Absent schema {}, proof req may be for another ledger'
                        .format(s_id))
                s_id2schema[s_id] = schema

            cd_id = cred_info['cred_def_id']
            if cd_id not in cd_id2cred_def:
                cred_def = json.loads(
                    await self.get_cred_def(cd_id))  # add to cache en passant
                cd_id2cred_def[cd_id] = cred_def

            rr_id = cred_info['rev_reg_id']
            if rr_id:
                await self._sync_revoc(
                    rr_id)  # link tails file to its rr_id if it's new
                if interval:
                    if rr_id not in rr_id2timestamp:
                        if interval['to'] > int(time()):
                            LOGGER.debug(
                                'HolderProver.create_proof: <!< interval to %s for rev reg %s is in the future',
                                interval['to'], rr_id)
                            raise BadRevStateTime(
                                'Revocation registry {} timestamp {} is in the future'
                                .format(rr_id, interval['to']))
                        rr_id2timestamp[rr_id] = interval['to']
                elif 'revocation' in cd_id2cred_def[cd_id]['value']:
                    LOGGER.debug(
                        'HolderProver.create_proof: <!< creds on cred def id %s missing non-revocation interval',
                        cd_id)
                    raise AbsentInterval(
                        'Creds on cred def id {} missing non-revocation interval'
                        .format(cd_id))
                if rr_id in rr_id2cr_id:
                    continue
                rr_id2cr_id[rr_id] = cred_info['cred_rev_id']

        rr_id2rev_state = {}  # revocation registry identifier to its state
        with REVO_CACHE.lock:
            for rr_id in rr_id2timestamp:
                revo_cache_entry = REVO_CACHE.get(rr_id, None)
                tails = revo_cache_entry.tails if revo_cache_entry else None
                if tails is None:  # missing tails file
                    LOGGER.debug(
                        'HolderProver.create_proof: <!< missing tails file for rev reg id %s',
                        rr_id)
                    raise AbsentTails(
                        'Missing tails file for rev reg id {}'.format(rr_id))
                rr_def_json = await self._get_rev_reg_def(rr_id)
                (rr_delta_json,
                 ledger_timestamp) = await revo_cache_entry.get_delta_json(
                     self._build_rr_delta_json, rr_id2timestamp[rr_id],
                     rr_id2timestamp[rr_id])
                rr_state_json = await anoncreds.create_revocation_state(
                    tails.reader_handle, rr_def_json, rr_delta_json,
                    ledger_timestamp, rr_id2cr_id[rr_id])
                rr_id2rev_state[rr_id] = {
                    rr_id2timestamp[rr_id]: json.loads(rr_state_json)
                }

        rv = await anoncreds.prover_create_proof(self.wallet.handle,
                                                 json.dumps(proof_req),
                                                 json.dumps(requested_creds),
                                                 self._link_secret,
                                                 json.dumps(s_id2schema),
                                                 json.dumps(cd_id2cred_def),
                                                 json.dumps(rr_id2rev_state))
        LOGGER.debug('HolderProver.create_proof <<< %s', rv)
        return rv