Beispiel #1
0
    def build(seed: bytes, path: str, seed_method: SeedMethod = SeedMethod.SEED_METHOD_BIP39,
              password: str = '') -> 'KeyPairSecrets':
        """
        Build a valid key pair secrets.
        :param seed: key seed
        :param path: key path
        :param seed_method: seed method (SEED_METHOD_NONE or SEED_METHOD_BIP39) default=SEED_METHOD_BIP39
        :param password: key password (secrets) (Optional: can be empty string)
        :return: valid key pair secrets

        :raises:
            IdentityValidationError: if invalid key seed
            IdentityValidationError: if invalid key path
        """
        if seed_method == SeedMethod.SEED_METHOD_NONE:
            if len(seed) < MIN_SEED_METHOD_NONE_LEN:
                raise IdentityValidationError(f'Invalid seed length for method \'SEED_METHOD_NONE\', '
                                              f'must be at least {MIN_SEED_METHOD_NONE_LEN} bytes')
        elif seed_method == SeedMethod.SEED_METHOD_BIP39:
            KeyPairSecretsHelper.validate_bip39_seed(seed)
        else:
            raise IdentityValidationError(f'Invalid seed method \'{seed_method}\', '
                                          f'must be in {[m.name for m in SeedMethod]}')
        if not path.startswith(KEY_PAIR_PATH_PREFIX):
            raise IdentityValidationError(f'Invalid key pair path \'{path}\', '
                                          f'must start with {KEY_PAIR_PATH_PREFIX}')

        return KeyPairSecrets(seed, path, seed_method, password)
Beispiel #2
0
    def decode_and_verify_token(token: str, public_base58: str, audience: str):
        """
        Decode a jwt token and verifying it.
        :param token: jwt token
        :param public_base58:  token public base58 key
        :param audience: token audience
        :return: decoded verified token

        :raises:
            IdentityValidationError: if invalid token
            IdentityValidationError: if invalid token signature
            IdentityValidationError: if expired token
        """
        try:
            key = KeysHelper.get_public_ECDSA_from_base58(public_base58)
            return jwt.decode(
                token,
                key,
                audience=audience,
                algorithms=[TOKEN_ALGORITHM],  # type: ignore
                verify=True,
                options={'verify_signature': True})
        except jwt.exceptions.InvalidSignatureError as err:
            raise IdentityValidationError(
                f'Invalid token signature: \'{err}\'') from err
        except jwt.exceptions.ExpiredSignatureError as err:
            raise IdentityValidationError(f'Expired token: \'{err}\'') from err
        except jwt.exceptions.DecodeError as err:
            raise IdentityValidationError(
                f'Can not decode invalid token: \'{err}\'') from err
Beispiel #3
0
    def build(label: Optional[str], comment: Optional[str],
              url: Optional[str]):
        """
        Build register metadata.
        :param label: metadata label
        :param comment: metadata comment
        :param url: metadata url
        :return: valid register metadata

        :raises:
            IdentityValidationError: if invalid label
            IdentityValidationError: if invalid comment
            IdentityValidationError: if invalid url
        """
        if label and len(label) > DOCUMENT_MAX_LABEL_LENGTH:
            raise IdentityValidationError(
                f'Document metadata label it too long, max size: \'{label}\'')
        if comment and len(comment) > DOCUMENT_MAX_COMMENT_LENGTH:
            raise IdentityValidationError(
                f'Document metadata comment it too long, max size: \'{comment}\''
            )
        if url and len(url) > DOCUMENT_MAX_URL_LENGTH:
            raise IdentityValidationError(
                f'Document metadata url it too long, max size: \'{url}\'')
        return Metadata(label, comment, url)
    def verify_authentication(resolver_client: ResolverClient, token: str) -> dict:
        """
        Verify if the authentication token is allowed for authentication.
        :param resolver_client: resolver client interface
        :param token: jwt authentication token
        :return: decoded verified authentication token

        :raises:
            IdentityAuthenticationFailed: if not allowed for authentication
        """
        try:
            unverified_token = JwtTokenHelper.decode_token(token)
            for field in ('iss', 'sub', 'aud', 'iat', 'exp'):
                if field not in unverified_token:
                    raise IdentityValidationError(f'Invalid token, missing {field} field')
            issuer = Issuer.from_string(unverified_token['iss'])
            doc = resolver_client.get_document(issuer.did)
            get_controller_doc = resolver_client.get_document
            issuer_key = RegisterDocumentHelper.get_valid_issuer_key_for_auth(doc, issuer.name, get_controller_doc)
            if not issuer_key:
                raise IdentityInvalidRegisterIssuerError(f'Invalid issuer {issuer}')
            verified_token = JwtTokenHelper.decode_and_verify_token(token, issuer_key.public_key_base58,
                                                                    unverified_token['aud'])

            IdentityAuthValidation.validate_allowed_for_auth(resolver_client, issuer_key.issuer, verified_token['sub'])

            return {'iss': verified_token['iss'],
                    'sub': verified_token['sub'],
                    'aud': verified_token['aud'],
                    'iat': verified_token['iat'],
                    'exp': verified_token['exp']}
        except (IdentityValidationError, IdentityResolverError,
                IdentityInvalidRegisterIssuerError, IdentityNotAllowed) as err:
            raise IdentityAuthenticationFailed('Not authenticated') from err
