Beispiel #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 <<<')
Beispiel #2
0
async def list_tails(request: Request, ident: str) -> HTTPResponse:
    """
    List tails files by corresponding rev reg ids: all, by rev reg id, by cred def id, or by issuer DID.

    :param request: Sanic request structure
    :param ident: 'all' for no filter; rev reg id, cred def id, or issuer DID to filter by any such identifier
    :return: HTTP response with JSON array of rev reg ids corresponding to available tails files
    """

    rv = []
    dir_tails = join(dirname(dirname(realpath(__file__))), 'tails')

    if ident == 'all':  # list everything: 'all' is not valid base58 so it can't be any case below
        rv = [basename(link) for link in Tails.links(dir_tails)]
    elif ok_rev_reg_id(ident):  # it's a rev reg id
        if Tails.linked(dir_tails, ident):
            rv = [ident]
    elif ok_cred_def_id(ident):  # it's a cred def id (starts with issuer DID)
        rv = [basename(link) for link in Tails.links(dir_tails, ident.split(':')[0])
            if rev_reg_id2cred_def_id(basename(link)) == ident]
    elif ok_did(ident):  # it's an issuer DID
        rv = [basename(link) for link in Tails.links(dir_tails, ident)]
    else:
        LOGGER.error('Token %s is not a valid specifier for tails files', ident)
        return response.text('Token {} is not a valid specifier for tails files'.format(ident), status=400)

    LOGGER.info('Fulfilling GET request listing tails files on filter %s', ident)
    return response.json(rv)
Beispiel #3
0
    def associate(base_dir: str, rr_id: str, tails_hash: str) -> None:
        """
        Create symbolic link to tails file named tails_hash for rev reg id rr_id.

        :param rr_id: rev reg id
        :param tails_hash: hash of tails file, serving as file name
        """

        LOGGER.debug(
            'Tails.associate >>> base_dir: %s, rr_id: %s, tails_hash: %s',
            base_dir, rr_id, tails_hash)

        if not ok_rev_reg_id(rr_id):
            LOGGER.debug('Tails.associate <!< Bad rev reg id %s', rr_id)
            raise BadIdentifier('Bad rev reg id {}'.format(rr_id))

        if not Tails.ok_hash(tails_hash):
            LOGGER.debug('Tails.associate <!< Bad tails hash %s', tails_hash)
            raise BadIdentifier('Bad tails hash {}'.format(tails_hash))

        cd_id = rev_reg_id2cred_def_id(rr_id)
        directory = join(base_dir, cd_id)
        cwd = getcwd()
        makedirs(directory, exist_ok=True)
        chdir(directory)
        symlink(tails_hash, rr_id)
        chdir(cwd)

        LOGGER.debug('Tails.associate <<<')
Beispiel #4
0
async def list_tails(request, ident):
    """
    List tails files by corresponding rev reg ids: all, by rev reg id, by cred def id, or by issuer DID.

    :param request: Sanic request structure
    :param ident: 'all' for no filter; rev reg id, cred def id, or issuer DID to filter by any such identifier
    :return: JSON array of rev reg ids corresponding to tails files available
    """

    rv = []
    dir_tails = pjoin(dirname(dirname(abspath(__file__))), 'tails')

    if ident == 'all':  # list everything: 'all' is not valid base58 so it can't be any case below
        rv = [basename(link) for link in Tails.links(dir_tails)]
    elif ok_rev_reg_id(ident) and Tails.linked(dir_tails, ident):  # it's a rev reg id
        rv = [ident]
    elif ok_cred_def_id(ident):  # it's a cred def id (starts with issuer DID)
        rv = [basename(link) for link in Tails.links(dir_tails, ident.split(':')[0])
            if rev_reg_id2cred_def_id(basename(link)) == ident]
    elif ok_did(ident):  # it's an issuer DID
        rv = [basename(link) for link in Tails.links(dir_tails, ident)]
    else:
        LOGGER.error("Token %s must be 'all', rev reg id, cred def id, or issuer DID", ident)
        raise InvalidUsage("Token {} must be 'all', rev reg id, cred def id, or issuer DID".format(ident))

    LOGGER.info('Fulfilling GET request listing tails files on filter %s', ident)
    return response.json(rv)
Beispiel #5
0
    def dir_tails_target(self, rr_id) -> str:
        """
        Return target directory for revocation registry and tails file production.

        :param rr_id: revocation registry identifier
        :return: tails target directory
        """

        return join(self.dir_tails_top(rr_id), rev_reg_id2cred_def_id(rr_id))
