Exemplo n.º 1
0
    async def fetch(self, limit: int = None) -> Sequence[StorageRecord]:
        """
        Fetch next batch of search results.

        Raise BadSearch if search is closed, WalletState if wallet is closed.

        :param limit: maximum number of records to return (default value Wallet.DEFAULT_CHUNK)
        :return: next batch of records found
        """

        LOGGER.debug('StorageRecordSearch.fetch >>> limit: %s', limit)

        if not self.opened:
            LOGGER.debug('StorageRecordSearch.fetch <!< Storage record search is closed')
            raise BadSearch('Storage record search is closed')

        if not self._wallet.opened:
            LOGGER.debug('StorageRecordSearch.fetch <!< Wallet %s is closed', self._wallet.name)
            raise WalletState('Wallet {} is closed'.format(self._wallet.name))

        records = json.loads(await non_secrets.fetch_wallet_search_next_records(
            self._wallet.handle,
            self.handle,
            limit or Wallet.DEFAULT_CHUNK))['records'] or []  # at exhaustion results['records'] = None

        rv = [StorageRecord(typ=rec['type'], value=rec['value'], tags=rec['tags'], ident=rec['id']) for rec in records]
        LOGGER.debug('StorageRecordSearch.fetch <<< %s', rv)
        return rv
Exemplo n.º 2
0
    async def decrypt(self,
                      ciphertext: bytes,
                      sender: str = None) -> (bytes, str):
        """
        Decrypt ciphertext and optionally authenticate sender.

        Raise BadKey if authentication operation reveals sender key distinct from current
        verification key of owner of input DID.  Raise WalletState if wallet is closed.

        :param ciphertext: ciphertext, as bytes
        :param sender: DID or verification key of sender, None for anonymously encrypted ciphertext
        :return: decrypted bytes and sender verification key (None for anonymous decryption)
        """

        LOGGER.debug('BaseAnchor.decrypt >>> ciphertext: %s, sender: %s',
                     ciphertext, sender)

        if not self.wallet.handle:  # don't risk fetching verkey from ledger only to fail encryption on closed wallet
            LOGGER.debug('BaseAnchor.decrypt <!< Wallet %s is closed',
                         self.name)
            raise WalletState('Wallet {} is closed'.format(self.name))

        from_verkey = None
        if sender:
            from_verkey = await self._verkey_for(sender)
        rv = await self.wallet.decrypt(ciphertext,
                                       True if from_verkey else None,
                                       to_verkey=None,
                                       from_verkey=from_verkey)

        LOGGER.debug('BaseAnchor.decrypt <<< %s', rv)
        return rv
Exemplo n.º 3
0
    async def delete_non_secret(self, typ: str, ident: str) -> None:
        """
        Remove a non-secret record by its type and identifier. Silently return if no such record is present.
        Raise WalletState for closed wallet.

        :param typ: non-secret storage record type
        :param ident: non-secret storage record identifier
        """

        LOGGER.debug('Wallet.delete_non_secret >>> typ: %s, ident: %s', typ, ident)

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

        try:
            await non_secrets.delete_wallet_record(self.handle, typ, ident)
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.WalletItemNotFound:
                LOGGER.info('Wallet.delete_non_secret <!< no record for type %s on identifier %s', typ, ident)
            else:
                LOGGER.debug(
                    'Wallet.delete_non_secret <!< deletion of %s record on identifier %s raised indy error code %s',
                    typ,
                    ident,
                    x_indy.error_code)
                raise

        LOGGER.debug('Wallet.delete_non_secret <<<')
