Exemple #1
0
async def get_tails(request: Request, rr_id: str) -> HTTPResponse:
    """
    Get tails file pertaining to input revocation registry identifier.

    :param request: Sanic request structure
    :param rr_id: rev reg id for revocation registry to which tails file pertains
    :return: HTTP response with tails file, having tails hash as name
    """

    if not ok_rev_reg_id(rr_id):
        LOGGER.error('GET cited bad rev reg id %s', rr_id)
        return response.text('GET cited bad rev reg id {}'.format(rr_id), status=400)

    dir_tails = join(dirname(dirname(realpath(__file__))), 'tails')
    dir_cd_id = Tails.dir(dir_tails, rr_id)

    if not isdir(dir_cd_id):
        LOGGER.error('GET cited rev reg id %s for which tails file dir %s not present', rr_id, dir_cd_id)
        return response.text(
            'GET cited rev reg id {} for which tails file dir {} not present'.format(rr_id, dir_cd_id),
            status=404)

    path_tails = Tails.linked(dir_tails, rr_id)
    if not path_tails:
        LOGGER.error('GET cited rev reg id %s for which tails file not present', rr_id)
        return response.text('GET cited rev reg id {} for which tails file not present'.format(rr_id), status=404)

    LOGGER.info('Fulfilling download GET request for tails file %s associated with rev reg id %s', path_tails, rr_id)
    return await response.file(path_tails, filename=basename(path_tails))
Exemple #2
0
async def get_tails(request, rr_id):
    """
    Get tails file pertaining to input revocation registry identifier.

    :param request: Sanic request structure
    :param rr_id: rev reg id for revocation registry to which tails file pertains
    :return: tails file, with tails hash as name
    """

    if not ok_rev_reg_id(rr_id):
        LOGGER.error('GET cited bad rev reg id %s', rr_id)
        raise InvalidUsage('GET cited bad rev reg id {}'.format(rr_id))

    dir_tails = pjoin(dirname(dirname(abspath(__file__))), 'tails')
    dir_cd_id = Tails.dir(dir_tails, rr_id)

    if not isdir(dir_cd_id):
        LOGGER.error('GET cited rev reg id %s for which tails file dir %s not present', rr_id, dir_cd_id)
        raise NotFound('GET cited rev reg id {} for which tails file dir {} not present'.format(rr_id, dir_cd_id))

    path_tails = Tails.linked(dir_tails, rr_id)
    if not path_tails:
        LOGGER.error('GET cited rev reg id %s for which tails file not present', rr_id)
        raise NotFound('GET cited rev reg id {} for which tails file not present'.format(rr_id))

    LOGGER.info('Fulfilling download GET request for tails file %s associated with rev reg id %s', path_tails, rr_id)
    return await response.file(path_tails, filename=basename(path_tails))
Exemple #3
0
    async def _create_rev_reg(self, rr_id: str, rr_size: int = None) -> None:
        """
        Create revocation registry and new tails file (and association to
        corresponding revocation registry definition via symbolic link) for input
        revocation registry identifier.

        :param rr_id: revocation registry identifier
        :param rr_size: revocation registry size (defaults to 256)
        """

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

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

        rr_size = rr_size or 256
        (cd_id, tag) = rev_reg_id2cred_def_id_tag(rr_id)

        LOGGER.info(
            'Creating revocation registry (capacity %s) for rev reg id %s',
            rr_size, rr_id)
        tails_writer_handle = await blob_storage.open_writer(
            'default',
            json.dumps({
                'base_dir': Tails.dir(self._dir_tails, rr_id),
                'uri_pattern': ''
            }))
        apriori = Tails.unlinked(self._dir_tails)
        (rr_id, rrd_json,
         rre_json) = await anoncreds.issuer_create_and_store_revoc_reg(
             self.wallet.handle, self.did, 'CL_ACCUM', tag, cd_id,
             json.dumps({
                 'max_cred_num': rr_size,
                 'issuance_type': 'ISSUANCE_ON_DEMAND'
             }), tails_writer_handle)
        delta = Tails.unlinked(self._dir_tails) - apriori
        if len(delta) != 1:
            LOGGER.debug(
                'Issuer._create_rev_reg: <!< Could not create tails file for rev reg id: %s',
                rr_id)
            raise CorruptTails(
                'Could not create tails file for rev reg id {}'.format(rr_id))
        tails_hash = basename(delta.pop())
        Tails.associate(self._dir_tails, rr_id, tails_hash)

        with REVO_CACHE.lock:
            rrd_req_json = await ledger.build_revoc_reg_def_request(
                self.did, rrd_json)
            await self._sign_submit(rrd_req_json)
            await self._get_rev_reg_def(rr_id)  # add to cache en passant

        rre_req_json = await ledger.build_revoc_reg_entry_request(
            self.did, rr_id, 'CL_ACCUM', rre_json)
        await self._sign_submit(rre_req_json)

        LOGGER.debug('Issuer._create_rev_reg <<<')