Beispiel #6
0
    def dflt_interval(self, cd_id: str) -> (int, int):
        """
        Return default non-revocation interval from latest 'to' times on delta frames
        of revocation cache entries on indices stemming from input cred def id.

        Compute the 'from'/'to' values as the earliest/latest 'to' values of all
        cached delta frames on all rev reg ids stemming from the input cred def id.

        E.g., on frames for
            rev-reg-0: -[xx]---[xxxx]-[x]---[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]--> time
            rev-reg-1: ----------------------[xxxx]----[xxx]---[xxxxxxxxxxxxxxxxxxxx]---------> time
            rev-reg-2: -------------------------------------------[xx]-----[xxxx]-----[xxxxx]-> time
            rev-reg-3: -----------------------------------------------------------[xxxxxxxx]--> time

        return the most recent interval covering all matching revocation registries in the cache; i.e.,:
            interval:  -------------------------------------------------------------[*******]-> time

        Raise CacheIndex if there are no matching entries.

        :param cd_id: cred def identifier to match
        :return: default non-revocation interval as 2-tuple (fro, to)
        """

        LOGGER.debug('RevocationCache.dflt_interval >>>')

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

        fro = None
        to = None

        for rr_id in self:
            if cd_id != rev_reg_id2cred_def_id(rr_id):
                continue
            entry = self[rr_id]
            if entry.rr_delta_frames:
                to = max(entry.rr_delta_frames, key=lambda f: f.to).to
                fro = min(fro or to, to)

        if not (fro and to):
            LOGGER.debug(
                'RevocationCache.dflt_interval <!< No data for default non-revoc interval on cred def id %s',
                cd_id)
            raise CacheIndex(
                'No data for default non-revoc interval on cred def id {}'.
                format(cd_id))

        rv = (fro, to)
        LOGGER.debug('RevocationCache.dflt_interval <<< %s', rv)
        return rv
Beispiel #7
0
    def dir(base_dir: str, rr_id: str) -> str:
        """
        Return correct subdirectory of input base dir for artifacts corresponding to input rev reg id.

        :param base_dir: base directory for tails files, thereafter split by cred def id
        :param rr_id: rev reg id
        """

        LOGGER.debug('Tails.dir >>> base_dir: %s, rr_id: %s', base_dir, rr_id)

        if not ok_rev_reg_id(rr_id):
            LOGGER.debug('Tails.dir <!< Bad rev reg id %s', rr_id)
            raise BadIdentifier('Bad rev reg id {}'.format(rr_id))

        rv = join(base_dir, rev_reg_id2cred_def_id(rr_id))
        LOGGER.debug('Tails.dir <<< %s', rv)
        return rv
Beispiel #8
0
    def linked(base_dir: str, rr_id: str) -> str:
        """
        Get, from the specified directory, the path to the tails file associated with
        the input revocation registry identifier, or None for no such file.

        :param base_dir: base directory for tails files, thereafter split by cred def id
        :param rr_id: rev reg id
        :return: (stringified) path to tails file of interest, or None for no such file.
        """

        LOGGER.debug('Tails.linked >>> base_dir: %s, rr_id: %s', base_dir, rr_id)

        if not ok_rev_reg_id(rr_id):
            LOGGER.debug('Tails.linked <!< Bad rev reg id %s', rr_id)
            raise BadIdentifier('Bad rev reg id {}'.format(rr_id))

        cd_id = rev_reg_id2cred_def_id(rr_id)
        link = join(base_dir, cd_id, rr_id)

        rv = join(base_dir, cd_id, readlink(link)) if islink(link) else None
        LOGGER.debug('Tails.linked <<< %s', rv)
        return rv