Exemplo n.º 4
0
    async def encrypt(self,
                      message: bytes,
                      authn: bool = False,
                      recip: str = None) -> bytes:
        """
        Encrypt plaintext for owner of DID or verification key, anonymously or via
        authenticated encryption scheme. If given DID, first check wallet and then pool
        for corresponding verification key.

        Raise WalletState if the wallet is closed. Given a recipient DID not in the wallet,
        raise AbsentPool if the instance has no pool or ClosedPool if its pool is closed.

        :param message: plaintext, as bytes
        :param authn: whether to use authenticated encryption scheme
        :param recip: DID or verification key of recipient, None for anchor's own
        :return: ciphertext, as bytes
        """

        LOGGER.debug(
            'BaseAnchor.encrypt >>> message: %s, authn: %s, recip: %s',
            message, authn, recip)

        if not self.wallet.handle:  # don't risk fetching verkey from ledger only to fail encryption on closed wallet
            LOGGER.debug('BaseAnchor.encrypt <!< Wallet %s is closed',
                         self.name)
            raise WalletState('Wallet {} is closed'.format(self.name))

        rv = await self.wallet.encrypt(message, authn, await
                                       self._verkey_for(recip))

        LOGGER.debug('BaseAnchor.auth_encrypt <<< %s', rv)
        return rv
Exemplo n.º 5
0
    async def create_link_secret(self, label: str) -> None:
        """
        Create link secret (a.k.a. master secret) used in proofs by HolderProver, if the
        current link secret does not already correspond to the input link secret label.

        Raise WalletState if wallet is closed, or any other IndyError causing failure
        to set link secret in wallet.

        :param label: label for link secret; indy-sdk uses label to generate link secret
        """

        LOGGER.debug('Wallet.create_link_secret >>> label: %s', label)

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

        try:
            await anoncreds.prover_create_master_secret(self.handle, label)
            await self._write_link_secret_label(label)
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.AnoncredsMasterSecretDuplicateNameError:
                LOGGER.warning(
                    'Wallet %s link secret already current: abstaining from updating label record', self.name)
                await self._write_link_secret_label(label)
            else:
                LOGGER.debug(
                    'Wallet.create_link_secret <!< cannot create link secret for wallet %s, indy error code %s',
                    self.name,
                    x_indy.error_code)
                raise

        LOGGER.debug('Wallet.create_link_secret <<<')
Exemplo n.º 6
0
    async def get_signing_key(self, verkey: str) -> KeyInfo:
        """
        Get signing key pair for input verification key.

        Raise WalletState if wallet is closed, AbsentRecord for no such key pair.

        :param verkey: verification key of key pair
        :return: KeyInfo for key pair
        """

        LOGGER.debug('Wallet.get_signing_key >>> seed: [SEED], verkey: %s', verkey)

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

        try:
            metadata = await crypto.get_key_metadata(self.handle, verkey)
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.WalletItemNotFound:
                LOGGER.debug('Wallet.get_signing_key <!< Verification key %s not in wallet %s', verkey, self.name)
                raise AbsentRecord('Verification key not in wallet {}'.format(self.name))
            LOGGER.debug('Wallet.get_signing_key <!< indy-sdk raised error %s', x_indy.error_code)
            raise

        rv = KeyInfo(verkey, json.loads(metadata) if metadata else {})
        LOGGER.debug('Wallet.get_signing_key <<< %s', rv)
        return rv
Exemplo n.º 7
0
    async def replace_signing_key_metadata(self, verkey: str, metadata: dict) -> KeyInfo:
        """
        Replace the metadata associated with a signing key pair.

        Raise WalletState if wallet is closed, AbsentRecord for no such key pair.

        :param verkey: verification key of key pair
        :param metadata: new metadata to store
        :return: resulting KeyInfo for key pair
        """

        LOGGER.debug('Wallet.replace_signing_key_metadata >>> verkey: %s, metadata: %s', verkey, metadata)

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

        try:
            await crypto.get_key_metadata(self.handle, verkey)
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.WalletItemNotFound:
                LOGGER.debug(
                    'Wallet.replace_signing_key_metadata <!< Verification key %s not in wallet %s',
                    verkey,
                    self.name)
                raise AbsentRecord('Verification key not in wallet {}'.format(self.name))
            LOGGER.debug('Wallet.replace_signing_key_metadata <!< indy-sdk raised error %s', x_indy.error_code)
            raise

        await crypto.set_key_metadata(self.handle, verkey, json.dumps(metadata or {}))

        rv = await self.get_signing_key(verkey)
        LOGGER.debug('Wallet.replace_signing_key_metadata <<< %s', rv)
        return rv