Exemple #4
0
async def delete_tails(request, ident):
    """
    Delete 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: empty text string
    """

    dir_tails = pjoin(dirname(dirname(abspath(__file__))), 'tails')

    if ident == 'all':  # delete everything: 'all' is not valid base58 so it can't be any case below
        rmtree(dir_tails)
        makedirs(dir_tails, exist_ok=True)

    elif ok_rev_reg_id(ident):  # it's a rev reg id
        path_tails = Tails.linked(dir_tails, ident)
        if path_tails and isfile(path_tails):
            unlink(path_tails)
            LOGGER.info('Deleted %s', path_tails)
        path_link = pjoin(Tails.dir(dir_tails, ident), ident)
        if path_link and islink(path_link):
            unlink(path_link)
            LOGGER.info('Deleted %s', path_link)

    elif ok_cred_def_id(ident):  # it's a cred def id (starts with issuer DID)
        dir_cd_id = pjoin(dir_tails, ident)
        if isdir(dir_cd_id):
            rmtree(dir_cd_id)
            LOGGER.info('Deleted %s', dir_cd_id)
        elif exists(dir_cd_id):  # non-dir is squatting on name reserved for dir: it's corrupt; remove it
            unlink(dir_cd_id)
            LOGGER.info('Deleted spurious non-directory %s', dir_cd_id)

    elif ok_did(ident):  # it's an issuer DID
        dirs_cd_id = {dirname(link) for link in Tails.links(dir_tails, ident)}
        for dir_cd_id in dirs_cd_id:
            if ok_cred_def_id(basename(dir_cd_id)):
                if isdir(dir_cd_id):
                    rmtree(dir_cd_id)
                    LOGGER.info('Deleted %s', dir_cd_id)
                elif exists(dir_cd_id):  # non-dir is squatting on name reserved for dir: it's corrupt; remove it
                    unlink(dir_cd_id)
                    LOGGER.info('Deleted spurious non-directory %s', dir_cd_id)

    else:
        LOGGER.error('Token %s must be rev reg id, cred def id, or issuer DID', ident)
        raise InvalidUsage('Token {} must be rev reg id, cred def id, or issuer DID'.format(ident))

    LOGGER.info('Fulfilled DELETE request deleting tails files on filter %s', ident)
    return response.text('')
Exemple #5
0
async def post_tails(request, rr_id):
    """
    Post tails file to server. Multipart file name must be tails hash.

    :param request: Sanic request structure
    :param rr_id: rev reg id for revocation registry to which tails file pertains
    :return: empty text string
    """

    if not ok_rev_reg_id(rr_id):
        LOGGER.error('POST cited bad rev reg id %s', rr_id)
        raise InvalidUsage('POST cited bad rev reg id {}'.format(rr_id))

    # curl uses 'data', python requests uses 'file', there may be others
    req_key = set(k for k in request.files
        if request.files[k]
        and isinstance(request.files[k], list)
        and isinstance(request.files[k][0], SanicReqFile)).pop()
    tails_hash = request.files[req_key][0].name
    if not Tails.ok_hash(tails_hash):
        LOGGER.error('POST attached file named with bad tails file hash %s', tails_hash)
        raise InvalidUsage('POST attached file named with bad tails file hash {}'.format(tails_hash))

    dir_tails = pjoin(dirname(dirname(abspath(__file__))), 'tails')
    dir_cd_id = Tails.dir(dir_tails, rr_id)
    makedirs(dir_cd_id, exist_ok=True)

    if Tails.linked(dir_tails, rr_id):
        LOGGER.error('POST attached tails file %s, already present', rr_id)
        raise Forbidden('POST attached tails file {}, already present'.format(rr_id))

    path_tails_hash = pjoin(dir_cd_id, tails_hash)
    if exists(path_tails_hash):
        LOGGER.error('POST attached tails file %s, already present at %s', rr_id, path_tails_hash)
        raise Forbidden('POST attached tails file {}, already present at {}'.format(rr_id, path_tails_hash))

    with open(path_tails_hash, 'wb') as fh_tails:
        fh_tails.write(request.files[req_key][0].body)

    Tails.associate(dir_tails, rr_id, tails_hash)
    LOGGER.info('Associated link %s to POST tails file attachment saved to %s', rr_id, path_tails_hash)

    return response.text('')