Beispiel #5
0
    def create_doc_token(issuer: Issuer, audience: str, doc: RegisterDocument,
                         private_key: ec.EllipticCurvePrivateKey) -> str:
        """
        Create a register document jwt token.
        :param issuer: document issuer
        :param audience: token audience
        :param doc: register document
        :param private_key: issuer private key
        :return: encoded jwt token

        :raises:
            IdentityValidationError: if can not encode the token
        """
        try:
            return jwt.encode(
                {
                    'iss': str(issuer),
                    'aud': audience,
                    'doc': doc.to_dict()
                },
                private_key,  # type: ignore
                algorithm=TOKEN_ALGORITHM)
        except (TypeError, ValueError) as err:
            raise IdentityValidationError(
                f'Can not create document token for {issuer}: \'{err}\''
            ) from err
Beispiel #6
0
    def from_challenge_token(resolver_client: ResolverClient,
                             challenge_token: str) -> 'Proof':
        """
        Build proof from challenge token.
        :param resolver_client: resolver client to get the registered documents
        :param challenge_token: jwt challenge token
        :return: valid proof

        :raises:
            IdentityValidationError: if invalid challenge token
        """
        decoded_token = JwtTokenHelper.decode_token(challenge_token)
        iss = decoded_token.get('iss')
        aud = decoded_token.get('aud')
        if not iss or not aud:
            raise IdentityValidationError(
                'Invalid challenge token, missing \'iss\' or \'aud\'')

        issuer = Issuer.from_string(iss)
        doc = resolver_client.get_document(issuer.did)
        get_controller_doc = resolver_client.get_document
        issuer_key = RegisterDocumentHelper.get_valid_issuer_key_for_control_only(
            doc, issuer.name, get_controller_doc)
        if not issuer_key:
            raise IdentityInvalidRegisterIssuerError(
                f'Invalid issuer {issuer}')
        verified_token = JwtTokenHelper.decode_and_verify_token(
            challenge_token, issuer_key.public_key_base58, aud)
        return Proof(issuer_key.issuer, aud.encode('ascii'),
                     verified_token['proof'])
Beispiel #7
0
    def create_auth_token(
            iss: str,
            sub: str,
            aud: str,
            duration: int,
            private_key: ec.EllipticCurvePrivateKey,
            start_offset: int = DEFAULT_TOKEN_START_OFFSET_SECONDS) -> str:
        """
        Create an authentication jwt token.
        :param iss: issuer as string
        :param sub: subject document did
        :param aud: token audience
        :param duration: token duration (seconds)
        :param private_key: issuer private key
        :param start_offset: offset for token valid-from time used (default=DEFAULT_TOKEN_START_OFFSET_SECONDS)
        :return: encoded jwt token

        :raises:
            IdentityValidationError: if invalid duration (<=0)
            IdentityValidationError: if can not encode the token
        """
        now = int(datetime.now().timestamp())
        if duration < 0:
            raise IdentityValidationError(
                f'Can not create auth token with duration={duration}, must be >0'
            )
        try:
            return jwt.encode(
                {
                    'iss': iss,
                    'aud': aud,
                    'sub': sub,
                    'iat': now + start_offset,
                    'exp': now + duration
                },
                private_key,
                algorithm='ES256')  # type: ignore
        except (TypeError, ValueError) as err:
            raise IdentityValidationError(
                f'Can not create auth token for {iss}/{sub}: \'{err}\''
            ) from err
Beispiel #8
0
    def create_seed(length: Optional[int] = 256) -> bytes:
        """
        Create a new seed (secrets).
        :param length: seed length
        :return: seed

        :raises:
            IdentityValidationError: if invalid seed length
        """
        if length not in (128, 256):
            raise IdentityValidationError('length must be 128 or 256')
        return secrets.token_bytes(nbytes=int(length / 8))