Exemplo n.º 8
0
    async def verify(self, message: bytes, signature: bytes, verkey: str = None) -> bool:
        """
        Verify signature against input signer verification key (default anchor's own).
        Raise AbsentMessage for missing message or signature, or WalletState if wallet is closed.

        :param message: Content to sign, as bytes
        :param signature: signature, as bytes
        :param verkey: signer verification key (default for anchor's own)
        :return: whether signature is valid
        """

        LOGGER.debug('Wallet.verify >>> message: %s, signature: %s, verkey: %s', message, signature, verkey)

        if not message:
            LOGGER.debug('Wallet.verify <!< No message to verify')
            raise AbsentMessage('No message to verify')

        if not signature:
            LOGGER.debug('Wallet.verify <!< No signature to verify')
            raise AbsentMessage('No signature to verify')

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

        rv = await crypto.crypto_verify(verkey or self.verkey, message, signature)

        LOGGER.debug('Wallet.verify <<< %s', rv)
        return rv
Exemplo n.º 9
0
    async def reseed_apply(self) -> None:
        """
        Replace verification key with new verification key from reseed operation.
        Raise WalletState if wallet is closed.
        """

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

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

        await did.replace_keys_apply(self.handle, self.did)
        self.verkey = await did.key_for_local_did(self.handle, self.did)

        await did.set_did_metadata(
            self.handle, self.did,
            json.dumps({
                'anchor': True,
                'since': int(time())
            }))

        LOGGER.info('Wallet %s set seed hash metadata for DID %s', self.name,
                    self.did)
        self._next_seed = None

        LOGGER.debug('Wallet.reseed_apply <<<')
Exemplo n.º 10
0
    async def verify(self,
                     message: bytes,
                     signature: bytes,
                     signer: str = None) -> bool:
        """
        Verify signature with input signer verification key (via lookup by DID first if need be).
        Raise WalletState if wallet is closed.

        :param message: Content to sign, as bytes
        :param signature: signature, as bytes
        :param signer: signer DID or verification key; omit for anchor's own
        :return: whether signature is valid
        """

        LOGGER.debug(
            'BaseAnchor.verify >>> signer: %s, message: %s, signature: %s',
            signer, message, signature)

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

        verkey = None
        if signer:
            verkey = await self._verkey_for(signer)
        rv = await self.wallet.verify(message, signature, verkey)

        LOGGER.debug('BaseAnchor.verify <<< %s', rv)
        return rv
Exemplo n.º 11
0
    async def get_public_did(self) -> str:
        """
        Get current public DID by metadata, None for not yet set.

        :return: DID
        """

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

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

        rv = None
        dids_with_meta = json.loads(await did.list_my_dids_with_meta(self.handle))  # list

        latest = 0
        for did_with_meta in dids_with_meta:
            try:
                meta = json.loads(did_with_meta['metadata']) if did_with_meta['metadata'] else {}
                if not meta.get('anchor', False):
                    continue
                if isinstance(meta, dict) and meta.get('since', -1) > latest:
                    rv = did_with_meta.get('did')
            except json.decoder.JSONDecodeError:
                continue  # it's not an public DID, carry on

        LOGGER.debug('Wallet.get_public_did <<< %s', rv)
        return rv
Exemplo n.º 12
0
    async def sign(self, message: bytes, verkey: str = None) -> bytes:
        """
        Derive signing key and Sign message; return signature. Raise WalletState if wallet is closed.
        Raise AbsentMessage for missing message, or WalletState if wallet is closed.

        :param message: Content to sign, as bytes
        :param verkey: verification key corresponding to private signing key (default anchor's own)
        :return: signature, as bytes
        """

        LOGGER.debug('Wallet.sign >>> message: %s, verkey: %s', message,
                     verkey)

        if not message:
            LOGGER.debug('Wallet.sign <!< No message to sign')
            raise AbsentMessage('No message to sign')

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

        rv = await crypto.crypto_sign(self.handle, verkey or self.verkey,
                                      message)

        LOGGER.debug('Wallet.sign <<< %s', rv)
        return rv