async def sync_prover(dir_tails: str, host: str, port: int,
                      remote_only: set) -> None:
    """
    Synchronize for prover: download any tails files appearing remotely but not locally.

    :param dir_tails: local tails directory
    :param host: tails server host
    :param port: tails server port
    :param remote_only: paths to remote rev reg ids without corresponding local tails files
    """

    if not remote_only:
        return

    for rr_id in remote_only:
        dir_cd_id = Tails.dir(dir_tails, rr_id)
        makedirs(dir_cd_id, exist_ok=True)
        url = 'http://{}:{}/tails/{}'.format(host, port, rr_id)
        try:
            resp = requests.get(url, stream=True)
            if resp.status_code == requests.codes.ok:
                re_tails_hash = re.search('filename="(.+)"',
                                          resp.headers['content-disposition'])
                tails_hash = re_tails_hash.group(
                    1) if re_tails_hash.lastindex > 0 else None

                if tails_hash:
                    logging.info('Downloaded: url %s tails-hash %s', url,
                                 tails_hash)
                    with open(join(dir_cd_id, tails_hash), 'wb') as fh_tails:
                        for chunk in resp.iter_content(chunk_size=1024):
                            if chunk:
                                fh_tails.write(chunk)
                    Tails.associate(dir_tails, rr_id, tails_hash)
                else:
                    logging.error(
                        'Download: url %s, responded with no tails-hash', url)

            else:
                logging.error('Download: url %s, responded with status %s',
                              url, resp.status_code)
        except RequestsConnectionError:
            logging.error('GET connection refused: %s', url)
Exemple #7
0
async def post_tails(request: Request, rr_id: str, epoch: int) -> HTTPResponse:
    """
    Post tails file to server, auth-encrypted from issuer (by DID) to tails server anchor.
    Multipart file name must be tails hash.

    :param request: Sanic request structure
    :param rr_id: revocation registry identifier
    :param epoch: current EPOCH time, must be within configured proximity to current server time
    :return: empty text response
    """

    if not ok_rev_reg_id(rr_id):
        LOGGER.error('POST cited bad rev reg id %s', rr_id)
        return response.text('POST cited bad rev reg id {}'.format(rr_id), status=400)
    did = rr_id.split(':')[0]

    if not await is_current(int(epoch)):
        LOGGER.error('POST epoch %s in too far from current server time', epoch)
        return response.text('POST epoch {} is too far from current server time'.format(epoch), status=400)

    tails_hash = request.files['tails-file'][0].name
    if not Tails.ok_hash(tails_hash):
        LOGGER.error('POST attached file named with bad tails file hash %s', tails_hash)
        return response.text('POST attached file named with bad tails file hash {}'.format(tails_hash), status=400)

    dir_tails = join(dirname(dirname(realpath(__file__))), 'tails')
    dir_cd_id = Tails.dir(dir_tails, rr_id)

    makedirs(dir_cd_id, exist_ok=True)

    if Tails.linked(dir_tails, rr_id):
        LOGGER.error('POST attached tails file %s, already present', rr_id)
        return response.text('POST attached tails file {}, already present'.format(rr_id), status=403)

    path_tails_hash = join(dir_cd_id, tails_hash)
    if exists(path_tails_hash):
        LOGGER.error('POST attached tails file %s, already present at %s', rr_id, path_tails_hash)
        return response.text(
            'POST attached tails file {}, already present at {}'.format(rr_id, path_tails_hash),
            status=403)

    tsan = await MEM_CACHE.get('tsan')
    signature = request.files['signature'][0].body
    epoch_tails = '{}||{}'.format(epoch, request.files['tails-file'][0].body)
    if not tsan.verify(epoch_tails, signature, did):
        LOGGER.error('POST attached file %s failed to verify', tails_hash)
        return response.text('POST attached file {} failed to verify'.format(tails_hash), status=400)

    try:
        rev_reg_def = json.loads(await tsan.get_rev_reg_def(rr_id))
        ledger_hash = rev_reg_def.get('value', {}).get('tailsHash', None)
        if ledger_hash != tails_hash:
            LOGGER.error('POST attached tails file hash %s differs from ledger value %s', tails_hash, ledger_hash)
            return response.text(
                'POST attached tails file hash {} differs from ledger value {}'.format(tails_hash, ledger_hash),
                status=400)
    except AbsentRevReg:
        LOGGER.error('POST revocation registry not present on ledger for %s', rr_id)
        return response.text('POST revocation registry not present on ledger for {}'.format(rr_id), status=400)

    with open(path_tails_hash, 'wb') as fh_tails:
        fh_tails.write(request.files['tails-file'][0].body)

    Tails.associate(dir_tails, rr_id, tails_hash)
    LOGGER.info('Associated link %s to POST tails file attachment saved to %s', rr_id, path_tails_hash)

    return response.text('')