Beispiel #9
0
    def mnemonic_bip39_to_seed(mnemonic: str, lang: str = 'english') -> bytes:
        """
        Take mnemonic string and return seed bytes
        :param mnemonic: mnemonic string
        :param lang: language
        :return: seed bytes

        :raises:
            IdentityValidationError: if invalid seed
            IdentityValidationError: if invalid lang
            IdentityDependencyError: if incompatible Mnemonic dependency
        """
        try:
            men = Mnemonic(lang)
            return men.to_entropy(mnemonic)
        except ConfigurationError as err:
            raise IdentityDependencyError(f'Dependency Mnemonic Internal Error: {err}') from err
        except (LookupError, ValueError) as err:
            raise IdentityValidationError(f'{err}') from err
        except OSError as err:
            raise IdentityValidationError(f'Invalid language for mnemonic: {err}') from err
    def validate_issuer_string(issuer: str):
        """
        Validate issuer.
        :param issuer: issuer as string

        :raises:
            IdentityValidationError: if invalid issuer
        """
        result = re.match(ISSUER_PATTERN, issuer)
        if result is None:
            raise IdentityValidationError(
                f'Identifier does not match pattern {issuer} - {ISSUER_PATTERN}'
            )
    def validate_identifier(did: str):
        """
        Validate decentralised identifier.
        :param did: decentralised identifier

        :raises:
            IdentityValidationError: if invalid identifier
        """
        result = re.match(IDENTIFIER_ID_PATTERN, did)
        if result is None:
            raise IdentityValidationError(
                f'Identifier does not match pattern {did} - {IDENTIFIER_ID_PATTERN}'
            )
    def validate_key_name(name: str) -> bool:
        """
        Validate key name.
        :param name: key name

        :raises:
            IdentityValidationError: if invalid key name
        """
        m = re.match(IDENTIFIER_NAME_PATTERN, name)
        if m is None:
            raise IdentityValidationError(
                f'Name is not valid: {name} - {IDENTIFIER_NAME_PATTERN}')
        return True
Beispiel #13
0
    def seed_bip39_to_mnemonic(seed: bytes, lang: str = 'english') -> str:
        """
        Convert a BIP39 seed to mnemonic.
        :param seed: BIP39 seed
        :param lang: a mnemonic language
        :return: a mnemonic

        :raises:
            IdentityValidationError: if invalid seed
            IdentityValidationError: if invalid lang
            IdentityDependencyError: if incompatible Mnemonic dependency
        """
        try:
            men = Mnemonic(lang)
            return men.to_mnemonic(seed)
        except ConfigurationError as err:
            raise IdentityDependencyError(f'Dependency Mnemonic Internal Error: {err}') from err
        except TypeError as err:
            raise IdentityValidationError(f'Invalid seed format for method \'SEED_METHOD_BIP39_LEN\': {err}') from err
        except ValueError as err:
            raise IdentityValidationError(f'Invalid seed length for method \'SEED_METHOD_BIP39_LEN\': {err}') from err
        except OSError as err:
            raise IdentityValidationError(f'Invalid language for mnemonic: {err}') from err
Beispiel #14
0
    def from_string(issuer_string: str) -> 'Issuer':
        """
        Build a valid issuer from issuer string.
        :param issuer_string: issuer string
        :return: valid issuer

        :raises:
            IdentityValidationError: if invalid issuer string
        """
        parts = issuer_string.split(ISSUER_SEPARATOR)
        if len(parts) != 2:
            raise IdentityValidationError(
                f'Invalid issuer string {issuer_string} should be of the form of [did]{ISSUER_SEPARATOR}[name]'
            )
        return Issuer.build(parts[0], f'{ISSUER_SEPARATOR}{parts[1]}')
Beispiel #15
0
    def from_dict(data: dict):
        """
        Build a register public key from dict.
        :param data: register public key as dict
        :return: valid register public key

        :raises:
            IdentityValidationError: if invalid register public key as dict
        """
        try:
            return RegisterPublicKey.build(data['id'], data['publicKeyBase58'],
                                           data.get('revoked', False))

        except (TypeError, KeyError, ValueError) as err:
            raise IdentityValidationError(f'Can not parse invalid register public key: \'{err}\'') from err
Beispiel #16
0
    def from_dict(data: dict):
        """
        Build a register delegation public key from dict.
        :param data: register delegation public key as dict
        :return: valid register delegation key

        :raises:
            IdentityValidationError: if invalid register delegation public key as dict
        """
        try:
            controller = Issuer.from_string(data['controller'])
            proof_type = DelegationProofType(data.get('proofType', DelegationProofType.DID.value))
            return RegisterDelegationProof.build(data['id'], controller, data['proof'],
                                                 data.get('revoked', False),
                                                 proof_type)
        except (TypeError, KeyError, ValueError) as err:
            raise IdentityValidationError(f'Can not parse invalid register delegation proof: \'{err}\'') from err