Exemplo n.º 13
0
    async def decrypt(self, ciphertext: bytes, sender: str = None) -> bytes:
        """
        Decrypt ciphertext and optionally authenticate sender.

        Raise BadKey if authentication operation reveals sender key distinct from current
        verification key of owner of input DID.  Raise WalletState if wallet is closed.

        :param ciphertext: ciphertext, as bytes
        :param sender: DID or verification key of sender, None for anonymously encrypted ciphertext
        :return: decrypted bytes
        """

        LOGGER.debug('BaseAnchor.decrypt >>> ciphertext: %s, sender: %s',
                     ciphertext, sender)

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

        verkey = None
        if sender:
            verkey = await self._verkey_for(sender)
        rv = await self.wallet.decrypt(ciphertext, verkey)

        LOGGER.debug('BaseAnchor.decrypt <<< %s', rv)
        return rv
Exemplo n.º 14
0
    async def get_nym(self, target_did: str = None) -> str:
        """
        Get json cryptonym (including current verification key) for input (anchor) DID from ledger.
        Return empty production {} if the ledger has no such cryptonym.

        Raise BadLedgerTxn on failure. Raise WalletState if target DID is default (own DID) value but
        wallet does not have it (neither created nor opened since initialization).

        :param target_did: DID of cryptonym to fetch (default own DID)
        :return: cryptonym json
        """

        LOGGER.debug('BaseAnchor.get_nym >>> target_did: %s', target_did)

        if target_did and not ok_did(target_did):
            LOGGER.debug('BaseAnchor.get_nym <!< Bad DID %s', target_did)
            raise BadIdentifier('Bad DID {}'.format(target_did))

        if not (target_did or self.did):
            LOGGER.debug('BaseAnchor.get_nym <!< Bad wallet state: DID for %s unavailable', self.name)
            raise WalletState('Bad wallet state: DID for {} unavailable'.format(self.name))

        rv = json.dumps({})
        get_nym_req = await ledger.build_get_nym_request(self.did, target_did or self.did)
        resp_json = await self._submit(get_nym_req)

        data_json = (json.loads(resp_json))['result']['data']  # it's double-encoded on the ledger
        if data_json:
            rv = data_json

        LOGGER.debug('BaseAnchor.get_nym <<< %s', rv)
        return rv
Exemplo n.º 15
0
    async def remove(self) -> bool:
        """
        Remove serialized wallet, best effort, if it exists. Return whether wallet absent after operation
        (removal successful or else not present a priori).

        Raise WalletState if wallet is open.

        :return: whether wallet gone from persistent storage
        """

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

        if self.handle:
            LOGGER.debug('Wallet.remove <!< Wallet %s is open', self.name)
            raise WalletState('Wallet {} is open'.format(self.name))

        rv = True
        try:
            LOGGER.info('Attempting to remove wallet: %s', self.name)
            await wallet.delete_wallet(
                json.dumps(self.config),
                json.dumps(self.access_creds))
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.WalletNotFoundError:
                LOGGER.info('Wallet %s not present; abstaining from removal', self.name)
            else:
                LOGGER.info('Failed wallet %s removal; indy-sdk error code %s', self.name, x_indy.error_code)
                rv = False

        LOGGER.debug('Wallet.remove <<< %s', rv)
        return rv
Exemplo n.º 16
0
    async def encrypt(self,
                      message: bytes,
                      authn: bool = False,
                      verkey: str = None) -> bytes:
        """
        Encrypt plaintext for owner of DID, anonymously or via authenticated encryption scheme.
        Raise AbsentMessage for missing message, or WalletState if wallet is closed.

        :param message: plaintext, as bytes
        :param authn: whether to use authenticated encryption scheme
        :param verkey: verification key of recipient, None for anchor's own
        :return: ciphertext, as bytes
        """

        LOGGER.debug('Wallet.encrypt >>> message: %s, authn: %s, verkey: %s',
                     message, authn, verkey)

        if not message:
            LOGGER.debug('Wallet.encrypt <!< No message to encrypt')
            raise AbsentMessage('No message to encrypt')

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

        if authn:
            rv = await crypto.auth_crypt(self.handle, self.verkey, verkey
                                         or self.verkey, message)
        else:
            rv = await crypto.anon_crypt(verkey or self.verkey, message)

        LOGGER.debug('Wallet.auth_encrypt <<< %s', rv)
        return rv