Beispiel #9
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 <<<')
Beispiel #10
0
async def test_ids():
    print(Ink.YELLOW('\n\n== Testing Identifier Checks =='))

    assert ok_wallet_reft('49ad0727-8663-45ae-a115-12b09860f9c6')
    assert not ok_wallet_reft('Q4zqM7aXqm7gDQkUVLng9I')
    assert not ok_wallet_reft('49ad0727-45ae-a115-12b09860f9c6')
    print('\n\n== 1 == Wallet referent identifier checks pass OK')

    assert ok_did('Q4zqM7aXqm7gDQkUVLng9h')
    assert not ok_did('Q4zqM7aXqm7gDQkUVLng9I')  # 'I' not a base58 char
    assert not ok_did('Q4zqM7aXqm7gDQkUVLng')  # too short
    print('\n\n== 2 == Distributed identifier checks pass OK')

    for value in (None, 'TRUSTEE', 'STEWARD', 'TRUST_ANCHOR', ''):
        assert ok_role(value)
    for value in (123, 'TRUSTY', 'STEW', 'ANCHOR', ' '):
        assert not ok_role(value)
    print('\n\n== 3 == Role identifier checks pass OK')

    assert Tails.ok_hash('Q4zqM7aXqm7gDQkUVLng9hQ4zqM7aXqm7gDQkUVLng9h')
    assert Tails.ok_hash('Q4zqM7aXqm7gDQkUVLng9hQ4zqM7aXqm7gDQkUVLng')
    assert not Tails.ok_hash('Q4zqM7aXqm7gDQkUVLng9h')
    assert not Tails.ok_hash('Q4zqM7aXqm7gDQkUVLng9hQ4zqM7aXqm7gDQkUVLng9hx')
    assert not Tails.ok_hash('Q4zqM7aXqm7gDQkUVLng9hQ4zqM7aXqm7gDQkUVLng90')
    print('\n\n== 4 == Tails hash identifier checks pass OK')

    assert ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:2:bc-reg:1.0')
    assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:3:bc-reg:1.0')
    assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h::bc-reg:1.0')
    assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:bc-reg:1.0')
    assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:2:1.0')
    assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:2::1.0')
    assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:2:bc-reg:')
    assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:2:bc-reg:1.0a')
    assert not ok_schema_id(
        'Q4zqM7aXqm7gDQkUVLng9I:2:bc-reg:1.0')  # I is not in base58
    print('\n\n== 5 == Schema identifier checks pass OK')

    assert ok_cred_def_id(
        'Q4zqM7aXqm7gDQkUVLng9h:3:CL:18:tag')  # protocol >= 1.4
    assert ok_cred_def_id(
        'Q4zqM7aXqm7gDQkUVLng9h:3:CL:Q4zqM7aXqm7gDQkUVLng9h:2:schema_name:1.0:tag'
    )  # long form
    assert ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:CL:18:tag',
                          'Q4zqM7aXqm7gDQkUVLng9h')  # issuer-did
    assert ok_cred_def_id(
        'Q4zqM7aXqm7gDQkUVLng9h:3:CL:Q999999999999999999999:2:schema_name:1.0:tag',
        'Q4zqM7aXqm7gDQkUVLng9h')  # long form, issuer-did
    assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:CL:18:tag',
                              'Xxxxxxxxxxxxxxxxxxxxxx')
    assert not ok_cred_def_id(
        'Q4zqM7aXqm7gDQkUVLng9h:3:CL:Q4zqM7aXqm7gDQkUVLng9h:2:schema_name:1.0:tag',
        'Xxxxxxxxxxxxxxxxxxxxxx')  # long form, issuer-did
    assert ok_cred_def_id(
        'Q4zqM7aXqm7gDQkUVLng9h:3:CL:Q4zqM7aXqm7gDQkUVLng9h:2:schema_name:1.0:tag'
    )  # long form
    assert not ok_cred_def_id(
        'Q4zqM7aXqm7gDQkUVLng9h:3:CL:Q4zqM7aXqm7gDQkUVLng9h:schema_name:1.0:tag'
    )  # no :2:
    assert not ok_cred_def_id(
        'Q4zqM7aXqm7gDQkUVLng9h:3:CL:QIIIIIIIII7gDQkUVLng9h:schema_name:1.0:tag'
    )  # I not base58
    assert not ok_cred_def_id(
        'Q4zqM7aXqm7gDQkUVLng9h:3:CL:QIIIIIIIII7gDQkUVLng9h:schema_name:v1.0:tag'
    )  # bad version
    assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:4:CL:18:0')
    assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h::CL:18:0')
    assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9I:3:CL:18:tag')
    assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3::18:tag')
    assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:18:tag')
    assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:CL:18z:tag')
    assert ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:CL:18')  # protocol == 1.3
    assert ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:CL:18',
                          'Q4zqM7aXqm7gDQkUVLng9h')
    assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:CL:18',
                              'Xxxxxxxxxxxxxxxxxxxxxx')
    assert ok_cred_def_id(
        rev_reg_id2cred_def_id(
            'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:Q4zqM7aXqm7gDQkUVLng9h:2:schema_name:1.0:tag:CL_ACCUM:1'
        ))
    print('\n\n== 6 == Credential definition identifier checks pass OK')

    assert ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:CL_ACCUM:1'
    )  # protocol >= 1.4
    assert ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:CL_ACCUM:1',
        'LjgpST2rjsoxYegQDRm7EL')
    assert ok_rev_reg_id(  # long form
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:Q4zqM7aXqm7gDQkUVLng9h:2:schema_name:1.0:tag:CL_ACCUM:1'
    )
    assert ok_rev_reg_id(  # long form
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:Q4zqM7aXqm7gDQkUVLng9h:2:schema_name:1.0:tag:CL_ACCUM:1',
        'LjgpST2rjsoxYegQDRm7EL')
    assert not ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:CL_ACCUM:1',
        'Xxxxxxxxxxxxxxxxxxxxxx')
    assert not ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:5:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:CL_ACCUM:1'
    )
    assert not ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:4:CL:20:0:CL_ACCUM:1')
    assert not ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL::CL:20:0:CL_ACCUM:1')
    assert not ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:NOT_CL:20:tag:CL_ACCUM:1'
    )
    assert not ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20z:tag:CL_ACCUM:1'
    )
    assert not ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20::CL_ACCUM:1')
    assert not ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag::1')
    assert not ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:1')
    assert not ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:CL_ACCUM:'
    )
    assert not ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:CL_ACCUM')
    assert ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:CL_ACCUM:1'
    )  # protocol == 1.3
    assert ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:CL_ACCUM:1',
        'LjgpST2rjsoxYegQDRm7EL')
    assert not ok_rev_reg_id(
        'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:CL_ACCUM:1',
        'Xxxxxxxxxxxxxxxxxxxxxx')
    print('\n\n== 7 == Revocation registry identifier checks pass OK')