Beispiel #17
0
    def decode_token(token: str) -> dict:
        """
        Decode a jwt token without verifying it.
        :param token: jwt token
        :return: decoded token

        :raises:
            IdentityValidationError: if invalid token
        """
        try:
            return jwt.decode(token,
                              options={'verify_signature': False},
                              algorithms=[TOKEN_ALGORITHM],
                              verify=False)
        except jwt.exceptions.DecodeError as err:
            raise IdentityValidationError(
                f'Can not decode invalid token: \'{err}\'') from err
Beispiel #18
0
    def get_public_ECDSA_from_base58(
            public_base58: str) -> ec.EllipticCurvePublicKey:
        """
        Get public key ECDSA from public key base58
        :param public_base58: public key base58
        :return: public key ECDSA

        :raises:
            IdentityValidationError: if invalid public base58 key
        """
        try:
            public_bytes = base58.b58decode(public_base58)
            public_ecdsa = ec.EllipticCurvePublicKey.from_encoded_point(
                ec.SECP256K1(), public_bytes)
            return public_ecdsa
        except ValueError as err:
            raise IdentityValidationError(
                f'Can not convert public key base58 to ECDSA: \'{err}\''
            ) from err
Beispiel #19
0
    def get_private_key(key_pair_secrets: KeyPairSecrets) -> ec.EllipticCurvePrivateKey:
        """
        Get private key from key pair secrets
        :param key_pair_secrets: key pair secrets
        :return: private key

        :raise:
            IdentityValidationError: if invalid seed method
            IdentityValidationError: if invalid lang
            IdentityDependencyError: if incompatible Mnemonic dependency
            IdentityDependencyError: if incompatible EllipticCurve dependency
        """
        if key_pair_secrets.seed_method == SeedMethod.SEED_METHOD_NONE:
            result = hmac.new(key_pair_secrets.seed, key_pair_secrets.password.encode(), sha512).digest()
        elif key_pair_secrets.seed_method == SeedMethod.SEED_METHOD_BIP39:
            men = KeyPairSecretsHelper.seed_bip39_to_mnemonic(key_pair_secrets.seed)
            result = Mnemonic.to_seed(men, key_pair_secrets.password)
        else:
            raise IdentityValidationError(f'Invalid seed method \'{key_pair_secrets.seed_method}\', '
                                          f'must be in {[m.name for m in SeedMethod]}')

        private_expo = hmac.new(result, key_pair_secrets.path.encode(), sha256).hexdigest()
        return KeysHelper.get_private_ECDSA(private_expo)
Beispiel #20
0
    def validate_delegation_from_doc(doc_id: str, controller_doc: RegisterDocument,
                                     deleg_proof: RegisterDelegationProof):
        """
        Validate register delegation proof against the deleagtion controller register document.
        :param doc_id: decentralised id of the register document owning the register delegation proof
        :param controller_doc: delegation controller register document
        :param deleg_proof: register delegation proof under validation

        :raises:
            IdentityInvalidDocumentDelegationError: if controller issuer does not belongs to the controller document
                                                    public keys
            IdentityInvalidDocumentDelegationError: if invalid register delegation proof signature
        """
        try:
            controller_issuer = deleg_proof.controller
            public_key = controller_doc.public_keys.get(controller_issuer.name)
            if not public_key:
                raise IdentityValidationError(f'Public key \'{controller_issuer.name}\' not found'
                                              f' on controller doc \'{controller_doc.did}\'')
            DelegationValidation._is_valid_issuer_or_reusable_proof(deleg_proof, public_key, doc_id)
        except IdentityValidationError as err:
            raise IdentityInvalidDocumentDelegationError(f'Invalid delegation for doc \'{doc_id}\''
                                                         f' with controller: \'{deleg_proof.name}\': {err}') from err
Beispiel #21
0
def build_new_challenge_token(proof: Proof,
                              private_key: ec.EllipticCurvePrivateKey) -> str:
    """
    Build a new challenge token from a proof.
    :param proof: proof
    :param private_key: private key
    :return: jwt challenge token

    :raises:
        IdentityValidationError: if can not encode the token

    """
    try:
        return jwt.encode(
            {
                'iss': str(proof.issuer),
                'aud': proof.content.decode('ascii'),
                'proof': proof.signature
            },
            private_key,
            algorithm=TOKEN_ALGORITHM)  # type: ignore
    except (TypeError, ValueError) as err:
        raise IdentityValidationError(
            f'Can not create challenge token for {proof}: \'{err}\'') from err