Exemplo n.º 17
0
    async def get_pairwise(self, pairwise_filt: str = None) -> dict:
        """
        Return dict mapping each pairwise DID of interest in wallet to its pairwise info, or,
        for no filter specified, mapping them all. If wallet has no such item, return empty dict.

        :param pairwise_filt: remote DID of interest, or WQL json (default all)
        :return: dict mapping remote DIDs to PairwiseInfo
        """

        LOGGER.debug('Wallet.get_pairwise >>> pairwise_filt: %s',
                     pairwise_filt)

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

        non_secs = await self.get_non_secret(
            TYPE_PAIRWISE, pairwise_filt if ok_did(pairwise_filt)
            or not pairwise_filt else json.loads(pairwise_filt),
            canon_pairwise_wql)
        rv = {k: non_secret2pairwise_info(non_secs[k])
              for k in non_secs}  # touch up tags, mute leading ~

        LOGGER.debug('Wallet.get_pairwise <<< %s', rv)
        return rv
Exemplo n.º 18
0
    async def create_signing_key(self, seed: str = None, metadata: dict = None) -> KeyInfo:
        """
        Create a new signing key pair.

        Raise WalletState if wallet is closed, ExtantRecord if verification key already exists.

        :param seed: optional seed allowing deterministic key creation
        :param metadata: optional metadata to store with key pair
        :return: KeyInfo for new key pair
        """

        LOGGER.debug('Wallet.create_signing_key >>> seed: [SEED], metadata: %s', metadata)

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

        try:
            verkey = await crypto.create_key(self.handle, json.dumps({'seed': seed} if seed else {}))
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.WalletItemAlreadyExists:
                LOGGER.debug('Wallet.create_signing_key <!< Verification key already present in wallet %s', self.name)
                raise ExtantRecord('Verification key already present in wallet {}'.format(self.name))
            LOGGER.debug('Wallet.create_signing_key <!< indy-sdk raised error %s', x_indy.error_code)
            raise

        await crypto.set_key_metadata(self.handle, verkey, json.dumps(metadata or {}))  # coerce None to empty

        rv = KeyInfo(verkey, metadata or {})
        LOGGER.debug('Wallet.create_signing_key <<< %s', rv)
        return rv
Exemplo n.º 19
0
    async def get_local_did_info(self, loc: str) -> DIDInfo:
        """
        Get local DID info by local DID or verification key.
        Raise AbsentRecord for no such local DID.

        :param loc: DID or verification key of interest
        :return: DIDInfo for local DID
        """

        LOGGER.debug('Wallet.get_local_did_info >>> loc: %s', loc)

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

        if ok_did(loc):  # it's a DID
            try:
                did_with_meta = json.loads(await did.get_my_did_with_meta(
                    self.handle, loc))
                rv = DIDInfo(
                    did_with_meta['did'], did_with_meta['verkey'],
                    json.loads(did_with_meta['metadata']) if
                    did_with_meta['metadata'] else {})  # nudge None to empty
            except IndyError as x_indy:
                if x_indy.error_code == ErrorCode.WalletItemNotFound:
                    LOGGER.debug(
                        'Wallet.get_local_did_info <!< DID %s not present in wallet %s',
                        loc, self.name)
                    raise AbsentRecord(
                        'Local DID {} not present in wallet {}'.format(
                            loc, self.name))
                else:
                    LOGGER.debug(
                        'Wallet.get_local_did_info <!< indy-sdk raised error %s',
                        x_indy.error_code)
                    raise
        else:  # it's a verkey
            dids_with_meta = json.loads(await
                                        did.list_my_dids_with_meta(self.handle
                                                                   ))  # list
            for did_with_meta in dids_with_meta:
                if did_with_meta['verkey'] == loc:
                    rv = DIDInfo(
                        did_with_meta['did'], did_with_meta['verkey'],
                        json.loads(did_with_meta['metadata'])
                        if did_with_meta['metadata'] else {})
                    break
            else:
                LOGGER.debug(
                    'Wallet.get_local_did_info <!< Wallet %s has no local DID for verkey %s',
                    self.name, loc)
                raise AbsentRecord(
                    'Wallet {} has no local DID for verkey {}'.format(
                        self.name, loc))

        LOGGER.debug('Wallet.get_local_did_info <<< %s', rv)
        return rv