Beispiel #11
0
    async def _send_rev_reg_def(self, rr_id: str) -> None:
        """
        Move tails file from hopper; deserialize revocation registry definition and initial entry;
        send to ledger and cache revocation registry definition.

        Operation serializes to subdirectory within tails hopper directory; symbolic
        link presence signals completion.

        Raise AbsentRevReg if revocation registry is not ready in hopper, or AbsentTails if
        tails file is not yet linked by its revocation registry identifier.

        :param rr_id: revocation registry identifier
        """

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

        dir_tails_rr_id = self.rrb.dir_tails_top(rr_id)
        dir_target = self.rrb.dir_tails_target(rr_id)

        if not Tails.linked(dir_tails_rr_id, rr_id):
            LOGGER.debug(
                'Issuer._send_rev_reg_def <!< Tails file for rev reg %s not ready in dir %s',
                rr_id, dir_target)
            raise AbsentRevReg(
                'Tails file for rev reg {} not ready in dir {}'.format(
                    rr_id, dir_target))

        file_rr_def = join(dir_target, 'rr_def.json')
        if not isfile(file_rr_def):
            LOGGER.debug(
                'Issuer._send_rev_reg_def <!< Rev reg def file %s not present',
                file_rr_def)
            raise AbsentRevReg(
                'Rev reg def file {} not present'.format(file_rr_def))
        with open(file_rr_def, 'r') as fh_rr_def:
            rr_def_json = fh_rr_def.read()

        file_rr_ent = join(dir_target, 'rr_ent.json')
        if not isfile(file_rr_ent):
            LOGGER.debug(
                'Issuer._send_rev_reg_def <!< Rev reg entry file %s not present',
                file_rr_ent)
            raise AbsentRevReg(
                'Rev reg entry file {} not present'.format(file_rr_ent))
        with open(file_rr_ent, 'r') as fh_rr_ent:
            rr_ent_json = fh_rr_ent.read()

        file_tails = Tails.linked(dir_tails_rr_id, rr_id)
        if not file_tails:
            LOGGER.debug(
                'Issuer._send_rev_reg_def <!< Tails link %s not present in dir %s',
                rr_id, dir_target)
            raise AbsentTails('Tails link {} not present in dir {}'.format(
                rr_id, dir_target))

        if self.rrbx:
            dir_cd_id = join(self.dir_tails, rev_reg_id2cred_def_id(rr_id))
            makedirs(dir_cd_id, exist_ok=True)
            rename(file_tails, join(dir_cd_id, basename(file_tails)))

        with REVO_CACHE.lock:
            rr_def_req_json = await ledger.build_revoc_reg_def_request(
                self.did, rr_def_json)
            await self._sign_submit(rr_def_req_json)
            await self.get_rev_reg_def(rr_id)  # add to cache en passant

        rr_ent_req_json = await ledger.build_revoc_reg_entry_request(
            self.did, rr_id, 'CL_ACCUM', rr_ent_json)
        await self._sign_submit(rr_ent_req_json)

        if self.rrbx:
            Tails.associate(self.dir_tails, rr_id, basename(file_tails))
            rmtree(dir_tails_rr_id)
        else:
            remove(file_rr_def)
            remove(file_rr_ent)

        LOGGER.debug('Issuer._send_rev_reg_def <<<')