Exemple #8
0
async def delete_tails(request: Request, ident: str, epoch: int) -> HTTPResponse:
    """
    Delete 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
    :param epoch: current EPOCH time, must be within 5 minutes of current server time
    :return: empty text response
    """

    if not await is_current(int(epoch)):
        LOGGER.error('DELETE epoch %s in too far from current server time', epoch)
        return response.text('DELETE epoch {} is too far from current server time'.format(epoch), status=400)

    signature = request.body
    plain = '{}||{}'.format(epoch, ident)

    tsan = await MEM_CACHE.get('tsan')
    if not tsan.verify(plain, signature, tsan.did):
        LOGGER.error('DELETE signature failed to verify')
        return response.text('DELETE signature failed to verify', status=400)

    dir_tails = join(dirname(dirname(realpath(__file__))), 'tails')

    if ident == 'all':  # delete everything -- note that 'all' is not valid base58 so no case below can apply
        if isdir(dir_tails):
            rmtree(dir_tails)
        makedirs(dir_tails, exist_ok=True)

    elif ok_rev_reg_id(ident):  # it's a rev reg id
        path_tails = Tails.linked(dir_tails, ident)
        if path_tails and isfile(path_tails):
            unlink(path_tails)
            LOGGER.info('Deleted %s', path_tails)
        path_link = join(Tails.dir(dir_tails, ident), ident)
        if path_link and islink(path_link):
            unlink(path_link)
            LOGGER.info('Deleted %s', path_link)

    elif ok_cred_def_id(ident):  # it's a cred def id (starts with issuer DID)
        dir_cd_id = join(dir_tails, ident)
        if isdir(dir_cd_id):
            rmtree(dir_cd_id)
            LOGGER.info('Deleted %s', dir_cd_id)
        elif exists(dir_cd_id):  # non-dir is squatting on name reserved for dir: it's corrupt; remove it
            unlink(dir_cd_id)
            LOGGER.info('Deleted spurious non-directory %s', dir_cd_id)

    elif ok_did(ident):  # it's an issuer DID
        dirs_cd_id = {dirname(link) for link in Tails.links(dir_tails, ident)}
        for dir_cd_id in dirs_cd_id:
            if ok_cred_def_id(basename(dir_cd_id)):
                if isdir(dir_cd_id):
                    rmtree(dir_cd_id)
                    LOGGER.info('Deleted %s', dir_cd_id)
                elif exists(dir_cd_id):  # non-dir is squatting on name reserved for dir: it's corrupt; remove it
                    unlink(dir_cd_id)
                    LOGGER.info('Deleted spurious non-directory %s', dir_cd_id)

    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('Fulfilled DELETE request deleting tails files on filter %s', ident)
    return response.text('')