Exemplo n.º 20
0
    async def revoke_cred(self, rr_id: str, cr_id) -> int:
        """
        Revoke credential that input revocation registry identifier and
        credential revocation identifier specify.

        Return (epoch seconds) time of revocation.

        Raise AbsentTails if no tails file is available for input revocation registry identifier.
        Raise WalletState for closed wallet.
        Raise BadRevocation if issuer cannot revoke specified credential for any other reason
        (e.g., did not issue it, already revoked it).

        :param rr_id: revocation registry identifier
        :param cr_id: credential revocation identifier
        :return: time of revocation, in epoch seconds
        """

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

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

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

        tails_reader_handle = (await Tails(
            self._dir_tails,
            *rev_reg_id2cred_def_id_tag(rr_id)).open()).reader_handle
        try:
            rrdelta_json = await anoncreds.issuer_revoke_credential(
                self.wallet.handle,
                tails_reader_handle,
                rr_id,
                cr_id)
        except IndyError as x_indy:
            LOGGER.debug(
                'Issuer.revoke_cred <!< Could not revoke revoc reg id %s, cred rev id %s: indy error code %s',
                rr_id,
                cr_id,
                x_indy.error_code)
            raise BadRevocation(
                'Could not revoke revoc reg id {}, cred rev id {}: indy error code {}'.format(
                    rr_id,
                    cr_id,
                    x_indy.error_code))

        rr_ent_req_json = await ledger.build_revoc_reg_entry_request(self.did, rr_id, 'CL_ACCUM', rrdelta_json)
        resp_json = await self._sign_submit(rr_ent_req_json)  # raises AbsentPool or ClosedPool if applicable
        resp = json.loads(resp_json)

        rv = self.pool.protocol.txn2epoch(resp)
        LOGGER.debug('Issuer.revoke_cred <<< %s', rv)
        return rv
Exemplo n.º 21
0
    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
Exemplo n.º 22
0
    async def create_local_did(self,
                               seed: str = None,
                               loc_did: str = None,
                               metadata: dict = None) -> DIDInfo:
        """
        Create and store a new local DID for use in pairwise DID relations.

        :param seed: seed from which to create (default random)
        :param loc_did: local DID value (default None to let indy-sdk generate)
        :param metadata: metadata to associate with the local DID (operation always sets 'since' epoch timestamp)
        :return: DIDInfo for new local DID
        """

        LOGGER.debug(
            'Wallet.create_local_did >>> seed: [SEED] loc_did: %s metadata: %s',
            loc_did, metadata)

        cfg = {}
        if seed:
            cfg['seed'] = seed
        if loc_did:
            cfg['did'] = loc_did

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

        try:
            (created_did, verkey) = await did.create_and_store_my_did(
                self.handle, json.dumps(cfg))
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.DidAlreadyExistsError:
                LOGGER.debug(
                    'Wallet.create_local_did <!< DID %s already present in wallet %s',
                    loc_did, self.name)
                raise ExtantRecord(
                    'Local DID {} already present in wallet {}'.format(
                        loc_did, self.name))
            else:
                LOGGER.debug(
                    'Wallet.create_local_did <!< indy-sdk raised error %s',
                    x_indy.error_code)
                raise

        loc_did_metadata = {**(metadata or {}), 'since': int(time())}
        await did.set_did_metadata(self.handle, created_did,
                                   json.dumps(loc_did_metadata))

        rv = DIDInfo(created_did, verkey, loc_did_metadata)

        LOGGER.debug('Wallet.create_local_did <<< %s', rv)
        return rv
Exemplo n.º 23
0
    async def decrypt(
            self,
            ciphertext: bytes,
            authn_check: bool = None,
            to_verkey: str = None,
            from_verkey: str = None) -> (bytes, str):
        """
        Decrypt ciphertext and optionally authenticate sender.

        Raise BadKey if authentication operation checks and reveals sender key distinct from input
        sender verification key.  Raise AbsentMessage for missing ciphertext, or WalletState if
        wallet is closed.

        :param ciphertext: ciphertext, as bytes
        :param authn_check: True to authenticate and check sender verification key,
            False to authenticate and return sender verification key for client to decide fitness, or
            None to use anonymous decryption
        :param to_verkey: recipient verification key, default anchor's own
        :param from_verkey: sender verification key, ignored for anonymous decryption,
            default anchor's own for authenticated decryption
        :return: decrypted bytes and sender verification key (None for anonymous decryption)
        """

        LOGGER.debug(
            'Wallet.decrypt >>> ciphertext: %s, authn_check: %s, to_verkey: %s, from_verkey: %s',
            ciphertext,
            authn_check,
            to_verkey,
            from_verkey)

        if not ciphertext:
            LOGGER.debug('Wallet.decrypt <!< No ciphertext to decrypt')
            raise AbsentMessage('No ciphertext to decrypt')

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

        sender_verkey = None
        if authn_check is None:
            plaintext = await crypto.anon_decrypt(self.handle, to_verkey or self.verkey, ciphertext)
        else:
            (sender_verkey, plaintext) = await crypto.auth_decrypt(self.handle, to_verkey or self.verkey, ciphertext)
            if authn_check and sender_verkey != (from_verkey or self.verkey):
                LOGGER.debug('Wallet.decrypt <!< Authentication revealed unexpected sender key on decryption')
                raise BadKey('Authentication revealed unexpected sender key on decryption')

        rv = (plaintext, sender_verkey)
        LOGGER.debug('Wallet.decrypt <<< %s', rv)
        return rv
Exemplo n.º 24
0
    async def open(self) -> 'Wallet':
        """
        Explicit entry. Open wallet as configured, for later closure via close().
        For use when keeping wallet open across multiple calls.

        Raise any IndyError causing failure to open wallet, WalletState if wallet already open,
        or AbsentWallet on attempt to enter wallet not yet created.

        :return: current object
        """

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

        created = False
        while True:
            try:
                self._handle = await wallet.open_wallet(
                    json.dumps(self.config),
                    json.dumps(self.access_creds))
                LOGGER.info('Opened wallet %s on handle %s', self.name, self.handle)
                break
            except IndyError as x_indy:
                if x_indy.error_code == ErrorCode.WalletNotFoundError:
                    if created:
                        LOGGER.debug('Wallet.open() <!< Wallet %s not found after creation', self.name)
                        raise AbsentWallet('Wallet {} not found after creation'.format(self.name))
                    if self.auto_create:
                        await self.create()
                        continue
                    else:
                        LOGGER.debug('Wallet.open() <!< Wallet %s not found', self.name)
                        raise AbsentWallet('Wallet {} not found'.format(self.name))
                elif x_indy.error_code == ErrorCode.WalletAlreadyOpenedError:
                    LOGGER.debug('Wallet.open() <!< Wallet %s is already open', self.name)
                    raise WalletState('Wallet {} is already open'.format(self.name))
                elif x_indy.error_code == ErrorCode.WalletAccessFailed:
                    LOGGER.debug('Wallet.open() <!< Bad access credentials value for wallet %s', self.name)
                    raise BadAccess('Bad access credentials value for wallet {}'.format(self.name))

                LOGGER.debug('Wallet %s open raised indy error %s', self.name, x_indy.error_code)
                raise

        self.did = await self.get_public_did()
        self.verkey = await did.key_for_local_did(self.handle, self.did) if self.did else None
        LOGGER.info('Wallet %s got verkey %s for existing DID %s', self.name, self.verkey, self.did)

        LOGGER.debug('Wallet.open <<<')
        return self
Exemplo n.º 25
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.
        Raise WalletState for closed wallet.

        :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)

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

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

        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.WalletItemNotFound:
                LOGGER.debug(
                    'Issuer.create_cred_offer <!< did not issue cred definition from wallet %s',
                    self.name)
                raise CorruptWallet(
                    'Cannot create cred offer: did not issue cred definition from wallet {}'
                    .format(self.name))
            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
Exemplo n.º 26
0
    async def reseed_init(self, next_seed: str = None) -> str:
        """
        Begin reseed operation: generate new key. Raise WalletState if wallet is closed.

        :param next_seed: incoming replacement seed (default random)
        :return: new verification key
        """

        LOGGER.debug('Wallet.reseed_init >>> next_seed: [SEED]')

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

        rv = await did.replace_keys_start(self.handle, self.did, json.dumps({'seed': next_seed} if next_seed else {}))
        LOGGER.debug('Wallet.reseed_init <<< %s', rv)
        return rv
Exemplo n.º 27
0
    async def sign(self, message: bytes) -> bytes:
        """
        Sign message; return signature. Raise WalletState if wallet is closed.

        :param message: Content to sign, as bytes
        :return: signature, as bytes
        """

        LOGGER.debug('BaseAnchor.sign >>> message: %s', message)

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

        rv = await self.wallet.sign(message)

        LOGGER.debug('BaseAnchor.sign <<< %s', rv)
        return rv
Exemplo n.º 28
0
    async def reset(self, von_wallet: Wallet, seed: str = None) -> Wallet:
        """
        Close and delete (open) VON anchor wallet and then create, open, and return
        replacement on current link secret.

        Note that this operation effectively destroys private keys for keyed data
        structures such as credential offers or credential definitions.

        Raise WalletState if the wallet is closed.

        :param von_wallet: open wallet
        :param seed: seed to use for new wallet (default random)
        :return: replacement wallet
        """

        LOGGER.debug('WalletManager.reset >>> von_wallet %s', von_wallet)

        if not von_wallet.handle:
            LOGGER.debug('WalletManager.reset <!< Wallet %s is closed',
                         von_wallet.name)
            raise WalletState('Wallet {} is closed'.format(von_wallet.name))

        w_config = von_wallet.config  # wallet under reset, no need to make copy
        w_config['did'] = von_wallet.did
        w_config['seed'] = seed
        w_config[
            'auto_create'] = von_wallet.auto_create  # in case both auto_remove+auto_create set (create every open)
        w_config['auto_remove'] = von_wallet.auto_remove

        label = await von_wallet.get_link_secret_label()
        if label:
            w_config['link_secret_label'] = label

        await von_wallet.close()
        if not von_wallet.auto_remove:
            await self.remove(von_wallet)

        rv = await self.create(w_config, von_wallet.access)
        await rv.open()

        LOGGER.debug('WalletManager.reset <<< %s', rv)
        return rv
Exemplo n.º 29
0
    async def get_link_secret_label(self) -> str:
        """
        Get current link secret label from non-secret storage records; return None for no match.

        :return: latest non-secret storage record for link secret label
        """

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

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

        rv = None
        records = await self.get_non_secret(TYPE_LINK_SECRET_LABEL)
        if records:
            rv = records[str(max(int(k) for k in records))].value  # str to int, max, and back again

        LOGGER.debug('Wallet.get_link_secret_label <<< %s', rv)
        return rv
Exemplo n.º 30
0
    async def decrypt(self, ciphertext: bytes, verkey: str = None) -> bytes:
        """
        Decrypt ciphertext and optionally authenticate sender.

        Raise BadKey if authentication operation reveals sender key distinct from input
        verification key.  Raise AbsentMessage for missing ciphertext, or WalletState if
        wallet is closed.

        :param ciphertext: ciphertext, as bytes
        :param verkey: sender's verification, or None for anonymously encrypted ciphertext
        :return: decrypted bytes
        """

        LOGGER.debug('Wallet.decrypt >>> ciphertext: %s, verkey: %s',
                     ciphertext, verkey)

        if not ciphertext:
            LOGGER.debug('Wallet.decrypt <!< No ciphertext to decrypt')
            raise AbsentMessage('No ciphertext to decrypt')

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

        if verkey:
            (sender_verkey,
             rv) = await crypto.auth_decrypt(self.handle, self.verkey,
                                             ciphertext)
            if sender_verkey != verkey:
                LOGGER.debug(
                    'Wallet.decrypt <!< Authentication revealed unexpected sender key on decryption'
                )
                raise BadKey(
                    'Authentication revealed unexpected sender key on decryption'
                )
        else:
            rv = await crypto.anon_decrypt(self.handle, self.verkey,
                                           ciphertext)

        LOGGER.debug('Wallet.decrypt <<< %s', rv)
        return rv